2 * Routines for BACnet (NPDU) dissection
3 * Copyright 2001, Hartmut Mueller <hartmut@abmlinux.org>, FH Dortmund
4 * Enhanced by Steve Karg, 2005, <skarg@users.sourceforge.net>
8 * Wireshark - Network traffic analyzer
9 * By Gerald Combs <gerald@wireshark.org>
10 * Copyright 1998 Gerald Combs
12 * Copied from README.developer,v 1.23
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
39 #include <epan/packet.h>
41 #include <epan/llcsaps.h>
43 static dissector_handle_t bacapp_handle;
44 static dissector_handle_t data_handle;
47 bacnet_mesgtyp_name (guint8 bacnet_mesgtyp){
48 static const char *type_names[] = {
49 "Who-Is-Router-To-Network",
50 "I-Am-Router-To-Network",
51 "I-Could-Be-Router-To-Network",
52 "Reject-Message-To-Network",
53 "Router-Busy-To-Network",
54 "Router-Available-To-Network",
55 "Initialize-Routing-Table",
56 "Initialize-Routing-Table-Ack",
57 "Establish-Connection-To-Network",
58 "Disconnect-Connection-To-Network"
60 if(bacnet_mesgtyp < 0x0a) {
61 return type_names[bacnet_mesgtyp];
63 return (bacnet_mesgtyp < 0x80)? "Reserved for Use by ASHRAE" : "Vendor Proprietary Message";
68 bacnet_rejectreason_name (guint8 bacnet_rejectreason) {
69 static const char *type_names[] = {
71 "The router is not directly connected to DNET and cannot find a router to DNET on any directly connected network using Who-Is-Router-To-Network messages.",
72 "The router is busy and unable to accept messages for the specified DNET at the present time.",
73 "It is an unknown network layer message type.",
74 "The message is too long to be routed to this DNET.",
75 "The router is no longer directly connected to DNET but can reconnect if requested.",
76 "The router is no longer directly connected to DNET and cannot reconnect even if requested."
78 return (bacnet_rejectreason > 6)? "Invalid Rejection Reason.": type_names[bacnet_rejectreason];
81 /* Network Layer Control Information */
82 #define BAC_CONTROL_NET 0x80
83 #define BAC_CONTROL_RES1 0x40
84 #define BAC_CONTROL_DEST 0x20
85 #define BAC_CONTROL_RES2 0x10
86 #define BAC_CONTROL_SRC 0x08
87 #define BAC_CONTROL_EXPECT 0x04
88 #define BAC_CONTROL_PRIO_HIGH 0x02
89 #define BAC_CONTROL_PRIO_LOW 0x01
91 /* Network Layer Message Types */
92 #define BAC_NET_WHO_R 0x00
93 #define BAC_NET_IAM_R 0x01
94 #define BAC_NET_ICB_R 0x02
95 #define BAC_NET_REJ 0x03
96 #define BAC_NET_R_BUSY 0x04
97 #define BAC_NET_R_AVA 0x05
98 #define BAC_NET_INIT_RTAB 0x06
99 #define BAC_NET_INIT_RTAB_ACK 0x07
100 #define BAC_NET_EST_CON 0x08
101 #define BAC_NET_DISC_CON 0x09
103 static const true_false_string control_net_set_high = {
104 "network layer message, message type field present.",
105 "BACnet APDU, message type field absent."
108 static const true_false_string control_res_high = {
109 "Shall be zero, but is one.",
110 "Shall be zero and is zero."
112 static const true_false_string control_dest_high = {
113 "DNET, DLEN and Hop Count present. If DLEN=0: broadcast, dest. address field absent.",
114 "DNET, DLEN, DADR and Hop Count absent."
117 static const true_false_string control_src_high = {
118 "SNET, SLEN and SADR present, SLEN=0 invalid, SLEN specifies length of SADR",
119 "SNET, SLEN and SADR absent"
122 static const true_false_string control_expect_high = {
123 "BACnet-Confirmed-Request-PDU, a segment of BACnet-ComplexACK-PDU or Network Message expecting a reply present.",
124 "Other than a BACnet-Confirmed-Request-PDU, segment of BACnet-ComplexACK-PDU or network layer message expecting a reply present."
127 static const true_false_string control_prio_high_high = {
128 "Life Safety or Critical Equipment message.",
129 "Not a Life Safety or Critical Equipment message."
132 static const true_false_string control_prio_low_high = {
138 static int proto_bacnet = -1;
139 static int hf_bacnet_version = -1;
140 static int hf_bacnet_control = -1;
141 static int hf_bacnet_control_net = -1;
142 static int hf_bacnet_control_res1 = -1;
143 static int hf_bacnet_control_dest = -1;
144 static int hf_bacnet_control_res2 = -1;
145 static int hf_bacnet_control_src = -1;
146 static int hf_bacnet_control_expect = -1;
147 static int hf_bacnet_control_prio_high = -1;
148 static int hf_bacnet_control_prio_low = -1;
149 static int hf_bacnet_dnet = -1;
150 static int hf_bacnet_dlen = -1;
151 static int hf_bacnet_dadr_eth = -1;
152 static int hf_bacnet_dadr_mstp = -1;
153 static int hf_bacnet_dadr_tmp = -1;
154 static int hf_bacnet_snet = -1;
155 static int hf_bacnet_slen = -1;
156 static int hf_bacnet_sadr_eth = -1;
157 static int hf_bacnet_sadr_mstp = -1;
158 static int hf_bacnet_sadr_tmp = -1;
159 static int hf_bacnet_hopc = -1;
160 static int hf_bacnet_mesgtyp = -1;
161 static int hf_bacnet_vendor = -1;
162 static int hf_bacnet_perf = -1;
163 static int hf_bacnet_rejectreason = -1;
164 static int hf_bacnet_rportnum = -1;
165 static int hf_bacnet_portid = -1;
166 static int hf_bacnet_pinfolen = -1;
167 static int hf_bacnet_term_time_value = -1;
169 static gint ett_bacnet = -1;
170 static gint ett_bacnet_control = -1;
173 dissect_bacnet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
177 proto_tree *bacnet_tree;
178 proto_tree *control_tree;
181 guint8 bacnet_version;
182 guint8 bacnet_control;
185 guint8 bacnet_mesgtyp;
186 guint8 bacnet_rejectreason;
187 guint8 bacnet_rportnum;
188 guint8 bacnet_pinfolen;
193 col_set_str(pinfo->cinfo, COL_PROTOCOL, "BACnet-NPDU");
195 col_set_str(pinfo->cinfo, COL_INFO, "Building Automation and Control Network NPDU");
198 bacnet_version = tvb_get_guint8(tvb, offset);
199 bacnet_control = tvb_get_guint8(tvb, offset+1);
203 bacnet_rejectreason = 0;
209 /* I don't know the length of the NPDU by now. Setting the length after dissection */
210 ti = proto_tree_add_item(tree, proto_bacnet, tvb, 0, -1, FALSE);
212 bacnet_tree = proto_item_add_subtree(ti, ett_bacnet);
214 proto_tree_add_uint_format_value(bacnet_tree, hf_bacnet_version, tvb,
216 bacnet_version,"0x%02x (%s)",bacnet_version,
217 (bacnet_version == 0x01)?"ASHRAE 135-1995":"unknown");
219 ct = proto_tree_add_uint(bacnet_tree, hf_bacnet_control,
220 tvb, offset, 1, bacnet_control);
221 control_tree = proto_item_add_subtree(ct, ett_bacnet_control);
222 proto_tree_add_boolean(control_tree, hf_bacnet_control_net,
223 tvb, offset, 1, bacnet_control);
224 proto_tree_add_boolean(control_tree, hf_bacnet_control_res1, tvb,
225 offset, 1, bacnet_control);
226 proto_tree_add_boolean(control_tree, hf_bacnet_control_dest, tvb,
227 offset, 1, bacnet_control);
228 proto_tree_add_boolean(control_tree, hf_bacnet_control_res2, tvb,
229 offset, 1, bacnet_control);
230 proto_tree_add_boolean(control_tree, hf_bacnet_control_src, tvb,
231 offset, 1, bacnet_control);
232 proto_tree_add_boolean(control_tree, hf_bacnet_control_expect, tvb,
233 offset, 1, bacnet_control);
234 proto_tree_add_boolean(control_tree, hf_bacnet_control_prio_high,
235 tvb, offset, 1, bacnet_control);
236 proto_tree_add_boolean(control_tree, hf_bacnet_control_prio_low,
237 tvb, offset, 1, bacnet_control);
239 if (bacnet_control & BAC_CONTROL_DEST) { /* DNET, DLEN, DADR */
240 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
241 tvb, offset, 2, FALSE);
243 bacnet_dlen = tvb_get_guint8(tvb, offset);
244 /* DLEN = 0 is broadcast on dest.network */
245 if( bacnet_dlen == 0) {
246 /* append to hf_bacnet_dlen: broadcast */
247 proto_tree_add_uint_format_value(bacnet_tree,
248 hf_bacnet_dlen, tvb, offset, 1, bacnet_dlen,
249 "%d indicates Broadcast on Destination Network",
253 } else if (bacnet_dlen==6) {
254 proto_tree_add_uint(bacnet_tree, hf_bacnet_dlen,
255 tvb, offset, 1, bacnet_dlen);
258 proto_tree_add_item(bacnet_tree,
259 hf_bacnet_dadr_eth, tvb, offset,
261 offset += bacnet_dlen;
262 } else if (bacnet_dlen==1) {
263 proto_tree_add_uint(bacnet_tree, hf_bacnet_dlen,
264 tvb, offset, 1, bacnet_dlen);
266 /* MS/TP or ARCNET MAC */
267 proto_tree_add_item(bacnet_tree,
268 hf_bacnet_dadr_mstp, tvb, offset,
270 offset += bacnet_dlen;
271 } else if (bacnet_dlen<7) {
272 proto_tree_add_uint(bacnet_tree, hf_bacnet_dlen,
273 tvb, offset, 1, bacnet_dlen);
275 /* Other MAC formats should be included here */
276 proto_tree_add_item(bacnet_tree,
277 hf_bacnet_dadr_tmp, tvb, offset,
279 offset += bacnet_dlen;
281 proto_tree_add_uint_format_value(bacnet_tree,
282 hf_bacnet_dlen, tvb, offset, 1, bacnet_dlen,
287 if (bacnet_control & BAC_CONTROL_SRC) { /* SNET, SLEN, SADR */
289 proto_tree_add_uint(bacnet_tree, hf_bacnet_snet,
290 tvb, offset, 2, tvb_get_ntohs(tvb, offset));
292 bacnet_slen = tvb_get_guint8(tvb, offset);
293 if( bacnet_slen == 0) { /* SLEN = 0 invalid */
294 proto_tree_add_uint_format_value(bacnet_tree,
295 hf_bacnet_slen, tvb, offset, 1, bacnet_slen,
299 } else if (bacnet_slen==6) {
301 proto_tree_add_uint(bacnet_tree, hf_bacnet_slen,
302 tvb, offset, 1, bacnet_slen);
305 proto_tree_add_item(bacnet_tree,
306 hf_bacnet_sadr_eth, tvb, offset,
308 offset += bacnet_slen;
309 } else if (bacnet_slen==1) {
311 proto_tree_add_uint(bacnet_tree, hf_bacnet_slen,
312 tvb, offset, 1, bacnet_slen);
314 /* MS/TP or ARCNET MAC */
315 proto_tree_add_item(bacnet_tree,
316 hf_bacnet_sadr_mstp, tvb, offset,
318 offset += bacnet_slen;
319 } else if (bacnet_slen<6) { /* LON MAC */
321 proto_tree_add_uint(bacnet_tree, hf_bacnet_slen,
322 tvb, offset, 1, bacnet_slen);
324 /* Other MAC formats should be included here */
325 proto_tree_add_item(bacnet_tree,
326 hf_bacnet_sadr_tmp, tvb, offset,
328 offset += bacnet_slen;
330 proto_tree_add_uint_format_value(bacnet_tree,
331 hf_bacnet_slen, tvb, offset, 1, bacnet_slen,
337 if (bacnet_control & BAC_CONTROL_DEST) { /* Hopcount */
338 proto_tree_add_item(bacnet_tree, hf_bacnet_hopc,
339 tvb, offset, 1, FALSE);
342 /* Network Layer Message Type */
343 if (bacnet_control & BAC_CONTROL_NET) {
344 bacnet_mesgtyp = tvb_get_guint8(tvb, offset);
345 proto_tree_add_uint_format_value(bacnet_tree,
346 hf_bacnet_mesgtyp, tvb, offset, 1, bacnet_mesgtyp,
347 "%02x (%s)", bacnet_mesgtyp,
348 bacnet_mesgtyp_name(bacnet_mesgtyp));
349 /* Put the NPDU Type in the info column */
350 if (check_col(pinfo->cinfo, COL_INFO))
352 col_clear(pinfo->cinfo, COL_INFO);
353 col_add_str(pinfo->cinfo, COL_INFO,
354 bacnet_mesgtyp_name(bacnet_mesgtyp));
358 * The standard says: "If Bit 7 of the control octet is 1 and
359 * the Message Type field contains a value in the range
360 * X'80' - X'FF', then a Vendor ID field shall be present (...)."
361 * We should not go any further in dissecting the packet if it's
362 * not present, but we don't know about that: No length field...
364 if (bacnet_mesgtyp > 0x7f) {
365 proto_tree_add_item(bacnet_tree, hf_bacnet_vendor,
366 tvb, offset, 2, FALSE);
368 /* attention: doesnt work here because of if(tree) */
369 call_dissector(data_handle,
370 tvb_new_subset(tvb, offset, -1, -1), pinfo, tree);
372 /* Performance Index (in I-Could-Be-Router-To-Network) */
373 if (bacnet_mesgtyp == BAC_NET_ICB_R) {
374 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
375 tvb, offset, 2, FALSE);
377 proto_tree_add_item(bacnet_tree, hf_bacnet_perf,
378 tvb, offset, 1, FALSE);
381 /* Reason, DNET (in Reject-Message-To-Network) */
382 if (bacnet_mesgtyp == BAC_NET_REJ) {
383 bacnet_rejectreason = tvb_get_guint8(tvb, offset);
384 proto_tree_add_uint_format_value(bacnet_tree,
385 hf_bacnet_rejectreason,
387 bacnet_rejectreason, "%d (%s)",
389 bacnet_rejectreason_name(bacnet_rejectreason));
391 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
392 tvb, offset, 2, FALSE);
395 /* N*DNET (in Router-Busy-To-Network,Router-Available-To-Network) */
396 if ((bacnet_mesgtyp == BAC_NET_R_BUSY) ||
397 (bacnet_mesgtyp == BAC_NET_WHO_R) ||
398 (bacnet_mesgtyp == BAC_NET_R_AVA) ||
399 (bacnet_mesgtyp == BAC_NET_IAM_R) ) {
400 while(tvb_reported_length_remaining(tvb, offset) > 1 ) {
401 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
402 tvb, offset, 2, FALSE);
406 /* Initialize-Routing-Table */
407 if ( (bacnet_mesgtyp == BAC_NET_INIT_RTAB) ||
408 (bacnet_mesgtyp == BAC_NET_INIT_RTAB_ACK) ) {
409 bacnet_rportnum = tvb_get_guint8(tvb, offset);
410 /* number of ports */
411 proto_tree_add_uint(bacnet_tree, hf_bacnet_rportnum,
412 tvb, offset, 1, bacnet_rportnum);
414 for(i=0; i<bacnet_rportnum; i++) {
416 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
417 tvb, offset, 2, FALSE);
420 proto_tree_add_item(bacnet_tree, hf_bacnet_portid,
421 tvb, offset, 1, FALSE);
423 /* Port Info Length */
424 bacnet_pinfolen = tvb_get_guint8(tvb, offset);
425 proto_tree_add_uint(bacnet_tree, hf_bacnet_pinfolen,
426 tvb, offset, 1, bacnet_pinfolen);
428 proto_tree_add_text(bacnet_tree, tvb, offset,
429 bacnet_pinfolen, "Port Info: %s",
430 tvb_bytes_to_str(tvb, offset, bacnet_pinfolen));
431 offset += bacnet_pinfolen;
434 /* Establish-Connection-To-Network */
435 if (bacnet_mesgtyp == BAC_NET_EST_CON) {
436 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
437 tvb, offset, 2, FALSE);
439 proto_tree_add_item(bacnet_tree, hf_bacnet_term_time_value,
440 tvb, offset, 1, FALSE);
443 /* Disconnect-Connection-To-Network */
444 if (bacnet_mesgtyp == BAC_NET_DISC_CON) {
445 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
446 tvb, offset, 2, FALSE);
449 proto_item_set_len(ti, offset);
451 /* dissect BACnet APDU */
452 next_tvb = tvb_new_subset(tvb,offset,-1,-1);
453 if (bacnet_control & BAC_CONTROL_NET) {
454 /* Unknown function - dissect the payload as data */
455 call_dissector(data_handle, next_tvb, pinfo, tree);
457 /* APDU - call the APDU dissector */
458 call_dissector(bacapp_handle, next_tvb, pinfo, tree);
463 proto_register_bacnet(void)
465 static hf_register_info hf[] = {
466 { &hf_bacnet_version,
467 { "Version", "bacnet.version",
468 FT_UINT8, BASE_DEC, NULL, 0,
469 "BACnet Version", HFILL }
471 { &hf_bacnet_control,
472 { "Control", "bacnet.control",
473 FT_UINT8, BASE_HEX, NULL, 0,
474 "BACnet Control", HFILL }
476 { &hf_bacnet_control_net,
478 "bacnet.control_net",
479 FT_BOOLEAN, 8, TFS(&control_net_set_high),
480 BAC_CONTROL_NET, "BACnet Control", HFILL }
482 { &hf_bacnet_control_res1,
484 "bacnet.control_res1",
485 FT_BOOLEAN, 8, TFS(&control_res_high),
486 BAC_CONTROL_RES1, "BACnet Control", HFILL }
488 { &hf_bacnet_control_dest,
489 { "Destination Specifier",
490 "bacnet.control_dest",
491 FT_BOOLEAN, 8, TFS(&control_dest_high),
492 BAC_CONTROL_DEST, "BACnet Control", HFILL }
494 { &hf_bacnet_control_res2,
496 "bacnet.control_res2",
497 FT_BOOLEAN, 8, TFS(&control_res_high),
498 BAC_CONTROL_RES2, "BACnet Control", HFILL }
500 { &hf_bacnet_control_src,
501 { "Source specifier",
502 "bacnet.control_src",
503 FT_BOOLEAN, 8, TFS(&control_src_high),
504 BAC_CONTROL_SRC, "BACnet Control", HFILL }
506 { &hf_bacnet_control_expect,
508 "bacnet.control_expect",
509 FT_BOOLEAN, 8, TFS(&control_expect_high),
510 BAC_CONTROL_EXPECT, "BACnet Control", HFILL }
512 { &hf_bacnet_control_prio_high,
514 "bacnet.control_prio_high",
515 FT_BOOLEAN, 8, TFS(&control_prio_high_high),
516 BAC_CONTROL_PRIO_HIGH, "BACnet Control", HFILL }
518 { &hf_bacnet_control_prio_low,
520 "bacnet.control_prio_low",
521 FT_BOOLEAN, 8, TFS(&control_prio_low_high),
522 BAC_CONTROL_PRIO_LOW, "BACnet Control", HFILL }
525 { "Destination Network Address", "bacnet.dnet",
526 FT_UINT16, BASE_DEC, NULL, 0,
530 { "Destination MAC Layer Address Length", "bacnet.dlen",
531 FT_UINT8, BASE_DEC, NULL, 0,
534 { &hf_bacnet_dadr_eth,
535 { "Destination ISO 8802-3 MAC Address", "bacnet.dadr_eth",
536 FT_ETHER, BASE_NONE, NULL, 0,
539 { &hf_bacnet_dadr_mstp,
540 { "DADR", "bacnet.dadr_mstp",
541 FT_UINT8, BASE_DEC, NULL, 0,
542 "Destination MS/TP or ARCNET MAC Address", HFILL }
544 { &hf_bacnet_dadr_tmp,
545 { "Unknown Destination MAC", "bacnet.dadr_tmp",
546 FT_BYTES, BASE_NONE, NULL, 0,
550 { "Source Network Address", "bacnet.snet",
551 FT_UINT16, BASE_DEC, NULL, 0,
555 { "Source MAC Layer Address Length", "bacnet.slen",
556 FT_UINT8, BASE_DEC, NULL, 0,
559 { &hf_bacnet_sadr_eth,
560 { "SADR", "bacnet.sadr_eth",
561 FT_ETHER, BASE_NONE, NULL, 0,
562 "Source ISO 8802-3 MAC Address", HFILL }
564 { &hf_bacnet_sadr_mstp,
565 { "SADR", "bacnet.sadr_mstp",
566 FT_UINT8, BASE_DEC, NULL, 0,
567 "Source MS/TP or ARCNET MAC Address", HFILL }
569 { &hf_bacnet_sadr_tmp,
570 { "Unknown Source MAC", "bacnet.sadr_tmp",
571 FT_BYTES, BASE_NONE, NULL, 0,
575 { "Hop Count", "bacnet.hopc",
576 FT_UINT8, BASE_DEC, NULL, 0,
579 { &hf_bacnet_mesgtyp,
580 { "Network Layer Message Type", "bacnet.mesgtyp",
581 FT_UINT8, BASE_HEX, NULL, 0,
585 { "Vendor ID", "bacnet.vendor",
586 FT_UINT16, BASE_DEC, NULL, 0,
590 { "Performance Index", "bacnet.perf",
591 FT_UINT8, BASE_DEC, NULL, 0,
594 { &hf_bacnet_rejectreason,
595 { "Reject Reason", "bacnet.rejectreason",
596 FT_UINT8, BASE_DEC, NULL, 0,
599 { &hf_bacnet_rportnum,
600 { "Number of Port Mappings", "bacnet.rportnum",
601 FT_UINT8, BASE_DEC, NULL, 0,
604 { &hf_bacnet_pinfolen,
605 { "Port Info Length", "bacnet.pinfolen",
606 FT_UINT8, BASE_DEC, NULL, 0,
610 { "Port ID", "bacnet.portid",
611 FT_UINT8, BASE_HEX, NULL, 0,
614 { &hf_bacnet_term_time_value,
615 { "Termination Time Value (seconds)", "bacnet.term_time_value",
616 FT_UINT8, BASE_DEC, NULL, 0,
617 "Termination Time Value", HFILL }
621 static gint *ett[] = {
626 proto_bacnet = proto_register_protocol("Building Automation and Control Network NPDU",
629 proto_register_field_array(proto_bacnet, hf, array_length(hf));
630 proto_register_subtree_array(ett, array_length(ett));
632 register_dissector("bacnet", dissect_bacnet, proto_bacnet);
636 proto_reg_handoff_bacnet(void)
638 dissector_handle_t bacnet_handle;
640 bacnet_handle = find_dissector("bacnet");
641 dissector_add("bvlc.function", 0x04, bacnet_handle);
642 dissector_add("bvlc.function", 0x09, bacnet_handle);
643 dissector_add("bvlc.function", 0x0a, bacnet_handle);
644 dissector_add("bvlc.function", 0x0b, bacnet_handle);
645 dissector_add("llc.dsap", SAP_BACNET, bacnet_handle);
646 bacapp_handle = find_dissector("bacapp");
647 data_handle = find_dissector("data");