Categories
Linux Networking raspberrypi

Using a Raspberry Pi 2 as a Router + Configuring Raspbian for IPv6 with Aussie Broadband

I recently decided to move away from using my Wifi access point as a router, and instead use an old my Raspberry Pi 2 as my router. I had a few reasons for doing this:

  • I wanted a more up-to-date device as my internet facing box. My Wifi AP hasn’t received any firmware updates in several years, so I don’t trust the security of it very highly.
  • I wanted to learn more about networking, particularly how to properly configure IPTables on Linux and how to see what traffic is flowing on my network.
  • I wanted to turn on the Aussie Broadband IPv6 beta, and get an internet-facing IPv6 network.

The Hardware

  • Raspberry Pi 2
  • Network Interface 1 – enxb827eb579e58 – Built-in Pi eth – internet facing
  • Network Interface 1 – enx3c18a0054c1e – Lenovo USB3 Ethernet adaptor (running at USB2 speed) – internal facing

This is sufficient for my network, which is limited to 50mb/s. However, if you have 100Mb/s or more you will hit the limit of what the built-in ethernet port on the Pi 2 can support. You may be able to go further with a Pi 4, which has gigabit ethernet and USB3.

The Software

  • Raspbian 10 Buster
  • NetworkManager – provides IPv4 networking
  • dhcpcd5 – provides IPv6 DHCPv6
  • fail2ban – brute force protection
  • iptables – firewalling and networking rules
  • iptables-persistent – load up my iptables rules on boot

Part 1 – Installation

To start, I just did a Raspbian Lite install, and then set up the Pi to provide SSH. I was then able to configure everything remotely.

Next, I wanted to install NetworkManager. I followed some instructions from here, but I didn’t remove dhcpcd5, as it is able to do DHCPv6 with Prefix-Delegation, something it seems isn’t possible with NetworkManager: https://raspberrypi.stackexchange.com/questions/29783/how-to-setup-network-manager-on-raspbian

sudo apt install network-manager
sudo apt purge openresolv 
sudo nano /etc/dhcpcd.conf
# Add to top of /etc/dhcpcd.conf
ipv6only

Network Manager configuration:

Make sure to edit the config files using the nmcli con edit command, eg nmcli con edit internet
Note in both configs the method=ignore line, as IPv6 is configured by dhcpcd5

root@routepi ###/e/N/system-connections> pwd
/etc/NetworkManager/system-connections
root@routepi ###/e/N/system-connections> cat internet.nmconnection
[connection]
id=internet
uuid=231de2fd-f890-49ba-baed-295fe30a5ee1
type=ethernet
interface-name=enxb827eb579e58
permissions=
timestamp=1565155463

[ethernet]
mac-address-blacklist=

[ipv4]
dns-search='lan';
method=auto

[ipv6]
addr-gen-mode=stable-privacy
dns-search=
method=ignore
root@routepi ###/e/N/system-connections> cat house.nmconnection
[connection]
id=house
uuid=4a683432-11e6-3c31-be8e-0603ca5fb6ce
type=ethernet
autoconnect-priority=-999
permissions=
timestamp=1563862423

[ethernet]
mac-address=3C:18:A0:05:4C:1E
mac-address-blacklist=

[ipv4]
address1=10.1.1.9/24
dns-search=
ignore-auto-dns=true
method=manual

[ipv6]
addr-gen-mode=stable-privacy
dns-search=
method=ignore

Dhcpcd5 configuration:

The main part of interest are the lines requesting an IA_NA and also an IA_PD from the internet facing interface, and assigning them to the internal interface. Aussie Broadband requires that you request both an IA_NA and an IA_PD, os this is the config to make it work:

allowinterfaces enxb827eb579e58
interface enxb827eb579e58
# Address from the /64
ia_na 1
# Request /56 and assign it to other interface
ia_pd 2 enx3c18a0054c1e/1

Whole file is here for reference:

# A sample configuration for dhcpcd.
# See dhcpcd.conf(5) for details.

# Allow users of this group to interact with dhcpcd via the control socket.
#controlgroup wheel
ipv6only
# Inform the DHCP server of our hostname for DDNS.
hostname

# Use the hardware address of the interface for the Client ID.
#clientid
# or
# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
# Some non-RFC compliant DHCP servers do not reply with this set.
# In this case, comment out duid and enable clientid above.
duid

# Persist interface configuration when dhcpcd exits.
persistent

