mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-13 08:35:00 +03:00
feat(amneziawg): Phase 2b — per-client port-forwarding
Admins can now set a per-client ForwardedPorts string (e.g. "80, 443, 8000-8100") that gets DNAT'd + FORWARD'd to that peer's tunnel address via iptables rules in PostUp/PostDown, ported and simplified from coinman-dev/3ax-ui's shared/portfwd. Two decisions worth flagging for future readers: - The iptables --comment tag on each rule is awg-fwd-<fnv32a(email)>, not the raw client email. Email is admin/API-supplied free text that ends up embedded in a shell-executed PostUp/PostDown line; a hash can never carry a shell metacharacter through where raw interpolation could. - The reconcile manager gained a third fingerprint (portFwdFP, next to the existing structural/peers ones). `awg syncconf` only touches the WireGuard peer table — it never re-applies PostUp/PostDown iptables rules — so a port-forward-only change has to force a full awg-quick down+up bounce, same as a structural change, rather than the lighter sync a plain peer add/remove can use. Also fixes a real pre-existing bug found while wiring up IPv6 client allocation in the previous commit's spirit: allocateWireguardAddress always suffixed "/32" regardless of address family, which produced invalid host bits for IPv6 (needs "/128"). ForwardedPorts flows through model.Client -> model.ClientRecord (gorm column wg_forwarded_ports, auto-migrated) -> ToRecord/ToClient/ MergeClientRecord, mirroring the awgServer field's earlier lesson that new fields need checking against a second, hand-maintained persistence-layer struct. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -102,6 +102,7 @@ type Values = ClientFormValues & {
|
||||
wgPublicKey: string;
|
||||
wgPreSharedKey: string;
|
||||
wgAllowedIPs: string;
|
||||
awgForwardedPorts: string;
|
||||
secret: string;
|
||||
adTag: string;
|
||||
};
|
||||
@@ -131,6 +132,7 @@ const EMPTY: Values = {
|
||||
wgPublicKey: '',
|
||||
wgPreSharedKey: '',
|
||||
wgAllowedIPs: '',
|
||||
awgForwardedPorts: '',
|
||||
secret: '',
|
||||
adTag: '',
|
||||
};
|
||||
@@ -243,6 +245,7 @@ export default function ClientFormModal({
|
||||
wgPublicKey: client.publicKey || '',
|
||||
wgPreSharedKey: client.preSharedKey || '',
|
||||
wgAllowedIPs: client.allowedIPs || '',
|
||||
awgForwardedPorts: client.forwardedPorts || '',
|
||||
secret: client.secret || '',
|
||||
adTag: client.adTag || '',
|
||||
};
|
||||
@@ -558,6 +561,11 @@ export default function ClientFormModal({
|
||||
if (allowedIPs.length > 0) {
|
||||
clientPayload.allowedIPs = allowedIPs;
|
||||
}
|
||||
// Port-forwarding has no WireGuard equivalent — Xray-native WireGuard
|
||||
// has no host-level iptables layer to hang per-client DNAT off of.
|
||||
if (showAmneziawg) {
|
||||
clientPayload.forwardedPorts = values.awgForwardedPorts.trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (showMtproto) {
|
||||
@@ -900,6 +908,15 @@ export default function ClientFormModal({
|
||||
>
|
||||
<Input placeholder="10.8.1.2/32" />
|
||||
</FormField>
|
||||
{showAmneziawg && (
|
||||
<FormField
|
||||
name="awgForwardedPorts"
|
||||
label={t('pages.clients.amneziaWgForwardedPorts')}
|
||||
extra={t('pages.clients.amneziaWgForwardedPortsHint')}
|
||||
>
|
||||
<Input placeholder="80, 443, 8000-8100" />
|
||||
</FormField>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{showMtproto && (
|
||||
|
||||
Reference in New Issue
Block a user