File Coverage

blib/lib/Net/SAP/Packet.pm
Criterion Covered Total %
statement 132 195 67.6
branch 38 86 44.1
condition 1 18 5.5
subroutine 21 25 84.0
pod 14 14 100.0
total 206 338 60.9


line stmt bran cond sub pod time code
1             package Net::SAP::Packet;
2              
3             ################
4             #
5             # Session Announcement Protocol Packet object
6             #
7             # Nicholas Humfrey
8             # njh@ecs.soton.ac.uk
9             #
10              
11 3     3   22097 use strict;
  3         6  
  3         100  
12 3     3   6691 use Compress::Zlib;
  3         340245  
  3         1046  
13 3     3   2922 use IO::Interface::Simple;
  3         102647  
  3         103  
14 3     3   28 use Socket qw/ AF_INET /;
  3         3  
  3         1069  
15 3     3   2702 use Socket6 qw/ AF_INET6 inet_ntop inet_pton /;
  3         4279  
  3         360  
16 3     3   22 use Carp;
  3         5  
  3         173  
17              
18 3     3   16 use vars qw/$VERSION/;
  3         5  
  3         6617  
19              
20             $VERSION="0.10";
21              
22              
23              
24             sub new {
25 2     2 1 17162 my $class = shift;
26 2         3 my ($packet_data) = @_;
27            
28             # Set defaults
29 2         21 my $self = {
30             'v' => 1, # Version (1)
31             'a' => 0, # Address type (0=v4, 1=v6)
32             't' => 0, # Message Type (0=announce, 1=delete)
33             'e' => 0, # Encrypted (0=no, 1=yes)
34             'c' => 0, # Compressed (0=no, 1=yes)
35             'origin_address' => undef, # No Origin
36             'msg_id_hash' => 0, # No Message Hash
37             'auth_len' => 0,
38             'auth_data' => '',
39             'payload_type' => 'application/sdp',
40             'payload' => '',
41             };
42 2         5 bless $self, $class;
43            
44            
45             # Given packet data ?
46 2 100       7 if (defined $packet_data) {
47 1         4 my $res = $self->parse( $packet_data );
48            
49             # Unsuccessful ?
50 1 50       7 undef $self if ($res);
51             }
52              
53 2         6 return $self;
54             }
55              
56              
57              
58             sub parse {
59 1     1 1 2 my $self = shift;
60 1         1 my ($data) = @_;
61 1         2 my $pos=0;
62            
63            
64             # Don't even attempt if there isn't enough data
65 1 50       4 if (length($data) < 10) {
66 0         0 carp "data isn't big enough to be a whole SAP packet";
67 0         0 return -1;
68             }
69            
70             # grab the first 32bits of the packet
71 1         8 my ($vartec, $auth_len, $id_hash) = unpack("CCn",substr($data,$pos,4)); $pos+=4;
  1         2  
72            
73 1         3 $self->{'v'} = (($vartec & 0xE0) >> 5); # Version (1)
74 1         1 $self->{'a'} = (($vartec & 0x10) >> 4); # Address type (0=v4, 1=v6)
75             # $self->{'r'} = (($vartec & 0x08) >> 3); # Reserved
76 1         2 $self->{'t'} = (($vartec & 0x04) >> 2); # Message Type (0=announce, 1=delete)
77 1         3 $self->{'e'} = (($vartec & 0x02) >> 1); # Encryped (0=no, 1=yes)
78 1         2 $self->{'c'} = (($vartec & 0x01) >> 0); # Compressed (0=no, 1=yes)
79            
80             # Show warning if unsupported SAP packet version
81 1 50 33     11 if ($self->{'v'} != 0 and $self->{'v'} != 1) {
82 0         0 warn "Unsupported SAP packet version: $self->{'v'}.\n";
83 0         0 return -1;
84             }
85            
86            
87 1         2 $self->{'auth_len'} = $auth_len;
88 1         4 $self->{'msg_id_hash'} = int($id_hash);
89             # $self->{'msg_id_hash'} = sprintf("0x%4.4X", $id_hash);
90            
91            
92             # Decide the origin address to a string
93 1 50       3 if ($self->{'a'} == 0) {
94             # IPv4 address
95 1         13 $self->{'origin_address'} = inet_ntop( AF_INET, substr($data,$pos,4) ); $pos+=4;
  1         2  
96             } else {
97             # IPv6 address
98 0         0 $self->{'origin_address'} = inet_ntop( AF_INET6, substr($data,$pos,16) ); $pos+=16;
  0         0  
99             }
100            
101            
102             # Get authentication data if it exists
103 1 50       4 if ($self->{'auth_len'}) {
104 0         0 $self->{'auth_data'} = substr($data,$pos,$self->{'auth_len'});
105 0         0 $pos+=$self->{'auth_len'};
106 0         0 warn "Net::SAP doesn't currently support encrypted SAP packets.";
107 0         0 return -1;
108             }
109            
110            
111             # Decompress the payload with zlib
112 1         2 my $payload = substr($data,$pos);
113 1 50       4 if ($self->{'c'}) {
114 1         5 my $inf = inflateInit();
115 1 50       141 unless (defined $inf) {
116 0         0 warn "Failed to initialize zlib to decompress SAP packet.";
117 0         0 return -1;
118             } else {
119 1         6 $payload = $inf->inflate( $payload );
120 1 50       81 unless (defined $payload) {
121 0         0 warn "Failed to decompress SAP packet.";
122 0         0 return -1;
123             }
124             }
125             }
126              
127              
128             # Check the next three bytes, to see if it is the start of an SDP file
129 1 50       5 if ($payload =~ /^v=\d+/) {
130 0         0 $self->{'payload_type'} = 'application/sdp';
131 0         0 $self->{'payload'} = $payload;
132             } else {
133 1         3 my $index = index($payload, "\x00");
134 1 50       3 if ($index==-1) {
135 0         0 $self->{'payload_type'} = "unknown";
136 0         0 $self->{'payload'} = $payload;
137             } else {
138 1         4 $self->{'payload_type'} = substr( $payload, 0, $index );
139 1         3 $self->{'payload'} = substr( $payload, $index+1 );
140             }
141             }
142              
143 1         3 return 0;
144             }
145              
146              
147              
148             sub _crc16 {
149 1     1   2 my ($data) = @_;
150 1         1 my $crc = 0;
151            
152 1         6 for (my $i=0; $i
153 12         16 $crc = $crc ^ ord(substr($data,$i,1)) << 8;
154 12         34 for( my $b=0; $b<8; $b++ ) {
155 96 100       131 if ($crc & 0x8000) {
156 45         93 $crc = $crc << 1 ^ 0x1021;
157             } else {
158 51         101 $crc = $crc << 1;
159             }
160             }
161             }
162            
163 1         3 return $crc & 0xFFFF;
164             }
165              
166              
167             sub generate {
168 1     1 1 2 my $self = shift;
169              
170             # Set field of 8 bits
171 1         2 my $vartec = 0;
172 1         4 $vartec |= (($self->{'v'} & 0x7) << 5); # Version (1)
173 1         2 $vartec |= (($self->{'a'} & 0x1) << 4); # Address type (0=v4, 1=v6)
174             # $vartec |= (($self->{'r'} & 0x1) << 3); # Reserved
175 1         3 $vartec |= (($self->{'t'} & 0x1) << 2); # Message Type (0=announce, 1=delete)
176 1         3 $vartec |= (($self->{'e'} & 0x1) << 1); # Encrypted (0=no, 1=yes)
177 1         2 $vartec |= (($self->{'c'} & 0x1) << 0); # Compressed (0=no, 1=yes)
178              
179              
180             # Calculate hash for packet
181 1         4 $self->{'msg_id_hash'} = _crc16( $self->{'payload'} );
182            
183            
184             # Build packet header
185 1         8 my $data = pack("CCn", $vartec, $self->{'auth_len'}, $self->{'msg_id_hash'});
186            
187             # Don't generate packet unless origin has been set
188 1 50       3 if ($self->origin_address() eq '') {
189 0         0 $self->_choose_origin_address();
190 0 0       0 if ($self->origin_address() eq '') {
191 0         0 croak("Failed to detect origin address: you must set an origin address before sending packets.");
192             }
193             }
194              
195              
196             # Append the Originating Source address
197 1 50       4 if ($self->{'a'} == 0) {
198             # IPv4 address
199 1         23 $data .= inet_pton( AF_INET, $self->{'origin_address'} );
200             } else {
201             # IPv6 address
202 0         0 $data .= inet_pton( AF_INET6, $self->{'origin_address'} );
203             }
204            
205              
206             # Append authentication data
207 1         3 $data .= $self->{'auth_data'};
208            
209             # Assemble payload section
210 1         4 my $payload = $self->{'payload_type'} . pack("x") . $self->{'payload'};
211              
212            
213             # Compress the payload with zlib
214 1 50       3 if ($self->{'c'}) {
215 1         6 my $def = deflateInit();
216 1 50       631 unless (defined $def) {
217 0         0 warn "Failed to initialize zlib to compress SAP packet.";
218 0         0 return undef;
219             } else {
220 1         11 $payload = $def->deflate( $payload );
221 1 50       27 unless (defined $payload) {
222 0         0 warn "Failed to compress SAP packet.";
223 0         0 return undef;
224             }
225 1         4 $payload .= $def->flush();
226             }
227             }
228            
229            
230             # Append payload to packet
231 1         127 $data .= $payload;
232            
233 1         5 return $data;
234             }
235              
236              
237             ## Find a public interface address for origin IP
238             #
239             sub _choose_origin_address {
240 0     0   0 my $self = shift;
241            
242             # There isn't any support for IPv6 in IO::Interface
243             # so we will just try and use a public v4 address
244 0         0 my @interfaces = IO::Interface::Simple->interfaces;
245 0         0 foreach my $if (@interfaces) {
246 0         0 my $addr = $if->address();
247              
248 0 0       0 next if ($if->is_loopback());
249 0 0       0 next unless (_addr_is_public( $addr ) );
250            
251             # Must be ok then: store it
252 0         0 $self->origin_address($addr);
253 0         0 $self->origin_address_type('ipv4');
254            
255             # Success
256 0         0 return 1;
257             }
258            
259             # Failure
260 0         0 return 0;
261             }
262              
263             ## Returns true if IP is a global IPv4 address
264             #
265             sub _addr_is_public {
266 0     0   0 my ($addr) = @_;
267            
268             # Check it looks like an IPv4 address
269 0 0       0 return 0 unless (defined $addr);
270 0         0 my ($a,$b,$c,$d) = ($addr =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
271 0 0       0 return 0 unless (defined $a);
272            
273             # 10.0.0.0/8 is private address space
274 0 0       0 return 0 if ($a==10);
275            
276             # 172.16.0.0/12 is private address space
277 0 0 0     0 return 0 if ($a==172 and $b==16 and $c<=31 and $c>=16);
      0        
      0        
278              
279             # 192.168.0.0/16 is private address space
280 0 0 0     0 return 0 if ($a==192 and $b==168);
281              
282             # 169.254.0.0/16 is link-local address space
283 0 0 0     0 return 0 if ($a==169 and $b==254);
284              
285             # 127.0.0.0/8 is reserved/localhost
286 0 0       0 return 0 if ($a==127);
287              
288             # 0.0.0.0/8 is reserved address space
289 0 0       0 return 0 if ($a==0);
290              
291             # 1.0.0.0/8 is reserved address space
292 0 0       0 return 0 if ($a==1);
293            
294              
295             # Otherwise global
296 0         0 return 1;
297             }
298              
299             sub origin_address_type {
300 2     2 1 620 my $self = shift;
301 2         3 my ($value) = @_;
302            
303 2 100       8 if (defined $value) {
304 1 50       13 if ($value =~ /ip6|ipv6/i) {
    50          
305 0         0 $self->{'a'} = 1;
306             } elsif ($value =~ /ip4|ipv4/i) {
307 1         5 $self->{'a'} = 0;
308             } else {
309 0         0 carp "Invalid parameter for origin_address_type(): $value\n";
310 0         0 carp "Should be 'ipv4' or 'ipv6'.";
311             }
312             }
313            
314 2 50       6 if ($self->{'a'}) { return 'ipv6'; }
  0         0  
315 2         9 else { return 'ipv4'; }
316             }
317              
318              
319             sub origin_address {
320 3     3 1 5 my $self = shift;
321 3         4 my ($value) = @_;
322            
323 3 100       11 if (defined $value) {
324             ## FIXME: should be some checking ?
325 1         3 $self->{'origin_address'} = $value;
326             }
327            
328 3         12 return $self->{'origin_address'};
329             }
330              
331              
332             sub compressed {
333 2     2 1 4 my $self = shift;
334 2         3 my ($value) = @_;
335            
336 2 100       7 if (defined $value) {
337 1 50       5 if ($value =~ /1|yes|true/i) {
    0          
338 1         2 $self->{'c'} = 1;
339             } elsif ($value =~ /0|no|false/i) {
340 0         0 $self->{'c'} = 0;
341             } else {
342 0         0 carp "Invalid parameter for compressed(): $value\n";
343 0         0 carp "Should be '1' or '0'.";
344             }
345             }
346            
347 2         9 return $self->{'c'};
348             }
349              
350             sub type {
351 2     2 1 4 my $self = shift;
352 2         4 my ($value) = @_;
353            
354 2 100       6 if (defined $value) {
355 1 50       7 if ($value =~ /advert/i) {
    50          
356 0         0 $self->{'t'} = 0;
357             } elsif ($value =~ /delet/i) {
358 1         2 $self->{'t'} = 1;
359             } else {
360 0         0 carp "Invalid parameter for type(): $value\n";
361 0         0 carp "Should be 'advertisement' or 'deletion'.";
362             }
363             }
364              
365 2 50       6 if ($self->{'t'} == 0) { return 'advertisement'; }
  0         0  
366 2         8 else { return 'deletion'; }
367             }
368              
369             sub version {
370 1     1 1 411 my $self = shift;
371 1         5 return $self->{'v'};
372             }
373              
374             sub message_id_hash {
375 1     1 1 2 my $self = shift;
376 1         5 return $self->{'msg_id_hash'};
377             }
378              
379             sub encrypted {
380 1     1 1 2 my $self = shift;
381 1         6 return $self->{'e'};
382             }
383              
384             sub encryption_key_length {
385 0     0 1 0 my $self = shift;
386 0         0 return $self->{'auth_len'};
387             }
388              
389             sub encryption_key {
390 0     0 1 0 my $self = shift;
391 0         0 return $self->{'auth_data'};
392             }
393              
394             sub payload_type {
395 2     2 1 25 my $self = shift;
396 2         4 my ($value) = @_;
397            
398 2 100       5 if (defined $value) {
399             ## FIXME: should be some checking ?
400 1         3 $self->{'payload_type'} = $value;
401             }
402            
403 2         8 return $self->{'payload_type'};
404             }
405              
406             sub payload {
407 2     2 1 4 my $self = shift;
408 2         3 my ($value) = @_;
409              
410 2 100       6 if (defined $value) {
411             ## FIXME: should be some checking ?
412 1         3 $self->{'payload'} = $value;
413             }
414              
415 2         8 return $self->{'payload'};
416             }
417              
418              
419              
420             sub DESTROY {
421 2     2   525 my $self=shift;
422            
423             }
424              
425              
426             1;
427              
428             __END__