diff --git a/config/config.go b/config/config.go index 1fe0e0ea..51d1f75f 100644 --- a/config/config.go +++ b/config/config.go @@ -43,6 +43,7 @@ type General struct { IPv6 bool `json:"ipv6"` Interface string `json:"-"` RoutingMark int `json:"-"` + Tun Tun `json:"tun"` } // Inbound config diff --git a/hub/executor/executor.go b/hub/executor/executor.go index f302ce61..06a18bc2 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -116,6 +116,7 @@ func GetGeneral() *config.General { Mode: tunnel.Mode(), LogLevel: log.Level(), IPv6: !resolver.DisableIPv6, + Tun: P.GetTunConf(), } return general diff --git a/hub/route/configs.go b/hub/route/configs.go index a930c32b..c9a4e833 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -2,6 +2,7 @@ package route import ( "net/http" + "net/netip" "path/filepath" "github.com/Dreamacro/clash/component/resolver" @@ -31,12 +32,20 @@ type configSchema struct { TProxyPort *int `json:"tproxy-port"` MixedPort *int `json:"mixed-port"` MitmPort *int `json:"mitm-port"` - Tun *config.Tun `json:"tun"` 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"` +} + +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"` } func getConfigs(w http.ResponseWriter, r *http.Request) { @@ -92,6 +101,29 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { resolver.DisableIPv6 = !*general.IPv6 } + if general.Tun != nil { + tunSchema := general.Tun + tunConf := P.GetTunConf() + + if tunSchema.Enable != nil { + tunConf.Enable = *tunSchema.Enable + } + if tunSchema.Device != nil { + tunConf.Device = *tunSchema.Device + } + if tunSchema.Stack != nil { + tunConf.Stack = *tunSchema.Stack + } + if tunSchema.DNSHijack != nil { + tunConf.DNSHijack = *tunSchema.DNSHijack + } + if tunSchema.AutoRoute != nil { + tunConf.AutoRoute = *tunSchema.AutoRoute + } + + P.ReCreateTun(&tunConf, nil, tcpIn, udpIn) + } + render.NoContent(w, r) } diff --git a/listener/listener.go b/listener/listener.go index 34c43f64..b7396c68 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -8,6 +8,7 @@ import ( "net" "net/netip" "os" + "sort" "strconv" "sync" "time" @@ -15,7 +16,6 @@ import ( "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/cert" - S "github.com/Dreamacro/clash/component/script" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/listener/http" @@ -32,8 +32,10 @@ import ( ) var ( - allowLan = false - bindAddress = "*" + allowLan = false + bindAddress = "*" + lastTunConf *config.Tun + lastTunAddressPrefix *netip.Prefix socksListener *socks.Listener socksUDPListener *socks.UDPListener @@ -66,6 +68,15 @@ type Ports struct { MitmPort int `json:"mitm-port"` } +func GetTunConf() config.Tun { + if lastTunConf == nil { + return config.Tun{ + Enable: false, + } + } + return *lastTunConf +} + func AllowLan() bool { return allowLan } @@ -329,14 +340,22 @@ func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan defer func() { if err != nil { log.Errorln("Start TUN listening error: %s", err.Error()) - S.Py_Finalize() - os.Exit(2) } }() + if tunAddressPrefix == nil { + tunAddressPrefix = lastTunAddressPrefix + } + + if !hasTunConfigChange(tunConf, tunAddressPrefix) { + return + } + if tunStackListener != nil { - tunStackListener.Close() + _ = tunStackListener.Close() tunStackListener = nil + lastTunConf = nil + lastTunAddressPrefix = nil } if !tunConf.Enable { @@ -344,6 +363,12 @@ func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan } tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn) + if err != nil { + return + } + + lastTunConf = tunConf + lastTunAddressPrefix = tunAddressPrefix } func ReCreateMitm(port int, tcpIn chan<- C.ConnContext) { @@ -484,6 +509,47 @@ func genAddr(host string, port int, allowLan bool) string { return fmt.Sprintf("127.0.0.1:%d", port) } +func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) bool { + if lastTunConf == nil { + return true + } + + if len(lastTunConf.DNSHijack) != len(tunConf.DNSHijack) { + 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 + } + } + + if lastTunConf.Enable != tunConf.Enable || + lastTunConf.Device != tunConf.Device || + lastTunConf.Stack != tunConf.Stack || + lastTunConf.AutoRoute != tunConf.AutoRoute { + return true + } + + if (tunAddressPrefix != nil && lastTunAddressPrefix == nil) || (tunAddressPrefix == nil && lastTunAddressPrefix != nil) { + return true + } + + if tunAddressPrefix != nil && lastTunAddressPrefix != nil && *tunAddressPrefix != *lastTunAddressPrefix { + return true + } + + return false +} + func initCert() error { if _, err := os.Stat(C.Path.RootCA()); os.IsNotExist(err) { log.Infoln("Can't find mitm_ca.crt, start generate") diff --git a/listener/tun/ipstack/commons/router_darwin.go b/listener/tun/ipstack/commons/router_darwin.go index 4185a48a..15beaf2d 100644 --- a/listener/tun/ipstack/commons/router_darwin.go +++ b/listener/tun/ipstack/commons/router_darwin.go @@ -9,7 +9,7 @@ import ( ) func GetAutoDetectInterface() (string, error) { - return cmd.ExecCmd("bash -c route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n") + return cmd.ExecCmd("/bin/bash -c /sbin/route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n") } func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error { @@ -24,14 +24,14 @@ func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, netmask = ipv4MaskString(addr.Bits()) ) - cmdStr := fmt.Sprintf("ifconfig %s inet %s netmask %s %s", interfaceName, ip, netmask, gw) + cmdStr := fmt.Sprintf("/sbin/ifconfig %s inet %s netmask %s %s", interfaceName, ip, netmask, gw) _, err := cmd.ExecCmd(cmdStr) if err != nil { return err } - _, err = cmd.ExecCmd(fmt.Sprintf("ipconfig set %s automatic-v6", interfaceName)) + _, err = cmd.ExecCmd(fmt.Sprintf("/usr/sbin/ipconfig set %s automatic-v6", interfaceName)) if err != nil { return err } @@ -49,7 +49,7 @@ func configInterfaceRouting(interfaceName string, addr netip.Prefix) error { ) for _, destination := range routes { - if _, err := cmd.ExecCmd(fmt.Sprintf("route add -net %s %s", destination, gateway)); err != nil { + if _, err := cmd.ExecCmd(fmt.Sprintf("/sbin/route add -net %s %s", destination, gateway)); err != nil { return err } } @@ -60,6 +60,6 @@ func configInterfaceRouting(interfaceName string, addr netip.Prefix) error { } func execRouterCmd(action, inet, route string, interfaceName string) error { - _, err := cmd.ExecCmd(fmt.Sprintf("route %s %s %s -interface %s", action, inet, route, interfaceName)) + _, err := cmd.ExecCmd(fmt.Sprintf("/sbin/route %s %s %s -interface %s", action, inet, route, interfaceName)) return err } diff --git a/listener/tun/tun_adapter.go b/listener/tun/tun_adapter.go index 98b0467c..8d861c9e 100644 --- a/listener/tun/tun_adapter.go +++ b/listener/tun/tun_adapter.go @@ -143,9 +143,9 @@ func setAtLatest(stackType C.TUNStack, devName string) { switch runtime.GOOS { case "darwin": - // _, _ = cmd.ExecCmd("sysctl -w net.inet.ip.forwarding=1") - // _, _ = cmd.ExecCmd("sysctl -w net.inet6.ip6.forwarding=1") - _, _ = cmd.ExecCmd("sudo launchctl limit maxfiles 10240 unlimited") + // _, _ = cmd.ExecCmd("/usr/sbin/sysctl -w net.inet.ip.forwarding=1") + // _, _ = cmd.ExecCmd("/usr/sbin/sysctl -w net.inet6.ip6.forwarding=1") + _, _ = cmd.ExecCmd("/bin/launchctl limit maxfiles 10240 unlimited") case "windows": _, _ = cmd.ExecCmd("ipconfig /renew") case "linux":