Freedom Internet on Debian based Router

Freedom Internet is a Dutch ISP that provides Internet Access over Fiber Networks (AON, GPON, XGSPON) from various Network Operators. As is common, these connections require the use of PPPoE and it costs extra to rent a modem from them. So why not terminate on your own equipment? In this case on a Debian based system.

There are some regional network operator variations that you'll need to take into account. Check Freedom's Helpdesk Page on running your own equipment for the current details.

This setup assumes you terminate the connection on your Debian router, either directly (fiber in router) or via a Layer2 device (e.g.: NTU that converts Fiber to Copper). In my setup, there is a switch that sits between the router (Copper) and the WAN (Fiber), acting as a NTU/media-converter. The switch is setup with the WAN interface as a 802.1q trunk port and the router port as access port in the correct VLAN, effectively stripping off the 802.1q tag towards the router. If you don't strip the vlan tag (i.e.: Your router port is also a trunk port), you will need to account for this in your router configuration.

In my setup, "enp1s0" is the LAN interface, "enp2s0" is the WAN interface and "freedom" is the ppp interface.

Relevant notes and prerequisits:
- This assumes you already set the appropriate sysctl values to support IPv4 and IPv6 routing features.
- ISC DHCP client (the default for Debian) does not support PPP interfaces and cannot be used.
- Make sure you've already have a firewall configuration file that includes your NAT setup to load into nftables.
- Ensure you installed the following packages: pppoeconf wide-dhcpv6-client nftables vlan
The installation of pppoeconf may prompt you for a setup wizard. Cancel out of this wizard - we configure by hand!

Optionally, add the following modules in /etc/modules-load.d/modules.conf to force-load them at boot:
- 8021q
- ipv6
- pppoe
This used to be mandatory on older Debian releases. Nowadays, it should dynamically load them "as needed". However, it may still be needed in some special situations.

/etc/network/interfaces

### using "allow-hotplug" to avoid the 90s "hang" on boot due to systemd quirks.
### Make sure to add VLAN interface configuration if needed - not applicable on my setup so not listed here.
### Freedom settings

allow-hotplug enp2s0
iface enp2s0 inet manual
    # To run an MTU of 1500 on PPPoE, overhead needs to be taken into account:
    # 1500 bytes + 8byte PPPoE header + 4byte dot1q tag, so interface MTU to 1512.
    # My setup is untagged so it should work with mtu 1508, but I opted for 1512 regardless.

    mtu 1512
    # Only try to bring up the PPPoE interface after this interface came up:
    up /sbin/ifup freedom
    # This should terminate the pppd process when the interface is brough down:
    pre-down /sbin/ifdown freedom

allow-hotplug freedom
iface freedom inet ppp
    # Provider neem should match name of peers file.
    provider freedom
    # Initialize nftables firewall and NAT setup when WAN interface comes up.
    up /usr/sbin/nft -f /path/to/your/firewall/file

/etc/ppp/pap-secrets

# <Username> <peer> <password>
fake@freedom.nl * 1234

/etc/ppp/peers/freedom

ifname freedom
plugin rp-pppoe.so enp2s0
nic-enp2s0
noauth
noipdefault
# Enable IPv6 using ip6cp
+ipv6 ipv6cp-use-ipaddr
ipv6 ,
defaultroute
hide-password
lcp-echo-interval 10
lcp-echo-failure 3
# Set PPPoE MTU size to 1500
mtu 1500
mru 1500
maxfail 0
persist
noaccomp
default-asyncmap
# Link this peer file to the user in pap-secrets file
user "fake@freedom.nl"
#Optional - Comment out usepeerdns to avoid overriding local DNS configuration
#usepeerdns

/etc/ppp/ip-up.d/0000usepeerdns

Optional - only use when you have an explicit local DNS configuration. Replace contents with below.

#!/bin/sh -e
## DISABLE THIS FILE TO AVOID OVERRIDING LOCAL DNS CONFIGURATOIN
## Bug in pppd makes it update /etc/resolve.conf even when "usepeerdns" is disabled in peers file.
## You can ignore this if you dont run your own local DNS setup.
exit 0

/etc/ppp/ip-up.d/freedom

#!/bin/sh
# Set parameters for PPPoE connection to Freedom on "UP".
#
# On ppp interface up event, the DHCP client needs to be restarted:

/bin/systemctl restart wide-dhcpv6-client
# No default route is pushed for IPv6 from PPPoE. Manually assign it:
/sbin/ip -6 route add default dev freedom
# Set the itnerface to accept Router Advertisements:
/sbin/sysctl -w net.ipv6.conf.freedom.accept_ra=2

/etc/wide-dhcpv6/dhcp6c.conf

interface freedom {
    # Only request Prefix Deligation
    # Network Address is assigned using ip6cp in ppp config.

