2 * Routines for BACnet (NPDU) dissection
3 * Copyright 2001, Hartmut Mueller <hartmut@abmlinux.org>, FH Dortmund
5 * $Id: packet-bacnet.c,v 1.10 2002/01/21 07:36:32 guy Exp $
7 * Ethereal - Network traffic analyzer
8 * By Gerald Combs <gerald@ethereal.com>
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.
36 #ifdef HAVE_SYS_TYPES_H
37 # include <sys/types.h>
40 #ifdef HAVE_NETINET_IN_H
41 # include <netinet/in.h>
46 #ifdef NEED_SNPRINTF_H
47 # include "snprintf.h"
50 #include <epan/packet.h>
52 static dissector_handle_t bacapp_handle;
53 static dissector_handle_t data_handle;
56 bacnet_mesgtyp_name (guint8 bacnet_mesgtyp){
57 static const char *type_names[] = {
58 "Who-Is-Router-To-Network",
59 "I-Am-Router-To-Network",
60 "I-Could-Be-Router-To-Network",
61 "Reject-Message-To-Network",
62 "Router-Busy-To-Network",
63 "Router-Available-To-Network",
64 "Initialize-Routing-Table",
65 "Initialize-Routing-Table-Ack",
66 "Establish-Connection-To-Network",
67 "Disconnect-Connection-To-Network"
69 if(bacnet_mesgtyp < 0x0a) {
70 return type_names[bacnet_mesgtyp];
72 return (bacnet_mesgtyp < 0x80)? "Reserved for Use by ASHRAE" : "Vendor Proprietary Message";
77 bacnet_rejectreason_name (guint8 bacnet_rejectreason) {
78 static const char *type_names[] = {
80 "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.",
81 "The router is busy and unable to accept messages for the specified DNET at the present time.",
82 "It is an unknown network layer message type.",
83 "The message is too long to be routed to this DNET.",
84 "The router is no longer directly connected to DNET but can reconnect if requested.",
85 "The router is no longer directly connected to DNET and cannot reconnect even if requested."
87 return (bacnet_rejectreason > 6)? "Invalid Rejection Reason.": type_names[bacnet_rejectreason];
90 /* Network Layer Control Information */
91 #define BAC_CONTROL_NET 0x80
92 #define BAC_CONTROL_RES1 0x40
93 #define BAC_CONTROL_DEST 0x20
94 #define BAC_CONTROL_RES2 0x10
95 #define BAC_CONTROL_SRC 0x08
96 #define BAC_CONTROL_EXPECT 0x04
97 #define BAC_CONTROL_PRIO_HIGH 0x02
98 #define BAC_CONTROL_PRIO_LOW 0x01
100 /* Network Layer Message Types */
101 #define BAC_NET_WHO_R 0x00
102 #define BAC_NET_IAM_R 0x01
103 #define BAC_NET_ICB_R 0x02
104 #define BAC_NET_REJ 0x03
105 #define BAC_NET_R_BUSY 0x04
106 #define BAC_NET_R_AVA 0x05
107 #define BAC_NET_INIT_RTAB 0x06
108 #define BAC_NET_INIT_RTAB_ACK 0x07
109 #define BAC_NET_EST_CON 0x08
110 #define BAC_NET_DISC_CON 0x09
112 static const true_false_string control_net_set_high = {
113 "network layer message, message type field present.",
114 "BACnet APDU, message type field absent."
117 static const true_false_string control_res_high = {
118 "Shall be zero, but is one.",
119 "Shall be zero and is zero."
121 static const true_false_string control_dest_high = {
122 "DNET, DLEN and Hop Count present. If DLEN=0: broadcast, dest. address field absent.",
123 "DNET, DLEN, DADR and Hop Count absent."
126 static const true_false_string control_src_high = {
127 "SNET, SLEN and SADR present, SLEN=0 invalid, SLEN specifies length of SADR",
128 "SNET, SLEN and SADR absent"
131 static const true_false_string control_expect_high = {
132 "BACnet-Confirmed-Request-PDU, a segment of BACnet-ComplexACK-PDU or Network Message expecting a reply present.",
133 "Other than a BACnet-Confirmed-Request-PDU, segment of BACnet-ComplexACK-PDU or network layer message expecting a reply present."
136 static const true_false_string control_prio_high_high = {
137 "Life Safety or Critical Equipment message.",
138 "Not a Life Safety or Critical Equipment message."
141 static const true_false_string control_prio_low_high = {
147 static int proto_bacnet = -1;
148 static int hf_bacnet_version = -1;
149 static int hf_bacnet_control = -1;
150 static int hf_bacnet_control_net = -1;
151 static int hf_bacnet_control_res1 = -1;
152 static int hf_bacnet_control_dest = -1;
153 static int hf_bacnet_control_res2 = -1;
154 static int hf_bacnet_control_src = -1;
155 static int hf_bacnet_control_expect = -1;
156 static int hf_bacnet_control_prio_high = -1;
157 static int hf_bacnet_control_prio_low = -1;
158 static int hf_bacnet_dnet = -1;
159 static int hf_bacnet_dlen = -1;
160 static int hf_bacnet_dadr_eth = -1;
161 static int hf_bacnet_dadr_tmp = -1;
162 static int hf_bacnet_snet = -1;
163 static int hf_bacnet_slen = -1;
164 static int hf_bacnet_sadr_eth = -1;
165 static int hf_bacnet_sadr_tmp = -1;
166 static int hf_bacnet_hopc = -1;
167 static int hf_bacnet_mesgtyp = -1;
168 static int hf_bacnet_vendor = -1;
169 static int hf_bacnet_perf = -1;
170 static int hf_bacnet_rejectreason = -1;
171 static int hf_bacnet_rportnum = -1;
172 static int hf_bacnet_portid = -1;
173 static int hf_bacnet_pinfolen = -1;
174 static int hf_bacnet_pinfo = -1;
176 static gint ett_bacnet = -1;
177 static gint ett_bacnet_control = -1;
180 dissect_bacnet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
184 proto_tree *bacnet_tree;
185 proto_tree *control_tree;
188 guint8 bacnet_version;
189 guint8 bacnet_control;
192 guint8 bacnet_mesgtyp;
193 guint8 bacnet_rejectreason;
194 guint8 bacnet_rportnum;
195 guint8 bacnet_pinfolen;
200 if (check_col(pinfo->cinfo, COL_PROTOCOL))
201 col_set_str(pinfo->cinfo, COL_PROTOCOL, "BACnet-NPDU");
203 if (check_col(pinfo->cinfo, COL_INFO))
204 col_set_str(pinfo->cinfo, COL_INFO, "Building Automation and Control Network NPDU");
207 bacnet_version = tvb_get_guint8(tvb, offset);
208 bacnet_control = tvb_get_guint8(tvb, offset+1);
212 bacnet_rejectreason = 0;
220 /* I don't know the length of the NPDU by know. Setting the length after dissection */
221 ti = proto_tree_add_item(tree, proto_bacnet, tvb, 0, -1, FALSE);
223 bacnet_tree = proto_item_add_subtree(ti, ett_bacnet);
225 proto_tree_add_uint_format(bacnet_tree, hf_bacnet_version, tvb,
227 bacnet_version,"Version: 0x%02x (%s)",bacnet_version,
228 (bacnet_version == 0x01)?"ASHRAE 135-1995":"unknown");
230 ct = proto_tree_add_uint_format(bacnet_tree, hf_bacnet_control,
232 bacnet_control,"Control: 0x%02x",bacnet_control);
233 control_tree = proto_item_add_subtree(ct,
235 proto_tree_add_boolean(control_tree, hf_bacnet_control_net,
236 tvb, offset, 1, bacnet_control);
237 proto_tree_add_boolean(control_tree, hf_bacnet_control_res1, tvb,
238 offset, 1, bacnet_control);
239 proto_tree_add_boolean(control_tree, hf_bacnet_control_dest, tvb,
240 offset, 1, bacnet_control);
241 proto_tree_add_boolean(control_tree, hf_bacnet_control_res2, tvb,
242 offset, 1, bacnet_control);
243 proto_tree_add_boolean(control_tree, hf_bacnet_control_src, tvb,
244 offset, 1, bacnet_control);
245 proto_tree_add_boolean(control_tree, hf_bacnet_control_expect, tvb,
246 offset, 1, bacnet_control);
247 proto_tree_add_boolean(control_tree, hf_bacnet_control_prio_high,
248 tvb, offset, 1, bacnet_control);
249 proto_tree_add_boolean(control_tree, hf_bacnet_control_prio_low,
250 tvb, offset, 1, bacnet_control);
252 if (bacnet_control & BAC_CONTROL_DEST) { /* DNET, DLEN, DADR */
253 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
254 tvb, offset, 2, FALSE);
256 bacnet_dlen = tvb_get_guint8(tvb, offset);
257 /* DLEN = 0 is broadcast on dest.network */
258 if( bacnet_dlen == 0) {
259 /* append to hf_bacnet_dlen: broadcast */
260 proto_tree_add_uint_format(bacnet_tree,
261 hf_bacnet_dlen, tvb, offset, 1, bacnet_dlen,
262 "Destination MAC Layer Address Length: %d indicates Broadcast on Destination Network",
266 } else if (bacnet_dlen==6) {
267 proto_tree_add_uint(bacnet_tree, hf_bacnet_dlen,
268 tvb, offset, 1, bacnet_dlen);
271 proto_tree_add_item(bacnet_tree,
272 hf_bacnet_dadr_eth, tvb, offset,
274 offset += bacnet_dlen;
275 } else if (bacnet_dlen<7) {
276 proto_tree_add_uint(bacnet_tree, hf_bacnet_dlen,
277 tvb, offset, 1, bacnet_dlen);
279 /* Other MAC formats should be included here */
280 proto_tree_add_item(bacnet_tree,
281 hf_bacnet_dadr_tmp, tvb, offset,
283 offset += bacnet_dlen;
285 proto_tree_add_uint_format(bacnet_tree,
286 hf_bacnet_dlen, tvb, offset, 1, bacnet_dlen,
287 "Destination MAC Layer Address Length: %d invalid!",
291 if (bacnet_control & BAC_CONTROL_SRC) { /* SNET, SLEN, SADR */
293 proto_tree_add_uint(bacnet_tree, hf_bacnet_snet,
294 tvb, offset, 2, tvb_get_ntohs(tvb, offset));
296 bacnet_slen = tvb_get_guint8(tvb, offset);
297 if( bacnet_slen == 0) { /* SLEN = 0 invalid */
298 proto_tree_add_uint_format(bacnet_tree,
299 hf_bacnet_slen, tvb, offset, 1, bacnet_slen,
300 "Source MAC Layer Address Length: %d invalid!",
303 } else if (bacnet_slen==6) {
305 proto_tree_add_uint(bacnet_tree, hf_bacnet_slen,
306 tvb, offset, 1, bacnet_slen);
309 proto_tree_add_item(bacnet_tree,
310 hf_bacnet_sadr_eth, tvb, offset,
312 offset += bacnet_slen;
313 } else if (bacnet_slen<6) { /* LON,ARCNET,MS/TP MAC */
315 proto_tree_add_uint(bacnet_tree, hf_bacnet_slen,
316 tvb, offset, 1, bacnet_slen);
318 /* Other MAC formats should be included here */
319 proto_tree_add_item(bacnet_tree,
320 hf_bacnet_sadr_tmp, tvb, offset,
322 offset += bacnet_slen;
324 proto_tree_add_uint_format(bacnet_tree,
325 hf_bacnet_slen, tvb, offset, 1, bacnet_slen,
326 "Source MAC Layer Address Length: %d invalid!",
331 if (bacnet_control & BAC_CONTROL_DEST) { /* Hopcount */
332 proto_tree_add_item(bacnet_tree, hf_bacnet_hopc,
333 tvb, offset, 1, FALSE);
336 /* Network Layer Message Type */
337 if (bacnet_control & BAC_CONTROL_NET) {
338 bacnet_mesgtyp = tvb_get_guint8(tvb, offset);
339 proto_tree_add_uint_format(bacnet_tree,
340 hf_bacnet_mesgtyp, tvb, offset, 1, bacnet_mesgtyp,
341 "Network Layer Message Type: %02x (%s)", bacnet_mesgtyp,
342 bacnet_mesgtyp_name(bacnet_mesgtyp));
346 * The standard says: "If Bit 7 of the control octet is 1 and
347 * the Message Type field contains a value in the range
348 * X'80' - X'FF', then a Vendor ID field shall be present (...)."
349 * We should not go any further in dissecting the packet if it's
350 * not present, but we don't know about that: No length field...
352 if ((bacnet_mesgtyp > 0x7f) && (bacnet_control == BAC_CONTROL_NET)) {
353 proto_tree_add_item(bacnet_tree, hf_bacnet_vendor,
354 tvb, offset, 2, FALSE);
356 /* attention: doesnt work here because of if(tree) */
357 call_dissector(data_handle,tvb_new_subset(tvb, offset,-1,tvb_reported_length_remaining(tvb,offset)), pinfo, tree);
359 /* Performance Index (in I-Could-Be-Router-To-Network) */
360 if (bacnet_mesgtyp == BAC_NET_ICB_R) {
361 proto_tree_add_item(bacnet_tree, hf_bacnet_perf,
362 tvb, offset, 1, FALSE);
365 /* Reason, DNET (in Reject-Message-To-Network) */
366 if (bacnet_mesgtyp == BAC_NET_REJ) {
367 bacnet_rejectreason = tvb_get_guint8(tvb, offset);
368 proto_tree_add_uint_format(bacnet_tree,
369 hf_bacnet_rejectreason,
371 bacnet_rejectreason, "Rejection Reason: %d (%s)",
373 bacnet_rejectreason_name(bacnet_rejectreason));
375 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
376 tvb, offset, 2, FALSE);
379 /* N*DNET (in Router-Busy-To-Network,Router-Available-To-Network) */
380 if ((bacnet_mesgtyp == BAC_NET_R_BUSY) ||
381 (bacnet_mesgtyp == BAC_NET_R_AVA) || (bacnet_mesgtyp == BAC_NET_IAM_R) ) {
382 while(tvb_reported_length_remaining(tvb, offset) > 1 ) {
383 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
384 tvb, offset, 2, FALSE);
388 /* Initialize-Routing-Table */
389 if ( (bacnet_mesgtyp == BAC_NET_INIT_RTAB) ||
390 (bacnet_mesgtyp == BAC_NET_INIT_RTAB_ACK) ) {
391 bacnet_rportnum = tvb_get_guint8(tvb, offset);
392 /* number of ports */
393 proto_tree_add_uint(bacnet_tree, hf_bacnet_rportnum,
394 tvb, offset, 1, bacnet_rportnum);
396 for(i=0; i>bacnet_rportnum; i++) {
398 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
399 tvb, offset, 2, FALSE);
402 proto_tree_add_item(bacnet_tree, hf_bacnet_portid,
403 tvb, offset, 1, FALSE);
405 /* Port Info Length */
406 bacnet_pinfolen = tvb_get_guint8(tvb, offset);
407 proto_tree_add_uint(bacnet_tree, hf_bacnet_pinfolen,
408 tvb, offset, 1, bacnet_pinfolen);
410 for(j=0; j>bacnet_pinfolen; j++){
412 proto_tree_add_item(bacnet_tree, hf_bacnet_pinfo,
413 tvb, offset, 1, FALSE);
419 proto_item_set_len(ti, offset);
422 /* dissect BACnet APDU
424 next_tvb = tvb_new_subset(tvb,offset,-1,-1);
425 if (bacnet_control & BAC_CONTROL_NET) {
426 /* Unknown function - dissect the payload as data */
427 call_dissector(data_handle, next_tvb, pinfo, tree);
429 /* APDU - call the APDU dissector */
430 call_dissector(bacapp_handle, next_tvb, pinfo, tree);
435 proto_register_bacnet(void)
437 static hf_register_info hf[] = {
438 { &hf_bacnet_version,
439 { "Version", "bacnet.version",
440 FT_UINT8, BASE_DEC, NULL, 0,
441 "BACnet Version", HFILL }
443 { &hf_bacnet_control,
444 { "Control", "bacnet.control",
445 FT_UINT8, BASE_HEX, NULL, 0xff,
446 "BACnet Control", HFILL }
448 { &hf_bacnet_control_net,
450 "bacnet.control_net",
451 FT_BOOLEAN, 8, TFS(&control_net_set_high),
452 BAC_CONTROL_NET, "BACnet Control", HFILL }
454 { &hf_bacnet_control_res1,
456 "bacnet.control_res1",
457 FT_BOOLEAN, 8, TFS(&control_res_high),
458 BAC_CONTROL_RES1, "BACnet Control", HFILL }
460 { &hf_bacnet_control_dest,
461 { "Destination Specifier",
462 "bacnet.control_dest",
463 FT_BOOLEAN, 8, TFS(&control_dest_high),
464 BAC_CONTROL_DEST, "BACnet Control", HFILL }
466 { &hf_bacnet_control_res2,
468 "bacnet.control_res2",
469 FT_BOOLEAN, 8, TFS(&control_res_high),
470 BAC_CONTROL_RES2, "BACnet Control", HFILL }
472 { &hf_bacnet_control_src,
473 { "Source specifier",
474 "bacnet.control_src",
475 FT_BOOLEAN, 8, TFS(&control_src_high),
476 BAC_CONTROL_SRC, "BACnet Control", HFILL }
478 { &hf_bacnet_control_expect,
480 "bacnet.control_expect",
481 FT_BOOLEAN, 8, TFS(&control_expect_high),
482 BAC_CONTROL_EXPECT, "BACnet Control", HFILL }
484 { &hf_bacnet_control_prio_high,
486 "bacnet.control_prio_high",
487 FT_BOOLEAN, 8, TFS(&control_prio_high_high),
488 BAC_CONTROL_PRIO_HIGH, "BACnet Control", HFILL }
490 { &hf_bacnet_control_prio_low,
492 "bacnet.control_prio_low",
493 FT_BOOLEAN, 8, TFS(&control_prio_low_high),
494 BAC_CONTROL_PRIO_LOW, "BACnet Control", HFILL }
497 { "Destination Network Address", "bacnet.dnet",
498 FT_UINT16, BASE_HEX, NULL, 0,
499 "Destination Network Address", HFILL }
502 { "Destination MAC Layer Address Length", "bacnet.dlen",
503 FT_UINT8, BASE_DEC, NULL, 0,
504 "Destination MAC Layer Address Length", HFILL }
506 { &hf_bacnet_dadr_eth,
507 { "Destination ISO 8802-3 MAC Address", "bacnet.dadr_eth",
508 FT_ETHER, BASE_HEX, NULL, 0,
509 "Destination ISO 8802-3 MAC Address", HFILL }
511 { &hf_bacnet_dadr_tmp,
512 { "Unknown Destination MAC", "bacnet.dadr_tmp",
513 FT_BYTES, BASE_HEX, NULL, 0,
514 "Unknown Destination MAC", HFILL }
517 { "Source Network Address", "bacnet.snet",
518 FT_UINT16, BASE_HEX, NULL, 0,
519 "Source Network Address", HFILL }
522 { "Source MAC Layer Address Length", "bacnet.slen",
523 FT_UINT8, BASE_DEC, NULL, 0,
524 "Source MAC Layer Address Length", HFILL }
526 { &hf_bacnet_sadr_eth,
527 { "SADR", "bacnet.sadr_eth",
528 FT_ETHER, BASE_HEX, NULL, 0,
529 "Source ISO 8802-3 MAC Address", HFILL }
531 { &hf_bacnet_sadr_tmp,
532 { "Unknown Source MAC", "bacnet.sadr_tmp",
533 FT_BYTES, BASE_HEX, NULL, 0,
534 "Unknown Source MAC", HFILL }
537 { "Hop Count", "bacnet.hopc",
538 FT_UINT8, BASE_DEC, NULL, 0,
541 { &hf_bacnet_mesgtyp,
542 { "Message Type", "bacnet.mesgtyp",
543 FT_UINT8, BASE_HEX, NULL, 0,
544 "Message Type", HFILL }
547 { "Vendor ID", "bacnet.vendor",
548 FT_UINT16, BASE_HEX, NULL, 0,
552 { "Performance Index", "bacnet.perf",
553 FT_UINT8, BASE_DEC, NULL, 0,
554 "Performance Index", HFILL }
556 { &hf_bacnet_rejectreason,
557 { "Reject Reason", "bacnet.rejectreason",
558 FT_UINT8, BASE_DEC, NULL, 0,
559 "Reject Reason", HFILL }
561 { &hf_bacnet_rportnum,
562 { "Number of Port Mappings", "bacnet.rportnum",
563 FT_UINT8, BASE_DEC, NULL, 0,
564 "Number of Port Mappings", HFILL }
566 { &hf_bacnet_pinfolen,
567 { "Port Info Length", "bacnet.pinfolen",
568 FT_UINT8, BASE_DEC, NULL, 0,
569 "Port Info Length", HFILL }
572 { "Port ID", "bacnet.portid",
573 FT_UINT8, BASE_HEX, NULL, 0,
577 { "Port Info", "bacnet.pinfo",
578 FT_UINT8, BASE_HEX, NULL, 0,
583 static gint *ett[] = {
588 proto_bacnet = proto_register_protocol("Building Automation and Control Network NPDU",
591 proto_register_field_array(proto_bacnet, hf, array_length(hf));
592 proto_register_subtree_array(ett, array_length(ett));
594 register_dissector("bacnet", dissect_bacnet, proto_bacnet);
598 proto_reg_handoff_bacnet(void)
600 dissector_handle_t bacnet_handle;
602 bacnet_handle = find_dissector("bacnet");
603 dissector_add("bvlc.function", 0x04, bacnet_handle);
604 dissector_add("bvlc.function", 0x09, bacnet_handle);
605 dissector_add("bvlc.function", 0x0a, bacnet_handle);
606 dissector_add("bvlc.function", 0x0b, bacnet_handle);
607 bacapp_handle = find_dissector("bacapp");
608 data_handle = find_dissector("data");