/* packet-nbns.c * Routines for NetBIOS-over-TCP packet disassembly (the name dates back * to when it had only NBNS) * Guy Harris * * $Id: packet-nbns.c,v 1.82 2004/01/06 02:42:50 guy Exp $ * * Ethereal - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include "packet-dns.h" #include "packet-netbios.h" #include "packet-tcp.h" #include "packet-frame.h" #include "prefs.h" static int proto_nbns = -1; static int hf_nbns_flags = -1; static int hf_nbns_flags_response = -1; static int hf_nbns_flags_opcode = -1; static int hf_nbns_flags_authoritative = -1; static int hf_nbns_flags_truncated = -1; static int hf_nbns_flags_recdesired = -1; static int hf_nbns_flags_recavail = -1; static int hf_nbns_flags_broadcast = -1; static int hf_nbns_flags_rcode = -1; static int hf_nbns_transaction_id = -1; static int hf_nbns_count_questions = -1; static int hf_nbns_count_answers = -1; static int hf_nbns_count_auth_rr = -1; static int hf_nbns_count_add_rr = -1; static gint ett_nbns = -1; static gint ett_nbns_qd = -1; static gint ett_nbns_flags = -1; static gint ett_nbns_nb_flags = -1; static gint ett_nbns_name_flags = -1; static gint ett_nbns_rr = -1; static gint ett_nbns_qry = -1; static gint ett_nbns_ans = -1; static int proto_nbdgm = -1; static int hf_nbdgm_type = -1; static int hf_nbdgm_fragment = -1; static int hf_nbdgm_first = -1; static int hf_nbdgm_node_type = -1; static int hf_nbdgm_datagram_id = -1; static int hf_nbdgm_src_ip = -1; static int hf_nbdgm_src_port = -1; static gint ett_nbdgm = -1; static int proto_nbss = -1; static int hf_nbss_type = -1; static int hf_nbss_flags = -1; static gint ett_nbss = -1; static gint ett_nbss_flags = -1; /* desegmentation of NBSS over TCP */ static gboolean nbss_desegment = TRUE; /* See RFC 1001 and 1002 for information on the first three, and see http://www.cifs.com/specs/draft-leach-cifs-v1-spec-01.txt Appendix B, and various messages on the CIFS mailing list such as http://discuss.microsoft.com/SCRIPTS/WA-MSD.EXE?A2=ind9811A&L=cifs&P=R386 for information on the fourth. */ #define UDP_PORT_NBNS 137 #define UDP_PORT_NBDGM 138 #define TCP_PORT_NBSS 139 #define TCP_PORT_CIFS 445 /* Packet structure taken from RFC 1002. See also RFC 1001. * Opcode, flags, and rcode treated as "flags", similarly to DNS, * to make it easier to lift the dissection code from "packet-dns.c". */ /* Offsets of fields in the NBNS header. */ #define NBNS_ID 0 #define NBNS_FLAGS 2 #define NBNS_QUEST 4 #define NBNS_ANS 6 #define NBNS_AUTH 8 #define NBNS_ADD 10 /* Length of NBNS header. */ #define NBNS_HDRLEN 12 /* type values */ #define T_NB 32 /* NetBIOS name service RR */ #define T_NBSTAT 33 /* NetBIOS node status RR */ /* Bit fields in the flags */ #define F_RESPONSE (1<<15) /* packet is response */ #define F_OPCODE (0xF<<11) /* query opcode */ #define OPCODE_SHIFT 11 #define F_AUTHORITATIVE (1<<10) /* response is authoritative */ #define F_TRUNCATED (1<<9) /* response is truncated */ #define F_RECDESIRED (1<<8) /* recursion desired */ #define F_RECAVAIL (1<<7) /* recursion available */ #define F_BROADCAST (1<<4) /* broadcast/multicast packet */ #define F_RCODE (0xF<<0) /* reply code */ static const true_false_string tfs_flags_response = { "Message is a response", "Message is a query" }; static const true_false_string tfs_flags_authoritative = { "Server is an authority for domain", "Server is not an authority for domain" }; static const true_false_string tfs_flags_truncated = { "Message is truncated", "Message is not truncated" }; static const true_false_string tfs_flags_recdesired = { "Do query recursively", "Don't do query recursively" }; static const true_false_string tfs_flags_recavail = { "Server can do recursive queries", "Server can't do recursive queries" }; static const true_false_string tfs_flags_broadcast = { "Broadcast packet", "Not a broadcast packet" }; /* Opcodes */ #define OPCODE_QUERY 0 /* standard query */ #define OPCODE_REGISTRATION 5 /* registration */ #define OPCODE_RELEASE 6 /* release name */ #define OPCODE_WACK 7 /* wait for acknowledgement */ #define OPCODE_REFRESH 8 /* refresh registration */ #define OPCODE_REFRESHALT 9 /* refresh registration (alternate opcode) */ #define OPCODE_MHREGISTRATION 15 /* multi-homed registration */ static const value_string opcode_vals[] = { { OPCODE_QUERY, "Name query" }, { OPCODE_REGISTRATION, "Registration" }, { OPCODE_RELEASE, "Release" }, { OPCODE_WACK, "Wait for acknowledgment" }, { OPCODE_REFRESH, "Refresh" }, { OPCODE_REFRESHALT, "Refresh (alternate opcode)" }, { OPCODE_MHREGISTRATION, "Multi-homed registration" }, { 0, NULL } }; /* Reply codes */ #define RCODE_NOERROR 0 #define RCODE_FMTERROR 1 #define RCODE_SERVFAIL 2 #define RCODE_NAMEERROR 3 #define RCODE_NOTIMPL 4 #define RCODE_REFUSED 5 #define RCODE_ACTIVE 6 #define RCODE_CONFLICT 7 static const value_string rcode_vals[] = { { RCODE_NOERROR, "No error" }, { RCODE_FMTERROR, "Request was invalidly formatted" }, { RCODE_SERVFAIL, "Server failure" }, { RCODE_NAMEERROR, "Requested name does not exist" }, { RCODE_NOTIMPL, "Request is not implemented" }, { RCODE_REFUSED, "Request was refused" }, { RCODE_ACTIVE, "Name is owned by another node" }, { RCODE_CONFLICT, "Name is in conflict" }, { 0, NULL } }; /* Values for the "NB_FLAGS" field of RR data. From RFC 1001 and 1002, * except for NB_FLAGS_ONT_H_NODE, which was discovered by looking at * packet traces. */ #define NB_FLAGS_ONT (3<<(15-2)) /* bits for node type */ #define NB_FLAGS_ONT_B_NODE (0<<(15-2)) /* B-mode node */ #define NB_FLAGS_ONT_P_NODE (1<<(15-2)) /* P-mode node */ #define NB_FLAGS_ONT_M_NODE (2<<(15-2)) /* M-mode node */ #define NB_FLAGS_ONT_H_NODE (3<<(15-2)) /* H-mode node */ #define NB_FLAGS_G (1<<(15-0)) /* group name */ /* Values for the "NAME_FLAGS" field of a NODE_NAME entry in T_NBSTAT * RR data. From RFC 1001 and 1002, except for NAME_FLAGS_ONT_H_NODE, * which was discovered by looking at packet traces. */ #define NAME_FLAGS_PRM (1<<(15-6)) /* name is permanent node name */ #define NAME_FLAGS_ACT (1<<(15-5)) /* name is active */ #define NAME_FLAGS_CNF (1<<(15-4)) /* name is in conflict */ #define NAME_FLAGS_DRG (1<<(15-3)) /* name is being deregistered */ #define NAME_FLAGS_ONT (3<<(15-2)) /* bits for node type */ #define NAME_FLAGS_ONT_B_NODE (0<<(15-2)) /* B-mode node */ #define NAME_FLAGS_ONT_P_NODE (1<<(15-2)) /* P-mode node */ #define NAME_FLAGS_ONT_M_NODE (2<<(15-2)) /* M-mode node */ #define NAME_FLAGS_G (1<<(15-0)) /* group name */ static char * nbns_type_name (int type) { switch (type) { case T_NB: return "NB"; case T_NBSTAT: return "NBSTAT"; } return "unknown"; } #define NBNAME_BUF_LEN 128 static int get_nbns_name(tvbuff_t *tvb, int offset, int nbns_data_offset, char *name_ret, int *name_type_ret) { int name_len; char name[MAXDNAME]; char nbname[NBNAME_BUF_LEN]; char *pname, *pnbname, cname, cnbname; int name_type; name_len = get_dns_name(tvb, offset, nbns_data_offset, name, sizeof(name)); /* OK, now undo the first-level encoding. */ pname = &name[0]; pnbname = &nbname[0]; for (;;) { /* Every two characters of the first level-encoded name * turn into one character in the decoded name. */ cname = *pname; if (cname == '\0') break; /* no more characters */ if (cname == '.') break; /* scope ID follows */ if (cname < 'A' || cname > 'Z') { /* Not legal. */ strcpy(nbname, "Illegal NetBIOS name (character not between A and Z in first-level encoding)"); goto bad; } cname -= 'A'; cnbname = cname << 4; pname++; cname = *pname; if (cname == '\0' || cname == '.') { /* No more characters in the name - but we're in * the middle of a pair. Not legal. */ strcpy(nbname, "Illegal NetBIOS name (odd number of bytes)"); goto bad; } if (cname < 'A' || cname > 'Z') { /* Not legal. */ strcpy(nbname, "Illegal NetBIOS name (character not between A and Z in first-level encoding)"); goto bad; } cname -= 'A'; cnbname |= cname; pname++; /* Do we have room to store the character? */ if (pnbname < &nbname[NETBIOS_NAME_LEN]) { /* Yes - store the character. */ *pnbname = cnbname; } /* We bump the pointer even if it's past the end of the name, so we keep track of how long the name is. */ pnbname++; } /* NetBIOS names are supposed to be exactly 16 bytes long. */ if (pnbname - nbname != NETBIOS_NAME_LEN) { /* It's not. */ sprintf(nbname, "Illegal NetBIOS name (%ld bytes long)", (long)(pnbname - nbname)); goto bad; } /* This one is; make its name printable. */ name_type = process_netbios_name(nbname, name_ret); name_ret += strlen(name_ret); sprintf(name_ret, "<%02x>", name_type); name_ret += 4; if (cname == '.') { /* We have a scope ID, starting at "pname"; append that to * the decoded host name. */ strcpy(name_ret, pname); } if (name_type_ret != NULL) *name_type_ret = name_type; return name_len; bad: if (name_type_ret != NULL) *name_type_ret = -1; strcpy (name_ret, nbname); return name_len; } static int get_nbns_name_type_class(tvbuff_t *tvb, int offset, int nbns_data_offset, char *name_ret, int *name_len_ret, int *name_type_ret, int *type_ret, int *class_ret) { int name_len; int type; int class; name_len = get_nbns_name(tvb, offset, nbns_data_offset, name_ret, name_type_ret); offset += name_len; type = tvb_get_ntohs(tvb, offset); offset += 2; class = tvb_get_ntohs(tvb, offset); *type_ret = type; *class_ret = class; *name_len_ret = name_len; return name_len + 4; } static void add_name_and_type(proto_tree *tree, tvbuff_t *tvb, int offset, int len, char *tag, char *name, int name_type) { if (name_type != -1) { proto_tree_add_text(tree, tvb, offset, len, "%s: %s (%s)", tag, name, netbios_name_type_descr(name_type)); } else { proto_tree_add_text(tree, tvb, offset, len, "%s: %s", tag, name); } } static int dissect_nbns_query(tvbuff_t *tvb, int offset, int nbns_data_offset, column_info *cinfo, proto_tree *nbns_tree) { int len; char name[(NETBIOS_NAME_LEN - 1)*4 + MAXDNAME]; int name_len; int name_type; int type; int class; char *class_name; char *type_name; int data_offset; int data_start; proto_tree *q_tree; proto_item *tq; data_start = data_offset = offset; len = get_nbns_name_type_class(tvb, offset, nbns_data_offset, name, &name_len, &name_type, &type, &class); data_offset += len; type_name = nbns_type_name(type); class_name = dns_class_name(class); if (cinfo != NULL) col_append_fstr(cinfo, COL_INFO, " %s %s", type_name, name); if (nbns_tree != NULL) { tq = proto_tree_add_text(nbns_tree, tvb, offset, len, "%s: type %s, class %s", name, type_name, class_name); q_tree = proto_item_add_subtree(tq, ett_nbns_qd); add_name_and_type(q_tree, tvb, offset, name_len, "Name", name, name_type); offset += name_len; proto_tree_add_text(q_tree, tvb, offset, 2, "Type: %s", type_name); offset += 2; proto_tree_add_text(q_tree, tvb, offset, 2, "Class: %s", class_name); offset += 2; } return data_offset - data_start; } static void nbns_add_nbns_flags(column_info *cinfo, proto_tree *nbns_tree, tvbuff_t *tvb, int offset, gushort flags, int is_wack) { char buf[128+1]; guint16 opcode; proto_tree *field_tree; proto_item *tf; opcode = (guint16) ((flags & F_OPCODE) >> OPCODE_SHIFT); strcpy(buf, val_to_str(opcode, opcode_vals, "Unknown operation")); if (flags & F_RESPONSE && !is_wack) { strcat(buf, " response"); strcat(buf, ", "); strcat(buf, val_to_str(flags & F_RCODE, rcode_vals, "Unknown error")); if ((flags & F_RCODE) && check_col(cinfo, COL_INFO)) col_append_fstr(cinfo, COL_INFO, ", %s", val_to_str(flags & F_RCODE, rcode_vals, "Unknown error")); } tf = proto_tree_add_uint_format(nbns_tree, hf_nbns_flags, tvb, offset, 2, flags, "Flags: 0x%04x (%s)", flags, buf); field_tree = proto_item_add_subtree(tf, ett_nbns_flags); proto_tree_add_item(field_tree, hf_nbns_flags_response, tvb, offset, 2, FALSE); proto_tree_add_item(field_tree, hf_nbns_flags_opcode, tvb, offset, 2, FALSE); if (flags & F_RESPONSE) { proto_tree_add_item(field_tree, hf_nbns_flags_authoritative, tvb, offset, 2, FALSE); } proto_tree_add_item(field_tree, hf_nbns_flags_truncated, tvb, offset, 2, FALSE); proto_tree_add_item(field_tree, hf_nbns_flags_recdesired, tvb, offset, 2, FALSE); if (flags & F_RESPONSE) { proto_tree_add_item(field_tree, hf_nbns_flags_recavail, tvb, offset, 2, FALSE); } proto_tree_add_item(field_tree, hf_nbns_flags_broadcast, tvb, offset, 2, FALSE); if (flags & F_RESPONSE && !is_wack) { proto_tree_add_item(field_tree, hf_nbns_flags_rcode, tvb, offset, 2, FALSE); } } static void nbns_add_nb_flags(proto_tree *rr_tree, tvbuff_t *tvb, int offset, gushort flags) { char buf[128+1]; proto_tree *field_tree; proto_item *tf; static const value_string nb_flags_ont_vals[] = { { NB_FLAGS_ONT_B_NODE, "B-node" }, { NB_FLAGS_ONT_P_NODE, "P-node" }, { NB_FLAGS_ONT_M_NODE, "M-node" }, { NB_FLAGS_ONT_H_NODE, "H-node" }, { 0, NULL } }; strcpy(buf, val_to_str(flags & NB_FLAGS_ONT, nb_flags_ont_vals, "Unknown")); strcat(buf, ", "); if (flags & NB_FLAGS_G) strcat(buf, "group"); else strcat(buf, "unique"); tf = proto_tree_add_text(rr_tree, tvb, offset, 2, "Flags: 0x%x (%s)", flags, buf); field_tree = proto_item_add_subtree(tf, ett_nbns_nb_flags); proto_tree_add_text(field_tree, tvb, offset, 2, "%s", decode_boolean_bitfield(flags, NB_FLAGS_G, 2*8, "Group name", "Unique name")); proto_tree_add_text(field_tree, tvb, offset, 2, "%s", decode_enumerated_bitfield(flags, NB_FLAGS_ONT, 2*8, nb_flags_ont_vals, "%s")); } static void nbns_add_name_flags(proto_tree *rr_tree, tvbuff_t *tvb, int offset, gushort flags) { char buf[128+1]; proto_item *field_tree; proto_item *tf; static const value_string name_flags_ont_vals[] = { { NAME_FLAGS_ONT_B_NODE, "B-node" }, { NAME_FLAGS_ONT_P_NODE, "P-node" }, { NAME_FLAGS_ONT_M_NODE, "M-node" }, { 0, NULL } }; strcpy(buf, val_to_str(flags & NAME_FLAGS_ONT, name_flags_ont_vals, "Unknown")); strcat(buf, ", "); if (flags & NAME_FLAGS_G) strcat(buf, "group"); else strcat(buf, "unique"); if (flags & NAME_FLAGS_DRG) strcat(buf, ", being deregistered"); if (flags & NAME_FLAGS_CNF) strcat(buf, ", in conflict"); if (flags & NAME_FLAGS_ACT) strcat(buf, ", active"); if (flags & NAME_FLAGS_PRM) strcat(buf, ", permanent node name"); tf = proto_tree_add_text(rr_tree, tvb, offset, 2, "Name flags: 0x%x (%s)", flags, buf); field_tree = proto_item_add_subtree(tf, ett_nbns_name_flags); proto_tree_add_text(field_tree, tvb, offset, 2, "%s", decode_boolean_bitfield(flags, NAME_FLAGS_G, 2*8, "Group name", "Unique name")); proto_tree_add_text(field_tree, tvb, offset, 2, "%s", decode_enumerated_bitfield(flags, NAME_FLAGS_ONT, 2*8, name_flags_ont_vals, "%s")); proto_tree_add_text(field_tree, tvb, offset, 2, "%s", decode_boolean_bitfield(flags, NAME_FLAGS_DRG, 2*8, "Name is being deregistered", "Name is not being deregistered")); proto_tree_add_text(field_tree, tvb, offset, 2, "%s", decode_boolean_bitfield(flags, NAME_FLAGS_CNF, 2*8, "Name is in conflict", "Name is not in conflict")); proto_tree_add_text(field_tree, tvb, offset, 2, "%s", decode_boolean_bitfield(flags, NAME_FLAGS_ACT, 2*8, "Name is active", "Name is not active")); proto_tree_add_text(field_tree, tvb, offset, 2, "%s", decode_boolean_bitfield(flags, NAME_FLAGS_PRM, 2*8, "Permanent node name", "Not permanent node name")); } static int dissect_nbns_answer(tvbuff_t *tvb, int offset, int nbns_data_offset, column_info *cinfo, proto_tree *nbns_tree, int opcode) { int len; char name[(NETBIOS_NAME_LEN - 1)*4 + MAXDNAME + 64]; int name_len; int name_type; int type; int class; char *class_name; char *type_name; int data_offset; int cur_offset; int data_start; guint ttl; gushort data_len; gushort flags; proto_tree *rr_tree; proto_item *trr; char name_str[(NETBIOS_NAME_LEN - 1)*4 + 1]; guint num_names; char nbname[16+4+1]; /* 4 for [] */ gushort name_flags; data_start = data_offset = offset; cur_offset = offset; len = get_nbns_name_type_class(tvb, offset, nbns_data_offset, name, &name_len, &name_type, &type, &class); data_offset += len; cur_offset += len; type_name = nbns_type_name(type); class_name = dns_class_name(class); ttl = tvb_get_ntohl(tvb, data_offset); data_offset += 4; cur_offset += 4; data_len = tvb_get_ntohs(tvb, data_offset); data_offset += 2; cur_offset += 2; switch (type) { case T_NB: /* "NB" record */ if (cinfo != NULL) { if (opcode != OPCODE_WACK) { col_append_fstr(cinfo, COL_INFO, " %s %s", type_name, ip_to_str(tvb_get_ptr(tvb, data_offset+2, 4))); } } if (nbns_tree == NULL) break; trr = proto_tree_add_text(nbns_tree, tvb, offset, (data_offset - data_start) + data_len, "%s: type %s, class %s", name, type_name, class_name); strcat(name, " ("); strcat(name, netbios_name_type_descr(name_type)); strcat(name, ")"); rr_tree = add_rr_to_tree(trr, ett_nbns_rr, tvb, offset, name, name_len, type_name, class_name, ttl, data_len); while (data_len > 0) { if (opcode == OPCODE_WACK) { /* WACK response. This doesn't contain the * same type of RR data as other T_NB * responses. */ if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } flags = tvb_get_ntohs(tvb, cur_offset); nbns_add_nbns_flags(cinfo, rr_tree, tvb, cur_offset, flags, 1); cur_offset += 2; data_len -= 2; } else { if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } flags = tvb_get_ntohs(tvb, cur_offset); nbns_add_nb_flags(rr_tree, tvb, cur_offset, flags); cur_offset += 2; data_len -= 2; if (data_len < 4) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 4, "Addr: %s", ip_to_str(tvb_get_ptr(tvb, cur_offset, 4))); cur_offset += 4; data_len -= 4; } } break; case T_NBSTAT: /* "NBSTAT" record */ if (cinfo != NULL) col_append_fstr(cinfo, COL_INFO, " %s", type_name); if (nbns_tree == NULL) break; trr = proto_tree_add_text(nbns_tree, tvb, offset, (data_offset - data_start) + data_len, "%s: type %s, class %s", name, type_name, class_name); rr_tree = add_rr_to_tree(trr, ett_nbns_rr, tvb, offset, name, name_len, type_name, class_name, ttl, data_len); if (data_len < 1) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } num_names = tvb_get_guint8(tvb, cur_offset); proto_tree_add_text(rr_tree, tvb, cur_offset, 1, "Number of names: %u", num_names); cur_offset += 1; while (num_names != 0) { if (data_len < NETBIOS_NAME_LEN) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); goto out; } tvb_memcpy(tvb, (guint8 *)nbname, cur_offset, NETBIOS_NAME_LEN); name_type = process_netbios_name(nbname, name_str); proto_tree_add_text(rr_tree, tvb, cur_offset, NETBIOS_NAME_LEN, "Name: %s<%02x> (%s)", name_str, name_type, netbios_name_type_descr(name_type)); cur_offset += NETBIOS_NAME_LEN; data_len -= NETBIOS_NAME_LEN; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); goto out; } name_flags = tvb_get_ntohs(tvb, cur_offset); nbns_add_name_flags(rr_tree, tvb, cur_offset, name_flags); cur_offset += 2; data_len -= 2; num_names--; } if (data_len < 6) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 6, "Unit ID: %s", ether_to_str(tvb_get_ptr(tvb, cur_offset, 6))); cur_offset += 6; data_len -= 6; if (data_len < 1) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 1, "Jumpers: 0x%x", tvb_get_guint8(tvb, cur_offset)); cur_offset += 1; data_len -= 1; if (data_len < 1) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 1, "Test result: 0x%x", tvb_get_guint8(tvb, cur_offset)); cur_offset += 1; data_len -= 1; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Version number: 0x%x", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Period of statistics: 0x%x", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Number of CRCs: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Number of alignment errors: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Number of collisions: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Number of send aborts: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 4) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 4, "Number of good sends: %u", tvb_get_ntohl(tvb, cur_offset)); cur_offset += 4; data_len -= 4; if (data_len < 4) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 4, "Number of good receives: %u", tvb_get_ntohl(tvb, cur_offset)); cur_offset += 4; data_len -= 4; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Number of retransmits: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Number of no resource conditions: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Number of command blocks: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Number of pending sessions: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Max number of pending sessions: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Max total sessions possible: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "(incomplete entry)"); break; } proto_tree_add_text(rr_tree, tvb, cur_offset, 2, "Session data packet size: %u", tvb_get_ntohs(tvb, cur_offset)); cur_offset += 2; data_len -= 2; out: break; default: if (cinfo != NULL) col_append_fstr(cinfo, COL_INFO, " %s", type_name); if (nbns_tree == NULL) break; trr = proto_tree_add_text(nbns_tree, tvb, offset, (data_offset - data_start) + data_len, "%s: type %s, class %s", name, type_name, class_name); rr_tree = add_rr_to_tree(trr, ett_nbns_rr, tvb, offset, name, name_len, type_name, class_name, ttl, data_len); proto_tree_add_text(rr_tree, tvb, cur_offset, data_len, "Data"); cur_offset += data_len; break; } return cur_offset - data_start; } static int dissect_query_records(tvbuff_t *tvb, int cur_off, int nbns_data_offset, int count, column_info *cinfo, proto_tree *nbns_tree) { int start_off, add_off; proto_tree *qatree = NULL; proto_item *ti = NULL; start_off = cur_off; if (nbns_tree != NULL) { ti = proto_tree_add_text(nbns_tree, tvb, start_off, -1, "Queries"); qatree = proto_item_add_subtree(ti, ett_nbns_qry); } while (count-- > 0) { add_off = dissect_nbns_query(tvb, cur_off, nbns_data_offset, cinfo, qatree); cur_off += add_off; } if (ti != NULL) proto_item_set_len(ti, cur_off - start_off); return cur_off - start_off; } static int dissect_answer_records(tvbuff_t *tvb, int cur_off, int nbns_data_offset, int count, column_info *cinfo, proto_tree *nbns_tree, int opcode, char *name) { int start_off, add_off; proto_tree *qatree = NULL; proto_item *ti = NULL; start_off = cur_off; if (nbns_tree != NULL) { ti = proto_tree_add_text(nbns_tree, tvb, start_off, -1, name); qatree = proto_item_add_subtree(ti, ett_nbns_ans); } while (count-- > 0) { add_off = dissect_nbns_answer(tvb, cur_off, nbns_data_offset, cinfo, qatree, opcode); cur_off += add_off; } if (ti != NULL) proto_item_set_len(ti, cur_off - start_off); return cur_off - start_off; } static void dissect_nbns(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { int offset = 0; int nbns_data_offset; column_info *cinfo; proto_tree *nbns_tree = NULL; proto_item *ti; guint16 id, flags, opcode, rcode, quest, ans, auth, add; int cur_off; nbns_data_offset = offset; if (check_col(pinfo->cinfo, COL_PROTOCOL)) col_set_str(pinfo->cinfo, COL_PROTOCOL, "NBNS"); if (check_col(pinfo->cinfo, COL_INFO)) col_clear(pinfo->cinfo, COL_INFO); /* To do: check for runts, errs, etc. */ id = tvb_get_ntohs(tvb, offset + NBNS_ID); flags = tvb_get_ntohs(tvb, offset + NBNS_FLAGS); opcode = (guint16) ((flags & F_OPCODE) >> OPCODE_SHIFT); rcode = (guint16) (flags & F_RCODE); if (check_col(pinfo->cinfo, COL_INFO)) { col_add_fstr(pinfo->cinfo, COL_INFO, "%s%s", val_to_str(opcode, opcode_vals, "Unknown operation (%u)"), (flags & F_RESPONSE) ? " response" : ""); cinfo = pinfo->cinfo; } else { /* Set "cinfo" to NULL; we pass a NULL "cinfo" to the query and answer dissectors, as a way of saying that they shouldn't add stuff to the COL_INFO column (a call to "check_col(cinfo, COL_INFO)" is more expensive than a check that a pointer isn't NULL). */ cinfo = NULL; } if (tree) { ti = proto_tree_add_item(tree, proto_nbns, tvb, offset, -1, FALSE); nbns_tree = proto_item_add_subtree(ti, ett_nbns); proto_tree_add_uint(nbns_tree, hf_nbns_transaction_id, tvb, offset + NBNS_ID, 2, id); nbns_add_nbns_flags(pinfo->cinfo, nbns_tree, tvb, offset + NBNS_FLAGS, flags, 0); } quest = tvb_get_ntohs(tvb, offset + NBNS_QUEST); if (tree) { proto_tree_add_uint(nbns_tree, hf_nbns_count_questions, tvb, offset + NBNS_QUEST, 2, quest); } ans = tvb_get_ntohs(tvb, offset + NBNS_ANS); if (tree) { proto_tree_add_uint(nbns_tree, hf_nbns_count_answers, tvb, offset + NBNS_ANS, 2, ans); } auth = tvb_get_ntohs(tvb, offset + NBNS_AUTH); if (tree) { proto_tree_add_uint(nbns_tree, hf_nbns_count_auth_rr, tvb, offset + NBNS_AUTH, 2, auth); } add = tvb_get_ntohs(tvb, offset + NBNS_ADD); if (tree) { proto_tree_add_uint(nbns_tree, hf_nbns_count_add_rr, tvb, offset + NBNS_ADD, 2, add); } cur_off = offset + NBNS_HDRLEN; if (quest > 0) { /* If this is a response, don't add information about the queries to the summary, just add information about the answers. */ cur_off += dissect_query_records(tvb, cur_off, nbns_data_offset, quest, (!(flags & F_RESPONSE) ? cinfo : NULL), nbns_tree); } if (ans > 0) { /* If this is a request, don't add information about the answers to the summary, just add information about the queries. */ cur_off += dissect_answer_records(tvb, cur_off, nbns_data_offset, ans, ((flags & F_RESPONSE) ? cinfo : NULL), nbns_tree, opcode, "Answers"); } if (tree) { /* Don't add information about the authoritative name servers, or the additional records, to the summary. */ if (auth > 0) cur_off += dissect_answer_records(tvb, cur_off, nbns_data_offset, auth, NULL, nbns_tree, opcode, "Authoritative nameservers"); if (add > 0) cur_off += dissect_answer_records(tvb, cur_off, nbns_data_offset, add, NULL, nbns_tree, opcode, "Additional records"); } } /* NetBIOS datagram packet, from RFC 1002, page 32 */ struct nbdgm_header { guint8 msg_type; struct { guint8 more; guint8 first; guint8 node_type; } flags; guint16 dgm_id; guint32 src_ip; guint16 src_port; /* For packets with data */ guint16 dgm_length; guint16 pkt_offset; /* For error packets */ guint8 error_code; }; /* * NBDS message types. */ #define NBDS_DIRECT_UNIQUE 0x10 #define NBDS_DIRECT_GROUP 0x11 #define NBDS_BROADCAST 0x12 #define NBDS_ERROR 0x13 #define NBDS_QUERY_REQUEST 0x14 #define NBDS_POS_QUERY_RESPONSE 0x15 #define NBDS_NEG_QUERY_RESPONSE 0x16 static const value_string nbds_msgtype_vals[] = { { NBDS_DIRECT_UNIQUE, "Direct_unique datagram" }, { NBDS_DIRECT_GROUP, "Direct_group datagram" }, { NBDS_BROADCAST, "Broadcast datagram" }, { NBDS_ERROR, "Datagram error" }, { NBDS_QUERY_REQUEST, "Datagram query request" }, { NBDS_POS_QUERY_RESPONSE, "Datagram positive query response" }, { NBDS_NEG_QUERY_RESPONSE, "Datagram negative query response" }, { 0, NULL } }; static const true_false_string yesno = { "Yes", "No" }; static const value_string node_type_vals[] = { { 0, "B node" }, { 1, "P node" }, { 2, "M node" }, { 3, "NBDD" }, { 0, NULL } }; static void dissect_nbdgm(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { int offset = 0; proto_tree *nbdgm_tree = NULL; proto_item *ti = NULL; struct nbdgm_header header; int flags; int message_index; tvbuff_t *next_tvb; static const value_string error_codes[] = { { 0x82, "Destination name not present" }, { 0x83, "Invalid source name format" }, { 0x84, "Invalid destination name format" }, { 0x00, NULL } }; char name[(NETBIOS_NAME_LEN - 1)*4 + MAXDNAME]; int name_type; int len; if (check_col(pinfo->cinfo, COL_PROTOCOL)) col_set_str(pinfo->cinfo, COL_PROTOCOL, "NBDS"); if (check_col(pinfo->cinfo, COL_INFO)) col_clear(pinfo->cinfo, COL_INFO); header.msg_type = tvb_get_guint8(tvb, offset); flags = tvb_get_guint8(tvb, offset+1); header.flags.more = flags & 1; header.flags.first = (flags & 2) >> 1; header.flags.node_type = (flags & 12) >> 2; header.dgm_id = tvb_get_ntohs(tvb, offset+2); tvb_memcpy(tvb, (guint8 *)&header.src_ip, offset+4, 4); header.src_port = tvb_get_ntohs(tvb, offset+8); switch (header.msg_type) { case NBDS_DIRECT_UNIQUE: case NBDS_DIRECT_GROUP: case NBDS_BROADCAST: header.dgm_length = tvb_get_ntohs(tvb, offset+10); header.pkt_offset = tvb_get_ntohs(tvb, offset+12); break; case NBDS_ERROR: header.error_code = tvb_get_guint8(tvb, offset+10); break; } message_index = header.msg_type - 0x0f; if (message_index < 1 || message_index > 8) { message_index = 0; } if (check_col(pinfo->cinfo, COL_INFO)) { col_add_str(pinfo->cinfo, COL_INFO, val_to_str(header.msg_type, nbds_msgtype_vals, "Unknown message type (0x%02X)")); } if (tree) { ti = proto_tree_add_item(tree, proto_nbdgm, tvb, offset, -1, FALSE); nbdgm_tree = proto_item_add_subtree(ti, ett_nbdgm); proto_tree_add_uint(nbdgm_tree, hf_nbdgm_type, tvb, offset, 1, header.msg_type); proto_tree_add_boolean(nbdgm_tree, hf_nbdgm_fragment, tvb, offset+1, 1, header.flags.more); proto_tree_add_boolean(nbdgm_tree, hf_nbdgm_first, tvb, offset+1, 1, header.flags.first); proto_tree_add_uint(nbdgm_tree, hf_nbdgm_node_type, tvb, offset+1, 1, header.flags.node_type); proto_tree_add_uint(nbdgm_tree, hf_nbdgm_datagram_id, tvb, offset+2, 2, header.dgm_id); proto_tree_add_ipv4(nbdgm_tree, hf_nbdgm_src_ip, tvb, offset+4, 4, header.src_ip); proto_tree_add_uint(nbdgm_tree, hf_nbdgm_src_port, tvb, offset+8, 2, header.src_port); } offset += 10; switch (header.msg_type) { case NBDS_DIRECT_UNIQUE: case NBDS_DIRECT_GROUP: case NBDS_BROADCAST: if (tree) { proto_tree_add_text(nbdgm_tree, tvb, offset, 2, "Datagram length: %d bytes", header.dgm_length); proto_tree_add_text(nbdgm_tree, tvb, offset+2, 2, "Packet offset: %d bytes", header.pkt_offset); } offset += 4; /* Source name */ len = get_nbns_name(tvb, offset, offset, name, &name_type); if (tree) { add_name_and_type(nbdgm_tree, tvb, offset, len, "Source name", name, name_type); } offset += len; /* Destination name */ len = get_nbns_name(tvb, offset, offset, name, &name_type); if (tree) { add_name_and_type(nbdgm_tree, tvb, offset, len, "Destination name", name, name_type); } offset += len; /* * Here we can pass the packet off to the next protocol. * Set the length of our top-level tree item to include * only our stuff. * * XXX - take the datagram length into account? */ proto_item_set_len(ti, offset); next_tvb = tvb_new_subset(tvb, offset, -1, -1); dissect_netbios_payload(next_tvb, pinfo, tree); break; case NBDS_ERROR: if (tree) { proto_tree_add_text(nbdgm_tree, tvb, offset, 1, "Error code: %s", val_to_str(header.error_code, error_codes, "Unknown (0x%x)")); } offset += 1; proto_item_set_len(ti, offset); break; case NBDS_QUERY_REQUEST: case NBDS_POS_QUERY_RESPONSE: case NBDS_NEG_QUERY_RESPONSE: /* Destination name */ len = get_nbns_name(tvb, offset, offset, name, &name_type); if (tree) { add_name_and_type(nbdgm_tree, tvb, offset, len, "Destination name", name, name_type); } offset += len; proto_item_set_len(ti, offset); break; } } /* * NetBIOS Session Service message types. */ #define SESSION_MESSAGE 0x00 #define SESSION_REQUEST 0x81 #define POSITIVE_SESSION_RESPONSE 0x82 #define NEGATIVE_SESSION_RESPONSE 0x83 #define RETARGET_SESSION_RESPONSE 0x84 #define SESSION_KEEP_ALIVE 0x85 static const value_string message_types[] = { { SESSION_MESSAGE, "Session message" }, { SESSION_REQUEST, "Session request" }, { POSITIVE_SESSION_RESPONSE, "Positive session response" }, { NEGATIVE_SESSION_RESPONSE, "Negative session response" }, { RETARGET_SESSION_RESPONSE, "Retarget session response" }, { SESSION_KEEP_ALIVE, "Session keep-alive" }, { 0x0, NULL } }; /* * NetBIOS Session Service flags. */ #define NBSS_FLAGS_E 0x1 static const value_string error_codes[] = { { 0x80, "Not listening on called name" }, { 0x81, "Not listening for called name" }, { 0x82, "Called name not present" }, { 0x83, "Called name present, but insufficient resources" }, { 0x8F, "Unspecified error" }, { 0x0, NULL } }; /* * Dissect a single NBSS packet (there may be more than one in a given * TCP segment). * * [ Hmmm, in my experience, I have never seen more than one NBSS in a * single segment, since they mostly contain SMBs which are essentially * a request response type protocol (RJS). ] * * [ However, under heavy load with many requests multiplexed on one * session it is not unusual to see multiple requests in one TCP * segment. Unfortunately, in this case a single session message is * frequently split over multiple segments, which frustrates decoding * (MMM). ] */ static int dissect_nbss_packet(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree, int is_cifs) { proto_tree *nbss_tree = NULL; proto_item *ti = NULL; proto_tree *field_tree; proto_item *tf; guint8 msg_type; guint8 flags; volatile int length; int length_remaining; int len; char name[(NETBIOS_NAME_LEN - 1)*4 + MAXDNAME]; int name_type; gint reported_len; tvbuff_t *next_tvb; const char *saved_proto; /* Desegmentation */ length_remaining = tvb_length_remaining(tvb, offset); /* * Can we do reassembly? */ if (nbss_desegment && pinfo->can_desegment) { /* * Yes - is the NBSS header split across segment boundaries? */ if (length_remaining < 4) { /* * Yes. Tell our caller how many more bytes * we need. */ return -(4 - length_remaining); } } /* * Get the length of the NBSS message. */ if (is_cifs) { flags = 0; length = tvb_get_ntoh24(tvb, offset + 1); } else { flags = tvb_get_guint8(tvb, offset + 1); length = tvb_get_ntohs(tvb, offset + 2); if (flags & NBSS_FLAGS_E) length += 65536; } /* * Can we do reassembly? */ if (nbss_desegment && pinfo->can_desegment) { /* * Yes - is the NBSS message split across segment boundaries? */ if (length_remaining < length + 4) { /* * Yes. Tell our caller how many more bytes * we need. */ return -((length + 4) - length_remaining); } } msg_type = tvb_get_guint8(tvb, offset); if (tree) { ti = proto_tree_add_item(tree, proto_nbss, tvb, offset, length + 4, FALSE); nbss_tree = proto_item_add_subtree(ti, ett_nbss); proto_tree_add_uint_format(nbss_tree, hf_nbss_type, tvb, offset, 1, msg_type, "Message Type: %s", val_to_str(msg_type, message_types, "Unknown (%x)")); } offset += 1; if (is_cifs) { if (tree) { proto_tree_add_text(nbss_tree, tvb, offset, 3, "Length: %u", length); } offset += 3; } else { if (tree) { tf = proto_tree_add_uint(nbss_tree, hf_nbss_flags, tvb, offset, 1, flags); field_tree = proto_item_add_subtree(tf, ett_nbss_flags); proto_tree_add_text(field_tree, tvb, offset, 1, "%s", decode_boolean_bitfield(flags, NBSS_FLAGS_E, 8, "Add 65536 to length", "Add 0 to length")); } offset += 1; if (tree) { proto_tree_add_text(nbss_tree, tvb, offset, 2, "Length: %u", length); } offset += 2; } switch (msg_type) { case SESSION_REQUEST: len = get_nbns_name(tvb, offset, offset, name, &name_type); if (tree) add_name_and_type(nbss_tree, tvb, offset, len, "Called name", name, name_type); offset += len; if (check_col(pinfo->cinfo, COL_INFO)) col_append_fstr(pinfo->cinfo, COL_INFO, ", to %s ", name); len = get_nbns_name(tvb, offset, offset, name, &name_type); if (tree) add_name_and_type(nbss_tree, tvb, offset, len, "Calling name", name, name_type); if (check_col(pinfo->cinfo, COL_INFO)) col_append_fstr(pinfo->cinfo, COL_INFO, "from %s", name); break; case NEGATIVE_SESSION_RESPONSE: if (tree) proto_tree_add_text(nbss_tree, tvb, offset, 1, "Error code: %s", val_to_str(tvb_get_guint8(tvb, offset), error_codes, "Unknown (%x)")); if (check_col(pinfo->cinfo, COL_INFO)) col_append_fstr(pinfo->cinfo, COL_INFO, ", %s", val_to_str(tvb_get_guint8(tvb, offset), error_codes, "Unknown (%x)")); break; case RETARGET_SESSION_RESPONSE: if (tree) proto_tree_add_text(nbss_tree, tvb, offset, 4, "Retarget IP address: %s", ip_to_str(tvb_get_ptr(tvb, offset, 4))); offset += 4; if (tree) proto_tree_add_text(nbss_tree, tvb, offset, 2, "Retarget port: %u", tvb_get_ntohs(tvb, offset)); break; case SESSION_MESSAGE: /* * Here we can pass the message off to the next protocol. * Set the length of our top-level tree item to include * only our stuff. */ proto_item_set_len(ti, offset); len = tvb_length_remaining(tvb, offset); reported_len = tvb_reported_length_remaining(tvb, offset); if (len > length) len = length; if (reported_len > length) reported_len = length; next_tvb = tvb_new_subset(tvb, offset, len, reported_len); /* * Catch the ReportedBoundsError exception; if this * particular message happens to get a ReportedBoundsError * exception, that doesn't mean that we should stop * dissecting NetBIOS messages within this frame or chunk * of reassembled data. * * If it gets a BoundsError, we can stop, as there's nothing * more to see, so we just re-throw it. */ saved_proto = pinfo->current_proto; TRY { dissect_netbios_payload(next_tvb, pinfo, tree); } CATCH(BoundsError) { RETHROW; } CATCH(ReportedBoundsError) { show_reported_bounds_error(tvb, pinfo, tree); pinfo->current_proto = saved_proto; } ENDTRY; break; } return length + 4; } static void dissect_nbss(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { struct tcpinfo *tcpinfo = pinfo->private_data; int offset = 0; int max_data; guint8 msg_type; guint8 flags; guint32 length; int len; gboolean is_cifs; proto_tree *nbss_tree; proto_item *ti; if (check_col(pinfo->cinfo, COL_PROTOCOL)) col_set_str(pinfo->cinfo, COL_PROTOCOL, "NBSS"); if (check_col(pinfo->cinfo, COL_INFO)) col_clear(pinfo->cinfo, COL_INFO); max_data = tvb_length(tvb); msg_type = tvb_get_guint8(tvb, offset); if (pinfo->match_port == TCP_PORT_CIFS) { /* * Windows 2000 CIFS clients can dispense completely * with the NETBIOS encapsulation and directly use CIFS * over TCP. As would be expected, the framing is * identical, except that the length is 24 bits instead * of 17. The only message types used are * SESSION_MESSAGE and SESSION_KEEP_ALIVE. */ is_cifs = TRUE; } else { is_cifs = FALSE; } /* * This might be a continuation of an earlier message. * (Yes, that might be true even if we're doing TCP reassembly, * as the first TCP segment in the capture might start in the * middle of an NBNS message.) */ /* * If this isn't reassembled data, check to see whether it * looks like a continuation of a message. * (If it is reassembled data, it shouldn't be a continuation, * as reassembly should've gathered the continuations together * into a message.) */ if (!tcpinfo->is_reassembled) { if (max_data < 4) { /* * Not enough data for an NBSS header; assume * it's a continuation of a message. * * XXX - if there's not enough data, we should * attempt to reassemble the data, if the first byte * is a valid message type. */ goto continuation; } /* * We have enough data for an NBSS header. * Get the flags and length of the message, * and see if they're sane. */ if (is_cifs) { flags = 0; length = tvb_get_ntoh24(tvb, offset + 1); } else { flags = tvb_get_guint8(tvb, offset + 1); length = tvb_get_ntohs(tvb, offset + 2); if (flags & NBSS_FLAGS_E) length += 65536; } if ((flags & (~NBSS_FLAGS_E)) != 0) { /* * A bogus flag was set; assume it's a continuation. */ goto continuation; } switch (msg_type) { case SESSION_MESSAGE: /* * This is variable-length. * All we know is that it shouldn't be zero. * (XXX - can we get zero-length messages? * Not with SMB, but perhaps other NetBIOS-based * protocols have them.) */ if (length == 0) goto continuation; break; case SESSION_REQUEST: /* * This is variable-length. * The names are DNS-encoded 32-byte values; * we need at least 2 bytes (one for each name; * actually, we should have more for the first * name, as there's no name preceding it so * there should be no compression), and we * shouldn't have more than 128 bytes (actually, * we shouldn't have that many). * * XXX - actually, MacOS X 10.1 (yes, that's * redundant, but that's what Apple calls it, * not MacOS X.1) puts names longer than 16 * characters into session request messages, * so we can have more than 32 bytes of * name value, so we can have more than 128 * bytes of data. */ if (length < 2 || length > 256) goto continuation; break; case POSITIVE_SESSION_RESPONSE: /* * This has no data, so the length must be zero. */ if (length != 0) goto continuation; break; case NEGATIVE_SESSION_RESPONSE: /* * This has 1 byte of data. */ if (length != 1) goto continuation; break; case RETARGET_SESSION_RESPONSE: /* * This has 6 bytes of data. */ if (length != 6) goto continuation; break; case SESSION_KEEP_ALIVE: /* * This has no data, so the length must be zero. */ if (length != 0) goto continuation; break; default: /* * Unknown message type; assume it's a continuation. */ goto continuation; } } if (check_col(pinfo->cinfo, COL_INFO)) { col_add_fstr(pinfo->cinfo, COL_INFO, val_to_str(msg_type, message_types, "Unknown (%02x)")); } while (tvb_reported_length_remaining(tvb, offset) > 0) { len = dissect_nbss_packet(tvb, offset, pinfo, tree, is_cifs); if (len < 0) { /* * We need more data to dissect this, and * desegmentation is enabled. "-len" is the * number of additional bytes of data we need. * * Tell the TCP dissector where the data for this * message starts in the data it handed us, and * how many more bytes we need, and return. */ pinfo->desegment_offset = offset; pinfo->desegment_len = -len; return; } offset += len; } return; continuation: /* * It looks like a continuation. */ if (check_col(pinfo->cinfo, COL_INFO)) col_add_fstr(pinfo->cinfo, COL_INFO, "NBSS Continuation Message"); if (tree) { ti = proto_tree_add_item(tree, proto_nbss, tvb, 0, -1, FALSE); nbss_tree = proto_item_add_subtree(ti, ett_nbss); proto_tree_add_text(nbss_tree, tvb, 0, -1, "Continuation data"); } } void proto_register_nbt(void) { static hf_register_info hf_nbns[] = { { &hf_nbns_flags, { "Flags", "nbns.flags", FT_UINT16, BASE_HEX, NULL, 0x0, "", HFILL }}, { &hf_nbns_flags_response, { "Response", "nbns.flags.response", FT_BOOLEAN, 16, TFS(&tfs_flags_response), F_RESPONSE, "Is the message a response?", HFILL }}, { &hf_nbns_flags_opcode, { "Opcode", "nbns.flags.opcode", FT_UINT16, BASE_DEC, VALS(opcode_vals), F_OPCODE, "Operation code", HFILL }}, { &hf_nbns_flags_authoritative, { "Authoritative", "nbns.flags.authoritative", FT_BOOLEAN, 16, TFS(&tfs_flags_authoritative), F_AUTHORITATIVE, "Is the server is an authority for the domain?", HFILL }}, { &hf_nbns_flags_truncated, { "Truncated", "nbns.flags.truncated", FT_BOOLEAN, 16, TFS(&tfs_flags_truncated), F_TRUNCATED, "Is the message truncated?", HFILL }}, { &hf_nbns_flags_recdesired, { "Recursion desired", "nbns.flags.recdesired", FT_BOOLEAN, 16, TFS(&tfs_flags_recdesired), F_RECDESIRED, "Do query recursively?", HFILL }}, { &hf_nbns_flags_recavail, { "Recursion available", "nbns.flags.recavail", FT_BOOLEAN, 16, TFS(&tfs_flags_recavail), F_RECAVAIL, "Can the server do recursive queries?", HFILL }}, { &hf_nbns_flags_broadcast, { "Broadcast", "nbns.flags.broadcast", FT_BOOLEAN, 16, TFS(&tfs_flags_broadcast), F_BROADCAST, "Is this a broadcast packet?", HFILL }}, { &hf_nbns_flags_rcode, { "Reply code", "nbns.flags.rcode", FT_UINT16, BASE_DEC, VALS(rcode_vals), F_RCODE, "Reply code", HFILL }}, { &hf_nbns_transaction_id, { "Transaction ID", "nbns.id", FT_UINT16, BASE_HEX, NULL, 0x0, "Identification of transaction", HFILL }}, { &hf_nbns_count_questions, { "Questions", "nbns.count.queries", FT_UINT16, BASE_DEC, NULL, 0x0, "Number of queries in packet", HFILL }}, { &hf_nbns_count_answers, { "Answer RRs", "nbns.count.answers", FT_UINT16, BASE_DEC, NULL, 0x0, "Number of answers in packet", HFILL }}, { &hf_nbns_count_auth_rr, { "Authority RRs", "nbns.count.auth_rr", FT_UINT16, BASE_DEC, NULL, 0x0, "Number of authoritative records in packet", HFILL }}, { &hf_nbns_count_add_rr, { "Additional RRs", "nbns.count.add_rr", FT_UINT16, BASE_DEC, NULL, 0x0, "Number of additional records in packet", HFILL }} }; static hf_register_info hf_nbdgm[] = { { &hf_nbdgm_type, { "Message Type", "nbdgm.type", FT_UINT8, BASE_DEC, VALS(nbds_msgtype_vals), 0x0, "NBDGM message type", HFILL }}, { &hf_nbdgm_fragment, { "More fragments follow", "nbdgm.next", FT_BOOLEAN, BASE_NONE, TFS(&yesno), 0x0, "TRUE if more fragments follow", HFILL }}, { &hf_nbdgm_first, { "This is first fragment", "nbdgm.first", FT_BOOLEAN, BASE_NONE, TFS(&yesno), 0x0, "TRUE if first fragment", HFILL }}, { &hf_nbdgm_node_type, { "Node Type", "nbdgm.node_type", FT_UINT8, BASE_DEC, VALS(node_type_vals), 0x0, "Node type", HFILL }}, { &hf_nbdgm_datagram_id, { "Datagram ID", "nbdgm.dgram_id", FT_UINT16, BASE_HEX, NULL, 0x0, "Datagram identifier", HFILL }}, { &hf_nbdgm_src_ip, { "Source IP", "nbdgm.src.ip", FT_IPv4, BASE_NONE, NULL, 0x0, "Source IPv4 address", HFILL }}, { &hf_nbdgm_src_port, { "Source Port", "nbdgm.src.port", FT_UINT16, BASE_DEC, NULL, 0x0, "Source port", HFILL }} }; static hf_register_info hf_nbss[] = { { &hf_nbss_type, { "Message Type", "nbss.type", FT_UINT8, BASE_DEC, NULL, 0x0, "NBSS message type", HFILL }}, { &hf_nbss_flags, { "Flags", "nbss.flags", FT_UINT8, BASE_HEX, NULL, 0x0, "NBSS message flags", HFILL }} }; static gint *ett[] = { &ett_nbns, &ett_nbns_qd, &ett_nbns_flags, &ett_nbns_nb_flags, &ett_nbns_name_flags, &ett_nbns_rr, &ett_nbns_qry, &ett_nbns_ans, &ett_nbdgm, &ett_nbss, &ett_nbss_flags, }; module_t *nbss_module; proto_nbns = proto_register_protocol("NetBIOS Name Service", "NBNS", "nbns"); proto_register_field_array(proto_nbns, hf_nbns, array_length(hf_nbns)); proto_nbdgm = proto_register_protocol("NetBIOS Datagram Service", "NBDS", "nbdgm"); proto_register_field_array(proto_nbdgm, hf_nbdgm, array_length(hf_nbdgm)); proto_nbss = proto_register_protocol("NetBIOS Session Service", "NBSS", "nbss"); proto_register_field_array(proto_nbss, hf_nbss, array_length(hf_nbss)); proto_register_subtree_array(ett, array_length(ett)); nbss_module = prefs_register_protocol(proto_nbss, NULL); prefs_register_bool_preference(nbss_module, "desegment_nbss_commands", "Desegment all NBSS packets spanning multiple TCP segments", "Whether NBSS dissector should desegment all packets spanning multiple TCP segments", &nbss_desegment); } void proto_reg_handoff_nbt(void) { dissector_handle_t nbns_handle, nbdgm_handle, nbss_handle; nbns_handle = create_dissector_handle(dissect_nbns, proto_nbns); dissector_add("udp.port", UDP_PORT_NBNS, nbns_handle); nbdgm_handle = create_dissector_handle(dissect_nbdgm, proto_nbdgm); dissector_add("udp.port", UDP_PORT_NBDGM, nbdgm_handle); nbss_handle = create_dissector_handle(dissect_nbss, proto_nbss); dissector_add("tcp.port", TCP_PORT_NBSS, nbss_handle); dissector_add("tcp.port", TCP_PORT_CIFS, nbss_handle); }