mirror of
https://github.com/SunBK201/UA3F.git
synced 2025-12-16 08:44:29 +00:00
feat: add packet modification features for TTL, IP ID, and TCP timestamp deletion
This commit is contained in:
parent
db4888cd0c
commit
4cb071cd19
@ -190,6 +190,14 @@ function M.add_others_fields(section)
|
||||
-- TTL Setting
|
||||
local ttl = section:taboption("others", Flag, "set_ttl", translate("Set TTL"))
|
||||
ttl.description = translate("Set the TTL 64 for packets")
|
||||
|
||||
-- TCP Timestamp Deletion
|
||||
local tcpts = section:taboption("others", Flag, "del_tcpts", translate("Delete TCP Timestamps"))
|
||||
tcpts.description = translate("Remove TCP Timestamp option")
|
||||
|
||||
-- IP ID Setting
|
||||
local ipid = section:taboption("others", Flag, "set_ipid", translate("Set IP ID"))
|
||||
ipid.description = translate("Set the IP ID to 0 for packets")
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@ -137,8 +137,8 @@ detect_backend() {
|
||||
if [ "$SERVER_MODE" = "TPROXY" ]; then
|
||||
if opkg list-installed kmod-nft-tproxy | grep -q 'kmod-nft-tproxy'; then
|
||||
if nft_available; then
|
||||
FW_BACKEND="nft"
|
||||
return 0
|
||||
FW_BACKEND="nft"
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
FW_BACKEND="ipt"
|
||||
@ -147,8 +147,8 @@ detect_backend() {
|
||||
elif [ "$SERVER_MODE" = "NFQUEUE" ]; then
|
||||
if opkg list-installed kmod-nft-queue | grep -q 'kmod-nft-queue'; then
|
||||
if nft_available; then
|
||||
FW_BACKEND="nft"
|
||||
return 0
|
||||
FW_BACKEND="nft"
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
FW_BACKEND="ipt"
|
||||
@ -510,6 +510,7 @@ start_service() {
|
||||
|
||||
local port bind ua log_level ua_regex partial_replace set_ttl
|
||||
local rewrite_mode rewrite_rules
|
||||
local set_ttl set_ipid del_tcpts
|
||||
config_get server_mode "main" "server_mode" "SOCKS5"
|
||||
config_get port "main" "port" "1080"
|
||||
config_get bind "main" "bind" "127.0.0.1"
|
||||
@ -517,15 +518,21 @@ start_service() {
|
||||
config_get ua_regex "main" "ua_regex" ""
|
||||
config_get_bool partial_replace "main" "partial_replace" 0
|
||||
config_get log_level "main" "log_level" "info"
|
||||
config_get_bool set_ttl "main" "set_ttl" 0
|
||||
config_get rewrite_mode "main" "rewrite_mode" "GLOBAL"
|
||||
config_get rewrite_rules "main" "rewrite_rules" ""
|
||||
config_get_bool set_ttl "main" "set_ttl" 0
|
||||
config_get_bool set_ipid "main" "set_ipid" 0
|
||||
config_get_bool del_tcpts "main" "del_tcpts" 0
|
||||
|
||||
# compose set_ttl set_ipid del_tcpts with others ",ttl,ipid,tcpts,"
|
||||
local others=","
|
||||
[ "$set_ipid" -eq "1" ] && others="${others}ipid,"
|
||||
[ "$del_tcpts" -eq "1" ] && others="${others}tcpts,"
|
||||
[ "$set_ttl" -eq "1" ] && others="${others}ttl,"
|
||||
|
||||
SERVER_MODE="$(echo "$server_mode" | tr '[:lower:]' '[:upper:]')"
|
||||
SERVER_MODE="$server_mode"
|
||||
|
||||
SET_TTL="$set_ttl"
|
||||
|
||||
LOG "Server Mode: $SERVER_MODE"
|
||||
LOG "Port: $port"
|
||||
LOG "Bind: $bind"
|
||||
@ -533,7 +540,6 @@ start_service() {
|
||||
LOG "User-Agent Regex: $ua_regex"
|
||||
LOG "Log level: $log_level"
|
||||
LOG "Partial Replace: $partial_replace"
|
||||
LOG "Set TTL: $SET_TTL"
|
||||
|
||||
set_ua3f_group
|
||||
LOG "Run as GID: $UA3F_GID, Group: $UA3F_GROUP"
|
||||
@ -553,6 +559,22 @@ start_service() {
|
||||
fw_revert_ipt
|
||||
fi
|
||||
|
||||
# dump all fw rules for debug
|
||||
if [ "$log_level" = "debug" ]; then
|
||||
if [ "$FW_BACKEND" = "nft" ]; then
|
||||
LOG "nftables before rules:"
|
||||
nft --handle list ruleset >>"$LOG_FILE" 2>&1
|
||||
elif [ "$FW_BACKEND" = "ipt" ]; then
|
||||
LOG "iptables before rules:"
|
||||
LOG "mangle table:"
|
||||
iptables -t mangle -L -v -n >>"$LOG_FILE" 2>&1
|
||||
LOG "nat table:"
|
||||
iptables -t nat -L -v -n >>"$LOG_FILE" 2>&1
|
||||
LOG "filter table:"
|
||||
iptables -t filter -L -v -n >>"$LOG_FILE" 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$SERVER_MODE" in
|
||||
HTTP)
|
||||
# No firewall interception
|
||||
@ -607,20 +629,6 @@ start_service() {
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$SET_TTL" = "1" ]; then
|
||||
if [ "$FW_BACKEND" = "nft" ]; then
|
||||
set_ttl_nft || {
|
||||
LOG "set_ttl_nft setup failed"
|
||||
}
|
||||
LOG "Set TTL 64 via nftables"
|
||||
else
|
||||
set_ttl_ipt || {
|
||||
LOG "set_ttl_ipt setup failed"
|
||||
}
|
||||
LOG "Set TTL 64 via iptables"
|
||||
fi
|
||||
fi
|
||||
|
||||
# dump all fw rules for debug
|
||||
if [ "$log_level" = "debug" ]; then
|
||||
if [ "$FW_BACKEND" = "nft" ]; then
|
||||
@ -653,6 +661,7 @@ start_service() {
|
||||
procd_append_param command -x "$rewrite_mode"
|
||||
procd_append_param command -z "$rewrite_rules"
|
||||
[ "$partial_replace" = "1" ] && procd_append_param command -s
|
||||
procd_append_param command -o "$others"
|
||||
|
||||
procd_set_param respawn
|
||||
procd_set_param stdout 1
|
||||
|
||||
@ -253,3 +253,15 @@ msgstr "注意:重写规则仅在规则判定模式下生效。NFQUEUE 模式
|
||||
|
||||
msgid "Rewrite Header"
|
||||
msgstr "重写请求头"
|
||||
|
||||
msgid "Delete TCP Timestamps"
|
||||
msgstr "删除 TCP 时间戳"
|
||||
|
||||
msgid "Remove TCP Timestamp option"
|
||||
msgstr "移除 TCP 时间戳选项"
|
||||
|
||||
msgid "Set IP ID"
|
||||
msgstr "固定 IP ID"
|
||||
|
||||
msgid "Set the IP ID to 0 for packets"
|
||||
msgstr "将数据包的 IP ID 设置为 0,不推荐开启,除非你知道这是什么"
|
||||
|
||||
13
src/go.mod
13
src/go.mod
@ -1,23 +1,26 @@
|
||||
module github.com/sunbk201/ua3f
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
toolchain go1.24.6
|
||||
|
||||
require (
|
||||
github.com/coreos/go-iptables v0.8.0
|
||||
github.com/dlclark/regexp2 v1.11.4
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.2
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
golang.org/x/sys v0.30.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
sigs.k8s.io/knftables v0.0.19
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
)
|
||||
|
||||
|
||||
20
src/go.sum
20
src/go.sum
@ -1,3 +1,5 @@
|
||||
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
||||
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -11,12 +13,12 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
|
||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
|
||||
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
@ -31,8 +33,8 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
@ -51,3 +53,5 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
sigs.k8s.io/knftables v0.0.19 h1:0orK0+tYhY575F5X9uJGu80t+aVQ/hJj48I3fz3TBk8=
|
||||
sigs.k8s.io/knftables v0.0.19/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk=
|
||||
|
||||
@ -25,16 +25,19 @@ const (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ServerMode ServerMode
|
||||
BindAddr string
|
||||
Port int
|
||||
ListenAddr string
|
||||
LogLevel string
|
||||
RewriteMode RewriteMode
|
||||
Rules string
|
||||
PayloadUA string
|
||||
UARegex string
|
||||
PartialReplace bool
|
||||
ServerMode ServerMode
|
||||
BindAddr string
|
||||
Port int
|
||||
ListenAddr string
|
||||
LogLevel string
|
||||
RewriteMode RewriteMode
|
||||
Rules string
|
||||
PayloadUA string
|
||||
UARegex string
|
||||
PartialReplace bool
|
||||
SetTTL bool
|
||||
SetIPID bool
|
||||
DelTCPTimestamp bool
|
||||
}
|
||||
|
||||
func Parse() (*Config, bool) {
|
||||
@ -48,6 +51,7 @@ func Parse() (*Config, bool) {
|
||||
partial bool
|
||||
rewriteMode string
|
||||
rules string
|
||||
others string
|
||||
showVer bool
|
||||
)
|
||||
|
||||
@ -60,6 +64,7 @@ func Parse() (*Config, bool) {
|
||||
flag.BoolVar(&partial, "s", false, "Enable regex partial replace")
|
||||
flag.StringVar(&rewriteMode, "x", string(RewriteModeGlobal), "Rewrite mode: GLOBAL, DIRECT, RULES")
|
||||
flag.StringVar(&rules, "z", "", "Rules JSON string")
|
||||
flag.StringVar(&others, "o", "", "Other options (tcpts, ttl, ipid)")
|
||||
flag.BoolVar(&showVer, "v", false, "Show version")
|
||||
flag.Parse()
|
||||
|
||||
@ -79,5 +84,19 @@ func Parse() (*Config, bool) {
|
||||
cfg.BindAddr = "0.0.0.0"
|
||||
cfg.ListenAddr = fmt.Sprintf("0.0.0.0:%d", port)
|
||||
}
|
||||
|
||||
// Parse other options
|
||||
opts := strings.Split(others, ",")
|
||||
for _, opt := range opts {
|
||||
switch strings.ToLower(strings.TrimSpace(opt)) {
|
||||
case "tcpts":
|
||||
cfg.DelTCPTimestamp = true
|
||||
case "ttl":
|
||||
cfg.SetTTL = true
|
||||
case "ipid":
|
||||
cfg.SetIPID = true
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, showVer
|
||||
}
|
||||
|
||||
@ -78,6 +78,7 @@ func LogHeader(version string, cfg *config.Config) {
|
||||
logrus.Infof("User-Agent Regex: '%s'", cfg.UARegex)
|
||||
logrus.Infof("Partial Replace: %v", cfg.PartialReplace)
|
||||
logrus.Infof("Log level: %s", cfg.LogLevel)
|
||||
logrus.Infof("Packet Modifications - SetTTL: %v, SetIPID: %v, DelTCPTimestamp: %v", cfg.SetTTL, cfg.SetIPID, cfg.DelTCPTimestamp)
|
||||
}
|
||||
|
||||
func LogDebugWithAddr(src string, dest string, msg string) {
|
||||
|
||||
@ -107,7 +107,6 @@ func (s *NfqueueServer) Start() error {
|
||||
s.wg.Wait()
|
||||
return fmt.Errorf("failed to register nfqueue handler: %w", err)
|
||||
}
|
||||
logrus.Info("NFQUEUE handler registered, listening for packets")
|
||||
|
||||
// Wait until context is done
|
||||
<-ctx.Done()
|
||||
|
||||
72
src/internal/server/netlink/firewall.go
Normal file
72
src/internal/server/netlink/firewall.go
Normal file
@ -0,0 +1,72 @@
|
||||
package netlink
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/sunbk201/ua3f/internal/config"
|
||||
)
|
||||
|
||||
const (
|
||||
NFT = "nft"
|
||||
IPT = "ipt"
|
||||
)
|
||||
|
||||
func (s *Server) setupFirewall() error {
|
||||
s.cleanupFirewall()
|
||||
backend := s.detectFirewallBackend()
|
||||
switch backend {
|
||||
case NFT:
|
||||
return s.nftSetup()
|
||||
case IPT:
|
||||
return s.iptSetup()
|
||||
default:
|
||||
return fmt.Errorf("unsupported or no firewall backend: %s", backend)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) cleanupFirewall() error {
|
||||
s.nftCleanup()
|
||||
s.iptCleanup()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) detectFirewallBackend() string {
|
||||
// Check if opkg is available (OpenWrt environment)
|
||||
if isCommandAvailable("opkg") {
|
||||
switch s.cfg.ServerMode {
|
||||
case config.ServerModeTProxy:
|
||||
// Check if kmod-nft-tproxy is installed
|
||||
if isOpkgPackageInstalled("kmod-nft-tproxy") && isCommandAvailable("nft") {
|
||||
logrus.Info("Detected nftables backend (kmod-nft-tproxy installed)")
|
||||
return NFT
|
||||
}
|
||||
logrus.Info("Detected iptables backend (kmod-nft-tproxy not installed)")
|
||||
return IPT
|
||||
case config.ServerModeNFQueue:
|
||||
// Check if kmod-nft-queue is installed
|
||||
if isOpkgPackageInstalled("kmod-nft-queue") && isCommandAvailable("nft") {
|
||||
logrus.Info("Detected nftables backend (kmod-nft-queue installed)")
|
||||
return NFT
|
||||
}
|
||||
logrus.Info("Detected iptables backend (kmod-nft-queue not installed)")
|
||||
return IPT
|
||||
}
|
||||
}
|
||||
|
||||
// Check if nft command is available
|
||||
if isCommandAvailable("nft") {
|
||||
logrus.Info("Detected nftables backend (nft command available)")
|
||||
return NFT
|
||||
}
|
||||
|
||||
// Check if iptables command is available
|
||||
if isCommandAvailable("iptables") {
|
||||
logrus.Info("Detected iptables backend (iptables command available)")
|
||||
return IPT
|
||||
}
|
||||
|
||||
// No backend detected
|
||||
logrus.Warn("No firewall backend detected")
|
||||
return ""
|
||||
}
|
||||
96
src/internal/server/netlink/iptables.go
Normal file
96
src/internal/server/netlink/iptables.go
Normal file
@ -0,0 +1,96 @@
|
||||
package netlink
|
||||
|
||||
import (
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
|
||||
const (
|
||||
table = "mangle"
|
||||
chain = "POSTROUTING"
|
||||
)
|
||||
|
||||
var RuleTTL = []string{
|
||||
"-j", "TTL",
|
||||
"--ttl-set", "64",
|
||||
}
|
||||
|
||||
var RuleIP = []string{
|
||||
"-j", "QUEUE",
|
||||
"--queue-num", "10301",
|
||||
"--queue-bypass",
|
||||
}
|
||||
|
||||
var RuleDelTCPTS = []string{
|
||||
"-p", "tcp",
|
||||
"--tcp-flags", "SYN,RST,ACK,FIN",
|
||||
"-j", "QUEUE",
|
||||
"--queue-num", "10301",
|
||||
"--queue-bypass",
|
||||
}
|
||||
|
||||
func (s *Server) iptSetup() error {
|
||||
_ = s.iptCleanup()
|
||||
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.cfg.SetTTL {
|
||||
err = IptSetTTL(ipt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if s.cfg.DelTCPTimestamp && !s.cfg.SetIPID {
|
||||
err = IptDelTCPTS(ipt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if s.cfg.SetIPID {
|
||||
err = IptSetIP(ipt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) iptCleanup() error {
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = ipt.DeleteIfExists(table, chain, RuleTTL...)
|
||||
_ = ipt.DeleteIfExists(table, chain, RuleIP...)
|
||||
err = ipt.DeleteIfExists(table, chain, RuleDelTCPTS...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IptSetTTL creates a chain that sets TTL to 64 for IPv4 packets
|
||||
func IptSetTTL(ipt *iptables.IPTables) error {
|
||||
err := ipt.Append(table, chain, RuleTTL...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IptSetIP(ipt *iptables.IPTables) error {
|
||||
err := ipt.Append(table, chain, RuleIP...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IptDelTCPTS(ipt *iptables.IPTables) error {
|
||||
err := ipt.Append(table, chain, RuleDelTCPTS...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
122
src/internal/server/netlink/netlink.go
Normal file
122
src/internal/server/netlink/netlink.go
Normal file
@ -0,0 +1,122 @@
|
||||
package netlink
|
||||
|
||||
import (
|
||||
nfq "github.com/florianl/go-nfqueue/v2"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/sunbk201/ua3f/internal/config"
|
||||
"github.com/sunbk201/ua3f/internal/netfilter"
|
||||
"sigs.k8s.io/knftables"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
cfg *config.Config
|
||||
nfqServer *netfilter.NfqueueServer
|
||||
nftable *knftables.Table
|
||||
}
|
||||
|
||||
func New(cfg *config.Config) *Server {
|
||||
s := &Server{
|
||||
cfg: cfg,
|
||||
nfqServer: &netfilter.NfqueueServer{
|
||||
QueueNum: 10301,
|
||||
},
|
||||
nftable: &knftables.Table{
|
||||
Name: "UA3F_HELPER",
|
||||
Family: knftables.InetFamily,
|
||||
},
|
||||
}
|
||||
s.nfqServer.HandlePacket = s.handlePacket
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) Setup() (err error) {
|
||||
err = s.setupFirewall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Start() (err error) {
|
||||
if s.cfg.SetTTL || s.cfg.DelTCPTimestamp || s.cfg.SetIPID {
|
||||
logrus.Info("Packet modification features enabled")
|
||||
return s.nfqServer.Start()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Close() (err error) {
|
||||
err = s.cleanupFirewall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// err = s.nfqServer.Nf.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// handlePacket processes a single NFQUEUE packet
|
||||
func (s *Server) handlePacket(packet *netfilter.Packet) {
|
||||
nf := s.nfqServer.Nf
|
||||
|
||||
modified := false
|
||||
if s.cfg.DelTCPTimestamp && packet.TCP != nil {
|
||||
modified = s.clearTCPTimestamp(packet.TCP) || modified
|
||||
}
|
||||
if s.cfg.SetIPID {
|
||||
modified = s.zeroIPID(packet) || modified
|
||||
}
|
||||
|
||||
if modified {
|
||||
newPacket, err := packet.Serialize()
|
||||
if err != nil {
|
||||
logrus.Errorf("packet.Serialize: %v", err)
|
||||
_ = nf.SetVerdict(*packet.A.PacketID, nfq.NfAccept)
|
||||
return
|
||||
}
|
||||
if err := nf.SetVerdictWithOption(*packet.A.PacketID, nfq.NfAccept, nfq.WithAlteredPacket(newPacket)); err != nil {
|
||||
logrus.Errorf("nf.SetVerdictWithOption: %v", err)
|
||||
_ = nf.SetVerdict(*packet.A.PacketID, nfq.NfAccept)
|
||||
}
|
||||
} else {
|
||||
_ = nf.SetVerdict(*packet.A.PacketID, nfq.NfAccept)
|
||||
}
|
||||
}
|
||||
|
||||
// clearTCPTimestamp removes the TCP timestamp option from the TCP layer
|
||||
// Returns true if the timestamp option was found and removed
|
||||
func (s *Server) clearTCPTimestamp(tcp *layers.TCP) bool {
|
||||
if len(tcp.Options) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
modified := false
|
||||
newOptions := make([]layers.TCPOption, 0, len(tcp.Options))
|
||||
|
||||
for _, opt := range tcp.Options {
|
||||
// TCP Timestamp option kind is 8
|
||||
if opt.OptionType == 8 {
|
||||
modified = true
|
||||
continue
|
||||
}
|
||||
newOptions = append(newOptions, opt)
|
||||
}
|
||||
if modified {
|
||||
tcp.Options = newOptions
|
||||
}
|
||||
return modified
|
||||
}
|
||||
|
||||
// zeroIPID sets the IP ID field to zero for IPv4 packets
|
||||
// Returns true if the packet was modified
|
||||
func (s *Server) zeroIPID(packet *netfilter.Packet) bool {
|
||||
if packet.IsIPv6 {
|
||||
return false
|
||||
}
|
||||
ip4 := packet.NetworkLayer.(*layers.IPv4)
|
||||
if ip4.Id == 0 {
|
||||
return false
|
||||
}
|
||||
ip4.Id = 0
|
||||
return true
|
||||
}
|
||||
107
src/internal/server/netlink/nftables.go
Normal file
107
src/internal/server/netlink/nftables.go
Normal file
@ -0,0 +1,107 @@
|
||||
package netlink
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"sigs.k8s.io/knftables"
|
||||
)
|
||||
|
||||
func (s *Server) nftSetup() error {
|
||||
// Clean up existing table if any
|
||||
_ = s.nftCleanup()
|
||||
|
||||
nft, err := knftables.New(s.nftable.Family, s.nftable.Name)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to create nftables table: %v", err)
|
||||
return err
|
||||
}
|
||||
tx := nft.NewTransaction()
|
||||
tx.Add(s.nftable)
|
||||
|
||||
if s.cfg.SetTTL {
|
||||
NftSetTTL(tx, s.nftable)
|
||||
}
|
||||
if s.cfg.DelTCPTimestamp && !s.cfg.SetIPID {
|
||||
NftDelTCPTS(tx, s.nftable)
|
||||
}
|
||||
if s.cfg.SetIPID {
|
||||
NftSetIP(tx, s.nftable)
|
||||
}
|
||||
|
||||
if err := nft.Run(context.TODO(), tx); err != nil {
|
||||
logrus.Errorf("Failed to run nftables transaction: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Info("Nftables setup completed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) nftCleanup() error {
|
||||
nft, err := knftables.New(s.nftable.Family, s.nftable.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx := nft.NewTransaction()
|
||||
tx.Delete(s.nftable)
|
||||
if err := nft.Run(context.TODO(), tx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NftSetTTL creates a chain that sets TTL to 64 for IPv4 packets
|
||||
func NftSetTTL(tx *knftables.Transaction, table *knftables.Table) {
|
||||
chain := &knftables.Chain{
|
||||
Name: "TTL64",
|
||||
Type: knftables.PtrTo(knftables.FilterType),
|
||||
Table: table.Name,
|
||||
Hook: knftables.PtrTo(knftables.PostroutingHook),
|
||||
Priority: knftables.PtrTo(knftables.ManglePriority),
|
||||
}
|
||||
rule := &knftables.Rule{
|
||||
Chain: chain.Name,
|
||||
Rule: knftables.Concat(
|
||||
"ip ttl set 64",
|
||||
),
|
||||
}
|
||||
tx.Add(chain)
|
||||
tx.Add(rule)
|
||||
}
|
||||
|
||||
func NftSetIP(tx *knftables.Transaction, table *knftables.Table) {
|
||||
chain := &knftables.Chain{
|
||||
Name: "HELPER_QUEUE",
|
||||
Type: knftables.PtrTo(knftables.FilterType),
|
||||
Table: table.Name,
|
||||
Hook: knftables.PtrTo(knftables.PostroutingHook),
|
||||
Priority: knftables.PtrTo(knftables.ManglePriority),
|
||||
}
|
||||
rule := &knftables.Rule{
|
||||
Chain: chain.Name,
|
||||
Rule: knftables.Concat(
|
||||
"counter queue num 10301 bypass",
|
||||
),
|
||||
}
|
||||
tx.Add(chain)
|
||||
tx.Add(rule)
|
||||
}
|
||||
|
||||
func NftDelTCPTS(tx *knftables.Transaction, table *knftables.Table) {
|
||||
chain := &knftables.Chain{
|
||||
Name: "HELPER_QUEUE",
|
||||
Type: knftables.PtrTo(knftables.FilterType),
|
||||
Table: table.Name,
|
||||
Hook: knftables.PtrTo(knftables.PostroutingHook),
|
||||
Priority: knftables.PtrTo(knftables.ManglePriority),
|
||||
}
|
||||
rule := &knftables.Rule{
|
||||
Chain: chain.Name,
|
||||
Rule: knftables.Concat(
|
||||
"tcp flags syn counter queue num 10301 bypass",
|
||||
),
|
||||
}
|
||||
tx.Add(chain)
|
||||
tx.Add(rule)
|
||||
}
|
||||
19
src/internal/server/netlink/utils.go
Normal file
19
src/internal/server/netlink/utils.go
Normal file
@ -0,0 +1,19 @@
|
||||
package netlink
|
||||
|
||||
import "os/exec"
|
||||
|
||||
// isCommandAvailable checks if a command is available in the system
|
||||
func isCommandAvailable(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// isOpkgPackageInstalled checks if a package is installed via opkg
|
||||
func isOpkgPackageInstalled(pkg string) bool {
|
||||
cmd := exec.Command("opkg", "list-installed", pkg)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(output) > 0
|
||||
}
|
||||
@ -38,6 +38,8 @@ func (s *Server) Start() (err error) {
|
||||
if client, err = s.listener.Accept(); err != nil {
|
||||
if errors.Is(err, syscall.EMFILE) {
|
||||
time.Sleep(time.Second)
|
||||
} else if errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
logrus.Error("s.listener.Accept:", err)
|
||||
continue
|
||||
|
||||
@ -65,6 +65,8 @@ func (s *Server) Start() (err error) {
|
||||
if client, err = s.listener.Accept(); err != nil {
|
||||
if errors.Is(err, syscall.EMFILE) {
|
||||
time.Sleep(time.Second)
|
||||
} else if errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
logrus.Error("s.listener.Accept:", err)
|
||||
continue
|
||||
|
||||
@ -58,6 +58,8 @@ func (s *Server) Start() error {
|
||||
if client, err = s.listener.Accept(); err != nil {
|
||||
if errors.Is(err, syscall.EMFILE) {
|
||||
time.Sleep(time.Second)
|
||||
} else if errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
logrus.Error("s.listener.Accept:", err)
|
||||
continue
|
||||
|
||||
16
src/main.go
16
src/main.go
@ -11,6 +11,7 @@ import (
|
||||
"github.com/sunbk201/ua3f/internal/log"
|
||||
"github.com/sunbk201/ua3f/internal/rewrite"
|
||||
"github.com/sunbk201/ua3f/internal/server"
|
||||
"github.com/sunbk201/ua3f/internal/server/netlink"
|
||||
"github.com/sunbk201/ua3f/internal/statistics"
|
||||
)
|
||||
|
||||
@ -38,22 +39,35 @@ func main() {
|
||||
|
||||
log.LogHeader(version, cfg)
|
||||
|
||||
go statistics.StartRecorder()
|
||||
helper := netlink.New(cfg)
|
||||
err = helper.Setup()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
cleanup := make(chan os.Signal, 1)
|
||||
signal.Notify(cleanup, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go helper.Start()
|
||||
go statistics.StartRecorder()
|
||||
|
||||
go func() {
|
||||
<-cleanup
|
||||
logrus.Info("Shutting down UA3F...")
|
||||
if err := helper.Close(); err != nil {
|
||||
logrus.Errorf("Error during helper close: %v", err)
|
||||
}
|
||||
if err := srv.Close(); err != nil {
|
||||
logrus.Errorf("Error during UA3F close: %v", err)
|
||||
}
|
||||
logrus.Info("UA3F exited gracefully")
|
||||
os.Exit(0)
|
||||
|
||||
}()
|
||||
|
||||
if err := srv.Start(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
helper.Close()
|
||||
srv.Close()
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user