mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-17 15:47:14 +00:00
fix: port the PR #6105 review-round fixes into this fork's own AmneziaWG code
Same 8 findings fixed on upstream-pr/amneziawg, ported here since this fork's internal/amneziawg + related web/service files predate that PR branch's own fix-up commits: 1. hostRulesFingerprint now folds in a peer's IPv4 whenever ForwardedPorts is set, not only when RouteThroughXray is on, so a re-IP forces the bounce needed to move the DNAT rule too. 2. ValidateConfigValue (new, params.go) rejects control characters in server/client keys, email and I1 at save time; sanitizeConfigValue strips them defensively at .conf-render time. 3. checkForwardedPortsConflict now scopes to node_id IS NULL and takes a pre-loaded portConflictContext (loadPortConflictContext), so a port used only on another node isn't a false collision and an inbound with N clients costs one query instead of N. 4. PostDown commands are now best-effort (appendOrTrue) so an external firewall flush can't abort the rest of the teardown chain. 5. The "ip rule list | grep -q" existence check now uses grep -c >/dev/null, avoiding a pipefail/SIGPIPE false negative that could re-add a duplicate rule. 6. route_egress.go's stale "always present, no opt-in" comment corrected to describe the real RouteThroughXray-gated behavior. (This fork's genAmneziaWGLink already emits vpn://, and there's no upstream-facing docs page here, so neither needed the PR branch's Finding 6 docs/link-format changes.) 7. install.sh: Arch's ndppd install uses pacman -Sy, not -Syu, matching every other pacman call in the script; should_install_amneziawg short-circuits to yes when awg is already installed, so `x-ui update` doesn't re-prompt -- this fork's own opt-out-by-default philosophy for should_install_amneziawg is unchanged, only the redundant-reprompt behavior is fixed. 8. CollectTraffic checks pointer identity before writing back a traffic-counter baseline, so a concurrent restart's freshly-reset (empty) baseline can't be clobbered by stale pre-restart counters. sweepOrphansLocked no longer permanently disables itself on a transient os.ReadDir failure. go build/vet/test and frontend typecheck/lint/build/vitest all pass.
This commit is contained in:
@@ -267,6 +267,19 @@ func TestHostRulesFingerprintCoversForwardedPortsAndPeerIP(t *testing.T) {
|
||||
t.Fatal("with RouteThroughXray off, changing a peer's IP must NOT change the host-rules fingerprint")
|
||||
}
|
||||
|
||||
// A peer with forwarded ports has its DNAT rule keyed on its IPv4 address
|
||||
// too, regardless of RouteThroughXray -- re-IPing it must force a bounce,
|
||||
// or the old DNAT rule survives pointed at an address a different peer
|
||||
// can be handed next.
|
||||
forwardedReIPed := baseInstance()
|
||||
forwardedReIPed.Peers[0].ForwardedPorts = "80,443"
|
||||
forwardedBase := baseInstance()
|
||||
forwardedBase.Peers[0].ForwardedPorts = "80,443"
|
||||
forwardedReIPed.Peers[0].AllowedIPs = []string{"10.8.1.250/32"}
|
||||
if forwardedBase.hostRulesFingerprint() == forwardedReIPed.hostRulesFingerprint() {
|
||||
t.Fatal("with RouteThroughXray off but ForwardedPorts set, changing a peer's IP must change the host-rules fingerprint -- its DNAT rule is keyed on that IP")
|
||||
}
|
||||
|
||||
// RouteThroughXray on: now the TPROXY rule really is keyed on the IP.
|
||||
routed := baseInstance()
|
||||
routed.RouteThroughXray = true
|
||||
@@ -383,8 +396,12 @@ func TestDefaultPostUpDownEmitsTproxyForEveryPeerWhenRouteThroughXrayOn(t *testi
|
||||
if !strings.Contains(up, fmt.Sprintf("ip rule add fwmark %#x", EgressFwmark)) {
|
||||
t.Errorf("expected the shared policy route to be added once in PostUp, got:\n%s", up)
|
||||
}
|
||||
if wantCheck := fmt.Sprintf("ip rule list | grep -q 'fwmark %#x lookup %d'", EgressFwmark, EgressTable); !strings.Contains(up, wantCheck) {
|
||||
t.Errorf("expected an existence check before 'ip rule add', so repeated bounces don't accumulate duplicate rules, got:\n%s", up)
|
||||
// grep -c, not -q: -q's early exit can SIGPIPE "ip rule list" and, under
|
||||
// pipefail, make the existence check itself report failure even when the
|
||||
// rule was found -- which would re-run "ip rule add" and reintroduce the
|
||||
// exact duplicate this check exists to prevent.
|
||||
if wantCheck := fmt.Sprintf("ip rule list | grep -c 'fwmark %#x lookup %d' >/dev/null", EgressFwmark, EgressTable); !strings.Contains(up, wantCheck) {
|
||||
t.Errorf("expected a pipefail-safe existence check before 'ip rule add', so repeated bounces don't accumulate duplicate rules, got:\n%s", up)
|
||||
}
|
||||
if strings.Contains(down, "ip rule") || strings.Contains(down, "ip route") {
|
||||
t.Error("the shared policy route must never be removed in PostDown -- other instances may still need it")
|
||||
@@ -430,6 +447,29 @@ func TestDefaultPostUpDownAddsInputAcceptForFwmarkWhenRouteThroughXrayOn(t *test
|
||||
}
|
||||
}
|
||||
|
||||
// PostDown is joined with "; " and run under `set -e`, so one command that
|
||||
// fails because something already flushed the firewall state out from under
|
||||
// the interface (a ufw/firewalld reload) would otherwise abort every command
|
||||
// after it -- including the nat-table DNAT deletes a filter-table flush does
|
||||
// NOT remove, which then survive and accumulate across bounces. Every
|
||||
// teardown command must be best-effort; PostUp must not be.
|
||||
func TestDefaultPostUpDownMakesEveryTeardownCommandBestEffort(t *testing.T) {
|
||||
inst := baseInstance() // two peers, a@x and b@x
|
||||
inst.RouteThroughXray = true
|
||||
inst.IPv6Enabled = true
|
||||
inst.Peers[0].ForwardedPorts = "80,443"
|
||||
up, down := defaultPostUpDown(inst, "eth0")
|
||||
|
||||
for _, cmd := range strings.Split(down, "; ") {
|
||||
if !strings.HasSuffix(cmd, "|| true") {
|
||||
t.Errorf("every PostDown command must end with '|| true' so a flushed firewall doesn't abort the rest, got: %q", cmd)
|
||||
}
|
||||
}
|
||||
if strings.Contains(up, "|| true") {
|
||||
t.Error("PostUp must stay strict -- a real setup failure there should surface, not be silently swallowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateServerConfigContainsExpectedLines(t *testing.T) {
|
||||
inst := baseInstance()
|
||||
inst.ExternalInterface = "eth0"
|
||||
|
||||
Reference in New Issue
Block a user