Subject: | problems with multihomed and family order (patch included) |
Hi,
Due to (my) last fixes on IO::Socket::INET6 multihomed got broken.
Additionally it could happen, that it preferred inet6 even if the
host usually prefers inet6.
This was not detected because the io_multihomed test was assumes
that it will try to connect to inet6 first w/o checking it.
Because the default on OpenBSD is to prefer IPv4 it was detected
by a friend.
The attached patch (against 2.61) contains the following:
- rewrite of io_multihomed test so that it tests the right thing
regardless if the host prefers inet or inet6
- fix for IO::Socket::INET6:
for( my $r=0;$r<@rres;$r+=5 ) {
- next if $rres[0] != $fam_listen; # must be same family
+ next if $rres[$r] != $fam_listen; # must be same family
^^^^
- change to the algorithm for finding out the combinations of
[family,localaddr,peeraddr] so that the order is driven by @rres,
eg the order given by getaddrinfo(peeraddr,AF_UNSPEC).
The problem with the older approach was, that
getaddrinfo('',AF_UNSPEC,AI_PASSIVE) returns on Linux (at least on
my system) inet before inet6, even if the system prefers inet6
before inet.
patch was tested with per5.8.8 and perl5.10.1 on ubuntu10.04 with
inet6 prefered to inet and also the other way.
Regards,
Steffen
Subject: | io-socket-inet6.patch |
diff -ru IO-Socket-INET6-2.61/lib/IO/Socket/INET6.pm IO-Socket-INET6-2.61.new/lib/IO/Socket/INET6.pm
--- IO-Socket-INET6-2.61/lib/IO/Socket/INET6.pm 2010-03-25 09:45:16.000000000 +0100
+++ IO-Socket-INET6-2.61.new/lib/IO/Socket/INET6.pm 2010-05-20 21:21:33.000000000 +0200
@@ -177,20 +177,25 @@
}
my @flr;
- for( my $l=0;$l<@lres;$l+=5) {
- my $fam_listen = $lres[$l];
- my $lsockaddr = $lres[$l+3];
- if (@rres) {
- # collect all combinations whith the same family in lres and rres
- for( my $r=0;$r<@rres;$r+=5 ) {
- next if $rres[0] != $fam_listen; # must be same family
- push @flr,[ $fam_listen,$lsockaddr,$rres[$r+3] ];
- }
- } else {
- # collect only the binding side
- push @flr,[ $fam_listen,$lsockaddr ];
- }
- }
+ if (@rres) {
+ # collect all combinations whith the same family in lres and rres
+ # the order we search should be defined by the order of @rres,
+ # not @lres!
+ for( my $r=0;$r<@rres;$r+=5 ) {
+ for( my $l=0;$l<@lres;$l+=5) {
+ my $fam_listen = $lres[$l];
+ next if $rres[$r] != $fam_listen; # must be same family
+ push @flr,[ $fam_listen,$lres[$l+3],$rres[$r+3] ];
+ }
+ }
+ } else {
+ for( my $l=0;$l<@lres;$l+=5) {
+ my $fam_listen = $lres[$l];
+ my $lsockaddr = $lres[$l+3];
+ # collect only the binding side
+ push @flr,[ $fam_listen,$lsockaddr ];
+ }
+ }
# try to bind and maybe connect
# if multihomed try all combinations until success
diff -ru IO-Socket-INET6-2.61/t/io_multihomed6.t IO-Socket-INET6-2.61.new/t/io_multihomed6.t
--- IO-Socket-INET6-2.61/t/io_multihomed6.t 2010-03-25 09:45:16.000000000 +0100
+++ IO-Socket-INET6-2.61.new/t/io_multihomed6.t 2010-05-20 21:24:16.000000000 +0200
@@ -1,9 +1,11 @@
#!./perl
+use strict;
+use warnings;
BEGIN {
unless(grep /blib/, @INC) {
chdir 't' if -d 't';
- @INC = '../lib';
+ unshift @INC,'../lib';
}
}
@@ -25,79 +27,118 @@
$reason = 'IO extension unavailable';
}
if ($reason) {
- print "1..0 # Skip: $reason\n";
+ print "1..0 # SKIP $reason\n";
exit 0;
}
}
if ($^O eq 'MSWin32') {
- print "1..0 # Skip: accept() fails for IPv6 under MSWin32\n";
+ print "1..0 # SKIP accept() fails for IPv6 under MSWin32\n";
exit 0;
}
}
-$| = 1;
+use IO::Socket::INET6;
-print "1..5\n";
+$| = 1;
+print "1..8\n";
eval {
$SIG{ALRM} = sub { die; };
alarm 60;
};
-# Okey:
-# To check the Multihome strategy, let's try the next :
-# Open a IPv4 server on a given port.
-# then, try a client on unspecified family -AF_UNSPEC-
-# The multihomed socket will try then firstly IPv6, fail,
-# and then IPv4.
-package main;
-
-use IO::Socket::INET6;
-
-$listen = IO::Socket::INET6->new(Listen => 2,
- # 8080 is a commonly used port
- # so we're using a more obscure port
- # instead.
- LocalPort => 28083,
- Family => AF_INET,
- Proto => 'tcp',
- Timeout => 5,
- ) or die "$@";
+# find out if the host prefers inet or inet6 by offering
+# both and checking where it connects
+my ($port,@srv);
+for my $addr ( '127.0.0.1','::1' ) {
+ push @srv, IO::Socket::INET6->new(
+ Listen => 2,
+ LocalAddr => $addr,
+ LocalPort => $port,
+ ) or die "listen on $addr port $port: $!";
+ $port ||= $srv[-1]->sockport;
+}
print "ok 1\n";
-$port = $listen->sockport;
-
-if($pid = fork()) {
-
- $sock = $listen->accept() or die "$!";
- print "ok 2\n";
-
- print $sock->getline();
- print $sock "ok 4\n";
+if (my $pid = fork()) {
+ my $vec = '';
+ vec($vec,fileno($_),1) = 1 for(@srv);
+ select($vec,undef,undef,5) or die $!;
+
+ # connected to first, not second
+ my ($first,$second) = vec($vec,fileno($srv[0]),1) ? @srv[0,1]:@srv[1,0];
+ my $cl = $first->accept or die $!;
+
+ # listener should not work for next connect
+ # so it needs to try second
+ close($first);
+
+ # make sure established connection works
+ my $fam0 = ( $cl->sockdomain == AF_INET ) ? 'inet':'inet6';
+ print $cl "ok 2 # $fam0\n";
+ print $cl->getline(); # ok 3
+ close($cl);
+
+ # ... ok 4 comes when client fails to connect to first
+
+ # wait for connect on second and make sure it works
+ $vec = '';
+ vec($vec,fileno($second),1) = 1;
+ if ( select($vec,undef,undef,5)) {
+ my $cl2 = $second->accept or die $!;
+ my $fam1 = ( $cl2->sockdomain == AF_INET ) ? 'inet':'inet6';
+ print $cl2 "ok 5 # $fam1\n";
+ print $cl2->getline(); # ok 6
+ close($cl2);
+
+ # should be different families
+ print "not " if $fam0 eq $fam1;
+ print "ok 7\n";
+ }
waitpid($pid,0);
+ print "ok 8\n";
- $sock->close;
-
- print "ok 5\n";
-
-} elsif(defined $pid) {
-
- $sock = IO::Socket::INET6->new(PeerPort => $port,
- Proto => 'tcp',
- PeerAddr => 'localhost',
- MultiHomed => 1,
- Timeout => 1,
- ) or die "$@";
-
- print $sock "ok 3\n";
- sleep(1); # race condition
- print $sock->getline();
-
- $sock->close;
+} elsif (defined $pid) {
+ close($_) for (@srv);
+ # should work because server is listening on inet and inet6
+ my $cl = IO::Socket::INET6->new(
+ PeerPort => $port,
+ PeerAddr => 'localhost',
+ Timeout => 5,
+ ) or die "$@";
+
+ print $cl->getline(); # ok 2
+ print $cl "ok 3\n";
+ close($cl);
+
+ # this should not work because listener is closed
+ if ( $cl = IO::Socket::INET6->new(
+ PeerPort => $port,
+ PeerAddr => 'localhost',
+ Timeout => 5,
+ )) {
+ print "not ok 4\n";
+ exit
+ }
+ print "ok 4\n";
+ # but same thing with multihoming should work because server
+ # is still listening on the other family
+ $cl = IO::Socket::INET6->new(
+ PeerPort => $port,
+ PeerAddr => 'localhost',
+ Timeout => 5,
+ MultiHomed => 1,
+ ) or do {
+ print "not ok 5\n";
+ exit
+ };
+ print $cl->getline(); # ok 5
+ print $cl "ok 6\n";
exit;
+
} else {
- die;
+ die $!; # fork failed
}