1*414a9b58Sbluhm# $OpenBSD: Packet.pm,v 1.3 2015/01/16 17:06:43 bluhm Exp $ 2ccf9d2bcSbluhm 3*414a9b58Sbluhm# Copyright (c) 2014-2015 Alexander Bluhm <bluhm@openbsd.org> 4*414a9b58Sbluhm# Copyright (c) 2015 Florian Riehm <mail@friehm.de> 56404c9ddSbluhm# 66404c9ddSbluhm# Permission to use, copy, modify, and distribute this software for any 76404c9ddSbluhm# purpose with or without fee is hereby granted, provided that the above 86404c9ddSbluhm# copyright notice and this permission notice appear in all copies. 96404c9ddSbluhm# 106404c9ddSbluhm# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 116404c9ddSbluhm# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 126404c9ddSbluhm# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 136404c9ddSbluhm# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 146404c9ddSbluhm# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 156404c9ddSbluhm# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 166404c9ddSbluhm# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 176404c9ddSbluhm 186404c9ddSbluhmuse strict; 196404c9ddSbluhmuse warnings; 206404c9ddSbluhm 216404c9ddSbluhmpackage Packet; 226404c9ddSbluhmuse parent 'Exporter'; 236404c9ddSbluhmuse Carp; 246404c9ddSbluhm 256404c9ddSbluhmour @EXPORT = qw( 266404c9ddSbluhm consume_ether 276404c9ddSbluhm consume_arp 286404c9ddSbluhm consume_ip 296404c9ddSbluhm consume_ospf 306404c9ddSbluhm consume_hello 31*414a9b58Sbluhm consume_dd 326404c9ddSbluhm construct_ether 336404c9ddSbluhm construct_arp 346404c9ddSbluhm construct_ip 356404c9ddSbluhm construct_ospf 366404c9ddSbluhm construct_hello 37*414a9b58Sbluhm construct_dd 386404c9ddSbluhm); 396404c9ddSbluhm 406404c9ddSbluhmsub ip_checksum { 416404c9ddSbluhm my ($msg) = @_; 426404c9ddSbluhm my $chk = 0; 436404c9ddSbluhm foreach my $short (unpack("n*", $msg."\0")) { 446404c9ddSbluhm $chk += $short; 456404c9ddSbluhm } 466404c9ddSbluhm $chk = ($chk >> 16) + ($chk & 0xffff); 476404c9ddSbluhm return(~(($chk >> 16) + $chk) & 0xffff); 486404c9ddSbluhm} 496404c9ddSbluhm 506404c9ddSbluhmsub consume_ether { 516404c9ddSbluhm my $packet = shift; 526404c9ddSbluhm 536404c9ddSbluhm length($$packet) >= 14 546404c9ddSbluhm or croak "ether packet too short: ". length($$packet); 556404c9ddSbluhm my $ether = substr($$packet, 0, 14, ""); 566404c9ddSbluhm my %fields; 576404c9ddSbluhm @fields{qw(dst src type)} = unpack("a6 a6 n", $ether); 586404c9ddSbluhm foreach my $addr (qw(src dst)) { 596404c9ddSbluhm $fields{"${addr}_str"} = sprintf("%02x:%02x:%02x:%02x:%02x:%02x", 606404c9ddSbluhm unpack("C6", $fields{$addr})); 616404c9ddSbluhm } 626404c9ddSbluhm $fields{type_hex} = sprintf("0x%04x", $fields{type}); 636404c9ddSbluhm 646404c9ddSbluhm return %fields; 656404c9ddSbluhm} 666404c9ddSbluhm 676404c9ddSbluhmsub construct_ether { 686404c9ddSbluhm my $fields = shift; 696404c9ddSbluhm my $subpacket = shift // ""; 706404c9ddSbluhm 716404c9ddSbluhm foreach my $addr (qw(src dst)) { 726404c9ddSbluhm $$fields{$addr} = 736404c9ddSbluhm pack("C6", map { hex $_ } split(/:/, $$fields{"${addr}_str"})); 746404c9ddSbluhm } 756404c9ddSbluhm my $packet = pack("a6 a6 n", @$fields{qw(dst src type)}); 766404c9ddSbluhm 776404c9ddSbluhm return $packet. $subpacket; 786404c9ddSbluhm} 796404c9ddSbluhm 806404c9ddSbluhmsub consume_arp { 816404c9ddSbluhm my $packet = shift; 826404c9ddSbluhm 836404c9ddSbluhm length($$packet) >= 28 846404c9ddSbluhm or croak "arp packet too short: ". length($$packet); 856404c9ddSbluhm my $arp = substr($$packet, 0, 28, ""); 866404c9ddSbluhm my %fields; 876404c9ddSbluhm @fields{qw(hdr sha spa tha tpa)} = unpack("a8 a6 a4 a6 a4", $arp); 886404c9ddSbluhm foreach my $addr (qw(sha tha)) { 896404c9ddSbluhm $fields{"${addr}_str"} = sprintf("%02x:%02x:%02x:%02x:%02x:%02x", 906404c9ddSbluhm unpack("C6", $fields{$addr})); 916404c9ddSbluhm } 926404c9ddSbluhm foreach my $addr (qw(spa tpa)) { 936404c9ddSbluhm $fields{"${addr}_str"} = join(".", unpack("C4", $fields{$addr})); 946404c9ddSbluhm } 956404c9ddSbluhm @fields{qw(hrd pro hln pln op)} = unpack("n n C C n", $fields{hdr}); 966404c9ddSbluhm 976404c9ddSbluhm return %fields; 986404c9ddSbluhm} 996404c9ddSbluhm 1006404c9ddSbluhmsub construct_arp { 1016404c9ddSbluhm my $fields = shift; 1026404c9ddSbluhm my $subpacket = shift // ""; 1036404c9ddSbluhm 1046404c9ddSbluhm foreach my $addr (qw(sha tha)) { 1056404c9ddSbluhm $$fields{$addr} = 1066404c9ddSbluhm pack("C6", map { hex $_ } split(/:/, $$fields{"${addr}_str"})); 1076404c9ddSbluhm } 1086404c9ddSbluhm foreach my $addr (qw(spa tpa)) { 1096404c9ddSbluhm $$fields{$addr} = pack("C4", split(/\./, $$fields{"${addr}_str"})); 1106404c9ddSbluhm } 1116404c9ddSbluhm $$fields{hdr} = pack("n n C C n", @$fields{qw(hrd pro hln pln op)}); 1126404c9ddSbluhm my $packet = pack("a8 a6 a4 a6 a4", @$fields{qw(hdr sha spa tha tpa)}); 1136404c9ddSbluhm 1146404c9ddSbluhm return $packet. $subpacket; 1156404c9ddSbluhm} 1166404c9ddSbluhm 1176404c9ddSbluhmsub consume_ip { 1186404c9ddSbluhm my $packet = shift; 1196404c9ddSbluhm 1206404c9ddSbluhm length($$packet) >= 20 or croak "ip packet too short: ". length($$packet); 1216404c9ddSbluhm my $ip = substr($$packet, 0, 20, ""); 1226404c9ddSbluhm my %fields; 1236404c9ddSbluhm @fields{qw(hlv tos len id off ttl p sum src dst)} = 1246404c9ddSbluhm unpack("C C n n n C C n a4 a4", $ip); 1256404c9ddSbluhm $fields{hlen} = ($fields{hlv} & 0x0f) << 2; 1266404c9ddSbluhm $fields{v} = ($fields{hlv} >> 4) & 0x0f; 1276404c9ddSbluhm 1286404c9ddSbluhm $fields{v} == 4 or croak "ip version is not 4: $fields{v}"; 1296404c9ddSbluhm $fields{hlen} >= 20 or croak "ip header length too small: $fields{hlen}"; 1306404c9ddSbluhm if ($fields{hlen} > 20) { 1316404c9ddSbluhm $fields{options} = substr($$packet, 0, 20 - $fields{hlen}, ""); 1326404c9ddSbluhm } 1336404c9ddSbluhm foreach my $addr (qw(src dst)) { 1346404c9ddSbluhm $fields{"${addr}_str"} = join(".", unpack("C4", $fields{$addr})); 1356404c9ddSbluhm } 1366404c9ddSbluhm 1376404c9ddSbluhm return %fields; 1386404c9ddSbluhm} 1396404c9ddSbluhm 1406404c9ddSbluhmsub construct_ip { 1416404c9ddSbluhm my $fields = shift; 1426404c9ddSbluhm my $subpacket = shift // ""; 1436404c9ddSbluhm 1446404c9ddSbluhm $$fields{options} //= ""; 1456404c9ddSbluhm 1466404c9ddSbluhm $$fields{hlen} = 20 + length($$fields{options}); 1476404c9ddSbluhm $$fields{hlen} & 3 and croak "bad ip header length: $$fields{hlen}"; 1486404c9ddSbluhm $$fields{hlen} < 20 1496404c9ddSbluhm and croak "ip header length too small: $$fields{hlen}"; 1506404c9ddSbluhm ($$fields{hlen} >> 2) > 0x0f 1516404c9ddSbluhm and croak "ip header length too big: $$fields{hlen}"; 1526404c9ddSbluhm $$fields{v} = 4; 1536404c9ddSbluhm $$fields{hlv} = 1546404c9ddSbluhm (($$fields{v} << 4) & 0xf0) | (($$fields{hlen} >> 2) & 0x0f); 1556404c9ddSbluhm 1566404c9ddSbluhm $$fields{len} = $$fields{hlen} + length($subpacket); 1576404c9ddSbluhm 1586404c9ddSbluhm foreach my $addr (qw(src dst)) { 1596404c9ddSbluhm $$fields{$addr} = pack("C4", split(/\./, $$fields{"${addr}_str"})); 1606404c9ddSbluhm } 1616404c9ddSbluhm my $packet = pack("C C n n n C C xx a4 a4", 1626404c9ddSbluhm @$fields{qw(hlv tos len id off ttl p src dst)}); 1636404c9ddSbluhm $$fields{sum} = ip_checksum($packet); 1646404c9ddSbluhm substr($packet, 10, 2, pack("n", $$fields{sum})); 1656404c9ddSbluhm $packet .= pack("a*", $$fields{options}); 1666404c9ddSbluhm 1676404c9ddSbluhm return $packet. $subpacket; 1686404c9ddSbluhm} 1696404c9ddSbluhm 1706404c9ddSbluhmsub consume_ospf { 1716404c9ddSbluhm my $packet = shift; 1726404c9ddSbluhm 1736404c9ddSbluhm length($$packet) >= 24 or croak "ospf packet too short: ". length($$packet); 1746404c9ddSbluhm my $ospf = substr($$packet, 0, 24, ""); 1756404c9ddSbluhm my %fields; 1766404c9ddSbluhm @fields{qw(version type packet_length router_id area_id checksum autype 1776404c9ddSbluhm authentication)} = 1786404c9ddSbluhm unpack("C C n a4 a4 n n a8", $ospf); 1796404c9ddSbluhm $fields{version} == 2 or croak "ospf version is not 2: $fields{v}"; 1806404c9ddSbluhm foreach my $addr (qw(router_id area_id)) { 1816404c9ddSbluhm $fields{"${addr}_str"} = join(".", unpack("C4", $fields{$addr})); 1826404c9ddSbluhm } 1836404c9ddSbluhm 1846404c9ddSbluhm return %fields; 1856404c9ddSbluhm} 1866404c9ddSbluhm 1876404c9ddSbluhmsub construct_ospf { 1886404c9ddSbluhm my $fields = shift; 1896404c9ddSbluhm my $subpacket = shift // ""; 1906404c9ddSbluhm 1916404c9ddSbluhm $$fields{packet_length} = 24 + length($subpacket); 1926404c9ddSbluhm $$fields{authentication} = "" if $$fields{autype} == 0; 1936404c9ddSbluhm 1946404c9ddSbluhm foreach my $addr (qw(router_id area_id)) { 1956404c9ddSbluhm if ($$fields{"${addr}_str"}) { 1966404c9ddSbluhm $$fields{$addr} = pack("C4", split(/\./, $$fields{"${addr}_str"})); 1976404c9ddSbluhm } 1986404c9ddSbluhm } 1996404c9ddSbluhm my $packet = pack("C C n a4 a4 xx n", 2006404c9ddSbluhm @$fields{qw(version type packet_length router_id area_id autype)}); 2016404c9ddSbluhm $$fields{checksum} = ip_checksum($packet. $subpacket); 2026404c9ddSbluhm substr($packet, 12, 2, pack("n", $$fields{checksum})); 2036404c9ddSbluhm $packet .= pack("a8", $$fields{authentication}); 2046404c9ddSbluhm 2056404c9ddSbluhm return $packet. $subpacket; 2066404c9ddSbluhm} 2076404c9ddSbluhm 2086404c9ddSbluhmsub consume_hello { 2096404c9ddSbluhm my $packet = shift; 2106404c9ddSbluhm 2116404c9ddSbluhm length($$packet) >= 20 2126404c9ddSbluhm or croak "hello packet too short: ". length($$packet); 2136404c9ddSbluhm my $hello = substr($$packet, 0, 20, ""); 2146404c9ddSbluhm my %fields; 2156404c9ddSbluhm @fields{qw(network_mask hellointerval options rtr_pri 2166404c9ddSbluhm routerdeadinterval designated_router backup_designated_router)} = 2176404c9ddSbluhm unpack("a4 n C C N a4 a4", $hello); 2186404c9ddSbluhm foreach my $addr (qw(network_mask designated_router 2196404c9ddSbluhm backup_designated_router)) { 2206404c9ddSbluhm $fields{"${addr}_str"} = join(".", unpack("C4", $fields{$addr})); 2216404c9ddSbluhm } 2226404c9ddSbluhm length($$packet) % 4 and croak "bad neighbor length: ". length($$packet); 2236404c9ddSbluhm my $n = length($$packet) / 4; 2246404c9ddSbluhm $fields{neighbors} = [unpack("a4" x $n, $$packet)]; 2256404c9ddSbluhm $$packet = ""; 2266404c9ddSbluhm foreach my $addr (@{$fields{neighbors}}) { 2276404c9ddSbluhm push @{$fields{neighbors_str}}, join(".", unpack("C4", $addr)); 2286404c9ddSbluhm } 2296404c9ddSbluhm 2306404c9ddSbluhm return %fields; 2316404c9ddSbluhm} 2326404c9ddSbluhm 233*414a9b58Sbluhmsub consume_dd { 234*414a9b58Sbluhm my $packet = shift; 235*414a9b58Sbluhm 236*414a9b58Sbluhm length($$packet) >= 8 237*414a9b58Sbluhm or croak "dd packet too short: ". length($$packet); 238*414a9b58Sbluhm my $dd = substr($$packet, 0, 8, ""); 239*414a9b58Sbluhm my %fields; 240*414a9b58Sbluhm @fields{qw(interface_mtu options bits dd_sequence_number)} = 241*414a9b58Sbluhm unpack("n C C N", $dd); 242*414a9b58Sbluhm $fields{bits} <= 7 243*414a9b58Sbluhm or croak "All bits except of I-, M- and MS-bit must be zero"; 244*414a9b58Sbluhm 245*414a9b58Sbluhm return %fields; 246*414a9b58Sbluhm} 247*414a9b58Sbluhm 2486404c9ddSbluhmsub construct_hello { 2496404c9ddSbluhm my $fields = shift; 2506404c9ddSbluhm 2516404c9ddSbluhm $$fields{neighbors_str} //= []; 2526404c9ddSbluhm $$fields{neighbors} //= []; 2536404c9ddSbluhm 2546404c9ddSbluhm foreach my $addr (qw(network_mask designated_router 2556404c9ddSbluhm backup_designated_router)) { 2566404c9ddSbluhm if ($$fields{"${addr}_str"}) { 2576404c9ddSbluhm $$fields{$addr} = pack("C4", split(/\./, $$fields{"${addr}_str"})); 2586404c9ddSbluhm } 2596404c9ddSbluhm } 2606404c9ddSbluhm my $packet = pack("a4 n C C N a4 a4", 2616404c9ddSbluhm @$fields{qw(network_mask hellointerval options rtr_pri 2626404c9ddSbluhm routerdeadinterval designated_router backup_designated_router)}); 2636404c9ddSbluhm 2646404c9ddSbluhm if ($$fields{neighbors_str}) { 2656404c9ddSbluhm $$fields{neighbors} = []; 2666404c9ddSbluhm } 2676404c9ddSbluhm foreach my $str (@{$$fields{neighbors_str}}) { 2686404c9ddSbluhm push @{$$fields{neighbors}}, pack("C4", split(/\./, $str)); 2696404c9ddSbluhm } 2706404c9ddSbluhm my $n = @{$$fields{neighbors}}; 2716404c9ddSbluhm $packet .= pack("a4" x $n, @{$$fields{neighbors}}); 2726404c9ddSbluhm 2736404c9ddSbluhm return $packet; 2746404c9ddSbluhm} 2756404c9ddSbluhm 276*414a9b58Sbluhmsub construct_dd { 277*414a9b58Sbluhm my $fields = shift; 278*414a9b58Sbluhm 279*414a9b58Sbluhm my $packet = pack("n C C N", 280*414a9b58Sbluhm @$fields{qw(interface_mtu options bits dd_sequence_number)}); 281*414a9b58Sbluhm 282*414a9b58Sbluhm return $packet; 283*414a9b58Sbluhm} 284*414a9b58Sbluhm 2856404c9ddSbluhm1; 286