File Coverage

blib/lib/Net/NAT/PMP.pm
Criterion Covered Total %
statement 9 61 14.7
branch 0 40 0.0
condition 0 3 0.0
subroutine 3 13 23.0
pod 5 10 50.0
total 17 127 13.3


line stmt bran cond sub pod time code
1             # Copyright (c) 2009 David Caldwell, All Rights Reserved. -*- cperl -*-
2              
3 1     1   1004 use strict;
  1         2  
  1         38  
4 1     1   6 use warnings;
  1         2  
  1         56  
5              
6             package Net::NAT::PMP;
7             our $VERSION = '0.9.4';
8              
9 1     1   999 use IO::Socket::INET;
  1         36716  
  1         9  
10 0     0 0   sub Port { 5351 }
11 0     0 0   sub Version { 0 } # protocol version we support
12              
13 0     0 0   sub socket { $_[0]->{socket} }
14 0     0 0   sub router_ip { $_[0]->{router_ip} }
15              
16             sub error {
17 0     0 1   my ($self, $message) = @_;
18 0 0         return $self->{error} unless defined $message;
19 0           $self->{error} = $message;
20             undef
21 0           }
22              
23             sub get_router_address {
24 0     0 0   my $gateway;
25 0 0         if ($^O eq 'darwin') {
    0          
26             # Would be much better to use the sysctl interface here, natively.
27 0 0         open NETSTAT, '-|', 'netstat', '-rlnf', 'inet' or die "Couldn't run netstat: $!";
28 0           while () {
29 0 0         $gateway = $1 if /^default\s+(\d+\.\d+\.\d+\.\d+)/;
30             }
31 0           close NETSTAT;
32             } elsif ($^O eq 'linux') {
33 0 0         open ROUTE, '<', '/proc/net/route' or die "Couldn't open /proc/net/route: $!";
34 0           while () {
35 0 0         $gateway = $1 if /^\S+\s+00000000\s+([0-9A-F]+)/;
36             }
37 0           close ROUTE;
38             # Stupid linux prints it as a hex number in network byte order. The following should work on both
39             # big and little endian machines. I don't have any big endian's on hand to test though.
40 0 0         $gateway = join(".", unpack "CCCC", pack "L", hex $gateway) if $gateway;
41 0           } else { die "Automatically discovering the gateway address is not supported on $^O yet! Please pass the address of your router to Net:NAT::PMP::new()" }
42 0           $gateway;
43             }
44              
45             sub new {
46 0     0 1   my ($class, $router_ip) = @_;
47 0   0       $router_ip ||= get_router_address();
48 0           my $self = bless {
49             router_ip => $router_ip,
50             socket => IO::Socket::INET->new(PeerAddr => $router_ip, PeerPort => Net::NAT::PMP::Port, Proto=>'udp'),#, Timeout => .25),
51             }, $class;
52 0 0         $self->{socket} ? $self : undef;
53             }
54              
55             sub external_address {
56 0     0 1   my ($self) = @_;
57 0           my $op = 0;
58 0 0         return $self->error("send: $!") unless defined $self->socket->send(pack("CC", Version, $op));
59 0           my $packet;
60 0 0         return $self->error("recv: $!") unless defined $self->socket->recv($packet, 12);
61 0           my (%response, @external_address);
62 0           (@response{qw(vers op result_code time)}, @external_address) = unpack("CCnNCCCC", $packet);
63 0 0         return $self->error("Got unexpected op $response{op} instead of @{[128 + $op]}") unless $response{op} == 128 + $op;
  0            
64 0 0         return $self->error("Got unexpected result_code $response{result_code} instead of 0") unless $response{result_code} == 0;
65 0           my $dotted = join('.', @external_address);
66 0           return $dotted;
67             }
68              
69             sub create_mapping {
70 0     0 1   my ($self, $internal_port, $external_port, $lifetime_seconds, $udp) = @_;
71 0 0         $external_port = $internal_port unless defined $external_port; # wheres my //= !!!
72 0 0         $lifetime_seconds = 3600 unless defined $lifetime_seconds;
73 0 0         my $op = $udp ? 1 : 2;
74 0 0         return $self->error("send: $!") unless defined $self->socket->send(pack ("CCnnnN", Version, $op, 0, $internal_port, $external_port, $lifetime_seconds));
75 0           my $packet;
76 0 0         return $self->error("recv: $!") unless defined $self->socket->recv($packet, 16);
77 0           my %response;
78 0           @response{qw(vers op result_code time internal_port external_port lifetime_seconds)} = unpack "CCnNnnN", $packet;
79 0 0         return $self->error("Got unexpected op $response{op} instead of @{[128 + $op]}") unless $response{op} == 128 + $op;
  0            
80 0 0         return $self->error("Got unexpected result_code $response{result_code} instead of 0") unless $response{result_code} == 0;
81 0           return $external_port;
82             }
83              
84             sub destroy_mapping {
85 0     0 1   my ($self, $internal_port, $udp) = @_;
86 0           $self->create_mapping($internal_port, 0, 0, $udp);
87             }
88              
89             1;
90              
91             __END__