github.com/igoogolx/clash@v1.19.8/component/dialer/dialer.go (about) 1 package dialer 2 3 import ( 4 "context" 5 "errors" 6 "net" 7 8 "github.com/igoogolx/clash/component/resolver" 9 ) 10 11 func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { 12 switch network { 13 case "tcp4", "tcp6", "udp4", "udp6": 14 host, port, err := net.SplitHostPort(address) 15 if err != nil { 16 return nil, err 17 } 18 19 var ip net.IP 20 switch network { 21 case "tcp4", "udp4": 22 ip, err = resolver.ResolveIPv4(host) 23 default: 24 ip, err = resolver.ResolveIPv6(host) 25 } 26 if err != nil { 27 return nil, err 28 } 29 30 return dialContext(ctx, network, ip, port, options) 31 case "tcp", "udp": 32 return dualStackDialContext(ctx, network, address, options) 33 default: 34 return nil, errors.New("network invalid") 35 } 36 } 37 38 func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { 39 cfg := &option{ 40 interfaceName: DefaultInterface.Load(), 41 routingMark: int(DefaultRoutingMark.Load()), 42 } 43 44 for _, o := range DefaultOptions { 45 o(cfg) 46 } 47 48 for _, o := range options { 49 o(cfg) 50 } 51 52 lc := &net.ListenConfig{} 53 if cfg.interfaceName != "" { 54 var ( 55 addr string 56 err error 57 ) 58 if cfg.fallbackBind { 59 addr, err = fallbackBindIfaceToListenConfig(cfg.interfaceName, lc, network, address) 60 } else { 61 addr, err = bindIfaceToListenConfig(cfg.interfaceName, lc, network, address) 62 } 63 if err != nil { 64 return nil, err 65 } 66 address = addr 67 } 68 if cfg.addrReuse { 69 addrReuseToListenConfig(lc) 70 } 71 if cfg.routingMark != 0 { 72 bindMarkToListenConfig(cfg.routingMark, lc, network, address) 73 } 74 75 return lc.ListenPacket(ctx, network, address) 76 } 77 78 func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) { 79 opt := &option{ 80 interfaceName: DefaultInterface.Load(), 81 routingMark: int(DefaultRoutingMark.Load()), 82 } 83 84 for _, o := range DefaultOptions { 85 o(opt) 86 } 87 88 for _, o := range options { 89 o(opt) 90 } 91 92 dialer := &net.Dialer{} 93 if opt.interfaceName != "" { 94 if opt.fallbackBind { 95 if err := fallbackBindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { 96 return nil, err 97 } 98 } else { 99 if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { 100 return nil, err 101 } 102 } 103 } 104 if opt.routingMark != 0 { 105 bindMarkToDialer(opt.routingMark, dialer, network, destination) 106 } 107 108 return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port)) 109 } 110 111 func dualStackDialContext(ctx context.Context, network, address string, options []Option) (net.Conn, error) { 112 host, port, err := net.SplitHostPort(address) 113 if err != nil { 114 return nil, err 115 } 116 117 returned := make(chan struct{}) 118 defer close(returned) 119 120 type dialResult struct { 121 net.Conn 122 error 123 resolved bool 124 ipv6 bool 125 done bool 126 } 127 results := make(chan dialResult) 128 var primary, fallback dialResult 129 130 startRacer := func(ctx context.Context, network, host string, ipv6 bool) { 131 result := dialResult{ipv6: ipv6, done: true} 132 defer func() { 133 select { 134 case results <- result: 135 case <-returned: 136 if result.Conn != nil { 137 result.Conn.Close() 138 } 139 } 140 }() 141 142 var ip net.IP 143 if ipv6 { 144 ip, result.error = resolver.ResolveIPv6(host) 145 } else { 146 ip, result.error = resolver.ResolveIPv4(host) 147 } 148 if result.error != nil { 149 return 150 } 151 result.resolved = true 152 153 result.Conn, result.error = dialContext(ctx, network, ip, port, options) 154 } 155 156 go startRacer(ctx, network+"4", host, false) 157 go startRacer(ctx, network+"6", host, true) 158 159 for res := range results { 160 if res.error == nil { 161 return res.Conn, nil 162 } 163 164 if !res.ipv6 { 165 primary = res 166 } else { 167 fallback = res 168 } 169 170 if primary.done && fallback.done { 171 if primary.resolved { 172 return nil, primary.error 173 } else if fallback.resolved { 174 return nil, fallback.error 175 } else { 176 return nil, primary.error 177 } 178 } 179 } 180 181 return nil, errors.New("never touched") 182 }