Tim's blah blah blah

My home network configuration - EdgeRouter X version

(Updated: )

Over the years I’ve grown my home network to the point I need to document it, which I did here. Hopefully this helps others as much as myself. Maybe I should just use the ISP’s modem instead of having a VLAN-aware collectd-powered fq_codel-sporting router, but that would be too simple now wouldn’t it? Below I document my config for Ubiquiti’s EdgeRouter X SFP.

Contents

Goal / requirements

Background

I used to have a Netgear R7800 WiFi router, which had pretty good WiFi coverage, worked well with OpenWRT, had sufficient processing power to support whatever, and sported a gigabit switch. However it was limited to 4 ports, had no SFP cage, and WiFI was built-in making it difficult to find a spot that was close to all the cabling and provided good WiFi throughout my home. With this background, I started to investigate alternatives. This guide is for the Ubiquiti EdgeRouter X SFP.

I used below reading material and sources for my basic configuration

Basic EdgeRouter configuration

Initial config

# Get current configuration
show configuration commands

# Setup pubkey & login
# Source: various
# Ensure no whitespace at end of key -- https://blog.pcfe.net/hugo/posts/2019-06-18-add-ssh-key-to-edgerouter/
scp ~tim/.ssh/id_rsa.pub ubnt@192.168.1.1:

configure;
loadkey ubnt id_rsa.pub
set service ssh disable-password-authentication
set service unms disable

compare; commit; save; exit

# Set system parameters
# Source: various
configure;
set system host-name edgerouterx
set system offload hwnat enable
set system offload ipsec enable
set system domain-name lan
set system analytics-handler send-analytics-report false
set system crash-handler send-crash-report false
set system systemd journal
set system time-zone Europe/Amsterdam

# Set trusted ntp servers
delete system ntp server
set system ntp server 0.nl.pool.ntp.org
set system ntp server 1.nl.pool.ntp.org
compare; commit; save; exit

Set up VLANs

Based on Ubiquiti’s “EdgeRouter - VLAN-Aware Switch” (ui.com)

# Clean up interfaces, create VLANs on switch
delete interfaces switch switch0 address
# VLAN 1 - Internal (all access - trusted devices, servers, etc)
set interfaces switch switch0 vif 1 address  172.16.99.1/24
set interfaces switch switch0 vif 1 description LAN
# VLAN 10 - Guest (only WAN -- work laptops, guest wifi, etc)
set interfaces switch switch0 vif 10 address 172.16.10.1/24
set interfaces switch switch0 vif 10 description Guest
# VLAN 20 - IoT (no WAN access - untrusted IoT devices)
set interfaces switch switch0 vif 20 address 172.16.20.1/24
set interfaces switch switch0 vif 20 description IoT

# dhcp/dnsmasq for three VLANs
# delete service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 start 172.16.99.100 stop 172.16.99.254
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 default-router 172.16.99.1
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 dns-server 172.16.99.1
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 domain-name lan

# delete service dhcp-server shared-network-name vlan10 subnet 172.16.10.0/24
set service dhcp-server shared-network-name vlan10 subnet 172.16.10.0/24 start 172.16.10.100 stop 172.16.10.254
set service dhcp-server shared-network-name vlan10 subnet 172.16.10.0/24 default-router 172.16.10.1
set service dhcp-server shared-network-name vlan10 subnet 172.16.10.0/24 dns-server 172.16.10.1
set service dhcp-server shared-network-name vlan10 subnet 172.16.10.0/24 domain-name lan

# delete service dhcp-server shared-network-name vlan20 subnet 172.16.20.0/24
set service dhcp-server shared-network-name vlan20 subnet 172.16.20.0/24 start 172.16.20.100 stop 172.16.20.254
set service dhcp-server shared-network-name vlan20 subnet 172.16.20.0/24 default-router 172.16.20.1
set service dhcp-server shared-network-name vlan20 subnet 172.16.20.0/24 dns-server 172.16.20.1
set service dhcp-server shared-network-name vlan20 subnet 172.16.20.0/24 domain-name lan

set interfaces switch switch0 switch-port vlan-aware enable

# Set WAN port: T-mobile WAN (tagged VLAN 300)
set interfaces ethernet eth0 vif 300
set interfaces ethernet eth0 vif 300 description T-mobile
set interfaces ethernet eth0 vif 300 firewall

set service nat rule 5010 outbound-interface eth0.300
set service nat rule 5010 description 'masquerade for WAN'
set service nat rule 5010 log disable
set service nat rule 5010 protocol all
set service nat rule 5010 type masquerade

# Trunk port to switch (tagged VLANS 10, 20, untagged default VLAN 1)
set interfaces switch switch0 switch-port interface eth1 vlan pvid 1
set interfaces switch switch0 switch-port interface eth1 vlan vid 10
set interfaces switch switch0 switch-port interface eth1 vlan vid 20
# Local ports: server & hue & appleTV (untagged default VLAN 1)
set interfaces switch switch0 switch-port interface eth2 vlan pvid 1
set interfaces switch switch0 switch-port interface eth3 vlan pvid 1
set interfaces switch switch0 switch-port interface eth4 vlan pvid 1

delete service dhcp-server shared-network-name LAN

compare;
commit; save;

Set up dhcp

# update to dnsmasq
set service dhcp-server use-dnsmasq enable 

# Whitelist interfaces to listen on
set service dns forwarding listen-on switch0.1
set service dns forwarding listen-on switch0.10
set service dns forwarding listen-on switch0.20
# Alternatively blacklist interfaces, e.g. WAN
# set service dns forwarding except-interface eth0

# Set options -- https://linux.die.net/man/8/dnsmasq
set service dns forwarding options bogus-priv
set service dns forwarding options domain-needed
set service dns forwarding options enable-ra
set service dns forwarding options localise-queries
# Increase cache, default is a very conservative 150 -- https://linux.die.net/man/8/dnsmasq under 'Limits'
set service dns forwarding cache-size 10000

