Article summary
I’ve been playing around with WireGuard a bit lately, and one of the things that keeps coming up is the iptables tool. Over the years, I’ve encountered iptables here and there, but it’s always been a bit of a black box to me. I’d be trying to accomplish something that would need it, and while I’d understand conceptually what I was trying to do, I never fully got my head wrapped around the commands I’d find in some instructions or documentation I’d need to run.
But no more! I’ve been reading up on iptables. In this post, I’m going to walk through a couple of different iptables scenarios I’ve encountered recently and see if I understand it enough to explain them.
WireGuard as a Gateway
I came across this use case in Jordan Nelson’s blog post Building a Personal Private Network with WireGuard. When using WireGuard as a VPN to your home network, like Jordan was writing about, traffic from your external WireGuard clients will need to be forwarded from the WireGuard server machine to other internal addresses. That’s not something that WireGuard does for you. However, you can configure iptables to do this.
The “PostUp” section of the configuration from Jordan’s post contains the iptables commands that will be run whenever his WireGuard tunnel is brought up:
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A
POSTROUTING -o wlan0 -j MASQUERADE
That’s actually three separate iptables commands, with the %i getting replaced with the WireGuard interface (often wg0) before execution.
iptables -A FORWARD -i wg0 -j ACCEPT
This says to allow traffic coming in from WireGuard (wg0) to be forwarded. In other words, any packet that enters the machine through wg0 may be routed onward through another interface. It appends a rule to the “filter” table’s FORWARD chain. iptables operates on the “filter” table by default, so it doesn’t need to be specified (but it could be if you wanted to: iptables -t filter ...). The -j ACCEPT tells it to jump to the ACCEPT target.
NOTE:
For forwarding to work at all, IP forwarding has to be enabled in Linux. The way to do that can vary by distribution, but it will have something to do with setting net.ipv4.ip_forward to 1.
iptables -A FORWARD -o wg0 -j ACCEPT
This says to allow traffic being forwarded back out to the WireGuard interface (wg0). This is what allows replies from internal hosts to go back out through WireGuard. Again operating on the “filter” table’s FORWARD chain.
iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
And finally, this says to append a rule that makes it look like anything being routed out through the wlan0 interface is coming from this WireGuard server. This is important because without this MASQUERADE, a host on your internal network would see the traffic as having originated from the WireGuard client’s tunnel IP address, and it almost certainly wouldn’t know how to route a reply. But with the MASQUERADE, the source is changed to be the IP address of the wlan0 (in this case) interface on the WireGuard server machine. The internal hosts know how to route back to it.
This command is modifying the POSTROUTING chain in the “nat” table. The POSTROUTING chain is for altering packets as they are about to go out.
Note that the LAN facing interface in the post, wlan0, might be different in any given WireGuard installation. For Jordan it was wlan0, but that needs to be the interface the server uses to communicate to your internal network. You can inspect ip route to see which interface Linux uses to reach your internal network.
> ip route
default via 192.168.1.1 dev eth0
192.168.1.0/24 dev eth0 proto kernel ...
With that configuration you’d use -o eth0 in the POSTROUTING command instead of -o wlan0.
But you don’t have to forward the traffic through the default interface. Let’s say you want to forward traffic coming in from WireGuard through an interface set up by another VPN client, you’d just specify that interface instead.
Stricter Forwarding Rules
Here’s a set of very similar commands I’ve come across, but these tighten up the very loose rules from the first set of commands:
iptables -I FORWARD 1 \
-i wg0 -o vpn0 \
-s 192.168.99.50/32 \
-j ACCEPT
iptables -I FORWARD 2 \
-i vpn0 -o wg0 \
-d 192.168.99.50/32 \
-m conntrack --ctstate ESTABLISHED,RELATED \
-j ACCEPT
iptables -t nat -A POSTROUTING \
-s 192.168.99.50/32 \
-o vpn0 \
-j MASQUERADE
These are more or less accomplishing the same thing as the first set of commands. Allow traffic from the WireGuard interface to be forwarded, allow traffic to be forwarded back to the WireGuard interface, and make it look like the traffic originated on this machine.
Differences
But there are a few differences to look at. The first command now explicitly says to insert the rule to the FORWARD chain as rule #1 (FORWARD 1). And the second is inserted as rule #2 (FORWARD 2). This is needed if you need to make sure these rules have to come before some other rules that might exist on the machine you’re running the WireGuard server on (other VPN software, perhaps).
The first command now explicitly specifies an outbound interface and source IP address. So instead of allowing any traffic that comes in from the wg0 interface, like the initial commands from earlier, this one says to only allow traffic that’s coming in on wg0, and has been routed to the vpn0 interface. And additionally, only allow it if it’s coming from a specific IP address (192.168.99.50/32). It should be noted that iptables isn’t doing the routing – the appropriate routes must already exist. It’s just allowing/filtering/blocking traffic based on these rules.
With a WireGuard setup, that IP address (192.168.99.50/32) should be the Address of a particular client. When traffic arrives from that specific client it will be on wg0 with a source IP address of 192.168.99.50. If the packet is going to be routed to vpn0 then the first rule will allow it to be forwarded.
The second command also tightens up the rule, specifying that it will only allow return traffic from vpn0 to wg0 that’s destined for 192.168.99.50, and part of an already established connection (-m conntrack --ctstate ESTABLISHED,RELATED). This rule only allows established or related traffic in that direction, rather than allowing hosts inside the VPN to initiate new connections to the WireGuard client. As long as no other FORWARD rule allows that new traffic, those connections will be blocked.
Custom Chains
Here’s another slightly different version:
iptables -A WIREGUARD-FORWARD \
-i wg0 -o eth0 -j ACCEPT
This is appending a rule to a WIREGUARD-FORWARD instead of the built-in FORWARD chain. You can create a custom chain with:
# Create the chain
iptables -N WIREGUARD-FORWARD
# Jump from the built-in FORWARD chain to our custom chain
iptables -A FORWARD -j WIREGUARD-FORWARD
This is basically for organizational purposes, as it allows you to see all of your rules together, as well as to easily flush (remove) them:
# Remove the jump into the custom chain
iptables -D FORWARD -j WIREGUARD-FORWARD
# Remove all rules from it
iptables -F WIREGUARD-FORWARD
# Delete the chain itself
iptables -X WIREGUARD-FORWARD
Conclusion
There’s only about a million other things you can do with iptables – we’ve barely scratched the surface. In fact, while researching this post, I came across an old Spin post (Useful iptables Port Forwarding Patterns) about the tool. I’m definitely no expert, but I feel like I actually have a solid grasp on what’s happening when I’m setting up filter rules for a WireGuard server now.