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