mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 19:25:05 +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)
|
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
|
// injectAmneziawgV6Egress gives every enabled, non-node-hosted AmneziaWG
|
||||||
// peer with an IPv6 AllowedIPs entry its own single-purpose freedom
|
// peer with an IPv6 AllowedIPs entry its own single-purpose freedom
|
||||||
// outbound, bound via sendThrough to that exact address, plus a routing
|
// 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,
|
"tag": tag,
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"sendThrough": v6,
|
"sendThrough": v6,
|
||||||
"settings": map[string]any{},
|
"settings": amneziawgV6EgressSettings(),
|
||||||
})
|
})
|
||||||
newRules = append(newRules, map[string]any{
|
newRules = append(newRules, map[string]any{
|
||||||
"type": "field",
|
"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])
|
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