diff --git a/component/sniffer/dispatcher.go b/component/sniffer/dispatcher.go index fa5f0c44f..11d255bd6 100644 --- a/component/sniffer/dispatcher.go +++ b/component/sniffer/dispatcher.go @@ -2,6 +2,7 @@ package sniffer import ( "errors" + "github.com/Dreamacro/clash/component/trie" "net" CN "github.com/Dreamacro/clash/common/net" @@ -12,48 +13,96 @@ import ( var ( ErrorUnsupportedSniffer = errors.New("unsupported sniffer") + ErrorSniffFailed = errors.New("all sniffer failed") ) var Dispatcher SnifferDispatcher type SnifferDispatcher struct { - enable bool - force bool - sniffers []C.Sniffer + enable bool + force bool + sniffers []C.Sniffer + reverseDomainTree *trie.DomainTrie[struct{}] + tcpHandler func(conn *CN.BufferedConn, metadata *C.Metadata) } -func (sd *SnifferDispatcher) Tcp(conn net.Conn, metadata *C.Metadata) { +func (sd *SnifferDispatcher) forceReplace(conn *CN.BufferedConn, metadata *C.Metadata) { + host, err := sd.sniffDomain(conn, metadata) + if err != nil { + log.Debugln("[Sniffer]All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.DstIP, metadata.DstPort) + return + } else { + if sd.force && sd.inReverse(host) { + log.Debugln("[Sniffer]Skip replace host:%s", host) + return + } + } + + sd.replaceDomain(metadata, host) +} + +func (sd *SnifferDispatcher) replace(conn *CN.BufferedConn, metadata *C.Metadata) { + if metadata.Host != "" && sd.inReverse(metadata.Host) { + log.Debugln("[Sniffer]Skip Sniff domain:%s", metadata.Host) + return + } + + host, err := sd.sniffDomain(conn, metadata) + if err != nil { + log.Debugln("[Sniffer]All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.DstIP, metadata.DstPort) + return + } + + sd.replaceDomain(metadata, host) +} + +func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) { bufConn, ok := conn.(*CN.BufferedConn) if !ok { return } - if sd.force { - sd.cover(bufConn, metadata) + sd.tcpHandler(bufConn, metadata) +} + +func (sd *SnifferDispatcher) inReverse(host string) bool { + return sd.reverseDomainTree != nil && sd.reverseDomainTree.Search(host) != nil +} + +func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string) { + log.Debugln("[Sniffer]Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]", + metadata.SrcIP, metadata.SrcPort, + metadata.DstIP, metadata.DstPort, + metadata.Host, host) + + metadata.AddrType = C.AtypDomainName + metadata.Host = host + if resolver.FakeIPEnabled() { + metadata.DNSMode = C.DNSFakeIP } else { - if metadata.Host != "" { - return - } - sd.cover(bufConn, metadata) + metadata.DNSMode = C.DNSMapping } + + resolver.InsertHostByIP(metadata.DstIP, host) + metadata.DstIP = nil } func (sd *SnifferDispatcher) Enable() bool { return sd.enable } -func (sd *SnifferDispatcher) cover(conn *CN.BufferedConn, metadata *C.Metadata) { +func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Metadata) (string, error) { for _, sniffer := range sd.sniffers { if sniffer.SupportNetwork() == C.TCP { _, err := conn.Peek(1) if err != nil { - return + return "", err } bufferedLen := conn.Buffered() bytes, err := conn.Peek(bufferedLen) if err != nil { - log.Debugln("[Sniffer] the data lenght not enough") + log.Debugln("[Sniffer] the data length not enough") continue } @@ -62,39 +111,46 @@ func (sd *SnifferDispatcher) cover(conn *CN.BufferedConn, metadata *C.Metadata) log.Debugln("[Sniffer][%s] Sniff data failed %s", sniffer.Protocol(), metadata.DstIP) continue } - metadata.Host = host - metadata.AddrType = C.AtypDomainName - log.Debugln("[Sniffer][%s] %s --> %s", sniffer.Protocol(), metadata.DstIP, metadata.Host) - if resolver.FakeIPEnabled() { - metadata.DNSMode = C.DNSFakeIP - } else { - metadata.DNSMode = C.DNSMapping - } - resolver.InsertHostByIP(metadata.DstIP, host) - metadata.DstIP = nil - break + return host, nil } } + + return "", ErrorSniffFailed } -func NewSnifferDispatcher(needSniffer []C.SnifferType, force bool) (SnifferDispatcher, error) { +func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) { dispatcher := SnifferDispatcher{ - enable: true, - force: force, + enable: false, + } + + return &dispatcher, nil +} + +func NewSnifferDispatcher(needSniffer []C.SnifferType, force bool, reverses *trie.DomainTrie[struct{}]) (*SnifferDispatcher, error) { + dispatcher := SnifferDispatcher{ + enable: true, + force: force, + reverseDomainTree: reverses, } for _, snifferName := range needSniffer { sniffer, err := NewSniffer(snifferName) if err != nil { log.Errorln("Sniffer name[%s] is error", snifferName) - return SnifferDispatcher{enable: false}, err + return &SnifferDispatcher{enable: false}, err } dispatcher.sniffers = append(dispatcher.sniffers, sniffer) } - return dispatcher, nil + if force { + dispatcher.tcpHandler = dispatcher.forceReplace + } else { + dispatcher.tcpHandler = dispatcher.replace + } + + return &dispatcher, nil } func NewSniffer(name C.SnifferType) (C.Sniffer, error) { diff --git a/config/config.go b/config/config.go index 677b22382..18099a27d 100644 --- a/config/config.go +++ b/config/config.go @@ -124,6 +124,7 @@ type Sniffer struct { Enable bool Force bool Sniffers []C.SnifferType + Reverses trie.DomainTrie[struct{}] } // Experimental config @@ -218,6 +219,7 @@ type SnifferRaw struct { Enable bool `yaml:"enable" json:"enable"` Force bool `yaml:"force" json:"force"` Sniffing []string `yaml:"sniffing" json:"sniffing"` + Reverse []string `yaml:"reverses" json:"reverses"` } // Parse config @@ -289,6 +291,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Enable: false, Force: false, Sniffing: []string{}, + Reverse: []string{}, }, Profile: Profile{ StoreSelected: true, @@ -925,5 +928,12 @@ func parseSniffer(snifferRaw SnifferRaw) (*Sniffer, error) { sniffer.Sniffers = append(sniffer.Sniffers, st) } + for _, domain := range snifferRaw.Reverse { + err := sniffer.Reverses.Insert(domain, struct{}{}) + if err != nil { + return nil, fmt.Errorf("error domian[%s], error:%v", domain, err) + } + } + return sniffer, nil } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index b9785c6c7..92984ff15 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -222,12 +222,21 @@ func updateTun(tun *config.Tun, dns *config.DNS) { func updateSniffer(sniffer *config.Sniffer) { if sniffer.Enable { - var err error - SNI.Dispatcher, err = SNI.NewSnifferDispatcher(sniffer.Sniffers, sniffer.Force) + dispatcher, err := SNI.NewSnifferDispatcher(sniffer.Sniffers, sniffer.Force, &sniffer.Reverses) if err != nil { log.Warnln("initial sniffer failed, err:%v", err) } + + tunnel.UpdateSniffer(dispatcher) log.Infoln("Sniffer is loaded and working") + } else { + dispatcher, err := SNI.NewCloseSnifferDispatcher() + if err != nil { + log.Warnln("initial sniffer failed, err:%v", err) + } + + tunnel.UpdateSniffer(dispatcher) + log.Infoln("Sniffer is closed") } } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index b7d8fd69a..efacea017 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -91,6 +91,12 @@ func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provid configMux.Unlock() } +func UpdateSniffer(dispatcher *sniffer.SnifferDispatcher) { + configMux.Lock() + sniffer.Dispatcher = *dispatcher + configMux.Unlock() +} + // Mode return current mode func Mode() TunnelMode { return mode @@ -300,7 +306,7 @@ func handleTCPConn(connCtx C.ConnContext) { } if sniffer.Dispatcher.Enable() { - sniffer.Dispatcher.Tcp(connCtx.Conn(), metadata) + sniffer.Dispatcher.TCPSniff(connCtx.Conn(), metadata) } proxy, rule, err := resolveMetadata(connCtx, metadata)