This is an automated email from the ASF dual-hosted git repository.

weizhouapache pushed a commit to branch network-namespace
in repository https://gitbox.apache.org/repos/asf/cloudstack-extensions.git


The following commit(s) were added to refs/heads/network-namespace by this push:
     new 0257d27  NE: add IPv6 support
0257d27 is described below

commit 0257d27333ee2f366a00a3bd493bb9186c62c210
Author: Wei Zhou <[email protected]>
AuthorDate: Tue Jun 30 21:46:11 2026 +0200

    NE: add IPv6 support
---
 Network-Namespace/README.md                    |  91 +++++++++-
 Network-Namespace/network-namespace-wrapper.sh | 227 +++++++++++++++++++++++--
 2 files changed, 293 insertions(+), 25 deletions(-)

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 @@ CloudStack resolves which extension to call by:
 
 ---
 
+## 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 @@ Actions:
    `--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 @@ network-namespace-wrapper.sh implement-vpc \
 
 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 @@ name bridges as `br<eth>-<vlan>` and veth pairs as 
`vh-<vlan>-<id>` /
 | `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 @@ parse_args() {
     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 @@ _load_state() {
         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 @@ cmd_implement_network() {
     # 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 @@ cmd_implement_network() {
         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 @@ cmd_implement_network() {
     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 @@ cmd_shutdown_network() {
         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 @@ cmd_destroy_network() {
 
     # 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_hosts()      { echo 
"$(_net_state_dir)/dnsmasq/hosts"; }
 _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"; }
@@ -1499,6 +1540,111 @@ _svc_stop_dnsmasq() {
     pkill -f "dnsmasq.*${NETWORK_ID}" 2>/dev/null || true
 }
 
+##############################################################################
+# 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 @@ cmd_config_dhcp_subnet() {
     [ -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 @@ cmd_config_dns_subnet() {
 
     _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 @@ cmd_remove_dhcp_subnet() {
     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 @@ cmd_add_dns_entry() {
     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 @@ cmd_remove_dns_entry() {
     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 @@ cmd_implement_vpc() {
     # 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

Reply via email to