Refactor: iptables auto config, disabled by default

This commit is contained in:
yaling888 2022-03-22 05:38:42 +08:00
parent 2c0890854e
commit ac4cde1411
6 changed files with 104 additions and 59 deletions

View file

@ -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.

View file

@ -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 {

6
go.mod
View file

@ -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 (

12
go.sum
View file

@ -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=

View file

@ -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()
}

View file

@ -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
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
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
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
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