github.com/chwjbn/xclash@v0.2.0/component/dialer/dialer.go (about) 1 package dialer 2 3 import ( 4 "context" 5 "errors" 6 "net" 7 8 "github.com/chwjbn/xclash/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 } 42 43 for _, o := range DefaultOptions { 44 o(cfg) 45 } 46 47 for _, o := range options { 48 o(cfg) 49 } 50 51 lc := &net.ListenConfig{} 52 if cfg.interfaceName != "" { 53 addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address) 54 if err != nil { 55 return nil, err 56 } 57 address = addr 58 } 59 if cfg.addrReuse { 60 addrReuseToListenConfig(lc) 61 } 62 if cfg.routingMark != 0 { 63 bindMarkToListenConfig(cfg.routingMark, lc, network, address) 64 } 65 66 return lc.ListenPacket(ctx, network, address) 67 } 68 69 func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) { 70 opt := &option{ 71 interfaceName: DefaultInterface.Load(), 72 } 73 74 for _, o := range DefaultOptions { 75 o(opt) 76 } 77 78 for _, o := range options { 79 o(opt) 80 } 81 82 dialer := &net.Dialer{} 83 if opt.interfaceName != "" { 84 if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { 85 return nil, err 86 } 87 } 88 if opt.routingMark != 0 { 89 bindMarkToDialer(opt.routingMark, dialer, network, destination) 90 } 91 92 return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port)) 93 } 94 95 func dualStackDialContext(ctx context.Context, network, address string, options []Option) (net.Conn, error) { 96 host, port, err := net.SplitHostPort(address) 97 if err != nil { 98 return nil, err 99 } 100 101 returned := make(chan struct{}) 102 defer close(returned) 103 104 type dialResult struct { 105 net.Conn 106 error 107 resolved bool 108 ipv6 bool 109 done bool 110 } 111 results := make(chan dialResult) 112 var primary, fallback dialResult 113 114 startRacer := func(ctx context.Context, network, host string, ipv6 bool) { 115 result := dialResult{ipv6: ipv6, done: true} 116 defer func() { 117 select { 118 case results <- result: 119 case <-returned: 120 if result.Conn != nil { 121 result.Conn.Close() 122 } 123 } 124 }() 125 126 var ip net.IP 127 if ipv6 { 128 ip, result.error = resolver.ResolveIPv6(host) 129 } else { 130 ip, result.error = resolver.ResolveIPv4(host) 131 } 132 if result.error != nil { 133 return 134 } 135 result.resolved = true 136 137 result.Conn, result.error = dialContext(ctx, network, ip, port, options) 138 } 139 140 go startRacer(ctx, network+"4", host, false) 141 go startRacer(ctx, network+"6", host, true) 142 143 for res := range results { 144 if res.error == nil { 145 return res.Conn, nil 146 } 147 148 if !res.ipv6 { 149 primary = res 150 } else { 151 fallback = res 152 } 153 154 if primary.done && fallback.done { 155 if primary.resolved { 156 return nil, primary.error 157 } else if fallback.resolved { 158 return nil, fallback.error 159 } else { 160 return nil, primary.error 161 } 162 } 163 } 164 165 return nil, errors.New("never touched") 166 }