feat: add ip-version param

This commit is contained in:
Skyxim 2022-08-28 13:41:19 +08:00
parent 42e489e199
commit 99effb051b
20 changed files with 398 additions and 216 deletions

View File

@ -13,13 +13,14 @@ import (
) )
type Base struct { type Base struct {
name string name string
addr string addr string
iface string iface string
tp C.AdapterType tp C.AdapterType
udp bool udp bool
rmark int rmark int
id string id string
prefer C.DNSPrefer
} }
// Name implements C.ProxyAdapter // Name implements C.ProxyAdapter
@ -103,12 +104,25 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
opts = append(opts, dialer.WithRoutingMark(b.rmark)) 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 return opts
} }
type BasicOption struct { type BasicOption struct {
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"` Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"` RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
} }
type BaseOption struct { type BaseOption struct {
@ -118,16 +132,18 @@ type BaseOption struct {
UDP bool UDP bool
Interface string Interface string
RoutingMark int RoutingMark int
Prefer C.DNSPrefer
} }
func NewBase(opt BaseOption) *Base { func NewBase(opt BaseOption) *Base {
return &Base{ return &Base{
name: opt.Name, name: opt.Name,
addr: opt.Addr, addr: opt.Addr,
tp: opt.Type, tp: opt.Type,
udp: opt.UDP, udp: opt.UDP,
iface: opt.Interface, iface: opt.Interface,
rmark: opt.RoutingMark, rmark: opt.RoutingMark,
prefer: opt.Prefer,
} }
} }

View File

@ -40,9 +40,10 @@ type directPacketConn struct {
func NewDirect() *Direct { func NewDirect() *Direct {
return &Direct{ return &Direct{
Base: &Base{ Base: &Base{
name: "DIRECT", name: "DIRECT",
tp: C.Direct, tp: C.Direct,
udp: true, udp: true,
prefer: C.DualStack,
}, },
} }
} }
@ -50,9 +51,10 @@ func NewDirect() *Direct {
func NewCompatible() *Direct { func NewCompatible() *Direct {
return &Direct{ return &Direct{
Base: &Base{ Base: &Base{
name: "COMPATIBLE", name: "COMPATIBLE",
tp: C.Compatible, tp: C.Compatible,
udp: true, udp: true,
prefer: C.DualStack,
}, },
} }
} }

View File

@ -153,11 +153,12 @@ func NewHttp(option HttpOption) (*Http, error) {
return &Http{ return &Http{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Http, tp: C.Http,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
}, },
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,

View File

@ -53,6 +53,9 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
hyDialer: func() (net.PacketConn, error) { hyDialer: func() (net.PacketConn, error) {
return dialer.ListenPacket(ctx, "udp", "", h.Base.DialOptions(opts...)...) 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) tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), &hdc)
@ -184,11 +187,11 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
option.Protocol = DefaultProtocol option.Protocol = DefaultProtocol
} }
if option.ReceiveWindowConn == 0 { if option.ReceiveWindowConn == 0 {
quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow / 10
quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow
} }
if option.ReceiveWindow == 0 { if option.ReceiveWindow == 0 {
quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow / 10
quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow
} }
if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery { if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery {
@ -216,12 +219,13 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
} }
return &Hysteria{ return &Hysteria{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Hysteria, tp: C.Hysteria,
udp: true, udp: true,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
}, },
client: client, client: client,
}, nil }, nil
@ -287,8 +291,9 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
} }
type hyDialerWithContext struct { type hyDialerWithContext struct {
hyDialer func() (net.PacketConn, error) hyDialer func() (net.PacketConn, error)
ctx context.Context ctx context.Context
remoteAddr func(host string) (net.Addr, error)
} }
func (h *hyDialerWithContext) ListenPacket() (net.PacketConn, 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 { func (h *hyDialerWithContext) Context() context.Context {
return h.ctx return h.ctx
} }
func (h *hyDialerWithContext) RemoteAddr(host string) (net.Addr, error) {
return h.remoteAddr(host)
}

View File

@ -27,9 +27,10 @@ func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
func NewReject() *Reject { func NewReject() *Reject {
return &Reject{ return &Reject{
Base: &Base{ Base: &Base{
name: "REJECT", name: "REJECT",
tp: C.Reject, tp: C.Reject,
udp: true, udp: true,
prefer: C.DualStack,
}, },
} }
} }
@ -37,9 +38,10 @@ func NewReject() *Reject {
func NewPass() *Reject { func NewPass() *Reject {
return &Reject{ return &Reject{
Base: &Base{ Base: &Base{
name: "PASS", name: "PASS",
tp: C.Pass, tp: C.Pass,
udp: true, udp: true,
prefer: C.DualStack,
}, },
} }
} }

View File

@ -116,7 +116,7 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta
return nil, err return nil, err
} }
addr, err := resolveUDPAddr("udp", ss.addr) addr, err := resolveUDPAddrWithPrefer("udp", ss.addr, ss.prefer)
if err != nil { if err != nil {
pc.Close() pc.Close()
return nil, err return nil, err
@ -186,12 +186,13 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return &ShadowSocks{ return &ShadowSocks{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Shadowsocks, tp: C.Shadowsocks,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
}, },
method: method, method: method,

View File

@ -79,7 +79,7 @@ func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Me
return nil, err return nil, err
} }
addr, err := resolveUDPAddr("udp", ssr.addr) addr, err := resolveUDPAddrWithPrefer("udp", ssr.addr, ssr.prefer)
if err != nil { if err != nil {
pc.Close() pc.Close()
return nil, err return nil, err
@ -143,12 +143,13 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
return &ShadowSocksR{ return &ShadowSocksR{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.ShadowsocksR, tp: C.ShadowsocksR,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
}, },
cipher: coreCiph, cipher: coreCiph,
obfs: obfs, obfs: obfs,

View File

@ -152,12 +152,13 @@ func NewSnell(option SnellOption) (*Snell, error) {
s := &Snell{ s := &Snell{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Snell, tp: C.Snell,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
}, },
psk: psk, psk: psk,
obfsOption: obfsOption, obfsOption: obfsOption,

View File

@ -160,12 +160,13 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
return &Socks5{ return &Socks5{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Socks5, tp: C.Socks5,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
}, },
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,

View File

@ -209,12 +209,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
t := &Trojan{ t := &Trojan{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: addr, addr: addr,
tp: C.Trojan, tp: C.Trojan,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
}, },
instance: trojan.New(tOption), instance: trojan.New(tOption),
option: &option, option: &option,

View File

@ -5,6 +5,7 @@ import (
"crypto/tls" "crypto/tls"
xtls "github.com/xtls/go" xtls "github.com/xtls/go"
"net" "net"
"net/netip"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@ -74,6 +75,59 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) 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) { func safeConnClose(c net.Conn, err error) {
if err != nil { if err != nil {
_ = c.Close() _ = c.Close()

View File

@ -418,11 +418,12 @@ func NewVless(option VlessOption) (*Vless, error) {
v := &Vless{ v := &Vless{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vless, tp: C.Vless,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
prefer: C.NewDNSPrefer(option.IPVersion),
}, },
client: client, client: client,
option: &option, option: &option,

View File

@ -316,12 +316,13 @@ func NewVmess(option VmessOption) (*Vmess, error) {
v := &Vmess{ v := &Vmess{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vmess, tp: C.Vmess,
udp: option.UDP, udp: option.UDP,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
}, },
client: client, client: client,
option: &option, option: &option,

View File

@ -5,8 +5,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"go.uber.org/atomic"
"net" "net"
"net/netip" "net/netip"
"strings"
"sync" "sync"
) )
@ -16,6 +18,8 @@ var (
actualDualStackDialContext = dualStackDialContext actualDualStackDialContext = dualStackDialContext
tcpConcurrent = false tcpConcurrent = false
DisableIPv6 = 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) { 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) 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 { switch network {
case "tcp4", "tcp6", "udp4", "udp6": case "tcp4", "tcp6", "udp4", "udp6":
return actualSingleDialContext(ctx, network, address, opt) return actualSingleDialContext(ctx, network, address, opt)
case "tcp", "udp": case "tcp", "udp":
return actualDualStackDialContext(ctx, network, address, opt) return actualDualStackDialContext(ctx, network, address, opt)
default: 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) o(cfg)
} }
if DisableIPv6 {
network = "udp4"
}
lc := &net.ListenConfig{} lc := &net.ListenConfig{}
if cfg.interfaceName != "" { if cfg.interfaceName != "" {
addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address) 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() { 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)) 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 ip netip.Addr
net.Conn net.Conn
error error
resolved bool isPrimary bool
done bool
} }
preferCount := atomic.NewInt32(0)
results := make(chan dialResult) results := make(chan dialResult)
tcpRacer := func(ctx context.Context, ip netip.Addr) { tcpRacer := func(ctx context.Context, ip netip.Addr) {
result := dialResult{ip: ip} result := dialResult{ip: ip, done: true}
defer func() { defer func() {
select { select {
case results <- result: case results <- result:
case <-returned: case <-returned:
if result.Conn != nil { if result.Conn != nil {
result.Conn.Close() _ = result.Conn.Close()
} }
} }
}() }()
if strings.Contains(network, "tcp") {
v := "4" network = "tcp"
if ip.Is6() { } else {
v = "6" network = "udp"
} }
result.Conn, result.error = dialContext(ctx, network+v, ip, port, opt) if ip.Is6() {
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, ip, port, opt)
} }
for _, ip := range ips { for _, ip := range ips {
@ -260,13 +290,28 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr
} }
connCount := len(ips) connCount := len(ips)
var fallback dialResult
for i := 0; i < connCount; i++ { for i := 0; i < connCount; i++ {
select { select {
case res := <-results: case res := <-results:
if res.error == nil { 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(): case <-ctx.Done():
if fallback.done {
return fallback.Conn, nil
}
break 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) { 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) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var ips []netip.Addr var ips []netip.Addr
switch network { if !opt.direct {
case "tcp4", "udp4": ips, err = resolver.ResolveAllIPv4ProxyServerHost(host)
if !opt.direct { } else {
ips, err = resolver.ResolveAllIPv4ProxyServerHost(host) ips, err = resolver.ResolveAllIPv4(host)
} else { }
ips, err = resolver.ResolveAllIPv4(host)
} if err != nil {
default: return nil, err
if !opt.direct { }
ips, err = resolver.ResolveAllIPv6ProxyServerHost(host)
} else { return concurrentDialContext(ctx, network, ips, port, opt)
ips, err = resolver.ResolveAllIPv6(host) }
}
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 { if err != nil {

View File

@ -1,6 +1,8 @@
package dialer package dialer
import "go.uber.org/atomic" import (
"go.uber.org/atomic"
)
var ( var (
DefaultOptions []Option DefaultOptions []Option
@ -13,6 +15,8 @@ type option struct {
addrReuse bool addrReuse bool
routingMark int routingMark int
direct bool direct bool
network int
prefer int
} }
type Option func(opt *option) type Option func(opt *option)
@ -40,3 +44,25 @@ func WithDirect() Option {
opt.direct = true 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
}
}
}

View File

@ -41,7 +41,6 @@ type Resolver interface {
ResolveIPv4(host string) (ip netip.Addr, err error) ResolveIPv4(host string) (ip netip.Addr, err error)
ResolveIPv6(host string) (ip netip.Addr, err error) ResolveIPv6(host string) (ip netip.Addr, err error)
ResolveAllIP(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) ResolveAllIPv4(host string) (ips []netip.Addr, err error)
ResolveAllIPv6(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 // ResolveIPWithResolver same as ResolveIP, but with a resolver
func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) { func ResolveIPWithResolver(host string, r Resolver) (netip.Addr, error) {
if ips, err := ResolveAllIPPrimaryIPv4WithResolver(host, r); err == nil { if ip, err := ResolveIPv4WithResolver(host, r); err == nil {
return ips[rand.Intn(len(ips))], nil return ip, nil
} else { } else {
return netip.Addr{}, err return ResolveIPv6WithResolver(host, r)
} }
} }
@ -95,7 +94,6 @@ func ResolveIPv4ProxyServerHost(host string) (netip.Addr, error) {
return ip, nil return ip, nil
} }
} }
return ResolveIPv4(host) return ResolveIPv4(host)
} }
@ -108,7 +106,6 @@ func ResolveIPv6ProxyServerHost(host string) (netip.Addr, error) {
return ip, nil return ip, nil
} }
} }
return ResolveIPv6(host) return ResolveIPv6(host)
} }
@ -121,7 +118,6 @@ func ResolveProxyServerHost(host string) (netip.Addr, error) {
return ip, err return ip, err
} }
} }
return ResolveIP(host) 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{netip.AddrFrom16(*(*[16]byte)(ipAddrs[rand.Intn(len(ipAddrs))]))}, nil
} }
return []netip.Addr{}, ErrIPNotFound 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{netip.AddrFrom4(*(*[4]byte)(ip))}, nil
} }
return []netip.Addr{}, ErrIPNotFound 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{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 return []netip.Addr{}, ErrIPNotFound
} }
@ -284,7 +245,6 @@ func ResolveAllIPv6ProxyServerHost(host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveAllIPv6WithResolver(host, ProxyServerHostResolver) return ResolveAllIPv6WithResolver(host, ProxyServerHostResolver)
} }
return ResolveAllIPv6(host) return ResolveAllIPv6(host)
} }
@ -292,7 +252,6 @@ func ResolveAllIPv4ProxyServerHost(host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveAllIPv4WithResolver(host, ProxyServerHostResolver) return ResolveAllIPv4WithResolver(host, ProxyServerHostResolver)
} }
return ResolveAllIPv4(host) return ResolveAllIPv4(host)
} }
@ -300,6 +259,5 @@ func ResolveAllIPProxyServerHost(host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil { if ProxyServerHostResolver != nil {
return ResolveAllIPWithResolver(host, ProxyServerHostResolver) return ResolveAllIPWithResolver(host, ProxyServerHostResolver)
} }
return ResolveAllIP(host) return ResolveAllIP(host)
} }

View File

@ -68,3 +68,46 @@ func (e DNSMode) String() string {
return "unknown" 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
}
}

View File

@ -88,7 +88,7 @@ func (r *Resolver) ResolveAllIP(host string) (ips []netip.Addr, err error) {
return nil, resolver.ErrIPNotFound return nil, resolver.ErrIPNotFound
} }
ips = append(ips, ipv6s...) ips = append(ips, ipv6s...)
case <-time.After(1 * time.Millisecond): case <-time.After(30 * time.Millisecond):
// wait ipv6 result // wait ipv6 result
} }

View File

@ -1,19 +1,19 @@
# port: 7890 # HTTP(S) 代理服务器端口 # port: 7890 # HTTP(S) 代理服务器端口
# socks-port: 7891 # SOCKS5 代理端口 # socks-port: 7891 # SOCKS5 代理端口
mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口 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) # Transparent proxy server port for Linux (TProxy TCP and TProxy UDP)
# tproxy-port: 7893 # tproxy-port: 7893
allow-lan: true # 允许局域网连接 allow-lan: true # 允许局域网连接
bind-address: "*" # 绑定IP地址仅作用于 allow-lan 为true, '*'表示所有地址 bind-address: "*" # 绑定IP地址仅作用于 allow-lan 为 true'*'表示所有地址
mode: rule 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 监听地址 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 # 设置出口网卡 # interface-name: en0 # 设置出口网卡
# routing-mark: 6666 # 配置fwmark 仅用于Linux # routing-mark: 6666 # 配置 fwmark 仅用于Linux
experimental: experimental:
# 具体配置待定 # 具体配置待定
# 证书指纹,SHA256格式,补充校验TLS证书 # 证书指纹,SHA256格式,补充校验TLS证书
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取 # 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
fingerprints: fingerprints:
- "8F111FA9AD3CD8E917A118522CAC39EA33741B3BBE73F91CECE548D5CCB0E5E8" # 忽略大小写 - "8F111FA9AD3CD8E917A118522CAC39EA33741B3BBE73F91CECE548D5CCB0E5E8" # 忽略大小写
# 类似于/etc/hosts, 仅支持配置单个IP # 类似于 /etc/hosts, 仅支持配置单个 IP
hosts: hosts:
# '*.clash.dev': 127.0.0.1 # '*.clash.dev': 127.0.0.1
# '.dev': 127.0.0.1 # '.dev': 127.0.0.1
# 'alpha.clash.dev': '::1' # 'alpha.clash.dev': '::1'
# Tun配置 # Tun 配置
tun: tun:
enable: false enable: false
stack: system # gvisor stack: system # gvisor
dns-hijack: dns-hijack:
- 198.18.0.2:53 # 需要劫持的DNS - 198.18.0.2:53 # 需要劫持的 DNS
# auto-detect-interface: true # 自动识别interface # auto-detect-interface: true # 自动识别出口网卡
# auto-route: true # 配置 # auto-route: true # 配置路由表
#ebpf配置 #ebpf配置
ebpf: ebpf:
auto-redir: #redirect模式仅支持TCP auto-redir: # redirect 模式,仅支持 TCP
- eth0 - eth0
redirect-to-tun: #UDP+TCP,使用该功能请勿启用auto-route redirect-to-tun: # UDP+TCP 使用该功能请勿启用 auto-route
- eth0 - eth0
# 嗅探域名 可选配置 # 嗅探域名 可选配置
@ -63,7 +63,7 @@ sniffer:
# 强制对此域名进行嗅探 # 强制对此域名进行嗅探
force-domain: force-domain:
- +.v2ex.com - +.v2ex.com
# 仅对白名单中的端口进行嗅探,默认为0-65535推荐只对需要嗅探写的的常见端口嗅探 # 仅对白名单中的端口进行嗅探,默认为 44380
port-whitelist: port-whitelist:
- "80" - "80"
- "443" - "443"
@ -78,12 +78,12 @@ profile:
# DNS配置 # DNS配置
dns: dns:
enable: false # 关闭将使用系统DNS enable: false # 关闭将使用系统 DNS
listen: 0.0.0.0:53 # 开启DNS服务器监听 listen: 0.0.0.0:53 # 开启 DNS 服务器监听
# ipv6: false # false将返回AAAA的空结果 # ipv6: false # false 将返回 AAAA 的空结果
# 用于解析nameserver,fallbacky以及其他DNS服务器配置的,DNS服务域名 # 用于解析 nameserverfallback 以及其他DNS服务器配置的DNS 服务域名
# 只能使用纯IP地址可使用加密DNS # 只能使用纯 IP 地址,可使用加密 DNS
default-nameserver: default-nameserver:
- 114.114.114.114 - 114.114.114.114
- 8.8.8.8 - 8.8.8.8
@ -93,7 +93,7 @@ dns:
fake-ip-range: 198.18.0.1/16 # fake-ip 池设置 fake-ip-range: 198.18.0.1/16 # fake-ip 池设置
# use-hosts: true # 查询hosts # use-hosts: true # 查询 hosts
# 配置不使用fake-ip的域名 # 配置不使用fake-ip的域名
# fake-ip-filter: # fake-ip-filter:
@ -101,40 +101,40 @@ dns:
# - localhost.ptlogin2.qq.com # - localhost.ptlogin2.qq.com
# DNS主要域名配置 # DNS主要域名配置
# 支持 UDP,TCP,DoT,DoH,DoQ # 支持 UDPTCPDoTDoHDoQ
# 这部分为主要DNS配置,影响所有直连,确保使用对大陆解析精准的DNS # 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
nameserver: nameserver:
- 114.114.114.114 # default value - 114.114.114.114 # default value
- 8.8.8.8 # default value - 8.8.8.8 # default value
- tls://223.5.5.5:853 # DNS over TLS - tls://223.5.5.5:853 # DNS over TLS
- https://doh.pub/dns-query # DNS over HTTPS - https://doh.pub/dns-query # DNS over HTTPS
- https://dns.alidns.com/dns-query#h3=true # 强制HTTP/3 - 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 - dhcp://en0 # dns from dhcp
- quic://dns.adguard.com:784 # DNS over QUIC - quic://dns.adguard.com:784 # DNS over QUIC
# - '8.8.8.8#en0' # 兼容指定DNS出口网卡 # - '8.8.8.8#en0' # 兼容指定DNS出口网卡
# 当配置fallback时会查询nameserver中返回的IP是否为CN非必要配置 # 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN非必要配置
# 当不是CN则使用fallback中的DNS查询结果 # 当不是 CN则使用 fallback 中的 DNS 查询结果
# 确保配置fallback时能够正常查询 # 确保配置 fallback 时能够正常查询
# fallback: # fallback:
# - tcp://1.1.1.1 # - tcp://1.1.1.1
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定DNS过代理查询ProxyGroupName为策略组名或节点名过代理配置优先于配置出口网卡当找不到策略组或节点名则设置为出口网卡 # - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
# 专用于节点域名解析的DNS服务器非必要配置项 # 专用于节点域名解析的 DNS 服务器,非必要配置项
# 配置服务器若查询失败将使用nameserver非并发查询 # 配置服务器若查询失败将使用 nameserver非并发查询
# proxy-server-nameserver: # proxy-server-nameserver:
# - https://dns.google/dns-query # - https://dns.google/dns-query
# - tls://one.one.one.one # - tls://one.one.one.one
# 配置fallback使用条件 # 配置 fallback 使用条件
# fallback-filter: # fallback-filter:
# geoip: true # 配置是否使用geoip # geoip: true # 配置是否使用 geoip
# geoip-code: CN # 当nameserver域名的IP查询geoip库为CN时不使用fallback # geoip-code: CN # 当 nameserver 域名的 IP 查询 geoip 库为 CN 时,不使用 fallback 中的 DNS 查询结果
# 配置强制fallback优先于IP判断具体分类自行查看geosite库 # 配置强制 fallback优先于 IP 判断,具体分类自行查看 geosite
# geosite: # geosite:
# - gfw # - gfw
# 配置不需要使用fallback的IP CIDR # 配置不需要使用 fallback IP CIDR
# ipcidr: # ipcidr:
# - 240.0.0.0/4 # - 240.0.0.0/4
# domain: # domain:
@ -142,7 +142,7 @@ dns:
# - '+.facebook.com' # - '+.facebook.com'
# - '+.youtube.com' # - '+.youtube.com'
# 配置查询域名使用的DNS服务器 # 配置查询域名使用的 DNS 服务器
# nameserver-policy: # nameserver-policy:
# 'www.baidu.com': '114.114.114.114' # 'www.baidu.com': '114.114.114.114'
# '+.internal.crop.com': '10.0.0.1' # '+.internal.crop.com': '10.0.0.1'
@ -164,7 +164,12 @@ proxies:
password: "password" password: "password"
# udp: true # udp: true
# udp-over-tcp: false # udp-over-tcp: false
# ip-version: ipv4 # 设置节点使用 IP 版本可选dualipv4ipv6ipv4-preferipv6-prefer。默认使用 dual
# ipv4仅使用 IPv4 ipv6仅使用 IPv6
# ipv4-prefer优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接,
# UDP 则为双栈解析,获取结果中的第一个 IPv4
# ipv6-prefer 同 ipv4-prefer
# 现有协议都支持此参数
- name: "ss2" - name: "ss2"
type: ss type: ss
server: server server: server
@ -249,6 +254,7 @@ proxies:
# # headers: # # headers:
# # Connection: # # Connection:
# # - keep-alive # # - keep-alive
# ip-version: ipv4 # 设置使用 IP 类型偏好可选ipv4ipv6dual默认值dual
- name: vmess-grpc - name: vmess-grpc
server: server server: server
@ -264,6 +270,7 @@ proxies:
# skip-cert-verify: true # skip-cert-verify: true
grpc-opts: grpc-opts:
grpc-service-name: "example" grpc-service-name: "example"
# ip-version: ipv4
# socks5 # socks5
- name: "socks" - name: "socks"
@ -276,6 +283,7 @@ proxies:
# fingerprint: xxxx # fingerprint: xxxx
# skip-cert-verify: true # skip-cert-verify: true
# udp: true # udp: true
# ip-version: ipv6
# http # http
- name: "http" - name: "http"
@ -288,6 +296,7 @@ proxies:
# skip-cert-verify: true # skip-cert-verify: true
# sni: custom.com # sni: custom.com
# fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints # fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints
# ip-version: dual
# Snell # Snell
# Beware that there's currently no UDP support yet # Beware that there's currently no UDP support yet
@ -389,9 +398,9 @@ proxies:
auth_str: yourpassword auth_str: yourpassword
# obfs: obfs_str # obfs: obfs_str
# alpn: h3 # alpn: h3
protocol: udp #支持udp/wechat-video/faketcp protocol: udp # 支持 udp/wechat-video/faketcp
up: "30 Mbps" #若不写单位默认为Mbps up: "30 Mbps" # 若不写单位,默认为 Mbps
down: "200 Mbps" #若不写单位默认为Mbps down: "200 Mbps" # 若不写单位,默认为 Mbps
#sni: server.com #sni: server.com
#skip-cert-verify: false #skip-cert-verify: false
#recv_window_conn: 12582912 #recv_window_conn: 12582912
@ -422,7 +431,7 @@ proxies:
# udp: true # udp: true
proxy-groups: proxy-groups:
# 代理链,若落地协议支持UDP over TCP则可支持UDP # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet # Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
- name: "relay" - name: "relay"
type: relay type: relay
@ -432,7 +441,7 @@ proxy-groups:
- ss1 - ss1
- ss2 - ss2
# url-test 将按照url测试结果使用延迟最低节点 # url-test 将按照 url 测试结果使用延迟最低节点
- name: "auto" - name: "auto"
type: url-test type: url-test
proxies: proxies:
@ -444,7 +453,7 @@ proxy-groups:
url: "http://www.gstatic.com/generate_204" url: "http://www.gstatic.com/generate_204"
interval: 300 interval: 300
# fallback 将按照url测试结果按照节点顺序选择 # fallback 将按照 url 测试结果按照节点顺序选择
- name: "fallback-auto" - name: "fallback-auto"
type: fallback type: fallback
proxies: proxies:
@ -475,7 +484,7 @@ proxy-groups:
- vmess1 - vmess1
- auto - auto
# 配置指定interface-name和fwmark的DIRECT # 配置指定 interface-name fwmark DIRECT
- name: en1 - name: en1
type: select type: select
interface-name: en1 interface-name: en1
@ -485,14 +494,14 @@ proxy-groups:
- name: UseProvider - name: UseProvider
type: select type: select
filter: "HK|TW" # 正则表达式,过滤provider1中节点名包含HK或TW filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK TW
use: use:
- provider1 - provider1
proxies: proxies:
- Proxy - Proxy
- DIRECT - DIRECT
# Clash格式的节点或支持*ray的分享格式 # Clash 格式的节点或支持 *ray 的分享格式
proxy-providers: proxy-providers:
provider1: provider1:
type: http type: http

View File

@ -4,12 +4,10 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/transport/hysteria/conns/faketcp" "github.com/Dreamacro/clash/transport/hysteria/conns/faketcp"
"github.com/Dreamacro/clash/transport/hysteria/conns/udp" "github.com/Dreamacro/clash/transport/hysteria/conns/udp"
"github.com/Dreamacro/clash/transport/hysteria/conns/wechat" "github.com/Dreamacro/clash/transport/hysteria/conns/wechat"
"github.com/Dreamacro/clash/transport/hysteria/obfs" "github.com/Dreamacro/clash/transport/hysteria/obfs"
"github.com/Dreamacro/clash/transport/hysteria/utils"
"github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go"
"net" "net"
) )
@ -61,29 +59,21 @@ func (ct *ClientTransport) quicPacketConn(proto string, server string, obfs obfs
type PacketDialer interface { type PacketDialer interface {
ListenPacket() (net.PacketConn, error) ListenPacket() (net.PacketConn, error)
Context() context.Context 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) { 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 { if err != nil {
return nil, err 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) pktConn, err := ct.quicPacketConn(proto, serverUDPAddr.String(), obfs, dialer)
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
_ = pktConn.Close() _ = pktConn.Close()
return nil, err return nil, err