Skip to content

Commit 79d04f6

Browse files
committed
feat: agregar soporte para alias de router en configuraciones S2S de OpenVPN
1 parent 308dc02 commit 79d04f6

File tree

6 files changed

+221
-8
lines changed

6 files changed

+221
-8
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,17 @@ OVPNS2S_LOCAL_INTERFACE=""
145145
OVPNS2S_LOG_ENABLED=false
146146
OVPNS2S_LOG_PREFIX="VPN_S2S_TRAFFIC: "
147147

148+
# (Opcional) Alias del router/LAN en modo S2S (DNAT)
149+
# Útil cuando hay subredes solapadas (ej. 192.168.1.0/24 en ambos sitios)
150+
# Formato multi-alias: "real_ip;alias_ip,real_ip;alias_ip"
151+
OVPNS2S_ROUTER_ALIAS=""
152+
153+
# Formato legacy (single alias, compatible)
154+
OVPNS2S_ROUTER_REAL_IP=""
155+
OVPNS2S_ROUTER_ALIAS_IP=""
156+
# Si true, hace SNAT de retorno hacia el router real (requiere OVPNS2S_LOCAL_INTERFACE)
157+
OVPNS2S_ROUTER_ALIAS_SNAT=false
158+
148159
# Puertos extra permitidos (servicios adicionales)
149160
# Formato: "puerto,puerto" o rangos "inicio:fin" (multiport)
150161
# Vacío = no agrega excepciones extras.

