2 * Routines for BACnet/IP (BVLL, BVLC) dissection
3 * Copyright 2001, Hartmut Mueller <hartmut@abmlinux.org>, FH Dortmund
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1998 Gerald Combs
11 * Copied from README.developer,v 1.23
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
32 #include <epan/prefs.h>
33 #include <epan/strutil.h>
37 #include <epan/packet.h>
39 /* Taken from add-135a (BACnet-IP-standard paper):
41 * The default UDP port for both directed messages and broadcasts shall
42 * be X'BAC0' and all B/IP devices shall support it. In some cases,
43 * e.g., a situation where it is desirable for two groups of BACnet devices
44 * to coexist independently on the same IP subnet, the UDP port may be
45 * configured locally to a different value without it being considered
46 * a violation of this protocol.
48 static guint global_additional_bvlc_udp_port = 0;
50 static int proto_bvlc = -1;
51 static int hf_bvlc_type = -1;
52 static int hf_bvlc_function = -1;
53 static int hf_bvlc_length = -1;
54 static int hf_bvlc_result = -1;
55 static int hf_bvlc_bdt_ip = -1;
56 static int hf_bvlc_bdt_mask = -1;
57 static int hf_bvlc_bdt_port = -1;
58 static int hf_bvlc_reg_ttl = -1;
59 static int hf_bvlc_fdt_ip = -1;
60 static int hf_bvlc_fdt_port = -1;
61 static int hf_bvlc_fdt_ttl = -1;
62 static int hf_bvlc_fdt_timeout = -1;
63 static int hf_bvlc_fwd_ip = -1;
64 static int hf_bvlc_fwd_port = -1;
66 static dissector_handle_t data_handle;
68 static dissector_table_t bvlc_dissector_table;
70 static const value_string bvlc_function_names[] = {
71 { 0x00, "BVLC-Result", },
72 { 0x01, "Write-Broadcast-Distribution-Table", },
73 { 0x02, "Read-Broadcast-Distribution-Table", },
74 { 0x03, "Read-Broadcast-Distribution-Table-Ack", },
75 { 0x04, "Forwarded-NPDU", },
76 { 0x05, "Register-Foreign-Device", },
77 { 0x06, "Read-Foreign-Device-Table", },
78 { 0x07, "Read-Foreign-Device-Table-Ack", },
79 { 0x08, "Delete-Foreign-Device-Table-Entry", },
80 { 0x09, "Distribute-Broadcast-To-Network", },
81 { 0x0a, "Original-Unicast-NPDU", },
82 { 0x0b, "Original-Broadcast-NPDU" },
86 static const value_string bvlc_result_names[] = {
87 { 0x00, "Successful completion" },
88 { 0x10, "Write-Broadcast-Distribution-Table NAK" },
89 { 0x20, "Read-Broadcast-Distribution-Table NAK" },
90 { 0x30, "Register-Foreign-Device NAK" },
91 { 0x40, "Read-Foreign-Device-Table NAK" },
92 { 0x50, "Delete-Foreign-Device-Table-Entry NAK" },
93 { 0x60, "Distribute-Broadcast-To-Network NAK" },
97 static gint ett_bvlc = -1;
98 static gint ett_bdt = -1;
99 static gint ett_fdt = -1;
101 #define BACNET_IP_ANNEX_J 0x81
103 static const value_string bvlc_types[] = {
104 { BACNET_IP_ANNEX_J, "BACnet/IP (Annex J)" },
109 dissect_bvlc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
115 proto_tree *bvlc_tree;
116 proto_tree *bdt_tree; /* Broadcast Distribution Table */
117 proto_tree *fdt_tree; /* Foreign Device Table */
121 guint8 bvlc_function;
123 guint16 packet_length;
125 guint length_remaining;
131 bvlc_type = tvb_get_guint8(tvb, offset);
134 * Simple sanity check - make sure the type is one we know about.
136 if (match_strval(bvlc_type, bvlc_types) == NULL)
139 col_set_str(pinfo->cinfo, COL_PROTOCOL, "BVLC");
141 col_set_str(pinfo->cinfo, COL_INFO, "BACnet Virtual Link Control");
143 bvlc_function = tvb_get_guint8(tvb, offset+1);
144 packet_length = tvb_get_ntohs(tvb, offset+2);
145 length_remaining = tvb_reported_length_remaining(tvb, offset);
146 if (bvlc_function > 0x08) {
147 /* We have a constant header length of BVLC of 4 in every
148 * BVLC-packet forewarding an NPDU. Beware: Changes in the
149 * BACnet-IP-standard may break this.
150 * At the moment, no functions above 0x0b
151 * exist (Addendum 135a to ANSI/ASHRAE 135-1995 - BACnet)
154 } else if(bvlc_function == 0x04) {
155 /* 4 Bytes + 6 Bytes for B/IP Address of Originating Device */
158 /* BVLC-packets with function below 0x09 contain
159 * routing-level data (e.g. Broadcast Distribution)
160 * but no NPDU for BACnet, so bvlc_length goes up to the end
161 * of the captured frame.
163 bvlc_length = packet_length;
167 if (bvlc_length < 4) {
168 proto_tree_add_text(tree, tvb, 2, 2,
169 "Bogus length: %d", bvlc_length);
170 return tvb_reported_length(tvb); /* XXX - reject? */
172 ti = proto_tree_add_item(tree, proto_bvlc, tvb, 0,
174 bvlc_tree = proto_item_add_subtree(ti, ett_bvlc);
175 proto_tree_add_uint(bvlc_tree, hf_bvlc_type, tvb, offset, 1,
178 proto_tree_add_uint(bvlc_tree, hf_bvlc_function, tvb,
179 offset, 1, bvlc_function);
181 if (length_remaining != packet_length)
182 proto_tree_add_uint_format_value(bvlc_tree, hf_bvlc_length, tvb, offset,
184 "%d of %d bytes (invalid length - expected %d bytes)",
185 bvlc_length, packet_length, length_remaining);
187 proto_tree_add_uint_format_value(bvlc_tree, hf_bvlc_length, tvb, offset,
188 2, bvlc_length, "%d of %d bytes BACnet packet length",
189 bvlc_length, packet_length);
191 switch (bvlc_function) {
192 case 0x00: /* BVLC-Result */
193 bvlc_result = tvb_get_ntohs(tvb, offset);
194 /* I dont know why the result code is encoded in 4 nibbles,
195 * but only using one: 0x00r0. Shifting left 4 bits.
197 /* We should bitmask the result correctly when we have a
198 * packet to dissect, see README.developer, 1.6.2, FID */
199 proto_tree_add_uint_format_value(bvlc_tree, hf_bvlc_result, tvb,
200 offset, 2, bvlc_result,"0x%04x (%s)",
201 bvlc_result, val_to_str(bvlc_result,
202 bvlc_result_names, "Unknown"));
205 case 0x01: /* Write-Broadcast-Distribution-Table */
206 case 0x03: /* Read-Broadcast-Distribution-Table-Ack */
207 /* List of BDT Entries: N*10-octet */
208 ti_bdt = proto_tree_add_item(bvlc_tree, proto_bvlc, tvb,
209 offset, bvlc_length-4, FALSE);
210 bdt_tree = proto_item_add_subtree(ti_bdt, ett_bdt);
211 /* List of BDT Entries: N*10-octet */
212 while ((bvlc_length - offset) > 9) {
213 proto_tree_add_item(bdt_tree, hf_bvlc_bdt_ip,
214 tvb, offset, 4, FALSE);
216 proto_tree_add_item(bdt_tree, hf_bvlc_bdt_port,
217 tvb, offset, 2, FALSE);
219 proto_tree_add_item(bdt_tree,
220 hf_bvlc_bdt_mask, tvb, offset, 4,
224 /* We check this if we get a BDT-packet somewhere */
226 case 0x02: /* Read-Broadcast-Distribution-Table */
227 /* nothing to do here */
229 case 0x05: /* Register-Foreign-Device */
230 /* Time-to-Live 2-octets T, Time-to-Live T, in seconds */
231 proto_tree_add_item(bvlc_tree, hf_bvlc_reg_ttl,
232 tvb, offset, 2, FALSE);
235 case 0x06: /* Read-Foreign-Device-Table */
236 /* nothing to do here */
238 case 0x07: /* Read-Foreign-Device-Table-Ack */
239 /* List of FDT Entries: N*10-octet */
240 /* N indicates the number of entries in the FDT whose
241 * contents are being returned. Each returned entry
242 * consists of the 6-octet B/IP address of the registrant;
243 * the 2-octet Time-to-Live value supplied at the time of
244 * registration; and a 2-octet value representing the
245 * number of seconds remaining before the BBMD will purge
246 * the registrant's FDT entry if no re-registration occurs.
248 ti_fdt = proto_tree_add_item(bvlc_tree, proto_bvlc, tvb,
249 offset, bvlc_length -4, FALSE);
250 fdt_tree = proto_item_add_subtree(ti_fdt, ett_fdt);
251 /* List of FDT Entries: N*10-octet */
252 while ((bvlc_length - offset) > 9) {
253 proto_tree_add_item(fdt_tree, hf_bvlc_fdt_ip,
254 tvb, offset, 4, FALSE);
256 proto_tree_add_item(fdt_tree, hf_bvlc_fdt_port,
257 tvb, offset, 2, FALSE);
259 proto_tree_add_item(fdt_tree,
260 hf_bvlc_fdt_ttl, tvb, offset, 2,
263 proto_tree_add_item(fdt_tree,
264 hf_bvlc_fdt_timeout, tvb, offset, 2,
268 /* We check this if we get a FDT-packet somewhere */
270 case 0x08: /* Delete-Foreign-Device-Table-Entry */
271 /* FDT Entry: 6-octets */
272 proto_tree_add_item(bvlc_tree, hf_bvlc_fdt_ip,
273 tvb, offset, 4, FALSE);
275 proto_tree_add_item(bvlc_tree, hf_bvlc_fdt_port,
276 tvb, offset, 2, FALSE);
279 /* We check this if we get a FDT-packet somewhere */
280 case 0x04: /* Forwarded-NPDU
281 * Why is this 0x04? It would have been a better
282 * idea to append all forewarded NPDUs at the
283 * end of the function table in the B/IP-standard!
285 /* proto_tree_add_bytes_format(); */
286 proto_tree_add_item(bvlc_tree, hf_bvlc_fwd_ip,
287 tvb, offset, 4, FALSE);
289 proto_tree_add_item(bvlc_tree, hf_bvlc_fwd_port,
290 tvb, offset, 2, FALSE);
292 default:/* Distribute-Broadcast-To-Network
293 * Original-Unicast-NPDU
294 * Original-Broadcast-NPDU
295 * Going to the next dissector...
301 /* Ok, no routing information BVLC packet. Dissect as
304 npdu_length = packet_length - bvlc_length;
305 next_tvb = tvb_new_subset(tvb,bvlc_length,-1,npdu_length);
306 /* Code from Guy Harris */
307 if (!dissector_try_port(bvlc_dissector_table,
308 bvlc_function, next_tvb, pinfo, tree)) {
309 /* Unknown function - dissect the paylod as data */
310 call_dissector(data_handle,next_tvb, pinfo, tree);
312 return tvb_reported_length(tvb);
315 void proto_reg_handoff_bvlc(void);
318 proto_register_bvlc(void)
320 static hf_register_info hf[] = {
322 { "Type", "bvlc.type",
323 FT_UINT8, BASE_HEX, VALS(bvlc_types), 0,
327 { "Function", "bvlc.function",
328 FT_UINT8, BASE_HEX, VALS(bvlc_function_names), 0,
329 "BVLC Function", HFILL }
332 { "BVLC-Length", "bvlc.length",
333 FT_UINT16, BASE_DEC, NULL, 0,
334 "Length of BVLC", HFILL }
336 /* We should bitmask the result correctly when we have a
337 * packet to dissect */
339 { "Result", "bvlc.result",
340 FT_UINT16, BASE_HEX, NULL, 0,
341 "Result Code", HFILL }
344 { "IP", "bvlc.bdt_ip",
345 FT_IPv4, BASE_NONE, NULL, 0,
349 { "Port", "bvlc.bdt_port",
350 FT_UINT16, BASE_DEC, NULL, 0,
354 { "Mask", "bvlc.bdt_mask",
355 FT_BYTES, BASE_NONE, NULL, 0,
356 "BDT Broadcast Distribution Mask", HFILL }
359 { "TTL", "bvlc.reg_ttl",
360 FT_UINT16, BASE_DEC, NULL, 0,
361 "Foreign Device Time To Live", HFILL }
364 { "IP", "bvlc.fdt_ip",
365 FT_IPv4, BASE_NONE, NULL, 0,
369 { "Port", "bvlc.fdt_port",
370 FT_UINT16, BASE_DEC, NULL, 0,
374 { "TTL", "bvlc.fdt_ttl",
375 FT_UINT16, BASE_DEC, NULL, 0,
376 "Foreign Device Time To Live", HFILL }
378 { &hf_bvlc_fdt_timeout,
379 { "Timeout", "bvlc.fdt_timeout",
380 FT_UINT16, BASE_DEC, NULL, 0,
381 "Foreign Device Timeout (seconds)", HFILL }
384 { "IP", "bvlc.fwd_ip",
385 FT_IPv4, BASE_NONE, NULL, 0,
389 { "Port", "bvlc.fwd_port",
390 FT_UINT16, BASE_DEC, NULL, 0,
395 static gint *ett[] = {
401 module_t *bvlc_module;
403 proto_bvlc = proto_register_protocol("BACnet Virtual Link Control",
406 proto_register_field_array(proto_bvlc, hf, array_length(hf));
407 proto_register_subtree_array(ett, array_length(ett));
409 bvlc_module = prefs_register_protocol(proto_bvlc, proto_reg_handoff_bvlc);
410 prefs_register_uint_preference(bvlc_module, "additional_udp_port",
411 "Additional UDP port", "Set an additional UDP port, "
412 "besides the standard X'BAC0' (47808) port.",
413 10, &global_additional_bvlc_udp_port);
415 new_register_dissector("bvlc", dissect_bvlc, proto_bvlc);
417 bvlc_dissector_table = register_dissector_table("bvlc.function",
418 "BVLC Function", FT_UINT8, BASE_HEX);
422 proto_reg_handoff_bvlc(void)
424 static gboolean bvlc_initialized = FALSE;
425 static dissector_handle_t bvlc_handle;
426 static guint additional_bvlc_udp_port;
428 if (!bvlc_initialized)
430 bvlc_handle = find_dissector("bvlc");
431 dissector_add("udp.port", 0xBAC0, bvlc_handle);
432 data_handle = find_dissector("data");
433 bvlc_initialized = TRUE;
437 if (additional_bvlc_udp_port != 0) {
438 dissector_delete("udp.port", additional_bvlc_udp_port, bvlc_handle);
442 if (global_additional_bvlc_udp_port != 0) {
443 dissector_add("udp.port", global_additional_bvlc_udp_port, bvlc_handle);
445 additional_bvlc_udp_port = global_additional_bvlc_udp_port;