# Ensure we can resolve the edgerouter hostname (instead of 127.0.0.1) 
# add extra static DNS entry for router hostname. Better than disabling 
# static hosts completely -- https://floating.io/2019/01/edgerouter-dns-forwarding-and-the-routers-hostname/
set system static-host-mapping host-name edgerouterx.lan inet 172.16.99.1

compare; commit; save

# Static ips/host names

set system static-host-mapping host-name proteus.lan inet 172.16.99.2
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 static-mapping proteus ip-address 172.16.99.2
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 static-mapping proteus mac-address 94:C6:91:12:5E:EC

set system static-host-mapping host-name gs108e.lan inet 172.16.99.3
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 static-mapping gs108e ip-address 172.16.99.3
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 static-mapping gs108e mac-address 78:D2:94:2F:81:F8

set system static-host-mapping host-name UAP-LR1-Office.lan inet 172.16.99.10
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 static-mapping UAP-LR1-office ip-address 172.16.99.10
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 static-mapping UAP-LR1-office mac-address 18:E8:29:93:E1:66

set system static-host-mapping host-name UAP-LR2-Living.lan inet 172.16.99.11
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 static-mapping UAP-LR2-Living ip-address 172.16.99.11
set service dhcp-server shared-network-name vlan1 subnet 172.16.99.0/24 static-mapping UAP-LR2-Living mac-address 18:E8:29:E6:00:2E

Harden services

set service gui listen-address 172.16.99.1
set service gui older-ciphers disable

set service ssh listen-address 172.16.99.1
set service ssh port 22
set service ssh protocol-version v2

# Create hashed password for user manually such that we don't have to store the plaintext password in our config
# openssl passwd -5
# set system login user ubnt authentication encrypted-password $5$<salt>$<hash>

# Optionally create alternative user instead of the default
# set system login user admin authentication plaintext-password <password>
# set system login user admin level admin
# delete system login user ubnt
commit; save

Set QoS with smart-queue

set traffic-control smart-queue WAN_QUEUE wan-interface eth0.300
set traffic-control smart-queue WAN_QUEUE upload rate 100mbit
set traffic-control smart-queue WAN_QUEUE download rate 100mbit
commit; save

Define zone based firewall

We define the following zones:

And have the following policies between them:

In tabular form:

to GUEST to IOT to LAN to LOCAL to WAN
GUEST FW_DROP GUESTIOT_TO_LAN GUESTIOT_TO_LOCAL FW_ACCEPT
IOT FW_DROP GUESTIOT_TO_LAN GUESTIOT_TO_LOCAL FW_DROP
LAN FW_ACCEPT FW_ACCEPT FW_ACCEPT FW_ACCEPT
LOCAL FW_ACCEPT FW_ACCEPT FW_ACCEPT FW_ACCEPT
WAN WAN_TO_REST FW_DROP WAN_TO_REST WAN_TO_LOCAL
### WAN to X

set firewall name WAN_TO_ALL default-action drop

set firewall name WAN_TO_ALL rule 10 action accept
set firewall name WAN_TO_ALL rule 10 description 'accept established/related'
set firewall name WAN_TO_ALL rule 10 state established enable
set firewall name WAN_TO_ALL rule 10 state related enable

set firewall name WAN_TO_ALL rule 100 action drop
set firewall name WAN_TO_ALL rule 100 description 'drop invalid'
set firewall name WAN_TO_ALL rule 100 state invalid enable

### LAN to X
### LOCAL to X
set firewall name FW_ACCEPT default-action accept

set firewall name FW_ACCEPT_EST_REL default-action drop
set firewall name FW_ACCEPT_EST_REL rule 10 action accept
set firewall name FW_ACCEPT_EST_REL rule 10 description 'accept established/related'
set firewall name FW_ACCEPT_EST_REL rule 10 state established enable
set firewall name FW_ACCEPT_EST_REL rule 10 state related enable

set firewall name FW_ACCEPT_EST_REL rule 100 action drop
set firewall name FW_ACCEPT_EST_REL rule 100 description 'drop invalid'
set firewall name FW_ACCEPT_EST_REL rule 100 state invalid enable

### GUEST to X
set firewall name FW_DROP default-action drop

set firewall name GUESTIOT_TO_LOCAL default-action drop

set firewall name GUESTIOT_TO_LOCAL rule 10 action accept
set firewall name GUESTIOT_TO_LOCAL rule 10 description 'accept dns'
set firewall name GUESTIOT_TO_LOCAL rule 10 log disable
set firewall name GUESTIOT_TO_LOCAL rule 10 protocol udp
set firewall name GUESTIOT_TO_LOCAL rule 10 destination port 53

set firewall name GUESTIOT_TO_LOCAL rule 20 action accept
set firewall name GUESTIOT_TO_LOCAL rule 20 description 'accept dhcp'
set firewall name GUESTIOT_TO_LOCAL rule 20 log disable
set firewall name GUESTIOT_TO_LOCAL rule 20 protocol udp
set firewall name GUESTIOT_TO_LOCAL rule 20 destination port 67-68

set firewall name GUESTIOT_TO_LOCAL rule 30 action drop
set firewall name GUESTIOT_TO_LOCAL rule 30 description 'drop invalid'
set firewall name GUESTIOT_TO_LOCAL rule 30 state invalid enable

### IOT to LAN
### GUEST to LAN

set firewall name GUESTIOT_TO_LAN default-action drop

set firewall name GUESTIOT_TO_LAN rule 10 action accept
set firewall name GUESTIOT_TO_LAN rule 10 description 'accept established/related'
set firewall name GUESTIOT_TO_LAN rule 10 log disable
set firewall name GUESTIOT_TO_LAN rule 10 state established enable
set firewall name GUESTIOT_TO_LAN rule 10 state related enable

