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")
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.readonly = true
log.rows = 30

View File

@ -455,7 +455,7 @@ start_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 port "main" "port" "1080"
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 log_level "main" "log_level" "info"
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="$server_mode"
@ -477,6 +478,7 @@ start_service() {
LOG "User-Agent Regex: $ua_regex"
LOG "Log level: $log_level"
LOG "Partial Replace: $partial_replace"
LOG "Direct Forward: $direct_forward"
LOG "Set TTL: $SET_TTL"
set_ua3f_group
@ -579,6 +581,7 @@ start_service() {
procd_append_param command -r "$ua_regex"
procd_append_param command -l "$log_level"
[ "$partial_replace" = "1" ] && procd_append_param command -s
[ "$direct_forward" = "1" ] && procd_append_param command -d
procd_set_param respawn
procd_set_param stdout 1

View File

@ -7,7 +7,8 @@ config 'ua3f' 'main'
option bind '0.0.0.0'
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 partial_replace false
option partial_replace '0'
option direct_forward '0'
option log_level 'error'
option log_lines '1000'
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"
msgstr "仅替换 User-Agent 正则匹配的部分,仅在 User-Agent 正则表达式非空时有效"
msgid "Direct Forward"
msgstr "直接转发"
msgid "Directly forward packets without rewriting"
msgstr "不进行重写直接转发数据包"
msgid "User-Agent Rewrite Statistics"
msgstr "User-Agent 重写次数实时统计"

View File

@ -6,7 +6,7 @@ require (
github.com/dlclark/regexp2 v1.11.4
github.com/hashicorp/golang-lru/v2 v2.0.7
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
)

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/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=

View File

@ -14,47 +14,51 @@ const (
)
type Config struct {
ServerMode string
BindAddr string
Port int
ListenAddr string
LogLevel string
PayloadUA string
UARegex string
EnablePartialReplace bool
ServerMode string
BindAddr string
Port int
ListenAddr string
LogLevel string
PayloadUA string
UARegex string
PartialReplace bool
DirectForward bool
}
func Parse() (*Config, bool) {
var (
serverMode string
bindAddr string
port int
loglevel string
payloadUA string
uaRegx string
partial bool
showVer bool
serverMode string
bindAddr string
port int
loglevel string
payloadUA string
uaRegx string
partial bool
directForward bool
showVer bool
)
flag.StringVar(&serverMode, "m", ServerModeSocks5, "server mode: HTTP, SOCKS5, TPROXY, REDIRECT (default: SOCKS5)")
flag.StringVar(&bindAddr, "b", "127.0.0.1", "bind address (default: 127.0.0.1)")
flag.IntVar(&port, "p", 1080, "port")
flag.StringVar(&serverMode, "m", ServerModeSocks5, "Server mode: HTTP, SOCKS5, TPROXY, REDIRECT")
flag.StringVar(&bindAddr, "b", "127.0.0.1", "Bind address")
flag.IntVar(&port, "p", 1080, "Port")
flag.StringVar(&loglevel, "l", "info", "Log level")
flag.StringVar(&payloadUA, "f", "FFF", "User-Agent")
flag.StringVar(&uaRegx, "r", "", "UA-Pattern")
flag.BoolVar(&partial, "s", false, "Enable Regex Partial Replace")
flag.StringVar(&loglevel, "l", "info", "Log level (default: info)")
flag.BoolVar(&showVer, "v", false, "show version")
flag.StringVar(&uaRegx, "r", "", "User-Agent regex")
flag.BoolVar(&partial, "s", false, "Enable regex partial replace")
flag.BoolVar(&directForward, "d", false, "Pure Forwarding (no User-Agent rewriting)")
flag.BoolVar(&showVer, "v", false, "Show version")
flag.Parse()
cfg := &Config{
ServerMode: strings.ToUpper(serverMode),
BindAddr: bindAddr,
Port: port,
ListenAddr: fmt.Sprintf("%s:%d", bindAddr, port),
LogLevel: loglevel,
PayloadUA: payloadUA,
UARegex: uaRegx,
EnablePartialReplace: partial,
ServerMode: strings.ToUpper(serverMode),
BindAddr: bindAddr,
Port: port,
ListenAddr: fmt.Sprintf("%s:%d", bindAddr, port),
LogLevel: loglevel,
PayloadUA: payloadUA,
UARegex: uaRegx,
DirectForward: directForward,
PartialReplace: partial,
}
if serverMode == ServerModeRedirect {
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("User-Agent: %s", cfg.PayloadUA)
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)
}

View File

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

View File

@ -26,9 +26,9 @@ func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil }
func newTestRewriter(t *testing.T) *Rewriter {
cfg := &config.Config{
UARegex: "TestUA",
PayloadUA: "MockUA/1.0",
EnablePartialReplace: false,
UARegex: "TestUA",
PayloadUA: "MockUA/1.0",
PartialReplace: false,
}
rewriter, err := New(cfg)
assert.NoError(t, err)
@ -37,15 +37,15 @@ func newTestRewriter(t *testing.T) *Rewriter {
func TestNewRewriter(t *testing.T) {
cfg := &config.Config{
UARegex: "TestUA",
PayloadUA: "FFF0",
EnablePartialReplace: false,
UARegex: "TestUA",
PayloadUA: "FFF0",
PartialReplace: false,
}
rewriter, err := New(cfg)
assert.NoError(t, err)
assert.Equal(t, cfg.PayloadUA, rewriter.payloadUA)
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.Cache)
}

View File

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