NE: add IPv6 support
diff --git a/Network-Namespace/README.md b/Network-Namespace/README.md index 30e63c5..018ba8a 100644 --- a/Network-Namespace/README.md +++ b/Network-Namespace/README.md
@@ -727,6 +727,74 @@ --- +## IPv6 support + +The network-namespace extension supports IPv6 guest networks via stateless address auto-configuration +(SLAAC) using **radvd** (Router Advertisement Daemon), mirroring the approach used by CloudStack's +built-in VPC router (`CsVpcGuestNetwork.py`). + +### How it works + +When `network_ip6_gateway` and `network_ip6_cidr` are present in the `implement-network` payload: + +1. **`implement-network`** enables IPv6 in the namespace (`net.ipv6.conf.all.disable_ipv6=0`, + forwarding on, DAD and temporary addresses disabled) and assigns the IPv6 gateway address + to the guest veth interface inside the namespace. +2. **`config-dhcp-subnet`** or **`config-dns-subnet`** writes `/radvd/radvd.conf` and starts + radvd inside the namespace. radvd sends Router Advertisements on the guest veth, advertising + the `/64` (or configured) prefix so VMs can self-configure via SLAAC. +3. **`add-dns-entry`** adds both an A record (IPv4) and an AAAA record (IPv6, from `ip6_address`) + to the dnsmasq hosts file so guests can resolve VM hostnames over IPv6. +4. **`remove-dhcp-subnet`** / **`shutdown-network`** / **`destroy-network`** stops radvd and + removes its state directory. + +### radvd configuration + +The generated `radvd.conf` follows the same format as `CsVpcGuestNetwork.py`: + +``` +interface <guest-veth> +{ + AdvSendAdvert on; + MinRtrAdvInterval 5; + MaxRtrAdvInterval 15; + prefix <network_ip6_gateway>/<prefix-len> + { + AdvOnLink on; + AdvAutonomous on; + }; + RDNSS <dns6-server> # one block per server from dns6 + { + AdvRDNSSLifetime 30; + }; +}; +``` + +### VPC networks + +For VPC networks the shared namespace is created by `implement-vpc` (IPv6-neutral — no IPv6 is +enabled or disabled at that stage). Each VPC tier network runs its own `implement-network`, which +independently enables or disables IPv6 on its own guest veth. This allows mixed VPC topologies +where some tiers have IPv6 and others do not. + +Each IPv6 tier also runs its own **radvd** instance inside the shared namespace, bound to its own +guest veth. radvd correctly handles multiple instances in the same namespace as long as each +instance is bound to a distinct interface. + +### State files + +IPv6 state is persisted under the per-network state directory: + +| File | Content | +|------|---------| +| `network-<id>/ip6-gateway` | IPv6 gateway address assigned to the namespace veth | +| `network-<id>/ip6-cidr` | IPv6 CIDR of the guest subnet | +| `network-<id>/dns6` | Comma-separated IPv6 DNS server list (RDNSS) | +| `network-<id>/radvd/radvd.conf` | Generated radvd configuration | +| `network-<id>/radvd/radvd.pid` | PID of the running radvd process | + +--- + ## Wrapper script operations reference CloudStack now invokes the wrapper through payload files. @@ -767,11 +835,17 @@ `--extension-ip` is not given) to `vn-<vlan>-<id>` inside the namespace. When the extension IP differs from the gateway a default route via the gateway is also added inside the namespace. -7. Disable IPv6 inside the namespace (all interfaces). -8. Enable IP forwarding inside the namespace. +7. IPv6 handling: + - When `--network-ip6-gateway` / `--network-ip6-cidr` are provided: enable IPv6 forwarding + inside the namespace (disable DAD, enable forwarding and accept_ra) and assign the IPv6 + gateway address to `vn-<vlan>-<id>`. + - When no IPv6 is configured: disable IPv6 on the guest veth interface. + For standalone networks the global namespace disable is also applied; for VPC tier networks + the global setting is left unchanged to avoid clobbering sibling tiers that may have IPv6. +8. Enable IPv4 IP forwarding inside the namespace. 9. Create iptables chains `CS_EXTNET_<id>_PR` (nat PREROUTING DNAT), `CS_EXTNET_<id>_POST` (nat POSTROUTING SNAT), and `CS_EXTNET_FWD_<id>` (filter FORWARD). -10. Save VLAN, gateway, CIDR, extension-ip, and namespace to state files. +10. Save VLAN, gateway, CIDR, extension-ip, IPv6 gateway, IPv6 CIDR, and namespace to state files. ### `shutdown-network` @@ -834,7 +908,9 @@ Actions: 1. Create the shared VPC namespace `cs-vpc-<vpc-id>` (idempotent). -2. Disable IPv6 and enable IP forwarding inside the namespace. +2. Enable IPv4 IP forwarding inside the namespace. + IPv6 is managed per-tier by `implement-network` (each tier enables or disables IPv6 on its + own guest veth without affecting sibling tiers). 3. Optionally, when `--source-nat true`, `--public-ip`, and `--public-vlan` are all provided and `--vpc-cidr` (VPC CIDR) is given: * Create public veth pair `vph-<pvlan>-<vpc-id>` (host) / `vpn-<pvlan>-<vpc-id>` (namespace). @@ -1499,10 +1575,11 @@ | `gateway` | Guest network gateway | | `cidr` | Guest network CIDR | | `vpc_id` | Present when the network belongs to a VPC; namespace becomes `cs-vpc-<vpcId>` | -| `network_ip6_gateway` | Guest IPv6 gateway, when configured | -| `network_ip6_cidr` | Guest IPv6 CIDR, when configured | +| `network_ip6_gateway` | Guest IPv6 gateway address (assigned to the namespace veth), when configured | +| `network_ip6_cidr` | Guest IPv6 CIDR (e.g. `2001:db8:1::/64`), when configured | | `extension_ip` | IP for DHCP/DNS/metadata service — equals gateway when SourceNat/Gateway is active, otherwise a dedicated placeholder IP | -| `dns` | Comma-separated DNS server list | +| `dns` | Comma-separated IPv4 DNS server list | +| `dns6` | Comma-separated IPv6 DNS server list (advertised via RDNSS in radvd RA) | | `domain` | Network domain suffix | | `current_details` | `ensure-network-device` only — previous selected-device JSON, used to preserve host affinity |
diff --git a/Network-Namespace/network-namespace-wrapper.sh b/Network-Namespace/network-namespace-wrapper.sh index 4d102b4..cf61e12 100755 --- a/Network-Namespace/network-namespace-wrapper.sh +++ b/Network-Namespace/network-namespace-wrapper.sh
@@ -530,6 +530,10 @@ FW_RULES_JSON=$(_payload_json_get "${payload_file}" "payload.fw_rules") ACL_RULES_JSON=$(_payload_json_get "${payload_file}" "payload.acl_rules") NETWORK_STATE=$(_payload_json_get "${payload_file}" "payload.network_state") + NETWORK_IP6_GATEWAY=$(_payload_json_get "${payload_file}" "payload.network_ip6_gateway") + NETWORK_IP6_CIDR=$(_payload_json_get "${payload_file}" "payload.network_ip6_cidr") + DNS6_SERVER=$(_payload_json_get "${payload_file}" "payload.dns6") + NIC_IP6_ADDRESS=$(_payload_json_get "${payload_file}" "payload.ip6_address") [ -z "${SOURCE_NAT}" ] && SOURCE_NAT="false" [ -z "${LB_RULES_JSON}" ] && LB_RULES_JSON="[]" @@ -582,10 +586,13 @@ done } - _read_sf VLAN vlan "${nsd}" "${old}" - _read_sf CIDR cidr "${nsd}" "${old}" - _read_sf GATEWAY gateway "${nsd}" "${old}" - _read_sf EXTENSION_IP extension-ip "${nsd}" "${old}" + _read_sf VLAN vlan "${nsd}" "${old}" + _read_sf CIDR cidr "${nsd}" "${old}" + _read_sf GATEWAY gateway "${nsd}" "${old}" + _read_sf EXTENSION_IP extension-ip "${nsd}" "${old}" + _read_sf NETWORK_IP6_GATEWAY ip6-gateway "${nsd}" "${old}" + _read_sf NETWORK_IP6_CIDR ip6-cidr "${nsd}" "${old}" + _read_sf DNS6_SERVER dns6 "${nsd}" "${old}" if [ -z "${NAMESPACE}" ]; then # Namespace is VPC-wide: check vpc state dir, then per-net, then legacy. @@ -635,11 +642,17 @@ # Ensure per-namespace iproute2 rt_tables for PBR isolation _pbr_ensure_table_file - # Disable IPv6 inside the namespace to avoid IPv6 autoconf/link-local behavior - # Apply globally and to the default and loopback interfaces. Idempotent. - ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null 2>&1 || true - ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null 2>&1 || true - ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.lo.disable_ipv6=1 >/dev/null 2>&1 || true + # IPv6: enable when a guest IPv6 gateway is configured; otherwise disable + # globally for standalone networks (VPC tiers only disable per-interface to + # avoid clobbering IPv6 state set by sibling tiers in the same namespace). + if [ -n "${NETWORK_IP6_GATEWAY}" ]; then + _enable_ipv6_in_namespace "${veth_n}" + log "implement-network: IPv6 enabled in namespace ${NAMESPACE}" + elif [ -z "${VPC_ID}" ]; then + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null 2>&1 || true + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null 2>&1 || true + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.lo.disable_ipv6=1 >/dev/null 2>&1 || true + fi # ---- 2. Host bridge + VLAN sub-interface ---- if [ -n "${VLAN}" ]; then @@ -688,9 +701,22 @@ fi fi - # Disable IPv6 on the guest veth namespace interface to prevent IPv6 addresses - # from appearing on the interface (idempotent) - ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf."${veth_n}".disable_ipv6=1 >/dev/null 2>&1 || true + # ---- 4b. Assign IPv6 gateway address to the guest veth ---- + if [ -n "${NETWORK_IP6_GATEWAY}" ] && [ -n "${NETWORK_IP6_CIDR}" ]; then + local ip6_prefix + ip6_prefix=$(echo "${NETWORK_IP6_CIDR}" | cut -d'/' -f2) + ip netns exec "${NAMESPACE}" ip -6 addr show "${veth_n}" 2>/dev/null | \ + grep -q "${NETWORK_IP6_GATEWAY}/" || \ + ip netns exec "${NAMESPACE}" ip -6 addr add \ + "${NETWORK_IP6_GATEWAY}/${ip6_prefix}" dev "${veth_n}" 2>/dev/null || true + log "implement-network: assigned IPv6 ${NETWORK_IP6_GATEWAY}/${ip6_prefix} to ${veth_n}" + else + # No IPv6 on this interface; disable per-interface to suppress link-local + # autoconf. For VPC tiers skip global disable to avoid breaking sibling + # tiers that may have IPv6 enabled. + ip netns exec "${NAMESPACE}" sysctl -w \ + net.ipv6.conf."${veth_n}".disable_ipv6=1 >/dev/null 2>&1 || true + fi if [ "${GUEST_TYPE}" = "isolated" ]; then # ---- 5. IP forwarding (needed for Isolated networks only) ---- @@ -724,6 +750,15 @@ echo "${GATEWAY}" > "${nsd}/gateway" echo "${CIDR}" > "${nsd}/cidr" echo "${ext_ip}" > "${nsd}/extension-ip" + if [ -n "${NETWORK_IP6_GATEWAY}" ]; then + echo "${NETWORK_IP6_GATEWAY}" > "${nsd}/ip6-gateway" + fi + if [ -n "${NETWORK_IP6_CIDR}" ]; then + echo "${NETWORK_IP6_CIDR}" > "${nsd}/ip6-cidr" + fi + if [ -n "${DNS6_SERVER}" ]; then + echo "${DNS6_SERVER}" > "${nsd}/dns6" + fi # Namespace + VPC tier tracking → vpc-<vpcId>/ (or network-<networkId>/ for isolated) local vsd; vsd=$(_vpc_state_dir) @@ -820,8 +855,9 @@ fi fi - # Stop per-network services (dnsmasq, haproxy, apache2, passwd-server) + # Stop per-network services (dnsmasq, radvd, haproxy, apache2, passwd-server) _svc_stop_dnsmasq + _svc_stop_radvd _svc_stop_haproxy _svc_stop_apache2 _svc_stop_passwd_server @@ -890,6 +926,7 @@ # Stop per-network services before removing state _svc_stop_dnsmasq + _svc_stop_radvd _svc_stop_haproxy _svc_stop_apache2 _svc_stop_passwd_server @@ -1341,6 +1378,10 @@ _dnsmasq_dhcp_hosts() { echo "$(_net_state_dir)/dnsmasq/dhcp-hosts"; } _dnsmasq_dhcp_opts() { echo "$(_net_state_dir)/dnsmasq/dhcp-opts"; } +_radvd_dir() { echo "$(_net_state_dir)/radvd"; } +_radvd_conf() { echo "$(_net_state_dir)/radvd/radvd.conf"; } +_radvd_pid() { echo "$(_net_state_dir)/radvd/radvd.pid"; } + _haproxy_dir() { echo "$(_net_state_dir)/haproxy"; } _haproxy_conf() { echo "$(_net_state_dir)/haproxy/haproxy.cfg"; } _haproxy_pid() { echo "$(_net_state_dir)/haproxy/haproxy.pid"; } @@ -1500,6 +1541,111 @@ } ############################################################################## +# Helpers: radvd (IPv6 Router Advertisement) +############################################################################## + +# _enable_ipv6_in_namespace <veth_n> +# Enables IPv6 forwarding inside the namespace for the given interface. +# Disables DAD and temporary addresses to avoid tentative/dadfailed states. +_enable_ipv6_in_namespace() { + local veth_n="$1" + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1 || true + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null 2>&1 || true + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null 2>&1 || true + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.all.accept_ra=1 >/dev/null 2>&1 || true + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.all.accept_dad=0 >/dev/null 2>&1 || true + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.default.accept_dad=0 >/dev/null 2>&1 || true + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.all.use_tempaddr=0 >/dev/null 2>&1 || true + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.default.use_tempaddr=0 >/dev/null 2>&1 || true + if [ -n "${veth_n}" ]; then + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf."${veth_n}".accept_dad=0 >/dev/null 2>&1 || true + ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf."${veth_n}".use_tempaddr=0 >/dev/null 2>&1 || true + fi +} + +# _write_radvd_conf <veth_n> <ip6_gateway> <ip6_cidr> [<dns6>] +# Generates /radvd/radvd.conf for the given interface and prefix. +# ip6_cidr must be in CIDR notation (e.g. 2001:db8:1::/64). +# dns6 is an optional comma-separated list of IPv6 DNS server addresses. +_write_radvd_conf() { + local veth_n="$1" ip6_gw="$2" ip6_cidr="$3" dns6="${4:-}" + + local dir; dir=$(_radvd_dir) + mkdir -p "${dir}" + + # Use the network address from ip6_cidr (all interface bits zero) as the + # radvd prefix. Using the gateway address (e.g. ::1/64) causes radvd to + # reject the config on strict versions because host bits are set. + local prefix_addr prefix_size + prefix_addr=$(echo "${ip6_cidr}" | cut -d'/' -f1) + prefix_size=$(echo "${ip6_cidr}" | cut -d'/' -f2) + + cat > "$(_radvd_conf)" << EOF +# Auto-generated by network-namespace-wrapper.sh — do not edit +interface ${veth_n} +{ + AdvSendAdvert on; + MinRtrAdvInterval 5; + MaxRtrAdvInterval 15; + prefix ${prefix_addr}/${prefix_size} + { + AdvOnLink on; + AdvAutonomous on; + }; +EOF + + if [ -n "${dns6}" ]; then + local dns + # shellcheck disable=SC2086 + for dns in $(echo "${dns6}" | tr ',' ' '); do + [ -z "${dns}" ] && continue + cat >> "$(_radvd_conf)" << EOF + RDNSS ${dns} + { + AdvRDNSSLifetime 30; + }; +EOF + done + fi + + echo "};" >> "$(_radvd_conf)" + log "radvd: wrote config $(_radvd_conf)" +} + +_svc_start_or_reload_radvd() { + _require_binary radvd + local pid_f; pid_f=$(_radvd_pid) + if [ -f "${pid_f}" ] && kill -0 "$(cat "${pid_f}")" 2>/dev/null; then + log "radvd: sending SIGHUP to reload (pid=$(cat "${pid_f}"))" + kill -HUP "$(cat "${pid_f}")" 2>/dev/null || true + else + log "radvd: starting in namespace ${NAMESPACE}" + local _ct_out + # Use 'if !' to prevent set -e from killing the script on radvd failure. + # Flag mapping (this radvd build): -C PATH = config file, -c = configtest. + if ! _ct_out=$(ip netns exec "${NAMESPACE}" radvd \ + -C "$(_radvd_conf)" -c 2>&1); then + die "radvd config test failed: ${_ct_out}" + fi + ip netns exec "${NAMESPACE}" radvd \ + -C "$(_radvd_conf)" \ + -p "$(_radvd_pid)" \ + -m syslog + log "radvd: started in namespace ${NAMESPACE}" + fi +} + +_svc_stop_radvd() { + local pid_f; pid_f=$(_radvd_pid) + if [ -f "${pid_f}" ]; then + local pid; pid=$(cat "${pid_f}") + kill "${pid}" 2>/dev/null || true + rm -f "${pid_f}" + log "radvd: stopped (pid=${pid})" + fi +} + +############################################################################## # Helpers: haproxy (LB via haproxy) ############################################################################## @@ -1922,6 +2068,25 @@ [ -z "${CIDR}" ] && die "config-dhcp-subnet: missing --cidr" _write_dnsmasq_conf false _svc_start_or_reload_dnsmasq + if [ -n "${NETWORK_IP6_GATEWAY}" ] && [ -n "${NETWORK_IP6_CIDR}" ]; then + local veth_n; veth_n=$(veth_ns_name "${VLAN}" "${CHOSEN_ID}") + # implement-network may have run before CloudStack assigned the IPv6 + # gateway to this network. Ensure the namespace is configured for IPv6, + # the gateway address is on the veth, and state files are persisted so + # that subsequent commands and the discovery path can find them. + _enable_ipv6_in_namespace "${veth_n}" + local ip6_prefix; ip6_prefix=$(echo "${NETWORK_IP6_CIDR}" | cut -d'/' -f2) + ip netns exec "${NAMESPACE}" ip -6 addr show "${veth_n}" 2>/dev/null | \ + grep -q "${NETWORK_IP6_GATEWAY}/" || \ + ip netns exec "${NAMESPACE}" ip -6 addr add \ + "${NETWORK_IP6_GATEWAY}/${ip6_prefix}" dev "${veth_n}" 2>/dev/null || true + local nsd; nsd=$(_net_state_dir) + [ -f "${nsd}/ip6-gateway" ] || echo "${NETWORK_IP6_GATEWAY}" > "${nsd}/ip6-gateway" + [ -f "${nsd}/ip6-cidr" ] || echo "${NETWORK_IP6_CIDR}" > "${nsd}/ip6-cidr" + [ -n "${DNS6_SERVER}" ] && { [ -f "${nsd}/dns6" ] || echo "${DNS6_SERVER}" > "${nsd}/dns6"; } + _write_radvd_conf "${veth_n}" "${NETWORK_IP6_GATEWAY}" "${NETWORK_IP6_CIDR}" "${DNS6_SERVER}" + _svc_start_or_reload_radvd + fi release_lock log "config-dhcp-subnet: done network=${NETWORK_ID}" } @@ -1954,6 +2119,21 @@ _write_dnsmasq_conf true _svc_start_or_reload_dnsmasq + if [ -n "${NETWORK_IP6_GATEWAY}" ] && [ -n "${NETWORK_IP6_CIDR}" ]; then + local veth_n; veth_n=$(veth_ns_name "${VLAN}" "${CHOSEN_ID}") + _enable_ipv6_in_namespace "${veth_n}" + local ip6_prefix; ip6_prefix=$(echo "${NETWORK_IP6_CIDR}" | cut -d'/' -f2) + ip netns exec "${NAMESPACE}" ip -6 addr show "${veth_n}" 2>/dev/null | \ + grep -q "${NETWORK_IP6_GATEWAY}/" || \ + ip netns exec "${NAMESPACE}" ip -6 addr add \ + "${NETWORK_IP6_GATEWAY}/${ip6_prefix}" dev "${veth_n}" 2>/dev/null || true + local nsd; nsd=$(_net_state_dir) + [ -f "${nsd}/ip6-gateway" ] || echo "${NETWORK_IP6_GATEWAY}" > "${nsd}/ip6-gateway" + [ -f "${nsd}/ip6-cidr" ] || echo "${NETWORK_IP6_CIDR}" > "${nsd}/ip6-cidr" + [ -n "${DNS6_SERVER}" ] && { [ -f "${nsd}/dns6" ] || echo "${DNS6_SERVER}" > "${nsd}/dns6"; } + _write_radvd_conf "${veth_n}" "${NETWORK_IP6_GATEWAY}" "${NETWORK_IP6_CIDR}" "${DNS6_SERVER}" + _svc_start_or_reload_radvd + fi release_lock log "config-dns-subnet: done network=${NETWORK_ID}" } @@ -1969,7 +2149,8 @@ acquire_lock "${NETWORK_ID}" log "remove-dhcp-subnet: network=${NETWORK_ID}" _svc_stop_dnsmasq - rm -rf "$(_dnsmasq_dir)" + _svc_stop_radvd + rm -rf "$(_dnsmasq_dir)" "$(_radvd_dir)" release_lock log "remove-dhcp-subnet: done network=${NETWORK_ID}" } @@ -2100,6 +2281,13 @@ mv "${hosts}.tmp" "${hosts}" echo "${VM_IP} ${HOSTNAME}" >> "${hosts}" + # Also add AAAA record when an IPv6 address is provided for this NIC + if [ -n "${NIC_IP6_ADDRESS}" ]; then + grep -v "^${NIC_IP6_ADDRESS}[[:space:]]" "${hosts}" > "${hosts}.tmp" 2>/dev/null || true + mv "${hosts}.tmp" "${hosts}" + echo "${NIC_IP6_ADDRESS} ${HOSTNAME}" >> "${hosts}" + fi + _svc_start_or_reload_dnsmasq release_lock log "add-dns-entry: done ${VM_IP} ${HOSTNAME}" @@ -2122,6 +2310,11 @@ if [ -f "${hosts}" ]; then grep -v "^${VM_IP}[[:space:]]" "${hosts}" > "${hosts}.tmp" 2>/dev/null || true mv "${hosts}.tmp" "${hosts}" + # Also remove the AAAA record when an IPv6 address was associated + if [ -n "${NIC_IP6_ADDRESS}" ]; then + grep -v "^${NIC_IP6_ADDRESS}[[:space:]]" "${hosts}" > "${hosts}.tmp" 2>/dev/null || true + mv "${hosts}.tmp" "${hosts}" + fi _svc_start_or_reload_dnsmasq fi release_lock @@ -3561,10 +3754,8 @@ # Ensure per-namespace iproute2 rt_tables for PBR isolation _pbr_ensure_table_file - # Disable IPv6 inside the namespace - ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null 2>&1 || true - ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null 2>&1 || true - ip netns exec "${NAMESPACE}" sysctl -w net.ipv6.conf.lo.disable_ipv6=1 >/dev/null 2>&1 || true + # IPv6 is managed per-tier by implement-network: each VPC tier enables or + # disables IPv6 on its own guest veth without touching sibling tiers. # ---- 2. IP forwarding ---- ip netns exec "${NAMESPACE}" sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1 || true