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  }