1
0
mirror of https://github.com/golang/go synced 2024-11-22 04:34:39 -07:00

net: make Dial and Listen behavior consistent across over platforms

This CL changes the behavior of Dial and Listen API family.

Previous Dial and Listen allow a combo of "tcp6" and IPv4 or IPv6
IPv4-mapped address as its argument, but it also makes slightly
different behaviors between Linux and other platforms. This CL fixes
such differences across over platforms by tweaking IP-level socket
option IPV6_V6ONLY. Consequently new Dial and Listen API family will
reject arguments consists of "tcp6" and IPv4 or IPv6 IPv4-mapped
address.

This CL also adds a bit clarified unicast listener tests.

Fixes #2581.

R=rsc, minux.ma
CC=golang-dev
https://golang.org/cl/5677086
This commit is contained in:
Mikio Hara 2012-03-06 00:13:10 +09:00
parent fae0d35043
commit b5dc8724cb
13 changed files with 596 additions and 113 deletions

View File

@ -60,12 +60,7 @@ func TestFileListener(t *testing.T) {
return return
} }
testFileListener(t, "tcp", "127.0.0.1") testFileListener(t, "tcp", "127.0.0.1")
testFileListener(t, "tcp", "127.0.0.1") testFileListener(t, "tcp", "[::ffff:127.0.0.1]")
if supportsIPv6 && supportsIPv4map {
testFileListener(t, "tcp", "[::ffff:127.0.0.1]")
testFileListener(t, "tcp", "127.0.0.1")
testFileListener(t, "tcp", "[::ffff:127.0.0.1]")
}
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
testFileListener(t, "unix", "@gotest/net") testFileListener(t, "unix", "@gotest/net")
testFileListener(t, "unixpacket", "@gotest/net") testFileListener(t, "unixpacket", "@gotest/net")
@ -125,12 +120,10 @@ func TestFilePacketConn(t *testing.T) {
} }
testFilePacketConnListen(t, "udp", "127.0.0.1:0") testFilePacketConnListen(t, "udp", "127.0.0.1:0")
testFilePacketConnDial(t, "udp", "127.0.0.1:12345") testFilePacketConnDial(t, "udp", "127.0.0.1:12345")
testFilePacketConnDial(t, "udp", "[::ffff:127.0.0.1]:12345")
if supportsIPv6 { if supportsIPv6 {
testFilePacketConnListen(t, "udp", "[::1]:0") testFilePacketConnListen(t, "udp", "[::1]:0")
} }
if supportsIPv6 && supportsIPv4map {
testFilePacketConnDial(t, "udp", "[::ffff:127.0.0.1]:12345")
}
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
testFilePacketConnListen(t, "unixgram", "@gotest1/net") testFilePacketConnListen(t, "unixgram", "@gotest1/net")
} }

View File

@ -34,6 +34,13 @@ func (a *IPAddr) family() int {
return syscall.AF_INET6 return syscall.AF_INET6
} }
func (a *IPAddr) isWildcard() bool {
if a == nil || a.IP == nil {
return true
}
return a.IP.IsUnspecified()
}
func (a *IPAddr) sockaddr(family int) (syscall.Sockaddr, error) { func (a *IPAddr) sockaddr(family int) (syscall.Sockaddr, error) {
return ipToSockaddr(family, a.IP, 0) return ipToSockaddr(family, a.IP, 0)
} }

View File

