Dual-Stack Router with Ubuntu 20.04 LTS


My primary motivation was to have a capable router that supports 25 gigabit (up & down) internet access. Big thanks to my local provide Init7. It’s not because I need it, it’s because I can. 😉 Specially as a network engineer I’m interested in new technologies.

So which router is able to serve 25G throughput? I’ve not found one that is payable for private use and makes not a lot of noise and power costs.

I’d like to thank Marcel M. as well for his support to make the initial setup.


I’ve chosen the following componets:

  • Case and Mainboard: Supermicro 5029S-TN2
  • CPU: Intel Core i5 6600K BOX (LGA 1151, 3.50GHz, 4-Core)
  • FAN: Noctua NH-L9i
  • RAM: HyperX Impact 32 GB RAM (2x 16GB)
  • Disk: Samsung 970 Evo Plus 1TB NVME
  • NIC: Lenovo ThinkSystem QLogic QL41262 (PCI-E x8)

Interesting thing is that the NIC shows capabilities for 40 & 50G too:

01:00.0 Ethernet controller: QLogic Corp. FastLinQ QL41000 Series 10/25/40/50GbE Controller (rev 02)
01:00.1 Ethernet controller: QLogic Corp. FastLinQ QL41000 Series 10/25/40/50GbE Controller (rev 02)

Basic software installation

A lot of tools are used for a router:

apt install iftop vnstat ncdu iotop iperf3 igmpproxy isc-dhcp-server htop dfc radvd strongswan wide-dhcpv6-client fail2ban ethtool bind9utils vim

and remove a ugly text editor:

apt --purge remove nano

Interface config with netplan

  version: 2
  renderer: networkd
      dhcp4: true
      critical: true #instead of workaround with networkd
      dhcp6: false
      accept-ra: true
      link-local: [ipv6]
        search: [example.net]
          - ""
          - ""
          - "2606:4700:4700::1001"
          - "2606:4700:4700::1111"
      dhcp4: false
      accept-ra: false
      link-local: []

      accept-ra: false
      id: 666
      link: enp1s0f1
      addresses: ["", "fe80::1/64"]
      accept-ra: false
      id: 660
      link: enp1s0f1
      addresses: ["", "fe80::1/64"]
      accept-ra: false
      id: 661
      link: enp1s0f1
      addresses: ["", "fe80::1/64"]
      accept-ra: false
      id: 699
      link: enp1s0f1
      addresses: ["", "fe80::1/64"]

I’ve used CloudFlare DNS because there is no faster available today. Netplan documentation can found here.

To keep the DHCPv4 lease with netplan, it’s necessary to configure the following in “/etc/systemd/network/default.network”




More info here.

Update: instead of using this ugly workaround with systemd-networkd, you just could you use netplan “critical”, which is only supported with networkd, but not with NetworkManager.
Source: https://netplan.readthedocs.io/en/stable/netplan-yaml/

DHCPv6PD with wide-dhcpv6-client

here my config file (/etc/wide-dhcpv6/dhcp6c.conf):

# Default dhpc6c configuration: it assumes the address is autoconfigured using
# router advertisements.

profile default

  request domain-name-servers;
  request domain-name;

  script "/etc/wide-dhcpv6/dhcp6c-script";

