diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index 0666d30681..1112732e76 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -13,13 +13,14 @@ import ( ) type Base struct { - name string - addr string - iface string - tp C.AdapterType - udp bool - rmark int - id string + name string + addr string + iface string + tp C.AdapterType + udp bool + rmark int + id string + prefer C.DNSPrefer } // Name implements C.ProxyAdapter @@ -103,12 +104,25 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option { opts = append(opts, dialer.WithRoutingMark(b.rmark)) } + switch b.prefer { + case C.IPv4Only: + opts = append(opts, dialer.WithOnlySingleStack(true)) + case C.IPv6Only: + opts = append(opts, dialer.WithOnlySingleStack(false)) + case C.IPv4Prefer: + opts = append(opts, dialer.WithPreferIPv4()) + case C.IPv6Prefer: + opts = append(opts, dialer.WithPreferIPv6()) + default: + } + return opts } type BasicOption struct { Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"` RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"` + IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"` } type BaseOption struct { @@ -118,16 +132,18 @@ type BaseOption struct { UDP bool Interface string RoutingMark int + Prefer C.DNSPrefer } func NewBase(opt BaseOption) *Base { return &Base{ - name: opt.Name, - addr: opt.Addr, - tp: opt.Type, - udp: opt.UDP, - iface: opt.Interface, - rmark: opt.RoutingMark, + name: opt.Name, + addr: opt.Addr, + tp: opt.Type, + udp: opt.UDP, + iface: opt.Interface, + rmark: opt.RoutingMark, + prefer: opt.Prefer, } } diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index f7c881244d..fdbbcb62e6 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -40,9 +40,10 @@ type directPacketConn struct { func NewDirect() *Direct { return &Direct{ Base: &Base{ - name: "DIRECT", - tp: C.Direct, - udp: true, + name: "DIRECT", + tp: C.Direct, + udp: true, + prefer: C.DualStack, }, } } @@ -50,9 +51,10 @@ func NewDirect() *Direct { func NewCompatible() *Direct { return &Direct{ Base: &Base{ - name: "COMPATIBLE", - tp: C.Compatible, - udp: true, + name: "COMPATIBLE", + tp: C.Compatible, + udp: true, + prefer: C.DualStack, }, } } diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go index e8ff3e96d6..ae4fd75b88 100644 --- a/adapter/outbound/http.go +++ b/adapter/outbound/http.go @@ -153,11 +153,12 @@ func NewHttp(option HttpOption) (*Http, error) { return &Http{ Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.Http, - iface: option.Interface, - rmark: option.RoutingMark, + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Http, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), }, user: option.UserName, pass: option.Password, diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index 5485d2b403..adc904a292 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -53,6 +53,9 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts . hyDialer: func() (net.PacketConn, error) { return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...) }, + remoteAddr: func(addr string) (net.Addr, error) { + return resolveUDPAddrWithPrefer("udp", addr, h.prefer) + }, } tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), &hdc) @@ -184,11 +187,11 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { option.Protocol = DefaultProtocol } if option.ReceiveWindowConn == 0 { - quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow + quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow / 10 quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow } if option.ReceiveWindow == 0 { - quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow + quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow / 10 quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow } if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery { @@ -216,12 +219,13 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { } return &Hysteria{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Hysteria, - udp: true, - iface: option.Interface, - rmark: option.RoutingMark, + name: option.Name, + addr: addr, + tp: C.Hysteria, + udp: true, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), }, client: client, }, nil @@ -287,8 +291,9 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { } type hyDialerWithContext struct { - hyDialer func() (net.PacketConn, error) - ctx context.Context + hyDialer func() (net.PacketConn, error) + ctx context.Context + remoteAddr func(host string) (net.Addr, error) } func (h *hyDialerWithContext) ListenPacket() (net.PacketConn, error) { @@ -298,3 +303,7 @@ func (h *hyDialerWithContext) ListenPacket() (net.PacketConn, error) { func (h *hyDialerWithContext) Context() context.Context { return h.ctx } + +func (h *hyDialerWithContext) RemoteAddr(host string) (net.Addr, error) { + return h.remoteAddr(host) +} diff --git a/adapter/outbound/reject.go b/adapter/outbound/reject.go index 30ca75eea8..43833238c8 100644 --- a/adapter/outbound/reject.go +++ b/adapter/outbound/reject.go @@ -27,9 +27,10 @@ func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, func NewReject() *Reject { return &Reject{ Base: &Base{ - name: "REJECT", - tp: C.Reject, - udp: true, + name: "REJECT", + tp: C.Reject, + udp: true, + prefer: C.DualStack, }, } } @@ -37,9 +38,10 @@ func NewReject() *Reject { func NewPass() *Reject { return &Reject{ Base: &Base{ - name: "PASS", - tp: C.Pass, - udp: true, + name: "PASS", + tp: C.Pass, + udp: true, + prefer: C.DualStack, }, } } diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index fc87283523..b26f880245 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -116,7 +116,7 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta return nil, err } - addr, err := resolveUDPAddr("udp", ss.addr) + addr, err := resolveUDPAddrWithPrefer("udp", ss.addr, ss.prefer) if err != nil { pc.Close() return nil, err @@ -186,12 +186,13 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { return &ShadowSocks{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Shadowsocks, - udp: option.UDP, - iface: option.Interface, - rmark: option.RoutingMark, + name: option.Name, + addr: addr, + tp: C.Shadowsocks, + udp: option.UDP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), }, method: method, diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index 57ef56046a..6b6b9a983b 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -79,7 +79,7 @@ func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Me return nil, err } - addr, err := resolveUDPAddr("udp", ssr.addr) + addr, err := resolveUDPAddrWithPrefer("udp", ssr.addr, ssr.prefer) if err != nil { pc.Close() return nil, err @@ -143,12 +143,13 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { return &ShadowSocksR{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.ShadowsocksR, - udp: option.UDP, - iface: option.Interface, - rmark: option.RoutingMark, + name: option.Name, + addr: addr, + tp: C.ShadowsocksR, + udp: option.UDP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), }, cipher: coreCiph, obfs: obfs, diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go index 7b272d5dbe..0aadb1c8c2 100644 --- a/adapter/outbound/snell.go +++ b/adapter/outbound/snell.go @@ -152,12 +152,13 @@ func NewSnell(option SnellOption) (*Snell, error) { s := &Snell{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Snell, - udp: option.UDP, - iface: option.Interface, - rmark: option.RoutingMark, + name: option.Name, + addr: addr, + tp: C.Snell, + udp: option.UDP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), }, psk: psk, obfsOption: obfsOption, diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index 4e79d6a95d..43900b1edd 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -160,12 +160,13 @@ func NewSocks5(option Socks5Option) (*Socks5, error) { return &Socks5{ Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.Socks5, - udp: option.UDP, - iface: option.Interface, - rmark: option.RoutingMark, + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Socks5, + udp: option.UDP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), }, user: option.UserName, pass: option.Password, diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 88c87e22c0..946a0101e2 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -209,12 +209,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { t := &Trojan{ Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Trojan, - udp: option.UDP, - iface: option.Interface, - rmark: option.RoutingMark, + name: option.Name, + addr: addr, + tp: C.Trojan, + udp: option.UDP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), }, instance: trojan.New(tOption), option: &option, diff --git a/adapter/outbound/util.go b/adapter/outbound/util.go index 071052a6f3..de9287bd6b 100644 --- a/adapter/outbound/util.go +++ b/adapter/outbound/util.go @@ -5,6 +5,7 @@ import ( "crypto/tls" xtls "github.com/xtls/go" "net" + "net/netip" "strconv" "sync" "time" @@ -74,6 +75,59 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) } +func resolveUDPAddrWithPrefer(network, address string, prefer C.DNSPrefer) (*net.UDPAddr, error) { + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + var ip netip.Addr + switch prefer { + case C.IPv4Only: + ip, err = resolver.ResolveIPv4ProxyServerHost(host) + case C.IPv6Only: + ip, err = resolver.ResolveIPv6ProxyServerHost(host) + case C.IPv6Prefer: + var ips []netip.Addr + ips, err = resolver.ResolveAllIPProxyServerHost(host) + var fallback netip.Addr + if err == nil { + for _, addr := range ips { + if addr.Is6() { + ip = addr + break + } else { + if !fallback.IsValid() { + fallback = addr + } + } + } + ip = fallback + } + case C.IPv4Prefer | C.DualStack: + var ips []netip.Addr + ips, err = resolver.ResolveAllIPProxyServerHost(host) + var fallback netip.Addr + if err == nil { + for _, addr := range ips { + if addr.Is4() { + ip = addr + break + } else { + if !fallback.IsValid() { + fallback = addr + } + } + } + ip = fallback + } + } + + if err != nil { + return nil, err + } + return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) +} + func safeConnClose(c net.Conn, err error) { if err != nil { _ = c.Close() diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 7c30c0a942..4811de0fe6 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -418,11 +418,12 @@ func NewVless(option VlessOption) (*Vless, error) { v := &Vless{ Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.Vless, - udp: option.UDP, - iface: option.Interface, + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Vless, + udp: option.UDP, + iface: option.Interface, + prefer: C.NewDNSPrefer(option.IPVersion), }, client: client, option: &option, diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index cf7814f1f8..fc8e1e87b1 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -316,12 +316,13 @@ func NewVmess(option VmessOption) (*Vmess, error) { v := &Vmess{ Base: &Base{ - name: option.Name, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - tp: C.Vmess, - udp: option.UDP, - iface: option.Interface, - rmark: option.RoutingMark, + name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + tp: C.Vmess, + udp: option.UDP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), }, client: client, option: &option, diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 521479b0c5..48cc1ce73d 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -5,8 +5,10 @@ import ( "errors" "fmt" "github.com/Dreamacro/clash/component/resolver" + "go.uber.org/atomic" "net" "net/netip" + "strings" "sync" ) @@ -16,6 +18,8 @@ var ( actualDualStackDialContext = dualStackDialContext tcpConcurrent = false DisableIPv6 = false + ErrorInvalidedNetworkStack = errors.New("invalided network stack") + ErrorDisableIPv6 = errors.New("IPv6 is disabled, dialer cancel") ) func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { @@ -32,13 +36,23 @@ func DialContext(ctx context.Context, network, address string, options ...Option o(opt) } + if opt.network == 4 || opt.network == 6 { + if strings.Contains(network, "tcp") { + network = "tcp" + } else { + network = "udp" + } + + network = fmt.Sprintf("%s%d", network, opt.network) + } + switch network { case "tcp4", "tcp6", "udp4", "udp6": return actualSingleDialContext(ctx, network, address, opt) case "tcp", "udp": return actualDualStackDialContext(ctx, network, address, opt) default: - return nil, errors.New("network invalid") + return nil, ErrorInvalidedNetworkStack } } @@ -56,10 +70,6 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio o(cfg) } - if DisableIPv6 { - network = "udp4" - } - lc := &net.ListenConfig{} if cfg.interfaceName != "" { addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address) @@ -108,7 +118,7 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po } if DisableIPv6 && destination.Is6() { - return nil, fmt.Errorf("IPv6 is diabled, dialer cancel") + return nil, ErrorDisableIPv6 } return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port)) @@ -230,29 +240,49 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr ip netip.Addr net.Conn error - resolved bool + isPrimary bool + done bool } + preferCount := atomic.NewInt32(0) results := make(chan dialResult) tcpRacer := func(ctx context.Context, ip netip.Addr) { - result := dialResult{ip: ip} + result := dialResult{ip: ip, done: true} defer func() { select { case results <- result: case <-returned: if result.Conn != nil { - result.Conn.Close() + _ = result.Conn.Close() } } }() + if strings.Contains(network, "tcp") { + network = "tcp" + } else { + network = "udp" + } - v := "4" if ip.Is6() { - v = "6" + network += "6" + if opt.prefer != 4 { + result.isPrimary = true + } + } + + if ip.Is4() { + network += "4" + if opt.prefer != 6 { + result.isPrimary = true + } + } + + if result.isPrimary { + preferCount.Add(1) } - result.Conn, result.error = dialContext(ctx, network+v, ip, port, opt) + result.Conn, result.error = dialContext(ctx, network, ip, port, opt) } for _, ip := range ips { @@ -260,13 +290,28 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr } connCount := len(ips) + var fallback dialResult for i := 0; i < connCount; i++ { select { case res := <-results: if res.error == nil { - return res.Conn, nil + if res.isPrimary { + return res.Conn, nil + } else { + fallback = res + } + } else { + if res.isPrimary { + preferCount.Add(-1) + if preferCount.Load() == 0 && fallback.done { + return fallback.Conn, nil + } + } } case <-ctx.Done(): + if fallback.done { + return fallback.Conn, nil + } break } } @@ -303,25 +348,45 @@ func singleDialContext(ctx context.Context, network string, address string, opt } func concurrentSingleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) { + switch network { + case "tcp4", "udp4": + return concurrentIPv4DialContext(ctx, network, address, opt) + default: + return concurrentIPv6DialContext(ctx, network, address, opt) + } +} + +func concurrentIPv4DialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) { host, port, err := net.SplitHostPort(address) if err != nil { return nil, err } var ips []netip.Addr - switch network { - case "tcp4", "udp4": - if !opt.direct { - ips, err = resolver.ResolveAllIPv4ProxyServerHost(host) - } else { - ips, err = resolver.ResolveAllIPv4(host) - } - default: - if !opt.direct { - ips, err = resolver.ResolveAllIPv6ProxyServerHost(host) - } else { - ips, err = resolver.ResolveAllIPv6(host) - } + if !opt.direct { + ips, err = resolver.ResolveAllIPv4ProxyServerHost(host) + } else { + ips, err = resolver.ResolveAllIPv4(host) + } + + if err != nil { + return nil, err + } + + return concurrentDialContext(ctx, network, ips, port, opt) +} + +func concurrentIPv6DialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) { + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + var ips []netip.Addr + if !opt.direct { + ips, err = resolver.ResolveAllIPv6ProxyServerHost(host) + } else { + ips, err = resolver.ResolveAllIPv6(host) } if err != nil { diff --git a/component/dialer/options.go b/component/dialer/options.go index 2985dc7ba2..ce911035b1 100644 --- a/component/dialer/options.go +++ b/component/dialer/options.go @@ -1,6 +1,8 @@ package dialer -import "go.uber.org/atomic" +import ( + "go.uber.org/atomic" +) var ( DefaultOptions []Option @@ -13,6 +15,8 @@ type option struct { addrReuse bool routingMark int direct bool + network int + prefer int } type Option func(opt *option) @@ -40,3 +44,25 @@ func WithDirect() Option { opt.direct = true } } + +func WithPreferIPv4() Option { + return func(opt *option) { + opt.prefer = 4 + } +} + +func WithPreferIPv6() Option { + return func(opt *option) { + opt.prefer = 6 + } +} + +func WithOnlySingleStack(isIPv4 bool) Option { + return func(opt *option) { + if isIPv4 { + opt.network = 4 + } else { + opt.network = 6 + } + } +} diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index 23725c7c40..abb455640d 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -41,7 +41,6 @@ type Resolver interface { ResolveIPv4(host string) (ip netip.Addr, err error) ResolveIPv6(host string) (ip netip.Addr, err error) ResolveAllIP(host string) (ip []netip.Addr, err error) - ResolveAllIPPrimaryIPv4(host string) (ips []netip.Addr, err error) ResolveAllIPv4(host string) (ips []netip.Addr, err error) ResolveAllIPv6(host string) (ips []netip.Addr, err error) } @@ -74,10 +73,10 @@ func ResolveIPv6WithResolver(host string, r Resolver) (netip.Addr, error) { // ResolveIPWithResolver same as ResolveIP, but with a resolver func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) { - if ips, err := ResolveAllIPPrimaryIPv4WithResolver(host, r); err == nil { - return ips[rand.Intn(len(ips))], nil + if ip, err := ResolveIPv4WithResolver(host, r); err == nil { + return ip, nil } else { - return netip.Addr{}, err + return ResolveIPv6WithResolver(host, r) } } @@ -95,7 +94,6 @@ func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) { return ip, nil } } - return ResolveIPv4(host) } @@ -108,7 +106,6 @@ func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) { return ip, nil } } - return ResolveIPv6(host) } @@ -121,7 +118,6 @@ func ResolveProxyServerHost(host string) (netip.Addr, error) { return ip, err } } - return ResolveIP(host) } @@ -160,7 +156,6 @@ func ResolveAllIPv6WithResolver(host string, r Resolver) ([]netip.Addr, error) { return []netip.Addr{netip.AddrFrom16(*(*[16]byte)(ipAddrs[rand.Intn(len(ipAddrs))]))}, nil } - return []netip.Addr{}, ErrIPNotFound } @@ -200,7 +195,6 @@ func ResolveAllIPv4WithResolver(host string, r Resolver) ([]netip.Addr, error) { return []netip.Addr{netip.AddrFrom4(*(*[4]byte)(ip))}, nil } - return []netip.Addr{}, ErrIPNotFound } @@ -232,39 +226,6 @@ func ResolveAllIPWithResolver(host string, r Resolver) ([]netip.Addr, error) { return []netip.Addr{nnip.IpToAddr(ipAddr.IP)}, nil } - - return []netip.Addr{}, ErrIPNotFound -} - -func ResolveAllIPPrimaryIPv4WithResolver(host string, r Resolver) ([]netip.Addr, error) { - if node := DefaultHosts.Search(host); node != nil { - return []netip.Addr{node.Data}, nil - } - - ip, err := netip.ParseAddr(host) - if err == nil { - return []netip.Addr{ip}, nil - } - - if r != nil { - if DisableIPv6 { - return r.ResolveAllIPv4(host) - } - - return r.ResolveAllIPPrimaryIPv4(host) - } else if DisableIPv6 { - return ResolveAllIPv4(host) - } - - if DefaultResolver == nil { - ipAddr, err := net.ResolveIPAddr("ip", host) - if err != nil { - return []netip.Addr{}, err - } - - return []netip.Addr{nnip.IpToAddr(ipAddr.IP)}, nil - } - return []netip.Addr{}, ErrIPNotFound } @@ -284,7 +245,6 @@ func ResolveAllIPv6ProxyServerHost(host string) ([]netip.Addr, error) { if ProxyServerHostResolver != nil { return ResolveAllIPv6WithResolver(host, ProxyServerHostResolver) } - return ResolveAllIPv6(host) } @@ -292,7 +252,6 @@ func ResolveAllIPv4ProxyServerHost(host string) ([]netip.Addr, error) { if ProxyServerHostResolver != nil { return ResolveAllIPv4WithResolver(host, ProxyServerHostResolver) } - return ResolveAllIPv4(host) } @@ -300,6 +259,5 @@ func ResolveAllIPProxyServerHost(host string) ([]netip.Addr, error) { if ProxyServerHostResolver != nil { return ResolveAllIPWithResolver(host, ProxyServerHostResolver) } - return ResolveAllIP(host) } diff --git a/constant/dns.go b/constant/dns.go index eee98e5dc2..4d94ad6d4f 100644 --- a/constant/dns.go +++ b/constant/dns.go @@ -68,3 +68,46 @@ func (e DNSMode) String() string { return "unknown" } } + +type DNSPrefer int + +const ( + DualStack DNSPrefer = iota + IPv4Only + IPv6Only + IPv4Prefer + IPv6Prefer +) + +var dnsPreferMap = map[string]DNSPrefer{ + DualStack.String(): DualStack, + IPv4Only.String(): IPv4Only, + IPv6Only.String(): IPv6Only, + IPv4Prefer.String(): IPv4Prefer, + IPv6Prefer.String(): IPv6Prefer, +} + +func (d DNSPrefer) String() string { + switch d { + case DualStack: + return "dual" + case IPv4Only: + return "ipv4" + case IPv6Only: + return "ipv6" + case IPv4Prefer: + return "ipv4-prefer" + case IPv6Prefer: + return "ipv6-prefer" + default: + return "dual" + } +} + +func NewDNSPrefer(prefer string) DNSPrefer { + if p, ok := dnsPreferMap[prefer]; ok { + return p + } else { + return DualStack + } +} diff --git a/dns/resolver.go b/dns/resolver.go index b576fe3675..aac22cc86e 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -88,7 +88,7 @@ func (r *Resolver) ResolveAllIP(host string) (ips []netip.Addr, err error) { return nil, resolver.ErrIPNotFound } ips = append(ips, ipv6s...) - case <-time.After(1 * time.Millisecond): + case <-time.After(30 * time.Millisecond): // wait ipv6 result } diff --git a/docs/config.yaml b/docs/config.yaml index 2f77a53b11..9240bf9638 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -1,19 +1,19 @@ # port: 7890 # HTTP(S) 代理服务器端口 # socks-port: 7891 # SOCKS5 代理端口 mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口 -# redir-port: 7892 # 透明代理端口 用于Linux和 +# redir-port: 7892 # 透明代理端口,用于 Linux 和 MacOS # Transparent proxy server port for Linux (TProxy TCP and TProxy UDP) # tproxy-port: 7893 allow-lan: true # 允许局域网连接 -bind-address: "*" # 绑定IP地址,仅作用于 allow-lan 为true, '*'表示所有地址 +bind-address: "*" # 绑定IP地址,仅作用于 allow-lan 为 true,'*'表示所有地址 mode: rule -log-level: debug #日志等级 silent/error/warning/info/debug +log-level: debug # 日志等级 silent/error/warning/info/debug -ipv6: true #开启IPv6总开关 关闭阻断所有IPv6和屏蔽DNS请求AAAA记录 +ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录 external-controller: 0.0.0.0:9093 # RESTful API 监听地址 @@ -24,33 +24,33 @@ external-ui: /path/to/ui/folder # 配置WEB UI目录,使用http://{{external-c # interface-name: en0 # 设置出口网卡 -# routing-mark: 6666 # 配置fwmark 仅用于Linux +# routing-mark: 6666 # 配置 fwmark 仅用于Linux experimental: # 具体配置待定 # 证书指纹,SHA256格式,补充校验TLS证书 # 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取 fingerprints: - "8F111FA9AD3CD8E917A118522CAC39EA33741B3BBE73F91CECE548D5CCB0E5E8" # 忽略大小写 -# 类似于/etc/hosts, 仅支持配置单个IP +# 类似于 /etc/hosts, 仅支持配置单个 IP hosts: # '*.clash.dev': 127.0.0.1 # '.dev': 127.0.0.1 # 'alpha.clash.dev': '::1' -# Tun配置 +# Tun 配置 tun: enable: false stack: system # gvisor dns-hijack: - - 198.18.0.2:53 # 需要劫持的DNS - # auto-detect-interface: true # 自动识别interface - # auto-route: true # 配置 + - 198.18.0.2:53 # 需要劫持的 DNS + # auto-detect-interface: true # 自动识别出口网卡 + # auto-route: true # 配置路由表 #ebpf配置 ebpf: - auto-redir: #redirect模式,仅支持TCP + auto-redir: # redirect 模式,仅支持 TCP - eth0 - redirect-to-tun: #UDP+TCP,使用该功能请勿启用auto-route + redirect-to-tun: # UDP+TCP 使用该功能请勿启用 auto-route - eth0 # 嗅探域名 可选配置 @@ -63,7 +63,7 @@ sniffer: # 强制对此域名进行嗅探 force-domain: - +.v2ex.com - # 仅对白名单中的端口进行嗅探,默认为0-65535,推荐只对需要嗅探写的的常见端口嗅探 + # 仅对白名单中的端口进行嗅探,默认为 443,80 port-whitelist: - "80" - "443" @@ -78,12 +78,12 @@ profile: # DNS配置 dns: - enable: false # 关闭将使用系统DNS - listen: 0.0.0.0:53 # 开启DNS服务器监听 - # ipv6: false # false将返回AAAA的空结果 + enable: false # 关闭将使用系统 DNS + listen: 0.0.0.0:53 # 开启 DNS 服务器监听 + # ipv6: false # false 将返回 AAAA 的空结果 - # 用于解析nameserver,fallbacky以及其他DNS服务器配置的,DNS服务域名 - # 只能使用纯IP地址,可使用加密DNS + # 用于解析 nameserver,fallback 以及其他DNS服务器配置的,DNS 服务域名 + # 只能使用纯 IP 地址,可使用加密 DNS default-nameserver: - 114.114.114.114 - 8.8.8.8 @@ -93,7 +93,7 @@ dns: fake-ip-range: 198.18.0.1/16 # fake-ip 池设置 - # use-hosts: true # 查询hosts + # use-hosts: true # 查询 hosts # 配置不使用fake-ip的域名 # fake-ip-filter: @@ -101,40 +101,40 @@ dns: # - localhost.ptlogin2.qq.com # DNS主要域名配置 - # 支持 UDP,TCP,DoT,DoH,DoQ - # 这部分为主要DNS配置,影响所有直连,确保使用对大陆解析精准的DNS + # 支持 UDP,TCP,DoT,DoH,DoQ + # 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS nameserver: - 114.114.114.114 # default value - 8.8.8.8 # default value - tls://223.5.5.5:853 # DNS over TLS - https://doh.pub/dns-query # DNS over HTTPS - https://dns.alidns.com/dns-query#h3=true # 强制HTTP/3 - - https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用HTTP/3 + - https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3 - dhcp://en0 # dns from dhcp - quic://dns.adguard.com:784 # DNS over QUIC # - '8.8.8.8#en0' # 兼容指定DNS出口网卡 - # 当配置fallback时,会查询nameserver中返回的IP是否为CN,非必要配置 - # 当不是CN,则使用fallback中的DNS查询结果 - # 确保配置fallback时能够正常查询 + # 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置 + # 当不是 CN,则使用 fallback 中的 DNS 查询结果 + # 确保配置 fallback 时能够正常查询 # fallback: # - tcp://1.1.1.1 - # - 'tcp://1.1.1.1#ProxyGroupName' # 指定DNS过代理查询,ProxyGroupName为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡 + # - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡 - # 专用于节点域名解析的DNS服务器,非必要配置项 - # 配置服务器若查询失败将使用nameserver,非并发查询 + # 专用于节点域名解析的 DNS 服务器,非必要配置项 + # 配置服务器若查询失败将使用 nameserver,非并发查询 # proxy-server-nameserver: # - https://dns.google/dns-query # - tls://one.one.one.one - # 配置fallback使用条件 + # 配置 fallback 使用条件 # fallback-filter: - # geoip: true # 配置是否使用geoip - # geoip-code: CN # 当nameserver域名的IP查询geoip库为CN时,不使用fallback - # 配置强制fallback,优先于IP判断,具体分类自行查看geosite库 + # geoip: true # 配置是否使用 geoip + # geoip-code: CN # 当 nameserver 域名的 IP 查询 geoip 库为 CN 时,不使用 fallback 中的 DNS 查询结果 + # 配置强制 fallback,优先于 IP 判断,具体分类自行查看 geosite 库 # geosite: # - gfw - # 配置不需要使用fallback的IP CIDR + # 配置不需要使用 fallback 的 IP CIDR # ipcidr: # - 240.0.0.0/4 # domain: @@ -142,7 +142,7 @@ dns: # - '+.facebook.com' # - '+.youtube.com' - # 配置查询域名使用的DNS服务器 + # 配置查询域名使用的 DNS 服务器 # nameserver-policy: # 'www.baidu.com': '114.114.114.114' # '+.internal.crop.com': '10.0.0.1' @@ -164,7 +164,12 @@ proxies: password: "password" # udp: true # udp-over-tcp: false - + # ip-version: ipv4 # 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer。默认使用 dual + # ipv4:仅使用 IPv4 ipv6:仅使用 IPv6 + # ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, + # UDP 则为双栈解析,获取结果中的第一个 IPv4 + # ipv6-prefer 同 ipv4-prefer + # 现有协议都支持此参数 - name: "ss2" type: ss server: server @@ -249,6 +254,7 @@ proxies: # # headers: # # Connection: # # - keep-alive + # ip-version: ipv4 # 设置使用 IP 类型偏好,可选:ipv4,ipv6,dual,默认值:dual - name: vmess-grpc server: server @@ -264,6 +270,7 @@ proxies: # skip-cert-verify: true grpc-opts: grpc-service-name: "example" + # ip-version: ipv4 # socks5 - name: "socks" @@ -276,6 +283,7 @@ proxies: # fingerprint: xxxx # skip-cert-verify: true # udp: true + # ip-version: ipv6 # http - name: "http" @@ -288,6 +296,7 @@ proxies: # skip-cert-verify: true # sni: custom.com # fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints + # ip-version: dual # Snell # Beware that there's currently no UDP support yet @@ -389,9 +398,9 @@ proxies: auth_str: yourpassword # obfs: obfs_str # alpn: h3 - protocol: udp #支持udp/wechat-video/faketcp - up: "30 Mbps" #若不写单位,默认为Mbps - down: "200 Mbps" #若不写单位,默认为Mbps + protocol: udp # 支持 udp/wechat-video/faketcp + up: "30 Mbps" # 若不写单位,默认为 Mbps + down: "200 Mbps" # 若不写单位,默认为 Mbps #sni: server.com #skip-cert-verify: false #recv_window_conn: 12582912 @@ -422,7 +431,7 @@ proxies: # udp: true proxy-groups: - # 代理链,若落地协议支持UDP over TCP则可支持UDP + # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP # Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet - name: "relay" type: relay @@ -432,7 +441,7 @@ proxy-groups: - ss1 - ss2 - # url-test 将按照url测试结果使用延迟最低节点 + # url-test 将按照 url 测试结果使用延迟最低节点 - name: "auto" type: url-test proxies: @@ -444,7 +453,7 @@ proxy-groups: url: "http://www.gstatic.com/generate_204" interval: 300 - # fallback 将按照url测试结果按照节点顺序选择 + # fallback 将按照 url 测试结果按照节点顺序选择 - name: "fallback-auto" type: fallback proxies: @@ -475,7 +484,7 @@ proxy-groups: - vmess1 - auto - # 配置指定interface-name和fwmark的DIRECT + # 配置指定 interface-name 和 fwmark 的 DIRECT - name: en1 type: select interface-name: en1 @@ -485,14 +494,14 @@ proxy-groups: - name: UseProvider type: select - filter: "HK|TW" # 正则表达式,过滤provider1中节点名包含HK或TW + filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK 或 TW use: - provider1 proxies: - Proxy - DIRECT -# Clash格式的节点或支持*ray的分享格式 +# Clash 格式的节点或支持 *ray 的分享格式 proxy-providers: provider1: type: http diff --git a/transport/hysteria/transport/client.go b/transport/hysteria/transport/client.go index 9d0158e5b8..d350b4ade0 100644 --- a/transport/hysteria/transport/client.go +++ b/transport/hysteria/transport/client.go @@ -4,12 +4,10 @@ import ( "context" "crypto/tls" "fmt" - "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/transport/hysteria/conns/faketcp" "github.com/Dreamacro/clash/transport/hysteria/conns/udp" "github.com/Dreamacro/clash/transport/hysteria/conns/wechat" "github.com/Dreamacro/clash/transport/hysteria/obfs" - "github.com/Dreamacro/clash/transport/hysteria/utils" "github.com/lucas-clemente/quic-go" "net" ) @@ -61,29 +59,21 @@ func (ct *ClientTransport) quicPacketConn(proto string, server string, obfs obfs type PacketDialer interface { ListenPacket() (net.PacketConn, error) Context() context.Context + RemoteAddr(host string) (net.Addr, error) } func (ct *ClientTransport) QUICDial(proto string, server string, tlsConfig *tls.Config, quicConfig *quic.Config, obfs obfs.Obfuscator, dialer PacketDialer) (quic.Connection, error) { - ipStr, port, err := utils.SplitHostPort(server) + serverUDPAddr, err := dialer.RemoteAddr(server) if err != nil { return nil, err } - ip, err := resolver.ResolveProxyServerHost(ipStr) - if err != nil { - return nil, err - } - - serverUDPAddr := net.UDPAddr{ - IP: net.ParseIP(ip.String()), - Port: int(port), - } - pktConn, err := ct.quicPacketConn(proto, serverUDPAddr.String(), obfs, dialer) if err != nil { return nil, err } - qs, err := quic.DialContext(dialer.Context(), pktConn, &serverUDPAddr, server, tlsConfig, quicConfig) + + qs, err := quic.DialContext(dialer.Context(), pktConn, serverUDPAddr, server, tlsConfig, quicConfig) if err != nil { _ = pktConn.Close() return nil, err