mirror of
https://github.com/SunBK201/UA3F.git
synced 2025-12-16 16:57:08 +00:00
feat: enhance user-agent handling and logging in rewriter
This commit is contained in:
parent
0a01c9f04f
commit
29183ad307
@ -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
|
||||
|
||||
@ -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 != "" {
|
||||
matches, err = r.uaRegex.MatchString(originalUA)
|
||||
if err != nil {
|
||||
log.LogErrorWithAddr(srcAddr, destAddr, fmt.Sprintf("User-Agent Regex Match Error: %s", err.Error()))
|
||||
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()))
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user