As a high-performance and easy-to-use L3 tunnel, WireGuard has many advantages that can’t be replaced. But even with such a simple protocol, it offers many complex possibilities. In this article, I will introduce various scenarios and requirements encountered during long-term usage, and share some usage tips based on them. And yea, some routing methods mentioned in the article are not only applicable to WireGuard, any similar complex scenarios may also be referred to.
Scenarios
Simplified Deployment
Let’s start from the beginning of deployment. If you like me, wanna minimize your mental burden as much as possible, rather than starting everything from ip link, then wg-quick is definitely a good choice. While WireGuard itself has included by Linux kernel, but wg-quick has not. Here, Debian is still used as an example:
apt update && apt install wireguardThen we can create our own configuration files under /etc/wireguard/. The configuration file specifications will not be discussed here, you can refer directly to the documentation. After preparing the configuration files, here we can use wg-quick systemd service teamplate. Here’s an example using wg0.conf:
systemctl enable wg-quick@wg0 --now
systemctl status wg-quick@wg0If there are no issues with the configuration file itself, you should see normal output from the service and the newly added wg0 virtual interface.
Route Priority
Next is the content inside the configuration file. Since wg-quick itself will help us configure routes according to the rules we provide by default, sometimes we don’t want wg-quick to do that actually, but rather want to control them ourselves. Let’s assume a relatively simple scenario here. For example, suppose we wanna add a WireGuard route with higher priority while retaining the original routes. This allows us to default to using the WireGuard tunnel but still have the option to use the original interface when needed:
[Interface]
...
Table = off
PostUp = ip r add default dev wg0 metric 128
PostUp = ip -6 r add default dev wg0 metric 128
PreDown = ip r del default dev wg0 metric 128
PreDown = ip -6 r del default dev wg0 metric 128
...Here, Table = off disables the routing feature of wg-quick. Then, using PostUp and PreDown, we execute two routing actions after bringing up and before shutting down wg0. We add wg0 as a route with metric 128 to the system’s routing table. Assuming our interface eth0 has a metric of 256, our default outbound route is now through the WireGuard tunnel. However, at the same time, we can also allow certain applications that we need to use the original eth0 interface, for example, by specifying the outbound interface using curl --interface eth0 ....
Custom Routes
Building on the previous discussion, a question arises. Not all applications can specify outbound interfaces, right? Perhaps you have already thought of it, we can use the same method here to configure some custom IP addresss using PostUp and PreDown. For example, if we need a certain internal network segment to use the original eth0 interface by default:
[Interface]
...
Table = off
PostUp = ip r add 192.0.2.0/24 via 192.0.2.1 dev eth0 metric 10
PostUp = ip r add default dev wg0 metric 128
PostUp = ip -6 r add default dev wg0 metric 128
PreDown = ip r del 192.0.2.0/24 via 192.0.2.1 dev eth0 metric 10
PreDown = ip r del default dev wg0 metric 128
PreDown = ip -6 r del default dev wg0 metric 128
...So that we can enable the 192.0.2.0/24 subnet to connect via the eth0 interface to the upstream gateway 192.0.2.1 by adding a lower metric route.
Advanced Routing
Let’s consider a more complex scenario. What if we have a very large collection of CIDRs organized in list? Obviously, we can’t directly write all the addresses. How should we handle this gracefully? Of course, we can accomplish this through loops:
cat /etc/network/ips/ipv4.txt | while read -r CIDR; do ip r add "$CIDR" via 192.0.2.1 dev eth1 metric 10; done
cat /etc/network/ips/ipv6.txt | while read -r CIDR; do ip -6 r add "$CIDR" via 2001:dab8::1 dev eth1 metric 10; doneIn this way, we configure each line of the IP list through a while loop. This certainly works well, but if the IP list to be processed is very large, it will significantly impact performance. Each initialization of the WireGuard tunnel will become very slow. Therefore, we use ip -batch to route all IP addresses at once:
sed 's/.*/route add & via 192.0.2.1 dev eth0 metric 10/' /etc/network/ips/ipv4.txt > /tmp/ipv4.routes
ip -batch - < /tmp/ipv4.routes
sed 's/.*/route add & via 2001:db8::1 dev eth1 metric 10/' /etc/network/ips/ipv6.txt > /tmp/ipv6.routes
ip -batch - < /tmp/ipv6.routesDuring PreDown, simply change add to del, and in the end, you can directly use PostUp and PreDown to point to two scripts to complete this series of actions.
Now we can easily handle thousands of CIDRs, flexibly addressing various complex network traffic handling requirements. By the way, if you don’t mind running an additional service specifically for routing, you can also consider using Bird.
Keep Alive
Considering the specific implementation and underlying protocol of WireGuard, the term keep alive here is not entirely accurate. However, sometimes we do have the requirement for WireGuard clients to actively send packets at regular intervals to keep the connection. For example, if a client hasn’t established or has disconnected from a valid connection with the server endpoint due to idleness or other reasons, but the endpoint server or other peers still wanna connect to this client node, they may find the client already asleep and impossible to awaken. In such cases, we can use the PersistentKeepalive parameter to set the packet sending interval, for instance:
[Interface]
...
[Peer]
...
PersistentKeepalive = 30This way, the WireGuard client will actively send packets to the WireGuard endpoint every 30 seconds, effectively preventing the client from idling or going lazy.
Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.