From d10cf52839e848870df0ea852d9a818ac03e7aa3 Mon Sep 17 00:00:00 2001 From: cubercsl <2014cais01@gmail.com> Date: Thu, 19 Jan 2023 16:43:30 +0800 Subject: [PATCH 1/5] feat: add nftables support fix: use iptables-nft if nftables-support is on fix: save nft to V2RAYA_CONFIG fix: tproxy for ipv6 chore: small change in table format --- service/conf/environmentConfig.go | 1 + service/core/iptables/dropSpoofing.go | 4 +- service/core/iptables/iptables.go | 7 +- service/core/iptables/redirect.go | 142 +++++++++++++++++-- service/core/iptables/tproxy.go | 195 +++++++++++++++++++++++++- service/core/iptables/utils.go | 23 ++- service/core/iptables/watcher.go | 1 + service/core/v2ray/asset/asset.go | 17 ++- service/core/v2ray/transparent.go | 9 +- 9 files changed, 367 insertions(+), 32 deletions(-) --- a/conf/environmentConfig.go +++ b/conf/environmentConfig.go @@ -24,6 +24,7 @@ type Params struct { WebDir string `id:"webdir" desc:"v2rayA web files directory. use embedded files if not specify."` VlessGrpcInboundCertKey []string `id:"vless-grpc-inbound-cert-key" desc:"Specify the certification path instead of automatically generating a self-signed certificate. Example: /etc/v2raya/grpc_certificate.crt,/etc/v2raya/grpc_private.key"` IPV6Support string `id:"ipv6-support" default:"auto" desc:"Optional values: auto, on, off. Make sure your IPv6 network works fine before you turn it on."` + NFTablesSupport string `id:"nftables-support" default:"off" desc:"Optional values: auto, on, off. Experimental feature. Make sure you have installed nftables."` PassCheckRoot bool `desc:"Skip privilege checking. Use it only when you cannot start v2raya but confirm you have root privilege"` ResetPassword bool `id:"reset-password"` LogLevel string `id:"log-level" default:"info" desc:"Optional values: trace, debug, info, warn or error"` --- a/core/iptables/dropSpoofing.go +++ b/core/iptables/dropSpoofing.go @@ -34,7 +34,7 @@ ip6tables -w 2 -I FORWARD -j DROP_SPOOFI ` } return Setter{ - Cmds: commands, + Cmds: commands, } } @@ -54,6 +54,6 @@ ip6tables -w 2 -X DROP_SPOOFING ` } return Setter{ - Cmds: commands, + Cmds: commands, } } --- a/core/iptables/iptables.go +++ b/core/iptables/iptables.go @@ -1,11 +1,12 @@ package iptables import ( - "github.com/v2rayA/v2rayA/common" - "github.com/v2rayA/v2rayA/common/cmds" "strings" "sync" "time" + + "github.com/v2rayA/v2rayA/common" + "github.com/v2rayA/v2rayA/common/cmds" ) // http://briteming.hatenablog.com/entry/2019/06/18/175518 @@ -56,6 +57,10 @@ func (c Setter) Run(stopAtError bool) er if common.IsDocker() { commands = strings.ReplaceAll(commands, "iptables", "iptables-legacy") commands = strings.ReplaceAll(commands, "ip6tables", "ip6tables-legacy") + } else if (!cmds.IsCommandValid("iptables") || IsNFTablesSupported()) && + cmds.IsCommandValid("iptables-nft") { + commands = strings.ReplaceAll(commands, "iptables", "iptables-nft") + commands = strings.ReplaceAll(commands, "ip6tables", "ip6tables-nft") } var errs []error if c.PreFunc != nil { --- a/core/iptables/redirect.go +++ b/core/iptables/redirect.go @@ -2,15 +2,34 @@ package iptables import ( "fmt" - "github.com/v2rayA/v2rayA/common/cmds" + "os" "strings" + + "github.com/v2rayA/v2rayA/common/cmds" + "github.com/v2rayA/v2rayA/core/v2ray/asset" ) -type redirect struct{} +type redirect interface { + AddIPWhitelist(cidr string) + RemoveIPWhitelist(cidr string) + GetSetupCommands() Setter + GetCleanCommands() Setter +} + +type legacyRedirect struct{} +type nftRedirect struct{} var Redirect redirect -func (r *redirect) AddIPWhitelist(cidr string) { +func init() { + if IsNFTablesSupported() { + Redirect = &nftRedirect{} + } else { + Redirect = &legacyRedirect{} + } +} + +func (r *legacyRedirect) AddIPWhitelist(cidr string) { // avoid duplication r.RemoveIPWhitelist(cidr) var commands string @@ -22,13 +41,13 @@ func (r *redirect) AddIPWhitelist(cidr s cmds.ExecCommands(commands, false) } -func (r *redirect) RemoveIPWhitelist(cidr string) { +func (r *legacyRedirect) RemoveIPWhitelist(cidr string) { var commands string commands = fmt.Sprintf(`iptables -w 2 -t mangle -D TP_RULE -d %s -j RETURN`, cidr) cmds.ExecCommands(commands, false) } -func (r *redirect) GetSetupCommands() Setter { +func (r *legacyRedirect) GetSetupCommands() Setter { commands := ` iptables -w 2 -t nat -N TP_OUT iptables -w 2 -t nat -N TP_PRE @@ -84,11 +103,11 @@ ip6tables -w 2 -t nat -A TP_OUT -j TP_RU ` } return Setter{ - Cmds: commands, + Cmds: commands, } } -func (r *redirect) GetCleanCommands() Setter { +func (r *legacyRedirect) GetCleanCommands() Setter { commands := ` iptables -w 2 -t nat -F TP_OUT iptables -w 2 -t nat -D OUTPUT -p tcp -j TP_OUT @@ -112,6 +131,113 @@ ip6tables -w 2 -t nat -X TP_RULE ` } return Setter{ - Cmds: commands, + Cmds: commands, + } +} + +func (t *nftRedirect) AddIPWhitelist(cidr string) { + command := fmt.Sprintf("nft add element inet v2raya interface { %s }", cidr) + if !strings.Contains(cidr, ".") { + command = strings.Replace(command, "interface", "interface6", 1) + } + cmds.ExecCommands(command, false) +} + +func (t *nftRedirect) RemoveIPWhitelist(cidr string) { + command := fmt.Sprintf("nft delete element inet v2raya interface { %s }", cidr) + if !strings.Contains(cidr, ".") { + command = strings.Replace(command, "interface", "interface6", 1) } + cmds.ExecCommands(command, false) +} + +func (r *nftRedirect) GetSetupCommands() Setter { + // 198.18.0.0/15 and fc00::/7 are reserved for private use but used by fakedns + table := ` +table inet v2raya { + set whitelist { + type ipv4_addr + flags interval + auto-merge + elements = { + 0.0.0.0/32, + 10.0.0.0/8, + 100.64.0.0/10, + 127.0.0.0/8, + 169.254.0.0/16, + 172.16.0.0/12, + 192.0.0.0/24, + 192.0.2.0/24, + 192.88.99.0/24, + 192.168.0.0/16, + 198.51.100.0/24, + 203.0.113.0/24, + 224.0.0.0/4, + 240.0.0.0/4 + } + } + + set whitelist6 { + type ipv6_addr + flags interval + auto-merge + elements = { + ::/128, + ::1/128, + 64:ff9b::/96, + 100::/64, + 2001::/32, + 2001:20::/28, + fe80::/10, + ff00::/8 + } + } + + set interface { + type ipv4_addr + flags interval + auto-merge + } + + set interface6 { + type ipv6_addr + flags interval + auto-merge + } + + chain tp_rule { + ip daddr @whitelist return + ip daddr @interface return + ip6 daddr @whitelist6 return + ip6 daddr @interface6 return + meta mark & 0x80 == 0x80 return + meta l4proto tcp redirect to :32345 + } + + chain tp_pre { + type nat hook prerouting priority dstnat - 5 + meta nfproto { ipv4, ipv6 } meta l4proto tcp jump tp_rule + } + + chain tp_out { + type nat hook output priority -105 + meta nfproto { ipv4, ipv6 } meta l4proto tcp jump tp_rule + } +} +` + if !IsIPv6Supported() { + table = strings.ReplaceAll(table, "meta nfproto { ipv4, ipv6 }", "meta nfproto ipv4") + } + + nftablesConf := asset.GetNFTablesConfigPath() + os.WriteFile(nftablesConf, []byte(table), 0644) + + command := `nft -f ` + nftablesConf + + return Setter{Cmds: command} +} + +func (r *nftRedirect) GetCleanCommands() Setter { + command := `nft delete table inet v2raya` + return Setter{Cmds: command} } --- a/core/iptables/tproxy.go +++ b/core/iptables/tproxy.go @@ -2,18 +2,36 @@ package iptables import ( "fmt" + "os" + "strings" + "github.com/v2rayA/v2rayA/common/cmds" + "github.com/v2rayA/v2rayA/core/v2ray/asset" "github.com/v2rayA/v2rayA/db/configure" - "strings" ) -type tproxy struct { - watcher *LocalIPWatcher +type tproxy interface { + AddIPWhitelist(cidr string) + RemoveIPWhitelist(cidr string) + GetSetupCommands() Setter + GetCleanCommands() Setter } +type legacyTproxy struct{} + +type nftTproxy struct{} + var Tproxy tproxy -func (t *tproxy) AddIPWhitelist(cidr string) { +func init() { + if IsNFTablesSupported() { + Tproxy = &nftTproxy{} + } else { + Tproxy = &legacyTproxy{} + } +} + +func (t *legacyTproxy) AddIPWhitelist(cidr string) { // avoid duplication t.RemoveIPWhitelist(cidr) pos := 7 @@ -30,7 +48,7 @@ func (t *tproxy) AddIPWhitelist(cidr str cmds.ExecCommands(commands, false) } -func (t *tproxy) RemoveIPWhitelist(cidr string) { +func (t *legacyTproxy) RemoveIPWhitelist(cidr string) { var commands string commands = fmt.Sprintf(`iptables -w 2 -t mangle -D TP_RULE -d %s -j RETURN`, cidr) if !strings.Contains(cidr, ".") { @@ -40,7 +58,7 @@ func (t *tproxy) RemoveIPWhitelist(cidr cmds.ExecCommands(commands, false) } -func (t *tproxy) GetSetupCommands() Setter { +func (t *legacyTproxy) GetSetupCommands() Setter { commands := ` ip rule add fwmark 0x40/0xc0 table 100 ip route add local 0.0.0.0/0 dev lo table 100 @@ -158,7 +176,7 @@ ip6tables -w 2 -t mangle -A TP_MARK -j C } } -func (t *tproxy) GetCleanCommands() Setter { +func (t *legacyTproxy) GetCleanCommands() Setter { commands := ` ip rule del fwmark 0x40/0xc0 table 100 ip route del local 0.0.0.0/0 dev lo table 100 @@ -195,3 +213,166 @@ ip6tables -w 2 -t mangle -X TP_MARK Cmds: commands, } } + +func (t *nftTproxy) AddIPWhitelist(cidr string) { + command := fmt.Sprintf("nft add element inet v2raya interface { %s }", cidr) + if !strings.Contains(cidr, ".") { + command = strings.Replace(command, "interface", "interface6", 1) + } + cmds.ExecCommands(command, false) +} + +func (t *nftTproxy) RemoveIPWhitelist(cidr string) { + command := fmt.Sprintf("nft delete element inet v2raya interface { %s }", cidr) + if !strings.Contains(cidr, ".") { + command = strings.Replace(command, "interface", "interface6", 1) + } + cmds.ExecCommands(command, false) +} + +func (t *nftTproxy) GetSetupCommands() Setter { + // 198.18.0.0/15 and fc00::/7 are reserved for private use but used by fakedns + table := ` +table inet v2raya { + set whitelist { + type ipv4_addr + flags interval + auto-merge + elements = { + 0.0.0.0/32, + 10.0.0.0/8, + 100.64.0.0/10, + 127.0.0.0/8, + 169.254.0.0/16, + 172.16.0.0/12, + 192.0.0.0/24, + 192.0.2.0/24, + 192.88.99.0/24, + 192.168.0.0/16, + 198.51.100.0/24, + 203.0.113.0/24, + 224.0.0.0/4, + 240.0.0.0/4 + } + } + + set whitelist6 { + type ipv6_addr + flags interval + auto-merge + elements = { + ::/128, + ::1/128, + 64:ff9b::/96, + 100::/64, + 2001::/32, + 2001:20::/28, + fe80::/10, + ff00::/8 + } + } + + set interface { + type ipv4_addr + flags interval + auto-merge + } + + set interface6 { + type ipv6_addr + flags interval + auto-merge + } + + chain tp_out { + meta mark & 0x80 == 0x80 return + meta l4proto { tcp, udp } fib saddr type local fib daddr type != local jump tp_rule + } + + chain tp_pre { + iifname "lo" mark & 0xc0 != 0x40 return + meta l4proto { tcp, udp } fib saddr type != local fib daddr type != local jump tp_rule + meta l4proto { tcp, udp } mark & 0xc0 == 0x40 tproxy ip to 127.0.0.1:32345 + meta l4proto { tcp, udp } mark & 0xc0 == 0x40 tproxy ip6 to [::1]:32345 + } + + chain output { + type route hook output priority mangle - 5; policy accept; + meta nfproto { ipv4, ipv6 } jump tp_out + } + + chain prerouting { + type filter hook prerouting priority mangle - 5; policy accept; + meta nfproto { ipv4, ipv6 } jump tp_pre + } + + chain tp_rule { + meta mark set ct mark + meta mark & 0xc0 == 0x40 return + iifname "docker*" return + iifname "veth*" return + iifname "wg*" return + iifname "ppp*" return + # anti-pollution + ip daddr @interface return + ip daddr @whitelist return + ip6 daddr @interface6 return + ip6 daddr @whitelist6 return + jump tp_mark + } + + chain tp_mark { + tcp flags & (fin | syn | rst | ack) == syn meta mark set mark | 0x40 + meta l4proto udp ct state new meta mark set mark | 0x40 + ct mark set mark + } +} +` + if configure.GetSettingNotNil().AntiPollution != configure.AntipollutionClosed { + table = strings.ReplaceAll(table, "# anti-pollution", ` + meta l4proto { tcp, udp } th dport 53 jump tp_mark + meta mark & 0xc0 == 0x40 return + `) + } + + if !IsIPv6Supported() { + // drop ipv6 packets hooks + table = strings.ReplaceAll(table, "meta nfproto { ipv4, ipv6 }", "meta nfproto ipv4") + } + + nftablesConf := asset.GetNFTablesConfigPath() + os.WriteFile(nftablesConf, []byte(table), 0644) + + command := ` +ip rule add fwmark 0x40/0xc0 table 100 +ip route add local 0.0.0.0/0 dev lo table 100 +` + if IsIPv6Supported() { + command += ` +ip -6 rule add fwmark 0x40/0xc0 table 100 +ip -6 route add local ::/0 dev lo table 100 +` + } + + command += `nft -f ` + nftablesConf + return Setter{Cmds: command} +} + +func (t *nftTproxy) GetCleanCommands() Setter { + command := ` +ip rule del fwmark 0x40/0xc0 table 100 +ip route del local 0.0.0.0/0 dev lo table 100 +` + if IsIPv6Supported() { + command += ` +ip -6 rule del fwmark 0x40/0xc0 table 100 +ip -6 route del local ::/0 dev lo table 100 + ` + } + + command += `nft delete table inet v2raya` + if !IsIPv6Supported() { + command = strings.Replace(command, "inet", "ip", 1) + } + return Setter{Cmds: command} +} --- a/core/iptables/utils.go +++ b/core/iptables/utils.go @@ -1,12 +1,13 @@ package iptables import ( + "net" + "strconv" + "github.com/v2rayA/v2rayA/common" "github.com/v2rayA/v2rayA/common/cmds" "github.com/v2rayA/v2rayA/conf" "golang.org/x/net/nettest" - "net" - "strconv" ) func IPNet2CIDR(ipnet *net.IPNet) string { @@ -44,3 +45,21 @@ func IsIPv6Supported() bool { } return cmds.IsCommandValid("ip6tables") } + +func IsNFTablesSupported() bool { + + switch conf.GetEnvironmentConfig().NFTablesSupport { + // Warning: + // This is an experimental feature for nftables support. + // The default value is "off" for now but may be changed to "auto" in the future + case "on": + return true + case "off": + return false + default: + } + if common.IsDocker() { + return false + } + return cmds.IsCommandValid("nft") +} --- a/core/iptables/watcher.go +++ b/core/iptables/watcher.go @@ -10,6 +10,7 @@ type LocalIPWatcher struct { cidrPool map[string]struct{} AddedFunc func(cidr string) RemovedFunc func(cidr string) + UpdateFunc func(cidrs []string) } func NewLocalIPWatcher(interval time.Duration, AddedFunc func(cidr string), RemovedFunc func(cidr string)) *LocalIPWatcher { --- a/core/v2ray/asset/asset.go +++ b/core/v2ray/asset/asset.go @@ -3,12 +3,6 @@ package asset import ( "errors" "fmt" - "github.com/adrg/xdg" - "github.com/muhammadmuzzammil1998/jsonc" - "github.com/v2rayA/v2rayA/common/files" - "github.com/v2rayA/v2rayA/conf" - "github.com/v2rayA/v2rayA/core/v2ray/where" - "github.com/v2rayA/v2rayA/pkg/util/log" "io" "io/fs" "net/http" @@ -17,6 +11,13 @@ import ( "path/filepath" "runtime" "time" + + "github.com/adrg/xdg" + "github.com/muhammadmuzzammil1998/jsonc" + "github.com/v2rayA/v2rayA/common/files" + "github.com/v2rayA/v2rayA/conf" + "github.com/v2rayA/v2rayA/core/v2ray/where" + "github.com/v2rayA/v2rayA/pkg/util/log" ) func GetV2rayLocationAssetOverride() string { @@ -140,6 +141,10 @@ func GetV2rayConfigDirPath() (p string) return conf.GetEnvironmentConfig().V2rayConfigDirectory } +func GetNFTablesConfigPath() (p string) { + return path.Join(conf.GetEnvironmentConfig().Config, "v2raya.nft") +} + func Download(url string, to string) (err error) { log.Info("Downloading %v to %v", url, to) c := http.Client{Timeout: 90 * time.Second} --- a/core/v2ray/transparent.go +++ b/core/v2ray/transparent.go @@ -2,13 +2,14 @@ package v2ray import ( "fmt" + "strings" + "time" + "github.com/v2rayA/v2rayA/conf" "github.com/v2rayA/v2rayA/core/iptables" "github.com/v2rayA/v2rayA/core/specialMode" "github.com/v2rayA/v2rayA/db/configure" "github.com/v2rayA/v2rayA/pkg/util/log" - "strings" - "time" ) func deleteTransparentProxyRules() { @@ -45,12 +46,12 @@ func writeTransparentProxyRules() (err e } return fmt.Errorf("not support \"tproxy\" mode of transparent proxy: %w", err) } - iptables.SetWatcher(&iptables.Tproxy) + iptables.SetWatcher(iptables.Tproxy) case configure.TransparentRedirect: if err = iptables.Redirect.GetSetupCommands().Run(true); err != nil { return fmt.Errorf("not support \"redirect\" mode of transparent proxy: %w", err) } - iptables.SetWatcher(&iptables.Redirect) + iptables.SetWatcher(iptables.Redirect) case configure.TransparentSystemProxy: if err = iptables.SystemProxy.GetSetupCommands().Run(true); err != nil { return fmt.Errorf("not support \"system proxy\" mode of transparent proxy: %w", err)