@@ -3383,6 +3383,129 @@ cmd_implement_vpc() {
33833383 log " implement-vpc: done vpc=${VPC_ID} namespace=${NAMESPACE} "
33843384}
33853385
3386+ # #############################################################################
3387+ # Command: update-vpc-source-nat-ip
3388+ # Updates VPC source NAT egress to a new public IP without restarting tiers.
3389+ # Reconciles public veth/IP state, default route, VPC SNAT iptables chain,
3390+ # and source NAT markers under ${STATE_DIR}/vpc-<vpcId>/ips/.
3391+ # #############################################################################
3392+
3393+ cmd_update_vpc_source_nat_ip () {
3394+ parse_vpc_args " $@ "
3395+ acquire_lock " vpc-${VPC_ID} "
3396+
3397+ [ -z " ${PUBLIC_IP} " ] && die " update-vpc-source-nat-ip: missing --public-ip"
3398+
3399+ local vsd=" ${STATE_DIR} /vpc-${VPC_ID} "
3400+ mkdir -p " ${vsd} /ips"
3401+
3402+ # Load persisted values when omitted by the caller.
3403+ if [ -z " ${VPC_CIDR} " ] && [ -f " ${vsd} /cidr" ]; then
3404+ VPC_CIDR=$( cat " ${vsd} /cidr" 2> /dev/null || true)
3405+ fi
3406+ if [ -z " ${PUBLIC_VLAN} " ] && [ -f " ${vsd} /ips/${PUBLIC_IP} .pvlan" ]; then
3407+ PUBLIC_VLAN=$( cat " ${vsd} /ips/${PUBLIC_IP} .pvlan" 2> /dev/null || true)
3408+ fi
3409+
3410+ [ -z " ${VPC_CIDR} " ] && die " update-vpc-source-nat-ip: missing --cidr (or persisted vpc cidr)"
3411+ [ -z " ${PUBLIC_VLAN} " ] && die " update-vpc-source-nat-ip: missing --public-vlan"
3412+
3413+ log " update-vpc-source-nat-ip: vpc=${VPC_ID} ns=${NAMESPACE} old=? new=${PUBLIC_IP} pvlan=${PUBLIC_VLAN} cidr=${VPC_CIDR} "
3414+
3415+ local old_source_nat_ip=" "
3416+ local old_public_vlan=" "
3417+ local f ip flag
3418+ for f in " ${vsd} /ips/" * ; do
3419+ [ -f " ${f} " ] || continue
3420+ ip=$( basename " ${f} " )
3421+ case " ${ip} " in
3422+ * .pvlan|* .tier) continue ;;
3423+ esac
3424+ flag=$( cat " ${f} " 2> /dev/null || true)
3425+ if [ " ${flag} " = " true" ]; then
3426+ old_source_nat_ip=" ${ip} "
3427+ break
3428+ fi
3429+ done
3430+
3431+ if [ -n " ${old_source_nat_ip} " ] && [ -f " ${vsd} /ips/${old_source_nat_ip} .pvlan" ]; then
3432+ old_public_vlan=$( cat " ${vsd} /ips/${old_source_nat_ip} .pvlan" 2> /dev/null || true)
3433+ fi
3434+
3435+ local new_pveth_h new_pveth_n pub_br
3436+ new_pveth_h=$( pub_veth_host_name " ${PUBLIC_VLAN} " " ${VPC_ID} " )
3437+ new_pveth_n=$( pub_veth_ns_name " ${PUBLIC_VLAN} " " ${VPC_ID} " )
3438+ ensure_host_bridge " ${PUB_ETH} " " ${PUBLIC_VLAN} "
3439+ pub_br=$( host_bridge_name " ${PUB_ETH} " " ${PUBLIC_VLAN} " )
3440+
3441+ if ! ip link show " ${new_pveth_h} " > /dev/null 2>&1 ; then
3442+ ip link add " ${new_pveth_h} " type veth peer name " ${new_pveth_n} "
3443+ ip link set " ${new_pveth_n} " netns " ${NAMESPACE} "
3444+ ip link set " ${new_pveth_h} " master " ${pub_br} "
3445+ ip link set " ${new_pveth_h} " up
3446+ ip netns exec " ${NAMESPACE} " ip link set " ${new_pveth_n} " up
3447+ log " update-vpc-source-nat-ip: created public veth ${new_pveth_h} <-> ${new_pveth_n} "
3448+ else
3449+ ip link set " ${new_pveth_h} " up 2> /dev/null || true
3450+ ip netns exec " ${NAMESPACE} " ip link set " ${new_pveth_n} " up 2> /dev/null || true
3451+ fi
3452+
3453+ ensure_public_ip_on_namespace " ${PUBLIC_IP} " " ${PUBLIC_CIDR} " " ${new_pveth_n} " " ${new_pveth_h} "
3454+ ip route replace " ${PUBLIC_IP} /32" dev " ${new_pveth_h} " 2> /dev/null || true
3455+
3456+ if [ -n " ${old_source_nat_ip} " ] && [ " ${old_source_nat_ip} " != " ${PUBLIC_IP} " ] && [ -n " ${old_public_vlan} " ]; then
3457+ local old_pveth_n
3458+ old_pveth_n=$( pub_veth_ns_name " ${old_public_vlan} " " ${VPC_ID} " )
3459+ if [ " ${old_pveth_n} " != " ${new_pveth_n} " ]; then
3460+ ip netns exec " ${NAMESPACE} " ip route show default 2> /dev/null | \
3461+ grep " dev ${old_pveth_n} \b" | \
3462+ while read -r route; do
3463+ ip netns exec " ${NAMESPACE} " ip route del ${route} 2> /dev/null || true
3464+ done
3465+ fi
3466+ fi
3467+
3468+ if [ -n " ${PUBLIC_GATEWAY} " ]; then
3469+ ip netns exec " ${NAMESPACE} " ip route replace default \
3470+ via " ${PUBLIC_GATEWAY} " dev " ${new_pveth_n} " 2> /dev/null || \
3471+ ip netns exec " ${NAMESPACE} " ip route add default \
3472+ via " ${PUBLIC_GATEWAY} " dev " ${new_pveth_n} " 2> /dev/null || true
3473+ log " update-vpc-source-nat-ip: default route via ${PUBLIC_GATEWAY} dev ${new_pveth_n} "
3474+ fi
3475+
3476+ local vpc_post_chain=" ${CHAIN_PREFIX} _${VPC_ID} _VPC_POST"
3477+ ensure_chain nat " ${vpc_post_chain} "
3478+ ensure_jump nat POSTROUTING " ${vpc_post_chain} "
3479+
3480+ # This chain is dedicated to VPC source NAT egress; rebuild to a single rule.
3481+ ip netns exec " ${NAMESPACE} " iptables -t nat -F " ${vpc_post_chain} "
3482+ ip netns exec " ${NAMESPACE} " iptables -t nat \
3483+ -A " ${vpc_post_chain} " -s " ${VPC_CIDR} " -o " ${new_pveth_n} " -j SNAT --to-source " ${PUBLIC_IP} "
3484+
3485+ # Keep exactly one source-NAT marker: new public IP=true, all others=false.
3486+ for f in " ${vsd} /ips/" * ; do
3487+ [ -f " ${f} " ] || continue
3488+ ip=$( basename " ${f} " )
3489+ case " ${ip} " in
3490+ * .pvlan|* .tier) continue ;;
3491+ esac
3492+ echo " false" > " ${f} "
3493+ done
3494+ echo " true" > " ${vsd} /ips/${PUBLIC_IP} "
3495+ echo " ${PUBLIC_VLAN} " > " ${vsd} /ips/${PUBLIC_IP} .pvlan"
3496+
3497+ local _arping_bin
3498+ _arping_bin=$( _find_arping) || true
3499+ if [ -n " ${_arping_bin} " ]; then
3500+ ip netns exec " ${NAMESPACE} " " ${_arping_bin} " -c 3 -U -I " ${new_pveth_n} " " ${PUBLIC_IP} " \
3501+ > /dev/null 2>&1 || true
3502+ fi
3503+
3504+ _dump_iptables " ${NAMESPACE} "
3505+ release_lock
3506+ log " update-vpc-source-nat-ip: done vpc=${VPC_ID} old=${old_source_nat_ip:- none} new=${PUBLIC_IP} "
3507+ }
3508+
33863509# #############################################################################
33873510# Command: shutdown-vpc
33883511# Removes the VPC namespace after all tiers have been shut down.
@@ -3590,6 +3713,7 @@ case "${COMMAND}" in
35903713 destroy-network) cmd_destroy_network " $@ " ;;
35913714 # VPC lifecycle
35923715 implement-vpc) cmd_implement_vpc " $@ " ;;
3716+ update-vpc-source-nat-ip) cmd_update_vpc_source_nat_ip " $@ " ;;
35933717 shutdown-vpc) cmd_shutdown_vpc " $@ " ;;
35943718 destroy-vpc) cmd_destroy_vpc " $@ " ;;
35953719 assign-ip) cmd_assign_ip " $@ " ;;
@@ -3624,7 +3748,7 @@ case "${COMMAND}" in
36243748 custom-action) cmd_custom_action " $@ " ;;
36253749 " " )
36263750 echo " Usage: $0 {implement-network|shutdown-network|destroy-network|" \
3627- " implement-vpc|shutdown-vpc|destroy-vpc|" \
3751+ " implement-vpc|update-vpc-source-nat-ip| shutdown-vpc|destroy-vpc|" \
36283752 " assign-ip|release-ip|" \
36293753 " add-static-nat|delete-static-nat|add-port-forward|delete-port-forward|" \
36303754 " config-dhcp-subnet|remove-dhcp-subnet|add-dhcp-entry|remove-dhcp-entry|set-dhcp-options|" \
0 commit comments