set firewall name GUESTIOT_TO_LAN rule 20 action accept
set firewall name GUESTIOT_TO_LAN rule 20 description 'accept mqtt(s) to proteus'
set firewall name GUESTIOT_TO_LAN rule 20 log disable
set firewall name GUESTIOT_TO_LAN rule 20 destination address 172.16.99.2
set firewall name GUESTIOT_TO_LAN rule 20 protocol tcp
set firewall name GUESTIOT_TO_LAN rule 20 destination port 8883,1883

set firewall name GUESTIOT_TO_LAN rule 30 action accept
set firewall name GUESTIOT_TO_LAN rule 30 description 'accept http(s) to proteus'
set firewall name GUESTIOT_TO_LAN rule 30 log disable
set firewall name GUESTIOT_TO_LAN rule 30 protocol tcp
set firewall name GUESTIOT_TO_LAN rule 30 destination port 80,443
set firewall name GUESTIOT_TO_LAN rule 30 destination address 172.16.99.2

set firewall name GUESTIOT_TO_LAN rule 100 action drop
set firewall name GUESTIOT_TO_LAN rule 100 description 'drop invalid'
set firewall name GUESTIOT_TO_LAN rule 100 state invalid enable

### Delete original firewall
delete interfaces ethernet eth0 firewall in 
delete interfaces ethernet eth0 firewall local
commit;save

delete firewall name WAN_IN
delete firewall name WAN_LOCAL
commit;save


### Enable policies
set zone-policy zone WAN interface eth0.300
set zone-policy zone WAN default-action drop
set zone-policy zone WAN from LOCAL firewall name FW_ACCEPT
set zone-policy zone WAN from LAN firewall name FW_ACCEPT
set zone-policy zone WAN from GUEST firewall name FW_ACCEPT
set zone-policy zone WAN from IOT firewall name FW_DROP

set zone-policy zone LAN interface switch0.1
set zone-policy zone LAN default-action drop
set zone-policy zone LAN from LOCAL firewall name FW_ACCEPT
set zone-policy zone LAN from WAN firewall name WAN_TO_ALL
set zone-policy zone LAN from GUEST firewall name IOT_TO_LAN
set zone-policy zone LAN from IOT firewall name IOT_TO_LAN

set zone-policy zone GUEST interface switch0.10
set zone-policy zone GUEST default-action drop
set zone-policy zone GUEST from LOCAL firewall name FW_ACCEPT
set zone-policy zone GUEST from WAN firewall name WAN_TO_ALL
set zone-policy zone GUEST from LAN firewall name FW_ACCEPT_EST_REL
set zone-policy zone GUEST from IOT firewall name FW_DROP

set zone-policy zone IOT interface switch0.20
set zone-policy zone IOT default-action drop
set zone-policy zone IOT from LOCAL firewall name FW_ACCEPT
set zone-policy zone IOT from WAN firewall name FW_DROP
set zone-policy zone IOT from LAN firewall name FW_ACCEPT
set zone-policy zone IOT from GUEST firewall name FW_DROP

set zone-policy zone LOCAL local-zone
set zone-policy zone LOCAL default-action drop
set zone-policy zone LOCAL from IOT firewall name GUESTIOT_TO_LOCAL
set zone-policy zone LOCAL from WAN firewall name WAN_TO_ALL
set zone-policy zone LOCAL from LAN firewall name FW_ACCEPT
set zone-policy zone LOCAL from GUEST firewall name GUESTIOT_TO_LOCAL

set firewall all-ping enable
set firewall broadcast-ping disable
set firewall ip-src-route disable
set firewall log-martians enable
set firewall receive-redirects disable
set firewall send-redirects enable
set firewall source-validation disable
set firewall syn-cookies enable

compare; commit; save

delete firewall name LOCAL_TO_ALL
delete firewall name WAN_TO_LAN
delete firewall name WAN_TO_REST
delete firewall name WAN_TO_LOCAL

Port forwarding

set port-forward auto-firewall enable
set port-forward hairpin-nat enable
set port-forward wan-interface eth0.300
set port-forward lan-interface switch0.1
set port-forward lan-interface switch0.10
set port-forward lan-interface wg0

set port-forward rule 10 description https-proteus
set port-forward rule 10 forward-to address 172.16.99.2
set port-forward rule 10 forward-to port 443
set port-forward rule 10 original-port 443
set port-forward rule 10 protocol tcp

set port-forward rule 20 description http-proteus
set port-forward rule 20 forward-to address 172.16.99.2
set port-forward rule 20 forward-to port 80
set port-forward rule 20 original-port 80
set port-forward rule 20 protocol tcp

set port-forward rule 30 description IKE-ESP-proteus
set port-forward rule 30 forward-to address 172.16.99.2
set port-forward rule 30 forward-to port 500
set port-forward rule 30 original-port 500
set port-forward rule 30 protocol udp

set port-forward rule 40 description IKE-AH-proteus
set port-forward rule 40 forward-to address 172.16.99.2
set port-forward rule 40 forward-to port 4500
set port-forward rule 40 original-port 4500
set port-forward rule 40 protocol udp

set port-forward rule 50 description ssh-proteus
set port-forward rule 50 forward-to address 172.16.99.2
set port-forward rule 50 forward-to port 10022
set port-forward rule 50 original-port 10022
set port-forward rule 50 protocol tcp

set port-forward rule 60 description mqtt-proteus
set port-forward rule 60 forward-to address 172.16.99.1
set port-forward rule 60 forward-to port 1883
set port-forward rule 60 original-port 8883
set port-forward rule 60 protocol tcp

compare; commit ; save

Statistics via collectd/SNMP

To read out statistics from the router and integrate it in my collectd-influxdb setup, I use below setup. Based on edgerouter_metrics.sh (github.com) and EdgeRouter - SNMP (ui.com)

set service snmp community public authorization ro
set service snmp listen-address 172.16.99.1
set service snmp ignore-interface switch
compare; commit ; save
LoadPlugin snmp

