RS485 BACnet sniffing

Our control attaches to a BACnet server via RS485 and we are trying to capture comms between the two. Currently we have the IO Ninja TX+/TX- and GND attached to the the servers TX, RX and Gnd. The issue we are having is that the bytes do not look correct. Here is the dump of what the internal controller packet memory sees(notice the 55FF which is correct):

00> TX: 55FF000C200000A9
00> RX: 55FF05200C0018D3010C000106C0A80182E38B
00> RX: 02759A0E0C034000021E09081F537E
00> TX: 55FF070C2000003E
55FF060C200113E30120000106C0A80182E38BFF309A0E0C034000021E
00> TX: 55FF000C200000A9
00> TX: 55FF000C200000A9
00> TX: 55FF000C200000A9

The serial tap trace looks like the byte start is is taken on the wrong clock edge(55FF becomes 5500.) The serial settings are correct and we don't see any other filters/tweaks we could try:
Capture.JPG

A BACnet vendors likes to runs a 485 to USB and feeds that into wire shark on a PC but we would rather stick with the serial tap as it's important to see the data in real time(USB capture on a PC can be to slow.)

Any advice is appreciated.

After changing the wiring(flipping TX+ and TX-) we now have the correct data showing. If there any way to have the packets broken into lines based on byte patterns or time stamps(the current view has to packets lumped into the same line?)

Thanks.

IO Ninja
TX + TX
TX- RX
GND Gnd

Capture.JPG

We looked at the Modbus Packet Templates because ModBus RTU uses time gaps to signal the Beginning/End of a packet. It is not clear how the time stamp works with this as timestamps are not shown in the templates. Tried doing a straight hex dump of the log file to see if we could import it into WireShark but when you save the log as a text file, WireShark takes it as a single packet. Any way to break the packets apart by gaps in the time stamp?

Capture.JPG

Is there any way to break these packets apart or export the log?

Thanks

We have come back to looking to have IO Ninja decode BACnet and maybe someone could help us understand how using a Jancy script could work with BACnet MS/TP packets. Defining the different fields is understandable until you get BACnet packets that have specific data which is attached onto the end of the packet. How do you tell Jacncy that if the packet length goes beyond the CRC you need to process it?

Thanks

![alt text](4455f381-1d23-43f5-9912-de5e910a6e86-image.png image url)

import "crc16.jnc"

enum MyEnum: int8_t {
	Value1,
	Value2,
	Value3,
}

pragma(Alignment, 1);

[ displayName = "BACnet packet" ]
struct MyPacket {
	[ displayName = "Preamble" ]
	int16_t m_field1;

	[ displayName = "Frame type" ]
	int8_t m_field2;

	[ displayName = "Destination Address" ]
	int8_t m_field3;

	[ displayName = "Source Address" ]
	int8_t m_field4;

	[ displayName = "Length" ]
	uint16_t m_length;

	[ displayName = "CRC-8" ]
	uint8_t m_checksum;

	[ userAction = "Initialize" ]
	void initialize(char const* name) {
		m_field1 = MyEnum.Value1;
		m_field2 = 2;
		m_field3 = 3;

		size_t size = strlen(name) + 1;
		if (size > sizeof(m_name))
			size = sizeof(m_name);

		memcpy(m_name, name, size);
	}

	[
		userAction = "Update length",
		autorun = "Auto-update length"
	]
	void updateLength() {
		m_length = dynamic sizeof(this);
	}

	[
		userAction = "Update checksum",
		autorun = "Auto-update checksum"
	]
	void updateChecksum() {
		m_checksum = crc16_ansi(this, dynamic sizeof(this));
	}
}

Hello John,

I'm really sorry for not answering your questions earlier; somehow this topic went under my radar...

The very first question (garbage input) you actually solved by yourself (by reversing polarity). Alas, there's a bit of trial and error involved when connecting RS485 lines; usually, A is negative and B is positive, but on some devices, it could be the opposite.

Now, to the next post -- how to break data into packets in the log. For simple visual aids, I'd recommend starting with the Regex Markup feature. It's very simple and could work quite well in your case, actually. Each packet is prefixed with a fixed preamble 0x55 0xff, so you can define a regex as \x55\xff and set "Markup Mode" to "Add delimiters before matches". All packets will be visually separated from one another with red lines.

659467c2-d6cb-4822-a90a-006a81b3dc4b-image.png

Now, if we are not talking about visual aids but rather want to export it into Wireshark as a sequence as separate packets, it's a different story. Here, you would need to write a custom script (using any language of your choise) that would parse the data from .njlog file (generated by IO Ninja), split it into packets, then write those to a .pcap file programmatically.

