diff --git a/proxy/tun/tun_darwin.go b/proxy/tun/tun_darwin.go index 299720cbd..a005681f4 100644 --- a/proxy/tun/tun_darwin.go +++ b/proxy/tun/tun_darwin.go @@ -558,19 +558,16 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error { } func setinterface(network, address string, fd uintptr, iface *net.Interface) error { - var err1, err2 error - switch network { case "tcp6", "udp6", "ip6": - err1 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index) - fallthrough + // IPV6_BOUND_IF binds the IPv4 traffic of a dual-stack socket as + // well, and an IPv6 socket rejects IP_BOUND_IF with EINVAL. + return unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index) case "tcp4", "udp4", "ip4": - err2 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index) + return unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index) default: panic(network + " " + address) } - - return errors.Join(err1, err2) } func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) { diff --git a/proxy/tun/tun_darwin_test.go b/proxy/tun/tun_darwin_test.go index 2c299e378..2656e62b6 100644 --- a/proxy/tun/tun_darwin_test.go +++ b/proxy/tun/tun_darwin_test.go @@ -3,7 +3,10 @@ package tun import ( + "context" + "net" "sync" + "syscall" "testing" "time" @@ -231,3 +234,40 @@ func TestWaitKqueueReportsPersistentFailure(t *testing.T) { } } } + +// TestSetinterfaceSocketFamilies binds each kind of socket Xray's dialers +// open to lo0, including the dual-stack one Go opens for UDP. +func TestSetinterfaceSocketFamilies(t *testing.T) { + lo, err := net.InterfaceByName("lo0") + if err != nil { + t.Skip(err) + } + lc := net.ListenConfig{Control: func(network, address string, c syscall.RawConn) error { + var err error + if controlErr := c.Control(func(fd uintptr) { + err = setinterface(network, address, fd, lo) + }); controlErr != nil { + return controlErr + } + return err + }} + for _, s := range []struct{ network, address string }{ + {"udp", "0.0.0.0:0"}, // dual-stack + {"udp4", "127.0.0.1:0"}, + {"udp6", "[::1]:0"}, + {"tcp4", "127.0.0.1:0"}, + {"tcp6", "[::1]:0"}, + } { + var closer interface{ Close() error } + if s.network[:3] == "udp" { + closer, err = lc.ListenPacket(context.Background(), s.network, s.address) + } else { + closer, err = lc.Listen(context.Background(), s.network, s.address) + } + if err != nil { + t.Errorf("%s %s: %v", s.network, s.address, err) + continue + } + closer.Close() + } +}