mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 03:05:02 +03:00
fix(xray): block private-range egress in per-peer AmneziaWG IPv6 outbounds
The per-peer IPv6 freedom outbounds the panel injects for AmneziaWG clients were built with empty settings and their routing rule sits ahead of the template's private-range block, so a peer's IPv6 traffic skipped both layers and could reach fc00::/7 hosts on the panel's network. Each per-peer outbound now carries the same private-range finalRules block every other freedom outbound gets.
This commit is contained in:
@@ -962,6 +962,17 @@ func amneziawgV6EgressTag(inboundID int, email string) string {
|
||||
return fmt.Sprintf("amneziawg-v6-%d-%s", inboundID, email)
|
||||
}
|
||||
|
||||
// amneziawgV6EgressSettings carries the template's private-range egress block:
|
||||
// the per-peer rule is matched ahead of the routing-level geoip:private rule.
|
||||
func amneziawgV6EgressSettings() map[string]any {
|
||||
return map[string]any{
|
||||
"finalRules": []any{
|
||||
map[string]any{"action": "block", "ip": []any{"geoip:private"}},
|
||||
map[string]any{"action": "allow"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// injectAmneziawgV6Egress gives every enabled, non-node-hosted AmneziaWG
|
||||
// peer with an IPv6 AllowedIPs entry its own single-purpose freedom
|
||||
// outbound, bound via sendThrough to that exact address, plus a routing
|
||||
@@ -1061,7 +1072,7 @@ func injectAmneziawgV6Egress(cfg *xray.Config, inbounds []*model.Inbound) {
|
||||
"tag": tag,
|
||||
"protocol": "freedom",
|
||||
"sendThrough": v6,
|
||||
"settings": map[string]any{},
|
||||
"settings": amneziawgV6EgressSettings(),
|
||||
})
|
||||
newRules = append(newRules, map[string]any{
|
||||
"type": "field",
|
||||
|
||||
@@ -1053,3 +1053,35 @@ func TestInjectAmneziawgV6Egress_RulesPrependedBeforeExistingRules(t *testing.T)
|
||||
t.Fatalf("the pre-existing rule must survive, got %+v", routing.Rules[1])
|
||||
}
|
||||
}
|
||||
|
||||
// The per-peer rule precedes the template's geoip:private routing rule, so the
|
||||
// outbound itself must block private-range egress, as "direct" does.
|
||||
func TestInjectAmneziawgV6Egress_OutboundBlocksPrivateEgress(t *testing.T) {
|
||||
cfg := egressTestConfig()
|
||||
inbound := amneziawgV6Inbound(1, "awg-1", "eth0", []model.Client{
|
||||
{Email: "a@x", Enable: true, PublicKey: "pub-a", AllowedIPs: []string{"fd86:ea04:1115::2/128"}},
|
||||
})
|
||||
injectAmneziawgSocksThenV6(cfg, []*model.Inbound{inbound})
|
||||
|
||||
var outbounds []struct {
|
||||
Tag string `json:"tag"`
|
||||
Settings struct {
|
||||
FinalRules []any `json:"finalRules"`
|
||||
} `json:"settings"`
|
||||
}
|
||||
if err := json.Unmarshal(cfg.OutboundConfigs, &outbounds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := `[{"action":"block","ip":["geoip:private"]},{"action":"allow"}]`
|
||||
for _, o := range outbounds {
|
||||
if o.Tag != amneziawgV6EgressTag(1, "a@x") {
|
||||
continue
|
||||
}
|
||||
got, _ := json.Marshal(o.Settings.FinalRules)
|
||||
if string(got) != want {
|
||||
t.Fatalf("per-peer outbound finalRules = %s, want %s", got, want)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Fatalf("no outbound tagged %q in %s", amneziawgV6EgressTag(1, "a@x"), cfg.OutboundConfigs)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user