    send ia-pd 0;
};
id-assoc pd 0 {
    # This *should* autoconfig the first /64 in your /48 to your LAN interface.
    # I have manually configured it in /etc/network/interfaces because this does not appear to work reliably.

    prefix-interface enp1s0 {
        # Sets /64 subnetmask (48+16=64)
        sla-len 16;
        # First available subnet x:0000::/64
        sla-id 0;
        # Select IP out of above subnet. in this case results in: x:0000::1/64
        ifid 1;
    };
};

/path/to/your/firewall/file

Setting up the firewall is beyond the scope of setting up PPPoE, so this is provided as-is. It is a generalised basic firewall "script", so use this as an example at your own risk. May contain syntax errors and typo's. The file can either be loaded using nft -f /path/to/your/firewall/file or by making it executable and running it directly.

#!/usr/bin/nft -f
# EXAMPLE FIREWALL FILE
# This example file uses vairables to define interfaces to apply rules on.

###
###Define variables
###

## Interfaces
# Add all your LAN interfaces to LANIF
# Add your WAN interface to WANIF
# Any undefined interface will drop any inbound connection.

define LANIF = { lo,enp1s0 }
define WANIF = { freedom }

## Open Ports on WANIF
# Add the desired TCP and UDP ports that need to be open on the local system here (Comma Separated)
# Example here opens SSH, HTTP, HTTPS and OpenVPN.

define TCPPORTS = { 22,80,443 }
define UDPPORTS = { 1194 }

###
### Start building Firewall
###

## flush all to start with clean slate.

flush ruleset

## Create required NAT and Filter tables
add table inet filter
add table ip nat

## Define chains
# Drop anything to this machine (INPUT) by default.
# Drop anything forwarded through this machine (FORWARD) by default.
# Permit any outbound from this machine (OUTPUT) by default.

add chain inet filter INPUT { type filter hook input priority 0; policy drop; }
add chain inet filter FORWARD { type filter hook forward priority 0; policy drop; }
add chain inet filter OUTPUT { type filter hook output priority 0; policy accept; }

###
### Input rules (to "this host")
###

## Permit all LAN traffic
# This assumes you fully control and trust your LANIF's.

add rule inet filter INPUT iifname $LANIF accept

## Allow ICMP and Established connections
# This allows return traffic for all connections initiated from this machine

add rule inet filter INPUT ct state related,established accept
# This allows all ICMP message types to this machine.
add rule inet filter INPUT meta l4proto { icmp, ipv6-icmp } accept

## Open up ports and protocols as defined
# Permit traffic to specific ports only:

add rule inet filter INPUT meta l4proto tcp tcp dport $TCPPORTS accept
add rule inet filter INPUT meta l4proto udp udp dport $UDPPORTS accept

## Some useful defaults to allow
# Permit DHCP and DHCPv6 on any interface.

add rule inet filter INPUT meta protocol ip udp sport 68 udp dport 67 accept
add rule inet filter INPUT meta protocol ip6 udp sport 547 udp dport 546 accept
# Permit traceroute/pmtud on any interface.
add rule inet filter INPUT meta l4proto udp udp dport 33434-33523 ct state new accept
add rule inet filter INPUT meta l4proto udp udp dport 33434-33523 ct state established drop
add rule inet filter INPUT meta l4proto udp udp dport 44444-44644 ct state new accept
add rule inet filter INPUT meta l4proto udp udp dport 44444-44644 ct state established drop

###
### Forwarding rules
###

## Allow all from LAN

add rule inet filter FORWARD iifname $LANIF accept

## only allow Established connections and ICMP from WAN
add rule inet filter FORWARD ct state related,established accept
add rule inet filter FORWARD meta l4proto { icmp, ipv6-icmp } accept

## Restrict from WAN
# This only permits established connections and makes your IPv6 behave like people are used to with IPv4 NAT.

add rule inet filter FORWARD iifname $WANIF meta protocol ip6 tcp flags & (fin|syn|rst|ack) != syn accept

## Allow traceroute/pmtud
# This helps for reliable tracing and mtu discovery

add rule inet filter FORWARD meta l4proto udp udp dport 33434-33523 ct state new accept
add rule inet filter FORWARD meta l4proto udp udp dport 33434-33523 ct state established drop
add rule inet filter FORWARD meta l4proto udp udp dport 44444-44644 ct state new accept
add rule inet filter FORWARD meta l4proto udp udp dport 44444-44644 ct state established drop

## NAT Routing
add chain ip nat PREROUTING { type nat hook prerouting priority -100; policy accept; }
add chain ip nat INPUT { type nat hook input priority 100; policy accept; }
add chain ip nat POSTROUTING { type nat hook postrouting priority 100; policy accept; }
add chain ip nat OUTPUT { type nat hook output priority -100; policy accept; }
add rule ip nat POSTROUTING oifname $WANIF masquerade