@@ -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+
1648nf_31_openvpn_sitetosite_validate_cidr_list () {
1749 local label=" $1 "
1850 local raw=" $2 "
@@ -32,8 +64,8 @@ ID=nf_openvpn_sitetosite
3264ALIASES=openvpn_sitetosite
3365DESCRIPTION=Applies OpenVPN site-to-site rules in the nftables backend
3466REQUIRED_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
3769EOF
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
58116nf_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