/* packet-nbt.c * Routines for NetBIOS-over-TCP packet disassembly * Guy Harris * * Wireshark - 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include #include #include "packet-dns.h" #include "packet-netbios.h" #include "packet-tcp.h" void proto_register_nbt(void); void proto_reg_handoff_nbt(void); 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 int hf_nbns_name_flags = -1; static int hf_nbns_name_flags_group = -1; static int hf_nbns_name_flags_ont = -1; static int hf_nbns_name_flags_drg = -1; static int hf_nbns_name_flags_cnf = -1; static int hf_nbns_name_flags_act = -1; static int hf_nbns_name_flags_prm = -1; static int hf_nbns_nb_flags = -1; static int hf_nbns_nb_flags_group = -1; static int hf_nbns_nb_flags_ont = -1; static int hf_nbns_name = -1; static int hf_nbns_type = -1; static int hf_nbns_class = -1; /* Generated from convert_proto_tree_add_text.pl */ static int hf_nbns_num_alignment_errors = -1; static int hf_nbns_data = -1; static int hf_nbns_unit_id = -1; static int hf_nbns_num_command_blocks = -1; static int hf_nbns_num_retransmits = -1; static int hf_nbns_period_of_statistics = -1; static int hf_nbns_addr = -1; static int hf_nbns_test_result = -1; static int hf_nbns_num_pending_sessions = -1; static int hf_nbns_num_no_resource_conditions = -1; static int hf_nbns_session_data_packet_size = -1; static int hf_nbns_version_number = -1; static int hf_nbns_max_num_pending_sessions = -1; static int hf_nbns_num_collisions = -1; static int hf_nbns_num_good_sends = -1; static int hf_nbns_num_send_aborts = -1; static int hf_nbns_number_of_names = -1; static int hf_nbns_num_crcs = -1; static int hf_nbns_num_good_receives = -1; static int hf_nbns_max_total_sessions_possible = -1; static int hf_nbns_jumpers = -1; static int hf_nbns_netbios_name = -1; static int hf_nbns_ttl = -1; static int hf_nbns_data_length = -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 expert_field ei_nbns_incomplete_entry = EI_INIT; 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 int hf_nbdgm_datagram_length = -1; static int hf_nbdgm_packet_offset = -1; static int hf_nbdgm_error_code = -1; static int hf_nbdgm_source_name = -1; static int hf_nbdgm_destination_name = -1; static gint ett_nbdgm = -1; static int proto_nbss = -1; static int hf_nbss_type = -1; static int hf_nbss_flags = -1; static int hf_nbss_flags_e = -1; static int hf_nbss_length = -1; static int hf_nbss_cifs_length = -1; static int hf_nbss_error_code = -1; static int hf_nbss_retarget_ip_address = -1; static int hf_nbss_retarget_port = -1; static int hf_nbss_continuation_data = -1; static int hf_nbss_called_name = -1; static int hf_nbss_calling_name = -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" }; static const true_false_string tfs_nbss_flags_e = { "Add 65536 to length", "Add 0 to length" }; /* 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; as I remember, the "NAME_FLAGS" * field doesn't include any special values for H-mode nodes, even * though one can register them (if so, perhaps that was done to * avoid surprising clients that don't know about H-mode nodes). */ #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 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 } }; 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 } }; static const value_string nb_type_name_vals[] = { { T_NB, "NB" }, { T_NBSTAT, "NBSTAT" }, { 0, NULL } }; #define NBNAME_BUF_LEN 128 static void add_rr_to_tree(proto_tree *rr_tree, tvbuff_t *tvb, int offset, const char *name, int namelen, int type, int class_val, guint ttl, gushort data_len) { proto_tree_add_string(rr_tree, hf_nbns_name, tvb, offset+1, namelen-1, name); offset += namelen; proto_tree_add_uint(rr_tree, hf_nbns_type, tvb, offset, 2, type); offset += 2; proto_tree_add_uint(rr_tree, hf_nbns_class, tvb, offset, 2, class_val); offset += 2; proto_tree_add_uint_format_value(rr_tree, hf_nbns_ttl, tvb, offset, 4, ttl, "%s", signed_time_secs_to_str(wmem_packet_scope(), ttl)); offset += 4; proto_tree_add_uint(rr_tree, hf_nbns_data_length, tvb, offset, 2, data_len); } static int get_nbns_name(tvbuff_t *tvb, int offset, int nbns_data_offset, char *name_ret, int name_ret_len, int *name_type_ret) { int name_len; const guchar *name; const guchar *nbname; char *nbname_buf; const guchar *pname; char cname, cnbname; int name_type; char *pname_ret; size_t idx = 0; nbname_buf = (char *)wmem_alloc(wmem_packet_scope(), NBNAME_BUF_LEN); nbname = nbname_buf; /* XXX Fix data len */ name_len = get_dns_name(tvb, offset, 0, nbns_data_offset, &name); /* OK, now undo the first-level encoding. */ pname = &name[0]; pname_ret = name_ret; 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. */ nbname = "Illegal NetBIOS name (1st 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. */ nbname = "Illegal NetBIOS name (odd number of bytes)"; goto bad; } if (cname < 'A' || cname > 'Z') { /* Not legal. */ nbname = "Illegal NetBIOS name (2nd 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 (idx < NETBIOS_NAME_LEN) { /* Yes - store the character. */ nbname_buf[idx++] = cnbname; } } /* NetBIOS names are supposed to be exactly 16 bytes long. */ if (idx != NETBIOS_NAME_LEN) { /* It's not. */ g_snprintf(nbname_buf, NBNAME_BUF_LEN, "Illegal NetBIOS name (%lu bytes long)", (unsigned long)idx); goto bad; } /* This one is; make its name printable. */ name_type = process_netbios_name(nbname, name_ret, name_ret_len); pname_ret += MIN(strlen(name_ret), (size_t) name_ret_len); pname_ret += MIN(name_ret_len-(pname_ret-name_ret), g_snprintf(pname_ret, name_ret_len-(gulong)(pname_ret-name_ret), "<%02x>", name_type)); if (cname == '.') { /* We have a scope ID, starting at "pname"; append that to * the decoded host name. */ g_snprintf(pname_ret, name_ret_len-(gulong)(pname_ret-name_ret), "%s", pname); } if (name_type_ret != NULL) *name_type_ret = name_type; return name_len; bad: if (name_type_ret != NULL) *name_type_ret = -1; /* This is only valid because nbname is always assigned an error string * before jumping to bad: Otherwise nbname wouldn't be \0 terminated */ g_snprintf(pname_ret, name_ret_len-(gulong)(pname_ret-name_ret), "%s", 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 rr_class; name_len = get_nbns_name(tvb, offset, nbns_data_offset, name_ret, *name_len_ret, name_type_ret); offset += name_len; type = tvb_get_ntohs(tvb, offset); offset += 2; rr_class = tvb_get_ntohs(tvb, offset); *type_ret = type; *class_ret = rr_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, int hf_tag, const char *name, int name_type) { if (name_type != -1) { proto_tree_add_string_format_value(tree, hf_tag, tvb, offset, len, name, "%s (%s)", name, netbios_name_type_descr(name_type)); } else { proto_tree_add_string(tree, hf_tag, tvb, offset, len, name); } } #define MAX_NAME_LEN (NETBIOS_NAME_LEN - 1)*4 + MAXDNAME + 64 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; int name_len; int name_type; int type; int dns_class; const char *type_name; int data_offset; int data_start; proto_tree *q_tree; name = (char *)wmem_alloc(wmem_packet_scope(), MAX_NAME_LEN); data_start = data_offset = offset; name_len = MAX_NAME_LEN; len = get_nbns_name_type_class(tvb, offset, nbns_data_offset, name, &name_len, &name_type, &type, &dns_class); data_offset += len; type_name = val_to_str_const(type, nb_type_name_vals, "Unknown"); if (cinfo != NULL) col_append_fstr(cinfo, COL_INFO, " %s %s", type_name, name); if (nbns_tree != NULL) { q_tree = proto_tree_add_subtree_format(nbns_tree, tvb, offset, len, ett_nbns_qd, NULL, "%s: type %s, class %s", name, type_name, val_to_str_const(dns_class, dns_classes, "Unknown")); add_name_and_type(q_tree, tvb, offset, name_len, hf_nbns_name, name, name_type); offset += name_len; proto_tree_add_uint(q_tree, hf_nbns_type, tvb, offset, 2, type); offset += 2; proto_tree_add_uint(q_tree, hf_nbns_class, tvb, offset, 2, dns_class); /*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, int is_wack) { guint16 flag; static const int * req_flags[] = { &hf_nbns_flags_response, &hf_nbns_flags_opcode, &hf_nbns_flags_truncated, &hf_nbns_flags_recdesired, &hf_nbns_flags_broadcast, NULL }; static const int * resp_flags[] = { &hf_nbns_flags_response, &hf_nbns_flags_opcode, &hf_nbns_flags_authoritative, &hf_nbns_flags_truncated, &hf_nbns_flags_recdesired, &hf_nbns_flags_recavail, &hf_nbns_flags_broadcast, &hf_nbns_flags_rcode, NULL }; static const int * resp_wack_flags[] = { &hf_nbns_flags_response, &hf_nbns_flags_opcode, &hf_nbns_flags_authoritative, &hf_nbns_flags_truncated, &hf_nbns_flags_recdesired, &hf_nbns_flags_recavail, &hf_nbns_flags_broadcast, NULL }; flag = tvb_get_ntohs(tvb, offset); if (cinfo) { if (flag & F_RESPONSE && !is_wack) { if ((flag & F_RCODE)) col_append_fstr(cinfo, COL_INFO, ", %s", val_to_str_const(flag & F_RCODE, rcode_vals, "Unknown error")); } } if (!nbns_tree) return; if (flag & F_RESPONSE) { if (!is_wack) { proto_tree_add_bitmask(nbns_tree, tvb, offset, hf_nbns_flags, ett_nbns_flags, resp_flags, ENC_BIG_ENDIAN); } else { proto_tree_add_bitmask(nbns_tree, tvb, offset, hf_nbns_flags, ett_nbns_flags, resp_wack_flags, ENC_BIG_ENDIAN); } } else { proto_tree_add_bitmask(nbns_tree, tvb, offset, hf_nbns_flags, ett_nbns_flags, req_flags, ENC_BIG_ENDIAN); } } static void nbns_add_nb_flags(proto_tree *rr_tree, tvbuff_t *tvb, int offset) { proto_item *tf; gushort flag; static const int * flags[] = { &hf_nbns_nb_flags_group, &hf_nbns_nb_flags_ont, NULL }; tf = proto_tree_add_bitmask(rr_tree, tvb, offset, hf_nbns_nb_flags, ett_nbns_nb_flags, flags, ENC_BIG_ENDIAN); flag = tvb_get_ntohs(tvb, offset); proto_item_append_text(tf, " (%s, %s)", val_to_str_const(flag & NB_FLAGS_ONT, nb_flags_ont_vals, "Unknown"), (flag & NB_FLAGS_G) ? "group" : "unique"); } static void nbns_add_name_flags(proto_tree *rr_tree, tvbuff_t *tvb, int offset) { proto_item *tf; gushort flag; static const int * flags[] = { &hf_nbns_name_flags_group, &hf_nbns_name_flags_ont, &hf_nbns_name_flags_drg, &hf_nbns_name_flags_cnf, &hf_nbns_name_flags_act, &hf_nbns_name_flags_prm, NULL }; if (!rr_tree) return; flag = tvb_get_ntohs(tvb, offset); tf = proto_tree_add_bitmask(rr_tree, tvb, offset, hf_nbns_name_flags, ett_nbns_name_flags, flags, ENC_BIG_ENDIAN); proto_item_append_text(tf, " (%s, %s", val_to_str_const(flag & NAME_FLAGS_ONT, name_flags_ont_vals, "Unknown"), (flag & NAME_FLAGS_G) ? "group" : "unique"); if (flag & NAME_FLAGS_DRG) proto_item_append_text(tf, ", being deregistered"); if (flag & NAME_FLAGS_CNF) proto_item_append_text(tf, ", in conflict"); if (flag & NAME_FLAGS_ACT) proto_item_append_text(tf, ", active"); if (flag & NAME_FLAGS_PRM) proto_item_append_text(tf, ", permanent node name"); proto_item_append_text(tf, ")"); } static int dissect_nbns_answer(tvbuff_t *tvb, packet_info *pinfo, int offset, int nbns_data_offset, column_info *cinfo, proto_tree *nbns_tree, int opcode) { int len; char *name; int name_len; int name_type; int type; int dns_class; const char *class_name; const char *type_name; int cur_offset; guint ttl; gushort data_len; proto_tree *rr_tree = NULL; char *name_str; guint num_names; char *nbname; cur_offset = offset; name = (char *)wmem_alloc(wmem_packet_scope(), MAX_NAME_LEN); name_str = (char *)wmem_alloc(wmem_packet_scope(), MAX_NAME_LEN); nbname = (char *)wmem_alloc(wmem_packet_scope(), 16+4+1); /* 4 for [] */ name_len = MAX_NAME_LEN; len = get_nbns_name_type_class(tvb, offset, nbns_data_offset, name, &name_len, &name_type, &type, &dns_class); cur_offset += len; type_name = val_to_str_const(type, nb_type_name_vals, "Unknown"); class_name = val_to_str_const(dns_class, dns_classes, "Unknown"); ttl = tvb_get_ntohl(tvb, cur_offset); cur_offset += 4; data_len = tvb_get_ntohs(tvb, cur_offset); cur_offset += 2; /* XXX: This code should be simplified */ switch (type) { case T_NB: /* "NB" record */ if (cinfo != NULL) { if (opcode != OPCODE_WACK) { col_append_fstr(cinfo, COL_INFO, " %s %s", type_name, tvb_ip_to_str(tvb, cur_offset+2)); } } if (nbns_tree) { rr_tree = proto_tree_add_subtree_format(nbns_tree, tvb, offset, (cur_offset - offset) + data_len, ett_nbns_rr, NULL, "%s: type %s, class %s", name, type_name, class_name); g_strlcat(name, " (", MAX_NAME_LEN); g_strlcat(name, netbios_name_type_descr(name_type), MAX_NAME_LEN); g_strlcat(name, ")", MAX_NAME_LEN); add_rr_to_tree(rr_tree, tvb, offset, name, name_len, type, dns_class, 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_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } nbns_add_nbns_flags(cinfo, rr_tree, tvb, cur_offset, 1); cur_offset += 2; data_len -= 2; } else { if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } nbns_add_nb_flags(rr_tree, tvb, cur_offset); cur_offset += 2; data_len -= 2; if (data_len < 4) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_addr, tvb, cur_offset, 4, ENC_BIG_ENDIAN); 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) { rr_tree = proto_tree_add_subtree_format(nbns_tree, tvb, offset, (cur_offset - offset) + data_len, ett_nbns_rr, NULL, "%s: type %s, class %s", name, type_name, class_name); add_rr_to_tree(rr_tree, tvb, offset, name, name_len, type, dns_class, ttl, data_len); } if (data_len < 1) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } num_names = tvb_get_guint8(tvb, cur_offset); proto_tree_add_item(rr_tree, hf_nbns_number_of_names, tvb, cur_offset, 1, ENC_BIG_ENDIAN); cur_offset += 1; while (num_names != 0) { if (data_len < NETBIOS_NAME_LEN) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); goto out; } if (rr_tree) { tvb_memcpy(tvb, (guint8 *)nbname, cur_offset, NETBIOS_NAME_LEN); name_type = process_netbios_name(nbname, name_str, name_len); proto_tree_add_string_format_value(rr_tree, hf_nbns_netbios_name, tvb, cur_offset, NETBIOS_NAME_LEN, name_str, "%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_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); goto out; } if (rr_tree) { nbns_add_name_flags(rr_tree, tvb, cur_offset); } cur_offset += 2; data_len -= 2; num_names--; } if (data_len < 6) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_unit_id, tvb, cur_offset, 6, ENC_NA); cur_offset += 6; data_len -= 6; if (data_len < 1) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_jumpers, tvb, cur_offset, 1, ENC_BIG_ENDIAN); cur_offset += 1; data_len -= 1; if (data_len < 1) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_test_result, tvb, cur_offset, 1, ENC_BIG_ENDIAN); cur_offset += 1; data_len -= 1; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_version_number, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_period_of_statistics, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_num_crcs, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_num_alignment_errors, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_num_collisions, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_num_send_aborts, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 4) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_num_good_sends, tvb, cur_offset, 4, ENC_BIG_ENDIAN); cur_offset += 4; data_len -= 4; if (data_len < 4) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_num_good_receives, tvb, cur_offset, 4, ENC_BIG_ENDIAN); cur_offset += 4; data_len -= 4; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_num_retransmits, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_num_no_resource_conditions, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_num_command_blocks, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_num_pending_sessions, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_max_num_pending_sessions, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_max_total_sessions_possible, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; data_len -= 2; if (data_len < 2) { proto_tree_add_expert(rr_tree, pinfo, &ei_nbns_incomplete_entry, tvb, cur_offset, data_len); break; } proto_tree_add_item(rr_tree, hf_nbns_session_data_packet_size, tvb, cur_offset, 2, ENC_BIG_ENDIAN); cur_offset += 2; /*data_len -= 2;*/ out: break; default: if (cinfo != NULL) col_append_fstr(cinfo, COL_INFO, " %s", type_name); if (nbns_tree) { rr_tree = proto_tree_add_subtree_format(nbns_tree, tvb, offset, (cur_offset - offset) + data_len, ett_nbns_rr, NULL, "%s: type %s, class %s", name, type_name, class_name); add_rr_to_tree(rr_tree, tvb, offset, name, name_len, type, dns_class, ttl, data_len); proto_tree_add_item(rr_tree, hf_nbns_data, tvb, cur_offset, data_len, ENC_NA); } cur_offset += data_len; break; } return cur_offset - offset; } 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; proto_item *ti; start_off = cur_off; qatree = proto_tree_add_subtree(nbns_tree, tvb, start_off, -1, ett_nbns_qry, &ti, "Queries"); while (count-- > 0) { add_off = dissect_nbns_query(tvb, cur_off, nbns_data_offset, cinfo, qatree); cur_off += add_off; } proto_item_set_len(ti, cur_off - start_off); return cur_off - start_off; } static int dissect_answer_records(tvbuff_t *tvb, packet_info *pinfo, int cur_off, int nbns_data_offset, int count, column_info *cinfo, proto_tree *nbns_tree, int opcode, const char *name) { int start_off, add_off; proto_tree *qatree; proto_item *ti; start_off = cur_off; qatree = proto_tree_add_subtree(nbns_tree, tvb, start_off, -1, ett_nbns_ans, &ti, name); while (count-- > 0) { add_off = dissect_nbns_answer(tvb, pinfo, cur_off, nbns_data_offset, cinfo, qatree, opcode); cur_off += add_off; } proto_item_set_len(ti, cur_off - start_off); return cur_off - start_off; } static int dissect_nbns(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) { int offset = 0; int nbns_data_offset; proto_tree *nbns_tree = NULL; proto_item *ti; guint16 id, flags, opcode, quest, ans, auth, add; int cur_off; nbns_data_offset = offset; col_set_str(pinfo->cinfo, COL_PROTOCOL, "NBNS"); 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); col_add_fstr(pinfo->cinfo, COL_INFO, "%s%s", val_to_str(opcode, opcode_vals, "Unknown operation (%u)"), (flags & F_RESPONSE) ? " response" : ""); if (tree) { ti = proto_tree_add_item(tree, proto_nbns, tvb, offset, -1, ENC_NA); 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, 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) ? pinfo->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, pinfo, cur_off, nbns_data_offset, ans, ((flags & F_RESPONSE) ? pinfo->cinfo : NULL), nbns_tree, opcode, "Answers"); } /* 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, pinfo, cur_off, nbns_data_offset, auth, NULL, nbns_tree, opcode, "Authoritative nameservers"); if (add > 0) /*cur_off += */dissect_answer_records(tvb, pinfo, cur_off, nbns_data_offset, add, NULL, nbns_tree, opcode, "Additional records"); return tvb_captured_length(tvb); } static heur_dissector_list_t netbios_heur_subdissector_list; static void dissect_netbios_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { heur_dtbl_entry_t *hdtbl_entry; /* * Try the heuristic dissectors for NetBIOS; if none of them * accept the packet, dissect it as data. */ if (!dissector_try_heuristic(netbios_heur_subdissector_list, tvb, pinfo, tree, &hdtbl_entry, NULL)) call_data_dissector(tvb, pinfo, tree); } /* 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 value_string node_type_vals[] = { { 0, "B node" }, { 1, "P node" }, { 2, "M node" }, { 3, "NBDD" }, { 0, NULL } }; static const value_string nbds_error_codes[] = { { 0x82, "Destination name not present" }, { 0x83, "Invalid source name format" }, { 0x84, "Invalid destination name format" }, { 0x00, NULL } }; static int dissect_nbdgm(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) { int offset = 0; proto_tree *nbdgm_tree = NULL; proto_item *ti = NULL; struct nbdgm_header header; int flags; tvbuff_t *next_tvb; char *name; int name_type; int len; col_set_str(pinfo->cinfo, COL_PROTOCOL, "NBDS"); col_clear(pinfo->cinfo, COL_INFO); if (tree) { ti = proto_tree_add_item(tree, proto_nbdgm, tvb, offset, -1, ENC_NA); nbdgm_tree = proto_item_add_subtree(ti, ett_nbdgm); } header.msg_type = tvb_get_guint8(tvb, offset); if (tree) { proto_tree_add_uint(nbdgm_tree, hf_nbdgm_type, tvb, offset, 1, header.msg_type); } col_add_str(pinfo->cinfo, COL_INFO, val_to_str(header.msg_type, nbds_msgtype_vals, "Unknown message type (0x%02X)")); 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; if (tree) { 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); } header.dgm_id = tvb_get_ntohs(tvb, offset+2); if (tree) { proto_tree_add_uint(nbdgm_tree, hf_nbdgm_datagram_id, tvb, offset+2, 2, header.dgm_id); } header.src_ip = tvb_get_ipv4( tvb, offset+4); if (tree) { proto_tree_add_ipv4(nbdgm_tree, hf_nbdgm_src_ip, tvb, offset+4, 4, header.src_ip); } header.src_port = tvb_get_ntohs(tvb, offset+8); if (tree) { 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) { header.dgm_length = tvb_get_ntohs(tvb, offset); proto_tree_add_uint_format_value(nbdgm_tree, hf_nbdgm_datagram_length, tvb, offset, 2, header.dgm_length, "%u bytes", header.dgm_length); } offset += 2; if (tree) { header.pkt_offset = tvb_get_ntohs(tvb, offset); proto_tree_add_uint_format_value(nbdgm_tree, hf_nbdgm_packet_offset, tvb, offset, 2, header.pkt_offset, "%u bytes", header.pkt_offset); } offset += 2; name = (char *)wmem_alloc(wmem_packet_scope(), MAX_NAME_LEN); /* Source name */ len = get_nbns_name(tvb, offset, offset, name, MAX_NAME_LEN, &name_type); if (tree) { add_name_and_type(nbdgm_tree, tvb, offset, len, hf_nbdgm_source_name, name, name_type); } offset += len; /* Destination name */ len = get_nbns_name(tvb, offset, offset, name, MAX_NAME_LEN, &name_type); if (tree) { add_name_and_type(nbdgm_tree, tvb, offset, len, hf_nbdgm_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, including * doing datagram reassembly? */ if (ti != NULL) proto_item_set_len(ti, offset); next_tvb = tvb_new_subset_remaining(tvb, offset); dissect_netbios_payload(next_tvb, pinfo, tree); break; case NBDS_ERROR: if (tree) { proto_tree_add_item(nbdgm_tree, hf_nbdgm_error_code, tvb, offset, 1, ENC_BIG_ENDIAN); } offset += 1; if (ti != NULL) proto_item_set_len(ti, offset); break; case NBDS_QUERY_REQUEST: case NBDS_POS_QUERY_RESPONSE: case NBDS_NEG_QUERY_RESPONSE: name = (char *)wmem_alloc(wmem_packet_scope(), MAX_NAME_LEN); /* Destination name */ len = get_nbns_name(tvb, offset, offset, name, MAX_NAME_LEN, &name_type); if (tree) { add_name_and_type(nbdgm_tree, tvb, offset, len, hf_nbdgm_destination_name, name, name_type); } offset += len; if (ti != NULL) proto_item_set_len(ti, offset); break; } return tvb_captured_length(tvb); } /* * NetBIOS Session Service message types (RFC 1002). */ #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 nbss_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 void dissect_nbss_packet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int is_cifs) { int offset = 0; proto_tree *nbss_tree = NULL; proto_item *ti = NULL; guint8 msg_type; guint8 flags; guint32 length; int len; char *name; int name_type; guint8 error_code; tvbuff_t *next_tvb; const char *saved_proto; static const int * nbss_flags[] = { &hf_nbss_flags_e, NULL }; name = (char *)wmem_alloc(wmem_packet_scope(), MAX_NAME_LEN); msg_type = tvb_get_guint8(tvb, offset); ti = proto_tree_add_item(tree, proto_nbss, tvb, offset, -1, ENC_NA); nbss_tree = proto_item_add_subtree(ti, ett_nbss); proto_tree_add_item(nbss_tree, hf_nbss_type, tvb, offset, 1, ENC_BIG_ENDIAN); offset += 1; if (is_cifs) { proto_tree_add_item(nbss_tree, hf_nbss_cifs_length, tvb, offset, 3, ENC_BIG_ENDIAN); offset += 3; } else { flags = tvb_get_guint8(tvb, offset); proto_tree_add_bitmask(nbss_tree, tvb, offset, hf_nbss_flags, ett_nbss_flags, nbss_flags, ENC_BIG_ENDIAN); length = tvb_get_ntohs(tvb, offset + 1); if (flags & NBSS_FLAGS_E) length += 0x10000; proto_tree_add_uint(nbss_tree, hf_nbss_length, tvb, offset, 3, length); offset += 3; } switch (msg_type) { case SESSION_REQUEST: len = get_nbns_name(tvb, offset, offset, name, MAX_NAME_LEN, &name_type); if (tree) add_name_and_type(nbss_tree, tvb, offset, len, hf_nbss_called_name, name, name_type); offset += len; col_append_fstr(pinfo->cinfo, COL_INFO, ", to %s ", name); len = get_nbns_name(tvb, offset, offset, name, MAX_NAME_LEN, &name_type); if (tree) add_name_and_type(nbss_tree, tvb, offset, len, hf_nbss_calling_name, name, name_type); col_append_fstr(pinfo->cinfo, COL_INFO, "from %s", name); break; case NEGATIVE_SESSION_RESPONSE: error_code = tvb_get_guint8(tvb, offset); if (tree) proto_tree_add_uint(nbss_tree, hf_nbss_error_code, tvb, offset, 1, error_code); col_append_fstr(pinfo->cinfo, COL_INFO, ", %s", val_to_str(error_code, nbss_error_codes, "Unknown (%x)")); break; case RETARGET_SESSION_RESPONSE: if (tree) proto_tree_add_item(nbss_tree, hf_nbss_retarget_ip_address, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; if (tree) proto_tree_add_item(nbss_tree, hf_nbss_retarget_port, tvb, offset, 2, ENC_BIG_ENDIAN); 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. */ next_tvb = tvb_new_subset_remaining(tvb, offset); /* * Dissect the message. * * If it gets an error that means there's no point in * dissecting any more PDUs, rethrow the exception in * question. * * If it gets any other error, report it and continue, as that * means that PDU got an error, but that doesn't mean we should * stop dissecting PDUs within this frame or chunk of reassembled * data. */ saved_proto = pinfo->current_proto; TRY { dissect_netbios_payload(next_tvb, pinfo, tree); } CATCH_NONFATAL_ERRORS { show_exception(tvb, pinfo, tree, EXCEPT_CODE, GET_MESSAGE); pinfo->current_proto = saved_proto; } ENDTRY; break; } } static int dissect_continuation_packet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { proto_tree *nbss_tree; proto_item *ti; /* * It looks like a continuation. */ col_set_str(pinfo->cinfo, COL_INFO, "NBSS Continuation Message"); if (tree) { ti = proto_tree_add_item(tree, proto_nbss, tvb, 0, -1, ENC_NA); nbss_tree = proto_item_add_subtree(ti, ett_nbss); proto_tree_add_item(nbss_tree, hf_nbss_continuation_data, tvb, 0, -1, ENC_NA); } return tvb_captured_length(tvb); } static int dissect_nbss(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) { struct tcpinfo *tcpinfo; int offset = 0; guint length_remaining; guint plen; int max_data; guint8 msg_type; guint8 flags; guint32 length; gboolean is_cifs; tvbuff_t *next_tvb; /* Reject the packet if data is NULL */ if (data == NULL) return 0; tcpinfo = (struct tcpinfo *)data; col_set_str(pinfo->cinfo, COL_PROTOCOL, "NBSS"); col_clear(pinfo->cinfo, COL_INFO); max_data = tvb_captured_length(tvb); msg_type = tvb_get_guint8(tvb, offset); if (pinfo->match_uint == 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. */ return dissect_continuation_packet(tvb, pinfo, tree); } /* * The largest size in for non-SMB NBSS traffic is * 17 bits (0x1FFFF). * * The SMB1 unix extensions and the SMB2 multi credit * feature allow more than 17 bits (0x1FFFF), they allow * 24 bits (0xFFFFFF). * * So if it is a SESSION_MESSAGE and SMB1 or SMB2 * mark it as is_cifs. */ if (tvb_captured_length_remaining(tvb, offset) >= 8 && tvb_get_guint8(tvb,offset+0) == SESSION_MESSAGE && tvb_get_guint8(tvb,offset+5) == 'S' && tvb_get_guint8(tvb,offset+6) == 'M' && tvb_get_guint8(tvb,offset+7) == 'B') { is_cifs = TRUE; } else { is_cifs = FALSE; } /* * 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 += 0x10000; } if ((flags & (~NBSS_FLAGS_E)) != 0) { /* * A bogus flag was set; assume it's a continuation. */ return dissect_continuation_packet(tvb, pinfo, tree); } 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) return dissect_continuation_packet(tvb, pinfo, tree); 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, Mac OS X 10.1 (yes, that's * redundant, but that's what Apple calls it, * not Mac OS 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) return dissect_continuation_packet(tvb, pinfo, tree); break; case POSITIVE_SESSION_RESPONSE: /* * This has no data, so the length must be zero. */ if (length != 0) return dissect_continuation_packet(tvb, pinfo, tree); break; case NEGATIVE_SESSION_RESPONSE: /* * This has 1 byte of data. */ if (length != 1) return dissect_continuation_packet(tvb, pinfo, tree); break; case RETARGET_SESSION_RESPONSE: /* * This has 6 bytes of data. */ if (length != 6) return dissect_continuation_packet(tvb, pinfo, tree); break; case SESSION_KEEP_ALIVE: /* * This has no data, so the length must be zero. */ if (length != 0) return dissect_continuation_packet(tvb, pinfo, tree); break; default: /* * Unknown message type; assume it's a continuation. */ return dissect_continuation_packet(tvb, pinfo, tree); } } col_add_str(pinfo->cinfo, COL_INFO, val_to_str(msg_type, message_types, "Unknown (%02x)")); while ((length_remaining = tvb_reported_length_remaining(tvb, offset)) > 0) { /* * 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 the TCP dissector where the data for this message * starts in the data it handed us and that we need "some more * data." Don't tell it exactly how many bytes we need because * if/when we ask for even more (after the header) that will * break reassembly. */ pinfo->desegment_offset = offset; pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; return tvb_captured_length(tvb); } } /* * Get the length of the NBSS message. */ if (is_cifs) { 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; } plen = length + 4; /* Include length of NBSS header */ /* give a hint to TCP where the next PDU starts * so that it can attempt to find it in case it starts * somewhere in the middle of a segment. */ if(!pinfo->fd->flags.visited){ /* 'Only' SMB is transported ontop of this so make sure * there is an SMB header there ... */ if( ((int)plen>tvb_reported_length_remaining(tvb, offset)) &&(tvb_captured_length_remaining(tvb, offset) >= 8) &&(tvb_get_guint8(tvb,offset+5) == 'S') &&(tvb_get_guint8(tvb,offset+6) == 'M') &&(tvb_get_guint8(tvb,offset+7) == 'B') ){ pinfo->want_pdu_tracking = 2; pinfo->bytes_until_next_pdu = (length+4)-tvb_reported_length_remaining(tvb, offset); } } /* * Can we do reassembly? */ if (nbss_desegment && pinfo->can_desegment) { /* * Yes - is the NBSS message split across segment boundaries? */ if (length_remaining < plen) { /* * Yes. 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 = plen - length_remaining; return tvb_captured_length(tvb); } } /* * Construct a tvbuff containing the amount of the payload we have * available. Make its reported length the amount of data in the PDU. */ length = length_remaining; if (length > plen) length = plen; next_tvb = tvb_new_subset(tvb, offset, length, plen); dissect_nbss_packet(next_tvb, pinfo, tree, is_cifs); offset += plen; } return tvb_captured_length(tvb); } void proto_register_nbt(void) { static hf_register_info hf_nbns[] = { { &hf_nbns_flags, { "Flags", "nbns.flags", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, 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, NULL, 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 }}, { &hf_nbns_name_flags, { "Name flags", "nbns.name_flags", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_name_flags_group, { "Name type", "nbns.name_flags.group", FT_BOOLEAN, 16, TFS(&tfs_group_unique_name), NAME_FLAGS_G, NULL, HFILL }}, { &hf_nbns_name_flags_ont, { "ONT", "nbns.name_flags.ont", FT_UINT16, BASE_DEC, VALS(name_flags_ont_vals), NAME_FLAGS_ONT, NULL, HFILL }}, { &hf_nbns_name_flags_drg, { "Name is being deregistered", "nbns.name_flags.drg", FT_BOOLEAN, 16, TFS(&tfs_yes_no), NAME_FLAGS_DRG, NULL, HFILL }}, { &hf_nbns_name_flags_cnf, { "Name is in conflict", "nbns.name_flags.cnf", FT_BOOLEAN, 16, TFS(&tfs_yes_no), NAME_FLAGS_CNF, NULL, HFILL }}, { &hf_nbns_name_flags_act, { "Name is active", "nbns.name_flags.act", FT_BOOLEAN, 16, TFS(&tfs_yes_no), NAME_FLAGS_ACT, NULL, HFILL }}, { &hf_nbns_name_flags_prm, { "Permanent node name", "nbns.name_flags.prm", FT_BOOLEAN, 16, TFS(&tfs_yes_no), NAME_FLAGS_PRM, NULL, HFILL }}, { &hf_nbns_nb_flags, { "Name flags", "nbns.nb_flags", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_nb_flags_group, { "Name type", "nbns.nb_flags.group", FT_BOOLEAN, 16, TFS(&tfs_group_unique_name), NB_FLAGS_G, NULL, HFILL }}, { &hf_nbns_nb_flags_ont, { "ONT", "nbns.nb_flags.ont", FT_UINT16, BASE_DEC, VALS(nb_flags_ont_vals), NB_FLAGS_ONT, NULL, HFILL }}, { &hf_nbns_type, { "Type", "nbns.type", FT_UINT16, BASE_DEC, VALS(nb_type_name_vals), 0x0, NULL, HFILL }}, { &hf_nbns_class, { "Class", "nbns.class", FT_UINT16, BASE_DEC, VALS(dns_classes), 0x0, NULL, HFILL }}, { &hf_nbns_name, { "Name", "nbns.name", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, /* Generated from convert_proto_tree_add_text.pl */ { &hf_nbns_addr, { "Addr", "nbns.addr", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_number_of_names, { "Number of names", "nbns.number_of_names", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_unit_id, { "Unit ID", "nbns.unit_id", FT_ETHER, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_jumpers, { "Jumpers", "nbns.jumpers", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_test_result, { "Test result", "nbns.test_result", FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_version_number, { "Version number", "nbns.version_number", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_period_of_statistics, { "Period of statistics", "nbns.period_of_statistics", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_num_crcs, { "Number of CRCs", "nbns.num_crcs", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_num_alignment_errors, { "Number of alignment errors", "nbns.num_alignment_errors", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_num_collisions, { "Number of collisions", "nbns.num_collisions", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_num_send_aborts, { "Number of send aborts", "nbns.num_send_aborts", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_num_good_sends, { "Number of good sends", "nbns.num_good_sends", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_num_good_receives, { "Number of good receives", "nbns.num_good_receives", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_num_retransmits, { "Number of retransmits", "nbns.numretransmits", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_num_no_resource_conditions, { "Number of no resource conditions", "nbns.num_no_resource_conditions", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_num_command_blocks, { "Number of command blocks", "nbns.numcommand_blocks", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_num_pending_sessions, { "Number of pending sessions", "nbns.numpending_sessions", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_max_num_pending_sessions, { "Max number of pending sessions", "nbns.max_num_pending_sessions", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_max_total_sessions_possible, { "Max total sessions possible", "nbns.max_total_sessions_possible", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_session_data_packet_size, { "Session data packet size", "nbns.session_data_packet_size", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_data, { "Data", "nbns.data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_netbios_name, { "Name", "nbns.netbios_name", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_ttl, { "Time to live", "nbns.ttl", FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbns_data_length, { "Data length", "nbns.data_length", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, 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(&tfs_yes_no), 0x0, "TRUE if more fragments follow", HFILL }}, { &hf_nbdgm_first, { "This is first fragment", "nbdgm.first", FT_BOOLEAN, BASE_NONE, TFS(&tfs_yes_no), 0x0, "TRUE if first fragment", HFILL }}, { &hf_nbdgm_node_type, { "Node Type", "nbdgm.node_type", FT_UINT8, BASE_DEC, VALS(node_type_vals), 0x0, NULL, 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, NULL, HFILL }}, { &hf_nbdgm_datagram_length, { "Datagram length", "nbdgm.dgram_len", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbdgm_packet_offset, { "Packet offset", "nbdgm.pkt_offset", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbdgm_error_code, { "Error code", "nbdgm.error_code", FT_UINT8, BASE_HEX, VALS(nbds_error_codes), 0x0, NULL, HFILL }}, { &hf_nbdgm_source_name, { "Source name", "nbdgm.source_name", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_nbdgm_destination_name, { "Destination name", "nbdgm.destination_name", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, }; static hf_register_info hf_nbss[] = { { &hf_nbss_type, { "Message Type", "nbss.type", FT_UINT8, BASE_HEX, VALS(message_types), 0x0, "NBSS message type", HFILL }}, { &hf_nbss_flags, { "Flags", "nbss.flags", FT_UINT8, BASE_HEX, NULL, 0x0, "NBSS message flags", HFILL }}, { &hf_nbss_flags_e, { "Extend", "nbss.flags.e", FT_BOOLEAN, 8, TFS(&tfs_nbss_flags_e), NBSS_FLAGS_E, NULL, HFILL }}, { &hf_nbss_length, { "Length", "nbss.length", FT_UINT24, BASE_DEC, NULL, 0x0, "Length of trailer (payload) following this field in bytes", HFILL }}, { &hf_nbss_cifs_length, { "Length", "nbss.length", FT_UINT24, BASE_DEC, NULL, 0x0, "Length trailer (payload) following this field in bytes", HFILL }}, { &hf_nbss_error_code, { "Error code", "nbss.error_code", FT_UINT8, BASE_HEX, VALS(nbss_error_codes), 0x0, NULL, HFILL }}, { &hf_nbss_retarget_ip_address, { "Retarget IP address", "nbss.retarget_ip_address", FT_IPv4, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_nbss_retarget_port, { "Retarget port", "nbss.retarget_port", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }}, { &hf_nbss_continuation_data, { "Continuation data", "nbss.continuation_data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_nbss_called_name, { "Called name", "nbss.called_name", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_nbss_calling_name, { "Calling name", "nbss.calling_name", FT_STRING, BASE_NONE, NULL, 0x0, NULL, 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, }; static ei_register_info ei[] = { { &ei_nbns_incomplete_entry, { "nbns.incomplete_entry", PI_MALFORMED, PI_ERROR, "incomplete entry", EXPFILL }}, }; module_t *nbss_module; expert_module_t* expert_nbns; proto_nbns = proto_register_protocol("NetBIOS Name Service", "NBNS", "nbns"); proto_register_field_array(proto_nbns, hf_nbns, array_length(hf_nbns)); expert_nbns = expert_register_protocol(proto_nbns); expert_register_field_array(expert_nbns, ei, array_length(ei)); 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", "Reassemble NBSS packets spanning multiple TCP segments", "Whether the NBSS dissector should reassemble packets spanning multiple TCP segments." " To use this option, you must also enable" " \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.", &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_uint("udp.port", UDP_PORT_NBNS, nbns_handle); nbdgm_handle = create_dissector_handle(dissect_nbdgm, proto_nbdgm); dissector_add_uint("udp.port", UDP_PORT_NBDGM, nbdgm_handle); nbss_handle = create_dissector_handle(dissect_nbss, proto_nbss); dissector_add_uint("tcp.port", TCP_PORT_NBSS, nbss_handle); dissector_add_uint("tcp.port", TCP_PORT_CIFS, nbss_handle); netbios_heur_subdissector_list = find_heur_dissector_list("netbios"); } /* * Editor modelines - http://www.wireshark.org/tools/modelines.html * * Local variables: * c-basic-offset: 4 * tab-width: 8 * indent-tabs-mode: nil * End: * * vi: set shiftwidth=4 tabstop=8 expandtab: * :indentSize=4:tabSize=8:noTabs=true: */