http://www.perlmonks.org?node_id=1026156

VinsWorldcom has asked for the wisdom of the Perl Monks concerning the following question:

Greetings,

I found the solution for my problem here http://fossies.org/dox/wireshark-1.9.1/packet-cdp_8c_source.html starting at line 230. Of course the solution is in C and I need it in Perl (obviously). I already did it and it's working (code below), but there HAS to be a better way than my hack job so if you have some time and want to show me a more elegant way, please proceed.

Background:

Cisco Discovery Protocol (CDP) uses the internet checksum routine to calculate it's packet checksum. Of course there's a bug (or perhaps proprietary glitch by design). In any case, the standard internet checksum sub (which I already have) will work, as long as you "massage" the input to compensate. A nice explanation and C code is provided in the link above.

Essentially, if the length of the CDP packet data is odd, internet checksum routine would add \x00 padding before calculating. The bug prevents this from working. Instead, because of an endian-ness assumption, I need to determine if the packet is odd length *BEFORE* sending to inet checksum. If so, instead of adding \x00 padding, we strip the last byte, add the \x00 padding and the re-add the stripped last byte. With an even packet length now, there's one more compensation.

If after the byte-swap, the last byte is now greater than \x80, we need to decrement the last two byes (the added \x00 padding and the re-added original last byte) each by 1.

I created a quick script to pass some data into the prepchksum() sub I wrote. The BEFORE represents the hex (unpack) versions of real CDP packet data payload (after the header) and the output I expect for each follows in the AFTER output:

# Even length payload = 24 bytes, no change BEFORE = 00010006523200060013436973636f203132303030475352 AFTER = 00010006523200060013436973636f203132303030475352 # Odd length payload = 23 bytes, add \x00 padding and then # byte-swap the last word (16-bit value) BEFORE = 00010006523200060012436973636f3132303030475352 MID1 = 00010006523200060012436973636f313230303047530052 AFTER = 00010006523200060012436973636f313230303047530052 # Ugly. Same as above, odd length payload = 25 bytes # but now, the last byte is >= \x80 # Compensate for glitch by decrementing each byte in last word by 1 # (\x00-1 = \xff, (-1/255 decimal) \x84-1 = \x83 (131 decimal) BEFORE = 00010006523200060013436973636f20313230303047535284 MID1 = 00010006523200060013436973636f2031323030304753520084 CMPSAT = -1 131 AFTER = 00010006523200060013436973636f203132303030475352ff83 # Even length payload = 24 bytes, # no change even though last byte is greater than \x80 BEFORE = 00010006523200060012436973636f313230303047535284 AFTER = 00010006523200060012436973636f313230303047535284

And now, the code - 'sub prepchksum' - could use some attention if you'd like to educate me on a better way to do it:

use strict; use warnings; my @payloads = qw( 00010006523200060013436973636f203132303030475352 00010006523200060012436973636f3132303030475352 00010006523200060013436973636f20313230303047535284 00010006523200060012436973636f313230303047535284 ); for (@payloads) { prepchksum(pack "H*", $_); print "\n" } sub prepchksum { my ($payload) = @_; ###DEBUG: printf "BEFORE = %s\n", (CORE::unpack "H*", $payload); if (length($payload)%2) { my $padded = substr $payload, 0, -1; $padded .= "\0"; $padded .= substr $payload, -1; ###DEBUG: printf "MID1 = %s\n", (CORE::unpack "H*", $padded); # Compensate off-by-one error if (ord(substr $padded, -1) >= 128) { my $end = ord(substr $padded, -2, 1); my $endend = ord(substr $padded, -1); $end--; $endend--; ###DEBUG: printf "CMPSAT = %i %i\n", $end, $endend; # if 0 before --, then -1 results in warning to pack below # -1 is in essense \xff or 255 if ($end == -1) { $end = 255 } if ($endend == -1) { $endend = 255 } $padded = substr $padded, 0, -2; $padded .= CORE::pack('CC', $end, $endend); } $payload = $padded; } ###DEBUG: printf "AFTER = %s\n", (CORE::unpack "H*", $payload); }