Skip to content

Commit

Permalink
Allow SO_REUSEADDR and SO_REUSEPORT to be set simultaneously (#1623)
Browse files Browse the repository at this point in the history
  • Loading branch information
rtpt-erikgeiser authored Jan 24, 2025
1 parent b77d1ed commit cbe4275
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 5 deletions.
22 changes: 18 additions & 4 deletions listen_no_reuseport.go → listen_no_socket_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@

package dns

import "net"
import (
"fmt"
"net"
)

const supportsReusePort = false
const (
supportsReusePort = false
supportsReuseAddr = false
)

func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, error) {
if reuseport || reuseaddr {
Expand All @@ -15,12 +21,20 @@ func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, e
return net.Listen(network, addr)
}

const supportsReuseAddr = false

func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn, error) {
if reuseport || reuseaddr {
// TODO(tmthrgd): return an error?
}

return net.ListenPacket(network, addr)
}

// this is just for test compatibility
func checkReuseport(fd uintptr) (bool, error) {
return false, fmt.Errorf("not supported")
}

// this is just for test compatibility
func checkReuseaddr(fd uintptr) (bool, error) {
return false, fmt.Errorf("not supported")
}
31 changes: 31 additions & 0 deletions listen_reuseport.go → listen_socket_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,40 @@ func reuseaddrControl(network, address string, c syscall.RawConn) error {
return opErr
}

func reuseaddrandportControl(network, address string, c syscall.RawConn) error {
err := reuseaddrControl(network, address, c)
if err != nil {
return err
}

return reuseportControl(network, address, c)
}

// this is just for test compatibility
func checkReuseport(fd uintptr) (bool, error) {
v, err := unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT)
if err != nil {
return false, err
}

return v == 1, nil
}

// this is just for test compatibility
func checkReuseaddr(fd uintptr) (bool, error) {
v, err := unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR)
if err != nil {
return false, err
}

return v == 1, nil
}

func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, error) {
var lc net.ListenConfig
switch {
case reuseaddr && reuseport:
lc.Control = reuseaddrandportControl
case reuseport:
lc.Control = reuseportControl
case reuseaddr:
Expand All @@ -56,6 +86,7 @@ func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn,
var lc net.ListenConfig
switch {
case reuseaddr && reuseport:
lc.Control = reuseaddrandportControl
case reuseport:
lc.Control = reuseportControl
case reuseaddr:
Expand Down
96 changes: 95 additions & 1 deletion server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,101 @@ func TestServerStartStopRace(t *testing.T) {
wg.Wait()
}

func TestSocketOptions(t *testing.T) {
if !supportsReuseAddr || !supportsReusePort {
t.Skip("reuseaddr or reuseport is not supported")
}

testSocketOptions := func(t *testing.T, reuseAddr bool, reusePort bool) {
wait := make(chan struct{})

srv := &Server{
Net: "udp",
Addr: ":0",
ReuseAddr: reuseAddr,
ReusePort: reusePort,
}

srv.NotifyStartedFunc = func() {
defer close(wait)

conn, ok := srv.PacketConn.(*net.UDPConn)
if !ok {
t.Errorf("unexpected conn type: %T", srv.PacketConn)
return
}

syscallConn, err := conn.SyscallConn()
if err != nil {
t.Errorf("cannot cast UDP conn to syscall conn: %v", err)
return

}

err = syscallConn.Control(func(fd uintptr) {
actualReusePort, err := checkReuseport(fd)
if err != nil {
t.Errorf("cannot get SO_REUSEPORT socket option: %v", err)
return
}

if actualReusePort != reusePort {
t.Errorf("SO_REUSEPORT is %v instead of %v", actualReusePort, reusePort)
}

actualReuseAddr, err := checkReuseaddr(fd)
if err != nil {
t.Errorf("cannot get SO_REUSEADDR socket option: %v", err)
return
}

if actualReuseAddr != reuseAddr {
t.Errorf("SO_REUSEADDR is %v instead of %v", actualReuseAddr, reusePort)
}
})
if err != nil {
t.Errorf("cannot check socket options: %v", err)
}
}

fin := make(chan error, 1)
go func() {
fin <- srv.ListenAndServe()
}()

select {
case <-wait:
err := srv.Shutdown()
if err != nil {
t.Fatalf("cannot shutdown server: %v", err)
}

err = <-fin
if err != nil {
t.Fatalf("listen adn serve: %v", err)
}
case err := <-fin:
t.Fatalf("listen adn serve: %v", err)
}
}

t.Run("no socket options", func(t *testing.T) {
testSocketOptions(t, false, false)
})

t.Run("SO_REUSEPORT", func(t *testing.T) {
testSocketOptions(t, false, true)
})

t.Run("SO_REUSEADDR", func(t *testing.T) {
testSocketOptions(t, true, false)
})

t.Run("SO_REUSEADDR and SO_REUSEPORT", func(t *testing.T) {
testSocketOptions(t, true, true)
})
}

func TestServerReuseport(t *testing.T) {
if !supportsReusePort {
t.Skip("reuseport is not supported")
Expand Down Expand Up @@ -1329,7 +1424,6 @@ func TestResponseWriteSinglePacket(t *testing.T) {
m.SetQuestion("miek.nl.", TypeTXT)
m.Response = true
err := rw.WriteMsg(m)

if err != nil {
t.Fatalf("failed to write: %v", err)
}
Expand Down

0 comments on commit cbe4275

Please sign in to comment.