Przeglądaj źródła

fix(amneziawg): read the outbound pseudo-protocol id like the core (#6531)

* fix(amneziawg): read the outbound pseudo-protocol id like the core

IsAmneziaWGOutbound compared the id exactly while every reader around it does
not: the probe lane already reads the same id with strings.EqualFold
(outbound/probe_http.go, pinned by TestBuildBatchTestConfigReadsTheProtocolIDLikeTheCore),
and the core lowercases a protocol id before it resolves the handler.

A template entry spelled "AmneziaWG" therefore stayed unbridged in two paths.
transformAmneziaWGOutbounds skipped it and handed the raw pseudo-protocol to
the core, which answers "unknown config id: amneziawg" -- Xray then fails to
start, since bridging is what makes that entry a socks outbound. The amneziawg
job skipped it too, so the reconcile loop never created the instance and the
outbound silently carried no tunnel.

The exact comparison also made the save path answer two ways for one spelling:
CheckXrayConfig routed the exact match to the panel's own validator and the
case variant to the core's, so the operator was told the core does not know a
protocol the panel implements (probe output, before: `xray core rejects
outbound "t1": infra/conf: unknown config id: amneziawg` for "AmneziaWG" and
`amneziawg outbound "t1": privateKey is required` for "amneziawg"; after: the
panel's own message for both).

Reachable only from a template that did not come through the panel's save,
which rejects the case variant today -- a restored backup, a direct DB edit, a
scripted template, or a legacy DB. That is the same class of data the
UppercaseFreedomFinalRulesFix seeder exists to repair, so the panel already
treats non-lowercase protocol ids as real operator input.

strings.EqualFold is the whole change; the package already imports strings.

* style(service): trim the amneziawg outbound test comment to two lines

The review flagged the three-line block: CLAUDE.md caps a committed Go
comment block at two lines and the test name already carries the what. The
remaining two lines keep the why — the core folds the id's case before
resolving it, so a mixed-case spelling must bridge here too.
BlindMaster24 15 godzin temu
rodzic
commit
78ab7a9246

+ 1 - 1
internal/amneziawg/outbound.go

@@ -112,7 +112,7 @@ func IsAmneziaWGOutbound(raw []byte) bool {
 	if err := json.Unmarshal(raw, &probe); err != nil {
 		return false
 	}
-	return probe.Protocol == "amneziawg"
+	return strings.EqualFold(probe.Protocol, "amneziawg")
 }
 
 // outboundSettingsOf extracts the nested "settings" block from a raw

+ 46 - 0
internal/web/service/xray_amneziawg_outbound_test.go

@@ -31,6 +31,52 @@ func makeAWGOutboundConfig(t *testing.T) *xray.Config {
 	return cfg
 }
 
+// The core folds the protocol id's case before resolving it, so a mixed-case
+// spelling must bridge here too or the raw pseudo-protocol reaches the core.
+func TestTransformAmneziaWGOutbounds_ReadsTheProtocolIDLikeTheCore(t *testing.T) {
+	for _, protocol := range []string{"amneziawg", "AmneziaWG", "AMNEZIAWG"} {
+		t.Run(protocol, func(t *testing.T) {
+			cfg := &xray.Config{}
+			raw := `{"outbounds":[
+				{"protocol":"freedom","tag":"direct"},
+				{"protocol":"` + protocol + `","tag":"awg-hop","settings":{"secretKey":"x"}}
+			]}`
+			if err := json.Unmarshal([]byte(raw), cfg); err != nil {
+				t.Fatal(err)
+			}
+			if err := transformAmneziaWGOutbounds(cfg); err != nil {
+				t.Fatal(err)
+			}
+
+			var outbounds []struct {
+				Protocol string `json:"protocol"`
+				Tag      string `json:"tag"`
+				Settings struct {
+					Address string `json:"address"`
+					Port    int    `json:"port"`
+					User    string `json:"user"`
+				} `json:"settings"`
+			}
+			if err := json.Unmarshal(cfg.OutboundConfigs, &outbounds); err != nil {
+				t.Fatal(err)
+			}
+			if len(outbounds) != 2 {
+				t.Fatalf("outbound count = %d, want 2 (no additions or drops)", len(outbounds))
+			}
+			got := outbounds[1]
+			if got.Protocol != "socks" {
+				t.Errorf("protocol = %q, want %q: the bridge never ran, so the raw pseudo-protocol reaches the core", got.Protocol, "socks")
+			}
+			if got.Tag != "awg-hop" {
+				t.Errorf("tag = %q, want %q", got.Tag, "awg-hop")
+			}
+			if got.Settings.Address != "127.0.0.1" || got.Settings.Port != amneziawgnetEgressPortForTest() || got.Settings.User != "awg-hop" {
+				t.Errorf("settings = %+v, want the socks bridge for tag %q on port %d", got.Settings, "awg-hop", amneziawgnetEgressPortForTest())
+			}
+		})
+	}
+}
+
 func TestTransformAmneziaWGOutbounds(t *testing.T) {
 	cfg := makeAWGOutboundConfig(t)
 	if err := transformAmneziaWGOutbounds(cfg); err != nil {