A gateway can hand a road warrior both an IPv4 and an IPv6 virtual address while the outer tunnel still travels over IPv4. Those are independent decisions, and treating them as one is where dual-stack plans usually fall apart. The client's inner families, the protected prefixes it may reach, and the transport that carries the encapsulation are three separate choices.

Most confusion comes from the word dual stack being applied to a design that is dual stack in only one place. A service can be dual stack internally, single stack on its outside address, and still work perfectly well for clients. What it cannot do is work by accident.

Two families inside, one path outside

The outer connection is IKE over UDP, and it needs one reachable address family from wherever the client happens to be. An inner IPv6 address does not require an IPv6-capable ISP, because the inner packet is payload inside the outer one. Plenty of working deployments carry IPv6 prefixes through an IPv4-only uplink.

What the outer path does constrain is reachability and NAT traversal. If the gateway advertises only an IPv4 address, a client on an IPv6-only network cannot connect at all, no matter what pools you configured. Decide the outer family from your client population, not from the inner address plan.

Give the client an address in each family

Requesting both families is a one-line statement on the client side, provided the gateway has something to hand out. Named pools keep the sources separate from the service networks, which is the property that makes firewall rules readable six months later.

pools {
  pool4 { addrs = 10.90.0.0/24 }
  pool6 { addrs = fd42:90::/64 }
}
connections {
  private-access {
    version = 2
    pools = pool4, pool6
    local  { auth = pubkey  certs = gateway.pem  id = vpn.example.com }
    remote { auth = pubkey  cacerts = ca.pem  vips = 0.0.0.0, :: }
    children {
      private { local_ts = 10.60.0.0/16, fd42:60::/48  remote_ts = dynamic }
    }
  }
}

The vips value asks for an address of each family, and the pool list decides where those addresses come from. The strongSwan virtual-IP documentation covers multiple requests and pool assignment, including the point that a client asking for a specific address is a preference the responder may override. Do not build an access rule that assumes the client always receives the same value.

Generate your own unique-local prefix instead of copying the example one. Two organisations that both deploy fd42::/16 will discover the collision the first time they interconnect, and diagnosing it is unpleasant.

Selectors describe destinations, not sources

The child SA's local traffic selectors say which protected networks sit behind the gateway, and the remote selectors describe what the client may use as a source. A virtual pool address belongs on the source side of that policy; putting it in the destination list is a common misreading of the configuration.

Both families need return routes and permitted forwarding on the gateway. Adding a prefix to a selector without a route back to the pool produces a tunnel that establishes, passes one direction of traffic, and silently drops the reply. The selector is policy, not routing.

Keep the pools and the service prefixes in separate blocks of your address plan. When they overlap, a route that should reach a protected host can match a pool address and disappear into the tunnel.

DNS and the reply path decide whether it feels finished

Resolver answers are independent of the transport used to reach the resolver. A name can return an A record, a AAAA record or both, and the client picks according to its own preference rules. If an internal service publishes a AAAA record but nothing routes that prefix, clients on dual-stack networks will try the IPv6 path first and sit there until it fails. Nothing in the tunnel configuration looks wrong while that happens.

So decide per name which families should be usable, then check that the corresponding prefix is in the selector and has a return route. That is a service-by-service decision. Blanket publishing of both record types is how a working IPv4-only service starts timing out for half its users.

Verify both families before you call it done

Configuration that loads is not configuration that works. Read the state the daemon reports, confirm the pool assignments, then look at the routes the client actually installed.

swanctl --list-sas
swanctl --list-pools
ip -6 addr show scope global
ip -6 route get fd42:60::10

The first two commands show the established child SAs and which pools still have addresses available, so a plan that works for the first client and fails for the tenth becomes visible. The last two show whether the client received a global IPv6 address from the pool and where traffic to a protected prefix would actually go. Compare the assigned address against the pool you configured, not against the address you hoped for. If the responder chose a different value under its policy, the routes and firewall rules must still line up with what it chose.

Then test one protected host per family, and test a name that resolves to both. A single-family test tells you nothing about the half of the plan you have not exercised, and that half is usually where the missing route lives.

Barruulee wal qabatan