@@ -929,10 +929,14 @@ cmd_assign_ip() {
929929 # After a network restart the veth is recreated with a new MAC address.
930930 # Without a gratuitous ARP the upstream gateway retains the stale ARP entry
931931 # for the old MAC and packets cannot reach the new veth.
932- if command -v arping > /dev/null 2>&1 ; then
933- ip netns exec " ${NAMESPACE} " arping -c 3 -U -I " ${pveth_n} " " ${PUBLIC_IP} " \
932+ # Use _find_arping to locate the binary in PATH and common sbin locations.
933+ local _arping_bin; _arping_bin=$( _find_arping) || true
934+ if [ -n " ${_arping_bin} " ]; then
935+ ip netns exec " ${NAMESPACE} " " ${_arping_bin} " -c 3 -U -I " ${pveth_n} " " ${PUBLIC_IP} " \
934936 > /dev/null 2>&1 || true
935937 log " assign-ip: sent gratuitous ARP for ${PUBLIC_IP} on ${pveth_n} "
938+ else
939+ log " assign-ip: arping not available — skipping gratuitous ARP for ${PUBLIC_IP} "
936940 fi
937941
938942 # ---- Default route inside namespace toward upstream gateway ----
@@ -1324,6 +1328,19 @@ _apache2_user() {
13241328 echo " nobody"
13251329}
13261330
1331+ # Locate the arping binary; checks PATH first, then common sbin paths.
1332+ # Prints the path and returns 0 on success, returns 1 when not found.
1333+ _find_arping () {
1334+ local bin
1335+ for bin in arping /usr/bin/arping /usr/sbin/arping /sbin/arping; do
1336+ if command -v " ${bin} " > /dev/null 2>&1 || [ -x " ${bin} " ]; then
1337+ echo " ${bin} "
1338+ return 0
1339+ fi
1340+ done
1341+ return 1
1342+ }
1343+
13271344# #############################################################################
13281345# Helpers: dnsmasq (DHCP + DNS via the same process)
13291346# #############################################################################
@@ -1520,8 +1537,6 @@ _svc_stop_haproxy() {
15201537# Helpers: apache2 (userdata / metadata HTTP service)
15211538#
15221539# apache2 runs inside the namespace, listening on <EXTENSION_IP>:80.
1523- # An iptables DNAT rule inside the namespace redirects requests destined for
1524- # 169.254.169.254:80 to <EXTENSION_IP>:80 so VMs can use the standard metadata URL.
15251540# EXTENSION_IP equals the network gateway when SourceNat/Gateway is enabled,
15261541# or a dedicated placeholder IP otherwise. Falls back to GATEWAY when absent.
15271542#
@@ -1648,21 +1663,13 @@ _svc_start_or_reload_apache2() {
16481663 fi
16491664 fi
16501665
1651- # DNAT 169.254.169.254:80 → EXTENSION_IP:80 (idempotent)
1652- # Use EXTENSION_IP as the metadata server address; fall back to GATEWAY.
1653- local meta_ip; meta_ip=" ${EXTENSION_IP:- ${GATEWAY} } "
1654- ip netns exec " ${NAMESPACE} " iptables -t nat \
1655- -C PREROUTING -d 169.254.169.254/32 -p tcp --dport 80 \
1656- -j DNAT --to-destination " ${meta_ip} :80" 2> /dev/null || \
1657- ip netns exec " ${NAMESPACE} " iptables -t nat \
1658- -A PREROUTING -d 169.254.169.254/32 -p tcp --dport 80 \
1659- -j DNAT --to-destination " ${meta_ip} :80"
1660-
1661- # Allow metadata traffic inbound to the namespace (INPUT)
1662- ip netns exec " ${NAMESPACE} " iptables -t filter \
1663- -C INPUT -p tcp --dport 80 -j ACCEPT 2> /dev/null || \
1664- ip netns exec " ${NAMESPACE} " iptables -t filter \
1665- -A INPUT -p tcp --dport 80 -j ACCEPT
1666+ # Allow metadata traffic inbound to the namespace (INPUT) from guest subnet only.
1667+ if [ -n " ${CIDR} " ]; then
1668+ ip netns exec " ${NAMESPACE} " iptables -t filter \
1669+ -C INPUT -p tcp -s " ${CIDR} " --dport 80 -j ACCEPT 2> /dev/null || \
1670+ ip netns exec " ${NAMESPACE} " iptables -t filter \
1671+ -A INPUT -p tcp -s " ${CIDR} " --dport 80 -j ACCEPT
1672+ fi
16661673}
16671674
16681675_svc_stop_apache2 () {
@@ -1782,26 +1789,28 @@ server.serve_forever()
17821789PYEOF
17831790 chmod +x " ${script_f} "
17841791
1785- # Skip restart if already running
1792+ # Only start if not already running; the iptables rule is (re-)applied regardless
1793+ # so that it is always present in the current namespace after a cleanup restart.
17861794 if [ -f " ${pid_f} " ] && kill -0 " $( cat " ${pid_f} " ) " 2> /dev/null; then
17871795 log " passwd-server: already running (pid=$( cat " ${pid_f} " ) )"
1788- return 0
1796+ else
1797+ # Use EXTENSION_IP as the listen address; fall back to GATEWAY when absent.
1798+ local listen_ip; listen_ip=" ${EXTENSION_IP:- ${GATEWAY} } "
1799+ log " passwd-server: starting in namespace ${NAMESPACE} on ${listen_ip} :8080"
1800+ ip netns exec " ${NAMESPACE} " python3 " ${script_f} " \
1801+ " ${listen_ip} " " ${passwd_f} " " ${pid_f} " \
1802+ >> " ${log_f} " 2>&1 &
1803+ # Brief pause to let the server write its PID
1804+ sleep 0.3
17891805 fi
17901806
1791- # Use EXTENSION_IP as the listen address; fall back to GATEWAY when absent.
1792- local listen_ip; listen_ip=" ${EXTENSION_IP:- ${GATEWAY} } "
1793- log " passwd-server: starting in namespace ${NAMESPACE} on ${listen_ip} :8080"
1794- ip netns exec " ${NAMESPACE} " python3 " ${script_f} " \
1795- " ${listen_ip} " " ${passwd_f} " " ${pid_f} " \
1796- >> " ${log_f} " 2>&1 &
1797- # Brief pause to let the server write its PID
1798- sleep 0.3
1799-
1800- # Allow inbound connections to port 8080 inside the namespace
1801- ip netns exec " ${NAMESPACE} " iptables -t filter \
1802- -C INPUT -p tcp --dport 8080 -j ACCEPT 2> /dev/null || \
1803- ip netns exec " ${NAMESPACE} " iptables -t filter \
1804- -A INPUT -p tcp --dport 8080 -j ACCEPT
1807+ # Always ensure the iptables INPUT rule is present (idempotent).
1808+ if [ -n " ${CIDR} " ]; then
1809+ ip netns exec " ${NAMESPACE} " iptables -t filter \
1810+ -C INPUT -p tcp -s " ${CIDR} " --dport 8080 -j ACCEPT 2> /dev/null || \
1811+ ip netns exec " ${NAMESPACE} " iptables -t filter \
1812+ -A INPUT -p tcp -s " ${CIDR} " --dport 8080 -j ACCEPT
1813+ fi
18051814}
18061815
18071816_svc_stop_passwd_server () {
0 commit comments