mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-29 14:37:05 +00:00
Sockopt: Handle single-family interface binding on Windows, including TUN inbound's autoOutboundsInterface (#7214)
And https://github.com/XTLS/Xray-core/pull/7214#issuecomment-6329118637 https://github.com/XTLS/Xray-core/pull/7201#issuecomment-6314453444 Fixes https://github.com/XTLS/Xray-core/issues/7160#issuecomment-6246944012 --------- Co-authored-by: patterniha <71074308+patterniha@users.noreply.github.com>
This commit is contained in:
co-authored by
patterniha
parent
1576e79563
commit
3071fbfa25
@@ -2,6 +2,7 @@ package localdns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -100,6 +101,15 @@ func New() *Client {
|
|||||||
if internet.IsSkippedDNSServer(address) {
|
if internet.IsSkippedDNSServer(address) {
|
||||||
return nil, errors.New("skipped DNS server ", address)
|
return nil, errors.New("skipped DNS server ", address)
|
||||||
}
|
}
|
||||||
|
// A socket of the server's IP version only needs that version bound
|
||||||
|
// to the interface, which a dual-stack one would need both of.
|
||||||
|
if ap, err := netip.ParseAddrPort(address); err == nil && (network == "udp" || network == "tcp") {
|
||||||
|
if ap.Addr().Unmap().Is4() {
|
||||||
|
network += "4"
|
||||||
|
} else {
|
||||||
|
network += "6"
|
||||||
|
}
|
||||||
|
}
|
||||||
return d.DialContext(ctx, network, address)
|
return d.DialContext(ctx, network, address)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,6 +401,15 @@ func resolveOnOwn() *savedResolver {
|
|||||||
if internet.IsSkippedDNSServer(address) {
|
if internet.IsSkippedDNSServer(address) {
|
||||||
return nil, errors.New("skipped DNS server ", address)
|
return nil, errors.New("skipped DNS server ", address)
|
||||||
}
|
}
|
||||||
|
// A socket of the server's IP version only needs that version bound
|
||||||
|
// to the interface, which a dual-stack one would need both of.
|
||||||
|
if ap, err := netip.ParseAddrPort(address); err == nil && (network == "udp" || network == "tcp") {
|
||||||
|
if ap.Addr().Unmap().Is4() {
|
||||||
|
network += "4"
|
||||||
|
} else {
|
||||||
|
network += "6"
|
||||||
|
}
|
||||||
|
}
|
||||||
return dialer.DialContext(ctx, network, address)
|
return dialer.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
net.DefaultResolver.PreferGo = true
|
net.DefaultResolver.PreferGo = true
|
||||||
@@ -558,10 +567,22 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
|
|||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp6", "udp6", "ip6":
|
case "tcp6", "udp6", "ip6":
|
||||||
|
dualStack := false
|
||||||
|
if network == "udp6" {
|
||||||
|
ipv6Only, err := windows.GetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_V6ONLY)
|
||||||
|
if err != nil {
|
||||||
|
return internet.NewBindError(err)
|
||||||
|
}
|
||||||
|
dualStack = ipv6Only == 0
|
||||||
|
}
|
||||||
err1 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_UNICAST_IF, iface.Index)
|
err1 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_UNICAST_IF, iface.Index)
|
||||||
if network == "udp6" {
|
if network == "udp6" {
|
||||||
err2 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_MULTICAST_IF, iface.Index)
|
err2 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_MULTICAST_IF, iface.Index)
|
||||||
}
|
}
|
||||||
|
// Only dual-stack UDP can send to both IPv4 and IPv6 destinations.
|
||||||
|
if !dualStack {
|
||||||
|
break
|
||||||
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
case "tcp4", "udp4", "ip4":
|
case "tcp4", "udp4", "ip4":
|
||||||
err3 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_UNICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
|
err3 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_UNICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
|
||||||
@@ -572,7 +593,10 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
|
|||||||
panic(network + " " + address)
|
panic(network + " " + address)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Combine(err1, err2, err3, err4)
|
if err := errors.Combine(err1, err2, err3, err4); err != nil {
|
||||||
|
return internet.NewBindError(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package internet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
go_errors "errors"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -92,6 +94,16 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
packetConn, err := lc.ListenPacket(ctx, srcAddr.Network(), srcAddr.String())
|
packetConn, err := lc.ListenPacket(ctx, srcAddr.Network(), srcAddr.String())
|
||||||
|
var bindErr *BindError
|
||||||
|
if err != nil && runtime.GOOS == "windows" && go_errors.As(err, &bindErr) {
|
||||||
|
// Keep dual-stack UDP when possible. If the interface cannot bind both
|
||||||
|
// families, retry a socket that cannot send on the unbound family.
|
||||||
|
network := "udp6"
|
||||||
|
if destAddr.IP.To4() != nil {
|
||||||
|
network = "udp4"
|
||||||
|
}
|
||||||
|
packetConn, err = lc.ListenPacket(ctx, network, srcAddr.String())
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package internet_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"slices"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestDialUDPBindFallback checks that a UDP dial whose dual-stack socket
|
||||||
|
// cannot be bound to both IP versions, as on an interface without one of
|
||||||
|
// them, moves to a socket of the destination's version only, and still
|
||||||
|
// fails when that one cannot be bound either.
|
||||||
|
func TestDialUDPBindFallback(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
dest string
|
||||||
|
reject []string // the kinds of socket that cannot be bound
|
||||||
|
tried []string // the kinds of socket tried, in order
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"127.0.0.1", nil, []string{"dual"}, true},
|
||||||
|
{"127.0.0.1", []string{"dual"}, []string{"dual", "udp4"}, true},
|
||||||
|
{"::1", []string{"dual"}, []string{"dual", "udp6"}, true},
|
||||||
|
{"127.0.0.1", []string{"dual", "udp4"}, []string{"dual", "udp4"}, false},
|
||||||
|
} {
|
||||||
|
var tried []string
|
||||||
|
internet.ControllersLock.Lock()
|
||||||
|
saved := internet.Controllers
|
||||||
|
internet.Controllers = []func(string, string, syscall.RawConn) error{func(network, _ string, rc syscall.RawConn) error {
|
||||||
|
kind := network
|
||||||
|
if network == "udp6" {
|
||||||
|
rc.Control(func(fd uintptr) {
|
||||||
|
if v6only, _ := windows.GetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_V6ONLY); v6only == 0 {
|
||||||
|
kind = "dual"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
tried = append(tried, kind)
|
||||||
|
if slices.Contains(c.reject, kind) {
|
||||||
|
return internet.NewBindError(errors.New("cannot bind " + kind))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}}
|
||||||
|
internet.ControllersLock.Unlock()
|
||||||
|
|
||||||
|
conn, err := internet.DialSystem(context.Background(), net.UDPDestination(net.ParseAddress(c.dest), 53), nil)
|
||||||
|
if conn != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
internet.ControllersLock.Lock()
|
||||||
|
internet.Controllers = saved
|
||||||
|
internet.ControllersLock.Unlock()
|
||||||
|
|
||||||
|
if (err == nil) != c.ok || !slices.Equal(tried, c.tried) {
|
||||||
|
t.Errorf("%s rejecting %v: tried %v, err %v", c.dest, c.reject, tried, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user