interface enp1s0f0 {
	send ia-pd 0;
	#request rapid-commit;

id-assoc pd {
	prefix 2001:db8:acdc::/48 3000 4000;
	prefix-interface dmz-wan {
		sla-id 0;
		ifid 1;
	prefix-interface lan {
		sla-id 1;
		ifid 1;
	prefix-interface mgmt {
		sla-id 2;
		ifid 1;
	prefix-interface wifi {
		sla-id 3;
		ifid 1;

The interface “enp1s0f0” is my outside facing one.

“sla-id” defines the “interfix” between prefix and host part, 0 means 2001:db8:acdc:0000::1.

“ifid 1” defines the end resp. host partition of the address: “::1”

Bonus information: I had to make a prefix hint, otherwise I got every time a new prefex, when the router gets rebooted or even worst, when the service and/or interface was restarted.

Thanks to Marcel for the hint to use that trick.

Update: this workaround is not anymore needed because Init7 has fixed there DHCPv6 issues and by now you always get your static /48 IPv6 prefix via DHCPv6PD.

DHCP with isc-dhcp-server(6)

modify “/etc/dhcp/dhcpd.conf” for vintage IP:

option domain-name "example.net";
option domain-name-servers;
option ntp-servers;

default-lease-time 600;
max-lease-time 7200;

subnet netmask {
	option routers;

and for IP in actual version “/etc/dhcp/dhcpd6.conf”:

# Server configuration file example for DHCPv6
# From the file used for TAHI tests - addresses chosen
# to match TAHI rather than example block.

# IPv6 address valid lifetime
#  (at the end the address is no longer usable by the client)
#  (set to 30 days, the usual IPv6 default)
default-lease-time 2592000;

# IPv6 address preferred lifetime
#  (at the end the address is deprecated, i.e., the client should use
#   other addresses for new connections)
#  (set to 7 days, the	usual IPv6 default)
preferred-lifetime 604800;

# T1, the delay before Renew
#  (default is 1/2 preferred lifetime)
#  (set to 1 hour)
option dhcp-renewal-time 3600;

# T2, the delay before Rebind (if Renews failed)
#  (default is 3/4 preferred lifetime)
#  (set to 2 hours)
option dhcp-rebinding-time 7200;

# Enable RFC 5007 support (same than for DHCPv4)
allow leasequery;

# Global definitions for name server address(es) and domain search list
option dhcp6.name-servers 2001:db8:6666:66::6;
option dhcp6.domain-search "example.net";

# Set preference to 255 (maximum) in order to avoid waiting for
# additional servers when there is only one
##option dhcp6.preference 255;
option dhcp6.preference 255;

# Server side command to enable rapid-commit (2 packet exchange)
##option dhcp6.rapid-commit;

# The delay before information-request refresh
#  (minimum is 10 minutes, maximum one day, default is to not refresh)
#  (set to 6 hours)
option dhcp6.info-refresh-time 21600;

subnet6 2001:db8:6666:66::/64 {
	range6 2001:db8:6666:66::d:0 2001:db8:6666:66::d:ffff;

Route Advertisements with RADVD

To provide “unknown” prefixes from DHCPv6PD, use the trick with “::/64” in prefix, file “/etc/radvd.conf”:

interface lan
	AdvSendAdvert on;
	AdvManagedFlag on;
	AdvOtherConfigFlag on;
	prefix ::/64
		AdvOnLink on;
		AdvAutonomous on;
		AdvRouterAddr on;
		AdvValidLifetime 7200;
		AdvPreferredLifetime 3600;
interface mgmt
	AdvSendAdvert on;
	AdvManagedFlag on;
	AdvOtherConfigFlag on;
	prefix ::/64
		AdvOnLink on;
		AdvAutonomous on;
		AdvValidLifetime 7200;
		AdvPreferredLifetime 3600;
interface wifi
	AdvSendAdvert on;
	AdvManagedFlag on;
	AdvOtherConfigFlag on;
	prefix ::/64
		AdvOnLink on;
		AdvAutonomous on;
		AdvValidLifetime 7200;
		AdvPreferredLifetime 3600;
interface dmz-wan
	AdvSendAdvert on;
	AdvManagedFlag on;
	AdvOtherConfigFlag on;
	prefix ::/64
		AdvOnLink on;
		AdvAutonomous on;
		AdvValidLifetime 7200;
		AdvPreferredLifetime 3600;

UFW as Firewall

Allow everything from interface “lan” to interface “enp1s0f0”:

ufw route allow in on lan out on enp1s0f0

to check, how UFW is configured, use the following command:

# ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)

it shows that incoming traffic is denied by default and outgoing is allowed. Important to know, that routed is also denied by default.

More infos about ufw firewalling here. I tried just to give you an idea how to make your firewall ruleset. 😉

For legacy IP you need to setup NAT, doing this in /etc/ufw/before.rules:

#to avoid multiple entries, flush the table first:
#do not masquerade everything from 10/8 to 10/8
-A POSTROUTING -o enp1s0f0 -s -d -j ACCEPT
#masquerade everything that goes to that interface:

If you need some port-forwardings, doing this also in /etc/ufw/before.rules

#avoid multiple entries, flush the table first:
# incoming TCP traffic on interface "enp1s0f0" to port 32400 will be translated to
-A PREROUTING -i enp1s0f0 -p tcp --dport 32400 -j DNAT --to-destination

IGMP Proxy for TV7

for TV7 from Init7, use the following “/etc/igmpproxy.conf” file:

phyint enp1s0f0 upstream ratelimit 0 threshold 1

phyint lan downstream ratelimit 0 threshold 1

“enp1s0f0” is the my provider “outside” facing interface and I’ll serve Multicast TV on my “lan” interface.

So we need to open the firewall to allow multicast streams:

ufw route allow in on enp1s0f0 out on lan proto udp from port 5000 to port 5000

for IGMP Proxy are some special UFW Rules needed, specially in “/etc/ufw/before.rules”:

-A ufw-before-input -p igmp -d -j ACCEPT

DDNS Script

if you need remote access or do you want to provide services from your own infrastructure, here’s an example script to update the DNS.


# Fully Qualified Domain Name, ending with a dot

# nsupdate key file


# get IPs from interface and remove CDR subnet notation
ip4=`ip a s dev $dev | awk '/inet / {print $2}' | sed -r 's/(\/[0-9]{1,2})'//`
ip6=`ip a s dev $dev | awk '/inet6 2/ {print $2}' | sed -r 's/(\/[0-9]{1,3})'//`

# get IPs from DNS
dns4=`host $fqdn | grep 'has address' | awk '{print $4}'`
dns6=`host $fqdn | grep 'has IPv6 address' | awk '{print $5}'`

# DNS legacy IP update
if [ $dns4 != $ip4 ]; then
 echo "update delete $fqdn A
update add $fqdn 300 A $ip4
quit" | nsupdate -k $keyf

# DNS IPv6 update
if [ $dns6 != $ip6 ]; then
 echo "update delete $fqdn AAAA
update add $fqdn 300 AAAA $ip6
quit" | nsupdate -k $keyf

Special thanks to Adrian B. for your inputs.


# speedtest

   Speedtest by Ookla

     Server: Init7 AG - Winterthur (id = 43030)
        ISP: Init7 (Switzerland) Ltd.
    Latency:     0.64 ms   (0.05 ms jitter)
   Download: 23440.14 Mbps (data used: 10.6 GB)
     Upload: 22780.67 Mbps (data used: 11.4 GB)
Packet Loss:     0.0%
 Result URL: https://www.speedtest.net/result/c/515d9bf5-2c10-4555-90ef-18e1144399a1

CLI Tool can be found here.


# vnstat -i enp1s0f0
Database updated: 2021-09-03 15:50:00

   enp1s0f0 since 2021-07-18

          rx:  2.09 TiB      tx:  1.99 TiB      total:  4.08 TiB

                     rx      |     tx      |    total    |   avg. rate
       2021-08      1.50 TiB |    1.36 TiB |    2.87 TiB |    9.42 Mbit/s
       2021-09    139.58 GiB |  101.30 GiB |  240.88 GiB |    9.00 Mbit/s
     estimated      1.54 TiB |    1.12 TiB |    2.65 TiB |

                     rx      |     tx      |    total    |   avg. rate
     yesterday     67.71 GiB |   37.10 GiB |  104.81 GiB |   10.42 Mbit/s
         today     35.67 GiB |   22.53 GiB |   58.19 GiB |    8.77 Mbit/s
     estimated     54.06 GiB |   34.15 GiB |   88.21 GiB |

impressing data since 18th of July 2021. 🙂