feat: enhance user-agent handling and logging in rewriter

This commit is contained in:
SunBK201 2025-11-05 00:40:48 +08:00
parent ab0cf0efdc
commit 2bd091bfcf
5 changed files with 38 additions and 52 deletions

View File

@ -24,6 +24,7 @@ UA3F_GROUP="nogroup"
SKIP_GIDS=""
SIDECAR="OC"
FAKEIP_RANGE="198.18.0.0/16, 198.18.0.1/15, 28.0.0.1/8"
SKIP_PORTS="22,51080,51090"
LOG_FILE="/var/log/ua3f/ua3f.log"
@ -195,6 +196,7 @@ fw_setup_nft_tproxy_tcp() {
nft add rule ip $NFT_TABLE prerouting mark {0x1ed4} counter return comment '"sc tproxy mark 7892"'
nft add rule ip $NFT_TABLE prerouting ip daddr {$FAKEIP_RANGE} counter return comment '"fakeip range"'
nft add rule ip $NFT_TABLE prerouting ip daddr @$UA3F_LANSET counter return
nft add rule ip $NFT_TABLE prerouting tcp dport {$SKIP_PORTS} return
nft add rule ip $NFT_TABLE prerouting meta l4proto tcp mark $UA3F_FWMARK tproxy to 127.0.0.1:$SERVER_PORT counter accept comment '"cap oc"'
nft add rule ip $NFT_TABLE prerouting meta l4proto tcp mark set $UA3F_FWMARK tproxy to 127.0.0.1:$SERVER_PORT counter accept comment '"default less hit. sc"'
@ -205,6 +207,7 @@ fw_setup_nft_tproxy_tcp() {
nft add rule ip $NFT_TABLE output ip daddr {$FAKEIP_RANGE} counter return
nft add rule ip $NFT_TABLE output meta skgid {$SKIP_GIDS} counter return
nft add rule ip $NFT_TABLE output ip daddr @$UA3F_LANSET counter return
nft add rule ip $NFT_TABLE output tcp dport {$SKIP_PORTS} return
nft add rule ip $NFT_TABLE output meta l4proto tcp meta skgid $UA3F_GID mark set $UA3F_FWMARK counter accept comment '"ghost oc"'
nft add rule ip $NFT_TABLE output meta l4proto tcp mark set $UA3F_FWMARK counter accept comment '"default tproxy mark. bypass sc pre pollution"'
}
@ -220,6 +223,7 @@ fw_setup_nft_redirect_tcp() {
nft add rule ip $NFT_TABLE prerouting mark {0x162} counter return comment '"354"'
nft add rule ip $NFT_TABLE prerouting mark {0x1ed4} counter return comment '"sc tproxy mark 7892"'
nft add rule ip $NFT_TABLE prerouting ip daddr {$FAKEIP_RANGE} counter return comment '"fakeip range"'
nft add rule ip $NFT_TABLE prerouting tcp dport {$SKIP_PORTS} return
nft add rule ip $NFT_TABLE prerouting ip daddr @$UA3F_LANSET counter return
nft add rule ip $NFT_TABLE prerouting tcp dport != {22} counter redirect to :$SERVER_PORT
@ -230,6 +234,7 @@ fw_setup_nft_redirect_tcp() {
nft add rule ip $NFT_TABLE output ip daddr {$FAKEIP_RANGE} counter return
nft add rule ip $NFT_TABLE output ip daddr @$UA3F_LANSET counter return
nft add rule ip $NFT_TABLE output meta skgid {$SKIP_GIDS} counter return
nft add rule ip $NFT_TABLE output tcp dport {$SKIP_PORTS} return
nft add rule ip $NFT_TABLE output meta l4proto tcp mark {0x1ed6} counter redirect to :$SERVER_PORT comment '"cap sc meta"'
nft add rule ip $NFT_TABLE output meta skgid $UA3F_GID tcp dport != {22} counter redirect to :$SERVER_PORT comment '"cap oc"'
nft add rule ip $NFT_TABLE output tcp dport != {22} counter redirect to :$SERVER_PORT comment '"cap scc"'
@ -280,6 +285,7 @@ fw_setup_ipt_tproxy_tcp() {
iptables -t mangle -A $UA3F_CHAIN -d 198.18.0.0/16 -j RETURN
iptables -t mangle -A $UA3F_CHAIN -d 28.0.0.1/8 -j RETURN
iptables -t mangle -A $UA3F_CHAIN -d 198.18.0.1/15 -j RETURN
iptables -t mangle -A $UA3F_CHAIN -p tcp -m multiport --dports $SKIP_PORTS -j RETURN
iptables -t mangle -A $UA3F_CHAIN -m set --match-set $UA3F_LANSET dst -j RETURN
iptables -t mangle -A $UA3F_CHAIN -p tcp -m mark --mark $UA3F_FWMARK -j TPROXY --on-ip 127.0.0.1 --on-port $SERVER_PORT
iptables -t mangle -A $UA3F_CHAIN -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port $SERVER_PORT --tproxy-mark $UA3F_FWMARK
@ -294,6 +300,7 @@ fw_setup_ipt_tproxy_tcp() {
iptables -t mangle -A $UA3F_OUT_CHAIN -d 198.18.0.0/16 -j RETURN
iptables -t mangle -A $UA3F_OUT_CHAIN -d 28.0.0.1/8 -j RETURN
iptables -t mangle -A $UA3F_OUT_CHAIN -d 198.18.0.1/15 -j RETURN
iptables -t mangle -A $UA3F_OUT_CHAIN -p tcp -m multiport --dports $SKIP_PORTS -j RETURN
iptables -t mangle -A $UA3F_OUT_CHAIN -p tcp -m owner --gid-owner 453 -j RETURN
iptables -t mangle -A $UA3F_OUT_CHAIN -m set --match-set $UA3F_LANSET dst -j RETURN
iptables -t mangle -A $UA3F_OUT_CHAIN -p tcp -m owner --gid-owner $UA3F_GID -j MARK --set-mark $UA3F_FWMARK
@ -316,6 +323,7 @@ fw_setup_ipt_redirect_tcp() {
iptables -t nat -A $UA3F_CHAIN -d 198.18.0.0/16 -j RETURN
iptables -t nat -A $UA3F_CHAIN -d 28.0.0.1/8 -j RETURN
iptables -t nat -A $UA3F_CHAIN -d 198.18.0.1/15 -j RETURN
iptables -t nat -A $UA3F_CHAIN -p tcp -m multiport --dports $SKIP_PORTS -j RETURN
iptables -t nat -A $UA3F_CHAIN -m set --match-set $UA3F_LANSET dst -j RETURN
iptables -t nat -A $UA3F_CHAIN -p tcp -j REDIRECT --to-ports $SERVER_PORT
@ -329,6 +337,7 @@ fw_setup_ipt_redirect_tcp() {
iptables -t nat -A $UA3F_OUT_CHAIN -d 198.18.0.0/16 -j RETURN
iptables -t nat -A $UA3F_OUT_CHAIN -d 28.0.0.1/8 -j RETURN
iptables -t nat -A $UA3F_OUT_CHAIN -d 198.18.0.1/15 -j RETURN
iptables -t nat -A $UA3F_OUT_CHAIN -p tcp -m multiport --dports $SKIP_PORTS -j RETURN
iptables -t nat -A $UA3F_OUT_CHAIN -m set --match-set $UA3F_LANSET dst -j RETURN
iptables -t nat -A $UA3F_OUT_CHAIN -m owner --gid-owner 453 -j RETURN
iptables -t nat -A $UA3F_OUT_CHAIN -p tcp -m mark --mark 0x1ed6 -j REDIRECT --to-ports $SERVER_PORT

View File

@ -48,6 +48,7 @@ func New(cfg *config.Config) (*Rewriter, error) {
whitelist: []string{
"MicroMessenger Client",
"Bilibili Freedoooooom/MarkII",
"Valve/Steam HTTP Client 1.0",
"Go-http-client/1.1",
"ByteDancePcdn",
},
@ -78,25 +79,33 @@ func (r *Rewriter) buildUserAgent(originUA string) string {
func (r *Rewriter) ShouldRewrite(req *http.Request, srcAddr, destAddr string) bool {
originalUA := req.Header.Get("User-Agent")
log.LogInfoWithAddr(srcAddr, destAddr, fmt.Sprintf("Original User-Agent: (%s)", originalUA))
log.LogInfoWithAddr(srcAddr, destAddr, fmt.Sprintf("original User-Agent: (%s)", originalUA))
if originalUA == "" {
req.Header.Set("User-Agent", "")
}
var err error
matches := true
matches := false
isWhitelist := r.inWhitelist(originalUA)
if !isWhitelist && r.pattern != "" {
if !isWhitelist {
if r.pattern == "" {
matches = true
} else {
matches, err = r.uaRegex.MatchString(originalUA)
if err != nil {
log.LogErrorWithAddr(srcAddr, destAddr, fmt.Sprintf("User-Agent Regex Match Error: %s", err.Error()))
log.LogErrorWithAddr(srcAddr, destAddr, fmt.Sprintf("User-Agent regex match error: %s", err.Error()))
matches = true
}
}
}
if isWhitelist {
log.LogInfoWithAddr(srcAddr, destAddr, fmt.Sprintf("Hit User-Agent Whitelist: %s", originalUA))
log.LogInfoWithAddr(srcAddr, destAddr, fmt.Sprintf("hit User-Agent whitelist: %s, add to cache", originalUA))
r.Cache.Add(destAddr, struct{}{})
}
if !matches {
log.LogDebugWithAddr(srcAddr, destAddr, fmt.Sprintf("Not Hit User-Agent Regex: %s", originalUA))
log.LogDebugWithAddr(srcAddr, destAddr, fmt.Sprintf("not hit User-Agent regex: %s", originalUA))
}
hit := !isWhitelist && matches
@ -140,12 +149,14 @@ func (r *Rewriter) Process(dst net.Conn, src net.Conn, destAddr string, srcAddr
if err != nil {
log.LogDebugWithAddr(srcAddr, destAddr, fmt.Sprintf("Process: %s", err.Error()))
}
io.Copy(dst, reader)
if _, err = io.Copy(dst, reader); err != nil {
log.LogErrorWithAddr(srcAddr, destAddr, fmt.Sprintf("Process io.Copy: %s", err.Error()))
}
}()
if strings.HasSuffix(destAddr, "443") && sniff.SniffTLSClientHello(reader) {
r.Cache.Add(destAddr, struct{}{})
log.LogInfoWithAddr(srcAddr, destAddr, "tls client hello detected, pass forward")
log.LogInfoWithAddr(srcAddr, destAddr, "tls client hello detected, added to cache")
return
}
@ -157,7 +168,7 @@ func (r *Rewriter) Process(dst net.Conn, src net.Conn, destAddr string, srcAddr
}
if !isHTTP {
r.Cache.Add(destAddr, struct{}{})
log.LogInfoWithAddr(srcAddr, destAddr, "Not HTTP, added to cache")
log.LogInfoWithAddr(srcAddr, destAddr, "sniff first request is not http, added to cache, switching to raw proxy")
return
}
@ -165,12 +176,11 @@ func (r *Rewriter) Process(dst net.Conn, src net.Conn, destAddr string, srcAddr
for {
if isHTTP, err = sniff.SniffHTTPFast(reader); err != nil {
err = fmt.Errorf("isHTTP: %w", err)
err = fmt.Errorf("sniff.SniffHTTPFast: %w", err)
return
}
if !isHTTP {
r.Cache.Add(destAddr, struct{}{})
log.LogInfoWithAddr(srcAddr, destAddr, "Not HTTP, added to LRU Relay Cache")
log.LogWarnWithAddr(srcAddr, destAddr, "sniff subsequent request is not http, switching to raw proxy")
return
}
if req, err = http.ReadRequest(reader); err != nil {
@ -185,7 +195,7 @@ func (r *Rewriter) Process(dst net.Conn, src net.Conn, destAddr string, srcAddr
return
}
if req.Header.Get("Upgrade") == "websocket" && req.Header.Get("Connection") == "Upgrade" {
log.LogInfoWithAddr(srcAddr, destAddr, "WebSocket Upgrade detected, switching to raw proxy")
log.LogInfoWithAddr(srcAddr, destAddr, "websocket upgrade detected, switching to raw proxy")
return
}
}

View File

@ -34,6 +34,7 @@ func CopyHalf(dst, src net.Conn) {
} else {
_ = src.Close()
}
log.LogDebugWithAddr(src.RemoteAddr().String(), dst.RemoteAddr().String(), "Connections half-closed")
}()
_, _ = io.Copy(dst, src)
}
@ -51,12 +52,13 @@ func ProxyHalf(dst, src net.Conn, rw *rewrite.Rewriter, destAddr string) {
} else {
_ = src.Close()
}
log.LogDebugWithAddr(src.RemoteAddr().String(), destAddr, "Connections half-closed")
}()
// Fast path: known pass-through
srcAddr := src.RemoteAddr().String()
if rw.Cache.Contains(destAddr) {
log.LogDebugWithAddr(srcAddr, destAddr, "LRU Relay Cache Hit, pass-through")
log.LogInfoWithAddr(srcAddr, destAddr, fmt.Sprintf("destination (%s) in cache, passing through", destAddr))
io.Copy(dst, src)
return
}

View File

@ -5,7 +5,6 @@ import (
"bytes"
"errors"
"io"
"time"
)
var ErrPeekTimeout = errors.New("peek timeout")
@ -48,28 +47,3 @@ func peekLineString(br *bufio.Reader, maxSize int) (string, error) {
}
return string(lineBytes), nil
}
// PeekWithTimeout peeks n bytes from bufio.Reader with a timeout.
func PeekWithTimeout(r *bufio.Reader, n int, timeout time.Duration) ([]byte, error) {
if buffered := r.Buffered(); buffered >= n {
data, err := r.Peek(n)
return data, err
}
type result struct {
data []byte
err error
}
ch := make(chan result, 1)
go func() {
data, err := r.Peek(n)
ch <- result{data, err}
}()
select {
case res := <-ch:
return res.data, res.err
case <-time.After(timeout):
return nil, ErrPeekTimeout
}
}

View File

@ -3,7 +3,6 @@ package sniff
import (
"bufio"
"strings"
"time"
)
// HTTP methods used to detect HTTP by request line.
@ -22,17 +21,9 @@ func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
// beginWithHTTPMethod peeks the first few bytes to check for known HTTP method prefixes.
func beginWithHTTPMethod(reader *bufio.Reader) (bool, error) {
const maxMethodLen = 7
const minMethodLen = 3
var hint []byte
hint, err := PeekWithTimeout(reader, maxMethodLen, 3*time.Second)
hint, err := reader.Peek(maxMethodLen)
if err != nil {
if err != ErrPeekTimeout {
return false, err
}
hint, err = PeekWithTimeout(reader, minMethodLen+1, time.Second)
if err != nil {
return false, err
}
}
method, _, _ := strings.Cut(string(hint), " ")
for _, m := range methods {