| # You may distribute under the terms of either the GNU General Public License |
| # or the Artistic License (the same terms as Perl itself) |
| # |
| # (C) Paul Evans, 2010-2015 -- leonerd@leonerd.org.uk |
| |
| package IO::Socket::IP; |
| # $VERSION needs to be set before use base 'IO::Socket' |
| # - https://rt.cpan.org/Ticket/Display.html?id=92107 |
| BEGIN { |
| $VERSION = '0.38'; |
| } |
| |
| use strict; |
| use warnings; |
| use base qw( IO::Socket ); |
| |
| use Carp; |
| |
| use Socket 1.97 qw( |
| getaddrinfo getnameinfo |
| sockaddr_family |
| AF_INET |
| AI_PASSIVE |
| IPPROTO_TCP IPPROTO_UDP |
| IPPROTO_IPV6 IPV6_V6ONLY |
| NI_DGRAM NI_NUMERICHOST NI_NUMERICSERV NIx_NOHOST NIx_NOSERV |
| SO_REUSEADDR SO_REUSEPORT SO_BROADCAST SO_ERROR |
| SOCK_DGRAM SOCK_STREAM |
| SOL_SOCKET |
| ); |
| my $AF_INET6 = eval { Socket::AF_INET6() }; # may not be defined |
| my $AI_ADDRCONFIG = eval { Socket::AI_ADDRCONFIG() } || 0; |
| use POSIX qw( dup2 ); |
| use Errno qw( EINVAL EINPROGRESS EISCONN ENOTCONN ETIMEDOUT EWOULDBLOCK ); |
| |
| use constant HAVE_MSWIN32 => ( $^O eq "MSWin32" ); |
| |
| # At least one OS (Android) is known not to have getprotobyname() |
| use constant HAVE_GETPROTOBYNAME => defined eval { getprotobyname( "tcp" ) }; |
| |
| my $IPv6_re = do { |
| # translation of RFC 3986 3.2.2 ABNF to re |
| my $IPv4address = do { |
| my $dec_octet = q<(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])>; |
| qq<$dec_octet(?: \\. $dec_octet){3}>; |
| }; |
| my $IPv6address = do { |
| my $h16 = qq<[0-9A-Fa-f]{1,4}>; |
| my $ls32 = qq<(?: $h16 : $h16 | $IPv4address)>; |
| qq<(?: |
| (?: $h16 : ){6} $ls32 |
| | :: (?: $h16 : ){5} $ls32 |
| | (?: $h16 )? :: (?: $h16 : ){4} $ls32 |
| | (?: (?: $h16 : ){0,1} $h16 )? :: (?: $h16 : ){3} $ls32 |
| | (?: (?: $h16 : ){0,2} $h16 )? :: (?: $h16 : ){2} $ls32 |
| | (?: (?: $h16 : ){0,3} $h16 )? :: $h16 : $ls32 |
| | (?: (?: $h16 : ){0,4} $h16 )? :: $ls32 |
| | (?: (?: $h16 : ){0,5} $h16 )? :: $h16 |
| | (?: (?: $h16 : ){0,6} $h16 )? :: |
| )> |
| }; |
| qr<$IPv6address>xo; |
| }; |
| |
| sub import |
| { |
| my $pkg = shift; |
| my @symbols; |
| |
| foreach ( @_ ) { |
| if( $_ eq "-register" ) { |
| IO::Socket::IP::_ForINET->register_domain( AF_INET ); |
| IO::Socket::IP::_ForINET6->register_domain( $AF_INET6 ) if defined $AF_INET6; |
| } |
| else { |
| push @symbols, $_; |
| } |
| } |
| |
| @_ = ( $pkg, @symbols ); |
| goto &IO::Socket::import; |
| } |
| |
| # Convenient capability test function |
| { |
| my $can_disable_v6only; |
| sub CAN_DISABLE_V6ONLY |
| { |
| return $can_disable_v6only if defined $can_disable_v6only; |
| |
| socket my $testsock, Socket::PF_INET6(), SOCK_STREAM, 0 or |
| die "Cannot socket(PF_INET6) - $!"; |
| |
| if( setsockopt $testsock, IPPROTO_IPV6, IPV6_V6ONLY, 0 ) { |
| return $can_disable_v6only = 1; |
| } |
| elsif( $! == EINVAL ) { |
| return $can_disable_v6only = 0; |
| } |
| else { |
| die "Cannot setsockopt() - $!"; |
| } |
| } |
| } |
| |
| sub new |
| { |
| my $class = shift; |
| my %arg = (@_ == 1) ? (PeerHost => $_[0]) : @_; |
| return $class->SUPER::new(%arg); |
| } |
| |
| # IO::Socket may call this one; neaten up the arguments from IO::Socket::INET |
| # before calling our real _configure method |
| sub configure |
| { |
| my $self = shift; |
| my ( $arg ) = @_; |
| |
| $arg->{PeerHost} = delete $arg->{PeerAddr} |
| if exists $arg->{PeerAddr} && !exists $arg->{PeerHost}; |
| |
| $arg->{PeerService} = delete $arg->{PeerPort} |
| if exists $arg->{PeerPort} && !exists $arg->{PeerService}; |
| |
| $arg->{LocalHost} = delete $arg->{LocalAddr} |
| if exists $arg->{LocalAddr} && !exists $arg->{LocalHost}; |
| |
| $arg->{LocalService} = delete $arg->{LocalPort} |
| if exists $arg->{LocalPort} && !exists $arg->{LocalService}; |
| |
| for my $type (qw(Peer Local)) { |
| my $host = $type . 'Host'; |
| my $service = $type . 'Service'; |
| |
| if( defined $arg->{$host} ) { |
| ( $arg->{$host}, my $s ) = $self->split_addr( $arg->{$host} ); |
| # IO::Socket::INET compat - *Host parsed port always takes precedence |
| $arg->{$service} = $s if defined $s; |
| } |
| } |
| |
| $self->_io_socket_ip__configure( $arg ); |
| } |
| |
| # Avoid simply calling it _configure, as some subclasses of IO::Socket::INET on CPAN already take that |
| sub _io_socket_ip__configure |
| { |
| my $self = shift; |
| my ( $arg ) = @_; |
| |
| my %hints; |
| my @localinfos; |
| my @peerinfos; |
| |
| my $listenqueue = $arg->{Listen}; |
| if( defined $listenqueue and |
| ( defined $arg->{PeerHost} || defined $arg->{PeerService} || defined $arg->{PeerAddrInfo} ) ) { |
| croak "Cannot Listen with a peer address"; |
| } |
| |
| if( defined $arg->{GetAddrInfoFlags} ) { |
| $hints{flags} = $arg->{GetAddrInfoFlags}; |
| } |
| else { |
| $hints{flags} = $AI_ADDRCONFIG; |
| } |
| |
| if( defined( my $family = $arg->{Family} ) ) { |
| $hints{family} = $family; |
| } |
| |
| if( defined( my $type = $arg->{Type} ) ) { |
| $hints{socktype} = $type; |
| } |
| |
| if( defined( my $proto = $arg->{Proto} ) ) { |
| unless( $proto =~ m/^\d+$/ ) { |
| my $protonum = HAVE_GETPROTOBYNAME |
| ? getprotobyname( $proto ) |
| : eval { Socket->${\"IPPROTO_\U$proto"}() }; |
| defined $protonum or croak "Unrecognised protocol $proto"; |
| $proto = $protonum; |
| } |
| |
| $hints{protocol} = $proto; |
| } |
| |
| # To maintain compatibility with IO::Socket::INET, imply a default of |
| # SOCK_STREAM + IPPROTO_TCP if neither hint is given |
| if( !defined $hints{socktype} and !defined $hints{protocol} ) { |
| $hints{socktype} = SOCK_STREAM; |
| $hints{protocol} = IPPROTO_TCP; |
| } |
| |
| # Some OSes (NetBSD) don't seem to like just a protocol hint without a |
| # socktype hint as well. We'll set a couple of common ones |
| if( !defined $hints{socktype} and defined $hints{protocol} ) { |
| $hints{socktype} = SOCK_STREAM if $hints{protocol} == IPPROTO_TCP; |
| $hints{socktype} = SOCK_DGRAM if $hints{protocol} == IPPROTO_UDP; |
| } |
| |
| if( my $info = $arg->{LocalAddrInfo} ) { |
| ref $info eq "ARRAY" or croak "Expected 'LocalAddrInfo' to be an ARRAY ref"; |
| @localinfos = @$info; |
| } |
| elsif( defined $arg->{LocalHost} or |
| defined $arg->{LocalService} or |
| HAVE_MSWIN32 and $arg->{Listen} ) { |
| # Either may be undef |
| my $host = $arg->{LocalHost}; |
| my $service = $arg->{LocalService}; |
| |
| unless ( defined $host or defined $service ) { |
| $service = 0; |
| } |
| |
| local $1; # Placate a taint-related bug; [perl #67962] |
| defined $service and $service =~ s/\((\d+)\)$// and |
| my $fallback_port = $1; |
| |
| my %localhints = %hints; |
| $localhints{flags} |= AI_PASSIVE; |
| ( my $err, @localinfos ) = getaddrinfo( $host, $service, \%localhints ); |
| |
| if( $err and defined $fallback_port ) { |
| ( $err, @localinfos ) = getaddrinfo( $host, $fallback_port, \%localhints ); |
| } |
| |
| if( $err ) { |
| $@ = "$err"; |
| $! = EINVAL; |
| return; |
| } |
| } |
| |
| if( my $info = $arg->{PeerAddrInfo} ) { |
| ref $info eq "ARRAY" or croak "Expected 'PeerAddrInfo' to be an ARRAY ref"; |
| @peerinfos = @$info; |
| } |
| elsif( defined $arg->{PeerHost} or defined $arg->{PeerService} ) { |
| defined( my $host = $arg->{PeerHost} ) or |
| croak "Expected 'PeerHost'"; |
| defined( my $service = $arg->{PeerService} ) or |
| croak "Expected 'PeerService'"; |
| |
| local $1; # Placate a taint-related bug; [perl #67962] |
| defined $service and $service =~ s/\((\d+)\)$// and |
| my $fallback_port = $1; |
| |
| ( my $err, @peerinfos ) = getaddrinfo( $host, $service, \%hints ); |
| |
| if( $err and defined $fallback_port ) { |
| ( $err, @peerinfos ) = getaddrinfo( $host, $fallback_port, \%hints ); |
| } |
| |
| if( $err ) { |
| $@ = "$err"; |
| $! = EINVAL; |
| return; |
| } |
| } |
| |
| my $INT_1 = pack "i", 1; |
| |
| my @sockopts_enabled; |
| push @sockopts_enabled, [ SOL_SOCKET, SO_REUSEADDR, $INT_1 ] if $arg->{ReuseAddr}; |
| push @sockopts_enabled, [ SOL_SOCKET, SO_REUSEPORT, $INT_1 ] if $arg->{ReusePort}; |
| push @sockopts_enabled, [ SOL_SOCKET, SO_BROADCAST, $INT_1 ] if $arg->{Broadcast}; |
| |
| if( my $sockopts = $arg->{Sockopts} ) { |
| ref $sockopts eq "ARRAY" or croak "Expected 'Sockopts' to be an ARRAY ref"; |
| foreach ( @$sockopts ) { |
| ref $_ eq "ARRAY" or croak "Bad Sockopts item - expected ARRAYref"; |
| @$_ >= 2 and @$_ <= 3 or |
| croak "Bad Sockopts item - expected 2 or 3 elements"; |
| |
| my ( $level, $optname, $value ) = @$_; |
| # TODO: consider more sanity checking on argument values |
| |
| defined $value or $value = $INT_1; |
| push @sockopts_enabled, [ $level, $optname, $value ]; |
| } |
| } |
| |
| my $blocking = $arg->{Blocking}; |
| defined $blocking or $blocking = 1; |
| |
| my $v6only = $arg->{V6Only}; |
| |
| # IO::Socket::INET defines this key. IO::Socket::IP always implements the |
| # behaviour it requests, so we can ignore it, unless the caller is for some |
| # reason asking to disable it. |
| if( defined $arg->{MultiHomed} and !$arg->{MultiHomed} ) { |
| croak "Cannot disable the MultiHomed parameter"; |
| } |
| |
| my @infos; |
| foreach my $local ( @localinfos ? @localinfos : {} ) { |
| foreach my $peer ( @peerinfos ? @peerinfos : {} ) { |
| next if defined $local->{family} and defined $peer->{family} and |
| $local->{family} != $peer->{family}; |
| next if defined $local->{socktype} and defined $peer->{socktype} and |
| $local->{socktype} != $peer->{socktype}; |
| next if defined $local->{protocol} and defined $peer->{protocol} and |
| $local->{protocol} != $peer->{protocol}; |
| |
| my $family = $local->{family} || $peer->{family} or next; |
| my $socktype = $local->{socktype} || $peer->{socktype} or next; |
| my $protocol = $local->{protocol} || $peer->{protocol} || 0; |
| |
| push @infos, { |
| family => $family, |
| socktype => $socktype, |
| protocol => $protocol, |
| localaddr => $local->{addr}, |
| peeraddr => $peer->{addr}, |
| }; |
| } |
| } |
| |
| if( !@infos ) { |
| # If there was a Family hint then create a plain unbound, unconnected socket |
| if( defined $hints{family} ) { |
| @infos = ( { |
| family => $hints{family}, |
| socktype => $hints{socktype}, |
| protocol => $hints{protocol}, |
| } ); |
| } |
| # If there wasn't, use getaddrinfo()'s AI_ADDRCONFIG side-effect to guess a |
| # suitable family first. |
| else { |
| ( my $err, @infos ) = getaddrinfo( "", "0", \%hints ); |
| if( $err ) { |
| $@ = "$err"; |
| $! = EINVAL; |
| return; |
| } |
| |
| # We'll take all the @infos anyway, because some OSes (HPUX) are known to |
| # ignore the AI_ADDRCONFIG hint and return AF_INET6 even if they don't |
| # support them |
| } |
| } |
| |
| # In the nonblocking case, caller will be calling ->setup multiple times. |
| # Store configuration in the object for the ->setup method |
| # Yes, these are messy. Sorry, I can't help that... |
| |
| ${*$self}{io_socket_ip_infos} = \@infos; |
| |
| ${*$self}{io_socket_ip_idx} = -1; |
| |
| ${*$self}{io_socket_ip_sockopts} = \@sockopts_enabled; |
| ${*$self}{io_socket_ip_v6only} = $v6only; |
| ${*$self}{io_socket_ip_listenqueue} = $listenqueue; |
| ${*$self}{io_socket_ip_blocking} = $blocking; |
| |
| ${*$self}{io_socket_ip_errors} = [ undef, undef, undef ]; |
| |
| # ->setup is allowed to return false in nonblocking mode |
| $self->setup or !$blocking or return undef; |
| |
| return $self; |
| } |
| |
| sub setup |
| { |
| my $self = shift; |
| |
| while(1) { |
| ${*$self}{io_socket_ip_idx}++; |
| last if ${*$self}{io_socket_ip_idx} >= @{ ${*$self}{io_socket_ip_infos} }; |
| |
| my $info = ${*$self}{io_socket_ip_infos}->[${*$self}{io_socket_ip_idx}]; |
| |
| $self->socket( @{$info}{qw( family socktype protocol )} ) or |
| ( ${*$self}{io_socket_ip_errors}[2] = $!, next ); |
| |
| $self->blocking( 0 ) unless ${*$self}{io_socket_ip_blocking}; |
| |
| foreach my $sockopt ( @{ ${*$self}{io_socket_ip_sockopts} } ) { |
| my ( $level, $optname, $value ) = @$sockopt; |
| $self->setsockopt( $level, $optname, $value ) or ( $@ = "$!", return undef ); |
| } |
| |
| if( defined ${*$self}{io_socket_ip_v6only} and defined $AF_INET6 and $info->{family} == $AF_INET6 ) { |
| my $v6only = ${*$self}{io_socket_ip_v6only}; |
| $self->setsockopt( IPPROTO_IPV6, IPV6_V6ONLY, pack "i", $v6only ) or ( $@ = "$!", return undef ); |
| } |
| |
| if( defined( my $addr = $info->{localaddr} ) ) { |
| $self->bind( $addr ) or |
| ( ${*$self}{io_socket_ip_errors}[1] = $!, next ); |
| } |
| |
| if( defined( my $listenqueue = ${*$self}{io_socket_ip_listenqueue} ) ) { |
| $self->listen( $listenqueue ) or ( $@ = "$!", return undef ); |
| } |
| |
| if( defined( my $addr = $info->{peeraddr} ) ) { |
| if( $self->connect( $addr ) ) { |
| $! = 0; |
| return 1; |
| } |
| |
| if( $! == EINPROGRESS or $! == EWOULDBLOCK ) { |
| ${*$self}{io_socket_ip_connect_in_progress} = 1; |
| return 0; |
| } |
| |
| # If connect failed but we have no system error there must be an error |
| # at the application layer, like a bad certificate with |
| # IO::Socket::SSL. |
| # In this case don't continue IP based multi-homing because the problem |
| # cannot be solved at the IP layer. |
| return 0 if ! $!; |
| |
| ${*$self}{io_socket_ip_errors}[0] = $!; |
| next; |
| } |
| |
| return 1; |
| } |
| |
| # Pick the most appropriate error, stringified |
| $! = ( grep defined, @{ ${*$self}{io_socket_ip_errors}} )[0]; |
| $@ = "$!"; |
| return undef; |
| } |
| |
| sub connect :method |
| { |
| my $self = shift; |
| |
| # It seems that IO::Socket hides EINPROGRESS errors, making them look like |
| # a success. This is annoying here. |
| # Instead of putting up with its frankly-irritating intentional breakage of |
| # useful APIs I'm just going to end-run around it and call core's connect() |
| # directly |
| |
| if( @_ ) { |
| my ( $addr ) = @_; |
| |
| # Annoyingly IO::Socket's connect() is where the timeout logic is |
| # implemented, so we'll have to reinvent it here |
| my $timeout = ${*$self}{'io_socket_timeout'}; |
| |
| return connect( $self, $addr ) unless defined $timeout; |
| |
| my $was_blocking = $self->blocking( 0 ); |
| |
| my $err = defined connect( $self, $addr ) ? 0 : $!+0; |
| |
| if( !$err ) { |
| # All happy |
| $self->blocking( $was_blocking ); |
| return 1; |
| } |
| elsif( not( $err == EINPROGRESS or $err == EWOULDBLOCK ) ) { |
| # Failed for some other reason |
| $self->blocking( $was_blocking ); |
| return undef; |
| } |
| elsif( !$was_blocking ) { |
| # We shouldn't block anyway |
| return undef; |
| } |
| |
| my $vec = ''; vec( $vec, $self->fileno, 1 ) = 1; |
| if( !select( undef, $vec, $vec, $timeout ) ) { |
| $self->blocking( $was_blocking ); |
| $! = ETIMEDOUT; |
| return undef; |
| } |
| |
| # Hoist the error by connect()ing a second time |
| $err = $self->getsockopt( SOL_SOCKET, SO_ERROR ); |
| $err = 0 if $err == EISCONN; # Some OSes give EISCONN |
| |
| $self->blocking( $was_blocking ); |
| |
| $! = $err, return undef if $err; |
| return 1; |
| } |
| |
| return 1 if !${*$self}{io_socket_ip_connect_in_progress}; |
| |
| # See if a connect attempt has just failed with an error |
| if( my $errno = $self->getsockopt( SOL_SOCKET, SO_ERROR ) ) { |
| delete ${*$self}{io_socket_ip_connect_in_progress}; |
| ${*$self}{io_socket_ip_errors}[0] = $! = $errno; |
| return $self->setup; |
| } |
| |
| # No error, so either connect is still in progress, or has completed |
| # successfully. We can tell by trying to connect() again; either it will |
| # succeed or we'll get EISCONN (connected successfully), or EALREADY |
| # (still in progress). This even works on MSWin32. |
| my $addr = ${*$self}{io_socket_ip_infos}[${*$self}{io_socket_ip_idx}]{peeraddr}; |
| |
| if( connect( $self, $addr ) or $! == EISCONN ) { |
| delete ${*$self}{io_socket_ip_connect_in_progress}; |
| $! = 0; |
| return 1; |
| } |
| else { |
| $! = EINPROGRESS; |
| return 0; |
| } |
| } |
| |
| sub connected |
| { |
| my $self = shift; |
| return defined $self->fileno && |
| !${*$self}{io_socket_ip_connect_in_progress} && |
| defined getpeername( $self ); # ->peername caches, we need to detect disconnection |
| } |
| |
| sub _get_host_service |
| { |
| my $self = shift; |
| my ( $addr, $flags, $xflags ) = @_; |
| |
| defined $addr or |
| $! = ENOTCONN, return; |
| |
| $flags |= NI_DGRAM if $self->socktype == SOCK_DGRAM; |
| |
| my ( $err, $host, $service ) = getnameinfo( $addr, $flags, $xflags || 0 ); |
| croak "getnameinfo - $err" if $err; |
| |
| return ( $host, $service ); |
| } |
| |
| sub _unpack_sockaddr |
| { |
| my ( $addr ) = @_; |
| my $family = sockaddr_family $addr; |
| |
| if( $family == AF_INET ) { |
| return ( Socket::unpack_sockaddr_in( $addr ) )[1]; |
| } |
| elsif( defined $AF_INET6 and $family == $AF_INET6 ) { |
| return ( Socket::unpack_sockaddr_in6( $addr ) )[1]; |
| } |
| else { |
| croak "Unrecognised address family $family"; |
| } |
| } |
| |
| sub sockhost_service |
| { |
| my $self = shift; |
| my ( $numeric ) = @_; |
| |
| $self->_get_host_service( $self->sockname, $numeric ? NI_NUMERICHOST|NI_NUMERICSERV : 0 ); |
| } |
| |
| sub sockhost { my $self = shift; scalar +( $self->_get_host_service( $self->sockname, NI_NUMERICHOST, NIx_NOSERV ) )[0] } |
| sub sockport { my $self = shift; scalar +( $self->_get_host_service( $self->sockname, NI_NUMERICSERV, NIx_NOHOST ) )[1] } |
| |
| sub sockhostname { my $self = shift; scalar +( $self->_get_host_service( $self->sockname, 0, NIx_NOSERV ) )[0] } |
| sub sockservice { my $self = shift; scalar +( $self->_get_host_service( $self->sockname, 0, NIx_NOHOST ) )[1] } |
| |
| sub sockaddr { my $self = shift; _unpack_sockaddr $self->sockname } |
| |
| sub peerhost_service |
| { |
| my $self = shift; |
| my ( $numeric ) = @_; |
| |
| $self->_get_host_service( $self->peername, $numeric ? NI_NUMERICHOST|NI_NUMERICSERV : 0 ); |
| } |
| |
| sub peerhost { my $self = shift; scalar +( $self->_get_host_service( $self->peername, NI_NUMERICHOST, NIx_NOSERV ) )[0] } |
| sub peerport { my $self = shift; scalar +( $self->_get_host_service( $self->peername, NI_NUMERICSERV, NIx_NOHOST ) )[1] } |
| |
| sub peerhostname { my $self = shift; scalar +( $self->_get_host_service( $self->peername, 0, NIx_NOSERV ) )[0] } |
| sub peerservice { my $self = shift; scalar +( $self->_get_host_service( $self->peername, 0, NIx_NOHOST ) )[1] } |
| |
| sub peeraddr { my $self = shift; _unpack_sockaddr $self->peername } |
| |
| # This unbelievably dodgy hack works around the bug that IO::Socket doesn't do |
| # it |
| # https://rt.cpan.org/Ticket/Display.html?id=61577 |
| sub accept |
| { |
| my $self = shift; |
| my ( $new, $peer ) = $self->SUPER::accept( @_ ) or return; |
| |
| ${*$new}{$_} = ${*$self}{$_} for qw( io_socket_domain io_socket_type io_socket_proto ); |
| |
| return wantarray ? ( $new, $peer ) |
| : $new; |
| } |
| |
| # This second unbelievably dodgy hack guarantees that $self->fileno doesn't |
| # change, which is useful during nonblocking connect |
| sub socket :method |
| { |
| my $self = shift; |
| return $self->SUPER::socket(@_) if not defined $self->fileno; |
| |
| # I hate core prototypes sometimes... |
| socket( my $tmph, $_[0], $_[1], $_[2] ) or return undef; |
| |
| dup2( $tmph->fileno, $self->fileno ) or die "Unable to dup2 $tmph onto $self - $!"; |
| } |
| |
| # Versions of IO::Socket before 1.35 may leave socktype undef if from, say, an |
| # ->fdopen call. In this case we'll apply a fix |
| BEGIN { |
| if( eval($IO::Socket::VERSION) < 1.35 ) { |
| *socktype = sub { |
| my $self = shift; |
| my $type = $self->SUPER::socktype; |
| if( !defined $type ) { |
| $type = $self->sockopt( Socket::SO_TYPE() ); |
| } |
| return $type; |
| }; |
| } |
| } |
| |
| sub as_inet |
| { |
| my $self = shift; |
| croak "Cannot downgrade a non-PF_INET socket to IO::Socket::INET" unless $self->sockdomain == AF_INET; |
| return IO::Socket::INET->new_from_fd( $self->fileno, "r+" ); |
| } |
| |
| sub split_addr |
| { |
| shift; |
| my ( $addr ) = @_; |
| |
| local ( $1, $2 ); # Placate a taint-related bug; [perl #67962] |
| if( $addr =~ m/\A\[($IPv6_re)\](?::([^\s:]*))?\z/ or |
| $addr =~ m/\A([^\s:]*):([^\s:]*)\z/ ) { |
| return ( $1, $2 ) if defined $2 and length $2; |
| return ( $1, undef ); |
| } |
| |
| return ( $addr, undef ); |
| } |
| |
| sub join_addr |
| { |
| shift; |
| my ( $host, $port ) = @_; |
| |
| $host = "[$host]" if $host =~ m/:/; |
| |
| return join ":", $host, $port if defined $port; |
| return $host; |
| } |
| |
| # Since IO::Socket->new( Domain => ... ) will delete the Domain parameter |
| # before calling ->configure, we need to keep track of which it was |
| |
| package # hide from indexer |
| IO::Socket::IP::_ForINET; |
| use base qw( IO::Socket::IP ); |
| |
| sub configure |
| { |
| # This is evil |
| my $self = shift; |
| my ( $arg ) = @_; |
| |
| bless $self, "IO::Socket::IP"; |
| $self->configure( { %$arg, Family => Socket::AF_INET() } ); |
| } |
| |
| package # hide from indexer |
| IO::Socket::IP::_ForINET6; |
| use base qw( IO::Socket::IP ); |
| |
| sub configure |
| { |
| # This is evil |
| my $self = shift; |
| my ( $arg ) = @_; |
| |
| bless $self, "IO::Socket::IP"; |
| $self->configure( { %$arg, Family => Socket::AF_INET6() } ); |
| } |
| |
| 0x55AA; |