Tuesday, April 02, 2013

Open Source and Closed Protocols

There isn't a better way to learn a protocol for me than reading the RFC and watching Wireshark captures and decodes. However, it becomes difficult when we're talking about a closed proprietary protocol.

Recently, I've been creating new protocol modules for the Net::Frame Perl module suite. Hot Standby Router Protocol (HSRP) is a Cisco proprietary protocol but is detailed in RFC 2281. However, that's only version 1. Version 2 isn't in an open RFC. Another example is Cisco Discovery Protocol (CDP) which has no RFC for version 1 or version 2. Luckily, there is plenty of information on the Internet.

I couldn't get all the information for all the CDP message types, but I'm about 90% there. A more interesting note was in the CDP header itself. There is a checksum that's described as the "normal Internet checksum". However, when I implemented that in a Net::Frame::Layer::CDP module, it didn't work. What was the problem?

A Google search lead me to the source code for Wireshark and a file called packet-cdp.c. A comment beginning on line 230 explains:

"CDP doesn't adhere to RFC 1071 section 2. (B). It incorrectly assumes checksums are calculated on a big endian platform, therefore i.s.o. padding odd sized data with a zero byte _at the end_ it sets the last big endian _word_ to contain the last network _octet_. This byteswap has to be done on the last octet of network data before feeding it to the Internet checksum routine. CDP checksumming code has a bug in the addition of this last _word_ as a signed number into the long word intermediate checksum. When reducing this long to word size checksum an off-by-one error can be made. This off-by-one error is compensated for in the last _word_ of the network data."

That meant I needed some "massaging" of my payload before sending to my Internet checksum routine. A quick proof of concept proved correct and then some solicitation for simplification produced this:

   [...]

   if (length( $payload )%2) {
      if (substr($payload, -1) ge "\x80") {
         substr $payload, -1, 1, chr(ord(substr $payload, -1) - 1);
         substr $payload, -1, 0, "\xff";
      } else {
         substr $payload, -1, 0, "\0";
      }
   }

   [...]

   $self->checksum(inetChecksum($phpkt));

I also found a very long thread in a mailing list archive that describes how some smart guys found this problem and came up with the Wireshark solution. Thanks for open source - even on closed protocols!

 

Copyright © VinsWorld. All Rights Reserved.