File Coverage

blib/lib/Net/Server/IP.pm
Criterion Covered Total %
statement 56 69 81.1
branch 31 50 62.0
condition 19 39 48.7
subroutine 8 8 100.0
pod 1 2 50.0
total 115 168 68.4


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3             # Net::Server::IP - IPv4 / IPv6 compatibility module
4             #
5             # Copyright (C) 2025-2026
6             #
7             # Rob Brown
8             #
9             # This package may be distributed under the terms of either the
10             # GNU General Public License
11             # or the
12             # Perl Artistic License
13             #
14             # All rights reserved.
15             #
16             ################################################################
17              
18             package Net::Server::IP;
19              
20 60     60   23883658 use strict;
  60         252  
  60         3615  
21 60     60   474 use warnings;
  60         191  
  60         7849  
22 60     60   25399 use Net::Server::Proto qw(AF_INET AF_INET6 AF_UNSPEC IPPROTO_IPV6 IPV6_V6ONLY);
  60         249  
  60         844  
23 60     60   1196 use IO::Socket::INET ();
  60         587  
  60         2242  
24 60     60   475 use Exporter qw(import);
  60         216  
  60         106521  
25              
26             our @ISA = qw(IO::Socket::INET); # we may dynamically change this to an IPv6-compatible class based upon our configuration
27             our $ipv6_package = undef;
28             our @preferred = qw(IO::Socket::IP IO::Socket::INET6);
29             our $fake_danger = undef; # Detect if fake interface is spoofed by kernel (e.g. OpenVZ venet) instead of a real public network interface.
30              
31             sub configure {
32 282     282 0 9191077 my ($self, $arg) = @_;
33 282 50 33     4357 die "configure: no arg" if !$arg or !%$arg;
34 282         1063 my $family = delete $arg->{'Family'};
35 282 50 33     1649 if (defined (my $family2 = delete $arg->{'Domain'}) and !defined $family) { $family = $family2 };
  0         0  
36 282 100 66     4495 if (!defined $family and my $addr = $arg->{'LocalHost'} || $arg->{'PeerHost'} || $arg->{'LocalAddr'} || $arg->{'PeerAddr'}) {
      66        
37             # Use Addr arg to hint which Family to use.
38 245 100 33     3589 if ($addr =~ /^(\d+\.\d+\.\d+\.\d+)(|:\w+|\w+\(\d+\))$/) {
    50          
39 170         1084 $family = AF_INET; # Surely IPv4
40             } elsif ($addr =~ /^\[[a-fA-F\d:]+\](|:\w+|\w+\(\d+\))$/ or $addr =~ /^(?:[a-fA-F\d]*:){2,7}([a-fA-F\d]*|\d+\.\d+\.\d+\.\d+)$/) {
41 75         348 $family = AF_INET6; # Surely IPv6
42             } else {
43 0         0 $family = AF_UNSPEC; # Some other Host, maybe a DNS word, so can't tell if it's IPv4 or IPv6 yet.
44             }
45             }
46 282 100 66     2641 if ($ISA[0] eq "IO::Socket::INET" and defined $family and $family ne AF_INET) {
      100        
47             # Look for IPv6-compatible module
48 33 100       329 my @try = ($ipv6_package?($ipv6_package):(), @preferred);
49 33     104   335 my $pm = sub { (my $f="$_[0].pm") =~ s|::|/|g; $f};
  104         468  
  104         30395  
50 33 100       108 my ($pkg) = grep { $INC{$pm->($_)} && !$_->isa(__PACKAGE__) } @try;
  70         254  
51 33         136 my $err = '';
52 33 100 33     146 for (@try) { last if $pkg; eval{local $^W=0;require $pm->($_);die "Circular ISA" if $_->isa(__PACKAGE__);$pkg=$_} or $err .= $@=~/^(.*)/ && "\n[$_] ($!) $1"; }
  67 100       390  
  34 100       118  
  34         297  
  34         149  
  33         511885  
  32         348  
53 33 50       193 if ($pkg) {
54 33         152 my $args = { Listen=>1 };
55 33 50 33     240 if (not $pkg->new(LocalAddr=>"[::]", Listen=>1) or not $pkg->new(LocalAddr=>"127.0.0.1", Listen=>1)) { # Simple ephemeral sanity pre-check didn't even work
56 33 50 33     42357 if ($pkg->new(LocalAddr=>"[::]", Listen=>1, GetAddrInfoFlags => 0) or $pkg->new(LocalAddr=>"127.0.0.1", Listen=>1, GetAddrInfoFlags => 0)) { # Yet DOES work without doing that getaddrinfo AI_ADDRCONFIG AF_NETLINK SOCK_RAW sendto RTM_GETADDR recvmsg interferfaces pre-check boogie dance?
57 33         17329 $fake_danger = 1; $@ = ""; # Flag to remember this special network configuration. Pretend like there is no error.
  33         110  
58             } else {
59 0         0 $@ = "bind: $pkg failed: $! $@"; $pkg = undef; # This $pkg is of no use. Set error $@ with good excuse.
  0         0  
60             }
61             } else {
62 0         0 $fake_danger = 0; # Works fine without monkeying anything.
63             }
64 33 50       2194 $ISA[0] = $ipv6_package = $pkg if $pkg;
65             } else {
66 0 0 0     0 return if $@ = "Preferred ipv6_package (@try) could not be loaded:$err" and $family;
67 0         0 $family = undef;
68             }
69             }
70 282 50       1001 if (defined $family) { # Set the corresponding 'Family' arg:
71 282 100       3130 $arg->{'Family'} = $family if $self->isa("IO::Socket::IP");
72 282 50       2014 $arg->{'Domain'} = $family if $self->isa("IO::Socket::INET6");
73 282 100 66     2288 $arg->{'GetAddrInfoFlags'} = 0 if !defined $arg->{'GetAddrInfoFlags'} and $fake_danger; # Special delicate network
74 282 50 66     1566 if (defined $arg->{'V6Only'} and $self->isa("IO::Socket::INET6")) {
75 0         0 ${ *$self }{'NS_v6only'} = delete $arg->{'V6Only'};
  0         0  
76 0 0       0 delete ${ *$self }{'NS_v6only'} if $family eq AF_INET;
  0         0  
77             }
78             }
79 282         2432 return $self->SUPER::configure($arg);
80             }
81              
82             sub socket {
83 282     282 1 57528 my $self = shift;
84 282         2345 my $ret = $self->SUPER::socket(@_);
85 282 50       23210 if (defined (my $opt = ${ *$self }{'NS_v6only'})) {
  282         1275  
86 0 0       0 eval { $self->setsockopt( IPPROTO_IPV6, IPV6_V6ONLY, $opt?1:0 ) } or warn "setsockopt(IPV6_V6ONLY) failed: ($!) ($@)";
  0 0       0  
87             }
88 282         846 return $ret;
89             }
90              
91             1;
92              
93             __END__