xref: /openbsd-src/regress/usr.sbin/ospfd/Packet.pm (revision 414a9b5818fd13bc861ad125ae1d11b95e05dfc0)
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