Gentoo as a router
Choosing Gentoo Linux over pfSense for router functionality, this detailed guide covers installation, setup, and configuration, including partitioning, chrooting, Portage settings, kernel updates, inittab modifications, networking with udev rules, bridge creation, hostapd setup, and advanced nftables and Wireguard integration for secure, efficient routing.
After trying out pfSense, OPNsense and VyOS and not being entirely happy about either of them I’ve decided to install my good old favorite GNU/Linux distribution Gentoo on my router.
The router is a PCEngine APU2C2 bought from TekLager. I’ll try to format this more as a reference than a complete guide.
1. Installation media
wget https://sourceforge.net/projects/systemrescuecd/files/latest/download
isohybrid systemrescuecd-x86-*iso
dd if=systemrescuecd-x86-*iso of=/dev/sda
2. Booting
Insert the SD card and start the router. To get the serial port terminal working correctly you may pick the option:
Then select Standard 64bit kernel (rescue64) with more choice and SystemRescueCD with a console in 800x600 and press TAB to edit the options. From the prompt the option video=800x600 needs to be removed. And the following options need to be added:
console=ttyS0,115200 text
It should now say:
linux rescue64 initrd=initram.igz console=ttyS0,115200 text
Press Ctrl-X to boot.
3. Partitioning
/dev/sda gets partitioned into two pieces: 1 for /boot and 1 for rootfs. No UEFI partition needed because MBR will be used.
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000
Device Boot Start End Sectors Size Id Type
/dev/sda1 * 6144 415743 409600 200M 83 Linux
/dev/sda2 415744 31277055 30861312 14.7G 83 Linux
Set up the file systems:
mkfs.vfat /dev/sda1
mkfs.ext4 /dev/sda2
And mount them:
mkdir /mnt/{boot,rootfs}
mount /dev/sda2 /mnt/rootfs
mkdir /mnt/rootfs/boot
mount /dev/sda1 /mnt/rootfs/boot
4. Chroot
Grab the stage 3 tarball from http://distfiles.gentoo.org/releases/amd64/autobuilds/current-stage3-amd64/ and extract it. I use the nomultilib versions because no 32 bit applications are going to be used.
wget http://distfiles.gentoo.org/releases/amd64/autobuilds/current-stage3-amd64/stage3-amd64-nomultilib-20180830T214502Z.tar.xz
tar xpf stage3-amd64-nomultilib-20180830T214502Z.tar.xz
rm -f stage3-amd64-nomultilib-20180830T214502Z.tar.xz
mount -o bind /dev /mnt/rootfs/dev
mount -t proc none /mnt/rootfs/proc
mount -o bind /sys /mnt/rootfs/sys
cp /etc/resolv.conf /mnt/rootfs/etc
chroot /mnt/rootfs /bin/bash
5. Portage
My make.conf looks like this:
CFLAGS="-O2 -pipe -march=native"
CHOST="x86_64-pc-linux-gnu"
PORTDIR="/usr/portage"
DISTDIR="/usr/portage/distfiles"
CPU_FLAGS_X86="aes avx f16c mmx mmxext pclmul popcnt sse sse2 sse3 sse4_1 sse4_2 sse4a ssse3"
USE="-dri -fortran -ipv6 -multilib"
ACCEPT_KEYWORDS="~amd64"
GRUB_PLATFORMS="efi-64"
FEATURES="noinfo nodoc noman"
6. Kernel
emerge gentoo-sources
The kernel configuration for 4.18.5-gentoo can be found here in case anyone wants to use it.
7. inittab
A serial console needs to be spawned during boot. Under the SERIAL CONSOLES section there should be a line saying:
# SERIAL CONSOLES
s0:12345:respawn:/sbin/agetty -L 115200 ttyS0 vt100
A small optimization can be made in inittab by not spawning any local TTYs. Remove or comment the lines in the TERMINALS section so it looks like this:
# TERMINALS
#x1:12345:respawn:/sbin/agetty 38400 console linux
#c1:12345:respawn:/sbin/agetty 38400 tty1 linux
#c2:2345:respawn:/sbin/agetty 38400 tty2 linux
#c3:2345:respawn:/sbin/agetty 38400 tty3 linux
#c4:2345:respawn:/sbin/agetty 38400 tty4 linux
#c5:2345:respawn:/sbin/agetty 38400 tty5 linux
#c6:2345:respawn:/sbin/agetty 38400 tty6 linux
8. Networking
Udev rules
Create udev rules to name the interfaces properly. It will be much easier to deal with them this way.
cat /etc/udev/rules.d/70-persistent-net.rules
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="redacted", NAME="wan0"
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="redacted", NAME="lan0"
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="redacted", NAME="lan1"
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="redacted", NAME="wlan0
Create a bridge
This will let the router acts as a switch on interfaces lan0 and lan1.
emerge net-misc/bridge-utils
cat /etc/conf.d/net
config_wan0="dhcp"
bridge_br0="lan0 lan1"
config_br0="192.168.0.1 netmask 255.255.255.0"
routes_br0="default via 192.168.0.1"
bridge_forward_delay_br0=0
bridge_hello_time_br0=1000
hostapd
As the name suggests hostapd sets up an access point.
emerge net-wireless/hostapd
For now I only use the 2.4GHz band.
cat /etc/hostapd/hostapd.conf
interface=wlan0
bridge=br0
logger_syslog=-1
logger_syslog_level=0
logger_stdout=-1
logger_stdout_level=2
ctrl_interface=/var/run/hostapd
ctrl_interface_group=0
ssid=redacted
country_code=redacted
ieee80211d=1
ieee80211h=1
ieee80211n=1
ieee80211ac=1
hw_mode=g
channel=10
auth_algs=1
wpa=2
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
wpa_passphrase=redacted
driver=nl80211
nftables + Wireguard
Added 2019-03-07.
The following nftables config will set up will route all outgoing traffic through
with the exception of traffic marked by wg-quick (see the init scripts provided),
Make sure to set vpn_port, fileserver_ip and port_to_forward which I have
redacted.
# http://kangran.su/~nnz/pub/nf-doc/nftables/nft.html
# http://wiki.nftables.org/wiki-nftables/index.php/Main_Page
define external = wan0
define internal = br0
define vpn_out = wg0
define vpn_in = wg1
define vpn_port = <redacted>
define fileserver_ip = <redacted>
define port_to_forward = <redacted>
flush ruleset
table firewall {
set blacklist {
type ipv4_addr
}
set tcp_open_ports {
type inet_service
}
set udp_open_ports {
type inet_service
elements = {
$vpn_port
}
}
chain outgoing-vpn {
udp dport $vpn_port counter accept
}
chain incoming {
type filter hook input priority 0
# established/related connections
ct state established,related accept
# invalid connections
ct state invalid drop
# bad tcp -> avoid network scanning:
tcp flags & (fin|syn) == (fin|syn) drop
tcp flags & (syn|rst) == (syn|rst) drop
tcp flags & (fin|syn|rst|psh|ack|urg) < (fin) drop
tcp flags & (fin|syn|rst|psh|ack|urg) == (fin|psh|urg) drop
# no ping floods:
ip protocol icmp limit rate 10/second accept
ip protocol icmp drop
# drop connections from blacklisted addresses
ip saddr @blacklist drop
# accept input from loopback and internal interfaces
iif { lo, $internal, $vpn_in } accept
udp sport bootps udp dport bootpc counter accept
# allow open tcp port
tcp dport @tcp_open_ports accept
# allow open udp ports
udp dport @udp_open_ports accept
reject
}
chain forwarding {
type filter hook forward priority 0
iif $external oif $internal ct state established,related accept
iif $internal oif $external accept
iif $internal oif $vpn_in accept
}
chain outgoing {
type filter hook output priority 0
}
}
table nat {
map tcp_forwarding {
type inet_service : ipv4_addr
}
map udp_forwarding {
type inet_service : ipv4_addr
}
chain prerouting {
type nat hook prerouting priority -100;
iifname wg0 tcp dport $port_to_forward dnat $fileserver_ip
}
chain postrouting {
type nat hook postrouting priority 100;
policy accept;
oifname { $external, $vpn_out, $vpn_in } masquerade
}
}
Here are the Wireguard init scripts for reference. The two most important commands in the scripts are:
- Make sure the
fwmarkmatches on both interfaces in order to mark the traffic fornftables. - The MTU need to match
on both interfaces or packets might be dropped when routing from
wg1(the “inbound” interface) towg0(the “outbound” interface).
Here’s /etc/init.d/wg0:
#!/sbin/openrc-run
# Copyright 1999-2018 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
name="Wireguard"
description="Starts a given wireguard tunnel."
command=/usr/bin/wg-quick
command_args="${wireguard_args}"
interface=wg0
depend() {
after net
}
start() {
ebegin "Starting Wireguard client"
$command up $interface
eend $?
}
stop() {
ebegin "Stopping Wireguard client"
$command down $interface
eend $?
}
And /etc/init.d/wg1(which is basically the same but adds the MTU and fwmark settings):
#!/sbin/openrc-run
# Copyright 1999-2018 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
name="Wireguard"
description="Starts a given wireguard tunnel."
command=/usr/bin/wg-quick
command_args="${wireguard_args}"
wg_command=/usr/bin/wg
interface=wg1
depend() {
after net
}
start() {
ebegin "Starting Wireguard server"
$command up $interface
eend $?
ebegin "Marking outgoing traffic"
$wg_command set $interface fwmark <redacted>
eend $?
ebegin "Matching mtu value to wg0 interface"
ifconfig wg1 mtu 1420
eend $?
}
stop() {
ebegin "Stopping Wireguard server"
$command down $interface
eend $?
}
Stuff I left out
- Tons of important instructions.