Skip to content

Commit

Permalink
net: make Dial and Listen behavior consistent across over platforms
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cixtor committed Mar 5, 2012
1 parent fae0d35 commit b5dc872
Show file tree
Hide file tree
Showing 13 changed files with 593 additions and 110 deletions.
11 changes: 2 additions & 9 deletions src/pkg/net/file_test.go
Expand Up @@ -60,12 +60,7 @@ func TestFileListener(t *testing.T) {
return
}
testFileListener(t, "tcp", "127.0.0.1")
testFileListener(t, "tcp", "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]")
}
testFileListener(t, "tcp", "[::ffff:127.0.0.1]")
if runtime.GOOS == "linux" {
testFileListener(t, "unix", "@gotest/net")
testFileListener(t, "unixpacket", "@gotest/net")
Expand Down Expand Up @@ -125,12 +120,10 @@ func TestFilePacketConn(t *testing.T) {
}
testFilePacketConnListen(t, "udp", "127.0.0.1:0")
testFilePacketConnDial(t, "udp", "127.0.0.1:12345")
testFilePacketConnDial(t, "udp", "[::ffff:127.0.0.1]:12345")
if supportsIPv6 {
testFilePacketConnListen(t, "udp", "[::1]:0")
}
if supportsIPv6 && supportsIPv4map {
testFilePacketConnDial(t, "udp", "[::ffff:127.0.0.1]:12345")
}
if runtime.GOOS == "linux" {
testFilePacketConnListen(t, "unixgram", "@gotest1/net")
}
Expand Down
7 changes: 7 additions & 0 deletions src/pkg/net/iprawsock_posix.go
Expand Up @@ -34,6 +34,13 @@ func (a *IPAddr) family() int {
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) {
return ipToSockaddr(family, a.IP, 0)
}
Expand Down
82 changes: 50 additions & 32 deletions src/pkg/net/ipsock_posix.go
Expand Up @@ -38,6 +38,7 @@ func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) {
continue
}
defer closesocket(s)
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 0)
sa, err := probes[i].la.toAddr().sockaddr(syscall.AF_INET6)
if err != nil {
continue
Expand All @@ -55,58 +56,75 @@ func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) {
// favoriteAddrFamily returns the appropriate address family to
// the given net, laddr, raddr and mode. At first it figures
// address family out from the net. If mode indicates "listen"
// and laddr.(type).IP is nil, it assumes that the user wants to
// make a passive connection with wildcard address family, both
// INET and INET6, and wildcard address. Otherwise guess: if the
// addresses are IPv4 then returns INET, or else returns INET6.
func favoriteAddrFamily(net string, laddr, raddr sockaddr, mode string) int {
// and laddr is a wildcard, it assumes that the user wants to
// make a passive connection with a wildcard address family, both
// AF_INET and AF_INET6, and a wildcard address like following:
//
// 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] {
case '4':
return syscall.AF_INET
return syscall.AF_INET, false
case '6':
return syscall.AF_INET6
return syscall.AF_INET6, true
}

if mode == "listen" {
// Note that OpenBSD allows neither "net.inet6.ip6.v6only"
// change nor IPPROTO_IPV6 level IPV6_V6ONLY socket option
// 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
}
if mode == "listen" && laddr.isWildcard() {
if supportsIPv4map {
return syscall.AF_INET6, false
}
return laddr.family(), false
}

if (laddr == nil || laddr.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.
type sockaddr interface {
Addr
sockaddr(family int) (syscall.Sockaddr, error)
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) {
var la, ra syscall.Sockaddr
family := favoriteAddrFamily(net, laddr, raddr, mode)
family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
if laddr != nil {
if la, err = laddr.sockaddr(family); err != nil {
goto Error
Expand All @@ -117,7 +135,7 @@ func internetSocket(net string, laddr, raddr sockaddr, sotype, proto int, mode s
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 {
goto Error
}
Expand Down Expand Up @@ -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".
// 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) {
ip = IPv6zero
}
Expand Down
8 changes: 8 additions & 0 deletions src/pkg/net/net_test.go
Expand Up @@ -11,6 +11,14 @@ import (
"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) {
if runtime.GOOS == "plan9" {
return
Expand Down
9 changes: 2 additions & 7 deletions src/pkg/net/server_test.go
Expand Up @@ -128,19 +128,14 @@ func TestTCPServer(t *testing.T) {
doTest(t, "tcp6", "[::]", "[::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", "[::]", "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", "[::ffff:127.0.0.1]", "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, "tcp6", "127.0.0.1", "[::ffff:127.0.0.1]")
doTest(t, "tcp6", "[::ffff:127.0.0.1]", "127.0.0.1")
}
}

Expand Down Expand Up @@ -215,7 +210,7 @@ func TestUDPServer(t *testing.T) {
for _, isEmpty := range []bool{false, true} {
doTestPacket(t, "udp", "0.0.0.0", "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", "[::]", "127.0.0.1", isEmpty)
doTestPacket(t, "udp", "0.0.0.0", "[::ffff:127.0.0.1]", isEmpty)
Expand Down
4 changes: 2 additions & 2 deletions src/pkg/net/sock.go
Expand Up @@ -16,7 +16,7 @@ import (
var listenerBacklog = maxListenerBacklog()

// 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.
syscall.ForkLock.RLock()
s, err := syscall.Socket(f, t, p)
Expand All @@ -27,7 +27,7 @@ func socket(net string, f, t, p int, la, ra syscall.Sockaddr, toAddr func(syscal
syscall.CloseOnExec(s)
syscall.ForkLock.RUnlock()

err = setDefaultSockopts(s, f, t)
err = setDefaultSockopts(s, f, t, ipv6only)
if err != nil {
closesocket(s)
return nil, err
Expand Down
13 changes: 9 additions & 4 deletions src/pkg/net/sockopt_bsd.go
Expand Up @@ -13,12 +13,17 @@ import (
"syscall"
)

func setDefaultSockopts(s, f, t int) error {
func setDefaultSockopts(s, f, t int, ipv6only bool) error {
switch f {
case syscall.AF_INET6:
// 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)
if ipv6only {
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1)
} 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.
err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)
Expand Down
13 changes: 9 additions & 4 deletions src/pkg/net/sockopt_linux.go
Expand Up @@ -11,12 +11,17 @@ import (
"syscall"
)

func setDefaultSockopts(s, f, t int) error {
func setDefaultSockopts(s, f, t int, ipv6only bool) error {
switch f {
case syscall.AF_INET6:
// 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)
if ipv6only {
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1)
} 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.
err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)
Expand Down
13 changes: 9 additions & 4 deletions src/pkg/net/sockopt_windows.go
Expand Up @@ -11,12 +11,17 @@ import (
"syscall"
)

func setDefaultSockopts(s syscall.Handle, f, t int) error {
func setDefaultSockopts(s syscall.Handle, f, t int, ipv6only bool) error {
switch f {
case syscall.AF_INET6:
// 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)
if ipv6only {
syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, 1)
} 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.
syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)
Expand Down
7 changes: 7 additions & 0 deletions src/pkg/net/tcpsock_posix.go
Expand Up @@ -46,6 +46,13 @@ func (a *TCPAddr) family() int {
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) {
return ipToSockaddr(family, a.IP, a.Port)
}
Expand Down
7 changes: 7 additions & 0 deletions src/pkg/net/udpsock_posix.go
Expand Up @@ -37,6 +37,13 @@ func (a *UDPAddr) family() int {
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) {
return ipToSockaddr(family, a.IP, a.Port)
}
Expand Down

0 comments on commit b5dc872

Please sign in to comment.