IO Ninja only knows how to generate a .pcap file when it captures actual network packets via Pcap Sniffer, Ethernet Tap and similar plugins. For Serial Tap and most other plugins, however, the data is a continuous data stream, and it doesn't map to the .pcap format very well -- at least, not well enough to do it automatically. However, with a custom script everything is possible. If you choose this path -- the .njlog file format is very simple, and all the related structure definitions are open source:

<ioninja-dir>/scripts/api/log_RecordCode.jnc
<ioninja-dir>/scripts/api/log_RecordFile.jnc

Finally, your last question is about how to use Jancy to work with BACnet MSTP. It's also possible, of course, but let's clarify your end goal here. Do you want to (a) conveniently prepare packets for sending -- i.e., fill the fields using the property grid, automatically calculate checksums, etc? Or (b) parse the incoming data and show the decoded packets in the log?

For (a) you would want to create a packet template. Here's a simple introduction and tutorial: https://ioninja.com/doc/developer-manual/tutorial-ias-packet.html. Also, for your reference, you can take a look at the Modbus Packet Template library (Packet Template Pane -> Load Stock Script -> Modbus RTU, sources are at /scripts/packets/ModbusRtu.jnc).

If it's (b) that you want, then you need a protocol analyzer plugin. This is a bit more challenging task. Here's a short tutorial: https://ioninja.com/doc/developer-manual/tutorial-plugin-analyzer.html to get you started. Also, I'd recommend skimming through the sources of the Modbus Analyzer plugin for a working real-world protocol analyzer. Sources could be found at /scripts/plugins/Modbus/

Let me know which one (a or b) is your priority, and we'll focus on that. Maybe, we can create a quick skeleton script for you to work on.

Really appreciate the information.
I tried using RegEx Markup with default Session settings and it does not want to break the packets up.
Capture.JPG

Using Session->Settings->Log Engine->Binary Data->Binary Data Merge with a value of 3msec it looks better but I'm really after the timestamp of each packet.
Capture.JPG

We have tried using a 485 to USB to monitor the BACnet traffic, but there is a point where the tokens are shown with the exact same timestamp and I suspect that the USB on the PC can't keep up with data which is surprising given the link speed is only 38400.
Capture.JPG

Thanks again for the help.

@Vladimir I think at this point it would be nice to do a basic packet decode of sender/receiver and and packet type as it is only one byte long so 255 possibilities.

How do you get to the Transmit pane if it is greyed out(un-selectable) ?

@vladimir After adding "Force Latin-1 encoding" to the Log Regex Markup settings I'm getting color highlights but no new timestamps when it see's the pattern.
Capture.JPG

Is there any way to get the log to show a timestamp at the beginning of each 55 FF pattern?

Thanks

Capture.JPG

How do you get to the Transmit pane if it is greyed out(un-selectable) ?

The Transmit pane is greyed out in the Serial Tap plugin because it's a read-only sniffer device. You can transmit using the Serial Terminal plugin and a regular USB-to-RS485 adapter.

I tried using RegEx Markup with default Session settings and it does not want to break the packets up.

After adding "Force Latin-1 encoding" to the Log Regex Markup settings I'm getting color highlights but no new timestamps when it see's the pattern.

Right, I should have mentioned that Latin-1 encoding might be necessary when the pattern contains invalid UTF-8 sequences (such as \x55\xff).

In general, using timestamps to split the data stream into packets is not a reliable enough approach (unless every captured byte is precisely timestamped). Serial Tap doesn't do that; instead, timestamps are assigned to the whole blocks of data as they are received over USB. There are multiple layers of buffering involved (at both the Serial Tap and the PC side), so the timing could be distorted to some extent, and bytes from different packets could end up being read within the same block. It's much better to parse the stream and split it into packets based on the actual data contents.

I've created a draft of the BACnet MSTP protocol analyzer for you: https://tibbo.com/downloads/archive/ioninja/.internal/scripts/BacNetMsTp.7z

It breaks the stream into BACnet frames, gives a human-readable representation of the frame header, and highlights the payload (if any).

ae83d05d-099d-4d65-a9e8-3902fff59533-image.png

