Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions ephemeral_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//go:build darwin && !baremetal && !tinygo.wasm

package net

import (
"syscall"
"testing"
)

func TestEphemeralAcceptedNoSigpipe(t *testing.T) {
client, server := ephemeralTestPair(t)
defer client.Close()
defer server.Close()
value, err := syscall.GetsockoptInt(server.(*TCPConn).fd, syscall.SOL_SOCKET, syscall.SO_NOSIGPIPE)
if err != nil || value != 1 {
t.Fatalf("accepted SO_NOSIGPIPE=%d error=%v", value, err)
}
}
78 changes: 78 additions & 0 deletions ephemeral_native_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//go:build (linux || darwin) && !baremetal && !tinygo.wasm

package net

import (
"net/netip"
"testing"
"time"
)

func ephemeralTestPair(t *testing.T) (Conn, Conn) {
t.Helper()
l, err := Listen("tcp4", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer l.Close()
if l.Addr().(*TCPAddr).Port == 0 {
t.Fatalf("listener reports port 0: %v", l.Addr())
}
client, err := Dial("tcp4", l.Addr().String())
if err != nil {
t.Fatal(err)
}
server, err := l.Accept()
if err != nil {
client.Close()
t.Fatal(err)
}
return client, server
}

func TestEphemeralListenDial(t *testing.T) {
client, server := ephemeralTestPair(t)
defer client.Close()
defer server.Close()
server.SetReadDeadline(time.Now().Add(time.Second))
if _, err := client.Write([]byte("x")); err != nil {
t.Fatal(err)
}
b := make([]byte, 1)
if n, err := server.Read(b); n != 1 || err != nil || b[0] != 'x' {
t.Fatalf("read=%d %q error=%v", n, b, err)
}
}

// ephemeralTestNetdev lets listenTCP succeed without a kernel socket, and
// reports a fixed bound address so the applied port and zone are predictable.
type ephemeralTestNetdev struct {
nopNetdev
}

func (*ephemeralTestNetdev) Socket(int, int, int) (int, error) { return 42, nil }
func (*ephemeralTestNetdev) Bind(int, netip.AddrPort) error { return nil }
func (*ephemeralTestNetdev) Listen(int, int) error { return nil }
func (*ephemeralTestNetdev) Close(int) error { return nil }
func (*ephemeralTestNetdev) GetSockname(int) (netip.AddrPort, error) {
return netip.MustParseAddrPort("[fe80::1]:12345"), nil
}

func TestEphemeralPreservesZone(t *testing.T) {
previous := netdev
defer func() { netdev = previous }()
netdev = &ephemeralTestNetdev{}
addr := &TCPAddr{IP: ParseIP("fe80::1"), Zone: "test-zone"}
l, err := listenTCP(addr)
if err != nil {
t.Fatal(err)
}
defer l.Close()
got := l.Addr().(*TCPAddr)
if got.Port != 12345 || got.Zone != addr.Zone || !got.IP.Equal(addr.IP) {
t.Fatalf("bound address=%v, want [%s%%%s]:12345", got, addr.IP, addr.Zone)
}
if addr.Port != 0 {
t.Fatalf("input address changed: %v", addr)
}
}
23 changes: 19 additions & 4 deletions netdev_native.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,29 @@ func (*hostNetdev) Accept(sockfd int) (int, netip.AddrPort, error) {
if err != nil {
return -1, netip.AddrPort{}, err
}
var raddr netip.AddrPort
return nfd, addrPortFromSockaddr(sa), nil
}

// addrPortFromSockaddr converts a syscall.Sockaddr, as returned by
// Accept/Getsockname, into a netip.AddrPort.
func addrPortFromSockaddr(sa syscall.Sockaddr) netip.AddrPort {
switch s := sa.(type) {
case *syscall.SockaddrInet4:
raddr = netip.AddrPortFrom(netip.AddrFrom4(s.Addr), uint16(s.Port))
return netip.AddrPortFrom(netip.AddrFrom4(s.Addr), uint16(s.Port))
case *syscall.SockaddrInet6:
raddr = netip.AddrPortFrom(netip.AddrFrom16(s.Addr), uint16(s.Port))
return netip.AddrPortFrom(netip.AddrFrom16(s.Addr), uint16(s.Port))
}
return netip.AddrPort{}
}

// GetSockname reports the bound address.
// See https://pkg.go.dev/syscall#Getsockname.
func (*hostNetdev) GetSockname(sockfd int) (netip.AddrPort, error) {
sa, err := syscall.Getsockname(sockfd)
if err != nil {
return netip.AddrPort{}, err
}
return nfd, raddr, nil
return addrPortFromSockaddr(sa), nil
}

func (*hostNetdev) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
Expand Down
12 changes: 12 additions & 0 deletions tcpsock.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,18 @@ func listenTCP(laddr *TCPAddr) (Listener, error) {
return nil, err
}

if laddr.Port == 0 {
// Report the selected port. See https://pkg.go.dev/net#Listen.
// Drivers without GetSockname keep the requested address.
if g, ok := netdev.(interface {
GetSockname(sockfd int) (netip.AddrPort, error)
}); ok {
if ap, err := g.GetSockname(fd); err == nil && ap.Port() != 0 {
laddr = &TCPAddr{IP: laddr.IP, Port: int(ap.Port()), Zone: laddr.Zone}
}
}
}

return &listener{fd: fd, laddr: laddr}, nil
}

Expand Down