WireGuard Basic Tutorial: Interpretation of wg-quick Routing Policy

A long time ago, we only needed to enter route -n in the Linux terminal (later evolved into ip route , which is the command provided by iproute2) to know the direction of all the data packets in the system, but, my lord, times have changed!

If you’re a WireGuard player and all traffic is routed through WireGuard, but you can’t see anything in the output of the ip route command:

 default via 192.168.100.254 dev eth0 proto dhcp src 192.168.100.63 metric 100 192.168.100.0/24 dev eth0 proto kernel scope link src 192.168.100.63 192.168.100.254 dev eth0 proto dhcp scope link src 192.168.100.63 metric 100

The routing table tells us that all traffic is going out through the physical NIC and not through the WireGuard virtual network interface. Why is this?

routing table

In fact, Linux has included multiple routing tables, not one, since the kernel around version 2.2! At the same time, there is a set of rules that tell the kernel how to choose the correct routing table for each packet.

When you execute ip route , what you see is a specific routing table main , and other routing tables exist besides main. Routing tables are generally identified by integers, and can also be named by text. These names are stored in the file /etc/iproute2/rt_tables . The default content is as follows:

 $ cat /etc/iproute2/rt_tables # # reserved values # 255 local 254 main 253 default 0 unspec # # local # #1 inr.ruhep

In the Linux system, you can customize from 1 to 1-252 routing tables. The Linux system maintains four routing tables by default:

  • 0 : The system reserves the table.
  • 253 : Defulte table. All default routes that are not specified are placed in this table.
  • 254 : main table. All routes that do not specify a routing table are placed in this table.
  • 255 : locale table. Save the local interface address, broadcast address and NAT address, which are maintained by the system and cannot be changed by the user.

There is a very strange word here: inr.ruhep , probably added by Alexey Kuznetsov, who is responsible for the implementation of Quality of Service (QoS) in the Linux kernel, as well as iproute2, this word means “Nuclear Research/Russian High Energy Physics” Research Institute”, where Alexey was working at the time, probably referring to their internal network. Of course, there is another possibility, there is an old Russian computer network/ISP called

RUHEP/Radio-MSU .

There are two ways to view the routing table:

 $ ip route show table table_number  $ ip route show table table_name

Do not confuse the routing table with iptables. The routing table determines how to transmit packets , and iptables determines whether to transmit packets . Their responsibilities are different.

routing strategy

How does the kernel know which routing table should be used for which packet? The answer has been given above. There is a set of rules in the system that will tell the kernel how to choose the correct routing table for each data packet. This set of rules is the routing policy database . This database is managed by the ip rule command. If no parameters are added, all routing rules will be printed:

 0: from all lookup local 32766: from all lookup main 32767: from all lookup default

The number on the left (0, 32764, …) indicates the priority of the rule: the smaller the value, the higher the priority of the rule . That is, rules with smaller numbers will be processed first.

The numerical range of routing rules: 1 ~ $2^{23}-1$

In addition to priority, each rule has a selector and corresponding execution strategy (action). The selector will determine whether the rule applies to the current packet, and if applicable, execute the corresponding policy. The most common execution strategy is to query a specific routing table (see the previous section). If the routing table contains the route of the current packet, then execute the route; otherwise, skip the current routing table and continue to match the next routing rule.

When a Linux system starts up, the kernel configures three default rules for the routing policy database:

  • 0 : Match any condition, query the routing table local (ID 255), which is a special routing table that contains priority control routes for local and broadcast addresses. rule 0 is very special and cannot be deleted or overwritten.
  • 32766 : Match any condition, query the routing table main (ID 254), which is a regular table that contains all policy-free routes. System administrators can delete or override this rule with another rule.
  • 32767 : Match any condition, query routing table default (ID 253), this table is an empty table, it is reserved for subsequent processing. For data packets that are not matched by the previous policy, the system uses this policy for processing, and this rule can also be deleted.

When routing by default, it first searches for a route in the local routing table according to rule 0. If the destination address is the local network or a broadcast address, a suitable route can be found here; if the route fails, it will match The next rule that is not empty, here there is only rule 32766 , will look for a route in the main routing table; if it fails, it will match rule 32767 , that is, look for the default routing table. If it fails, the route will fail. It can be seen from this that policy routing is forward compatible .

WireGuard Global Routing Policy

Now back to WireGuard, many WireGuard users will choose to route all the local traffic through the WireGuard peer, and the reason is well known ?.

The configuration is also very simple, just add 0.0.0.0/0 to AllowedIPs :

 # /etc/wireguard/wg0.conf [Interface] PrivateKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxx Address = 10.0.0.2/32 # PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE # PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE # ListenPort = 51820 [Peer] PublicKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxx Endpoint = 192.168.100.251:51820 AllowedIPs = 0.0.0.0/0

In theory, this allows all traffic to be routed through the peer end, but if you use an older version of wg-quick, after a ferocious operation ( wg-quick up wg0 ), you will find that things are not what you imagined In that case, it may not even be able to connect to the WireGuard peer. Mainly because WireGuard’s own traffic is also routed through the virtual network interface, which is definitely not acceptable.