@ -38,6 +38,7 @@ func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) {
continue continue
} }
defer closesocket(s) defer closesocket(s)
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0)
sa, err := probes[i].la.toAddr().sockaddr(syscall.AF_INET6) sa, err := probes[i].la.toAddr().sockaddr(syscall.AF_INET6)
if err != nil { if err != nil {
continue continue
@ -55,58 +56,75 @@ func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) {
// favoriteAddrFamily returns the appropriate address family to // favoriteAddrFamily returns the appropriate address family to
// the given net, laddr, raddr and mode. At first it figures // the given net, laddr, raddr and mode. At first it figures
// address family out from the net. If mode indicates "listen" // address family out from the net. If mode indicates "listen"
// and laddr.(type).IP is nil, it assumes that the user wants to // and laddr is a wildcard, it assumes that the user wants to
// make a passive connection with wildcard address family, both // make a passive connection with a wildcard address family, both
// INET and INET6, and wildcard address. Otherwise guess: if the // AF_INET and AF_INET6, and a wildcard address like following:
// addresses are IPv4 then returns INET, or else returns INET6. //
func favoriteAddrFamily(net string, laddr, raddr sockaddr, mode string) int { // 1. A wild-wild listen, "tcp" + ""
// If the platform supports both IPv6 and IPv6 IPv4-mapping
// capabilities, we assume that the user want to listen on
// both IPv4 and IPv6 wildcard address over an AF_INET6
// socket with IPV6_V6ONLY=0. Otherwise we prefer an IPv4
// wildcard address listen over an AF_INET socket.
//
// 2. A wild-ipv4wild listen, "tcp" + "0.0.0.0"
// Same as 1.
//
// 3. A wild-ipv6wild listen, "tcp" + "[::]"
// Almost same as 1 but we prefer an IPv6 wildcard address
// listen over an AF_INET6 socket with IPV6_V6ONLY=0 when
// the platform supports IPv6 capability but not IPv6 IPv4-
// mapping capability.
//
// 4. A ipv4-ipv4wild listen, "tcp4" + "" or "0.0.0.0"
// We use an IPv4 (AF_INET) wildcard address listen.
//
// 5. A ipv6-ipv6wild listen, "tcp6" + "" or "[::]"
// We use an IPv6 (AF_INET6, IPV6_V6ONLY=1) wildcard address
// listen.
//
// Otherwise guess: if the addresses are IPv4 then returns AF_INET,
// or else returns AF_INET6. It also returns a boolean value what
// designates IPV6_V6ONLY option.
//
// Note that OpenBSD allows neither "net.inet6.ip6.v6only=1" change
// nor IPPROTO_IPV6 level IPV6_V6ONLY socket option setting.
func favoriteAddrFamily(net string, laddr, raddr sockaddr, mode string) (family int, ipv6only bool) {
switch net[len(net)-1] { switch net[len(net)-1] {
case '4': case '4':
return syscall.AF_INET return syscall.AF_INET, false
case '6': case '6':
return syscall.AF_INET6 return syscall.AF_INET6, true
} }
if mode == "listen" { if mode == "listen" && laddr.isWildcard() {
// Note that OpenBSD allows neither "net.inet6.ip6.v6only" if supportsIPv4map {
// change nor IPPROTO_IPV6 level IPV6_V6ONLY socket option return syscall.AF_INET6, false
// setting.
switch a := laddr.(type) {
case *TCPAddr:
if a.IP == nil && supportsIPv6 && supportsIPv4map {
return syscall.AF_INET6
}
case *UDPAddr:
if a.IP == nil && supportsIPv6 && supportsIPv4map {
return syscall.AF_INET6
}
case *IPAddr:
if a.IP == nil && supportsIPv6 && supportsIPv4map {
return syscall.AF_INET6
}
} }
return laddr.family(), false
} }
if (laddr == nil || laddr.family() == syscall.AF_INET) && if (laddr == nil || laddr.family() == syscall.AF_INET) &&
(raddr == nil || raddr.family() == syscall.AF_INET) { (raddr == nil || raddr.family() == syscall.AF_INET) {
return syscall.AF_INET return syscall.AF_INET, false
} }
return syscall.AF_INET6 return syscall.AF_INET6, false
} }
// Internet sockets (TCP, UDP) // Internet sockets (TCP, UDP, IP)
// A sockaddr represents a TCP or UDP network address that can // A sockaddr represents a TCP, UDP or IP network address that can
// be converted into a syscall.Sockaddr. // be converted into a syscall.Sockaddr.
type sockaddr interface { type sockaddr interface {
Addr Addr
sockaddr(family int) (syscall.Sockaddr, error)
family() int family() int
isWildcard() bool
sockaddr(family int) (syscall.Sockaddr, error)
} }
func internetSocket(net string, laddr, raddr sockaddr, sotype, proto int, mode string, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) { func internetSocket(net string, laddr, raddr sockaddr, sotype, proto int, mode string, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) {
var la, ra syscall.Sockaddr var la, ra syscall.Sockaddr
family := favoriteAddrFamily(net, laddr, raddr, mode) family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
if laddr != nil { if laddr != nil {
if la, err = laddr.sockaddr(family); err != nil { if la, err = laddr.sockaddr(family); err != nil {
goto Error goto Error
@ -117,7 +135,7 @@ func internetSocket(net string, laddr, raddr sockaddr, sotype, proto int, mode s
goto Error goto Error
} }
} }
fd, err = socket(net, family, sotype, proto, la, ra, toAddr) fd, err = socket(net, family, sotype, proto, ipv6only, la, ra, toAddr)
if err != nil { if err != nil {
goto Error goto Error
} }
@ -152,7 +170,7 @@ func ipToSockaddr(family int, ip IP, port int) (syscall.Sockaddr, error) {
} }
// IPv4 callers use 0.0.0.0 to mean "announce on any available address". // IPv4 callers use 0.0.0.0 to mean "announce on any available address".
// In IPv6 mode, Linux treats that as meaning "announce on 0.0.0.0", // In IPv6 mode, Linux treats that as meaning "announce on 0.0.0.0",
// which it refuses to do. Rewrite to the IPv6 all zeros. // which it refuses to do. Rewrite to the IPv6 unspecified address.
if ip.Equal(IPv4zero) { if ip.Equal(IPv4zero) {
ip = IPv6zero ip = IPv6zero
} }

View File

@ -11,6 +11,14 @@ import (
"time" "time"
) )
// avoidOSXFirewallDialogPopup avoids OS X, former konwn as MacOS X,
// firewall dialog popups during tests. It looks like OS X checks
// wildcard listens by default for security reasons. A listen with
// specific address doesn't make dialog popups for now.
var avoidOSXFirewallDialogPopup = func() bool {
return testing.Short() && runtime.GOOS == "darwin"
}
func TestShutdown(t *testing.T) { func TestShutdown(t *testing.T) {
if runtime.GOOS == "plan9" { if runtime.GOOS == "plan9" {
return return

View File

@ -128,19 +128,14 @@ func TestTCPServer(t *testing.T) {
doTest(t, "tcp6", "[::]", "[::1]") doTest(t, "tcp6", "[::]", "[::1]")
doTest(t, "tcp6", "[::1]", "[::1]") doTest(t, "tcp6", "[::1]", "[::1]")
} }
if supportsIPv6 && supportsIPv4map { if supportsIPv4map {
doTest(t, "tcp", "[::ffff:0.0.0.0]", "127.0.0.1") doTest(t, "tcp", "[::ffff:0.0.0.0]", "127.0.0.1")
doTest(t, "tcp", "[::]", "127.0.0.1") doTest(t, "tcp", "[::]", "127.0.0.1")
doTest(t, "tcp4", "[::ffff:0.0.0.0]", "127.0.0.1") doTest(t, "tcp4", "[::ffff:0.0.0.0]", "127.0.0.1")
doTest(t, "tcp6", "", "127.0.0.1")
doTest(t, "tcp6", "[::ffff:0.0.0.0]", "127.0.0.1")
doTest(t, "tcp6", "[::]", "127.0.0.1")
doTest(t, "tcp", "127.0.0.1", "[::ffff:127.0.0.1]") doTest(t, "tcp", "127.0.0.1", "[::ffff:127.0.0.1]")
doTest(t, "tcp", "[::ffff:127.0.0.1]", "127.0.0.1") doTest(t, "tcp", "[::ffff:127.0.0.1]", "127.0.0.1")
doTest(t, "tcp4", "127.0.0.1", "[::ffff:127.0.0.1]") doTest(t, "tcp4", "127.0.0.1", "[::ffff:127.0.0.1]")
doTest(t, "tcp4", "[::ffff:127.0.0.1]", "127.0.0.1") doTest(t, "tcp4", "[::ffff:127.0.0.1]", "127.0.0.1")
doTest(t, "tcp6", "127.0.0.1", "[::ffff:127.0.0.1]")
doTest(t, "tcp6", "[::ffff:127.0.0.1]", "127.0.0.1")
} }
} }
@ -215,7 +210,7 @@ func TestUDPServer(t *testing.T) {
for _, isEmpty := range []bool{false, true} { for _, isEmpty := range []bool{false, true} {
doTestPacket(t, "udp", "0.0.0.0", "127.0.0.1", isEmpty) doTestPacket(t, "udp", "0.0.0.0", "127.0.0.1", isEmpty)
doTestPacket(t, "udp", "", "127.0.0.1", isEmpty) doTestPacket(t, "udp", "", "127.0.0.1", isEmpty)
if supportsIPv6 && supportsIPv4map { if supportsIPv4map {
doTestPacket(t, "udp", "[::]", "[::ffff:127.0.0.1]", isEmpty) doTestPacket(t, "udp", "[::]", "[::ffff:127.0.0.1]", isEmpty)
doTestPacket(t, "udp", "[::]", "127.0.0.1", isEmpty) doTestPacket(t, "udp", "[::]", "127.0.0.1", isEmpty)
doTestPacket(t, "udp", "0.0.0.0", "[::ffff:127.0.0.1]", isEmpty) doTestPacket(t, "udp", "0.0.0.0", "[::ffff:127.0.0.1]", isEmpty)

View File

@ -16,7 +16,7 @@ import (
var listenerBacklog = maxListenerBacklog() var listenerBacklog = maxListenerBacklog()
// Generic socket creation. // Generic socket creation.
func socket(net string, f, t, p int, la, ra syscall.Sockaddr, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) { func socket(net string, f, t, p int, ipv6only bool, la, ra syscall.Sockaddr, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) {
// See ../syscall/exec.go for description of ForkLock. // See ../syscall/exec.go for description of ForkLock.
syscall.ForkLock.RLock() syscall.ForkLock.RLock()
s, err := syscall.Socket(f, t, p) s, err := syscall.Socket(f, t, p)
@ -27,7 +27,7 @@ func socket(net string, f, t, p int, la, ra syscall.Sockaddr, toAddr func(syscal
syscall.CloseOnExec(s) syscall.CloseOnExec(s)
syscall.ForkLock.RUnlock() syscall.ForkLock.RUnlock()
err = setDefaultSockopts(s, f, t) err = setDefaultSockopts(s, f, t, ipv6only)
if err != nil { if err != nil {
closesocket(s) closesocket(s)
return nil, err return nil, err

View File

@ -13,12 +13,17 @@ import (
"syscall" "syscall"
) )
func setDefaultSockopts(s, f, t int) error { func setDefaultSockopts(s, f, t int, ipv6only bool) error {
switch f { switch f {
case syscall.AF_INET6: case syscall.AF_INET6:
// Allow both IP versions even if the OS default is otherwise. if ipv6only {
// Note that some operating systems never admit this option. syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1)
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0) } else {
// Allow both IP versions even if the OS default
// is otherwise. Note that some operating systems
// never admit this option.
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0)
}
} }
// Allow broadcast. // Allow broadcast.
err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1) err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)

View File

@ -11,12 +11,17 @@ import (
"syscall" "syscall"
) )
func setDefaultSockopts(s, f, t int) error { func setDefaultSockopts(s, f, t int, ipv6only bool) error {
switch f { switch f {
case syscall.AF_INET6: case syscall.AF_INET6:
// Allow both IP versions even if the OS default is otherwise. if ipv6only {
// Note that some operating systems never admit this option. syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1)
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0) } else {
// Allow both IP versions even if the OS default
// is otherwise. Note that some operating systems
// never admit this option.
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0)
}
} }
// Allow broadcast. // Allow broadcast.
err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1) err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)

View File

@ -11,12 +11,17 @@ import (
"syscall" "syscall"
) )
func setDefaultSockopts(s syscall.Handle, f, t int) error { func setDefaultSockopts(s syscall.Handle, f, t int, ipv6only bool) error {
switch f { switch f {
case syscall.AF_INET6: case syscall.AF_INET6:
// Allow both IP versions even if the OS default is otherwise. if ipv6only {
// Note that some operating systems never admit this option. syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1)
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0) } else {
// Allow both IP versions even if the OS default
// is otherwise. Note that some operating systems
// never admit this option.
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0)
}
} }
// Allow broadcast. // Allow broadcast.
syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1) syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)

View File

@ -46,6 +46,13 @@ func (a *TCPAddr) family() int {
return syscall.AF_INET6 return syscall.AF_INET6
} }
func (a *TCPAddr) isWildcard() bool {
if a == nil || a.IP == nil {
return true
}
return a.IP.IsUnspecified()
}
func (a *TCPAddr) sockaddr(family int) (syscall.Sockaddr, error) { func (a *TCPAddr) sockaddr(family int) (syscall.Sockaddr, error) {
return ipToSockaddr(family, a.IP, a.Port) return ipToSockaddr(family, a.IP, a.Port)
} }

View File

@ -37,6 +37,13 @@ func (a *UDPAddr) family() int {
return syscall.AF_INET6 return syscall.AF_INET6
} }
func (a *UDPAddr) isWildcard() bool {
if a == nil || a.IP == nil {
return true
}
return a.IP.IsUnspecified()
}
func (a *UDPAddr) sockaddr(family int) (syscall.Sockaddr, error) { func (a *UDPAddr) sockaddr(family int) (syscall.Sockaddr, error) {
return ipToSockaddr(family, a.IP, a.Port) return ipToSockaddr(family, a.IP, a.Port)
} }

View File

@ -7,81 +7,484 @@ package net
import ( import (
"io" "io"
"runtime" "runtime"
"syscall"
"testing" "testing"
) )
var unicastTests = []struct { var listenerTests = []struct {
net string net string
laddr string laddr string
ipv6 bool ipv6 bool // test with underlying AF_INET6 socket
packet bool wildcard bool // test with wildcard address
}{ }{
{net: "tcp4", laddr: "127.0.0.1:0"}, {net: "tcp", laddr: "", wildcard: true},
{net: "tcp4", laddr: "previous"}, {net: "tcp", laddr: "0.0.0.0", wildcard: true},
{net: "tcp6", laddr: "[::1]:0", ipv6: true}, {net: "tcp", laddr: "[::ffff:0.0.0.0]", wildcard: true},
{net: "tcp6", laddr: "previous", ipv6: true}, {net: "tcp", laddr: "[::]", ipv6: true, wildcard: true},
{net: "udp4", laddr: "127.0.0.1:0", packet: true},
{net: "udp6", laddr: "[::1]:0", ipv6: true, packet: true}, {net: "tcp", laddr: "127.0.0.1"},
{net: "tcp", laddr: "[::ffff:127.0.0.1]"},
{net: "tcp", laddr: "[::1]", ipv6: true},
{net: "tcp4", laddr: "", wildcard: true},
{net: "tcp4", laddr: "0.0.0.0", wildcard: true},
{net: "tcp4", laddr: "[::ffff:0.0.0.0]", wildcard: true},
{net: "tcp4", laddr: "127.0.0.1"},
{net: "tcp4", laddr: "[::ffff:127.0.0.1]"},
{net: "tcp6", laddr: "", ipv6: true, wildcard: true},
{net: "tcp6", laddr: "[::]", ipv6: true, wildcard: true},
{net: "tcp6", laddr: "[::1]", ipv6: true},
} }
func TestUnicastTCPAndUDP(t *testing.T) { // TestTCPListener tests both single and double listen to a test
if runtime.GOOS == "plan9" || runtime.GOOS == "windows" { // listener with same address family, same listening address and
// same port.
func TestTCPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9", "windows":
return return
} }
prevladdr := "" for _, tt := range listenerTests {
for _, tt := range unicastTests { if tt.wildcard && avoidOSXFirewallDialogPopup() {
continue
}
if tt.ipv6 && !supportsIPv6 { if tt.ipv6 && !supportsIPv6 {
continue continue
} }
var ( port := usableLocalPort(t, tt.net, tt.laddr)
fd *netFD l1, err := Listen(tt.net, tt.laddr+":"+port)
closer io.Closer if err != nil {
) t.Fatalf("First Listen(%q, %q) failed: %v", tt.net, tt.laddr+":"+port, err)
if !tt.packet {
if tt.laddr == "previous" {
tt.laddr = prevladdr
}
l, err := Listen(tt.net, tt.laddr)
if err != nil {
t.Fatalf("Listen failed: %v", err)
}
prevladdr = l.Addr().String()
closer = l
fd = l.(*TCPListener).fd
} else {
c, err := ListenPacket(tt.net, tt.laddr)
if err != nil {
t.Fatalf("ListenPacket failed: %v", err)
}
closer = c
fd = c.(*UDPConn).fd
} }
if !tt.ipv6 { checkFirstListener(t, tt.net, tt.laddr+":"+port, l1)
l2, err := Listen(tt.net, tt.laddr+":"+port)
checkSecondListener(t, tt.net, tt.laddr+":"+port, err, l2)
fd := l1.(*TCPListener).fd
switch fd.family {
case syscall.AF_INET:
testIPv4UnicastSocketOptions(t, fd) testIPv4UnicastSocketOptions(t, fd)
} else { case syscall.AF_INET6:
testIPv6UnicastSocketOptions(t, fd) testIPv6UnicastSocketOptions(t, fd)
} }
closer.Close() l1.(io.Closer).Close()
}
}
// TestUDPListener tests both single and double listen to a test
// listener with same address family, same listening address and
// same port.
func TestUDPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9", "windows":
return
}
toudpnet := func(net string) string {
switch net {
case "tcp":
return "udp"
case "tcp4":
return "udp4"
case "tcp6":
return "udp6"
}
return "<nil>"
}
for _, tt := range listenerTests {
if tt.wildcard && avoidOSXFirewallDialogPopup() {
continue
}
if tt.ipv6 && !supportsIPv6 {
continue
}
tt.net = toudpnet(tt.net)
port := usableLocalPort(t, tt.net, tt.laddr)
l1, err := ListenPacket(tt.net, tt.laddr+":"+port)
if err != nil {
t.Fatalf("First ListenPacket(%q, %q) failed: %v", tt.net, tt.laddr+":"+port, err)
}
checkFirstListener(t, tt.net, tt.laddr+":"+port, l1)
l2, err := ListenPacket(tt.net, tt.laddr+":"+port)
checkSecondListener(t, tt.net, tt.laddr+":"+port, err, l2)
fd := l1.(*UDPConn).fd
switch fd.family {
case syscall.AF_INET:
testIPv4UnicastSocketOptions(t, fd)
case syscall.AF_INET6:
testIPv6UnicastSocketOptions(t, fd)
}
l1.(io.Closer).Close()
}
}
func TestSimpleTCPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
return
}
for _, tt := range listenerTests {
if tt.wildcard && avoidOSXFirewallDialogPopup() {
continue
}
if tt.ipv6 {
continue
}
port := usableLocalPort(t, tt.net, tt.laddr)
l1, err := Listen(tt.net, tt.laddr+":"+port)
if err != nil {
t.Fatalf("First Listen(%q, %q) failed: %v", tt.net, tt.laddr+":"+port, err)
}
checkFirstListener(t, tt.net, tt.laddr+":"+port, l1)
l2, err := Listen(tt.net, tt.laddr+":"+port)
checkSecondListener(t, tt.net, tt.laddr+":"+port, err, l2)
l1.(io.Closer).Close()
}
}
func TestSimpleUDPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
return
}
toudpnet := func(net string) string {
switch net {
case "tcp":
return "udp"
case "tcp4":
return "udp4"
case "tcp6":
return "udp6"
}
return "<nil>"
}
for _, tt := range listenerTests {
if tt.wildcard && avoidOSXFirewallDialogPopup() {
continue
}
if tt.ipv6 {
continue
}
tt.net = toudpnet(tt.net)
port := usableLocalPort(t, tt.net, tt.laddr)
l1, err := ListenPacket(tt.net, tt.laddr+":"+port)
if err != nil {
t.Fatalf("First ListenPacket(%q, %q) failed: %v", tt.net, tt.laddr+":"+port, err)
}
checkFirstListener(t, tt.net, tt.laddr+":"+port, l1)
l2, err := ListenPacket(tt.net, tt.laddr+":"+port)
checkSecondListener(t, tt.net, tt.laddr+":"+port, err, l2)
l1.(io.Closer).Close()
}
}
var dualStackListenerTests = []struct {
net1 string // first listener
laddr1 string
net2 string // second listener
laddr2 string
wildcard bool // test with wildcard address
xerr error // expected error value, nil or other
}{
// Test cases and expected results for the attemping 2nd listen on the same port
// 1st listen 2nd listen darwin freebsd linux openbsd
// ------------------------------------------------------------------------------------
// "tcp" "" "tcp" "" - - - -
// "tcp" "" "tcp" "0.0.0.0" - - - -
// "tcp" "0.0.0.0" "tcp" "" - - - -
// ------------------------------------------------------------------------------------
// "tcp" "" "tcp" "[::]" - - - ok
// "tcp" "[::]" "tcp" "" - - - ok
// "tcp" "0.0.0.0" "tcp" "[::]" - - - ok
// "tcp" "[::]" "tcp" "0.0.0.0" - - - ok
// "tcp" "[::ffff:0.0.0.0]" "tcp" "[::]" - - - ok
// "tcp" "[::]" "tcp" "[::ffff:0.0.0.0]" - - - ok
// ------------------------------------------------------------------------------------
// "tcp4" "" "tcp6" "" ok ok ok ok
// "tcp6" "" "tcp4" "" ok ok ok ok
// "tcp4" "0.0.0.0" "tcp6" "[::]" ok ok ok ok
// "tcp6" "[::]" "tcp4" "0.0.0.0" ok ok ok ok
// ------------------------------------------------------------------------------------
// "tcp" "127.0.0.1" "tcp" "[::1]" ok ok ok ok
// "tcp" "[::1]" "tcp" "127.0.0.1" ok ok ok ok
// "tcp4" "127.0.0.1" "tcp6" "[::1]" ok ok ok ok
// "tcp6" "[::1]" "tcp4" "127.0.0.1" ok ok ok ok
//
// Platform default configurations:
// darwin, kernel version 11.3.0
// net.inet6.ip6.v6only=0 (overridable by sysctl or IPV6_V6ONLY option)
// freebsd, kernel version 8.2
// net.inet6.ip6.v6only=1 (overridable by sysctl or IPV6_V6ONLY option)
// linux, kernel version 3.0.0
// net.ipv6.bindv6only=0 (overridable by sysctl or IPV6_V6ONLY option)
// openbsd, kernel version 5.0
// net.inet6.ip6.v6only=1 (overriding is prohibited)
{net1: "tcp", laddr1: "", net2: "tcp", laddr2: "", wildcard: true, xerr: syscall.EADDRINUSE},
{net1: "tcp", laddr1: "", net2: "tcp", laddr2: "0.0.0.0", wildcard: true, xerr: syscall.EADDRINUSE},
{net1: "tcp", laddr1: "0.0.0.0", net2: "tcp", laddr2: "", wildcard: true, xerr: syscall.EADDRINUSE},
{net1: "tcp", laddr1: "", net2: "tcp", laddr2: "[::]", wildcard: true, xerr: syscall.EADDRINUSE},
{net1: "tcp", laddr1: "[::]", net2: "tcp", laddr2: "", wildcard: true, xerr: syscall.EADDRINUSE},
{net1: "tcp", laddr1: "0.0.0.0", net2: "tcp", laddr2: "[::]", wildcard: true, xerr: syscall.EADDRINUSE},
{net1: "tcp", laddr1: "[::]", net2: "tcp", laddr2: "0.0.0.0", wildcard: true, xerr: syscall.EADDRINUSE},
{net1: "tcp", laddr1: "[::ffff:0.0.0.0]", net2: "tcp", laddr2: "[::]", wildcard: true, xerr: syscall.EADDRINUSE},
{net1: "tcp", laddr1: "[::]", net2: "tcp", laddr2: "[::ffff:0.0.0.0]", wildcard: true, xerr: syscall.EADDRINUSE},
{net1: "tcp4", laddr1: "", net2: "tcp6", laddr2: "", wildcard: true},
{net1: "tcp6", laddr1: "", net2: "tcp4", laddr2: "", wildcard: true},
{net1: "tcp4", laddr1: "0.0.0.0", net2: "tcp6", laddr2: "[::]", wildcard: true},
{net1: "tcp6", laddr1: "[::]", net2: "tcp4", laddr2: "0.0.0.0", wildcard: true},
{net1: "tcp", laddr1: "127.0.0.1", net2: "tcp", laddr2: "[::1]"},
{net1: "tcp", laddr1: "[::1]", net2: "tcp", laddr2: "127.0.0.1"},
{net1: "tcp4", laddr1: "127.0.0.1", net2: "tcp6", laddr2: "[::1]"},
{net1: "tcp6", laddr1: "[::1]", net2: "tcp4", laddr2: "127.0.0.1"},
}
// TestDualStackTCPListener tests both single and double listen
// to a test listener with various address families, differnet
// listening address and same port.
func TestDualStackTCPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
return
}
if !supportsIPv6 {
return
}
for _, tt := range dualStackListenerTests {
if tt.wildcard && avoidOSXFirewallDialogPopup() {
continue
}
switch runtime.GOOS {
case "openbsd":
if tt.wildcard && differentWildcardAddr(tt.laddr1, tt.laddr2) {
tt.xerr = nil
}
}
port := usableLocalPort(t, tt.net1, tt.laddr1)
laddr := tt.laddr1 + ":" + port
l1, err := Listen(tt.net1, laddr)
if err != nil {
t.Fatalf("First Listen(%q, %q) failed: %v", tt.net1, laddr, err)
}
checkFirstListener(t, tt.net1, laddr, l1)
laddr = tt.laddr2 + ":" + port
l2, err := Listen(tt.net2, laddr)
checkDualStackSecondListener(t, tt.net2, laddr, tt.xerr, err, l2)
l1.Close()
}
}
// TestDualStackUDPListener tests both single and double listen
// to a test listener with various address families, differnet
// listening address and same port.
func TestDualStackUDPListener(t *testing.T) {
switch runtime.GOOS {
case "plan9":
return
}
if !supportsIPv6 {
return
}
toudpnet := func(net string) string {
switch net {
case "tcp":
return "udp"
case "tcp4":
return "udp4"
case "tcp6":
return "udp6"
}
return "<nil>"
}
for _, tt := range dualStackListenerTests {
if tt.wildcard && avoidOSXFirewallDialogPopup() {
continue
}
tt.net1 = toudpnet(tt.net1)
tt.net2 = toudpnet(tt.net2)
switch runtime.GOOS {
case "openbsd":
if tt.wildcard && differentWildcardAddr(tt.laddr1, tt.laddr2) {
tt.xerr = nil
}
}
port := usableLocalPort(t, tt.net1, tt.laddr1)
laddr := tt.laddr1 + ":" + port
l1, err := ListenPacket(tt.net1, laddr)
if err != nil {
t.Fatalf("First ListenPacket(%q, %q) failed: %v", tt.net1, laddr, err)
}
checkFirstListener(t, tt.net1, laddr, l1)
laddr = tt.laddr2 + ":" + port
l2, err := ListenPacket(tt.net2, laddr)
checkDualStackSecondListener(t, tt.net2, laddr, tt.xerr, err, l2)
l1.Close()
}
}
func usableLocalPort(t *testing.T, net, laddr string) string {
var nladdr string
switch net {
case "tcp", "tcp4", "tcp6":
l, err := Listen(net, laddr+":0")
if err != nil {
t.Fatalf("Probe Listen(%q, %q) failed: %v", net, laddr, err)
}
defer l.Close()
nladdr = l.(*TCPListener).Addr().String()
case "udp", "udp4", "udp6":
c, err := ListenPacket(net, laddr+":0")
if err != nil {
t.Fatalf("Probe ListenPacket(%q, %q) failed: %v", net, laddr, err)
}
defer c.Close()
nladdr = c.(*UDPConn).LocalAddr().String()
}
_, port, err := SplitHostPort(nladdr)
if err != nil {
t.Fatalf("SplitHostPort failed: %v", err)
}
return port
}
func differentWildcardAddr(i, j string) bool {
if (i == "" || i == "0.0.0.0" || i == "::ffff:0.0.0.0") && (j == "" || j == "0.0.0.0" || j == "::ffff:0.0.0.0") {
return false
}
if i == "[::]" && j == "[::]" {
return false
}
return true
}
func checkFirstListener(t *testing.T, net, laddr string, l interface{}) {
switch net {
case "tcp":
fd := l.(*TCPListener).fd
checkDualStackAddrFamily(t, net, laddr, fd)
case "tcp4":
fd := l.(*TCPListener).fd
if fd.family != syscall.AF_INET {
t.Fatalf("First Listen(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET)
}
case "tcp6":
fd := l.(*TCPListener).fd
if fd.family != syscall.AF_INET6 {
t.Fatalf("First Listen(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET6)
}
case "udp":
fd := l.(*UDPConn).fd
checkDualStackAddrFamily(t, net, laddr, fd)
case "udp4":
fd := l.(*UDPConn).fd
if fd.family != syscall.AF_INET {
t.Fatalf("First ListenPacket(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET)
}
case "udp6":
fd := l.(*UDPConn).fd
if fd.family != syscall.AF_INET6 {
t.Fatalf("First ListenPacket(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET6)
}
default:
t.Fatalf("Unexpected network: %q", net)
}
}
func checkSecondListener(t *testing.T, net, laddr string, err error, l interface{}) {
switch net {
case "tcp", "tcp4", "tcp6":
if err == nil {
l.(*TCPListener).Close()
t.Fatalf("Second Listen(%q, %q) should fail", net, laddr)
}
case "udp", "udp4", "udp6":
if err == nil {
l.(*UDPConn).Close()
t.Fatalf("Second ListenPacket(%q, %q) should fail", net, laddr)
}
default:
t.Fatalf("Unexpected network: %q", net)
}
}
func checkDualStackSecondListener(t *testing.T, net, laddr string, xerr, err error, l interface{}) {
switch net {
case "tcp", "tcp4", "tcp6":
if xerr == nil && err != nil || xerr != nil && err == nil {
t.Fatalf("Second Listen(%q, %q) returns %v, expected %v", net, laddr, err, xerr)
}
l.(*TCPListener).Close()
case "udp", "udp4", "udp6":
if xerr == nil && err != nil || xerr != nil && err == nil {
t.Fatalf("Second ListenPacket(%q, %q) returns %v, expected %v", net, laddr, err, xerr)
}
l.(*UDPConn).Close()
default:
t.Fatalf("Unexpected network: %q", net)
}
}
func checkDualStackAddrFamily(t *testing.T, net, laddr string, fd *netFD) {
switch a := fd.laddr.(type) {
case *TCPAddr:
// If a node under test supports both IPv6 capability
// and IPv6 IPv4-mapping capability, we can assume
// that the node listens on a wildcard address with an
// AF_INET6 socket.
if supportsIPv4map && fd.laddr.(*TCPAddr).isWildcard() {
if fd.family != syscall.AF_INET6 {
t.Fatalf("Listen(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET6)
}
} else {
if fd.family != a.family() {
t.Fatalf("Listen(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, a.family())
}
}
case *UDPAddr:
// If a node under test supports both IPv6 capability
// and IPv6 IPv4-mapping capability, we can assume
// that the node listens on a wildcard address with an
// AF_INET6 socket.
if supportsIPv4map && fd.laddr.(*UDPAddr).isWildcard() {
if fd.family != syscall.AF_INET6 {
t.Fatalf("ListenPacket(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, syscall.AF_INET6)
}
} else {
if fd.family != a.family() {
t.Fatalf("ListenPacket(%q, %q) returns address family %v, expected %v", net, laddr, fd.family, a.family())
}
}
default:
t.Fatalf("Unexpected protocol address type: %T", a)
} }
} }
func testIPv4UnicastSocketOptions(t *testing.T, fd *netFD) { func testIPv4UnicastSocketOptions(t *testing.T, fd *netFD) {
tos, err := ipv4TOS(fd) _, err := ipv4TOS(fd)
if err != nil { if err != nil {
t.Fatalf("ipv4TOS failed: %v", err) t.Fatalf("ipv4TOS failed: %v", err)
} }
t.Logf("IPv4 TOS: %v", tos)
err = setIPv4TOS(fd, 1) err = setIPv4TOS(fd, 1)
if err != nil { if err != nil {
t.Fatalf("setIPv4TOS failed: %v", err) t.Fatalf("setIPv4TOS failed: %v", err)
} }
_, err = ipv4TTL(fd)
ttl, err := ipv4TTL(fd)
if err != nil { if err != nil {
t.Fatalf("ipv4TTL failed: %v", err) t.Fatalf("ipv4TTL failed: %v", err)
} }
t.Logf("IPv4 TTL: %v", ttl)
err = setIPv4TTL(fd, 1) err = setIPv4TTL(fd, 1)
if err != nil { if err != nil {
t.Fatalf("setIPv4TTL failed: %v", err) t.Fatalf("setIPv4TTL failed: %v", err)
@ -89,23 +492,53 @@ func testIPv4UnicastSocketOptions(t *testing.T, fd *netFD) {
} }
func testIPv6UnicastSocketOptions(t *testing.T, fd *netFD) { func testIPv6UnicastSocketOptions(t *testing.T, fd *netFD) {
tos, err := ipv6TrafficClass(fd) _, err := ipv6TrafficClass(fd)
if err != nil { if err != nil {
t.Fatalf("ipv6TrafficClass failed: %v", err) t.Fatalf("ipv6TrafficClass failed: %v", err)
} }
t.Logf("IPv6 TrafficClass: %v", tos)
err = setIPv6TrafficClass(fd, 1) err = setIPv6TrafficClass(fd, 1)
if err != nil { if err != nil {
t.Fatalf("setIPv6TrafficClass failed: %v", err) t.Fatalf("setIPv6TrafficClass failed: %v", err)
} }
_, err = ipv6HopLimit(fd)
hoplim, err := ipv6HopLimit(fd)
if err != nil { if err != nil {
t.Fatalf("ipv6HopLimit failed: %v", err) t.Fatalf("ipv6HopLimit failed: %v", err)
} }
t.Logf("IPv6 HopLimit: %v", hoplim)
err = setIPv6HopLimit(fd, 1) err = setIPv6HopLimit(fd, 1)
if err != nil { if err != nil {
t.Fatalf("setIPv6HopLimit failed: %v", err) t.Fatalf("setIPv6HopLimit failed: %v", err)
} }
} }
var prohibitionaryDialArgTests = []struct {
net string
addr string
}{
{"tcp6", "127.0.0.1"},
{"tcp6", "[::ffff:127.0.0.1]"},
}
func TestProhibitionaryDialArgs(t *testing.T) {
switch runtime.GOOS {
case "plan9":
return
}
// This test requires both IPv6 and IPv6 IPv4-mapping functionality.
if !supportsIPv4map || avoidOSXFirewallDialogPopup() {
return
}
port := usableLocalPort(t, "tcp", "[::]")
l, err := Listen("tcp", "[::]"+":"+port)
if err != nil {
t.Fatalf("Listen failed: %v", err)
}
defer l.Close()
for _, tt := range prohibitionaryDialArgTests {
_, err = Dial(tt.net, tt.addr+":"+port)
if err == nil {
t.Fatal("Dial(%q, %q) should fail", tt.net, tt.addr)
}
}
}

View File

@ -59,7 +59,7 @@ func unixSocket(net string, laddr, raddr *UnixAddr, mode string) (fd *netFD, err
f = sockaddrToUnixpacket f = sockaddrToUnixpacket
} }
fd, err = socket(net, syscall.AF_UNIX, sotype, 0, la, ra, f) fd, err = socket(net, syscall.AF_UNIX, sotype, 0, false, la, ra, f)
if err != nil { if err != nil {
goto Error goto Error
} }