# Rapid commit support.
# Safe to enable by default because it requires the equivalent option set
# on the server to actually work.
option rapid_commit

# A list of options to request from the DHCP server.
option domain_name_servers, domain_name, domain_search, host_name
#option classless_static_routes
# Respect the network MTU. This is applied to DHCP routes.
#option interface_mtu

# Most distributions have NTP support.
#option ntp_servers

# A ServerID is required by RFC2131.
require dhcp_server_identifier

# disable running any hooks; not typically required for simple DHCPv6-PD setup
script /bin/true

# Disable dhcpcd's own router solicitation support; allow slaacd
# to do this instead by setting "inet6 autoconf" in hostname.em0
noipv6rs

# Generate SLAAC address using the Hardware Address of the interface
#slaac hwaddr
# OR generate Stable Private IPv6 Addresses based from the DUID
#slaac private


allowinterfaces enxb827eb579e58
interface enxb827eb579e58
# Address from the /64
ia_na 1
# Request /56 and assign it to other interface
ia_pd 2 enx3c18a0054c1e/1


# Example static IP configuration:
#interface eth0
#static ip_address=192.168.0.10/24
#static ip6_address=fd51:42f8:caae:d92e::ff/64
#static routers=192.168.0.1
#static domain_name_servers=192.168.0.1 8.8.8.8 fd51:42f8:caae:d92e::1

# It is possible to fall back to a static IP if DHCP fails:
# define static profile
#profile static_eth0
#static ip_address=192.168.1.23/24
#static routers=192.168.1.1
#static domain_name_servers=192.168.1.1

# fallback to static profile on eth0
#interface eth0
#fallback static_eth0

SysCTL config to allow routing

Now we need to turn on some kernel options in the sysctl config file:

root@routepi ###~> cat /etc/sysctl.conf 
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.enxb827eb579e58.accept_ra=2

The ipv4 line sets the pi to forward IPv4 packets
The net.ipv6.conf.enxb827eb579e58.accept_ra=2 is important because without that, the routing table won’t be automatically updated with the routes provided by the Aussie Broadband Router Advertisments (RA’s). This config has three possible values:
0 = don't accept RA's
1 = accept RA's if not acting as a router
2 = accept RA's even if acting as a router
I found this info here: http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/

Fail2Ban Configuration

Important in any machine that is internet-facing is to have some sort of brute-force lockout system. I’m using fail2ban to help secure my SSH. However, because I run SSH on port 2, I need a little extra fail2ban config:

root@routepi ###~> cat /etc/fail2ban/jail.d/routepi.local 
[sshd]
port    = 2
ignoreip = 138.80.14.0/24, 10.1.0.0/16

IPTables configuration

Finally, to properly secure incoming traffic, I use IPTables. My iptables config is fairly lax, but provides the basics of preventing any incoming traffic without an established session from an internal device. I am using the iptables-persistent package to auto-load my iptables rules on boot. It can be installed with:

sudo apt install iptables-persistent
root@routepi ###/> cat /etc/iptables/rules.v4
# Jays IPTables on Pi
# enxb827eb579e58 - WAN interface
# enx3c18a0054c1e - LAN interface
# 10.1.1.13 - TVPi

*filter
:INPUT DROP
:FORWARD DROP
:OUTPUT ACCEPT
## INPUT rules
# ACCEPT related or established connections
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# icmp
-A INPUT -p icmp -j ACCEPT
# DHCP from ISP
-A INPUT -p udp --dport 68 -j ACCEPT
# DHCP for internal
-A INPUT -i enx3c18a0054c1e -p udp --dport 67 -j ACCEPT
# SSH
-A INPUT -p tcp --dport 2 -j ACCEPT
-A INPUT -p udp -m multiport --dports 60001:60099 -j ACCEPT
# DNS - only on internal
-A INPUT -i enx3c18a0054c1e -p udp --dport 53 -j ACCEPT
-A INPUT -i lo              -p udp --dport 53 -j ACCEPT
# We dont participate in mDNS, so drop it without logs
-A INPUT -p udp --dport 5353 -j DROP
# We dont participate in syncthing, so drop it without logs
-A INPUT -p tcp --dport 22000 -j DROP
-A INPUT -p udp --dport 22000 -j DROP
-A INPUT -p udp --dport 21027 -j DROP
# We dont participate in tvheadend, so drop it without logs
-A INPUT -p udp --dport 65001 -j DROP
# We dont participate in uuuggghhh NetBios, so drop it without logs
-A INPUT -p udp -m multiport --dports 137,138,139 -j DROP
-A INPUT -p tcp -m multiport --dports 137,138,139 -j DROP
# We dont participate in igmp, so drop it without logs
-A INPUT -p 2 -j DROP
# Log everything else before it's dropped - limit to 1/s
-A INPUT -i enxb827eb579e58 -m limit --limit 1/second --limit-burst 100 -j LOG --log-prefix :nf4_INPUT_ext_dropped:
-A INPUT -i enx3c18a0054c1e -m limit --limit 1/second --limit-burst 100 -j LOG --log-prefix :nf4_INPUT_int_dropped:
## End INPUT rules