The new version of wg-quick cleverly solves this problem with routing strategy, let’s see what it is!

First, use wg-quick to start the wg0 network card:

 $ wg-quick up wg0 [ #] ip link add wg0 type wireguard [ #] wg setconf wg0 /dev/fd/63 [ #] ip -4 address add 10.0.0.2/32 dev wg0 [ #] ip link set mtu 1420 up dev wg0 [ #] wg set wg0 fwmark 51820 [ #] ip -4 route add 0.0.0.0/0 dev wg0 table 51820 [ #] ip -4 rule add not fwmark 51820 table 51820 [ #] ip -4 rule add table main suppress_prefixlength 0 [ #] sysctl -q net.ipv4.conf.all.src_valid_mark=1 [ #] iptables-restore -n

Hee hee, seeing the familiar routing policy, print all routing rules to see:

 $ ip rule 0: from all lookup local 32764: from all lookup main suppress_prefixlength 0 32765: not from all fwmark 0xca6c lookup 51820 32766: from all lookup main 32767: from all lookup default

Good guy, two more rules:

 32764: from all lookup main suppress_prefixlength 0 32765: not from all fwmark 0xca6c lookup 51820

Let’s peel off their underpants and unravel the mystery. First come to the three questions of the soul: What is suppress_prefixlength ? What is 0xca6c ? How could the packets not from all ?

Rule 32764

Start the analysis with rule 32764 , because its value is relatively small, it will be preferentially matched:

 32764: from all lookup main suppress_prefixlength 0

This rule does not use selectors, that is, the kernel will look up the main routing table for each packet. Let’s take a look at the contents of the main routing table:

 $ ip route default via 192.168.100.254 dev eth0 proto dhcp src 192.168.100.63 metric 100 192.168.100.0/24 dev eth0 proto kernel scope link src 192.168.100.63 192.168.100.254 dev eth0 proto dhcp scope link src 192.168.100.63 metric 100

If this were the case, then all packets would be routed through the main routing table and would never reach wg0. Don’t forget, there is another parameter at the end of this rule: suppress_prefixlength 0 , what does this mean? Refer to ip-rule(8) man page:

 suppress_prefixlength NUMBER reject routing decisions that have a prefix length of NUMBER or less.

The prefix here is also the prefix , which represents the mask of the matching address range in the routing table. Therefore, if the routing table contains a route for 10.2.3.4 , the prefix length is 32; if it is 10.0.0.0/8 , the prefix length is 8.

suppress means suppression, so suppress_prefixlength 0 means: reject routing policies whose prefix length is less than or equal to 0 .

So what kind of address range prefix length is less than or equal to 0? There is only one possibility: 0.0.0.0/0 , which is the default route. Taking my machine as an example, the default route is:

 default via 192.168.100.254 dev eth0 proto dhcp src 192.168.100.63 metric 100

If the packet matches the default route, it will refuse to forward it; if it is another route, it will be forwarded normally.

The purpose of this rule is very simple. Routes manually added to the main routing table by the administrator will be forwarded normally, while the default route will be ignored and continue to match the next rule .

Rule 32765

The next rule is 32765 :

 32765: not from all fwmark 0xca6c lookup 51820

The not from all here is a problem of ip rule formatting, which is a bit anti-human. The order that is easier for humans to understand should be like this:

 32765: from all not fwmark 0xca6c lookup 51820

From the previous output of wg-quick up wg0 , the selector of the rule does not add the from prefix (address or address range):

 ip -4 rule add not fwmark 51820 table 51820

If the rule selector doesn’t have the from prefix, ip rule will print from all , so this is what the rule looks like.

51820 is a routing table, also created by wg-quick, that contains only one route:

 $ ip route show table 51820 default dev wg0 scope link

So the effect of this rule is: all packets matching this rule are routed through the WireGuard peer , except not fwmark 0xca6c .

0xca6c is just a firewall mark, wg-quick will make wg mark all the packets it sends out ( wg set wg0 fwmark 51820 ), these packets have encapsulated other packets, if these packets are also routed through WireGuard, it will form An infinite routing loop.

So not from all fwmark 0xca6c lookup 51820 means that if the condition from all fwmark 0xca6c is satisfied (all fwmark 0xca6c sent by WireGuard), please ignore this rule and continue to go down. Otherwise, use the 51820 routing table and tunnel out through wg0.

For the 0xca6c that comes with the wg0 interface, continue to the next rule, which is to match the default main routing table:

 32766: from all lookup main

At this point, there is no suppressor, and all packets can freely use the main routing table, so the Endpoint address of the WireGuard peer will be sent out through the eth0 interface.

Perfect!

The routing table created by wg-quick and fwmark use the same number: 51820. 0xca6c is the hex representation of 51820.

Summarize

The neat thing about wg-quick’s approach is that it doesn’t mess with your main routing table, but instead matches the newly created routing table with rules. Simply delete these two routing rules when disconnecting and the default route will be reactivated. Have you lost your studies?

This article is reprinted from https://icloudnative.io/posts/linux-routing-of-wireguard/
This site is for inclusion only, and the copyright belongs to the original author.

Leave a Comment