<Plugin snmp>
  <Data "load1">
    Type "if_octets"
    Table true
    Instance "IF-MIB::ifDescr"
    Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
  </Data>
  <Data "std_traffic">
    Type "if_octets"
    Table true
    Instance "IF-MIB::ifDescr"
    Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
  </Data>
  <Host "my.lab.dev">
    Address "172.16.99.1"
    Version 2
    Community "public"
    Collect "std_traffic"
    Interval 30
  </Host>
</Plugin>

VPN

I like using my personal VPN when away from home for security and privacy reasons: on e.g. hotel WiFi my traffic cannot be intercepted, and the same hotel operator cannot track (or block) my browsing habits. While I can run a VPN on a much more powerful home server, I prefer running it on my router to I isolate my more ‘vital’ services from hobby projects. Since I only need performance to match my WAN speed (~100 Mbit), my router is usually fast enough. Furthermore, since EdgeOS is based on linux, this gives ample opportunity.

Below I document my VPN configuration for a typical roadwarrior setup (wikipedia.org). I test a classical IPsec IKEv2 strongswan setup (pro: native in EdgeOS, natively supported by many clients, proven software), and the new-ish wireguard setup (pro: faster, easier setup, possibly more secure due to clean new audited code.)

Algorithm speed and security

Crypto can be complex, and it’s usually best not to tweak too much and leave stuff to professionals. However, some understanding of algorithm security and performance is useful to get a smoothly running setup.

Some sources for specifically VPN:

N.B. while SHA1 is broken as digest, as HMAC for IPSec it’s OK, see How vulnerable is IPSec HMAC-SHA1 (stackexchange.com).

I benchmarked some ciphers & digest algorithms to get an idea of what performance I could expect, and which algos benefit from hardware acceleration. Other sources include:

Overall it seems most algorithsm achieve >100Mbit/sec, so I should be fine. chacha20-poly1305 looks especially promosing for doing the bulk of the work.

Get list of supported ciphers (N.B. does not always list the right names)

openssl list -cipher-algorithms
openssl list -digest-algorithms
openssl list -public-key-algorithms
openssl ciphers

Benchmark digest algorithms:

openssl speed sha1 sha256

The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes  16384 bytes
sha1              7758.29k    19668.84k    37277.01k    48216.75k    52434.24k    52969.47k
sha1 (evp)        3336.90k    10655.45k    26682.37k    42721.62k    51773.44k    52347.15k
sha256            4204.55k     9675.05k    16945.83k    20919.98k    22330.68k    22544.38k
sha256 (evp)      2433.22k     6776.30k    14358.36k    19803.48k    22197.33k    22446.08k

Benchmark encryption algorithms:

openssl speed -evp AES-128-cbc AES-192-CBC AES-256-CBC ChaCha20-Poly1305 aes-128-gcm aes-256-gcm
# Use -evp to select cpu optimization -- see https://security.stackexchange.com/questions/35036/different-performance-of-openssl-speed-on-the-same-hardware-with-aes-256-evp-an/35042#35042
The 'numbers' are in 1000s of bytes per second processed.
type                  16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes  16384 bytes
aes-128-cbc          10203.55k    12601.71k    13388.29k    13575.65k    13645.14k    13602.53k
aes-192-cbc           8986.30k    10836.80k    11393.79k    11493.97k    11613.53k    11561.33k
aes-256-cbc           8072.41k     9452.59k     9945.43k    10028.74k    10078.89k    10092.54k
chacha20-poly1305    10727.41k    17301.67k    19646.81k    20274.18k    20561.92k    20477.28k
aes-128-gcm           5904.12k     6709.03k     6923.74k     7017.47k     7039.66k     7032.60k
aes-256-gcm           5117.99k     5688.05k     5885.35k     5915.99k     5938.52k     5947.39k
des-cbc               6249.88k     7090.37k     7453.01k     7518.55k     7539.37k     7536.64k

Benchmark pubkey algorithms:

openssl speed ecdsap192 ecdsap224 ecdsap256 rsa2048 dsa2048 rsa1024 dsa1024 rsa4096
                               sign    verify    sign/s verify/s
 192 bit ecdsa (nistp192)    0.0119s   0.0077s     83.8    129.3
 224 bit ecdsa (nistp224)    0.0168s   0.0106s     59.6     94.1
 256 bit ecdsa (nistp256)    0.0231s   0.0139s     43.3     71.9
rsa 1024 bits                0.013908s 0.000719s   71.9   1391.3
dsa 1024 bits                0.009891s 0.009167s  101.1    109.1
rsa 2048 bits                0.095000s 0.002426s   10.5    412.2
dsa 2048 bits                0.031847s 0.029792s   31.4     33.6
rsa 4096 bits                0.625625s 0.008817s    1.6    113.4

IPSec IKEv2 native setup

(Apparently) since firmware version 2.0.9, EdgeOS offers native IKEv2 IPSec integration, so called ‘remote-access’ in EdgeOS configuration lingo. Below I document my setup

Based on:

Certificate generation (adapted from Creekside (creekside.network), but with 4096bit certificate instead of 2048.)

mkdir -p ~/ipsec.d/{cacerts,certs,private,reqs}

openssl req -x509 -newkey rsa:4096 -nodes -days 3650 \
    -keyout ~/ipsec.d/private/ca-key.pem \
    -out ~/ipsec.d/cacerts/ca-cert.cer \
    -subj "/CN=EdgerouterX Root CA"

generate vpn rsa-key bits 4096 | tee localhost.pub

sudo cp /config/ipsec.d/rsa-keys/localhost.key ~/ipsec.d/private/server-key.pem

mv ./localhost.pub ~/ipsec.d/certs/
sudo chmod +r ~/ipsec.d/private/server-key.pem

openssl req -new -nodes \
  -key ~/ipsec.d/private/server-key.pem \
  -out ~/ipsec.d/reqs/server-req.csr \
  -subj /CN="home.vanwerkhoven.org"

echo "authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth,clientAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = home.vanwerkhoven.org" >  ~/ipsec.d/reqs/server-req.ext