# Forward all packets that are being DNAT'd
-A FORWARD -m conntrack --ctstate DNAT -j ACCEPT
# Forward all packets on the LAN side
-A FORWARD -i enx3c18a0054c1e -j ACCEPT
# Forward active connections on the WAN side
-A FORWARD -i enxb827eb579e58 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
COMMIT


*nat
:PREROUTING ACCEPT
:INPUT ACCEPT
:POSTROUTING ACCEPT
:OUTPUT ACCEPT

# HTTP(S) Forwarding
-A PREROUTING -m addrtype --dst-type LOCAL -p tcp --dport 80 -j DNAT --to-destination 10.1.1.13:80
-A PREROUTING -m addrtype --dst-type LOCAL -p tcp --dport 443 -j DNAT --to-destination 10.1.1.13:443
# UDP too for SPDY/HTTP3
-A PREROUTING -m addrtype --dst-type LOCAL -p udp --dport 443 -j DNAT --to-destination 10.1.1.13:443

# port 4 goes to TVPi SSH
-A PREROUTING -m addrtype --dst-type LOCAL -p tcp --dport 4 -j DNAT --to-destination 10.1.1.13:4
# MOSH ports
-A PREROUTING -m addrtype --dst-type LOCAL -p udp --dport 60200:60299 -j DNAT --to-destination 10.1.1.13:60200-60299
# port 3 goes to TVPi SSH
-A PREROUTING -m addrtype --dst-type LOCAL -p tcp --dport 3 -j DNAT --to-destination 10.1.1.11:22
# MOSH ports
-A PREROUTING -m addrtype --dst-type LOCAL -p udp --dport 60100:60199 -j DNAT --to-destination 10.1.1.11:60100-60199

# MASQ for packets that are being DNAT'd, so that they go back to the router
-A POSTROUTING -m conntrack --ctstate DNAT -j MASQUERADE

# MASQ (NAT) all packets that are accepted by the forwarding
-A POSTROUTING -o enxb827eb579e58 -j MASQUERADE
-A POSTROUTING -o enx3c18a0054c1e -m conntrack --ctstate RELATED,ESTABLISHED -j MASQUERADE
COMMIT
root@routepi ###/> cat /etc/iptables/rules.v6
# Jays ipv6 config
# enxb827eb579e58 - WAN interface
# enx3c18a0054c1e - LAN interface

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]

