diff --git a/README.md b/README.md index fd3d427f..a6b65d0d 100644 --- a/README.md +++ b/README.md @@ -141,16 +141,16 @@ proxies: # skip-cert-verify: true ``` -### IPTABLES auto-configuration -Only work on Linux OS who support `iptables`, Clash will auto-configuration iptables for tproxy listener when `tproxy-port` value isn't zero. +### IPTABLES configuration +Work on Linux OS who's supported `iptables` -If `TPROXY` is enabled, the `TUN` must be disabled. ```yaml # Enable the TPROXY listener tproxy-port: 9898 -# Disable the TUN listener -tun: - enable: false + +iptables: + enable: true # default is false + inbound-interface: eth0 # detect the inbound interface, default is 'lo' ``` Run Clash as a daemon. diff --git a/config/config.go b/config/config.go index 8a8ee29a..36c2223a 100644 --- a/config/config.go +++ b/config/config.go @@ -100,6 +100,12 @@ type Tun struct { AutoRoute bool `yaml:"auto-route" json:"auto-route"` } +// IPTables config +type IPTables struct { + Enable bool `yaml:"enable" json:"enable"` + InboundInterface string `yaml:"inbound-interface" json:"inbound-interface"` +} + // Experimental config type Experimental struct{} @@ -107,6 +113,7 @@ type Experimental struct{} type Config struct { General *General Tun *Tun + IPTables *IPTables DNS *DNS Experimental *Experimental Hosts *trie.DomainTrie @@ -170,6 +177,7 @@ type RawConfig struct { Hosts map[string]string `yaml:"hosts"` DNS RawDNS `yaml:"dns"` Tun RawTun `yaml:"tun"` + IPTables IPTables `yaml:"iptables"` Experimental Experimental `yaml:"experimental"` Profile Profile `yaml:"profile"` Proxy []map[string]any `yaml:"proxies"` @@ -206,6 +214,10 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query AutoRoute: true, }, + IPTables: IPTables{ + Enable: false, + InboundInterface: "lo", + }, DNS: RawDNS{ Enable: false, UseHosts: true, @@ -243,6 +255,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config.Experimental = &rawCfg.Experimental config.Profile = &rawCfg.Profile + config.IPTables = &rawCfg.IPTables general, err := parseGeneral(rawCfg) if err != nil { diff --git a/go.mod b/go.mod index 77c9c28f..07feed38 100644 --- a/go.mod +++ b/go.mod @@ -23,11 +23,11 @@ require ( golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 - golang.zx2c4.com/wireguard v0.0.0-20220310012736-ae6bc4dd64e1 - golang.zx2c4.com/wireguard/windows v0.5.3 + golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 + golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 - gvisor.dev/gvisor v0.0.0-20220315202956-f1399ecf1672 + gvisor.dev/gvisor v0.0.0-20220319025644-e785bfc153f5 ) require ( diff --git a/go.sum b/go.sum index cedd9b96..3b2daa45 100644 --- a/go.sum +++ b/go.sum @@ -148,10 +148,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-20220310012736-ae6bc4dd64e1 h1:iuQdvJn3LrXxz3Iony1qBGVS7kEy2uHYnnjHsVbzq/s= -golang.zx2c4.com/wireguard v0.0.0-20220310012736-ae6bc4dd64e1/go.mod h1:TjUWrnD5ATh7bFvmm/ALEJZQ4ivKbETb6pmyj1vUoNI= -golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= -golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= +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= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= @@ -163,5 +163,5 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gvisor.dev/gvisor v0.0.0-20220315202956-f1399ecf1672 h1:aXIFpjZYl3zv2rQyr4rSit5Uq0k7BVXC8lJaDa4Cg7M= -gvisor.dev/gvisor v0.0.0-20220315202956-f1399ecf1672/go.mod h1:V4WNP2Uwtx69eOhvLDSQ734EaTJTaBI3P8KgRAlROsg= +gvisor.dev/gvisor v0.0.0-20220319025644-e785bfc153f5 h1:a3DiynlmTM9IGW+twgZSDhYNGcnxvU/+pbzrl1xMcJ4= +gvisor.dev/gvisor v0.0.0-20220319025644-e785bfc153f5/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index dd4f4acf..24b8693e 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -80,8 +80,8 @@ func ApplyConfig(cfg *config.Config, force bool) { updateProfile(cfg) updateDNS(cfg.DNS, cfg.Tun) updateGeneral(cfg.General, force) - updateIPTables(cfg.DNS, cfg.General.TProxyPort, cfg.General.Interface, cfg.Tun.Enable) - updateTun(cfg.Tun, cfg.DNS.FakeIPRange.IPNet().String()) + updateIPTables(cfg) + updateTun(cfg.Tun, cfg.DNS) updateExperimental(cfg) log.SetLevel(cfg.General.LogLevel) @@ -176,7 +176,12 @@ func updateRules(rules []C.Rule) { tunnel.UpdateRules(rules) } -func updateTun(tun *config.Tun, tunAddressPrefix string) { +func updateTun(tun *config.Tun, dns *config.DNS) { + var tunAddressPrefix string + if dns.FakeIPRange != nil { + tunAddressPrefix = dns.FakeIPRange.IPNet().String() + } + P.ReCreateTun(tun, tunAddressPrefix, tunnel.TCPIn(), tunnel.UDPIn()) } @@ -261,51 +266,67 @@ func patchSelectGroup(proxies map[string]C.Proxy) { } } -func updateIPTables(dns *config.DNS, tProxyPort int, interfaceName string, tunEnable bool) { - tproxy.CleanUpTProxyLinuxIPTables() +func updateIPTables(cfg *config.Config) { + tproxy.CleanupTProxyIPTables() - if runtime.GOOS != "linux" || tProxyPort == 0 { + iptables := cfg.IPTables + if runtime.GOOS != "linux" || !iptables.Enable { return } var err error defer func() { if err != nil { - log.Errorln("Setting iptables failed: %s", err.Error()) + log.Errorln("[IPTABLES] setting iptables failed: %s", err.Error()) os.Exit(2) } }() - if !dns.Enable || dns.Listen == "" { + var ( + inboundInterface = "lo" + tProxyPort = cfg.General.TProxyPort + dnsCfg = cfg.DNS + ) + + if tProxyPort == 0 { + err = fmt.Errorf("tproxy-port must be greater than zero") + return + } + + if !dnsCfg.Enable { err = fmt.Errorf("DNS server must be enable") return } - if tunEnable { - err = fmt.Errorf("TUN device must be disabe") - return - } - - _, dnsPortStr, err := net.SplitHostPort(dns.Listen) - if dnsPortStr == "0" || dnsPortStr == "" || err != nil { - return - } - - dnsPort, err := strconv.Atoi(dnsPortStr) + _, dnsPortStr, err := net.SplitHostPort(dnsCfg.Listen) if err != nil { + err = fmt.Errorf("DNS server must be enable") return } + dnsPort, err := strconv.ParseUint(dnsPortStr, 10, 16) + if err != nil { + err = fmt.Errorf("DNS server must be enable") + return + } + + if iptables.InboundInterface != "" { + inboundInterface = iptables.InboundInterface + } + if dialer.DefaultRoutingMark.Load() == 0 { dialer.DefaultRoutingMark.Store(2158) } - err = tproxy.SetTProxyLinuxIPTables(interfaceName, tProxyPort, dnsPort) + err = tproxy.SetTProxyIPTables(inboundInterface, uint16(tProxyPort), uint16(dnsPort)) + if err != nil { + return + } + + log.Infoln("[IPTABLES] Setting iptables completed") } func Cleanup() { P.Cleanup() - if runtime.GOOS == "linux" { - tproxy.CleanUpTProxyLinuxIPTables() - } + tproxy.CleanupTProxyIPTables() } diff --git a/listener/tproxy/tproxy_linux_iptables.go b/listener/tproxy/tproxy_linux_iptables.go index ae6ad6a8..fbd0d2d2 100644 --- a/listener/tproxy/tproxy_linux_iptables.go +++ b/listener/tproxy/tproxy_linux_iptables.go @@ -11,9 +11,9 @@ import ( ) var ( - dnsPort = 0 - tProxyPort = 0 - interfaceName = "" + dnsPort uint16 + tProxyPort uint16 + interfaceName string ) const ( @@ -21,7 +21,7 @@ const ( PROXY_ROUTE_TABLE = "0x2d0" ) -func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error { +func SetTProxyIPTables(ifname string, tport uint16, dport uint16) error { var err error if err = execCmd("iptables -V"); err != nil { return fmt.Errorf("current operations system [%s] are not support iptables or command iptables does not exist", runtime.GOOS) @@ -40,11 +40,13 @@ func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error { execCmd(fmt.Sprintf("ip -f inet route add local default dev %s table %s", interfaceName, PROXY_ROUTE_TABLE)) // set FORWARD - execCmd("sysctl -w net.ipv4.ip_forward=1") - execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -o %s -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT", interfaceName)) - execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -o %s -j ACCEPT", interfaceName)) - execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -i %s ! -o %s -j ACCEPT", interfaceName, interfaceName)) - execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -i %s -o %s -j ACCEPT", interfaceName, interfaceName)) + if interfaceName != "lo" { + execCmd("sysctl -w net.ipv4.ip_forward=1") + execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -o %s -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT", interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -o %s -j ACCEPT", interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -i %s ! -o %s -j ACCEPT", interfaceName, interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -A FORWARD -i %s -o %s -j ACCEPT", interfaceName, interfaceName)) + } // set clash divert execCmd("iptables -t mangle -N clash_divert") @@ -70,7 +72,9 @@ func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error { execCmd(fmt.Sprintf("iptables -t nat -I PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p udp --dport 53 -j REDIRECT --to %d", dnsPort)) // set post routing - execCmd(fmt.Sprintf("iptables -t nat -A POSTROUTING -o %s -m addrtype ! --src-type LOCAL -j MASQUERADE", interfaceName)) + if interfaceName != "lo" { + execCmd(fmt.Sprintf("iptables -t nat -A POSTROUTING -o %s -m addrtype ! --src-type LOCAL -j MASQUERADE", interfaceName)) + } // set output execCmd("iptables -t mangle -N clash_output") @@ -95,16 +99,15 @@ func SetTProxyLinuxIPTables(ifname string, tport int, dport int) error { execCmd("iptables -t nat -I OUTPUT -p tcp --dport 53 -j clash_dns_output") execCmd("iptables -t nat -I OUTPUT -p udp --dport 53 -j clash_dns_output") - log.Infoln("[TPROXY] Setting iptables completed") return nil } -func CleanUpTProxyLinuxIPTables() { - if interfaceName == "" || tProxyPort == 0 || dnsPort == 0 { +func CleanupTProxyIPTables() { + if runtime.GOOS != "linux" || interfaceName == "" || tProxyPort == 0 || dnsPort == 0 { return } - log.Warnln("Clean up tproxy linux iptables") + log.Warnln("Cleanup tproxy linux iptables") if int(dialer.DefaultRoutingMark.Load()) == 2158 { dialer.DefaultRoutingMark.Store(0) @@ -119,10 +122,12 @@ func CleanUpTProxyLinuxIPTables() { execCmd(fmt.Sprintf("ip -f inet route del local default dev %s table %s", interfaceName, PROXY_ROUTE_TABLE)) // clean FORWARD - execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -i %s ! -o %s -j ACCEPT", interfaceName, interfaceName)) - execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -i %s -o %s -j ACCEPT", interfaceName, interfaceName)) - execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -o %s -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT", interfaceName)) - execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -o %s -j ACCEPT", interfaceName)) + if interfaceName != "lo" { + execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -i %s ! -o %s -j ACCEPT", interfaceName, interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -i %s -o %s -j ACCEPT", interfaceName, interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -o %s -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT", interfaceName)) + execCmd(fmt.Sprintf("iptables -t filter -D FORWARD -o %s -j ACCEPT", interfaceName)) + } // clean PREROUTING execCmd(fmt.Sprintf("iptables -t nat -D PREROUTING ! -s 172.17.0.0/16 ! -d 127.0.0.0/8 -p tcp --dport 53 -j REDIRECT --to %d", dnsPort)) @@ -130,7 +135,9 @@ func CleanUpTProxyLinuxIPTables() { execCmd("iptables -t mangle -D PREROUTING -j clash_prerouting") // clean POSTROUTING - execCmd(fmt.Sprintf("iptables -t nat -D POSTROUTING -o %s -m addrtype ! --src-type LOCAL -j MASQUERADE", interfaceName)) + if interfaceName != "lo" { + execCmd(fmt.Sprintf("iptables -t nat -D POSTROUTING -o %s -m addrtype ! --src-type LOCAL -j MASQUERADE", interfaceName)) + } // clean OUTPUT execCmd(fmt.Sprintf("iptables -t mangle -D OUTPUT -o %s -j clash_output", interfaceName)) @@ -146,6 +153,10 @@ func CleanUpTProxyLinuxIPTables() { execCmd("iptables -t mangle -X clash_output") execCmd("iptables -t nat -F clash_dns_output") execCmd("iptables -t nat -X clash_dns_output") + + interfaceName = "" + tProxyPort = 0 + dnsPort = 0 } func addLocalnetworkToChain(chain string) { @@ -167,11 +178,11 @@ func addLocalnetworkToChain(chain string) { } func execCmd(cmdStr string) error { - log.Debugln("[TPROXY] %s", cmdStr) + log.Debugln("[IPTABLES] %s", cmdStr) _, err := cmd.ExecCmd(cmdStr) if err != nil { - log.Warnln("[TPROXY] exec cmd: %v", err) + log.Warnln("[IPTABLES] exec cmd: %v", err) } return err