openssl x509 -req -days 1825 -CAcreateserial \
  -in  ~/ipsec.d/reqs/server-req.csr \
  -CA ~/ipsec.d/cacerts/ca-cert.cer \
  -CAkey ~/ipsec.d/private/ca-key.pem  \
  -out ~/ipsec.d/certs/server-cert.pem \
  -extfile ~/ipsec.d/reqs/server-req.ext

# Install certs in system dir
sudo cp -r ~/ipsec.d /config/

Edgerouter configuration

# Delete other config (if present)
delete vpn ipsec

# Add users
set vpn ipsec remote-access authentication local-users username USERNAME password PASSWORD
set vpn ipsec remote-access authentication mode local
set vpn ipsec remote-access client-ip-pool subnet 172.16.30.128/25
set vpn ipsec remote-access compatibility-mode disable
# Required to get the WAN IP of the IPsec server/router
set vpn ipsec remote-access dhcp-interface eth0.300
set vpn ipsec remote-access dns-servers server-1 172.16.99.1

# Commercial National Security Algorithm Suite recommends (https://docs.strongswan.org/docs/5.9/config/IKEv2CipherSuites.html#_commercial_national_security_algorithm_suite)
# aes256-sha384-modp3072
# aes256-sha384-ecp384
# Default iOS 15 IKEv2 offers these suites (2 good, 1 OK, 2 poor)
# IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048
# IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256
# IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1536
# IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024
# IKE:3DES_CBC/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024

# We stick to the two strongest iOS offers, and don't allow weaker options
set vpn ipsec remote-access esp-settings proposal 1 dh-group 19 # ecp256
set vpn ipsec remote-access esp-settings proposal 1 encryption aes256
set vpn ipsec remote-access esp-settings proposal 1 hash sha256
set vpn ipsec remote-access esp-settings proposal 2 dh-group 14 # modp2048
set vpn ipsec remote-access esp-settings proposal 2 encryption aes256
set vpn ipsec remote-access esp-settings proposal 2 hash sha256

set vpn ipsec remote-access ike-settings authentication mode x509
set vpn ipsec remote-access ike-settings authentication x509 ca-cert-file /config/ipsec.d/cacerts/ca-cert.cer
set vpn ipsec remote-access ike-settings authentication x509 local-id home.vanwerkhoven.org
set vpn ipsec remote-access ike-settings authentication x509 remote-id %any
set vpn ipsec remote-access ike-settings authentication x509 remote-ca-cert-file /config/ipsec.d/cacerts/ca-cert.cer
set vpn ipsec remote-access ike-settings authentication x509 server-cert-file /config/ipsec.d/certs/server-cert.pem
set vpn ipsec remote-access ike-settings authentication x509 server-key-file /config/ipsec.d/private/server-key.pem
set vpn ipsec remote-access ike-settings authentication x509 server-key-type rsa
set vpn ipsec remote-access ike-settings fragmentation disable
set vpn ipsec remote-access ike-settings ike-lifetime 86400
set vpn ipsec remote-access ike-settings operating-mode ikev2-mobike

# Same as above
set vpn ipsec remote-access ike-settings proposal 1 dh-group 19 # ecp256
set vpn ipsec remote-access ike-settings proposal 1 encryption aes256
set vpn ipsec remote-access ike-settings proposal 1 hash sha256
set vpn ipsec remote-access ike-settings proposal 2 dh-group 14 # modp2048
set vpn ipsec remote-access ike-settings proposal 2 encryption aes256
set vpn ipsec remote-access ike-settings proposal 2 hash sha256

set vpn ipsec remote-access inactivity 28800

# Ensure DNS is available for VPN clients
set service dns forwarding options "listen-address=172.16.99.1"

# Don't masquerade VPN traffic
set service nat rule 5001 description "exclude roadwarrior ipsec"
set service nat rule 5001 log disable
set service nat rule 5001 outbound-interface eth0.300
set service nat rule 5001 protocol all
set service nat rule 5001 type masquerade
set service nat rule 5001 destination address 172.16.30.128/25
set service nat rule 5001 exclude
commit; save; exit;

Below we refine our WAN firewall for IPsec-based zone-based firewall

Initially I thought ipsec traffic originates from WAN, hence these zones are split. In reality it seems

set firewall name WAN_TO_LOCAL default-action drop

set firewall name WAN_TO_LOCAL rule 10 action accept
set firewall name WAN_TO_LOCAL rule 10 description 'accept established/related'
set firewall name WAN_TO_LOCAL rule 10 state established enable
set firewall name WAN_TO_LOCAL rule 10 state related enable
set firewall name WAN_TO_LOCAL rule 10 log disable

set firewall name WAN_TO_LOCAL rule 20 action accept
set firewall name WAN_TO_LOCAL rule 20 description 'Allow IKE'
set firewall name WAN_TO_LOCAL rule 20 destination port 500
set firewall name WAN_TO_LOCAL rule 20 protocol udp

set firewall name WAN_TO_LOCAL rule 30 action accept
set firewall name WAN_TO_LOCAL rule 30 description 'Allow ESP'
set firewall name WAN_TO_LOCAL rule 30 protocol esp

set firewall name WAN_TO_LOCAL rule 40 action accept
set firewall name WAN_TO_LOCAL rule 40 description 'Allow NAT-T'
set firewall name WAN_TO_LOCAL rule 40 destination port 4500
set firewall name WAN_TO_LOCAL rule 40 protocol udp

set firewall name WAN_TO_LOCAL rule 50 action accept
set firewall name WAN_TO_LOCAL rule 50 description 'wireguard'
set firewall name WAN_TO_LOCAL rule 50 destination port 51820
set firewall name WAN_TO_LOCAL rule 50 protocol udp

set firewall name WAN_TO_LOCAL rule 100 action drop
set firewall name WAN_TO_LOCAL rule 100 description 'drop invalid'
set firewall name WAN_TO_LOCAL rule 100 state invalid enable
set firewall name WAN_TO_LOCAL rule 100 log disable

