[Edit 2018-10-18] Make sure to use FortiOS 6.0.3 or later for this, as earlier versions of 6.0.x will force your interface to IPv6 “static” when you make any change to the interface from the GUI, including changes to its IPv4 configuration, such as a DHCP reservation. I have not tested 5.6.x but am assuming it has the same issue.
[Edit 2021-02-21] Same issue as above came back with FortiOS 6.2.x; fixed again in 6.4.4.
This post is meant to be a full description of how to enable IPv6 connectivity on an ISP link with Prefix Delegation, using a Fortigate firewall. I’ll use Comcast as an example, since that’s my ISP.
This post focuses on home / home office connections, though a small business that uses the Fortigate unit as the LAN router would work the same way. If you use an ISP link with Prefix Delegation but have an internal core router downstream from the Fortigate, you may need a static IPv6 prefix instead.
I am not covering how link failover / SDWAN would work with IPv6. It’s an interesting use case, and I lack the second link to test it.
There are three components to setting up IPv6 in this environment.
- Receiving an external IP and a prefix using Prefix Delegation
- Assigning subnets to Fortigate internal interfaces and assigning addresses to client devices
- IPv6 firewall policy
This post pulls together information already available elsewhere. I have given references at the end of the post.
A quick IPv6 refresher
There are just a few things to remember for home / office use if you are coming from an IPv4 world. This post does not apply to Enterprise networks, though I mention Enterprise for reference here and there.
– Your “site” (home, office) will receive a /64 or /60 prefix from Comcast (residential), or as large as /56 (business). (A /48 is the typical Enterprise site prefix size.)
– All local networks (subnets) have a /64 prefix length. Subnetting further really isn’t a thing, with the exception of /127 point to point links, done for security reasons. You can have more than one /64 on one VLAN and clients can have more than one IPv6 address.
– There is no NAT. All your clients will have public addresses. (This might not be true in Enterprise networks where you may decide to either use public addresses or ULAs with NPT, Network Prefix Translation.)
– ICMPv6 is crucial to connection health. Just dropping all ICMP at the border won’t do the trick.
– In an IPv6 address, the first four fields are the network, the last four fields the device. Leading zeroes can be omitted, and a bunch of zeroes can be summarized as :: – with the caveat that there’s only one :: per address. For example, 2001:db8:3c4d:f40::/64 might be your subnet, and 2001:db8:3c4d:f40::1/64 is the address assigned to your Fortigate interface on that subnet.
– DHCPv6 cannot assign a next hop. Not all client operating systems can receive a DNS server without DHCPv6.
Receiving a prefix via Prefix Delegation
Before you get started, make sure that IPv6 is turned on in “System -> Feature Visibility”.
On a residential or business line, your ISP will assign you a prefix to use for your internal network(s). This prefix is received on your ISP-facing interface via DHCPv6 Prefix Delegation (PD), and can then be assigned dynamically to your internal interface(s).
Comcast will assign you a delegated /64 or /60 prefix on a residential line. A business line can receive up to a /56. These prefixes are dynamic and will change, just like a DHCPv4 address.
If you have a residential line and just one network internally, the default /64 will do fine.
If you have more than one network, you can give your ISP a “hint” that you’d like a /60 (16 networks) or /56 (256 networks, business line).
To clarify the underlying mechanics, DHCPv6 assigns a /128 address to your outside interface, and “delegates” a prefix that you can then use to assign /64s to your internal interface(s) as desired, or, indeed, delegate further inward to another router.
Here’s an example that’s requesting a /60 prefix. This example only shows the ipv6 portion of the configuration.
config system interface edit "wan1" config ipv6 set ip6-mode dhcp set ip6-allowaccess ping set dhcp6-prefix-delegation enable set dhcp6-prefix-hint ::/60 end next end
If you don’t have more than one internal interface, you can leave the hint off. Comcast on a residential line will assign a /64 in that case, for example.
Assigning prefixes to internal interfaces and addresses to clients
I owe others for explaining how to do this, notably Myles and /u/iwanttoride . Without their explanations, I’d still be stuck thinking that FortiOS doesn’t support dynamic allocations. The examples given in the FortiOS handbook are brief and lack all explanation.
Before getting started, two decisions need to be made:
Which DNS servers will be used? Those of the ISP or others? If others, such as Cisco Umbrella / OpenDNS or CloudFlare’s privacy DNS, enter those servers, both IPv4 and IPv6, under Network -> DNS, and choose to “Specify” your own servers. I’m showing using specified DNS servers, and will mention the commands required to use the ISP’s DNS servers instead.
Will you assign addresses using DHCPv6, or use DHCPv6 for DNS assignment only? I am showing DNS only, DHCPv6-lite. I’ll mention the commands required if you want to use DHCPv6 address assignment, though I’m not sure what would be gained. DHCP monitor seems to show only IPv4.
Assigning addresses to clients is reasonably straightforward, though there are implementation differences. Some OSs will receive DNS via DHCPv6, others only through RDNSS. Some can receive addresses via DHCPv6, others, notably Android, can’t.
This means you’ll be presenting DNS via both DHCPv6 and RDNSS. Make sure the two match and deliver the same server(s). Likewise, if you use DHCPv6 for address assignment, make sure it matches the SLAAC assignment on the interface.
Here are the commands to use the first /64 of your delegated prefix on an internal interface. As before, I’m only showing the ipv6 portion of the configuration.
config system interface edit "lan" config ipv6 set ip6-mode delegated set ip6-allowaccess ping https ssh set ip6-send-adv enable set ip6-other-flag enable set ip6-upstream-interface "wan1" set ip6-subnet ::1/64 config ip6-delegated-prefix-list edit 1 set upstream-interface "wan1" set autonomous-flag enable set onlink-flag enable set subnet ::/64 set rdnss-service default next end end next end
If you wanted to use DHCPv6 for address assignment, add “set ip6-managed-flag enable” to the “config ipv6” section. Because of OS implementation quirks, you should keep both the managed-flag and the other-flag in that case.
This may require a reboot. I am not sure of that, and it might depend on FortiOS version. I think FortiOS 6.0.1, where I tested that, just needs a couple minutes time to assign the interface its address, but I’m not 100% certain of that.
Keep in mind you can “get” the actual values on your interface once you are inside its configuration via “edit lan” or whatever your interface name is.
Let’s discuss these in some more detail.
We’re, obviously, in delegated mode, and we got a delegated prefix on our “ip6-upstream-interface”, in my case “wan1”.
ip6-allowaccess is for management access. If you have a FortiManager on this interface, or FortiAPs in tunnel mode, add the relevant services like you would in IPv4.
We’re sending RAs so that clients can learn an address and a next-hop, that’s ip6-send-adv. We’re telling clients they can get a DNS server (and if you feel like it, a dns-search-list) via DHCPv6, that’s “ip6-other-flag”.
The ip6-subnet is the part I didn’t understand for the past 2 years or so. This is a mask that is applied to the delegated prefix using a logical Boolean “AND” operation. In practice, you can think of this as just “adding” this value to the delegated prefix. “::1/64” means that we’ll take the delegated prefix as-is, and add “1” at the very right, then change the length to 64. This becomes the IPv6 address of this interface. Let’s say my delegated prefix is 2001:db8:3c4d:f40::/60, then the IPv6 address of my “lan” interface would become 2001:db8:3c4d:f40::1/64.
Now we need to hand out addresses to clients on this network. That’s what the “ip6-delegated-prefix-list” is for. I only need one subnet per interface, so “edit 1” it is. The “upstreams-interface” needs to be set, once more. “autonomous-flag” and “onlink-flag” tell the client that it can get an address via SLAAC here and that this interface has an address in that range, that is, can be routed to, respectively.
The “subnet” functions similarly to the “ip6-subnet” for the interface address. Here, we’d want to add to the network portion, not the host portion of the IPv6 address. Since I’m using the very first subnet in my allocation, there’s nothing to add. “::/64” combined with my 2001:db8:3c4d:f40::/60 prefix gives me 2001:db8:3c4d:f40::/64 to be used for client addresses on this interface.
Lastly, “rdnss-service default” will hand out my system DNS server(s) to clients on this link, if the client speaks RDNSS. That’s the belt and suspenders approach to DNS, discussed above. If you wanted to use the ISP’s servers instead, you’d configure “rdnss-service delegated”.
And here’s the DHCPv6 portion to hand out DNS.
config system dhcp6 server edit 1 set dns-service default set interface "lan" next end
If you wanted to use the ISP’s DNS servers, that would become “set dns-service delegated”, and you’d add “set upstream-interface “wan1″”.
If you had DHCPv6 also hand out addresses, it’d look like this.
config system dhcp6 server edit 1 set dns-service default set subnet ::/64 set interface "lan" set upstream-interface "wan1" set ip-mode delegated next end
Now, let’s do this all over again for guest WiFi, assigning the second /64 subnet out of my /60 allocation.
config system interface edit "wifi" config ipv6 set ip6-mode delegated set ip6-allowaccess ping set ip6-send-adv enable set ip6-other-flag enable set ip6-upstream-interface "wan1" set ip6-subnet ::1:0:0:0:1/64 config ip6-delegated-prefix-list edit 1 set upstream-interface "wan1" set autonomous-flag enable set onlink-flag enable set subnet 0:0:0:1::/64 set rdnss-service default next end end next end
Guests don’t need management access to my Fortigate, so “ip6-allowaccess ping” suffices here.
The ip6-subnet value of ::1:0:0:0:1/64 combines with my 2001:db8:3c4d:f40::/60 prefix to give this interface an IPv6 address of 2001:db8:3c4d:f41::1/64. Keep in mind that the left-most four fields of the address are the network, and the right-most four fields are the host.
For assigning addresses to clients, I’m matching this and set subnet to 0:0:0:1::/64. This is for the subnet, which means I care about the network portion only, hence the :: for the host fields. Combine that again with my delegated 2001:db8:3c4d:f40::/60 prefix and clients will receive addresses from 2001:db8:3c4d:f41::/64. That’s on-link to the interface address and they’ll be able to route.
Remember to create another DHCPv6 entry for DNS, and if you use DHCPv6 to give out addresses, make sure that entry’s subnet value matches the value here.
config system dhcp6 server edit 2 set dns-service default set interface "wifi" next end
And with DHCPv6 handing out addresses:
config system dhcp6 server edit 2 set dns-service default set subnet 0:0:0:1::/64 set interface "wifi" set upstream-interface "wan1" set ip-mode delegated next end
Rinse repeat for any other interfaces you may have. A /60 delegated prefix gives you 16 available subnets, from 0 through f.
IPv6 firewall policy
Time for something simpler. All you need here is a policy allowing ICMPv6 in and out, and a policy for traffic out to the Internet or other subnets.
I have multiple interfaces so I enabled “Multiple Interface Policies” in “System -> Feature Visibility”, to make my life easier.
I’d like to follow RFC 4890 instead of allowing “all ICMPv6”. ICMPv6 is needed for session health, and I still want to be security-conscious.
config firewall service custom edit "ICMP6-DestUnreach" set category "General" set protocol ICMP6 set icmptype 1 unset icmpcode next edit "ICMP6-PacketTooBig" set category "General" set protocol ICMP6 set icmptype 2 unset icmpcode next edit "ICMP6-TimeExceeded0" set category "General" set protocol ICMP6 set icmptype 3 set icmpcode 0 next edit "ICMP6-TimeExceeded1" set category "General" set protocol ICMP6 set icmptype 3 set icmpcode 1 next edit "ICMP6-ParmProb0" set category "General" set protocol ICMP6 set icmptype 4 set icmpcode 0 next edit "ICMP6-ParmProb1" set category "General" set protocol ICMP6 set icmptype 4 set icmpcode 1 next edit "ICMP6-ParmProb2" set category "General" set protocol ICMP6 set icmptype 4 set icmpcode 2 next edit "ICMP6-EchoRequest set category "General" set protocol ICMP6 set icmptype 128 unset icmpcode next edit "ICMP6-EchoResponse" set category "General" set protocol ICMP6 set icmptype 129 unset icmpcode next end config firewall service group edit "ICMP6-allow" set member "ICMP6-DestUnreach" "ICMP6-EchoRequest" "ICMP6-EchoResponse" "ICMP6-PacketTooBig" "ICMP6-ParmProb0" "ICMP6-ParmProb1" "ICMP6-ParmProb2" "ICMP6-TimeExceeded0" "ICMP6-TimeExceeded1" set comment "ICMP6 services to be allowed as per RFC4890" next end
With that group created, you can now create your first three IPv6 policies.
Allow desired ICMPv6 and drop all other ICMPv6:
From “any” interface to “any” interface, source and destination “all”, schedule “always”, Service “ICMP6-allow”, action “Accept”, and no NAT. I turn logging off as well.
From “any” interface to “any” interface, source and destination “all”, schedule “always”, Service “ALL_ICMP6”, action “Deny”. I turn off logging.
Allow your interfaces out to the Internet:
From internal interface, say “lan”, to external interface, say “wan1”, source and destination “all”, NAT disabled, action “Accept”, and Schedule, Service, Security Profiles and Log as desired. Add additional “from” interfaces if they share the same policy.
Myles documented prefix delegation in a way I could understand.
/u/iwanttoride explained an example configuration of PD on reddit.
Antonios Atlasis and Enno Rey lab-tested client implementation differences in receiving an address and DNS server.
James Sanders explained Android’s requirement for RDNSS.
E. Davies and J. Mohacsi wrote RFC 4890, a recommendation on how to filter ICMPv6 messages on a firewall while still allowing IPv6 to function.