1# $OpenBSD: Packet.pm,v 1.3 2015/01/16 17:06:43 bluhm Exp $ 2 3# Copyright (c) 2014-2015 Alexander Bluhm <bluhm@openbsd.org> 4# Copyright (c) 2015 Florian Riehm <mail@friehm.de> 5# 6# Permission to use, copy, modify, and distribute this software for any 7# purpose with or without fee is hereby granted, provided that the above 8# copyright notice and this permission notice appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18use strict; 19use warnings; 20 21package Packet; 22use parent 'Exporter'; 23use Carp; 24 25our @EXPORT = qw( 26 consume_ether 27 consume_arp 28 consume_ip 29 consume_ospf 30 consume_hello 31 consume_dd 32 construct_ether 33 construct_arp 34 construct_ip 35 construct_ospf 36 construct_hello 37 construct_dd 38); 39 40sub ip_checksum { 41 my ($msg) = @_; 42 my $chk = 0; 43 foreach my $short (unpack("n*", $msg."\0")) { 44 $chk += $short; 45 } 46 $chk = ($chk >> 16) + ($chk & 0xffff); 47 return(~(($chk >> 16) + $chk) & 0xffff); 48} 49 50sub consume_ether { 51 my $packet = shift; 52 53 length($$packet) >= 14 54 or croak "ether packet too short: ". length($$packet); 55 my $ether = substr($$packet, 0, 14, ""); 56 my %fields; 57 @fields{qw(dst src type)} = unpack("a6 a6 n", $ether); 58 foreach my $addr (qw(src dst)) { 59 $fields{"${addr}_str"} = sprintf("%02x:%02x:%02x:%02x:%02x:%02x", 60 unpack("C6", $fields{$addr})); 61 } 62 $fields{type_hex} = sprintf("0x%04x", $fields{type}); 63 64 return %fields; 65} 66 67sub construct_ether { 68 my $fields = shift; 69 my $subpacket = shift // ""; 70 71 foreach my $addr (qw(src dst)) { 72 $$fields{$addr} = 73 pack("C6", map { hex $_ } split(/:/, $$fields{"${addr}_str"})); 74 } 75 my $packet = pack("a6 a6 n", @$fields{qw(dst src type)}); 76 77 return $packet. $subpacket; 78} 79 80sub consume_arp { 81 my $packet = shift; 82 83 length($$packet) >= 28 84 or croak "arp packet too short: ". length($$packet); 85 my $arp = substr($$packet, 0, 28, ""); 86 my %fields; 87 @fields{qw(hdr sha spa tha tpa)} = unpack("a8 a6 a4 a6 a4", $arp); 88 foreach my $addr (qw(sha tha)) { 89 $fields{"${addr}_str"} = sprintf("%02x:%02x:%02x:%02x:%02x:%02x", 90 unpack("C6", $fields{$addr})); 91 } 92 foreach my $addr (qw(spa tpa)) { 93 $fields{"${addr}_str"} = join(".", unpack("C4", $fields{$addr})); 94 } 95 @fields{qw(hrd pro hln pln op)} = unpack("n n C C n", $fields{hdr}); 96 97 return %fields; 98} 99 100sub construct_arp { 101 my $fields = shift; 102 my $subpacket = shift // ""; 103 104 foreach my $addr (qw(sha tha)) { 105 $$fields{$addr} = 106 pack("C6", map { hex $_ } split(/:/, $$fields{"${addr}_str"})); 107 } 108 foreach my $addr (qw(spa tpa)) { 109 $$fields{$addr} = pack("C4", split(/\./, $$fields{"${addr}_str"})); 110 } 111 $$fields{hdr} = pack("n n C C n", @$fields{qw(hrd pro hln pln op)}); 112 my $packet = pack("a8 a6 a4 a6 a4", @$fields{qw(hdr sha spa tha tpa)}); 113 114 return $packet. $subpacket; 115} 116 117sub consume_ip { 118 my $packet = shift; 119 120 length($$packet) >= 20 or croak "ip packet too short: ". length($$packet); 121 my $ip = substr($$packet, 0, 20, ""); 122 my %fields; 123 @fields{qw(hlv tos len id off ttl p sum src dst)} = 124 unpack("C C n n n C C n a4 a4", $ip); 125 $fields{hlen} = ($fields{hlv} & 0x0f) << 2; 126 $fields{v} = ($fields{hlv} >> 4) & 0x0f; 127 128 $fields{v} == 4 or croak "ip version is not 4: $fields{v}"; 129 $fields{hlen} >= 20 or croak "ip header length too small: $fields{hlen}"; 130 if ($fields{hlen} > 20) { 131 $fields{options} = substr($$packet, 0, 20 - $fields{hlen}, ""); 132 } 133 foreach my $addr (qw(src dst)) { 134 $fields{"${addr}_str"} = join(".", unpack("C4", $fields{$addr})); 135 } 136 137 return %fields; 138} 139 140sub construct_ip { 141 my $fields = shift; 142 my $subpacket = shift // ""; 143 144 $$fields{options} //= ""; 145 146 $$fields{hlen} = 20 + length($$fields{options}); 147 $$fields{hlen} & 3 and croak "bad ip header length: $$fields{hlen}"; 148 $$fields{hlen} < 20 149 and croak "ip header length too small: $$fields{hlen}"; 150 ($$fields{hlen} >> 2) > 0x0f 151 and croak "ip header length too big: $$fields{hlen}"; 152 $$fields{v} = 4; 153 $$fields{hlv} = 154 (($$fields{v} << 4) & 0xf0) | (($$fields{hlen} >> 2) & 0x0f); 155 156 $$fields{len} = $$fields{hlen} + length($subpacket); 157 158 foreach my $addr (qw(src dst)) { 159 $$fields{$addr} = pack("C4", split(/\./, $$fields{"${addr}_str"})); 160 } 161 my $packet = pack("C C n n n C C xx a4 a4", 162 @$fields{qw(hlv tos len id off ttl p src dst)}); 163 $$fields{sum} = ip_checksum($packet); 164 substr($packet, 10, 2, pack("n", $$fields{sum})); 165 $packet .= pack("a*", $$fields{options}); 166 167 return $packet. $subpacket; 168} 169 170sub consume_ospf { 171 my $packet = shift; 172 173 length($$packet) >= 24 or croak "ospf packet too short: ". length($$packet); 174 my $ospf = substr($$packet, 0, 24, ""); 175 my %fields; 176 @fields{qw(version type packet_length router_id area_id checksum autype 177 authentication)} = 178 unpack("C C n a4 a4 n n a8", $ospf); 179 $fields{version} == 2 or croak "ospf version is not 2: $fields{v}"; 180 foreach my $addr (qw(router_id area_id)) { 181 $fields{"${addr}_str"} = join(".", unpack("C4", $fields{$addr})); 182 } 183 184 return %fields; 185} 186 187sub construct_ospf { 188 my $fields = shift; 189 my $subpacket = shift // ""; 190 191 $$fields{packet_length} = 24 + length($subpacket); 192 $$fields{authentication} = "" if $$fields{autype} == 0; 193 194 foreach my $addr (qw(router_id area_id)) { 195 if ($$fields{"${addr}_str"}) { 196 $$fields{$addr} = pack("C4", split(/\./, $$fields{"${addr}_str"})); 197 } 198 } 199 my $packet = pack("C C n a4 a4 xx n", 200 @$fields{qw(version type packet_length router_id area_id autype)}); 201 $$fields{checksum} = ip_checksum($packet. $subpacket); 202 substr($packet, 12, 2, pack("n", $$fields{checksum})); 203 $packet .= pack("a8", $$fields{authentication}); 204 205 return $packet. $subpacket; 206} 207 208sub consume_hello { 209 my $packet = shift; 210 211 length($$packet) >= 20 212 or croak "hello packet too short: ". length($$packet); 213 my $hello = substr($$packet, 0, 20, ""); 214 my %fields; 215 @fields{qw(network_mask hellointerval options rtr_pri 216 routerdeadinterval designated_router backup_designated_router)} = 217 unpack("a4 n C C N a4 a4", $hello); 218 foreach my $addr (qw(network_mask designated_router 219 backup_designated_router)) { 220 $fields{"${addr}_str"} = join(".", unpack("C4", $fields{$addr})); 221 } 222 length($$packet) % 4 and croak "bad neighbor length: ". length($$packet); 223 my $n = length($$packet) / 4; 224 $fields{neighbors} = [unpack("a4" x $n, $$packet)]; 225 $$packet = ""; 226 foreach my $addr (@{$fields{neighbors}}) { 227 push @{$fields{neighbors_str}}, join(".", unpack("C4", $addr)); 228 } 229 230 return %fields; 231} 232 233sub consume_dd { 234 my $packet = shift; 235 236 length($$packet) >= 8 237 or croak "dd packet too short: ". length($$packet); 238 my $dd = substr($$packet, 0, 8, ""); 239 my %fields; 240 @fields{qw(interface_mtu options bits dd_sequence_number)} = 241 unpack("n C C N", $dd); 242 $fields{bits} <= 7 243 or croak "All bits except of I-, M- and MS-bit must be zero"; 244 245 return %fields; 246} 247 248sub construct_hello { 249 my $fields = shift; 250 251 $$fields{neighbors_str} //= []; 252 $$fields{neighbors} //= []; 253 254 foreach my $addr (qw(network_mask designated_router 255 backup_designated_router)) { 256 if ($$fields{"${addr}_str"}) { 257 $$fields{$addr} = pack("C4", split(/\./, $$fields{"${addr}_str"})); 258 } 259 } 260 my $packet = pack("a4 n C C N a4 a4", 261 @$fields{qw(network_mask hellointerval options rtr_pri 262 routerdeadinterval designated_router backup_designated_router)}); 263 264 if ($$fields{neighbors_str}) { 265 $$fields{neighbors} = []; 266 } 267 foreach my $str (@{$$fields{neighbors_str}}) { 268 push @{$$fields{neighbors}}, pack("C4", split(/\./, $str)); 269 } 270 my $n = @{$$fields{neighbors}}; 271 $packet .= pack("a4" x $n, @{$$fields{neighbors}}); 272 273 return $packet; 274} 275 276sub construct_dd { 277 my $fields = shift; 278 279 my $packet = pack("n C C N", 280 @$fields{qw(interface_mtu options bits dd_sequence_number)}); 281 282 return $packet; 283} 284 2851; 286