set firewall name WAN_TO_REST default-action drop

set firewall name WAN_TO_REST rule 10 action accept
set firewall name WAN_TO_REST rule 10 description 'accept established/related'
set firewall name WAN_TO_REST rule 10 state established enable
set firewall name WAN_TO_REST rule 10 state related enable
set firewall name WAN_TO_REST rule 10 log disable

set firewall name WAN_TO_REST rule 100 action drop
set firewall name WAN_TO_REST rule 100 description 'drop invalid'
set firewall name WAN_TO_REST rule 100 state invalid enable
set firewall name WAN_TO_REST rule 100 log disable

set zone-policy zone LAN from WAN firewall name WAN_TO_REST
set zone-policy zone GUEST from WAN firewall name WAN_TO_REST
set zone-policy zone LOCAL from WAN firewall name WAN_TO_LOCAL

delete firewall name WAN_TO_ALL

TODO: To isolate ipsec clients, we should add some rules about traffic originating from local. Also asked via https://community.ui.com/questions/How-to-merge-zone-based-firewall-with-a-IPSec-VPN-setup/35750a89-ca80-464d-9bcb-37778ae6c31e (ui.com)

# Update ipsec-traffic firewalling from zone LOCAL --> LATER, not sure where ipsec traffic originates from...

# set firewall name LOCAL_TO_ALL default-action accept

# set firewall name LOCAL_TO_ALL rule 10 action accept
# set firewall name LOCAL_TO_ALL rule 10 description 'accept ipsec traffic to server'
# set firewall name LOCAL_TO_ALL rule 10 ipsec match-ipsec
# set firewall name LOCAL_TO_ALL rule 10 destination address  172.16.99.2/32
# set firewall name LOCAL_TO_ALL rule 10 log disable

# set firewall name LOCAL_TO_ALL rule 20 action drop
# set firewall name LOCAL_TO_ALL rule 20 description 'drop other ipsec traffic'
# set firewall name LOCAL_TO_ALL rule 20 ipsec match-ipsec
# set firewall name LOCAL_TO_ALL rule 20 log disable

# set zone-policy zone LAN from LOCAL firewall name LOCAL_TO_ALL
# set zone-policy zone LAN from GUEST firewall name LOCAL_TO_ALL
# set zone-policy zone LAN from IOT firewall name LOCAL_TO_ALL

IPSec IKEv2 manual Strongswan configuration

Alternatively, you can configure Strongswan yourself, but this is not really necessary or useful since EdgeOS firmware 2.0.9. If you want to, the config could/should look something like this:

configure
set vpn rsa-keys local-key file /config/ipsec.d/rsa-keys/localhost.key
set vpn rsa-keys rsa-key-name localhost.pub rsa-key <pubkey> # from localhost.pub
commit;save;exit

echo "
# customized roadwarrior configuration by Creekside Networks LLC
config setup
    uniqueids=never

ca rootca
    cacert=/config/ipsec.d/cacerts/ca-cert.cer
    auto=add

# This can be improved, don't use 3des and use modp2048 at least
conn winlx
	#iOS offers: IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256, IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1536, IKE:AES_CBC_128/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024, IKE:3DES_CBC/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_1024
	# notation: comma-separated list of IKE/ISAKMP SA encryption/authentication algorithms to be used, e.g.
aes128-sha256-modp3072. The notation is encryption-integrity[-prf]-dhgroup. In IKEv2, multiple algorithms
and proposals may be included, such as aes128-aes256-sha1-modp3072-modp2048,3des-sha1-md5-modp1024.

	#AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256
	#AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048
    ike=aes256-sha256-ecp256,aes256-sha256-modp2048! #aes128-sha1-ecp256,chacha20poly1305-sha1-ecp256! #,

    # notation: comma-separated list of ESP encryption/authentication algorithms to be used for the connection, e.g.
aes128-sha256. The notation is encryption-integrity[-dhgroup][-esnmode].
For IKEv2, multiple algorithms (separated by -) of the same type can be included in a single proposal.
    esp=aes256-sha256-ecp256,aes256-sha256-modp2048! #aes128-sha1-ecp256, chacha20poly1305-sha1-ecp256!
    keyexchange=ikev2
    compress=no
    type=tunnel
    fragmentation=yes
    forceencaps=yes
    ikelifetime=4h
    lifetime=2h
    auto=add
    dpddelay=300s
    dpdtimeout=30s
    dpdaction=clear
    rekey=no
    left=%any
    leftcert=/config/ipsec.d/certs/server-cert.pem
    leftsendcert=always
    leftsubnet=0.0.0.0/0
    right=%any
    rightid=%any
    rightsourceip=172.16.30.128/25
    rightdns=172.16.99.1
    rightsendcert=never
    rightauth=eap-mschapv2
    eap_identity=%identity

conn apple
    also=winlx
    leftid=@home.vanwerkhoven.org
" | sudo tee /config/ipsec.d/ipsec.conf

echo "
# roadwarrior user accounts 
 : RSA /config/ipsec.d/private/server-key.pem
bob  : EAP bobpasswd
alice : EAP alicepasswd
"  | sudo tee /config/ipsec.d/ipsec.secrets

EdgeOS configuration

delete vpn ipsec remote-access
set vpn ipsec include-ipsec-conf /config/ipsec.d/ipsec.conf
set vpn ipsec include-ipsec-secrets /config/ipsec.d/ipsec.secrets

Wireguard setup

The latest VPN buzz is all about Wireguard, which I document below. Wireguard is faster (better key choice), easier (config should be simpler / quicker / less error-prone), and more secure (cleaner codebase without legacy stuff that people can actually audit).

Based on

For Edgerouter X, my b name is e50, the latest release at time of writing is 1.0.20220627. Since I’m using firmware v2 I need the v2* release branch.

