feat: add direct forwarding option

This commit is contained in:
SunBK201 2025-11-06 13:24:03 +08:00
parent 47a2a1d29a
commit 77ca782fa2
14 changed files with 97 additions and 49 deletions

View File

@ -66,6 +66,11 @@ partialRepalce.description =
translate("Replace only the matched part of the User-Agent, only works when User-Agent Regex Pattern is not empty") translate("Replace only the matched part of the User-Agent, only works when User-Agent Regex Pattern is not empty")
partialRepalce.default = "0" partialRepalce.default = "0"
directForward = general:taboption("general", Flag, "direct_forward", translate("Direct Forward"))
directForward.description =
translate("Directly forward packets without rewriting")
directForward.default = "0"
log = general:taboption("log", TextValue, "log") log = general:taboption("log", TextValue, "log")
log.readonly = true log.readonly = true
log.rows = 30 log.rows = 30

View File

@ -455,7 +455,7 @@ start_service() {
LOG "Starting $NAME service..." LOG "Starting $NAME service..."
local port bind ua log_level ua_regex partial_replace set_ttl local port bind ua log_level ua_regex partial_replace set_ttl direct_forward
config_get server_mode "main" "server_mode" "SOCKS5" config_get server_mode "main" "server_mode" "SOCKS5"
config_get port "main" "port" "1080" config_get port "main" "port" "1080"
config_get bind "main" "bind" "127.0.0.1" config_get bind "main" "bind" "127.0.0.1"
@ -464,6 +464,7 @@ start_service() {
config_get_bool partial_replace "main" "partial_replace" 0 config_get_bool partial_replace "main" "partial_replace" 0
config_get log_level "main" "log_level" "info" config_get log_level "main" "log_level" "info"
config_get_bool set_ttl "main" "set_ttl" 0 config_get_bool set_ttl "main" "set_ttl" 0
config_get_bool direct_forward "main" "direct_forward" 0
SERVER_MODE="$(echo "$server_mode" | tr '[:lower:]' '[:upper:]')" SERVER_MODE="$(echo "$server_mode" | tr '[:lower:]' '[:upper:]')"
SERVER_MODE="$server_mode" SERVER_MODE="$server_mode"
@ -477,6 +478,7 @@ start_service() {
LOG "User-Agent Regex: $ua_regex" LOG "User-Agent Regex: $ua_regex"
LOG "Log level: $log_level" LOG "Log level: $log_level"
LOG "Partial Replace: $partial_replace" LOG "Partial Replace: $partial_replace"
LOG "Direct Forward: $direct_forward"
LOG "Set TTL: $SET_TTL" LOG "Set TTL: $SET_TTL"
set_ua3f_group set_ua3f_group
@ -579,6 +581,7 @@ start_service() {
procd_append_param command -r "$ua_regex" procd_append_param command -r "$ua_regex"
procd_append_param command -l "$log_level" procd_append_param command -l "$log_level"
[ "$partial_replace" = "1" ] && procd_append_param command -s [ "$partial_replace" = "1" ] && procd_append_param command -s
[ "$direct_forward" = "1" ] && procd_append_param command -d
procd_set_param respawn procd_set_param respawn
procd_set_param stdout 1 procd_set_param stdout 1

View File

@ -7,7 +7,8 @@ config 'ua3f' 'main'
option bind '0.0.0.0' option bind '0.0.0.0'
option ua 'FFF' option ua 'FFF'
option ua_regex '(Apple|iPhone|iPad|Macintosh|Mac OS X|Mac|Darwin|Microsoft|Windows|Linux|Android|OpenHarmony|HUAWEI|OPPO|Vivo|XiaoMi|Mobile|Dalvik)' option ua_regex '(Apple|iPhone|iPad|Macintosh|Mac OS X|Mac|Darwin|Microsoft|Windows|Linux|Android|OpenHarmony|HUAWEI|OPPO|Vivo|XiaoMi|Mobile|Dalvik)'
option partial_replace false option partial_replace '0'
option direct_forward '0'
option log_level 'error' option log_level 'error'
option log_lines '1000' option log_lines '1000'
option set_ttl '0' option set_ttl '0'

View File

@ -56,6 +56,12 @@ msgstr "部分替换"
msgid "Replace only the matched part of the User-Agent, only works when User-Agent Regex Pattern is not empty" msgid "Replace only the matched part of the User-Agent, only works when User-Agent Regex Pattern is not empty"
msgstr "仅替换 User-Agent 正则匹配的部分,仅在 User-Agent 正则表达式非空时有效" msgstr "仅替换 User-Agent 正则匹配的部分,仅在 User-Agent 正则表达式非空时有效"
msgid "Direct Forward"
msgstr "直接转发"
msgid "Directly forward packets without rewriting"
msgstr "不进行重写直接转发数据包"
msgid "User-Agent Rewrite Statistics" msgid "User-Agent Rewrite Statistics"
msgstr "User-Agent 重写次数实时统计" msgstr "User-Agent 重写次数实时统计"

View File

@ -6,7 +6,7 @@ require (
github.com/dlclark/regexp2 v1.11.4 github.com/dlclark/regexp2 v1.11.4
github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
golang.org/x/sys v0.26.0 golang.org/x/sys v0.30.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )

View File

@ -14,8 +14,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=

View File

@ -21,7 +21,8 @@ type Config struct {
LogLevel string LogLevel string
PayloadUA string PayloadUA string
UARegex string UARegex string
EnablePartialReplace bool PartialReplace bool
DirectForward bool
} }
func Parse() (*Config, bool) { func Parse() (*Config, bool) {
@ -33,17 +34,19 @@ func Parse() (*Config, bool) {
payloadUA string payloadUA string
uaRegx string uaRegx string
partial bool partial bool
directForward bool
showVer bool showVer bool
) )
flag.StringVar(&serverMode, "m", ServerModeSocks5, "server mode: HTTP, SOCKS5, TPROXY, REDIRECT (default: SOCKS5)") flag.StringVar(&serverMode, "m", ServerModeSocks5, "Server mode: HTTP, SOCKS5, TPROXY, REDIRECT")
flag.StringVar(&bindAddr, "b", "127.0.0.1", "bind address (default: 127.0.0.1)") flag.StringVar(&bindAddr, "b", "127.0.0.1", "Bind address")
flag.IntVar(&port, "p", 1080, "port") flag.IntVar(&port, "p", 1080, "Port")
flag.StringVar(&loglevel, "l", "info", "Log level")
flag.StringVar(&payloadUA, "f", "FFF", "User-Agent") flag.StringVar(&payloadUA, "f", "FFF", "User-Agent")
flag.StringVar(&uaRegx, "r", "", "UA-Pattern") flag.StringVar(&uaRegx, "r", "", "User-Agent regex")
flag.BoolVar(&partial, "s", false, "Enable Regex Partial Replace") flag.BoolVar(&partial, "s", false, "Enable regex partial replace")
flag.StringVar(&loglevel, "l", "info", "Log level (default: info)") flag.BoolVar(&directForward, "d", false, "Pure Forwarding (no User-Agent rewriting)")
flag.BoolVar(&showVer, "v", false, "show version") flag.BoolVar(&showVer, "v", false, "Show version")
flag.Parse() flag.Parse()
cfg := &Config{ cfg := &Config{
@ -54,7 +57,8 @@ func Parse() (*Config, bool) {
LogLevel: loglevel, LogLevel: loglevel,
PayloadUA: payloadUA, PayloadUA: payloadUA,
UARegex: uaRegx, UARegex: uaRegx,
EnablePartialReplace: partial, DirectForward: directForward,
PartialReplace: partial,
} }
if serverMode == ServerModeRedirect { if serverMode == ServerModeRedirect {
cfg.BindAddr = "0.0.0.0" cfg.BindAddr = "0.0.0.0"

View File

@ -74,7 +74,8 @@ func LogHeader(version string, cfg *config.Config) {
logrus.Infof("Listen on %s", cfg.ListenAddr) logrus.Infof("Listen on %s", cfg.ListenAddr)
logrus.Infof("User-Agent: %s", cfg.PayloadUA) logrus.Infof("User-Agent: %s", cfg.PayloadUA)
logrus.Infof("User-Agent Regex: '%s'", cfg.UARegex) logrus.Infof("User-Agent Regex: '%s'", cfg.UARegex)
logrus.Infof("Partial Replace: %v", cfg.EnablePartialReplace) logrus.Infof("Partial Replace: %v", cfg.PartialReplace)
logrus.Infof("Direct Forward: %v", cfg.DirectForward)
logrus.Infof("Log level: %s", cfg.LogLevel) logrus.Infof("Log level: %s", cfg.LogLevel)
} }

View File

@ -44,7 +44,7 @@ func New(cfg *config.Config) (*Rewriter, error) {
return &Rewriter{ return &Rewriter{
payloadUA: cfg.PayloadUA, payloadUA: cfg.PayloadUA,
pattern: cfg.UARegex, pattern: cfg.UARegex,
partialReplace: cfg.EnablePartialReplace, partialReplace: cfg.PartialReplace,
uaRegex: uaRegex, uaRegex: uaRegex,
Cache: expirable.NewLRU[string, struct{}](1024, nil, 30*time.Minute), Cache: expirable.NewLRU[string, struct{}](1024, nil, 30*time.Minute),
whitelist: []string{ whitelist: []string{

View File

@ -28,7 +28,7 @@ func newTestRewriter(t *testing.T) *Rewriter {
cfg := &config.Config{ cfg := &config.Config{
UARegex: "TestUA", UARegex: "TestUA",
PayloadUA: "MockUA/1.0", PayloadUA: "MockUA/1.0",
EnablePartialReplace: false, PartialReplace: false,
} }
rewriter, err := New(cfg) rewriter, err := New(cfg)
assert.NoError(t, err) assert.NoError(t, err)
@ -39,13 +39,13 @@ func TestNewRewriter(t *testing.T) {
cfg := &config.Config{ cfg := &config.Config{
UARegex: "TestUA", UARegex: "TestUA",
PayloadUA: "FFF0", PayloadUA: "FFF0",
EnablePartialReplace: false, PartialReplace: false,
} }
rewriter, err := New(cfg) rewriter, err := New(cfg)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, cfg.PayloadUA, rewriter.payloadUA) assert.Equal(t, cfg.PayloadUA, rewriter.payloadUA)
assert.Equal(t, cfg.UARegex, rewriter.pattern) assert.Equal(t, cfg.UARegex, rewriter.pattern)
assert.Equal(t, cfg.EnablePartialReplace, rewriter.partialReplace) assert.Equal(t, cfg.PartialReplace, rewriter.partialReplace)
assert.NotNil(t, rewriter.uaRegex) assert.NotNil(t, rewriter.uaRegex)
assert.NotNil(t, rewriter.Cache) assert.NotNil(t, rewriter.Cache)
} }

View File

@ -58,11 +58,19 @@ func (s *Server) handleHTTP(w http.ResponseWriter, req *http.Request) {
} }
defer target.Close() defer target.Close()
if s.cfg.DirectForward {
err = req.Write(target)
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
} else {
err = s.rewriteAndForward(target, req, req.Host, req.RemoteAddr) err = s.rewriteAndForward(target, req, req.Host, req.RemoteAddr)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable) http.Error(w, err.Error(), http.StatusServiceUnavailable)
return return
} }
}
resp, err := http.ReadResponse(bufio.NewReader(target), req) resp, err := http.ReadResponse(bufio.NewReader(target), req)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable) http.Error(w, err.Error(), http.StatusServiceUnavailable)
@ -123,6 +131,11 @@ func (s *Server) ForwardTCP(client, target net.Conn, destAddr string) {
// Server -> Client (raw) // Server -> Client (raw)
go utils.CopyHalf(client, target) go utils.CopyHalf(client, target)
if s.cfg.DirectForward {
// Client -> Server (raw)
go utils.CopyHalf(target, client)
return
}
// Client -> Server (rewriter) // Client -> Server (rewriter)
go utils.ProxyHalf(target, client, s.rw, destAddr) go utils.ProxyHalf(target, client, s.rw, destAddr)
} }

View File

@ -71,6 +71,11 @@ func (s *Server) ForwardTCP(client, target net.Conn, destAddr string) {
// Server -> Client (raw) // Server -> Client (raw)
go utils.CopyHalf(client, target) go utils.CopyHalf(client, target)
if s.cfg.DirectForward {
// Client -> Server (raw)
go utils.CopyHalf(target, client)
return
}
// Client -> Server (rewriter) // Client -> Server (rewriter)
go utils.ProxyHalf(target, client, s.rw, destAddr) go utils.ProxyHalf(target, client, s.rw, destAddr)
} }

View File

@ -213,6 +213,11 @@ func (s *Server) ForwardTCP(client, target net.Conn, destAddr string) {
// Server -> Client (raw) // Server -> Client (raw)
go utils.CopyHalf(client, target) go utils.CopyHalf(client, target)
if s.cfg.DirectForward {
// Client -> Server (raw)
go utils.CopyHalf(target, client)
return
}
// Client -> Server (rewriter) // Client -> Server (rewriter)
go utils.ProxyHalf(target, client, s.rw, destAddr) go utils.ProxyHalf(target, client, s.rw, destAddr)
} }

View File

@ -92,6 +92,11 @@ func (s *Server) ForwardTCP(client, target net.Conn, destAddr string) {
// Server -> Client (raw) // Server -> Client (raw)
go utils.CopyHalf(client, target) go utils.CopyHalf(client, target)
if s.cfg.DirectForward {
// Client -> Server (raw)
go utils.CopyHalf(target, client)
return
}
// Client -> Server (rewriter) // Client -> Server (rewriter)
go utils.ProxyHalf(target, client, s.rw, destAddr) go utils.ProxyHalf(target, client, s.rw, destAddr)
} }