2 * Routines for BACnet (NPDU) dissection
3 * Copyright 2001, Hartmut Mueller <hartmut@abmlinux.org>, FH Dortmund
5 * $Id: packet-bacnet.c,v 1.4 2001/06/18 02:17:44 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"
52 static dissector_table_t bacnet_dissector_table;
55 bacnet_mesgtyp_name (guint8 bacnet_mesgtyp){
56 static const char *type_names[] = {
57 "Who-Is-Router-To-Network",
58 "I-Am-Router-To-Network",
59 "I-Could-Be-Router-To-Network",
60 "Reject-Message-To-Network",
61 "Router-Busy-To-Network",
62 "Router-Available-To-Network",
63 "Initialize-Routing-Table",
64 "Initialize-Routing-Table-Ack",
65 "Establish-Connection-To-Network",
66 "Disconnect-Connection-To-Network"
68 if(bacnet_mesgtyp < 0x0a) {
69 return type_names[bacnet_mesgtyp];
71 return (bacnet_mesgtyp < 0x80)? "Reserved for Use by ASHRAE" : "Vendor Proprietary Message";
76 bacnet_rejectreason_name (guint8 bacnet_rejectreason) {
77 static const char *type_names[] = {
79 "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.",
80 "The router is busy and unable to accept messages for the specified DNET at the present time.",
81 "It is an unknown network layer message type.",
82 "The message is too long to be routed to this DNET.",
83 "The router is no longer directly connected to DNET but can reconnect if requested.",
84 "The router is no longer directly connected to DNET and cannot reconnect even if requested."
86 return (bacnet_rejectreason > 6)? "Invalid Rejection Reason.": type_names[bacnet_rejectreason];
89 /* Network Layer Control Information */
90 #define BAC_CONTROL_NET 0x80
91 #define BAC_CONTROL_RES1 0x40
92 #define BAC_CONTROL_DEST 0x20
93 #define BAC_CONTROL_RES2 0x10
94 #define BAC_CONTROL_SRC 0x08
95 #define BAC_CONTROL_EXPECT 0x04
96 #define BAC_CONTROL_PRIO_HIGH 0x02
97 #define BAC_CONTROL_PRIO_LOW 0x01
99 /* Network Layer Message Types */
100 #define BAC_NET_WHO_R 0x00
101 #define BAC_NET_IAM_R 0x01
102 #define BAC_NET_ICB_R 0x02
103 #define BAC_NET_REJ 0x03
104 #define BAC_NET_R_BUSY 0x04
105 #define BAC_NET_R_AVA 0x05
106 #define BAC_NET_INIT_RTAB 0x06
107 #define BAC_NET_INIT_RTAB_ACK 0x07
108 #define BAC_NET_EST_CON 0x08
109 #define BAC_NET_DISC_CON 0x09
111 static const true_false_string control_net_set_high = {
112 "network layer message, message type field present.",
113 "BACnet APDU, message type field absent."
116 static const true_false_string control_res_high = {
117 "Shall be zero, but is one.",
118 "Shall be zero and is zero."
120 static const true_false_string control_dest_high = {
121 "DNET, DLEN and Hop Count present. If DLEN=0: broadcast, dest. address field absent.",
122 "DNET, DLEN, DADR and Hop Count absent."
125 static const true_false_string control_src_high = {
126 "SNET, SLEN and SADR present, SLEN=0 invalid, SLEN specifies length of SADR",
127 "SNET, SLEN and SADR absent"
130 static const true_false_string control_expect_high = {
131 "BACnet-Confirmed-Request-PDU, a segment of BACnet-ComplexACK-PDU or Network Message expecting a reply present.",
132 "Other than a BACnet-Confirmed-Request-PDU, segment of BACnet-ComplexACK-PDU or network layer message expecting a reply present."
135 static const true_false_string control_prio_high_high = {
136 "Life Safety or Critical Equipment message.",
137 "Not a Life Safety or Critical Equipment message."
140 static const true_false_string control_prio_low_high = {
146 static int proto_bacnet = -1;
147 static int hf_bacnet_version = -1;
148 static int hf_bacnet_control = -1;
149 static int hf_bacnet_control_net = -1;
150 static int hf_bacnet_control_res1 = -1;
151 static int hf_bacnet_control_dest = -1;
152 static int hf_bacnet_control_res2 = -1;
153 static int hf_bacnet_control_src = -1;
154 static int hf_bacnet_control_expect = -1;
155 static int hf_bacnet_control_prio_high = -1;
156 static int hf_bacnet_control_prio_low = -1;
157 static int hf_bacnet_dnet = -1;
158 static int hf_bacnet_dlen = -1;
159 static int hf_bacnet_dadr_eth = -1;
160 static int hf_bacnet_dadr_tmp = -1;
161 static int hf_bacnet_snet = -1;
162 static int hf_bacnet_slen = -1;
163 static int hf_bacnet_sadr_eth = -1;
164 static int hf_bacnet_sadr_tmp = -1;
165 static int hf_bacnet_hopc = -1;
166 static int hf_bacnet_mesgtyp = -1;
167 static int hf_bacnet_vendor = -1;
168 static int hf_bacnet_perf = -1;
169 static int hf_bacnet_rejectreason = -1;
170 static int hf_bacnet_rportnum = -1;
171 static int hf_bacnet_portid = -1;
172 static int hf_bacnet_pinfolen = -1;
173 static int hf_bacnet_pinfo = -1;
175 static gint ett_bacnet = -1;
176 static gint ett_bacnet_control = -1;
179 dissect_bacnet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
183 proto_tree *bacnet_tree;
184 proto_tree *control_tree;
187 guint8 bacnet_version;
188 guint8 bacnet_control;
189 guint8 bacnet_control_net;
192 guint8 bacnet_mesgtyp;
193 guint8 bacnet_rejectreason;
194 guint8 bacnet_rportnum;
195 guint8 bacnet_pinfolen;
200 if (check_col(pinfo->fd, COL_PROTOCOL))
201 col_set_str(pinfo->fd, COL_PROTOCOL, "BACnet-NPDU");
203 if (check_col(pinfo->fd, COL_INFO))
204 col_set_str(pinfo->fd, 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);
209 bacnet_control_net = tvb_get_guint8(tvb, offset+1) & BAC_CONTROL_NET;
213 bacnet_rejectreason = 0;
221 /* I don't know the length of the NPDU by know. Setting the length after dissection */
222 ti = proto_tree_add_item(tree, proto_bacnet, tvb, 0, 0, FALSE);
224 bacnet_tree = proto_item_add_subtree(ti, ett_bacnet);
226 proto_tree_add_uint_format(bacnet_tree, hf_bacnet_version, tvb,
228 bacnet_version,"Version: 0x%02x (%s)",bacnet_version,
229 (bacnet_version == 0x01)?"ASHRAE 135-1995":"unknown");
231 ct = proto_tree_add_uint_format(bacnet_tree, hf_bacnet_control,
233 bacnet_control,"Control: 0x%02x",bacnet_control);
234 control_tree = proto_item_add_subtree(ct,
236 proto_tree_add_boolean(control_tree, hf_bacnet_control_net,
237 tvb, offset, 1, bacnet_control);
238 proto_tree_add_boolean(control_tree, hf_bacnet_control_res1, tvb,
239 offset, 1, bacnet_control);
240 proto_tree_add_boolean(control_tree, hf_bacnet_control_dest, tvb,
241 offset, 1, bacnet_control);
242 proto_tree_add_boolean(control_tree, hf_bacnet_control_res2, tvb,
243 offset, 1, bacnet_control);
244 proto_tree_add_boolean(control_tree, hf_bacnet_control_src, tvb,
245 offset, 1, bacnet_control);
246 proto_tree_add_boolean(control_tree, hf_bacnet_control_expect, tvb,
247 offset, 1, bacnet_control);
248 proto_tree_add_boolean(control_tree, hf_bacnet_control_prio_high,
249 tvb, offset, 1, bacnet_control);
250 proto_tree_add_boolean(control_tree, hf_bacnet_control_prio_low,
251 tvb, offset, 1, bacnet_control);
253 if (bacnet_control & BAC_CONTROL_DEST) { /* DNET, DLEN, DADR */
254 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
255 tvb, offset, 2, FALSE);
257 bacnet_dlen = tvb_get_guint8(tvb, offset);
258 /* DLEN = 0 is broadcast on dest.network */
259 if( bacnet_dlen == 0) {
260 /* append to hf_bacnet_dlen: broadcast */
261 proto_tree_add_uint_format(bacnet_tree,
262 hf_bacnet_dlen, tvb, offset, 1, bacnet_dlen,
263 "Destination MAC Layer Address Length: %d indicates Broadcast on Destination Network",
267 } else if (bacnet_dlen==6) {
268 proto_tree_add_uint(bacnet_tree, hf_bacnet_dlen,
269 tvb, offset, 1, bacnet_dlen);
272 proto_tree_add_item(bacnet_tree,
273 hf_bacnet_dadr_eth, tvb, offset,
275 offset += bacnet_dlen;
276 } else if (bacnet_dlen<7) {
277 proto_tree_add_uint(bacnet_tree, hf_bacnet_dlen,
278 tvb, offset, 1, bacnet_dlen);
280 /* Other MAC formats should be included here */
281 proto_tree_add_item(bacnet_tree,
282 hf_bacnet_dadr_tmp, tvb, offset,
284 offset += bacnet_dlen;
286 proto_tree_add_uint_format(bacnet_tree,
287 hf_bacnet_dlen, tvb, offset, 1, bacnet_dlen,
288 "Destination MAC Layer Address Length: %d invalid!",
292 if (bacnet_control & BAC_CONTROL_SRC) { /* SNET, SLEN, SADR */
294 proto_tree_add_uint(bacnet_tree, hf_bacnet_snet,
295 tvb, offset, 2, tvb_get_ntohs(tvb, offset));
297 bacnet_slen = tvb_get_guint8(tvb, offset);
298 if( bacnet_slen == 0) { /* SLEN = 0 invalid */
299 proto_tree_add_uint_format(bacnet_tree,
300 hf_bacnet_slen, tvb, offset, 1, bacnet_slen,
301 "Source MAC Layer Address Length: %d invalid!",
304 } else if (bacnet_slen==6) {
306 proto_tree_add_uint(bacnet_tree, hf_bacnet_slen,
307 tvb, offset, 1, bacnet_slen);
310 proto_tree_add_item(bacnet_tree,
311 hf_bacnet_sadr_eth, tvb, offset,
313 offset += bacnet_slen;
314 } else if (bacnet_slen<6) { /* LON,ARCNET,MS/TP MAC */
316 proto_tree_add_uint(bacnet_tree, hf_bacnet_slen,
317 tvb, offset, 1, bacnet_slen);
319 /* Other MAC formats should be included here */
320 proto_tree_add_item(bacnet_tree,
321 hf_bacnet_sadr_tmp, tvb, offset,
323 offset += bacnet_slen;
325 proto_tree_add_uint_format(bacnet_tree,
326 hf_bacnet_slen, tvb, offset, 1, bacnet_slen,
327 "Source MAC Layer Address Length: %d invalid!",
332 if (bacnet_control & BAC_CONTROL_DEST) { /* Hopcount */
333 proto_tree_add_item(bacnet_tree, hf_bacnet_hopc,
334 tvb, offset, 1, FALSE);
337 /* Network Layer Message Type */
338 if (bacnet_control & BAC_CONTROL_NET) {
339 bacnet_mesgtyp = tvb_get_guint8(tvb, offset);
340 proto_tree_add_uint_format(bacnet_tree,
341 hf_bacnet_mesgtyp, tvb, offset, 1, bacnet_mesgtyp,
342 "Network Layer Message Type: %02x (%s)", bacnet_mesgtyp,
343 bacnet_mesgtyp_name(bacnet_mesgtyp));
347 * The standard says: "If Bit 7 of the control octet is 1 and
348 * the Message Type field contains a value in the range
349 * X'80' - X'FF', then a Vendor ID field shall be present (...)."
350 * We should not go any further in dissecting the packet if it's
351 * not present, but we don't know about that: No length field...
353 if ((bacnet_mesgtyp > 0x7f) && (bacnet_control == BAC_CONTROL_NET)) {
354 proto_tree_add_item(bacnet_tree, hf_bacnet_vendor,
355 tvb, offset, 2, FALSE);
357 /* attention: doesnt work here because of if(tree) */
358 dissect_data(tvb, offset, pinfo, tree);
360 /* Performance Index (in I-Could-Be-Router-To-Network) */
361 if (bacnet_mesgtyp == BAC_NET_ICB_R) {
362 proto_tree_add_item(bacnet_tree, hf_bacnet_perf,
363 tvb, offset, 1, FALSE);
366 /* Reason, DNET (in Reject-Message-To-Network) */
367 if (bacnet_mesgtyp == BAC_NET_REJ) {
368 bacnet_rejectreason = tvb_get_guint8(tvb, offset);
369 proto_tree_add_uint_format(bacnet_tree,
370 hf_bacnet_rejectreason,
372 bacnet_rejectreason, "Rejection Reason: %d (%s)",
374 bacnet_rejectreason_name(bacnet_rejectreason));
376 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
377 tvb, offset, 2, FALSE);
380 /* N*DNET (in Router-Busy-To-Network,Router-Available-To-Network) */
381 if ((bacnet_mesgtyp == BAC_NET_R_BUSY) ||
382 (bacnet_mesgtyp == BAC_NET_R_AVA) || (bacnet_mesgtyp == BAC_NET_IAM_R) ) {
383 while(tvb_reported_length_remaining(tvb, offset) > 1 ) {
384 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
385 tvb, offset, 2, FALSE);
389 /* Initialize-Routing-Table */
390 if ( (bacnet_mesgtyp == BAC_NET_INIT_RTAB) ||
391 (bacnet_mesgtyp == BAC_NET_INIT_RTAB_ACK) ) {
392 bacnet_rportnum = tvb_get_guint8(tvb, offset);
393 /* number of ports */
394 proto_tree_add_uint(bacnet_tree, hf_bacnet_rportnum,
395 tvb, offset, 1, bacnet_rportnum);
397 for(i=0; i>bacnet_rportnum; i++) {
399 proto_tree_add_item(bacnet_tree, hf_bacnet_dnet,
400 tvb, offset, 2, FALSE);
403 proto_tree_add_item(bacnet_tree, hf_bacnet_portid,
404 tvb, offset, 1, FALSE);
406 /* Port Info Length */
407 bacnet_pinfolen = tvb_get_guint8(tvb, offset);
408 proto_tree_add_uint(bacnet_tree, hf_bacnet_pinfolen,
409 tvb, offset, 1, bacnet_pinfolen);
411 for(j=0; j>bacnet_pinfolen; j++){
413 proto_tree_add_item(bacnet_tree, hf_bacnet_pinfo,
414 tvb, offset, 1, FALSE);
420 proto_item_set_len(ti, offset);
423 /* dissect BACnet APDU
425 next_tvb = tvb_new_subset(tvb,offset,-1,-1);
426 /* Code from Guy Harris */
427 if (!dissector_try_port(bacnet_dissector_table,
428 bacnet_control_net, next_tvb, pinfo, tree)) {
429 /* Unknown function - dissect the payload as data */
430 dissect_data(next_tvb, 0, 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);
595 bacnet_dissector_table = register_dissector_table("bacnet_control_net");
599 proto_reg_handoff_bacnet(void)
601 dissector_add("bvlc.function", 0x04, dissect_bacnet, proto_bacnet);
602 dissector_add("bvlc.function", 0x09, dissect_bacnet, proto_bacnet);
603 dissector_add("bvlc.function", 0x0a, dissect_bacnet, proto_bacnet);
604 dissector_add("bvlc.function", 0x0b, dissect_bacnet, proto_bacnet);