curl -OL https://github.com/WireGuard/wireguard-vyatta-ubnt/releases/download/1.0.20220627-1/e50-v2-v1.0.20220627-v1.0.20210914.deb
sudo dpkg -i e50-v2-v1.0.20220627-v1.0.20210914.deb

Now configure, include pre-shared keys (wireguard.com) for quantum resistance (archlinux.org):

wg genkey | tee /config/auth/wg.key | wg pubkey >  wg.public
wg genpsk | tee /config/auth/peer_YyEE.psk
wg genpsk | tee /config/auth/peer_58uk.psk
wg genpsk | tee /config/auth/peer_wtE3.psk

configure

# Create interface and assign IP
set interfaces wireguard wg0 address 172.16.40.1/24
set interfaces wireguard wg0 listen-port 51820

# Allow DNS on wg0
set service dns forwarding listen-on wg0

# Create routes automatically for allowed (peer?) IPs?
# set interfaces wireguard wg0 route-allowed-ips true # combine with limited allowed-ips 172.16.40.128/32 but gives routing problem
# set interfaces wireguard wg0 route-allowed-ips false # combine with unlimited allowed-ips 0.0.0.0/0 to check if this resolves routing issues --> no does not

# Add peer(s) with allowed source IPs
set interfaces wireguard wg0 peer YyEEroGylL0qcYn9oRn8sPG5uDqdJmj+p33QZ6HkYWc= allowed-ips 172.16.40.129/32 # iPhone Neptune
set interfaces wireguard wg0 peer YyEEroGylL0qcYn9oRn8sPG5uDqdJmj+p33QZ6HkYWc= preshared-key <FROM /config/auth/peer_YyEE.psk>

set interfaces wireguard wg0 peer kzKlp6jt3zwSpSaAUVqGJX9lMPRi91vxEFykN0XQB2Y= allowed-ips 172.16.40.130/32 # iPad?

set interfaces wireguard wg0 peer 58ukfs/kQcqmvTYZGpBiSANyv4mOJ2/zUdM3p5cg/2Y= allowed-ips 172.16.40.131/32 # iPhone Helene
set interfaces wireguard wg0 peer 58ukfs/kQcqmvTYZGpBiSANyv4mOJ2/zUdM3p5cg/2Y= preshared-key <FROM /config/auth/peer_58uk.psk>

set interfaces wireguard wg0 peer wtE3q8VtTnwWmBV0WX7RcL0kpQC8ib/3Q1gtqXx8ZWI= allowed-ips 172.16.40.132/32 # Macbook pro
set interfaces wireguard wg0 peer wtE3q8VtTnwWmBV0WX7RcL0kpQC8ib/3Q1gtqXx8ZWI= preshared-key <FROM /config/auth/peer_wtE3.psk>

# Private key
set interfaces wireguard wg0 private-key /config/auth/wg.key

# Allow wireguard traffic into the router
set firewall name WAN_TO_ALL rule 15 action accept
set firewall name WAN_TO_ALL rule 15 description 'WireGuard'
set firewall name WAN_TO_ALL rule 15 log disable
set firewall name WAN_TO_ALL rule 15 protocol udp
set firewall name WAN_TO_ALL rule 15 destination port 51820

# Add wireguard clients to GUEST zone
# delete zone-policy zone GUEST interface wg0
set zone-policy zone LAN interface wg0

delete service nat rule 5002 -- not required as wireguard traffic goes over interface wg0, should be masqueraded there.
# set service nat rule 5002 description "exclude roadwarrior ipsec"
# set service nat rule 5002 log disable
# set service nat rule 5002 outbound-interface eth0.300
# set service nat rule 5002 protocol udp
# set service nat rule 5002 type masquerade
# set service nat rule 5002 destination address 172.16.40.129/25
# set service nat rule 5002 exclude

commit; save; exit

Real VPN performance

With two working VPN setups, I tested performance of both on a real world setup: me remotely working on a fairly decent DSL internet connection, testing CPU load on my router using while true; do top -b -d 0.5 | grep %Cpu; done then averaging the data during the 30 seconds of running the download test on fast.com (fast.com). Since this method measures idle CPU (a bit convulated, I know), I also ran an idle No VPN test to see base CPU load, which gives an idle of 96%, or 4% CPU load. To convert idle CPU to bandwith, I calculated 39 MBps (the local line speed I got via VPN)/(100% - system idle - 4%). Note that this load includes everything, e.g. also routing and firewalling. I calculcated both the average bandwidth as the minimum bandwidth, which can typically be the gating factor.

This shows IPsec IKEv2 gives me significant higher max bandwidth (130-150Mbps) than Wireguard (90 Mbps), this is 45%-65% faster. This is a bit surprising because ChaCha20Poly1305 is supposed to be pretty fast, as shown in the static openssl benchmarks above. Perhaps openssl somehow does not use hardware acceleration, and the ipsec VPN does. Also, there seems to be little correlation between the IPsec algorithms chosen and the achieved speed (e.g. AES128 vs AES256, or SHA1 vs SHA256), despire the performance delta shown by openssl benchmarking. Again, it could be that openssl does not utilize the hardware acceleration and ipsec does. Finally, I don’t see clear differences between the native EdgeOS performance and the custom strongswan configuration, which is nice to know in case you want to customize strongswan further than EdgeOS allows.

N.B.

Bandwidth comparison of VPN configurations, extrapolated from cpu load for 39 Mbps. IPsec gives better performance, especially for average cpu load (45%-65% faster).

Bandwidth comparison of VPN configurations, extrapolated from cpu load for 39 Mbps. Blue shows the extrapolation for average cpu load, green shows the extrapolation for maximum cpu load.

