github.com/chwjbn/xclash@v0.2.0/tunnel/tunnel.go (about) 1 package tunnel 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "runtime" 8 "sync" 9 "time" 10 11 "github.com/chwjbn/xclash/adapter/inbound" 12 "github.com/chwjbn/xclash/component/nat" 13 "github.com/chwjbn/xclash/component/resolver" 14 C "github.com/chwjbn/xclash/constant" 15 "github.com/chwjbn/xclash/constant/provider" 16 icontext "github.com/chwjbn/xclash/context" 17 "github.com/chwjbn/xclash/log" 18 "github.com/chwjbn/xclash/tunnel/statistic" 19 ) 20 21 var ( 22 tcpQueue = make(chan C.ConnContext, 200) 23 udpQueue = make(chan *inbound.PacketAdapter, 200) 24 natTable = nat.New() 25 rules []C.Rule 26 proxies = make(map[string]C.Proxy) 27 providers map[string]provider.ProxyProvider 28 configMux sync.RWMutex 29 30 // Outbound Rule 31 mode = Rule 32 33 // default timeout for UDP session 34 udpTimeout = 60 * time.Second 35 ) 36 37 func init() { 38 go process() 39 } 40 41 // TCPIn return fan-in queue 42 func TCPIn() chan<- C.ConnContext { 43 return tcpQueue 44 } 45 46 // UDPIn return fan-in udp queue 47 func UDPIn() chan<- *inbound.PacketAdapter { 48 return udpQueue 49 } 50 51 // Rules return all rules 52 func Rules() []C.Rule { 53 return rules 54 } 55 56 // UpdateRules handle update rules 57 func UpdateRules(newRules []C.Rule) { 58 configMux.Lock() 59 rules = newRules 60 configMux.Unlock() 61 } 62 63 // Proxies return all proxies 64 func Proxies() map[string]C.Proxy { 65 return proxies 66 } 67 68 // Providers return all compatible providers 69 func Providers() map[string]provider.ProxyProvider { 70 return providers 71 } 72 73 // UpdateProxies handle update proxies 74 func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provider.ProxyProvider) { 75 configMux.Lock() 76 proxies = newProxies 77 providers = newProviders 78 configMux.Unlock() 79 } 80 81 // Mode return current mode 82 func Mode() TunnelMode { 83 return mode 84 } 85 86 // SetMode change the mode of tunnel 87 func SetMode(m TunnelMode) { 88 mode = m 89 } 90 91 // processUDP starts a loop to handle udp packet 92 func processUDP() { 93 queue := udpQueue 94 for conn := range queue { 95 handleUDPConn(conn) 96 } 97 } 98 99 func process() { 100 numUDPWorkers := 4 101 if num := runtime.GOMAXPROCS(0); num > numUDPWorkers { 102 numUDPWorkers = num 103 } 104 for i := 0; i < numUDPWorkers; i++ { 105 go processUDP() 106 } 107 108 queue := tcpQueue 109 for conn := range queue { 110 go handleTCPConn(conn) 111 } 112 } 113 114 func needLookupIP(metadata *C.Metadata) bool { 115 return resolver.MappingEnabled() && metadata.Host == "" && metadata.DstIP != nil 116 } 117 118 func preHandleMetadata(metadata *C.Metadata) error { 119 // handle IP string on host 120 if ip := net.ParseIP(metadata.Host); ip != nil { 121 metadata.DstIP = ip 122 metadata.Host = "" 123 if ip.To4() != nil { 124 metadata.AddrType = C.AtypIPv4 125 } else { 126 metadata.AddrType = C.AtypIPv6 127 } 128 } 129 130 // preprocess enhanced-mode metadata 131 if needLookupIP(metadata) { 132 host, exist := resolver.FindHostByIP(metadata.DstIP) 133 if exist { 134 metadata.Host = host 135 metadata.AddrType = C.AtypDomainName 136 metadata.DNSMode = C.DNSMapping 137 if resolver.FakeIPEnabled() { 138 metadata.DstIP = nil 139 metadata.DNSMode = C.DNSFakeIP 140 } else if node := resolver.DefaultHosts.Search(host); node != nil { 141 // redir-host should lookup the hosts 142 metadata.DstIP = node.Data.(net.IP) 143 } 144 } else if resolver.IsFakeIP(metadata.DstIP) { 145 return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) 146 } 147 } 148 149 return nil 150 } 151 152 func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) { 153 switch mode { 154 case Direct: 155 proxy = proxies["DIRECT"] 156 case Global: 157 proxy = proxies["GLOBAL"] 158 // Rule 159 default: 160 proxy, rule, err = match(metadata) 161 } 162 return 163 } 164 165 func handleUDPConn(packet *inbound.PacketAdapter) { 166 metadata := packet.Metadata() 167 if !metadata.Valid() { 168 log.Warnln("[Metadata] not valid: %#v", metadata) 169 return 170 } 171 172 // make a fAddr if request ip is fakeip 173 var fAddr net.Addr 174 if resolver.IsExistFakeIP(metadata.DstIP) { 175 fAddr = metadata.UDPAddr() 176 } 177 178 if err := preHandleMetadata(metadata); err != nil { 179 log.Debugln("[Metadata PreHandle] error: %s", err) 180 return 181 } 182 183 key := packet.LocalAddr().String() 184 185 handle := func() bool { 186 pc := natTable.Get(key) 187 if pc != nil { 188 handleUDPToRemote(packet, pc, metadata) 189 return true 190 } 191 return false 192 } 193 194 if handle() { 195 return 196 } 197 198 lockKey := key + "-lock" 199 cond, loaded := natTable.GetOrCreateLock(lockKey) 200 201 go func() { 202 if loaded { 203 cond.L.Lock() 204 cond.Wait() 205 handle() 206 cond.L.Unlock() 207 return 208 } 209 210 defer func() { 211 natTable.Delete(lockKey) 212 cond.Broadcast() 213 }() 214 215 pCtx := icontext.NewPacketConnContext(metadata) 216 proxy, rule, err := resolveMetadata(pCtx, metadata) 217 if err != nil { 218 log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) 219 return 220 } 221 222 ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) 223 defer cancel() 224 rawPc, err := proxy.ListenPacketContext(ctx, metadata.Pure()) 225 if err != nil { 226 if rule == nil { 227 log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) 228 } else { 229 log.Warnln("[UDP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.RemoteAddress(), err.Error()) 230 } 231 return 232 } 233 pCtx.InjectPacketConn(rawPc) 234 pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule) 235 236 switch true { 237 case rule != nil: 238 log.Infoln("[UDP] %s --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String()) 239 case mode == Global: 240 log.Infoln("[UDP] %s --> %s using GLOBAL", metadata.SourceAddress(), metadata.RemoteAddress()) 241 case mode == Direct: 242 log.Infoln("[UDP] %s --> %s using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress()) 243 default: 244 log.Infoln("[UDP] %s --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress()) 245 } 246 247 go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr) 248 249 natTable.Set(key, pc) 250 handle() 251 }() 252 } 253 254 func handleTCPConn(connCtx C.ConnContext) { 255 defer connCtx.Conn().Close() 256 257 metadata := connCtx.Metadata() 258 if !metadata.Valid() { 259 log.Warnln("[Metadata] not valid: %#v", metadata) 260 return 261 } 262 263 if err := preHandleMetadata(metadata); err != nil { 264 log.Debugln("[Metadata PreHandle] error: %s", err) 265 return 266 } 267 268 proxy, rule, err := resolveMetadata(connCtx, metadata) 269 if err != nil { 270 log.Warnln("[Metadata] parse failed: %s", err.Error()) 271 return 272 } 273 274 ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) 275 defer cancel() 276 remoteConn, err := proxy.DialContext(ctx, metadata.Pure()) 277 if err != nil { 278 if rule == nil { 279 log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) 280 } else { 281 log.Warnln("[TCP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.RemoteAddress(), err.Error()) 282 } 283 return 284 } 285 remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule) 286 defer remoteConn.Close() 287 288 switch true { 289 case rule != nil: 290 log.Infoln("[TCP] %s --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String()) 291 case mode == Global: 292 log.Infoln("[TCP] %s --> %s using GLOBAL", metadata.SourceAddress(), metadata.RemoteAddress()) 293 case mode == Direct: 294 log.Infoln("[TCP] %s --> %s using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress()) 295 default: 296 log.Infoln("[TCP] %s --> %s doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.RemoteAddress()) 297 } 298 299 handleSocket(connCtx, remoteConn) 300 } 301 302 func shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { 303 return rule.ShouldResolveIP() && metadata.Host != "" && metadata.DstIP == nil 304 } 305 306 func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { 307 configMux.RLock() 308 defer configMux.RUnlock() 309 310 var resolved bool 311 312 if node := resolver.DefaultHosts.Search(metadata.Host); node != nil { 313 ip := node.Data.(net.IP) 314 metadata.DstIP = ip 315 resolved = true 316 } 317 318 for _, rule := range rules { 319 if !resolved && shouldResolveIP(rule, metadata) { 320 ip, err := resolver.ResolveIP(metadata.Host) 321 if err != nil { 322 log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error()) 323 } else { 324 log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) 325 metadata.DstIP = ip 326 } 327 resolved = true 328 } 329 330 if rule.Match(metadata) { 331 adapter, ok := proxies[rule.Adapter()] 332 if !ok { 333 continue 334 } 335 336 if metadata.NetWork == C.UDP && !adapter.SupportUDP() { 337 log.Debugln("%s UDP is not supported", adapter.Name()) 338 continue 339 } 340 return adapter, rule, nil 341 } 342 } 343 344 return proxies["DIRECT"], nil, nil 345 }