mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-28 14:07:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b291961d18 | ||
|
|
fd599e8a4d |
@@ -82,7 +82,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v4.7.0
|
uses: docker/login-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
|
|||||||
+59
-17
@@ -8,23 +8,65 @@ import (
|
|||||||
|
|
||||||
var privateIPMatcher = sync.OnceValue(func() IPMatcher {
|
var privateIPMatcher = sync.OnceValue(func() IPMatcher {
|
||||||
return common.Must2(IPReg.BuildIPMatcher(common.Must2(ParseIPRules([]string{
|
return common.Must2(IPReg.BuildIPMatcher(common.Must2(ParseIPRules([]string{
|
||||||
"0.0.0.0/8",
|
// https://www.iana.org/assignments/iana-ipv4-special-registry
|
||||||
"10.0.0.0/8",
|
"0.0.0.0/8", // "This network"
|
||||||
"100.64.0.0/10",
|
"0.0.0.0/32", // "This host on this network"
|
||||||
"127.0.0.0/8",
|
"10.0.0.0/8", // Private-Use
|
||||||
"169.254.0.0/16",
|
"100.64.0.0/10", // Shared Address Space
|
||||||
"172.16.0.0/12",
|
"127.0.0.0/8", // Loopback
|
||||||
"192.0.0.0/24",
|
"169.254.0.0/16", // Link Local
|
||||||
"192.0.2.0/24",
|
"172.16.0.0/12", // Private-Use
|
||||||
"192.88.99.0/24",
|
// "192.0.0.0/24", // IETF Protocol Assignments
|
||||||
"192.168.0.0/16",
|
"192.0.0.0/29", // IPv4 Service Continuity Prefix
|
||||||
"198.18.0.0/15",
|
"192.0.0.8/32", // IPv4 dummy address
|
||||||
"198.51.100.0/24",
|
"192.0.0.9/32", // Port Control Protocol Anycast
|
||||||
"203.0.113.0/24",
|
"192.0.0.10/32", // Traversal Using Relays around NAT Anycast
|
||||||
"224.0.0.0/3",
|
"192.0.0.170/32", // NAT64/DNS64 Discovery
|
||||||
"::/127",
|
"192.0.0.171/32", // NAT64/DNS64 Discovery
|
||||||
"fc00::/7",
|
"192.0.2.0/24", // Documentation (TEST-NET-1)
|
||||||
"fe80::/10",
|
// "192.31.196.0/24", // AS112-v4
|
||||||
|
"192.52.193.0/24", // AMT
|
||||||
|
// "192.88.99.0/24", // Deprecated (6to4 Relay Anycast)
|
||||||
|
"192.88.99.2/32", // 6a44-relay anycast address
|
||||||
|
"192.168.0.0/16", // Private-Use
|
||||||
|
// "192.175.48.0/24", // Direct Delegation AS112 Service
|
||||||
|
"198.18.0.0/15", // Benchmarking
|
||||||
|
"198.51.100.0/24", // Documentation (TEST-NET-2)
|
||||||
|
"203.0.113.0/24", // Documentation (TEST-NET-3)
|
||||||
|
"240.0.0.0/4", // Reserved
|
||||||
|
"255.255.255.255/32", // Limited Broadcast
|
||||||
|
|
||||||
|
// https://www.iana.org/assignments/multicast-addresses
|
||||||
|
"224.0.0.0/4",
|
||||||
|
|
||||||
|
// https://www.iana.org/assignments/iana-ipv6-special-registry
|
||||||
|
"::1/128", // Loopback Address
|
||||||
|
"::/128", // Unspecified Address
|
||||||
|
// "::ffff:0:0/96", // IPv4-mapped Address
|
||||||
|
"64:ff9b::/96", // IPv4-IPv6 Translat.
|
||||||
|
"64:ff9b:1::/48", // IPv4-IPv6 Translat.
|
||||||
|
"100::/64", // Discard-Only Address Block
|
||||||
|
"100:0:0:1::/64", // Dummy IPv6 Prefix
|
||||||
|
// "2001::/23", // IETF Protocol Assignments
|
||||||
|
"2001::/32", // TEREDO
|
||||||
|
"2001:1::1/128", // Port Control Protocol Anycast
|
||||||
|
"2001:1::2/128", // Traversal Using Relays around NAT Anycast
|
||||||
|
"2001:1::3/128", // DNS-SD Service Registration Protocol Anycast
|
||||||
|
"2001:2::/48", // Benchmarking
|
||||||
|
"2001:3::/32", // AMT
|
||||||
|
// "2001:4:112::/48", // AS112-v6
|
||||||
|
// "2001:10::/28", // Deprecated (previously ORCHID)
|
||||||
|
"2001:20::/28", // ORCHIDv2
|
||||||
|
"2001:30::/28", // Drone Remote ID Protocol Entity Tags (DETs) Prefix
|
||||||
|
"2001:db8::/32", // Documentation
|
||||||
|
"2002::/16", // 6to4
|
||||||
|
// "2620:4f:8000::/48", // Direct Delegation AS112 Service
|
||||||
|
"3fff::/20", // Documentation
|
||||||
|
"5f00::/16", // Segment Routing (SRv6) SIDs
|
||||||
|
"fc00::/7", // Unique-Local
|
||||||
|
"fe80::/10", // Link-Local Unicast
|
||||||
|
|
||||||
|
// https://www.iana.org/assignments/ipv6-multicast-addresses
|
||||||
"ff00::/8",
|
"ff00::/8",
|
||||||
}))))
|
}))))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package localdns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -101,15 +100,6 @@ 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)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ require (
|
|||||||
github.com/miekg/dns v1.1.73
|
github.com/miekg/dns v1.1.73
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/pion/stun/v3 v3.1.7
|
github.com/pion/stun/v3 v3.1.7
|
||||||
github.com/pires/go-proxyproto v0.16.0
|
github.com/pires/go-proxyproto v0.15.0
|
||||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/stretchr/testify v1.12.1
|
github.com/stretchr/testify v1.12.1
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ github.com/pion/stun/v3 v3.1.7 h1:uRXMTlGLf89WgItGNyZ6aR5jMTX0NBbybXADpQCzn+E=
|
|||||||
github.com/pion/stun/v3 v3.1.7/go.mod h1:Nq77RW4aRrSNrltf2ksUJLjxWeipj4lnlgdsYIxC8g8=
|
github.com/pion/stun/v3 v3.1.7/go.mod h1:Nq77RW4aRrSNrltf2ksUJLjxWeipj4lnlgdsYIxC8g8=
|
||||||
github.com/pion/transport/v4 v4.1.0 h1:8S+nF2reM2cJuqC6g78OVy2BBgmbdns+acx3jA97BvQ=
|
github.com/pion/transport/v4 v4.1.0 h1:8S+nF2reM2cJuqC6g78OVy2BBgmbdns+acx3jA97BvQ=
|
||||||
github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
github.com/pion/transport/v4 v4.1.0/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
||||||
github.com/pires/go-proxyproto v0.16.0 h1:9KYT/fM3wpjT8bdMEj/7FBcoiUG1KyzEjRYnPu+vz7A=
|
github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI=
|
||||||
github.com/pires/go-proxyproto v0.16.0/go.mod h1:n7yhg8yIfNl3KgYDCwBzmeUHNpKTVnUhcWpc7rV4RW4=
|
github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
|
||||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
||||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
|
|||||||
+3
-2
@@ -237,10 +237,11 @@ type OutboundDetourConfig struct {
|
|||||||
|
|
||||||
func requiresTransportSecurity(address *Address) bool {
|
func requiresTransportSecurity(address *Address) bool {
|
||||||
if address == nil || address.Address == nil {
|
if address == nil || address.Address == nil {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
if address.Family().IsIP() {
|
if address.Family().IsIP() {
|
||||||
return !geodata.GetPrivateIPMatcher().Match(address.IP())
|
ip := address.IP()
|
||||||
|
return !ip.IsPrivate() && !ip.IsLoopback() && !ip.IsLinkLocalUnicast()
|
||||||
}
|
}
|
||||||
domain := strings.TrimSuffix(strings.ToLower(address.Domain()), ".")
|
domain := strings.TrimSuffix(strings.ToLower(address.Domain()), ".")
|
||||||
return !geodata.GetPrivateDomainMatcher().MatchAny(domain)
|
return !geodata.GetPrivateDomainMatcher().MatchAny(domain)
|
||||||
|
|||||||
@@ -229,19 +229,14 @@ func (h *Handler) blockDelay(rule *FinalRule) time.Duration {
|
|||||||
func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error {
|
func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Writer, rule *FinalRule, dest *net.Destination) error {
|
||||||
delay := h.blockDelay(rule)
|
delay := h.blockDelay(rule)
|
||||||
errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay)
|
errors.LogInfo(ctx, "blocked target: ", *dest, ", blackholing connection for ", delay)
|
||||||
defer common.Close(output)
|
timer := time.AfterFunc(delay, func() {
|
||||||
done := make(chan struct{})
|
|
||||||
go func() { // a DispatchLink inbound's reader ignores Interrupt, so don't wait for this copy
|
|
||||||
_ = buf.Copy(input, buf.Discard)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(delay):
|
|
||||||
common.Interrupt(input)
|
common.Interrupt(input)
|
||||||
common.Interrupt(output)
|
common.Interrupt(output)
|
||||||
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest)
|
errors.LogInfo(ctx, "closed blackholed connection to blocked target: ", *dest)
|
||||||
}
|
})
|
||||||
|
defer timer.Stop()
|
||||||
|
defer common.Close(output)
|
||||||
|
_ = buf.Copy(input, buf.Discard)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -401,15 +401,6 @@ 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
|
||||||
@@ -567,22 +558,10 @@ 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])))
|
||||||
@@ -593,10 +572,7 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
|
|||||||
panic(network + " " + address)
|
panic(network + " " + address)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := errors.Combine(err1, err2, err3, err4); err != nil {
|
return errors.Combine(err1, err2, err3, err4)
|
||||||
return internet.NewBindError(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
func findOutboundInterface(tunIndex int, fixedName string) (*net.Interface, error) {
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package scenarios
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -22,7 +20,6 @@ import (
|
|||||||
"github.com/xtls/xray-core/proxy/dokodemo"
|
"github.com/xtls/xray-core/proxy/dokodemo"
|
||||||
"github.com/xtls/xray-core/proxy/freedom"
|
"github.com/xtls/xray-core/proxy/freedom"
|
||||||
"github.com/xtls/xray-core/proxy/vless"
|
"github.com/xtls/xray-core/proxy/vless"
|
||||||
"github.com/xtls/xray-core/proxy/vless/encoding"
|
|
||||||
"github.com/xtls/xray-core/proxy/vless/inbound"
|
"github.com/xtls/xray-core/proxy/vless/inbound"
|
||||||
"github.com/xtls/xray-core/proxy/vless/outbound"
|
"github.com/xtls/xray-core/proxy/vless/outbound"
|
||||||
"github.com/xtls/xray-core/testing/servers/tcp"
|
"github.com/xtls/xray-core/testing/servers/tcp"
|
||||||
@@ -660,82 +657,3 @@ func TestVlessRealityFingerprints(t *testing.T) {
|
|||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
// A blocked connection must be closed after blockDelay even while the client keeps it open and sends nothing.
|
|
||||||
func TestVlessBlockDelay(t *testing.T) {
|
|
||||||
tcpServer := tcp.Server{
|
|
||||||
MsgProcessor: xor,
|
|
||||||
}
|
|
||||||
dest, err := tcpServer.Start()
|
|
||||||
common.Must(err)
|
|
||||||
defer tcpServer.Close()
|
|
||||||
|
|
||||||
for _, delay := range []uint64{0, 1} {
|
|
||||||
t.Run(fmt.Sprint("blockDelay=", delay), func(t *testing.T) {
|
|
||||||
userID := protocol.NewID(uuid.New())
|
|
||||||
serverPort := tcp.PickPort()
|
|
||||||
serverConfig := &core.Config{
|
|
||||||
Inbound: []*core.InboundHandlerConfig{
|
|
||||||
{
|
|
||||||
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
|
|
||||||
PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
|
|
||||||
Listen: net.NewIPOrDomain(net.LocalHostIP),
|
|
||||||
}),
|
|
||||||
ProxySettings: serial.ToTypedMessage(&inbound.Config{
|
|
||||||
Users: []*protocol.User{
|
|
||||||
{
|
|
||||||
Account: serial.ToTypedMessage(&vless.Account{
|
|
||||||
Id: userID.String(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Outbound: []*core.OutboundHandlerConfig{
|
|
||||||
{
|
|
||||||
ProxySettings: serial.ToTypedMessage(&freedom.Config{
|
|
||||||
FinalRules: []*freedom.FinalRuleConfig{
|
|
||||||
{
|
|
||||||
Action: freedom.RuleAction_Block,
|
|
||||||
BlockDelay: &freedom.Range{Min: delay, Max: delay},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
servers, err := InitializeServerConfigs(serverConfig)
|
|
||||||
common.Must(err)
|
|
||||||
defer CloseAllServers(servers)
|
|
||||||
|
|
||||||
conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{
|
|
||||||
IP: []byte{127, 0, 0, 1},
|
|
||||||
Port: int(serverPort),
|
|
||||||
})
|
|
||||||
common.Must(err)
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
common.Must(encoding.EncodeRequestHeader(conn, &protocol.RequestHeader{
|
|
||||||
Version: encoding.Version,
|
|
||||||
User: &protocol.MemoryUser{Account: &vless.MemoryAccount{ID: userID}},
|
|
||||||
Command: protocol.RequestCommandTCP,
|
|
||||||
Address: dest.Address,
|
|
||||||
Port: dest.Port,
|
|
||||||
}, &encoding.Addons{}))
|
|
||||||
|
|
||||||
blockDelay := time.Duration(delay) * time.Second
|
|
||||||
common.Must(conn.SetReadDeadline(time.Now().Add(blockDelay + 5*time.Second)))
|
|
||||||
_, err = io.Copy(io.Discard, conn)
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
|
||||||
t.Fatalf("connection is still open after %v", elapsed)
|
|
||||||
}
|
|
||||||
if elapsed < blockDelay {
|
|
||||||
t.Fatalf("connection is closed after %v, before blockDelay", elapsed)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package internet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
go_errors "errors"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -94,16 +92,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
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