docs/iptables.rules.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,18 @@ OVPNS2S_ENABLE_NAT=false
161161
OVPNS2S_LOCAL_INTERFACE=""
162162
OVPNS2S_LOG_ENABLED=false
163163
OVPNS2S_LOG_PREFIX="VPN_S2S_TRAFFIC: "
164+
OVPNS2S_ROUTER_ALIAS=""
165+
OVPNS2S_ROUTER_REAL_IP=""
166+
OVPNS2S_ROUTER_ALIAS_IP=""
167+
OVPNS2S_ROUTER_ALIAS_SNAT=false
164168
```
165169

170+
Alias S2S para router/LAN solapada:
171+
- `OVPNS2S_ROUTER_ALIAS`: formato multi-alias `real_ip;alias_ip,real_ip;alias_ip`.
172+
- Ejemplo: `192.168.1.1;10.99.2.1,192.168.1.254;10.99.2.254`.
173+
- Legacy compatible: `OVPNS2S_ROUTER_REAL_IP` + `OVPNS2S_ROUTER_ALIAS_IP` (single alias).
174+
- `OVPNS2S_ROUTER_ALIAS_SNAT=true`: fuerza retorno vía host S2S (requiere `OVPNS2S_LOCAL_INTERFACE`).
175+
166176
`openvpn_server` y `openvpn_sitetosite` son incompatibles y no pueden ejecutarse juntos.
167177

168178
**Modos Docker soportados (comportamiento esperado):**

docs/modules/06_openvpn.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ Separar explícitamente los dos modos de operación de OpenVPN:
2828
- `OVPNS2S_INTERFACE`
2929
- `OVPNS2S_LOCAL_SUBNETS`
3030
- `OVPNS2S_REMOTE_SUBNETS`
31-
- extras en `ip`: `OVPNS2S_ENABLE_NAT`, `OVPNS2S_LOCAL_INTERFACE`, `OVPNS2S_LOG_*`
31+
- extras en `ip`: `OVPNS2S_ENABLE_NAT`, `OVPNS2S_LOCAL_INTERFACE`, `OVPNS2S_LOG_*`, `OVPNS2S_ROUTER_ALIAS`, `OVPNS2S_ROUTER_REAL_IP`, `OVPNS2S_ROUTER_ALIAS_IP`, `OVPNS2S_ROUTER_ALIAS_SNAT`
32+
- extras en `nf`: `OVPNS2S_ENABLE_NAT`, `OVPNS2S_LOCAL_INTERFACE`, `OVPNS2S_ROUTER_ALIAS`, `OVPNS2S_ROUTER_REAL_IP`, `OVPNS2S_ROUTER_ALIAS_IP`, `OVPNS2S_ROUTER_ALIAS_SNAT`
3233

3334
## Activación
3435
- Se activan por inclusión en `MODULES_ONLY` usando tokens:
@@ -41,4 +42,4 @@ Separar explícitamente los dos modos de operación de OpenVPN:
4142

4243
## Diferencias por backend
4344
- `ip` incluye más opciones avanzadas (NAT/log/DNAT).
44-
- `nf` mantiene implementación más directa para input/forward.
45+
- `nf` mantiene implementación directa para input/forward y soporta NAT S2S (tabla `ip l4d2_s2s_nat`) para alias de router y MASQUERADE opcional.

example.env

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ OVPNS2S_ENABLE_NAT=false
9494
OVPNS2S_LOCAL_INTERFACE=""
9595
OVPNS2S_LOG_ENABLED=false
9696
OVPNS2S_LOG_PREFIX="VPN_S2S_TRAFFIC: "
97+
# Multi-alias format: "real_ip;alias_ip,real_ip;alias_ip"
98+
# Example: OVPNS2S_ROUTER_ALIAS="192.168.1.1;10.99.2.1,192.168.1.254;10.99.2.254"
99+
OVPNS2S_ROUTER_ALIAS=""
100+
# Legacy single-alias format (optional, backwards compatible)
101+
OVPNS2S_ROUTER_REAL_IP=""
102+
OVPNS2S_ROUTER_ALIAS_IP=""
103+
OVPNS2S_ROUTER_ALIAS_SNAT=false
97104

98105
# Extra allowed ports (servicios adicionales)
99106
# Formato: "puerto,puerto" o rangos "inicio:fin" (multiport)

modules/ip/ip_31_openvpn_sitetosite.sh

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,38 @@ ip_31_openvpn_sitetosite_expand_list() {
2424
done | awk '!seen[$0]++'
2525
}
2626

27+
ip_31_openvpn_sitetosite_expand_alias_list() {
28+
local raw="$1"
29+
raw="${raw//,/ }"
30+
raw="${raw//$'\n'/ }"
31+
for item in $raw; do
32+
echo "$item"
33+
done | awk '!seen[$0]++'
34+
}
35+
36+
ip_31_openvpn_sitetosite_validate_ipv4() {
37+
local label="$1"
38+
local value="$2"
39+
40+
if ! [[ "$value" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
41+
echo "ERROR: ip_openvpn_sitetosite: $label must be a valid IPv4 address (current: '$value')"
42+
return 2
43+
fi
44+
}
45+
46+
ip_31_openvpn_sitetosite_router_alias_entries() {
47+
local legacy_real="${OVPNS2S_ROUTER_REAL_IP:-}"
48+
local legacy_alias="${OVPNS2S_ROUTER_ALIAS_IP:-}"
49+
50+
if [ -n "${OVPNS2S_ROUTER_ALIAS:-}" ]; then
51+
ip_31_openvpn_sitetosite_expand_alias_list "$OVPNS2S_ROUTER_ALIAS"
52+
fi
53+
54+
if [ -n "$legacy_real" ] && [ -n "$legacy_alias" ]; then
55+
echo "$legacy_real;$legacy_alias"
56+
fi
57+
}
58+
2759
ip_31_openvpn_sitetosite_validate_cidr_list() {
2860
local label="$1"
2961
local raw="$2"
@@ -43,8 +75,8 @@ ID=ip_openvpn_sitetosite
4375
ALIASES=openvpn_sitetosite
4476
DESCRIPTION=Applies OpenVPN site-to-site rules in the iptables backend
4577
REQUIRED_VARS=TYPECHAIN OVPNS2S_INTERFACE OVPNS2S_LOCAL_SUBNETS OVPNS2S_REMOTE_SUBNETS
46-
OPTIONAL_VARS=OVPNS2S_INTERFACE OVPNS2S_LOCAL_SUBNETS OVPNS2S_REMOTE_SUBNETS OVPNS2S_ENABLE_NAT OVPNS2S_LOCAL_INTERFACE OVPNS2S_LOG_ENABLED OVPNS2S_LOG_PREFIX
47-
DEFAULTS=TYPECHAIN=0 OVPNS2S_ENABLE_NAT=false OVPNS2S_LOCAL_INTERFACE= OVPNS2S_LOG_ENABLED=false OVPNS2S_LOG_PREFIX=VPN_S2S_TRAFFIC:
78+
OPTIONAL_VARS=OVPNS2S_INTERFACE OVPNS2S_LOCAL_SUBNETS OVPNS2S_REMOTE_SUBNETS OVPNS2S_ENABLE_NAT OVPNS2S_LOCAL_INTERFACE OVPNS2S_LOG_ENABLED OVPNS2S_LOG_PREFIX OVPNS2S_ROUTER_REAL_IP OVPNS2S_ROUTER_ALIAS_IP OVPNS2S_ROUTER_ALIAS OVPNS2S_ROUTER_ALIAS_SNAT
79+
DEFAULTS=TYPECHAIN=0 OVPNS2S_ENABLE_NAT=false OVPNS2S_LOCAL_INTERFACE= OVPNS2S_LOG_ENABLED=false OVPNS2S_LOG_PREFIX=VPN_S2S_TRAFFIC: OVPNS2S_ROUTER_REAL_IP= OVPNS2S_ROUTER_ALIAS_IP= OVPNS2S_ROUTER_ALIAS= OVPNS2S_ROUTER_ALIAS_SNAT=false
4880
EOF
4981
}
5082

@@ -64,10 +96,36 @@ ip_31_openvpn_sitetosite_validate() {
6496

6597
ip_31_openvpn_sitetosite_validate_cidr_list "OVPNS2S_LOCAL_SUBNETS" "$OVPNS2S_LOCAL_SUBNETS" || return $?
6698
ip_31_openvpn_sitetosite_validate_cidr_list "OVPNS2S_REMOTE_SUBNETS" "$OVPNS2S_REMOTE_SUBNETS" || return $?
99+
100+
if { [ -n "${OVPNS2S_ROUTER_REAL_IP:-}" ] && [ -z "${OVPNS2S_ROUTER_ALIAS_IP:-}" ]; } \
101+
|| { [ -z "${OVPNS2S_ROUTER_REAL_IP:-}" ] && [ -n "${OVPNS2S_ROUTER_ALIAS_IP:-}" ]; }; then
102+
echo "ERROR: ip_openvpn_sitetosite: OVPNS2S_ROUTER_REAL_IP and OVPNS2S_ROUTER_ALIAS_IP must be set together"
103+
return 2
104+
fi
105+
106+
local alias_entry alias_real alias_ip
107+
while IFS= read -r alias_entry; do
108+
[ -z "$alias_entry" ] && continue
109+
if [[ "$alias_entry" != *";"* ]]; then
110+
echo "ERROR: ip_openvpn_sitetosite: invalid OVPNS2S_ROUTER_ALIAS entry '$alias_entry' (expected real_ip;alias_ip)"
111+
return 2
112+
fi
113+
114+
alias_real="${alias_entry%%;*}"
115+
alias_ip="${alias_entry#*;}"
116+
117+
if [ -z "$alias_real" ] || [ -z "$alias_ip" ]; then
118+
echo "ERROR: ip_openvpn_sitetosite: invalid OVPNS2S_ROUTER_ALIAS entry '$alias_entry' (empty real/alias IP)"
119+
return 2
120+
fi
121+
122+
ip_31_openvpn_sitetosite_validate_ipv4 "OVPNS2S router real IP" "$alias_real" || return $?
123+
ip_31_openvpn_sitetosite_validate_ipv4 "OVPNS2S router alias IP" "$alias_ip" || return $?
124+
done < <(ip_31_openvpn_sitetosite_router_alias_entries)
67125
}
68126

69127
ip_31_openvpn_sitetosite_apply() {
70-
local local_subnet remote_subnet
128+
local local_subnet remote_subnet alias_entry alias_real alias_ip
71129

72130
while IFS= read -r remote_subnet; do
73131
[ -z "$remote_subnet" ] && continue
@@ -86,6 +144,28 @@ ip_31_openvpn_sitetosite_apply() {
86144
done < <(ip_31_openvpn_sitetosite_expand_list "$OVPNS2S_LOCAL_SUBNETS")
87145
done < <(ip_31_openvpn_sitetosite_expand_list "$OVPNS2S_REMOTE_SUBNETS")
88146

147+
while IFS= read -r alias_entry; do
148+
[ -z "$alias_entry" ] && continue
149+
alias_real="${alias_entry%%;*}"
150+
alias_ip="${alias_entry#*;}"
151+
152+
while IFS= read -r remote_subnet; do
153+
[ -z "$remote_subnet" ] && continue
154+
ip_31_openvpn_sitetosite_add_rule_table nat PREROUTING -i "$OVPNS2S_INTERFACE" -s "$remote_subnet" -d "$alias_ip" -j DNAT --to-destination "$alias_real"
155+
done < <(ip_31_openvpn_sitetosite_expand_list "$OVPNS2S_REMOTE_SUBNETS")
156+
157+
if [ "${OVPNS2S_ROUTER_ALIAS_SNAT:-false}" = "true" ]; then
158+
if [ -n "${OVPNS2S_LOCAL_INTERFACE:-}" ]; then
159+
while IFS= read -r remote_subnet; do
160+
[ -z "$remote_subnet" ] && continue
161+
ip_31_openvpn_sitetosite_add_rule_table nat POSTROUTING -s "$remote_subnet" -d "$alias_real" -o "$OVPNS2S_LOCAL_INTERFACE" -j MASQUERADE
162+
done < <(ip_31_openvpn_sitetosite_expand_list "$OVPNS2S_REMOTE_SUBNETS")
163+
else
164+
echo "WARNING: ip_openvpn_sitetosite: OVPNS2S_ROUTER_ALIAS_SNAT=true but OVPNS2S_LOCAL_INTERFACE is empty; skipping alias SNAT"
165+
fi
166+
fi
167+
done < <(ip_31_openvpn_sitetosite_router_alias_entries)
168+
89169
if [ "$OVPNS2S_ENABLE_NAT" = "true" ]; then
90170
if [ -n "$OVPNS2S_LOCAL_INTERFACE" ]; then
91171
while IFS= read -r remote_subnet; do

modules/nf/nf_31_openvpn_sitetosite.sh

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,38 @@ nf_31_openvpn_sitetosite_expand_list() {
1313
done | awk '!seen[$0]++'
1414
}
1515

16+
nf_31_openvpn_sitetosite_expand_alias_list() {
17+
local raw="$1"
18+
raw="${raw//,/ }"
19+
raw="${raw//$'\n'/ }"
20+
for item in $raw; do
21+
echo "$item"
22+
done | awk '!seen[$0]++'
23+
}
24+
25+
nf_31_openvpn_sitetosite_validate_ipv4() {
26+
local label="$1"
27+
local value="$2"
28+
29+
if ! [[ "$value" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
30+
echo "ERROR: nf_openvpn_sitetosite: $label must be a valid IPv4 address (current: '$value')"
31+
return 2
32+
fi
33+
}
34+
35+
nf_31_openvpn_sitetosite_router_alias_entries() {
36+
local legacy_real="${OVPNS2S_ROUTER_REAL_IP:-}"
37+
local legacy_alias="${OVPNS2S_ROUTER_ALIAS_IP:-}"
38+
39+
if [ -n "${OVPNS2S_ROUTER_ALIAS:-}" ]; then
40+
nf_31_openvpn_sitetosite_expand_alias_list "$OVPNS2S_ROUTER_ALIAS"
41+
fi
42+
43+
if [ -n "$legacy_real" ] && [ -n "$legacy_alias" ]; then
44+
echo "$legacy_real;$legacy_alias"
45+
fi
46+
}
47+
1648
nf_31_openvpn_sitetosite_validate_cidr_list() {
1749
local label="$1"
1850
local raw="$2"
@@ -32,8 +64,8 @@ ID=nf_openvpn_sitetosite
3264
ALIASES=openvpn_sitetosite
3365
DESCRIPTION=Applies OpenVPN site-to-site rules in the nftables backend
3466
REQUIRED_VARS=TYPECHAIN OVPNS2S_INTERFACE OVPNS2S_LOCAL_SUBNETS OVPNS2S_REMOTE_SUBNETS
35-
OPTIONAL_VARS=OVPNS2S_INTERFACE OVPNS2S_LOCAL_SUBNETS OVPNS2S_REMOTE_SUBNETS
36-
DEFAULTS=TYPECHAIN=0
67+
OPTIONAL_VARS=OVPNS2S_INTERFACE OVPNS2S_LOCAL_SUBNETS OVPNS2S_REMOTE_SUBNETS OVPNS2S_ENABLE_NAT OVPNS2S_LOCAL_INTERFACE OVPNS2S_ROUTER_REAL_IP OVPNS2S_ROUTER_ALIAS_IP OVPNS2S_ROUTER_ALIAS OVPNS2S_ROUTER_ALIAS_SNAT
68+
DEFAULTS=TYPECHAIN=0 OVPNS2S_ENABLE_NAT=false OVPNS2S_LOCAL_INTERFACE= OVPNS2S_ROUTER_REAL_IP= OVPNS2S_ROUTER_ALIAS_IP= OVPNS2S_ROUTER_ALIAS= OVPNS2S_ROUTER_ALIAS_SNAT=false
3769
EOF
3870
}
3971

@@ -53,10 +85,42 @@ nf_31_openvpn_sitetosite_validate() {
5385

5486
nf_31_openvpn_sitetosite_validate_cidr_list "OVPNS2S_LOCAL_SUBNETS" "$OVPNS2S_LOCAL_SUBNETS" || return $?
5587
nf_31_openvpn_sitetosite_validate_cidr_list "OVPNS2S_REMOTE_SUBNETS" "$OVPNS2S_REMOTE_SUBNETS" || return $?
88+
89+
if { [ -n "${OVPNS2S_ROUTER_REAL_IP:-}" ] && [ -z "${OVPNS2S_ROUTER_ALIAS_IP:-}" ]; } \
90+
|| { [ -z "${OVPNS2S_ROUTER_REAL_IP:-}" ] && [ -n "${OVPNS2S_ROUTER_ALIAS_IP:-}" ]; }; then
91+
echo "ERROR: nf_openvpn_sitetosite: OVPNS2S_ROUTER_REAL_IP and OVPNS2S_ROUTER_ALIAS_IP must be set together"
92+
return 2
93+
fi
94+
95+
local alias_entry alias_real alias_ip
96+
while IFS= read -r alias_entry; do
97+
[ -z "$alias_entry" ] && continue
98+
if [[ "$alias_entry" != *";"* ]]; then
99+
echo "ERROR: nf_openvpn_sitetosite: invalid OVPNS2S_ROUTER_ALIAS entry '$alias_entry' (expected real_ip;alias_ip)"
100+
return 2
101+
fi
102+
103+
alias_real="${alias_entry%%;*}"
104+
alias_ip="${alias_entry#*;}"
105+
106+
if [ -z "$alias_real" ] || [ -z "$alias_ip" ]; then
107+
echo "ERROR: nf_openvpn_sitetosite: invalid OVPNS2S_ROUTER_ALIAS entry '$alias_entry' (empty real/alias IP)"
108+
return 2
109+
fi
110+
111+
nf_31_openvpn_sitetosite_validate_ipv4 "OVPNS2S router real IP" "$alias_real" || return $?
112+
nf_31_openvpn_sitetosite_validate_ipv4 "OVPNS2S router alias IP" "$alias_ip" || return $?
113+
done < <(nf_31_openvpn_sitetosite_router_alias_entries)
56114
}
57115

58116
nf_31_openvpn_sitetosite_apply() {
59-
local local_subnet remote_subnet
117+
local local_subnet remote_subnet alias_entry alias_real alias_ip
118+
local nat_table="l4d2_s2s_nat"
119+
local need_nat_table=false
120+
121+
if [ "${OVPNS2S_ENABLE_NAT:-false}" = "true" ] || [ -n "$(nf_31_openvpn_sitetosite_router_alias_entries)" ]; then
122+
need_nat_table=true
123+
fi
60124

61125
if nf_chain_enabled input; then
62126
while IFS= read -r remote_subnet; do
@@ -75,4 +139,44 @@ nf_31_openvpn_sitetosite_apply() {
75139
done < <(nf_31_openvpn_sitetosite_expand_list "$OVPNS2S_LOCAL_SUBNETS")
76140
done < <(nf_31_openvpn_sitetosite_expand_list "$OVPNS2S_REMOTE_SUBNETS")
77141
fi
142+
143+
if [ "$need_nat_table" = "true" ]; then
144+
nft delete table ip "$nat_table" 2>/dev/null || true
145+
nft add table ip "$nat_table"
146+
nft add chain ip "$nat_table" prerouting '{ type nat hook prerouting priority dstnat; policy accept; }'
147+
nft add chain ip "$nat_table" postrouting '{ type nat hook postrouting priority srcnat; policy accept; }'
148+
fi
149+
150+
while IFS= read -r alias_entry; do
151+
[ -z "$alias_entry" ] && continue
152+
alias_real="${alias_entry%%;*}"
153+
alias_ip="${alias_entry#*;}"
154+
155+
while IFS= read -r remote_subnet; do
156+
[ -z "$remote_subnet" ] && continue
157+
nft add rule ip "$nat_table" prerouting iifname "$OVPNS2S_INTERFACE" ip saddr "$remote_subnet" ip daddr "$alias_ip" dnat to "$alias_real"
158+
done < <(nf_31_openvpn_sitetosite_expand_list "$OVPNS2S_REMOTE_SUBNETS")
159+
160+
if [ "${OVPNS2S_ROUTER_ALIAS_SNAT:-false}" = "true" ]; then
161+
if [ -n "${OVPNS2S_LOCAL_INTERFACE:-}" ]; then
162+
while IFS= read -r remote_subnet; do
163+
[ -z "$remote_subnet" ] && continue
164+
nft add rule ip "$nat_table" postrouting ip saddr "$remote_subnet" ip daddr "$alias_real" oifname "$OVPNS2S_LOCAL_INTERFACE" masquerade
165+
done < <(nf_31_openvpn_sitetosite_expand_list "$OVPNS2S_REMOTE_SUBNETS")
166+
else
167+
echo "WARNING: nf_openvpn_sitetosite: OVPNS2S_ROUTER_ALIAS_SNAT=true but OVPNS2S_LOCAL_INTERFACE is empty; skipping alias SNAT"
168+
fi
169+
fi
170+
done < <(nf_31_openvpn_sitetosite_router_alias_entries)
171+
172+
if [ "${OVPNS2S_ENABLE_NAT:-false}" = "true" ] && [ "$need_nat_table" = "true" ]; then
173+
if [ -n "${OVPNS2S_LOCAL_INTERFACE:-}" ]; then
174+
while IFS= read -r remote_subnet; do
175+
[ -z "$remote_subnet" ] && continue
176+
nft add rule ip "$nat_table" postrouting ip saddr "$remote_subnet" oifname "$OVPNS2S_LOCAL_INTERFACE" masquerade
177+
done < <(nf_31_openvpn_sitetosite_expand_list "$OVPNS2S_REMOTE_SUBNETS")
178+
else
179+
echo "WARNING: nf_openvpn_sitetosite: OVPNS2S_ENABLE_NAT=true but OVPNS2S_LOCAL_INTERFACE is empty; skipping NAT"
180+
fi
181+
fi
78182
}

0 commit comments

Comments
 (0)