IPv4 & IPv6 Router and PF Firewall

This article details how to configure an IPv4 and IPv6 dual-stack Internet gateway router secured with PF firewall.

It is a prerequisite that your Internet Service Provider is providing native IPv6 Internet connectivity and IP addressing via DHCPv6.

FreeBSD's base DHCP client (dhclient) will be replaced with net/dhcpcd. dhcpcd will manage acquiring DHCP address leases for both IPv4 and IPv6. dhcpcd will be configured to request a /64 PD prefix delegation, and assign the first address of the offered PD to the internal interface. Already installed with base system rtadvd will be enabled and configured to send router advertisements on the internal interface, providing default gateway and temporary IPv6 addresses from the acquired /64 prefix delegation to all connected LAN nodes.


Install and Configure dhcpcd

Install net/dhcpcd using your preferred method.

Edit and add the following to /usr/local/etc/dhcpcd.conf:

denyinterfaces re1	# Don't touch re1 at all
interface re0
	ipv6rs		# enable routing solicitation for re0
	ia_na 1		# request a NA network address
	ia_pd 2 re1/0	# request a PD /64 prefix delegation and assign it to re1


Configure System Startup

Configure the network interfaces, enable IP forwarding, PF firewall, and rtadvd by adding the following to /etc/rc.conf:

ifconfig_re1="inet netmask"


# pf firewall

# rtadvd


Configure the PF Firewall Rules

Edit /etc/pf.conf

# Macros
ext_if = re0
int_if = re1

# ICMP types
icmp_types = "{ echoreq }"
icmp6_types = "{ echoreq, neighbradv, neighbrsol, routeradv, routersol }"

# Private and documentation example networks
priv_nets = "{,,,, }"
doc_example_nets = "{ 2001:DB8::/32,,, }"

# Options
set block-policy drop
set skip on lo0

# Scrub
scrub on $ext_if all no-df random-id reassemble tcp

# IPv4 NAT
nat on $ext_if from to any -> ($ext_if)

### Block ###
block log all

# Antispoof
antispoof quick log for $ext_if
block in log on $ext_if from { urpf-failed, no-route }
block in quick log on $ext_if from { $priv_nets, $doc_example_nets }

# Block ICMP Port Unreachable Messages to Prevent and Mitigate DDOS and Other Attacks i.e. SAD DNS
block out quick log on $ext_if inet proto icmp icmp-type unreach code port-unr
block out quick log on $ext_if inet6 proto icmp6 icmp6-type unreach code port-unr

### Allow ###
# Allow all traffic on internal interface
pass quick on $int_if flags any

# Allow all traffic out on external interface(s)
pass out on $ext_if flags any

pass in on $ext_if inet proto icmp all icmp-type $icmp_types
pass in on $ext_if inet6 proto icmp6 all icmp6-type $icmp6_types allow-opts

# Allow DHCP v6 Client
pass in log on $ext_if inet6 proto udp from fe80::201:5cff:fe9c:e445 to fe80::213:3bff:fe0e:2e4 port dhcpv6-client

pfctl -f /etc/pf.conf to load the new ruleset.

NOTE: When specifying ingress allow rules it is important to create separate rules for IPv4 and IPv6 as well as specifying the exact destination addresses for the required server/service. This ensures that unwanted ingress IPv6 traffic is not allowed to reach other destinations on your internal network.


Testing the Setup

Before dhcpcd can be run we must kill any running dhclient processes. It may be necessary to be logged into FreeBSD at the local console to prevent loss of connectivity if initially connected via SSH.

killall dhclient

Observe the output as dhcpcd requests IP address leases and adds routes for addresses offered.

Watch specifically for a line output similar to:

re0: REPLY6 received from fe80::201:5cff:fe9c:e445

This is the link-local address of the responding DHCPv6 server and should be copy/pasted into the "from" address of the Allow DHCP v6 Client PF firewall rule.

Use ifconfig to confirm that the system received both an IPv4 address as well as an IPv6 network address. Also verify that a prefix delegation was offered and the first address of the offered /64 address block is assigned to the internal interface.

netstat -rn can be used to verify the routing table.

When both IPv4 and IPv6 connectivity are confirmed working, you can start the ratdvd service to begin assigning IPv6 addresses from your assigned /64 prefix delegation. By default no configuration is required, simply specifying the internal interface with rtadvd_interfaces="re1" in /etc/rc.conf is typically sufficient for most cases. Start the service by executing:

service rtadvd start

After verifying the setup and configuration is all working normally, reboot the system and reverify.


To install IPv4 and IPv6 DHCP servers for stateful IP address assignments, refer to: IPv4 & IPv6 ISC DHCP Server on a Dual-Stack Network