IPv6 from two sources via FreeBSD gateway via pf (4)

Version 6 of IP was originally designed with multiple Internet connections and multiple IP addresses on a single host in mind. Which can be useful in a place where there is no “good” IPv6 Internet access. Alas, it is still possible to run an access business without providing IPv6 in the menu. This forces local admins to use various surrogates. Imagine, for example, that the first surrogate is reliable, but slow, and the second is fast, but there is little trust in it. It is reasonable to keep both on “just in case”. But if the two IPv6 ranges are routed through the same gateway machine, then how to prevent it from getting confused about which packets where to go? Common routing rules based on IP addresses recipient, are not suitable here.

What is being decided

This article describes a solution to the IPv6 routing problem when the local network is endowed with two IPv6 sources working through the same router. Specifically, the IPv6 packet from the local network goes to the Internet through the interface to which its outgoing address is assigned.

What is not solved

This article does not address how application programs choose which Internet connection to use. In particular, how to choose between two global “native” IPv6 addresses (or send over IPv4).

The article does not focus on IPv6 connectivity issues, believing their solution is available to the independent efforts of the administrator.

Description of conditions

In the example below, the LAN administrator has two blocks of (global) IPv6 space, both implemented over a 6in4 tunnel (v6 encapsulation in IPv4 packets). The first is 6to4, which maps the 2002 :: / 16 IPv6 address range to Internet v4. If your ISP gives you a white IP address, has a route to anycast address and is generally not too crooked, FreeBSD will pick it up by itself (under the name stf0) when specified in /etc/rc.conf:

stf_interface_ipv4addr="▀▀.▄▄.◥◥.◣◣"  # Здесь должен быть записан белый IP

This method has been considered obsolete since 2015, but its advantage is to bind the IPv6 range to the public IPv4 address (which, in my case, is permanent). Even if something changes in the network infrastructure that provides 6to4, this will not be reflected in the addresses assigned to devices in the local network. The most important disadvantage of 6to4 in modern conditions is its low throughput. Also, my 6to4 drives IPv6 from a LAN in European Russia through a gateway in Amsterdam, which adds about 40 ms of latency.

The second source for my IPv6 in the 2001: 470 :: / 32 range is closer and has more bandwidth, since it is made by Hurricane Electric (under the tunnelbroker.net brand). This tunnel the core calls gif0 and I raise it with a special system script, the production and installation of which is beyond the scope of the article; the commands required to enable tunnelbroker are listed here… Its disadvantages are the IPv6 block without much headroom (/ 64), as well as dependence on a commercial firm with which you have no contract.

Description of pf (4)

File /etc/pf.conf contains the code for the pf (4) packet filter supplied with BSD systems. The text consists of definitions (macros) and a sequence of rules of the form “for such and such a package, do this and that from there and there.” When specifying pf_enable="YES" v /etc/rc.conf rule submission is automatically enabled during FreeBSD initialization. You can manually enable a (new) set of rules with the command

pfctl -e -f /etc/pf.conf

If pf is already included, then the “-e” switch is optional.


We assume that our /etc/pf.conf contains the following definitions:

myv6_6to4 = "2002:▀▀▄▄:◥◥◣◣::/48"
myv6_he   = "2001:470:◆◆:●●●::/64"
v6_noglobal = "fc00::/6"

This allows you to solve the problem with two rules:

# routing related to tunnels
pass quick from $myv6_he to {$myv6_6to4, $myv6_he, $v6_noglobal}
pass out route-to gif0 from $myv6_he to any

The main work is done by the rule “pass out route-to ⋯”, which prescribes outgoing (out) packets to go (pass) to the interface gif0 regardless of the route table (where, as we recall, there is default on stf0).

The rule “pass quick ⋯” early (quick) stops the filter from checking packets coming from the range to addresses in the same range ($ myv6_he), or in a different range belonging to the network ($ myv6_6to4), as well as to different addresses ($ v6_noglobal), not global. This traffic does not need to be forcibly routed to gif0

Important: in pf.conf the rules pass must come after the definitions and rules of address reassignment (rdr, nat).


If instead of “pass out route-to ⋯” you put “pass in route-to ⋯”, then the rule will serve traffic passing through the gateway, but will not work for packets created locally on the FreeBSD machine.

Since there are only two channels for IPv6, we were able to do 6to4 routing by “excluding alternatives” based on the standard kernel route table. What if there are three channels and you need to route 6to4 explicitly? In principle, you can would add similar rules for stf0:

pass quick from $myv6_6to4 to {$myv6_6to4, $myv6_he, $v6_noglobal}
pass out route-to stf0 from $myv6_6to4 to any

The obstacle is that the 6to4 protocol is more complex than a “simple” tunnel gif0 (in which there is no choice through which point to send the packet). And we assumed that the default route does not lead to stf0… Although pf (4) understands the immediate recipient of a packet with the option “route-to (interface, gateway)”, In 6to4 this gateway should be included only if the destination address is not in the range 2002 :: / 16. You can try to replace one “pass out route-to ⋯” with a two-way

pass out quick route-to stf0 from $myv6_6to4 to 2002::/16
pass out route-to (stf0, 2002:c058:6301::) from $myv6_6to4 to any

but for me personally, it is no longer interesting to try …

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *