My ISP gives me one dynamic IPv6
/64, while my router can pass it through to only one VLAN. I wanted native IPv6 on every selected VLAN without replacing the router, merging the VLANs, or moving the working IPv4 network to OPNsense.
This project started because I could provide IPv6 to only one VLAN.
My ISP's ONT terminates PPPoE and exposes a single dynamic /64. The TP-Link ER605 behind it already handled IPv4 routing, DHCP, firewall policy, and VLAN isolation correctly. Its limitation was IPv6: passthrough could place the ISP's prefix on one VLAN, but it could not delegate a separate prefix to each network.
That left me with an awkward choice. I could preserve my VLAN design and accept IPv4-only networks, or flatten the network just to make one IPv6 prefix visible everywhere. Neither was acceptable.
I instead carried the ONT-facing IPv6 network into Proxmox through dedicated passthrough VLAN 999 and deployed OPNsense as an IPv6 control plane. The ER605 continued to own IPv4. OPNsense received my ISP's router advertisements, forwarded them to selected VLANs, installed per-client /128 routes, and proxied Neighbor Discovery between otherwise isolated layer-2 segments.
The result is deliberately split:
- IPv4 address, gateway, DHCP, DNS options, and inter-VLAN policy come from the ER605.
- Public IPv6 addressing, the IPv6 default route, NDP proxying, and IPv6 firewall policy come from OPNsense.
- The managed switch and Proxmox bridge transport the required VLANs without routing them.
The topology worked, but a later ISP renumbering event exposed a second problem. Clients learned the new prefix through SLAAC, while the upstream ONT did not always learn the new proxied addresses. My first watchdog protected one critical VPN VM and the active privacy addresses it observed on VLAN 10.
That was not general enough. A website VM on VLAN 99 later received the correct SLAAC address and default route, yet still had no public IPv6 connectivity. Its requests crossed OPNsense and left the upstream interface, but no replies returned. The repair was the same short-lived /128 neighbor-seeding operation that had recovered the VPN VM. I therefore extended the design with an active-client reconciler that discovers addresses on every configured downstream instead of hard-coding one client VLAN.
This article describes the complete design and the repeatable pattern for extending one ISP /64 to multiple VLANs while leaving IPv4 separate.
I have tested this approach with Indian residential connections from Airtel Fiber, Jio Fiber, and BSNL. The architecture is provider-neutral, but the exact Router Advertisement, prefix-renumbering, and neighbor-cache behavior can vary by ONT model and firmware.
Addressing note: Public addresses, credentials, serial numbers, MAC addresses, and interface identifiers in this article are omitted or replaced with documentation-safe values.
The constraint: one /64, many VLANs
In a conventional IPv6 deployment, the ISP delegates a /56 or /60. The edge router then assigns a separate /64 to every VLAN. Routing between those /64s is ordinary layer-3 forwarding.
That is not what my ISP provides. I receive one dynamic /64 and no usable downstream prefix delegation. Splitting that /64 into /68s or /72s is not a valid replacement because SLAAC expects a /64 on each LAN.
The workaround is therefore not subnetting. It is extending the same on-link /64 across multiple isolated VLANs with NDP proxying and per-host /128 routes.
| Constraint | Effect | Design decision |
|---|---|---|
ISP supplies one dynamic /64 | No unique /64 is available per VLAN | Extend the on-link prefix with NDP Proxy |
| ISP router remains in place | PPPoE and prefix acquisition stay upstream | Carry its IPv6 network to OPNsense on VLAN 999 |
| ER605 IPv6 passthrough supports one VLAN | Other VLANs remain IPv4-only | Make VLAN 999 the upstream feed, not a client VLAN |
| Existing IPv4 network already works | Migrating DHCP and routing adds unnecessary risk | Leave all IPv4 ownership on the ER605 |
| VLANs must remain isolated | A layer-2 bridge across them is unacceptable | Route client /128s and proxy NDP per VLAN |
| ISP prefix and client addresses change | Static proxy entries become stale | Reconcile every active downstream address with watchdogs |
This is an operational workaround for a constrained ISP service. If proper DHCPv6 Prefix Delegation becomes available, routed per-VLAN /64s are the better design.
Architecture
The physical path is:
Internet
-> ISP ONT
-> ER605
-> VLAN-aware managed switch
-> Proxmox vmbr0
-> OPNsense VM
The switch carries VLAN 999, VLAN 99, VLAN 10, and the existing IPv4 networks. It performs only 802.1Q layer-2 transport. It does not run DHCP, route between VLANs, or advertise IPv6 prefixes.
In my deployment, the OPNsense virtual NICs are deliberately simple:
| Proxmox NIC | OPNsense interface | Role | Configuration |
|---|---|---|---|
net0 | vtnet0 | VLAN 999 upstream | IPv6 SLAAC; no downstream services |
net1 | vtnet1 | VLAN 99 downstream | Link-local IPv6; NDP Proxy downstream |
net2 | vtnet2 | VLAN 10 | Link-local IPv6; NDP Proxy downstream |
The corresponding Proxmox mapping is conceptually:
net0: virtio=<redacted>,bridge=vmbr0,tag=999
net1: virtio=<redacted>,bridge=vmbr0
net2: virtio=<redacted>,bridge=vmbr0,tag=10
VLAN 99 is already bound to the Proxmox uplink as its default untagged network, so net1 does not need an explicit Proxmox tag. OPNsense receives that network directly on plain vtnet1.
For VLAN 10, Proxmox applies tag=10, so OPNsense receives the VLAN as untagged frames on plain vtnet2. I do not create another VLAN 10 subinterface inside OPNsense. Applying the tag in both places would double-tag the traffic.
The same model can be repeated for VLAN 20, VLAN 30, or any other selected VLAN: present the VLAN to Proxmox, attach another OPNsense NIC with that Proxmox tag, and add its OPNsense interface as an NDP Proxy downstream.
Keeping IPv4 separate
The most important architectural decision is that OPNsense does not replace the existing IPv4 router.
The ER605 remains authoritative for:
- IPv4 addresses and default gateways.
- DHCPv4 leases and DNS options.
- Inter-VLAN IPv4 routing and firewall policy.
- The existing management path.
OPNsense is authoritative only for the proxied IPv6 path:
- Receiving the dynamic ISP
/64onvtnet0. - Forwarding upstream Router Advertisements to approved downstream interfaces.
- Presenting its link-local address as the clients' IPv6 next hop.
- Proxying Neighbor Solicitation and Neighbor Advertisement messages.
- Installing per-client
/128routes toward the correct VLAN. - Applying stateful IPv6 firewall policy.
A dual-stack client can use different routers for the two protocols. For example:
IPv4 address: 10.1.10.50/24
IPv4 default route: 10.1.10.1 (ER605)
IPv6 address: 2001:db8:1234:5678::50/64
IPv6 default route: fe80::...%interface (OPNsense)
There is no conflict because IPv4 and IPv6 maintain independent routing tables and neighbor-discovery mechanisms.
Every downstream OPNsense interface uses the following policy:
IPv4 configuration: None
IPv6 configuration: Link-local only
DHCPv4 server: Disabled
DHCPv6 server: Disabled
Independent RA service: Disabled
The ER605 remains the only IPv4 DHCP server. ndp-proxy-go forwards the upstream IPv6 Router Advertisements; OPNsense does not invent another prefix or run a competing DHCP service.
How NDP Proxy makes the /64 reachable
The ONT advertises the ISP /64 as an on-link network. It therefore expects every address in that prefix to exist on its local Ethernet segment. Clients on VLAN 99 or VLAN 10 are not physically on that segment.
NDP Proxy reconciles those two views:
- The ONT sends a Router Advertisement containing the current
/64. - OPNsense forwards the advertisement to every configured downstream.
- A client creates a public address with SLAAC.
- OPNsense learns the client and installs a
/128route to its VLAN. - When the ONT asks who owns the client address, OPNsense answers on the upstream interface.
- OPNsense receives the packet and routes it to the client using the
/128route.
The client believes the /64 is on its VLAN. The ONT believes the client is reachable through OPNsense's upstream MAC. OPNsense holds the routing and neighbor state that connects the two.
This is not a bridge between the VLANs. Broadcast domains remain separate, and traffic crosses OPNsense under firewall policy.
The proxy invocation is conceptually:
ndp-proxy-go \
--pf=:ndp_proxy_global \
--pf=vtnet1:ndp_proxy_downstream \
vtnet0 vtnet1 vtnet2
The first interface is upstream. Every following interface is a downstream network that should receive the same /64.
Adding IPv6 to another VLAN
Adding a VLAN is a repeatable operation.
1. Carry the VLAN to Proxmox
Allow the VLAN on the managed-switch trunk connected to the Proxmox host. The Proxmox bridge must be VLAN-aware.
2. Add an OPNsense virtual NIC
Attach a VirtIO NIC to vmbr0 and set its Proxmox VLAN tag to the client VLAN. This keeps tagging at the hypervisor boundary and presents an ordinary Ethernet interface to OPNsense.
3. Assign the OPNsense interface
Enable the interface with no IPv4 configuration and link-local-only IPv6. Do not enable DHCPv4, DHCPv6, or a separate RA service.
interface: optN
device: vtnetX
enabled: true
ipv4: none
ipv6: link-local
dhcp4: disabled
dhcp6: disabled
4. Add it as an NDP Proxy downstream
The upstream remains vtnet0 on VLAN 999. Add the new interface to the downstream list and reload the proxy.
5. Add IPv6 firewall policy
Permit required ICMPv6 control traffic and the desired stateful client traffic on the new downstream. Do not assume an IPv4 rule automatically covers IPv6.
6. Validate both protocol families
The client should retain its IPv4 lease and gateway from the ER605 while gaining a public IPv6 address and an IPv6 default route through OPNsense.
ip -4 address
ip -4 route
ip -6 address
ip -6 route
ping -6 -c 3 2606:4700:4700::1111
curl -6 https://cloudflare.com/cdn-cgi/trace
Repeat these steps for every VLAN that requires public IPv6.
Why a watchdog is necessary
The static topology is only the first half of the solution. Two kinds of state change over time:
- The ISP can replace the dynamic
/64. - Clients can rotate stable-private and temporary privacy addresses.
After one ISP renumbering event, my clients correctly learned the new prefix, yet public IPv6 failed. Packet captures showed requests entering OPNsense on a downstream interface and leaving vtnet0, with no replies returning. The ONT and proxy did not have complete neighbor and /128 state for the new addresses.
The same signature later appeared on the website VM while the VPN VM remained healthy. OPNsense had learned the VPN VM's global /128 on vtnet1, but it knew only the website VM's link-local address. This was important evidence: a correct SLAAC address and default route on a client do not prove that the proxy, PF tables, host route, and upstream neighbor cache agree.
A one-time boot script is not enough for this design. Recovery must continuously reconcile four sources of truth:
- The currently preferred
/64on the upstream interface. - The proxy service and its learned PF tables.
- The
/128routes that map clients to downstream VLANs. - The upstream ONT's neighbor state.
A general watchdog design
I now use two cooperating FreeBSD services on OPNsense:
- A critical-prefix watchdog restores the preferred
/64and a known infrastructure VM first. - An active-client watchdog discovers and reconciles every other address across all configured downstream interfaces.
Both run every 30 seconds and share the same action lock. Normal checks are read-only while an address has a fresh, healthy record. Recovery is per-client: it does not restart OPNsense, bounce an interface, or interrupt IPv4 for the rest of the network.
Critical infrastructure
The prefix watchdog keeps one known infrastructure client as the recovery anchor. It combines the current prefix with that VM's stable interface identifier, verifies its /128 route and PF entries, and performs full prefix recovery when necessary. Giving the critical path an explicit address means it does not depend on a firewall state appearing before recovery can begin.
Active-client discovery
The second service does not require a list of website servers, Docker hosts, laptops, or phones. It builds its candidate set from four live sources:
- Global source addresses in
pfctl -ssfirewall states. - Addresses already learned in the global NDP Proxy PF table.
- Existing downstream
/128routes. - Global entries in the NDP neighbor cache.
For an address without a trustworthy route, it probes each configured downstream interface using that interface's link-local source address. The interface that returns a Neighbor Advertisement owns the client. This works for stable server addresses and rotating privacy addresses without assuming that VLAN 10 is the only client network.
Discovery is intentionally activity-driven. A completely idle privacy address cannot be inferred in advance; it becomes visible as soon as the client attempts IPv6 traffic. The daemon then locates it, seeds it, and stores the address-to-interface relationship.
Prefix changes
When the preferred upstream /64 differs from the saved value, the two services converge in stages:
- Clear state associated with the old prefix.
- Restore the critical infrastructure address and validate the upstream path.
- Relearn the critical client's downstream route and PF entries.
- Rediscover every other active address from states, routes, PF tables, and NDP.
- Probe the downstream interfaces to identify each owning VLAN.
- Seed and record clients in bounded batches as traffic appears.
Seeding the upstream neighbor cache
Some ONTs do not accept a newly proxied address until they have completed a real neighbor exchange for it. The bootstrap sequence is:
- Confirm that the address responds on one downstream interface.
- Temporarily remove its downstream
/128route. - Add the client address as a short-lived
/128alias on OPNsense WAN. - Probe the ONT's global LAN address using that source.
- Remove the alias immediately.
- Restore the
/128route to the owning downstream. - Reconcile the global and interface-specific PF tables explicitly.
This causes the ONT to associate the client address with OPNsense's upstream MAC. The alias is a recovery mechanism, never a permanent interface address.
Failure-safety requirements
The watchdogs use a shared lock and cleanup traps. On success, failure, or termination, they must remove:
- Temporary WAN aliases.
- Temporary
no_dadinterface state. - Action locks.
- Half-installed routes.
- Any stopped proxy-service state.
The client watchdog processes bounded batches so a large VLAN cannot monopolize the recovery lock. A seed operation briefly affects only the address being repaired; it does not restart NDP Proxy or cause a network-wide outage. Fresh and healthy client records remain untouched.
Watchdog implementation
My production setup runs two native FreeBSD services on OPNsense:
/usr/local/sbin/ipv6-ndp-watchdog
/usr/local/etc/rc.d/ipv6_ndp_watchdog
/etc/rc.conf.d/ipv6_ndp_watchdog
/usr/local/sbin/ipv6-ndp-client-watchdog
/usr/local/etc/rc.d/ipv6_ndp_client_watchdog
/etc/rc.conf.d/ipv6_ndp_client_watchdog
The first service retains the deterministic recovery path for the critical VM. The second is the generalized reconciler for all active clients on vtnet1, vtnet2, and any additional downstream interfaces added to its configuration. They share /var/run/ipv6-ndp-watchdog.lock, so neither can manipulate a temporary WAN alias while the other is recovering an address.
The published files are sanitized implementation templates. Configure the interface names, upstream-router identifier, PF table names, and critical-client values before installing them.
- Watchdog implementation template
- FreeBSD rc.d service wrapper
- Service enable configuration
- Active-client watchdog template
- Active-client rc.d service wrapper
- Active-client service configuration
install -o root -g wheel -m 0755 ipv6-ndp-watchdog.sh \
/usr/local/sbin/ipv6-ndp-watchdog
install -o root -g wheel -m 0755 ipv6_ndp_watchdog.rc \
/usr/local/etc/rc.d/ipv6_ndp_watchdog
install -o root -g wheel -m 0644 ipv6_ndp_watchdog.conf \
/etc/rc.conf.d/ipv6_ndp_watchdog
install -o root -g wheel -m 0755 ipv6-ndp-client-watchdog.sh \
/usr/local/sbin/ipv6-ndp-client-watchdog
install -o root -g wheel -m 0755 ipv6_ndp_client_watchdog.rc \
/usr/local/etc/rc.d/ipv6_ndp_client_watchdog
install -o root -g wheel -m 0644 ipv6_ndp_client_watchdog.conf \
/etc/rc.conf.d/ipv6_ndp_client_watchdog
/usr/local/sbin/ipv6-ndp-watchdog status
service ipv6_ndp_watchdog start
service ipv6_ndp_watchdog status
service ipv6_ndp_client_watchdog start
service ipv6_ndp_client_watchdog status
/usr/local/sbin/ipv6-ndp-client-watchdog --status
Do not use the template unchanged. Interface names, upstream behavior, and proxy tables vary between deployments.
Firewall policy
ICMPv6 is part of IPv6 operation, not an optional diagnostic protocol. The firewall must allow the control messages required for:
- Router Solicitation and Router Advertisement.
- Neighbor Solicitation and Neighbor Advertisement.
- Packet Too Big and Path MTU Discovery.
- Destination Unreachable and Time Exceeded.
I use stateful outbound IPv6 policy on each downstream and keep unsolicited WAN-to-client traffic default-deny. NDP Proxy makes an address reachable; it does not replace firewall policy.
IPv4 rules remain on the ER605. IPv6 rules remain on OPNsense. This ownership boundary makes failures and audits much easier to reason about.
Validation and monitoring
Validate the control plane on OPNsense:
ifconfig vtnet0
ifconfig vtnet1
ifconfig vtnet2
netstat -rn -f inet6
ndp -an
service ndp-proxy-go status
service ipv6_ndp_watchdog status
service ipv6_ndp_client_watchdog status
/usr/local/sbin/ipv6-ndp-client-watchdog --status
Validate learned proxy state:
pfctl -t ndp_proxy_global -T show
pfctl -t ndp_proxy_downstream -T show
Validate both stacks from one client on every VLAN:
ping -4 -c 3 1.1.1.1
ping -6 -c 3 2606:4700:4700::1111
curl -4 https://cloudflare.com/cdn-cgi/trace
curl -6 https://cloudflare.com/cdn-cgi/trace
The important assertions are:
- IPv4 still uses the ER605.
- IPv6 uses OPNsense's link-local address.
- Every global client address has a
/128route to the correct VLAN. - No temporary WAN alias or
no_dadflag remains after recovery. - Prefix changes and repeated seed failures produce alerts.
Why I did not use NAT66
NAT66 would simplify the upstream neighbor problem. Clients could use ULAs, and OPNsense could translate all outbound traffic behind one accepted global address.
I rejected it because my goal was native public IPv6 without address translation. NDP proxying preserves the client's global address and the end-to-end IPv6 model.
NAT66 is still a reasonable fallback when only outbound connectivity matters, upstream NDP behavior is unreliable, or operational simplicity is more valuable than native addressing.
Limitations
This design intentionally stretches one subnet across several links. It is less conventional than proper prefix delegation and requires stateful client discovery.
Privacy addresses need an initial connection attempt before the watchdog can observe them. This is active-client discovery, not an address-space scan. The design also depends on implementation details in the ISP router's neighbor cache. An ONT firmware update can change that behavior.
Most importantly, this should not be mistaken for a superior replacement for DHCPv6 Prefix Delegation. If the ISP supplies a /56 or /60, route a unique /64 to each VLAN and remove the proxy layer.
Final result
The network now has two cleanly separated control planes:
- The ER605 continues to provide IPv4 routing and DHCP to every VLAN.
- OPNsense extends my ISP's one dynamic public IPv6
/64to every selected VLAN. - The VLANs remain separate layer-2 and firewall domains.
- Clients retain native public IPv6 without NAT66.
- Stable and privacy addresses are learned dynamically across every configured downstream.
- Prefix changes and missing neighbor state recover automatically.
- Recovery remains targeted per client instead of restarting the entire IPv6 path.
This is not the topology I would choose if proper prefix delegation were available. Given one dynamic /64, an ISP router I cannot replace, and an existing IPv4 network I do not want to disturb, it provides a controlled and observable way to bring IPv6 to multiple VLANs.

Written by
Tharun P Karun
Full-Stack Engineer & AI Enthusiast. Writing tutorials, reviews, and lessons learned.