Feel free to modify or extend it to your liking. However, please note, that ioninja-5.5.0 has a regression that prevents log.representStruct() used in this script from operating properly (it's already fixed but the service release is not out yet). So, to use this plugin, please either (a) roll back to ioninja-5.4.2 or (b) use the internal build ioninja-5.5.0-a: https://tibbo.com/downloads/archive/ioninja/.internal/prerelease/ioninja-5.5.0-a-windows-amd64.7z

Let me know if it works for you, and feel free to ask me anything about the internal implementation of the analyzer script.

P.S. FYI, just as with any representation generated by log.representStruct(), you can click on a header field, and the corresponding data bytes will be highlighted below -- so that you can see the mapping between header fields and data bytes.

Wow, this is great. Couple questions:

  • The Pre-release does not have an install script(msi file.) Can I just drop the folders into the current C:\Program Files\Tibbo\IO Ninja directory?

Loaded your analyzer(on version 5.5.0) using Settings->Add-on Plugins->Layers->Add and selected BacNetMsTp.njplg(this was a guess).

  • It ran very well except that after a large packet came through it seemed to stop(see timestamp 16:21:35 +00:06:437. Looked through the scripts(everything you sent) to see if there was a max number of bytes for a packet but did not find one.

Capture.JPG

Thanks again.

Version 5.5.0 will not display the contents of BACnet headers properly (after you click [+] to expand). The pre-release .7z file is a portable archive; extract it anywhere and run ioninja-5.5.1\bin\ioninja.exe.

Re "stop" of the analyzer -- could you share the original .njlog with the large packet?

I suspect that the reason is the incorrect handling of padding in the script (it went out of sync immediately after the large frame). Try removing the padding:

size_t frameSize =
	sizeof(BacNetMsTpHdr) +  // header
	hdr.m_length +           // payload
	sizeof(uint16_t);        // CRC
	// (hdr.m_length & 1);      // padding <---

Also, it makes sense to wait and sync on \x55\xff before starting buffering the packet; this way, the parser will re-sync after synchronization is lost (this still can occasionally happen because RS485 does not guarantee lossless delivery).

P.S.
For simplicity, just sync on 0x55; that should be good enough.

In BacNetMsTpParser.parse(...), add this:

	while (p < end) {
		void const* p0 = p;

		// 0. sync on 0x55

		if (!m_buffer.m_size) {
			void const* preamble = memchr(p, 0x55, end - p);
			if (!preamble)
				break;

			p = preamble; // skip everything before the preamble
		}

		...

Re "stop" of the analyzer -- could you share the original .njlog with the large packet?

How do I send you the log that is out of sync. I'll try your fixes and report back. Also here are a couple frame types if you want to add them to the analyzer:

enum BacNetMsTcpFrameType: uint8_t {
	Token                       = 0,
	PollForMaster               = 1,
	PollForMasterReply          = 2,
	TestRequest                 = 3,
	TestResponse                = 4,
	BacNetDataExpectingReply    = 5,
	BacNetDataNotExpectingReply = 6,
	ReplyPostponed              = 7,
	ExtendedDataExpectingReply	= 32,
	ExtendedDataNotExpectingReply = 33,
	CustomFrame					= 254,
	NoFrame						= 255
}

@vladimir Removing the padding gives this trying to load the Analyzer:
Capture.JPG

Inserting the changes to BacNetMsTpParser.jnc(without the modification to the padding) it runs very well with an occasional issues toward the end of a large packet:
Capture.JPG

While using this analyzer should we have been running Settings->Log Engine->Binary Data->Binary Data Merge with a 3msec threshold. Should we still be using this ?

Just want to also add how nice it is to be able to see the packets.

Thanks.

How do I send you the log that is out of sync

Hit "Reply" (not "Post quick reply"); the rightmost button on the toolbar is "Upload File". 7-zip it first so that it has the .7z extension permitted by this forum.

Removing the padding gives this trying to load the Analyzer:

Most likely, you just commented out the padding code, but forgot to add a semicolon ; at the end of the statement. In Jancy, just like in C, declarations and expression statements must be terminated with a semicolon.

While using this analyzer should we have been running Settings->Log Engine->Binary Data->Binary Data Merge with a 3msec threshold.

This won't affect the analyzer, but I think it's completely unnecessary now. You will see packets boundaries anyway -- and not just boundaries, decoded header fields, too.

Most likely, you just commented out the padding code, but forgot to add a semicolon ;

Exactly correct as I had missed the '+' on the line above.

Traces look very good now as I will try to move to your latest code. Tried using the upload images(right most icon) to send a 7z zip file but I don't have privileges for it.

Capture.JPG

Tried using the upload images(right most icon) to send a 7z zip file but I don't have privileges for it.

Not "Upload image", "Upload file". Does your "Reply toolbar" have this button?

8537fcf3-5daa-4d0d-8b6e-dd4ba104fe29-image.png

@vladimir That icon does not show on my Firefox. I'll try a different browser.
Capture.JPG