Skip to content

Commit 4038d82

Browse files
committed
support snat eip ping fip eip
Signed-off-by: zbb88888 <jmdxjsjgcxy@gmail.com>
1 parent 304cd06 commit 4038d82

File tree

1 file changed

+107
-3
lines changed

1 file changed

+107
-3
lines changed

dist/images/vpcnatgateway/nat-gateway.sh

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ function show_help() {
3939
echo " dnat-del - Delete DNAT rule"
4040
echo " snat-add - Add SNAT rule"
4141
echo " snat-del - Delete SNAT rule"
42+
echo " hairpin-snat-add - Add hairpin SNAT rule for internal FIP access"
43+
echo " hairpin-snat-del - Delete hairpin SNAT rule"
4244
echo " qos-add - Add QoS rule"
4345
echo " qos-del - Delete QoS rule"
4446
echo " eip-ingress-qos-add - Add EIP ingress QoS"
@@ -124,6 +126,7 @@ function init() {
124126
$iptables_cmd -t nat -N SNAT_FILTER
125127
$iptables_cmd -t nat -N EXCLUSIVE_DNAT # floatingIp DNAT
126128
$iptables_cmd -t nat -N EXCLUSIVE_SNAT # floatingIp SNAT
129+
$iptables_cmd -t nat -N HAIRPIN_SNAT # hairpin SNAT for internal FIP access
127130
$iptables_cmd -t nat -N SHARED_DNAT
128131
$iptables_cmd -t nat -N SHARED_SNAT
129132

@@ -134,6 +137,7 @@ function init() {
134137
$iptables_cmd -t nat -A POSTROUTING -j SNAT_FILTER
135138
$iptables_cmd -t nat -A SNAT_FILTER -j EXCLUSIVE_SNAT
136139
$iptables_cmd -t nat -A SNAT_FILTER -j SHARED_SNAT
140+
$iptables_cmd -t nat -A SNAT_FILTER -j HAIRPIN_SNAT
137141

138142
# Send gratuitous ARP for all the IPs on the external network interface at initialization
139143
# This is especially useful to update the MAC of the nexthop we announce to the BGP speaker
@@ -153,6 +157,26 @@ function get_iptables_version() {
153157
exec_cmd "$iptables_cmd --version"
154158
}
155159

160+
# Check if the given CIDR exists in VPC_INTERFACE's routes (indicates it's an internal CIDR)
161+
# This is used to determine if hairpin SNAT is needed for a given SNAT rule
162+
# Args: $1 - CIDR to check (e.g., "10.0.1.0/24")
163+
# Returns: 0 if the CIDR is found in VPC_INTERFACE routes, 1 otherwise
164+
# Example: VPC_INTERFACE=eth0, route "10.0.1.0/24 dev eth0" exists
165+
# is_internal_cidr "10.0.1.0/24" -> returns 0 (true)
166+
# is_internal_cidr "192.168.1.0/24" -> returns 1 (false, not in routes)
167+
function is_internal_cidr() {
168+
local cidr="$1"
169+
if [ -z "$cidr" ]; then
170+
return 1
171+
fi
172+
# Match CIDR at the start of line to ensure exact match
173+
# e.g., "10.0.1.0/24 dev eth0" matches, but "10.0.1.0/25 ..." does not
174+
if ip -4 route show dev "$VPC_INTERFACE" | grep -q "^$cidr "; then
175+
return 0
176+
fi
177+
return 1
178+
}
179+
156180
function add_vpc_internal_route() {
157181
# make sure inited
158182
check_inited
@@ -270,9 +294,16 @@ function add_snat() {
270294
eip=(${arr[0]//\// })
271295
internalCIDR=${arr[1]}
272296
randomFullyOption=${arr[2]}
273-
# check if already exist
274-
$iptables_save_cmd | grep SHARED_SNAT | grep "\-s $internalCIDR" | grep "source $eip" && exit 0
275-
exec_cmd "$iptables_cmd -t nat -A SHARED_SNAT -o $EXTERNAL_INTERFACE -s $internalCIDR -j SNAT --to-source $eip $randomFullyOption"
297+
# check if already exist, skip adding if exists (idempotent)
298+
if ! $iptables_save_cmd | grep SHARED_SNAT | grep "\-s $internalCIDR" | grep "source $eip" > /dev/null; then
299+
exec_cmd "$iptables_cmd -t nat -A SHARED_SNAT -o $EXTERNAL_INTERFACE -s $internalCIDR -j SNAT --to-source $eip $randomFullyOption"
300+
fi
301+
# Add hairpin SNAT when internalCIDR is routed via VPC_INTERFACE
302+
# This enables internal VMs to access other internal VMs via FIP
303+
if is_internal_cidr "$internalCIDR"; then
304+
echo "SNAT cidr $internalCIDR is internal, adding hairpin SNAT with EIP $eip"
305+
add_hairpin_snat "$eip,$internalCIDR"
306+
fi
276307
done
277308
}
278309
function del_snat() {
@@ -290,6 +321,71 @@ function del_snat() {
290321
ruleMatch=$(echo $ruleMatch | sed 's/-A //')
291322
exec_cmd "$iptables_cmd -t nat -D $ruleMatch"
292323
fi
324+
# Delete hairpin SNAT when internalCIDR is routed via VPC_INTERFACE
325+
if is_internal_cidr "$internalCIDR"; then
326+
echo "SNAT cidr $internalCIDR is internal, deleting hairpin SNAT with EIP $eip"
327+
del_hairpin_snat "$eip,$internalCIDR"
328+
fi
329+
done
330+
}
331+
332+
# Hairpin SNAT: Enables internal VM to access another internal VM's FIP
333+
# Packet flow when VM A accesses VM B's EIP:
334+
# 1. VM A (10.0.1.6) -> EIP (10.1.69.216) arrives at NAT GW
335+
# 2. DNAT translates destination to VM B's internal IP (10.0.1.11)
336+
# 3. Without hairpin SNAT, reply from VM B goes directly to VM A (same subnet),
337+
# but VM A expects reply from EIP, causing asymmetric routing failure
338+
# 4. Hairpin SNAT translates source to EIP, ensuring symmetric return path via NAT GW
339+
#
340+
# RECOMMENDED: NAT-GW binds to a single VPC internal subnet. In this case,
341+
# only one hairpin SNAT rule is needed (matching the VPC's directly connected route).
342+
#
343+
# Multi-subnet scenarios are supported but NOT recommended. For multiple subnets,
344+
# create separate NAT gateways for each subnet to achieve more direct forwarding paths.
345+
# Each CIDR can only have one hairpin rule to avoid conflicting SNAT sources.
346+
#
347+
# Rule format: eip,internalCIDR
348+
# Example: 10.1.69.219,10.0.1.0/24
349+
# Creates: iptables -t nat -A HAIRPIN_SNAT -s 10.0.1.0/24 -d 10.0.1.0/24 -j SNAT --to-source 10.1.69.219
350+
function add_hairpin_snat() {
351+
# make sure inited
352+
check_inited
353+
for rule in $@
354+
do
355+
arr=(${rule//,/ })
356+
eip=(${arr[0]//\// })
357+
internalCIDR=${arr[1]}
358+
359+
# Check if this exact rule already exists (idempotent)
360+
if $iptables_save_cmd -t nat | grep HAIRPIN_SNAT | grep "\-s $internalCIDR" | grep "\-d $internalCIDR" | grep "source $eip" > /dev/null; then
361+
echo "Hairpin SNAT rule for $internalCIDR with EIP $eip already exists, skipping"
362+
continue
363+
fi
364+
365+
# Check if this CIDR already has a hairpin rule with a different EIP
366+
if $iptables_save_cmd -t nat | grep HAIRPIN_SNAT | grep "\-s $internalCIDR" | grep "\-d $internalCIDR" > /dev/null; then
367+
echo "WARNING: Hairpin SNAT rule for $internalCIDR already exists with different EIP. Skipping."
368+
continue
369+
fi
370+
371+
exec_cmd "$iptables_cmd -t nat -A HAIRPIN_SNAT -s $internalCIDR -d $internalCIDR -j SNAT --to-source $eip"
372+
echo "Hairpin SNAT rule added: $internalCIDR -> $eip"
373+
done
374+
}
375+
376+
function del_hairpin_snat() {
377+
# make sure inited
378+
check_inited
379+
for rule in $@
380+
do
381+
arr=(${rule//,/ })
382+
eip=(${arr[0]//\// })
383+
internalCIDR=${arr[1]}
384+
# check if rule exists (idempotent - skip if not found)
385+
if $iptables_save_cmd -t nat | grep HAIRPIN_SNAT | grep "\-s $internalCIDR" | grep "\-d $internalCIDR" | grep "source $eip" > /dev/null; then
386+
exec_cmd "$iptables_cmd -t nat -D HAIRPIN_SNAT -s $internalCIDR -d $internalCIDR -j SNAT --to-source $eip"
387+
echo "Hairpin SNAT rule deleted: $internalCIDR -> $eip"
388+
fi
293389
done
294390
}
295391

@@ -562,6 +658,14 @@ case $opt in
562658
echo "floating-ip-del $rules"
563659
del_floating_ip $rules
564660
;;
661+
hairpin-snat-add)
662+
echo "hairpin-snat-add $rules"
663+
add_hairpin_snat $rules
664+
;;
665+
hairpin-snat-del)
666+
echo "hairpin-snat-del $rules"
667+
del_hairpin_snat $rules
668+
;;
565669
get-iptables-version)
566670
echo "get-iptables-version $rules"
567671
get_iptables_version $rules

0 commit comments

Comments
 (0)