(D240722) Build It Yourself: Alpine Linux Router with PPP DHCP DNS NAT Wi-Fi 4G LTE 5G!
Alpine Linux Router
This guide demonstrates how to set up a simple NAT-box or you may call it a router.
After hours searching on internet, we think there are some articles you may interest:
- OpenBSD PF - Building a Router
- OpenBSD Router Guide
- Gentoo Home router
- Firewalling with OpenBSD's PF packet filter
- Setting up an Internet gateway with NPF
- NetBSD Security Processes and Services
Why Linux instead of FreeBSD / OpenBSD?
It will be easier to do the job with *BSD but hardware support on Linux is better. The performance was not good on FreeBSD Raspberry Pi 4 last time so.
If you have a hardware that works on *BSD you should go for it, their network stack is pretty rock solid and performant.
PF syntax make more sense to me than nftables, we would stick with FreeBSD if possible.
PF (Packet Filter) and Unbound then you good to go.
Oh good, what next?
A router that performs the following duties:
- Connect to ISP via DHCP / PPP (PPPoE, cellular network 4G/5G)
- Network Address Translation (NAT)
- Handing out IP addresses to clients via DHCP
- Doing DNS caching for the LAN
- Providing wireless connectivity (Wi-Fi)
We will build it from scratch on FriendlyElec NanoPi R2S.
For compatibility you may want x86 instead of ARM, every AMD64 computer with at least 2 NICs should works.
Category | Program | License | Note |
---|---|---|---|
OS | Alpine Linux | VAR | |
WAN | ppp (Paul's PPP Package) | BSD | PPPoE, cellular network 4G LTE / 5G |
DHCP | Kea DHCP | MPL | |
DNS | Unbound | BSD | |
ROUTE | nftables | GPL |
1. Install OS
For general information you can read on Alpine Linux Wiki.
This is how we did:
- Download Generic ARM image
- Burn the image to SD Card
- Boot it from SD Card then install
2. Config
We install some packages from Alpine store:
Then config them as follow:
1. Interfaces
rabbit:~% cat /etc/network/interfaces
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
auto eth1
iface eth1 inet static
address 192.168.200.1
netmask 255.255.255.0
broadcast 192.168.200.255
This Alpine box is under our main router which is running OPNsense.
The box get its dynamic IP from the main router through eth0
. You should change it depend on how you get connection from: dhcp, static, ppp..
The box set its static IP on eth1
, this address 192.168.200.1
will be the gateway of any devices connect into it (eth1
interface).
For more information, you can read here.
2. DHCP
rabbit:~% cat /etc/kea/kea-dhcp4.conf
# https://kea.readthedocs.io/en/kea-2.6.0/arm/config.html
{
# DHCPv4 specific configuration.
"Dhcp4": {
"interfaces-config": {
"interfaces": [ "eth1" ],
"dhcp-socket-type": "raw"
},
"valid-lifetime": 4000,
"renew-timer": 1000,
"rebind-timer": 2000,
"subnet4": [{
"pools": [ { "pool": "192.168.200.20-192.168.200.200" } ],
"subnet": "192.168.200.0/24",
"id": 1
}],
# Now loggers are inside the DHCPv4 object.
"loggers": [{
"name": "*",
"severity": "DEBUG"
}],
# Routing and DNS
"option-data": [{
"name": "routers",
"data": "192.168.200.1"
},
{
"name": "domain-name-servers",
"data": "192.168.200.1",
"always-send": true
}]
}
}
Kea Configuration Docs is here, you may want to read it.
Don't forget to set routers
(routing) and domain-name-servers
(DNS), they are important.
3. DNS
rabbit:~% cat /etc/unbound/unbound.conf
server:
interface: 192.168.200.1
access-control: 192.168.200.0/24 allow
forward-zone:
name: "."
forward-addr: 8.8.8.8
forward-addr: 8.8.4.4
Since we simply want to get DNS from Google, even when Unbound can do better.
3. ip_forward
rabbit:~% cat /etc/sysctl.d/local.conf
# Forward Packets between interfaces
# https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
net.ipv4.ip_forward = 1
net.ipv4.ip_dynaddr = 1
Notes:
net.ipv4.ip_forward
forwarding between interfaces (eth0
WAN ð1
LAN)net.ipv4.ip_dynaddr
for dialup interface with changing IP addresses
4. nftables
rabbit:~% cat /etc/nftables.nft
# Clear all prior state
flush ruleset
# Include modular config files
include "/etc/nftables.d/*.nft"
rabbit:~% cat /etc/nftables.d/rules.nft
define DEV_PRIVATE = eth1
define DEV_WORLD = eth0
define NET_PRIVATE = 192.168.200.0/24
table ip global {
chain inbound_world {
# accepting ping (icmp-echo-request) for diagnostic purposes.
# However, it also lets probes discover this host is alive.
# This sample accepts them within a certain rate limit:
#
icmp type echo-request limit rate 5/second accept
# allow SSH connections from some well-known internet host
ip saddr 192.168.100.100 tcp dport ssh accept
}
chain inbound_private {
# accepting ping (icmp-echo-request) for diagnostic purposes.
icmp type echo-request limit rate 5/second accept
# allow DHCP, DNS and SSH from the private network
ip protocol . th dport vmap { tcp . 22 : accept, udp . 53 : accept, tcp . 53 : accept, udp . 67 : accept}
}
chain inbound {
type filter hook input priority 0; policy drop;
# Allow traffic from established and related packets, drop invalid
ct state vmap { established : accept, related : accept, invalid : drop }
# allow loopback traffic, anything else jump to chain for further evaluation
iifname vmap { lo : accept, $DEV_WORLD : jump inbound_world, $DEV_PRIVATE : jump inbound_private }
# the rest is dropped by the above policy
}
chain forward {
type filter hook forward priority 0; policy drop;
# Allow traffic from established and related packets, drop invalid
ct state vmap { established : accept, related : accept, invalid : drop }
# connections from the internal net to the internet or to other
# internal nets are allowed
iifname $DEV_PRIVATE accept
# the rest is dropped by the above policy
}
chain postrouting {
type nat hook postrouting priority 100; policy accept;
# masquerade private IP addresses
ip saddr $NET_PRIVATE oifname $DEV_WORLD masquerade
}
}
Honestly we don't familiar with nftables, we copied it from Simple ruleset for a home router. This is a demo so we didn't put our time on it.
We just wanted to SNAT from LAN to WAN then we can connect to the world.
If you're in serious, you'll need to read their wiki carefully!
How all of them works together?
You may made your DIY router and you wonder why it work?
For example:
- You connect your laptop to Alpine box, your laptop will get from DHCP (kea):
- An IP in subnet pool
192.168.200.20
-192.168.200.200
- DNS address:
192.168.200.1
(unbound) - Gateway address:
192.168.200.1
(iptables)
- An IP in subnet pool
- You made a request to website https://example.com on your laptop:
- The laptop will ask DNS address: what is the IP of domain example.com
- Send packets to Gateway address with source address = laptop IP, destination address = example IP
- The
nftables
catch those packets, thenmasquerade
them: new source address = alpine box WAN address - The Alpine box will send those packets to its Gateway (main router OPNsense) and so on...
Maybe we should
SNAT
and changeeth0
WAN to static IP in this situation, it will be faster thanmasquerade
.
This is how it works in general.
nftables
will handle rules, it is like a door. You write the rules, you make the firewall.
And there are techniques those use DNS
like unbound
, bind
or dnsmasq
to block unwanted data, you will found them soon!
Other information
Packet filter: PF, netfilter, NPF, IPFW, IPF
Packet flow:
Conclusion
We wrote this guide because we wanted to know how to build a router, it may useful when you want to share your internet (4G, 5G) from your custom hardware.
For convenience we continue run OPNsense and you should too.