## INPUT rules
# Allow related or established traffic
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Allow NDP on all interfaces (it's link-local, so pretty safe)
-A INPUT -p icmpv6 --icmpv6-type router-solicitation      -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type router-advertisement     -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type neighbour-solicitation   -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type neighbour-advertisement  -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type redirect                 -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type 141                      -j ACCEPT -m comment --comment "inverse NDP" 
-A INPUT -p icmpv6 --icmpv6-type 142                      -j ACCEPT -m comment --comment "inverse NDP"
# Allow internal icmp
-A INPUT -p icmpv6 -i enx3c18a0054c1e -j ACCEPT
# Allow external/internal echo req/resp
-A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
-A INPUT -p icmpv6 --icmpv6-type echo-reply   -j ACCEPT
# Multicast Receiver Notification messages
-A INPUT -p icmpv6 --icmpv6-type 130 -j ACCEPT -m comment --comment "Listener Query" 
-A INPUT -p icmpv6 --icmpv6-type 131 -j ACCEPT -m comment --comment "Listener Report" 
-A INPUT -p icmpv6 --icmpv6-type 132 -j ACCEPT -m comment --comment "Listener Done" 
-A INPUT -p icmpv6 --icmpv6-type 143 -j ACCEPT -m comment --comment "Listener Report v2" 
# SEND Certificate Path Notification messages
-A INPUT -p icmpv6 --icmpv6-type 148 -j ACCEPT -m comment --comment "Certificate Path Solicitation" 
-A INPUT -p icmpv6 --icmpv6-type 149 -j ACCEPT -m comment --comment "Certificate Path Advertisement"
# Multicast Router Discovery messages
-A INPUT -p icmpv6 --icmpv6-type 151 -j ACCEPT -m comment --comment "Multicast Router Advertisement" 
-A INPUT -p icmpv6 --icmpv6-type 152 -j ACCEPT -m comment --comment "Multicast Router Solicitation" 
-A INPUT -p icmpv6 --icmpv6-type 153 -j ACCEPT -m comment --comment "Multicast Router Termination" 
# Drop fake loopback traffic 
-A INPUT -s ::1/128 ! -i lo -j DROP
# Allow incoming DHCPv6 from ISP
-A INPUT -p udp --dport 546 -j ACCEPT
# SSH
-A INPUT -p tcp --dport 2 -j ACCEPT
-A INPUT -p udp -m multiport --dports 60001:60099 -j ACCEPT
# DNS - only on internal
-A INPUT -i enx3c18a0054c1e -p udp --dport 53 -j ACCEPT
-A INPUT -i lo              -p udp --dport 53 -j ACCEPT
# We dont participate in mDNS, so drop it without logs
-A INPUT -p udp --dport 5353 -j DROP
# We dont participate in syncthing, so drop it without logs
-A INPUT -p tcp --dport 22000 -j DROP
-A INPUT -p udp --dport 22000 -j DROP
-A INPUT -p udp --dport 21027 -j DROP
# We dont participate in tvheadend, so drop it without logs
-A INPUT -p udp --dport 65001 -j DROP
# We dont participate in uuuggghhh NetBios, so drop it without logs
-A INPUT -p udp -m multiport --dports 137,138,139 -j DROP
-A INPUT -p tcp -m multiport --dports 137,138,139 -j DROP
# Log everything else before it's dropped - limit to 1/s
-A INPUT -i enxb827eb579e58 -m limit --limit 1/second --limit-burst 100 -j LOG --log-prefix :nf6_INPUT_ext_dropped:
-A INPUT -i enx3c18a0054c1e -m limit --limit 1/second --limit-burst 100 -j LOG --log-prefix :nf6_INPUT_int_dropped:
## End INPUT rules

# Allow internal traffic out and external traffic in if rel/est
# This should also cover all icmpv6 error messages
-A FORWARD -i enx3c18a0054c1e -j ACCEPT
-A FORWARD -i enxb827eb579e58 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Allow imcpv6 echo
-A FORWARD -p icmpv6 --icmpv6-type echo-request -j ACCEPT
-A FORWARD -p icmpv6 --icmpv6-type echo-reply   -j ACCEPT

# TVpi - 2403:0000:0000:0000::7
# SSH
-A FORWARD -d 2403:0000:0000:0000::7 -p tcp --dport 4 -j ACCEPT
-A FORWARD -d 2403:0000:0000:0000::7 -p udp --dport 60000:60099 -j ACCEPT
# HTTP/s
-A FORWARD -d 2403:0000:0000:0000::7 -p tcp --dport 80 -j ACCEPT
-A FORWARD -d 2403:0000:0000:0000::7 -p tcp --dport 443 -j ACCEPT
-A FORWARD -d 2403:0000:0000:0000::7 -p udp --dport 443 -j ACCEPT


COMMIT

Finally – Backing Up

There’s always a risk of SD card failure on a Raspberry Pi, so I make sure to backup all my configurations into a git repository in my home directory. Here is the script I use to do so:

root@routepi ###~/l/router_conf> cat backup_router_conf.fish
#!/usr/bin/env fish

rsync -a /etc/dnsmasq.d ./
rsync -a /etc/iptables ./
rsync -a /etc/NetworkManager ./
rsync -a /etc/sysctl.conf ./
rsync -a /etc/dhcpcd.conf ./
rsync -a /etc/fail2ban/jail.d ./fail2ban/

Any time I make a change, I will run the script to backup any changed config files, and then commit + push them to my git server.

That’s it! Feel free to leave a comment if you have a similar setup, or suggestions on how to do things better.

Leave a Reply

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