diff --git a/.github/workflows/build-windows-amd.yml b/.github/workflows/build-windows-amd.yml index ba0cb41b..918e7f15 100644 --- a/.github/workflows/build-windows-amd.yml +++ b/.github/workflows/build-windows-amd.yml @@ -42,7 +42,7 @@ jobs: echo "::set-output name=file_sha::$(git describe --tags --always)" echo "::set-output name=file_date::$(Get-Date -Format 'yyyyMMdd')" - ((Get-Content -path constant/version.go -Raw) -replace 'unknown version',$(git describe --tags --always)) | Set-Content -Path constant/version.go + ((Get-Content -path constant/version.go -Raw) -replace 'unknown version',$(Get-Date -Format 'yyyy.MM.dd')) | Set-Content -Path constant/version.go ((Get-Content -path constant/version.go -Raw) -replace 'unknown time',$(Get-Date)) | Set-Content -Path constant/version.go # go test diff --git a/README.md b/README.md index 395ede5d..9e358fc7 100644 --- a/README.md +++ b/README.md @@ -125,19 +125,60 @@ Use `curl -X POST controllerip:port/cache/fakeip/flush` to flush persistence fak ``` ### TUN configuration -Supports macOS, Linux and Windows. +Simply add the following to the main configuration: + +#### NOTE: +> auto-route and auto-detect-interface only available on macOS, Windows and Linux, receive IPv4 traffic -On Windows, you should download the [Wintun](https://www.wintun.net) driver and copy `wintun.dll` into the system32 directory. ```yaml -# Enable the TUN listener tun: enable: true - stack: gvisor # System or gVisor + stack: system # or gvisor # device: tun://utun8 # or fd://xxx, it's optional - dns-hijack: - - 0.0.0.0:53 # hijack all public + # dns-hijack: + # - 8.8.8.8:53 + # - tcp://8.8.8.8:53 + # - any:53 + # - tcp://any:53 + auto-route: true # auto set global route + auto-detect-interface: true # conflict with interface-name +``` +or +```yaml +interface-name: en0 + +tun: + enable: true + stack: system # or gvisor + # dns-hijack: + # - 8.8.8.8:53 + # - tcp://8.8.8.8:53 auto-route: true # auto set global route ``` +It's recommended to use fake-ip mode for the DNS server. + +Clash needs elevated permission to create TUN device: +```sh +$ sudo ./clash +``` +Then manually create the default route and DNS server. If your device already has some TUN device, Clash TUN might not work. In this case, fake-ip-filter may helpful. + +Enjoy! :) + +#### For Windows: +go to [https://www.wintun.net](https://www.wintun.net) and download the latest release, copy the right `wintun.dll` into the system32 directory. +```yaml +tun: + enable: true + stack: gvisor # or system + dns-hijack: + - 198.18.0.2:53 # when `fake-ip-range` is 198.18.0.1/16, should hijack 198.18.0.2:53 + auto-route: true # auto set global route for Windows + # It is recommended to use `interface-name` + auto-detect-interface: true # auto detect interface, conflict with `interface-name` +``` +Finally, open the Clash + ### Rules configuration - Support rule `GEOSITE`. - Support rule `USER-AGENT`. @@ -315,6 +356,12 @@ proxies: # skip-cert-verify: true ``` +### Sniffing configuration +Sniff TLS SNI. +```yaml +sniffing: true +``` + ### IPTABLES configuration Work on Linux OS who's supported `iptables` @@ -353,14 +400,14 @@ $ systemctl start clash ``` ### Display Process name -To display process name online by click [https://yaling888.github.io/yacd/](https://yaling888.github.io/yacd/). +To display process name online by click [http://yacd.clash-plus.cf](http://yacd.clash-plus.cf) or [https://yacd.clash-plus.cf](https://yacd.clash-plus.cf) for local. You can download the [Dashboard](https://github.com/yaling888/yacd/archive/gh-pages.zip) into Clash home directory: ```sh -cd ~/.config/clash -curl -LJ https://github.com/yaling888/yacd/archive/gh-pages.zip -o yacd-gh-pages.zip -unzip yacd-gh-pages.zip -mv yacd-gh-pages dashboard +$ cd ~/.config/clash +$ curl -LJ https://github.com/yaling888/yacd/archive/gh-pages.zip -o yacd-gh-pages.zip +$ unzip yacd-gh-pages.zip +$ mv yacd-gh-pages dashboard ``` Add to config file: diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index 61eb4571..84f26764 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -3,6 +3,7 @@ package outbound import ( "context" "net" + "net/netip" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" @@ -19,12 +20,20 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ... if err != nil { return nil, err } + tcpKeepAlive(c) + + if !metadata.DstIP.IsValid() && c.RemoteAddr() != nil { + if h, _, err := net.SplitHostPort(c.RemoteAddr().String()); err == nil { + metadata.DstIP = netip.MustParseAddr(h) + } + } + return NewConn(c, d), nil } // ListenPacketContext implements C.ProxyAdapter -func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { +func (d *Direct) ListenPacketContext(ctx context.Context, _ *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { opts = append(opts, dialer.WithDirect()) pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...) if err != nil { diff --git a/config/config.go b/config/config.go index 9a28ff97..bb39c5f9 100644 --- a/config/config.go +++ b/config/config.go @@ -41,6 +41,7 @@ type General struct { Mode T.TunnelMode `json:"mode"` LogLevel log.LogLevel `json:"log-level"` IPv6 bool `json:"ipv6"` + Sniffing bool `json:"sniffing"` Interface string `json:"-"` RoutingMark int `json:"-"` Tun Tun `json:"tun"` @@ -99,11 +100,12 @@ type Profile struct { // Tun config type Tun struct { - Enable bool `yaml:"enable" json:"enable"` - Device string `yaml:"device" json:"device"` - Stack C.TUNStack `yaml:"stack" json:"stack"` - DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"` - AutoRoute bool `yaml:"auto-route" json:"auto-route"` + Enable bool `yaml:"enable" json:"enable"` + Device string `yaml:"device" json:"device"` + Stack C.TUNStack `yaml:"stack" json:"stack"` + DNSHijack []C.DNSUrl `yaml:"dns-hijack" json:"dns-hijack"` + AutoRoute bool `yaml:"auto-route" json:"auto-route"` + AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` } // Script config @@ -130,7 +132,6 @@ type Experimental struct{} // Config is clash config manager type Config struct { General *General - Tun *Tun IPTables *IPTables Mitm *Mitm DNS *DNS @@ -168,14 +169,6 @@ type RawFallbackFilter struct { GeoSite []string `yaml:"geosite"` } -type RawTun struct { - Enable bool `yaml:"enable" json:"enable"` - Device string `yaml:"device" json:"device"` - Stack C.TUNStack `yaml:"stack" json:"stack"` - DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"` - AutoRoute bool `yaml:"auto-route" json:"auto-route"` -} - type RawMitm struct { Hosts []string `yaml:"hosts" json:"hosts"` Rules []string `yaml:"rules" json:"rules"` @@ -199,11 +192,12 @@ type RawConfig struct { Secret string `yaml:"secret"` Interface string `yaml:"interface-name"` RoutingMark int `yaml:"routing-mark"` + Sniffing bool `yaml:"sniffing"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` Hosts map[string]string `yaml:"hosts"` DNS RawDNS `yaml:"dns"` - Tun RawTun `yaml:"tun"` + Tun Tun `yaml:"tun"` IPTables IPTables `yaml:"iptables"` MITM RawMitm `yaml:"mitm"` Experimental Experimental `yaml:"experimental"` @@ -228,6 +222,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { // config with default value rawCfg := &RawConfig{ AllowLan: false, + Sniffing: false, BindAddress: "*", Mode: T.Rule, Authentication: []string{}, @@ -236,12 +231,26 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Rule: []string{}, Proxy: []map[string]any{}, ProxyGroup: []map[string]any{}, - Tun: RawTun{ - Enable: false, - Device: "", - Stack: C.TunGvisor, - DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query - AutoRoute: true, + Tun: Tun{ + Enable: false, + Device: "", + Stack: C.TunGvisor, + DNSHijack: []C.DNSUrl{ // default hijack all dns lookup + { + Network: "udp", + AddrPort: C.DNSAddrPort{ + AddrPort: netip.MustParseAddrPort("0.0.0.0:53"), + }, + }, + { + Network: "tcp", + AddrPort: C.DNSAddrPort{ + AddrPort: netip.MustParseAddrPort("0.0.0.0:53"), + }, + }, + }, + AutoRoute: false, + AutoDetectInterface: false, }, IPTables: IPTables{ Enable: false, @@ -263,7 +272,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { "223.5.5.5", }, NameServer: []string{ // default if user not set - "https://doh.pub/dns-query", + "https://120.53.53.53/dns-query", "tls://223.5.5.5:853", }, }, @@ -296,14 +305,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } config.General = general - tunCfg, err := parseTun(rawCfg.Tun, config.General) - if err != nil { - return nil, err - } - config.Tun = tunCfg - - dialer.DefaultInterface.Store(config.General.Interface) - proxies, providers, err := parseProxies(rawCfg) if err != nil { return nil, err @@ -357,6 +358,19 @@ func parseGeneral(cfg *RawConfig) (*General, error) { } } + if cfg.Tun.Enable && cfg.Tun.AutoDetectInterface { + outboundInterface, err := commons.GetAutoDetectInterface() + if err != nil && cfg.Interface == "" { + return nil, fmt.Errorf("get auto detect interface fail: %w", err) + } + + if outboundInterface != "" { + cfg.Interface = outboundInterface + } + } + + dialer.DefaultInterface.Store(cfg.Interface) + return &General{ Inbound: Inbound{ Port: cfg.Port, @@ -378,6 +392,8 @@ func parseGeneral(cfg *RawConfig) (*General, error) { IPv6: cfg.IPv6, Interface: cfg.Interface, RoutingMark: cfg.RoutingMark, + Sniffing: cfg.Sniffing, + Tun: cfg.Tun, }, nil } @@ -826,7 +842,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.R } func parseAuthentication(rawRecords []string) []auth.AuthUser { - users := []auth.AuthUser{} + var users []auth.AuthUser for _, line := range rawRecords { if user, pass, found := strings.Cut(line, ":"); found { users = append(users, auth.AuthUser{User: user, Pass: pass}) @@ -835,40 +851,6 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser { return users } -func parseTun(rawTun RawTun, general *General) (*Tun, error) { - if (rawTun.Enable || general.TProxyPort != 0) && general.Interface == "" { - autoDetectInterfaceName, err := commons.GetAutoDetectInterface() - if err != nil || autoDetectInterfaceName == "" { - return nil, fmt.Errorf("can not find auto detect interface: %w. you must be detect `interface-name` if tun set to enable or `tproxy-port` isn't zore", err) - } - - general.Interface = autoDetectInterfaceName - } - - var dnsHijack []netip.AddrPort - - for _, d := range rawTun.DNSHijack { - if _, after, ok := strings.Cut(d, "://"); ok { - d = after - } - - addrPort, err := netip.ParseAddrPort(d) - if err != nil { - return nil, fmt.Errorf("parse dns-hijack url error: %w", err) - } - - dnsHijack = append(dnsHijack, addrPort) - } - - return &Tun{ - Enable: rawTun.Enable, - Device: rawTun.Device, - Stack: rawTun.Stack, - DNSHijack: dnsHijack, - AutoRoute: rawTun.AutoRoute, - }, nil -} - func parseScript(script Script) error { mainCode := script.MainCode shortcutsCode := script.ShortcutsCode diff --git a/config/updateGeo.go b/config/updateGeo.go new file mode 100644 index 00000000..77fd9232 --- /dev/null +++ b/config/updateGeo.go @@ -0,0 +1,69 @@ +package config + +import ( + "fmt" + "os" + "runtime" + + "github.com/Dreamacro/clash/component/geodata" + _ "github.com/Dreamacro/clash/component/geodata/standard" + C "github.com/Dreamacro/clash/constant" + + "github.com/oschwald/geoip2-golang" +) + +func UpdateGeoDatabases() error { + var ( + tmpMMDB = C.Path.Resolve("temp_country.mmdb") + tmpGeoSite = C.Path.Resolve("temp_geosite.dat") + ) + + if err := downloadMMDB(tmpMMDB); err != nil { + return fmt.Errorf("can't download MMDB database file: %w", err) + } + + if err := verifyMMDB(tmpMMDB); err != nil { + _ = os.Remove(tmpMMDB) + return fmt.Errorf("invalid MMDB database file, %w", err) + } + + if err := os.Rename(tmpMMDB, C.Path.MMDB()); err != nil { + return fmt.Errorf("can't rename MMDB database file: %w", err) + } + + if err := downloadGeoSite(tmpGeoSite); err != nil { + return fmt.Errorf("can't download GeoSite database file: %w", err) + } + + if err := verifyGeoSite(tmpGeoSite); err != nil { + _ = os.Remove(tmpGeoSite) + return fmt.Errorf("invalid GeoSite database file, %w", err) + } + + if err := os.Rename(tmpGeoSite, C.Path.GeoSite()); err != nil { + return fmt.Errorf("can't rename GeoSite database file: %w", err) + } + + return nil +} + +func verifyMMDB(path string) error { + instance, err := geoip2.Open(path) + if err == nil { + _ = instance.Close() + } + return err +} + +func verifyGeoSite(path string) error { + geoLoader, err := geodata.GetGeoDataLoader("standard") + if err != nil { + return err + } + + _, err = geoLoader.LoadSite(path, "cn") + + runtime.GC() + + return err +} diff --git a/constant/metadata.go b/constant/metadata.go index 2e5332bb..bdca0320 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -101,8 +101,8 @@ func (m *Metadata) Resolved() bool { // Pure is used to solve unexpected behavior // when dialing proxy connection in DNSMapping mode. -func (m *Metadata) Pure() *Metadata { - if m.DNSMode == DNSMapping && m.DstIP.IsValid() { +func (m *Metadata) Pure(isMitmOutbound bool) *Metadata { + if !isMitmOutbound && m.DNSMode == DNSMapping && m.DstIP.IsValid() { copyM := *m copyM.Host = "" if copyM.DstIP.Is4() { diff --git a/constant/tun.go b/constant/tun.go index 10e24861..ddd65d71 100644 --- a/constant/tun.go +++ b/constant/tun.go @@ -3,7 +3,11 @@ package constant import ( "encoding/json" "errors" + "net/netip" + "strconv" "strings" + + "golang.org/x/exp/slices" ) var StackTypeMapping = map[string]TUNStack{ @@ -40,7 +44,7 @@ func (e TUNStack) MarshalYAML() (any, error) { // UnmarshalJSON unserialize TUNStack with json func (e *TUNStack) UnmarshalJSON(data []byte) error { var tp string - json.Unmarshal(data, &tp) + _ = json.Unmarshal(data, &tp) mode, exist := StackTypeMapping[strings.ToUpper(tp)] if !exist { return errors.New("invalid tun stack") @@ -64,3 +68,113 @@ func (e TUNStack) String() string { return "unknown" } } + +type DNSAddrPort struct { + netip.AddrPort +} + +func (p *DNSAddrPort) UnmarshalText(text []byte) error { + if len(text) == 0 { + *p = DNSAddrPort{} + return nil + } + + addrPort := string(text) + if strings.HasPrefix(addrPort, "any") { + _, port, _ := strings.Cut(addrPort, "any") + addrPort = "0.0.0.0" + port + } + + ap, err := netip.ParseAddrPort(addrPort) + *p = DNSAddrPort{AddrPort: ap} + return err +} + +func (p DNSAddrPort) String() string { + addrPort := p.AddrPort.String() + if p.AddrPort.Addr().IsUnspecified() { + addrPort = "any:" + strconv.Itoa(int(p.AddrPort.Port())) + } + return addrPort +} + +type DNSUrl struct { + Network string + AddrPort DNSAddrPort +} + +func (d *DNSUrl) UnmarshalYAML(unmarshal func(any) error) error { + var text string + if err := unmarshal(&text); err != nil { + return err + } + + text = strings.ToLower(text) + network := "udp" + if before, after, found := strings.Cut(text, "://"); found { + network = before + text = after + } + + if network != "udp" && network != "tcp" { + return errors.New("invalid dns url schema") + } + + ap := &DNSAddrPort{} + if err := ap.UnmarshalText([]byte(text)); err != nil { + return err + } + + *d = DNSUrl{Network: network, AddrPort: *ap} + + return nil +} + +func (d DNSUrl) MarshalYAML() (any, error) { + return d.String(), nil +} + +func (d *DNSUrl) UnmarshalJSON(data []byte) error { + var text string + if err := json.Unmarshal(data, &text); err != nil { + return err + } + + text = strings.ToLower(text) + network := "udp" + if before, after, found := strings.Cut(text, "://"); found { + network = before + text = after + } + + if network != "udp" && network != "tcp" { + return errors.New("invalid dns url schema") + } + + ap := &DNSAddrPort{} + if err := ap.UnmarshalText([]byte(text)); err != nil { + return err + } + + *d = DNSUrl{Network: network, AddrPort: *ap} + + return nil +} + +func (d DNSUrl) MarshalJSON() ([]byte, error) { + return json.Marshal(d.String()) +} + +func (d DNSUrl) String() string { + return d.Network + "://" + d.AddrPort.String() +} + +func RemoveDuplicateDNSUrl(slice []DNSUrl) []DNSUrl { + slices.SortFunc[DNSUrl](slice, func(a, b DNSUrl) bool { + return a.Network < b.Network || (a.Network == b.Network && a.AddrPort.Addr().Less(b.AddrPort.Addr())) + }) + + return slices.CompactFunc[[]DNSUrl, DNSUrl](slice, func(a, b DNSUrl) bool { + return a.Network == b.Network && a.AddrPort == b.AddrPort + }) +} diff --git a/go.mod b/go.mod index 5016499f..9619cc8d 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/gofrs/uuid v4.2.0+incompatible github.com/gorilla/websocket v1.5.0 github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f - github.com/miekg/dns v1.1.48 + github.com/miekg/dns v1.1.49 github.com/oschwald/geoip2-golang v1.7.0 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.1 @@ -18,17 +18,18 @@ require ( go.etcd.io/bbolt v1.3.6 go.uber.org/atomic v1.9.0 go.uber.org/automaxprocs v1.5.1 - golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 - golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 + golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 + golang.org/x/exp v0.0.0-20220428152302-39d4317da171 + golang.org/x/net v0.0.0-20220513224357-95641704303c + golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 + golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab golang.org/x/time v0.0.0-20220411224347-583f2d630306 golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e google.golang.org/protobuf v1.28.0 gopkg.in/yaml.v2 v2.4.0 - gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150 + gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38 ) require ( diff --git a/go.sum b/go.sum index 1b8a0eb8..a4aebf87 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,8 @@ github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcK github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= -github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ= -github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8= +github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8= github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ= github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y= @@ -80,8 +80,10 @@ go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8= -golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c= +golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4= +golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4= golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= @@ -97,11 +99,12 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0= +golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -122,8 +125,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY= +golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -159,5 +162,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150 h1:bspdBY1iCLtW6JXold8yhXHkAiE9UoWfmHShNkTc9JA= -gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI= +gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38 h1:a4PsEqiJG7UmtAMcEtxgLcscq49mqpjZgBAi82+2Y1c= +gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 4f9bf833..90845360 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -84,10 +84,9 @@ func ApplyConfig(cfg *config.Config, force bool) { updateHosts(cfg.Hosts) updateMitm(cfg.Mitm) updateProfile(cfg) - updateDNS(cfg.DNS, cfg.Tun) + updateDNS(cfg.DNS, cfg.General.Tun) updateGeneral(cfg.General, force) updateIPTables(cfg) - updateTun(cfg.Tun, cfg.DNS) updateExperimental(cfg) log.SetLevel(cfg.General.LogLevel) @@ -96,8 +95,8 @@ func ApplyConfig(cfg *config.Config, force bool) { func GetGeneral() *config.General { ports := P.GetPorts() authenticator := []string{} - if auth := authStore.Authenticator(); auth != nil { - authenticator = auth.Users() + if authM := authStore.Authenticator(); authM != nil { + authenticator = authM.Users() } general := &config.General{ @@ -115,15 +114,16 @@ func GetGeneral() *config.General { Mode: tunnel.Mode(), LogLevel: log.Level(), IPv6: !resolver.DisableIPv6, + Sniffing: tunnel.Sniffing(), Tun: P.GetTunConf(), } return general } -func updateExperimental(c *config.Config) {} +func updateExperimental(_ *config.Config) {} -func updateDNS(c *config.DNS, t *config.Tun) { +func updateDNS(c *config.DNS, t config.Tun) { cfg := dns.Config{ Main: c.NameServer, Fallback: c.Fallback, @@ -174,6 +174,10 @@ func updateDNS(c *config.DNS, t *config.Tun) { } dns.ReCreateServer("", nil, nil) } + + if cfg.Pool != nil { + P.SetTunAddressPrefix(cfg.Pool.IPNet()) + } } func updateHosts(tree *trie.DomainTrie[netip.Addr]) { @@ -192,15 +196,6 @@ func updateRuleProviders(providers map[string]C.Rule) { S.UpdateRuleProviders(providers) } -func updateTun(tun *config.Tun, dns *config.DNS) { - var tunAddressPrefix *netip.Prefix - if dns.FakeIPRange != nil { - tunAddressPrefix = dns.FakeIPRange.IPNet() - } - - P.ReCreateTun(tun, tunAddressPrefix, tunnel.TCPIn(), tunnel.UDPIn()) -} - func updateGeneral(general *config.General, force bool) { tunnel.SetMode(general.Mode) resolver.DisableIPv6 = !general.IPv6 @@ -229,6 +224,11 @@ func updateGeneral(general *config.General, force bool) { bindAddress := general.BindAddress P.SetBindAddress(bindAddress) + sniffing := general.Sniffing + tunnel.SetSniffing(sniffing) + + log.Infoln("Use TLS SNI sniffer: %v", sniffing) + tcpIn := tunnel.TCPIn() udpIn := tunnel.UDPIn() @@ -238,6 +238,7 @@ func updateGeneral(general *config.General, force bool) { P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn) P.ReCreateMixed(general.MixedPort, tcpIn, udpIn) P.ReCreateMitm(general.MitmPort, tcpIn) + P.ReCreateTun(&general.Tun, tcpIn, udpIn) } func updateUsers(users []auth.AuthUser) { @@ -279,7 +280,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) { continue } - selector.Set(selected) + _ = selector.Set(selected) } } diff --git a/hub/route/configs.go b/hub/route/configs.go index c9a4e833..1a299160 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -1,15 +1,17 @@ package route import ( + "encoding/json" "net/http" - "net/netip" "path/filepath" + "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/hub/executor" P "github.com/Dreamacro/clash/listener" + "github.com/Dreamacro/clash/listener/tun/ipstack/commons" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" @@ -26,26 +28,28 @@ func configRouter() http.Handler { } type configSchema struct { - Port *int `json:"port"` - SocksPort *int `json:"socks-port"` - RedirPort *int `json:"redir-port"` - TProxyPort *int `json:"tproxy-port"` - MixedPort *int `json:"mixed-port"` - MitmPort *int `json:"mitm-port"` - AllowLan *bool `json:"allow-lan"` - BindAddress *string `json:"bind-address"` - Mode *tunnel.TunnelMode `json:"mode"` - LogLevel *log.LogLevel `json:"log-level"` - IPv6 *bool `json:"ipv6"` - Tun *tunConfigSchema `json:"tun"` + Port *int `json:"port,omitempty"` + SocksPort *int `json:"socks-port,omitempty"` + RedirPort *int `json:"redir-port,omitempty"` + TProxyPort *int `json:"tproxy-port,omitempty"` + MixedPort *int `json:"mixed-port,omitempty"` + MitmPort *int `json:"mitm-port,omitempty"` + AllowLan *bool `json:"allow-lan,omitempty"` + BindAddress *string `json:"bind-address,omitempty"` + Mode *tunnel.TunnelMode `json:"mode,omitempty"` + LogLevel *log.LogLevel `json:"log-level,omitempty"` + IPv6 *bool `json:"ipv6,omitempty"` + Sniffing *bool `json:"sniffing,omitempty"` + Tun *tunConfigSchema `json:"tun,omitempty"` } type tunConfigSchema struct { - Enable *bool `json:"enable"` - Device *string `json:"device"` - Stack *constant.TUNStack `json:"stack"` - DNSHijack *[]netip.AddrPort `json:"dns-hijack"` - AutoRoute *bool `json:"auto-route"` + Enable *bool `json:"enable,omitempty"` + Device *string `json:"device,omitempty"` + Stack *constant.TUNStack `json:"stack,omitempty"` + DNSHijack *[]constant.DNSUrl `json:"dns-hijack,omitempty"` + AutoRoute *bool `json:"auto-route,omitempty"` + AutoDetectInterface *bool `json:"auto-detect-interface,omitempty"` } func getConfigs(w http.ResponseWriter, r *http.Request) { @@ -101,6 +105,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { resolver.DisableIPv6 = !*general.IPv6 } + if general.Sniffing != nil { + tunnel.SetSniffing(*general.Sniffing) + } + if general.Tun != nil { tunSchema := general.Tun tunConf := P.GetTunConf() @@ -120,10 +128,26 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { if tunSchema.AutoRoute != nil { tunConf.AutoRoute = *tunSchema.AutoRoute } + if tunSchema.AutoDetectInterface != nil { + tunConf.AutoDetectInterface = *tunSchema.AutoDetectInterface + } - P.ReCreateTun(&tunConf, nil, tcpIn, udpIn) + if dialer.DefaultInterface.Load() == "" && tunConf.Enable { + outboundInterface, err := commons.GetAutoDetectInterface() + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError("Get auto detect interface fail: "+err.Error())) + return + } + dialer.DefaultInterface.Store(outboundInterface) + } + + P.ReCreateTun(&tunConf, tcpIn, udpIn) } + msg, _ := json.Marshal(general) + log.Warnln("[REST-API] patch config by: %s", string(msg)) + render.NoContent(w, r) } @@ -151,6 +175,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, newError(err.Error())) return } + log.Warnln("[REST-API] update config by payload") } else { if req.Path == "" { req.Path = constant.Path.Config() @@ -167,6 +192,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, newError(err.Error())) return } + log.Warnln("[REST-API] reload config from path: %s", req.Path) } executor.ApplyConfig(cfg, force) diff --git a/hub/route/configsGeo.go b/hub/route/configsGeo.go new file mode 100644 index 00000000..a38ab03a --- /dev/null +++ b/hub/route/configsGeo.go @@ -0,0 +1,64 @@ +package route + +import ( + "net/http" + "sync" + + "github.com/Dreamacro/clash/config" + "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/hub/executor" + "github.com/Dreamacro/clash/log" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +var ( + updatingGeo bool + updateGeoMux sync.Mutex +) + +func configGeoRouter() http.Handler { + r := chi.NewRouter() + r.Post("/", updateGeoDatabases) + return r +} + +func updateGeoDatabases(w http.ResponseWriter, r *http.Request) { + updateGeoMux.Lock() + + if updatingGeo { + updateGeoMux.Unlock() + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError("updating...")) + return + } + + updatingGeo = true + updateGeoMux.Unlock() + + go func() { + defer func() { + updatingGeo = false + }() + + log.Warnln("[REST-API] updating GEO databases...") + + if err := config.UpdateGeoDatabases(); err != nil { + log.Errorln("[REST-API] update GEO databases failed: %v", err) + return + } + + cfg, err := executor.ParseWithPath(constant.Path.Config()) + if err != nil { + log.Errorln("[REST-API] update GEO databases failed: %v", err) + return + } + + log.Warnln("[REST-API] update GEO databases successful, apply config...") + + executor.ApplyConfig(cfg, false) + }() + + render.NoContent(w, r) +} diff --git a/hub/route/server.go b/hub/route/server.go index 99575415..2ab6afe6 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -67,6 +67,7 @@ func Start(addr string, secret string) { r.Get("/traffic", traffic) r.Get("/version", version) r.Mount("/configs", configRouter()) + r.Mount("/configs/geo", configGeoRouter()) r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) r.Mount("/connections", connectionRouter()) diff --git a/listener/http/upgrade.go b/listener/http/upgrade.go index 4737db48..5c2dae89 100644 --- a/listener/http/upgrade.go +++ b/listener/http/upgrade.go @@ -13,7 +13,15 @@ import ( ) func isUpgradeRequest(req *http.Request) bool { - return strings.EqualFold(req.Header.Get("Connection"), "Upgrade") + for _, header := range req.Header["Connection"] { + for _, elm := range strings.Split(header, ",") { + if strings.EqualFold(strings.TrimSpace(elm), "Upgrade") { + return true + } + } + } + + return false } func HandleUpgrade(localConn net.Conn, serverConn *N.BufferedConn, request *http.Request, in chan<- C.ConnContext) (resp *http.Response) { diff --git a/listener/listener.go b/listener/listener.go index e2a182ba..c9913a22 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -8,7 +8,6 @@ import ( "net" "net/netip" "os" - "sort" "strconv" "sync" "time" @@ -36,6 +35,7 @@ var ( bindAddress = "*" lastTunConf *config.Tun lastTunAddressPrefix *netip.Prefix + tunAddressPrefix *netip.Prefix socksListener *socks.Listener socksUDPListener *socks.UDPListener @@ -70,11 +70,24 @@ type Ports struct { func GetTunConf() config.Tun { if lastTunConf == nil { + addrPort := C.DNSAddrPort{ + AddrPort: netip.MustParseAddrPort("0.0.0.0:53"), + } return config.Tun{ - Enable: false, - Stack: C.TunGvisor, - DNSHijack: []netip.AddrPort{netip.MustParseAddrPort("0.0.0.0:53")}, - AutoRoute: true, + Enable: false, + Stack: C.TunGvisor, + DNSHijack: []C.DNSUrl{ // default hijack all dns query + { + Network: "udp", + AddrPort: addrPort, + }, + { + Network: "tcp", + AddrPort: addrPort, + }, + }, + AutoRoute: true, + AutoDetectInterface: false, } } return *lastTunConf @@ -96,6 +109,10 @@ func SetBindAddress(host string) { bindAddress = host } +func SetTunAddressPrefix(tunAddress *netip.Prefix) { + tunAddressPrefix = tunAddress +} + func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) { httpMux.Lock() defer httpMux.Unlock() @@ -335,7 +352,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address()) } -func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { +func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { tunMux.Lock() defer tunMux.Unlock() @@ -350,6 +367,8 @@ func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan tunAddressPrefix = lastTunAddressPrefix } + tunConf.DNSHijack = C.RemoveDuplicateDNSUrl(tunConf.DNSHijack) + if tunStackListener != nil { if !hasTunConfigChange(tunConf, tunAddressPrefix) { return @@ -519,14 +538,6 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo return true } - sort.Slice(lastTunConf.DNSHijack, func(i, j int) bool { - return lastTunConf.DNSHijack[i].Addr().Less(lastTunConf.DNSHijack[j].Addr()) - }) - - sort.Slice(tunConf.DNSHijack, func(i, j int) bool { - return tunConf.DNSHijack[i].Addr().Less(tunConf.DNSHijack[j].Addr()) - }) - for i, dns := range tunConf.DNSHijack { if dns != lastTunConf.DNSHijack[i] { return true @@ -536,7 +547,8 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo if lastTunConf.Enable != tunConf.Enable || lastTunConf.Device != tunConf.Device || lastTunConf.Stack != tunConf.Stack || - lastTunConf.AutoRoute != tunConf.AutoRoute { + lastTunConf.AutoRoute != tunConf.AutoRoute || + lastTunConf.AutoDetectInterface != tunConf.AutoDetectInterface { return true } diff --git a/listener/tun/ipstack/commons/dns.go b/listener/tun/ipstack/commons/dns.go index 667c2642..bef1fb4e 100644 --- a/listener/tun/ipstack/commons/dns.go +++ b/listener/tun/ipstack/commons/dns.go @@ -5,15 +5,16 @@ import ( "time" "github.com/Dreamacro/clash/component/resolver" + C "github.com/Dreamacro/clash/constant" D "github.com/miekg/dns" ) const DefaultDnsReadTimeout = time.Second * 10 -func ShouldHijackDns(dnsAdds []netip.AddrPort, targetAddr netip.AddrPort) bool { - for _, addrPort := range dnsAdds { - if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) { +func ShouldHijackDns(dnsHijack []C.DNSUrl, targetAddr netip.AddrPort, network string) bool { + for _, dns := range dnsHijack { + if dns.Network == network && (dns.AddrPort.AddrPort == targetAddr || (dns.AddrPort.Addr().IsUnspecified() && dns.AddrPort.Port() == targetAddr.Port())) { return true } } diff --git a/listener/tun/ipstack/commons/router.go b/listener/tun/ipstack/commons/router.go index 1cebfa98..d378cef7 100644 --- a/listener/tun/ipstack/commons/router.go +++ b/listener/tun/ipstack/commons/router.go @@ -3,6 +3,7 @@ package commons import ( "fmt" "net" + "sync" "time" "github.com/Dreamacro/clash/component/dialer" @@ -12,7 +13,10 @@ import ( var ( defaultRoutes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"} - defaultInterfaceMonitorDuration = 20 * time.Second + monitorDuration = 10 * time.Second + monitorStarted = false + monitorStop = make(chan struct{}, 2) + monitorMux sync.Mutex ) func ipv4MaskString(bits int) string { @@ -24,26 +28,52 @@ func ipv4MaskString(bits int) string { return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3]) } -func defaultInterfaceChangeMonitor() { - t := time.NewTicker(defaultInterfaceMonitorDuration) +func StartDefaultInterfaceChangeMonitor() { + monitorMux.Lock() + if monitorStarted { + monitorMux.Unlock() + return + } + monitorStarted = true + monitorMux.Unlock() + + select { + case <-monitorStop: + default: + } + + t := time.NewTicker(monitorDuration) defer t.Stop() for { - <-t.C + select { + case <-t.C: + interfaceName, err := GetAutoDetectInterface() + if err != nil { + log.Warnln("[TUN] default interface monitor err: %v", err) + continue + } - interfaceName, err := GetAutoDetectInterface() - if err != nil { - log.Warnln("[TUN] default interface monitor exited, cause: %v", err) + old := dialer.DefaultInterface.Load() + if interfaceName == old { + continue + } + + dialer.DefaultInterface.Store(interfaceName) + + log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName) + case <-monitorStop: break } - - old := dialer.DefaultInterface.Load() - if interfaceName == old { - continue - } - - dialer.DefaultInterface.Store(interfaceName) - - log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName) + } +} + +func StopDefaultInterfaceChangeMonitor() { + monitorMux.Lock() + defer monitorMux.Unlock() + + if monitorStarted { + monitorStop <- struct{}{} + monitorStarted = false } } diff --git a/listener/tun/ipstack/commons/router_darwin.go b/listener/tun/ipstack/commons/router_darwin.go index 15beaf2d..5cbab1fa 100644 --- a/listener/tun/ipstack/commons/router_darwin.go +++ b/listener/tun/ipstack/commons/router_darwin.go @@ -3,13 +3,23 @@ package commons import ( "fmt" "net/netip" + "strings" "github.com/Dreamacro/clash/common/cmd" "github.com/Dreamacro/clash/listener/tun/device" ) func GetAutoDetectInterface() (string, error) { - return cmd.ExecCmd("/bin/bash -c /sbin/route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n") + rs, err := cmd.ExecCmd("/bin/bash -c /sbin/route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n") + if err != nil { + return "", err + } + + if rs == "" || strings.HasSuffix(rs, "\n") { + return "", fmt.Errorf("invalid interface name: %s", rs) + } + + return rs, nil } func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error { @@ -54,8 +64,6 @@ func configInterfaceRouting(interfaceName string, addr netip.Prefix) error { } } - go defaultInterfaceChangeMonitor() - return execRouterCmd("add", "-inet6", "2000::/3", interfaceName) } diff --git a/listener/tun/ipstack/commons/router_linux.go b/listener/tun/ipstack/commons/router_linux.go index 63479bae..06df068c 100644 --- a/listener/tun/ipstack/commons/router_linux.go +++ b/listener/tun/ipstack/commons/router_linux.go @@ -42,8 +42,6 @@ func configInterfaceRouting(interfaceName string, addr netip.Prefix) error { } } - go defaultInterfaceChangeMonitor() - return nil } diff --git a/listener/tun/ipstack/commons/router_windows.go b/listener/tun/ipstack/commons/router_windows.go index 2e5a0dea..b29b20ab 100644 --- a/listener/tun/ipstack/commons/router_windows.go +++ b/listener/tun/ipstack/commons/router_windows.go @@ -205,8 +205,6 @@ startOver: wintunInterfaceName = dev.Name() - go defaultInterfaceChangeMonitor() - return nil } diff --git a/listener/tun/ipstack/gvisor/handler.go b/listener/tun/ipstack/gvisor/handler.go index 9560937c..476374bd 100644 --- a/listener/tun/ipstack/gvisor/handler.go +++ b/listener/tun/ipstack/gvisor/handler.go @@ -20,7 +20,7 @@ var _ adapter.Handler = (*gvHandler)(nil) type gvHandler struct { gateway netip.Addr - dnsHijack []netip.AddrPort + dnsHijack []C.DNSUrl tcpIn chan<- C.ConnContext udpIn chan<- *inbound.PacketAdapter @@ -37,7 +37,7 @@ func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) { rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort) - if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) { + if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "tcp") { go func() { log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String()) @@ -111,7 +111,7 @@ func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) { payload := buf[:n] - if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) { + if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "udp") { go func() { defer func() { _ = pool.Put(buf) diff --git a/listener/tun/ipstack/gvisor/stack.go b/listener/tun/ipstack/gvisor/stack.go index 1307bc71..f6e432ca 100644 --- a/listener/tun/ipstack/gvisor/stack.go +++ b/listener/tun/ipstack/gvisor/stack.go @@ -8,6 +8,7 @@ import ( C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/listener/tun/device" "github.com/Dreamacro/clash/listener/tun/ipstack" + "github.com/Dreamacro/clash/listener/tun/ipstack/commons" "github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option" "gvisor.dev/gvisor/pkg/tcpip" @@ -25,6 +26,8 @@ type gvStack struct { } func (s *gvStack) Close() error { + commons.StopDefaultInterfaceChangeMonitor() + var err error if s.device != nil { err = s.device.Close() @@ -37,7 +40,7 @@ func (s *gvStack) Close() error { } // New allocates a new *gvStack with given options. -func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, opts ...option.Option) (ipstack.Stack, error) { +func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, opts ...option.Option) (ipstack.Stack, error) { s := &gvStack{ Stack: stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ diff --git a/listener/tun/ipstack/system/stack.go b/listener/tun/ipstack/system/stack.go index 803e5db0..fd6b7cde 100644 --- a/listener/tun/ipstack/system/stack.go +++ b/listener/tun/ipstack/system/stack.go @@ -34,6 +34,8 @@ type sysStack struct { } func (s *sysStack) Close() error { + D.StopDefaultInterfaceChangeMonitor() + defer func() { if s.device != nil { _ = s.device.Close() @@ -49,7 +51,7 @@ func (s *sysStack) Close() error { return err } -func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) { +func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) { var ( gateway = tunAddress.Masked().Addr().Next() portal = gateway.Next() @@ -91,7 +93,7 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref continue } - if D.ShouldHijackDns(dnsAddr, rAddrPort) { + if D.ShouldHijackDns(dnsAddr, rAddrPort, "tcp") { go func() { log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String()) @@ -175,7 +177,7 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref continue } - if D.ShouldHijackDns(dnsAddr, rAddrPort) { + if D.ShouldHijackDns(dnsAddr, rAddrPort, "udp") { go func() { msg, err := D.RelayDnsPacket(raw) if err != nil { diff --git a/listener/tun/tun_adapter.go b/listener/tun/tun_adapter.go index 8d861c9e..d9178f3e 100644 --- a/listener/tun/tun_adapter.go +++ b/listener/tun/tun_adapter.go @@ -38,6 +38,12 @@ func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.Con err error ) + defer func() { + if err != nil && tunDevice != nil { + _ = tunDevice.Close() + } + }() + if devName == "" { devName = generateDeviceName() } @@ -63,26 +69,22 @@ func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.Con case C.TunGvisor: err = tunDevice.UseEndpoint() if err != nil { - _ = tunDevice.Close() return nil, fmt.Errorf("can't attach endpoint to tun: %w", err) } tunStack, err = gvisor.New(tunDevice, tunConf.DNSHijack, tunAddress, tcpIn, udpIn, option.WithDefault()) if err != nil { - _ = tunDevice.Close() return nil, fmt.Errorf("can't New gvisor stack: %w", err) } case C.TunSystem: err = tunDevice.UseIOBased() if err != nil { - _ = tunDevice.Close() return nil, fmt.Errorf("can't New system stack: %w", err) } tunStack, err = system.New(tunDevice, tunConf.DNSHijack, tunAddress, tcpIn, udpIn) if err != nil { - _ = tunDevice.Close() return nil, fmt.Errorf("can't New system stack: %w", err) } default: @@ -92,10 +94,13 @@ func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.Con // setting address and routing err = commons.ConfigInterfaceAddress(tunDevice, tunAddress, mtu, autoRoute) if err != nil { - _ = tunDevice.Close() return nil, fmt.Errorf("setting interface address and routing failed: %w", err) } + if tunConf.AutoDetectInterface { + go commons.StartDefaultInterfaceChangeMonitor() + } + setAtLatest(stackType, devName) log.Infoln("TUN stack listening at: %s(%s), mtu: %d, auto route: %v, ip stack: %s", tunDevice.Name(), tunAddress.Masked().Addr().Next().String(), mtu, autoRoute, stackType) diff --git a/test/go.mod b/test/go.mod index 3733d3ac..206790f9 100644 --- a/test/go.mod +++ b/test/go.mod @@ -6,15 +6,15 @@ require ( github.com/Dreamacro/clash v1.7.2-0.20211108085948-bd2ea2b917aa github.com/docker/docker v20.10.13+incompatible github.com/docker/go-connections v0.4.0 - github.com/miekg/dns v1.1.48 + github.com/miekg/dns v1.1.49 github.com/stretchr/testify v1.7.1 - golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 + golang.org/x/net v0.0.0-20220513224357-95641704303c ) replace github.com/Dreamacro/clash => ../ require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect + github.com/Dreamacro/go-shadowsocks2 v0.1.8 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/containerd/containerd v1.6.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -26,7 +26,7 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd // indirect + github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect @@ -39,22 +39,23 @@ require ( github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect + golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 // indirect + golang.org/x/exp v0.0.0-20220428152302-39d4317da171 // indirect golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect + golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect + golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a // indirect golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect golang.org/x/tools v0.1.9 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect - golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 // indirect - golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 // indirect + golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d // indirect + golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e // indirect google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect google.golang.org/grpc v1.45.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gotest.tools/v3 v3.1.0 // indirect - gvisor.dev/gvisor v0.0.0-20220422224113-2cca6b79d9f4 // indirect + gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38 // indirect ) diff --git a/test/go.sum b/test/go.sum index dbf6ed07..6175f656 100644 --- a/test/go.sum +++ b/test/go.sum @@ -60,8 +60,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q= -github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc= +github.com/Dreamacro/go-shadowsocks2 v0.1.8 h1:Ixejp5JscEc866gAvm/l6TFd7BOBvDviKgwb1quWw3g= +github.com/Dreamacro/go-shadowsocks2 v0.1.8/go.mod h1:51y4Q6tJoCE7e8TmYXcQRqfoxPfE9Cvn79V6pB6Df7Y= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -525,8 +525,8 @@ github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd h1:efcJu2Vzz6DoSq245deWNzTz6l/gsqdphm3FjmI88/g= -github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo= +github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= @@ -597,8 +597,8 @@ github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZ github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ= -github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8= +github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -910,11 +910,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c= +golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -925,6 +924,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4= +golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1012,8 +1013,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8= -golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0= +golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1037,8 +1038,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1143,8 +1145,8 @@ golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= -golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY= +golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1244,10 +1246,10 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 h1:kgBK1EGuTIYbwoKROmsoV0FQp08gnCcVa110A4Unqhk= -golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U= -golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 h1:SEYkJAIuYAsSAPkCffOiYLtq5brBDSI+L0mRjSsvSTY= -golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4= +golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0= +golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U= +golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e h1:yV04h6Tx19uDR6LvuEbR19cDU+3QrB9LuGjtF7F5G0w= +golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e/go.mod h1:1CeiatTZwcwSFA3cAtMm8CQoroviTldnxd7DOgM/vI4= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -1413,8 +1415,8 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk= gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= -gvisor.dev/gvisor v0.0.0-20220422224113-2cca6b79d9f4 h1:CSkd548jw5hmVwdJ+JuUhMtRV56oQBER7sbkIOePP2Y= -gvisor.dev/gvisor v0.0.0-20220422224113-2cca6b79d9f4/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI= +gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38 h1:a4PsEqiJG7UmtAMcEtxgLcscq49mqpjZgBAi82+2Y1c= +gvisor.dev/gvisor v0.0.0-20220513212916-a23dc0715d38/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/transport/ssr/protocol/auth_aes128_sha1.go b/transport/ssr/protocol/auth_aes128_sha1.go index d31fb9bf..7b4da962 100644 --- a/transport/ssr/protocol/auth_aes128_sha1.go +++ b/transport/ssr/protocol/auth_aes128_sha1.go @@ -154,6 +154,9 @@ func (a *authAES128) Encode(buf *bytes.Buffer, b []byte) error { } func (a *authAES128) DecodePacket(b []byte) ([]byte, error) { + if len(b) < 4 { + return nil, errAuthAES128LengthError + } if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) { return nil, errAuthAES128ChksumError } diff --git a/tunnel/statistic/sniffing.go b/tunnel/statistic/sniffing.go index 30716a09..5fa0ae69 100644 --- a/tunnel/statistic/sniffing.go +++ b/tunnel/statistic/sniffing.go @@ -26,11 +26,9 @@ func (r *sniffing) Read(b []byte) (int, error) { func (r *sniffing) Write(b []byte) (int, error) { if r.totalWrite.Load() < 128 && r.metadata.Host == "" && (r.metadata.DstPort == "443" || r.metadata.DstPort == "8443" || r.metadata.DstPort == "993" || r.metadata.DstPort == "465" || r.metadata.DstPort == "995") { header, err := tls.SniffTLS(b) - if err != nil { - // log.Errorln("Expect no error but actually %s %s:%s:%s", err.Error(), tt.Metadata.Host, tt.Metadata.DstIP.String(), tt.Metadata.DstPort) - } else { + if err == nil { resolver.InsertHostByIP(r.metadata.DstIP, header.Domain()) - log.Warnln("use sni update host: %s ip: %s", header.Domain(), r.metadata.DstIP.String()) + log.Debugln("[SNIFFER] use sni update host: %s ip: %s", header.Domain(), r.metadata.DstIP.String()) if r.allowBreak { _ = r.Conn.Close() return 0, errors.New("sni update, break current link to avoid leaks") diff --git a/tunnel/statistic/tracker.go b/tunnel/statistic/tracker.go index b86fce6e..c8fb9bba 100644 --- a/tunnel/statistic/tracker.go +++ b/tunnel/statistic/tracker.go @@ -58,13 +58,13 @@ func (tt *tcpTracker) Close() error { } func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) C.Conn { - uuid, _ := uuid.NewV4() + uuidM, _ := uuid.NewV4() t := &tcpTracker{ Conn: conn, manager: manager, trackerInfo: &trackerInfo{ - UUID: uuid, + UUID: uuidM, Start: time.Now(), Metadata: metadata, Chain: conn.Chains(), @@ -80,7 +80,7 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R } manager.Join(t) - return NewSniffing(t, metadata, rule) + return t } type udpTracker struct { @@ -115,13 +115,13 @@ func (ut *udpTracker) Close() error { } func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker { - uuid, _ := uuid.NewV4() + uuidM, _ := uuid.NewV4() ut := &udpTracker{ PacketConn: conn, manager: manager, trackerInfo: &trackerInfo{ - UUID: uuid, + UUID: uuidM, Start: time.Now(), Metadata: metadata, Chain: conn.Chains(), diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 22e45149..6a7d0723 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -39,6 +39,9 @@ var ( // Outbound Rule mode = Rule + // sniffing switch + sniffing = false + // default timeout for UDP session udpTimeout = 60 * time.Second @@ -100,9 +103,21 @@ func SetMode(m TunnelMode) { mode = m } +func Sniffing() bool { + return sniffing +} + +func SetSniffing(s bool) { + sniffing = s +} + // SetMitmOutbound set the MITM outbound func SetMitmOutbound(outbound C.ProxyAdapter) { - mitmProxy = A.NewProxy(outbound) + if outbound != nil { + mitmProxy = A.NewProxy(outbound) + } else { + mitmProxy = nil + } } // Rewrites return all rewrites @@ -272,7 +287,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) defer cancel() - rawPc, err := proxy.ListenPacketContext(ctx, metadata.Pure()) + rawPc, err := proxy.ListenPacketContext(ctx, metadata.Pure(false)) if err != nil { if rule == nil { log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) @@ -326,14 +341,11 @@ func handleTCPConn(connCtx C.ConnContext) { return } - mtd := metadata - if proxy != mitmProxy { - mtd = metadata.Pure() - } + isMitmOutbound := proxy == mitmProxy ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout) defer cancel() - remoteConn, err := proxy.DialContext(ctx, mtd) + remoteConn, err := proxy.DialContext(ctx, metadata.Pure(isMitmOutbound)) if err != nil { if rule == nil { log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error()) @@ -343,8 +355,11 @@ func handleTCPConn(connCtx C.ConnContext) { return } - if remoteConn.Chains().Last() != "REJECT" && proxy != mitmProxy { + if remoteConn.Chains().Last() != "REJECT" && !isMitmOutbound { remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule) + if sniffing { + remoteConn = statistic.NewSniffing(remoteConn, metadata, rule) + } } defer func(remoteConn C.Conn) { @@ -352,7 +367,7 @@ func handleTCPConn(connCtx C.ConnContext) { }(remoteConn) switch true { - case proxy == mitmProxy: + case isMitmOutbound: break case rule != nil: log.Infoln("[TCP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String())