What changes when you turn a Linux box into a router

An in-depth look at the technical transformations required to turn a standard Linux system into a functional network router, focusing on kernel hooks and packet processing.
This was written on March 1, 2026
What does it mean to turn a Linux system into networking infrastructure?
I think it is incredibly cool that we can change a Linux system into a networking device. But have you ever wondered:
What are we changing when we turn a Linux system into a router or switch? What are we changing if we make a raspberry pi into a WiFi access point? How significant is the system performance monitoring change? What are the gates we have to change to enable packet forwarding and processing?
I’m going to start out with a narrative explanation of the changes that turn a Linux system into a WiFi access point and then I’ll show the commands for implementing it.
I have a cognitive bias: I think of networking devices and computers as different things. This is because the command line experience on networking gear is different than what you experience on servers/hosts. On servers and workstations: you tend to focus a lot on objects on the file system. On networking gear, you’re spending most of your time working with running processes directly. Commands and interaction objectives on networking gear is very different than those on hosts.
I suspect a lot of other people who have worked in networking have similar feelings about networking appliances versus host operating systems. This might be specific to my journey. But for better or worse, I felt that networking was different than general computing. It isn’t. If you know networking, you can make Linux do networking things if you make 7 changes.
- Activating IP Forwarding
- Defining The Bridge
- Activating nftables policies
- Stateful Firewalling with conntrack
- Defining NAT and Masquerade policies
- Vending DHCP and DNS with dnsmasq
- Vending WiFi networks with hostapd
To activate packet processing and forwarding in the Linux Kernel, you start by changing the Kernel’s configuration for networking. Every Android device that vends a personal WiFi hotspot makes the same general changes.
A packet’s journey through the kernel
Let’s assume we have a Linux machine with a single network interfaces. A packet arrives on the externally facing interface. The Network Interface Card (NIC) signals an interrupt and the driver pulls the frame into a ring buffer in kernel memory via Direct Memory Access (DMA), where the hardware writes data into RAM without Central Processing Unit (CPU) involvement. The kernel’s networking stack picks the frame up from there, strips the Ethernet header, and examines the Internet Protocol (IP) destination address.
At that point the kernel consults its routing table. If the destination address matches one of the machine’s own interfaces, the packet travels up through the network stack to a listening socket, to a process waiting to handle it. If the destination address matches no local interface and IP forwarding is disabled, the kernel drops the packet and increments a counter in /proc/net/snmp.
The default behavior of Linux is the end of the line for a packet: the kernel cannot forward the packet to another host. We need to make changes to the system if we want to enable routing. We also need another nic to send across network interfaces. A workstation is a host, not a router.
Now imagine that same system with two NICs (aka dual-homed)- how do we get closer to routing packets?
A router’s role is to forward the packets our single-homed host drops by default. Let’s explore each of the steps that move the kernel from a workstation’s conservative posture as a host into a router that routes packets, modifies packet headers, and filters traffic between interfaces.
What is a hook?
In the Linux kernel, a hook is a designated interception point in a code path where external functions can register themselves to execute. Think of it as a slot in an assembly line: the main process pauses at predefined points and runs every function that has registered at that slot, in priority order. Each registered function can inspect, modify, accept, or drop the item passing through. Hooks let the kernel separate its core packet-processing logic from policy decisions like filtering and address translation. The kernel defines where the hooks are; administrators and tools like nftables decide what code runs at each one. The kernel implements hooks as arrays of function pointers stored in structures like struct nf_hook_entries. At each hook point, the kernel iterates the array via nf_hook_slow(), passing each registered callback a pointer to the packet’s sk_buff structure.
Earlier, I made reference to “The kernel’s networking stack.” Just what does that mean?
A packet arrives at the NIC. The driver places it in memory and the kernel’s networking stack processes it through several ordered stages. At defined points along this path, the kernel passes the packet through netfilter, a hook-based framework built directly into the kernel’s networking code.
Netfilter hooks are function pointer arrays registered inside the kernel’s packet processing path. At each hook point, the kernel iterates through every registered function in priority order, passing a pointer to the packet’s socket buffer (sk_buff). Each registered function can accept, drop, modify, or queue the packet. Userspace tools like nftables register callback functions at these hooks by sending commands through a netlink socket, a kernel-userspace Inter-Process Communication (IPC) channel designed for networking configuration.
You can observe netfilter’s activity at runtime. nft list ruleset shows all currently registered tables and chains. conntrack -L shows the live connection tracking table. For deeper inspection, perf trace or bpftrace can attach probes to kernel functions like nf_hook_slow (the function the kernel calls when it iterates hook callbacks), letting you watch individual packet decisions in real time.
The five standard hook points are:
| Hook | Position in the packet path | |---|---| PREROUTING | Immediately on arrival, before any routing decision | INPUT | For packets destined for a local process | FORWARD | For packets passing through the machine to another host | OUTPUT | For packets generated by local processes | POSTROUTING | Just before a packet leaves an interface |
After PREROUTING, the kernel makes its routing decision. Packets addressed to the machine itself travel up through INPUT. Packets addressed to other hosts, when forwarding is enabled, move to FORWARD and then out through POSTROUTING. Every configuration step either registers code on one of these hooks or changes how the routing decision behaves.
Change 1: Activating IP Forwarding
IP forwarding is the first gate for enabling transport of packets across interfaces. Without it, the FORWARD hook might exist, but the kernel never sends packets to it. Packets arriving for foreign destinations die after the routing lookup. With it open, the kernel hands those packets to FORWARD, and every other piece of the router configuration takes effect.
You manage ip forwarding through the /etc/sysctl.d/10-forward.conf file:
/etc/sysctl.d/10-forward.conf
net.ipv4.ip_forward=1
/etc/sysctl.d/ is a drop-in configuration directory for kernel runtime parameters. At boot, systemd-sysctl.service reads every *.conf file in that directory (plus /etc/sysctl.conf) and writes each parameter to its corresponding path under /proc/sys/.
The kernel exposes a virtual filesystem at /proc/sys/ where every tuneable parameter appears as a file. The dotted sysctl notation is just a path translation: net.ipv4.ip_forward maps to /proc/sys/net/ipv4/ip_forward. Writing 1 to this file tells the IPv4 stack to send packets with non-local destinations through the FORWARD hook rather than discarding them. The kernel implements this decision in ip_forward() in net/ipv4/ip_forward.c.
Writing 1 to sysctl.d/10-forward.conf makes those writes persistent across reboots.
systemd-sysctl.service re
Source: Hacker News