Bandwidth @ avg system idle [Mbps] Bandwith @ min. system idle [Mbps] Avg. system idle [%] Stddev [%] Min. system idle [%]
No VPN 96% 4% 78%
Wireguard1 88.7 73.0 52% 8% 43%
Wireguard2 90.1 52.5 53% 13% 22%
IPsec1 - native (AES128/SHA256/ECP256) 139.3 114.4 68% 5% 62%
IPsec2 - native (AES128/SHA256/ECP256) 137.5 78.6 68% 7% 46%
IPsec3 - custom (AES128/SHA1/ECP256) 134.8 77.2 67% 5% 46%
IPsec4 - custom (AES256/SHA256/MODP2048) 151.6 90.3 70% 6% 53%
IPsec5 - native (AES128/SHA1/MODP1024) 124.8 67.0 65% 9% 38%
IPsec6 - native (AES256/SHA256/ECP256) 118.1 88.2 63% 5% 52%
IPsec7 - native (AES256/SHA256/ECP256) 148.2 108.9 70% 6% 60%

Debugging / troubleshooting

EdgeOS firewall out of sync

Somewhere during updating firewall rules, I partially locked myself out. Trying to revert to last known working config, I got a funny error:

ubnt@edgerouterx:~$ configure
[edit]
ubnt@edgerouterx# delete zone-policy zone LAN interface wg0
[edit]
ubnt@edgerouterx# compare
[edit zone-policy zone LAN]
-interface wg0
[edit]
ubnt@edgerouterx# commit; save;
[ zone-policy zone LAN interface wg0 ]
iptables: No chain/target/match by that name.
Error: Error: call to delete rule for -o wg0
in zone chain VZONE_LOCAL_OUT with target FW_ACCEPT failed [256]

Commit failed

And another similar error:

ubnt@edgerouterx# compare
[edit firewall]
+name FW_ACCEPT2 {
+    default-action accept
+}
[edit zone-policy zone LAN from LOCAL firewall]
>name FW_ACCEPT2
[edit]
ubnt@edgerouterx# commit; save
[ zone-policy zone LAN from LOCAL firewall name FW_ACCEPT2 ]
iptables: No chain/target/match by that name.
Error: Error: call to delete rule for -o switch0.1
in zone chain VZONE_LOCAL_OUT with target FW_ACCEPT failed [256]

Commit failed

It turns out iptables and the VyOS/EdgeOS configuration can get out of sync (???). I could reboot and hope it solves itself, instead I’d rather do that when I’m physically near the router. However since I didn’t find another option (???), I took a leap of faith and rebooted the router remotely. Those were some 4 very scary minutes, but of course it all worked and the firewall ‘repaired’ itself.

To debug stuff, use e.g. the following commands

set vpn ipsec logging log-level 2
sudo swanctl --log
show vpn log
show vpn ipsec status 
show vpn ipsec policy

These settings modify the firewall somewhere (?) but can be overruled by your own config. Propose not to touch/use these settings.

# Trying to disable and increase debug to see what's happening
delete vpn ipsec allow-access-to-local-interface disable
delete vpn ipsec auto-firewall-nat-exclude disable

DNS-based adblock

Setup to get DNS-based adblocking. Based on:

Always block using NXDOMAIN, intended (quad9.net) for invalid domains (pi-hole.net) (mileage may vary (github.com)), for dnsmasq this works as follows, which blocks google.com and all subdomains

address=/google.com/

To white-list a domain, use

server=/mail.google.com/#

I compiled my list as follows

curl https://dnsmasq.oisd.nl/basic/ -o oisd.dnsmasq.txt
curl 'https://pgl.yoyo.org/adservers/serverlist.php?hostformat=dnsmasq&showintro=0&mimetype=plaintext' -o yoyo.dnsmasq.txt
curl 'https://raw.githubusercontent.com/r-a-y/mobile-hosts/master/AdguardDNS.txt' -o adguarddns.hosts.txt

cat oisd.dnsmasq.txt | sed 's/\/\#$/\//' > adblock.dnsmasq.txt
cat yoyo.dnsmasq.txt | sed 's/\/127\.0\.0\.1$/\//' >> adblock.dnsmasq.txt
cat adguarddns.hosts.txt | grep 0.0.0.0 | awk '{print "address=/"$2"/"}' >> adblock.dnsmasq.txt

sort adblock.dnsmasq.txt | uniq > adblock-sorted.dnsmasq.txt

scp adblock-sorted.dnsmasq.txt edgerouterx:
ssh edgerouterx
sudo mv adblock-sorted.dnsmasq.txt /etc/dnsmasq.d/

/usr/sbin/dnsmasq --test
sudo /etc/init.d/dnsmasq restart
less /var/log/dnsmasq.log

dnsmasq claims to be quite fast:

It is possible to use dnsmasq to block Web advertising by using a list of known banner-ad servers, all resolving to 127.0.0.1 or 0.0.0.0, in /etc/hosts or an additional hosts file. The list can be very long, dnsmasq has been tested successfully with one million names. That size file needs a 1GHz processor and about 60Mb of RAM.

Without block list, I get query times of 7-25 msec for new domains, and 2-4 msec for cached domains (using drill aa.com | grep "Query time"). After adding 60k blocked domains, I get 80-110 msec for new domains and 2-4msec for cached domains. Performance doens’t seem to matter whether I use NXDOMAIN or 127.0.0.1 or 0.0.0.0 blocking. Optimization is to increase cached domains (150 to max of 10000) and keep list to ~10k domains. This leads to ~30msec delay for new domains, and 2msec for cached domains (repeated query).

Testing

Migration from 20220804 20:00 to 21:15

  1. VLAN 1 can access VLAN 1 (proteus, switch)? – ~OK, no switch?
  2. VLAN 1 can access WAN? – OK
  3. VLAN 10 can access WAN? – OK
  4. VLAN 10 cannot access proteus? – OK
  5. VLAN 20 cannot access WAN? – OK
  6. VLAN 20 can access proteus? – OK
  7. Hairpin NAT works (can access proteus via home.vanwerkhoven.org)? – OK
  8. Adblock works? – OK
  9. VPN works (can access proteus VPN from WAN)? – OK

Debugging

#Networking #Security #Server #Smarthome #Unix