diff options
Diffstat (limited to 'posts')
-rw-r--r-- | posts/wg-quick-deep-dive/main.md | 511 | ||||
-rw-r--r-- | posts/wg-quick-deep-dive/meta.json | 4 |
2 files changed, 515 insertions, 0 deletions
diff --git a/posts/wg-quick-deep-dive/main.md b/posts/wg-quick-deep-dive/main.md new file mode 100644 index 0000000..5f671fe --- /dev/null +++ b/posts/wg-quick-deep-dive/main.md @@ -0,0 +1,511 @@ +`wg-quick(8)` on Linux - a deep dive +==================================== + +Perhaps you have decided to secure your company's internal database or tunnel all your traffic through a VPN. +You enter the command wg-quick up wg0, watch as commands scroll past your screen, and suddenly realize you +can't access the network. + +If this situation sounds familiar, you're not alone. WireGuard is a Layer 3 VPN that has become the de-facto +standard for good reasons — it's fast, simple, and secure. Anecdotally, it's known to be easier to configure +than its bulkier and more convoluted predecessor, OpenVPN. However, configuring or debugging WireGuard networks +requires a robust understanding of networking, something the thorough WireGuard +[documentation](https://www.wireguard.com/#conceptual-overview) can help most users with. + +I recently reviewed the source for `wg-quick(8)` and discovered that it was not entirely documented. Some of the documented +parts assumed an in-depth understanding of networking. This research turned out to be a surprisingly +instructive exercise in Linux networking, and I'm sharing my results here. Hopefully, it can fill in some gaps in the +existing documentation. + +In particular, this guide: + +1. Provides additional exposition on Linux networking +2. Details default-route handling +3. Explains the default firewall configuration +4. Describes the multicast limitations of WireGuard tunnels + +The Basics +---------- + +WireGuard adheres to the Unix philosophy of "doing one thing well," fostering a design of modular systems. +Accordingly, it integrates into the Linux networking stack as a network interface, configured +through the user space tool `wg(8)`. However, to enable traffic flow, IP addresses must be assigned, and +routes need to be configured. While this is a relatively simple procedure, it can become tedious and requires +automation to ensure reliability. This is where `wg-quick(8)` steps in. + +> This is an extremely simple script for easily bringing up a WireGuard interface, suitable for a few +common use cases. ... Generally speaking, this utility is just a simple script that wraps invocations +to `wg(8)` and `ip(8)` in order to set up a WireGuard interface. It is designed for users with simple +needs, and users with more advanced needs are highly encouraged to use a more specific tool, a more +complete network manager, or otherwise just use `wg(8)` and `ip(8)`, as usual. +> +> \- `wg-quick(8)` + +`wg-quick` serves as a straightforward orchestration tool for creating and removing WireGuard tunnels. +It is the de-facto standard for configuring tunnels since it is cross-platform, supported by all official +clients. Its strength lies in the fact that the configuration provides a complete description of the tunnel, +allowing tunnels to be brought up or down with a single command. Additionally, the interface can be initiated +at boot through systemd, although - oddly - this feature remains undocumented. Without additional tooling, `wg-quick` +can configure either a static server endpoint or a roaming peer. + +A "static server endpoint" refers to a computer with a static IP, typically provided by cloud computing vendors, +which functions as a node connecting peers. Often, it may facilitate access to protected resources, like a database. +A roaming peer, on the other hand, is a machine that connects using the static IP of the server and can access the +server or other peers. Peers typically lack a fixed endpoint and "roam" from one IP to another. + +There's also a special provision for cases where a peer routes all their internet traffic through the tunnel, aligning +with how most consumers conceive of a VPN. However, VPNs can also selectively transmit traffic bound for specific +computers. Unfortunately, the server configuration for this setup cannot be handled with wg-quick alone since it +necessitates Network Address Translation (NAT). This limitation likely stands as an exception, enabling VPN providers +to distribute `wg-quick` configurations instead of writing their own tools to route traffic. + +wg-quick is by no means the sole configuration tool for WireGuard. It's supported alongside other network management +systems such as networkd and NetworkManager. Specifically, NetworkManager is a prevalent tool for managing WiFi networks +on desktop environments, and it has the capability to import WireGuard tunnels using the .conf format, just like wg-quick. +(The extent of feature compatibility between them, however, is something I'm not fully aware of at the moment.) On the +other hand, networkd is more commonly leveraged for network management on servers. In the near future, I'll be releasing a +tool that can "import" (or more accurately, transpile) wg-quick files into networkd configurations. When choosing between +these options, it would be wise to defer to your system's network manager. Most users configure WireGuard tunnels within +the initial network namespace. Multiple tools can end up tripping over one another while +managing the same network resources. + +It is also worth stating that `wg-quick` is not the only configuration tool. WireGuard is supported both by +`networkd` and `NetworkManager`. `NetworkManager` is commonly used to manage WiFi networks on the desktop +and can import WireGuard tunnels using the `.conf` format shared with `wg-quick`. (Although I am unaware of +the feature compatibility.) `networkd` is commonly used to manage networks on the server. Soon, I will be +releasing a tool I wrote which allows `wg-quick` files to be "imported" (or more accurately transpiled) to +`networkd`. When considering which option to use, I would default to your system's network manager. WireGuard +tunnels affect resources in the global network namespace, so it makes sense for them to be managed by an entity +responsible for managing network resources globally. + +The process of manually configuring a WireGuard tunnel is thoroughly outlined on the [quickstart page](https://www.wireguard.com/quickstart/). +If you've set up a network with static IP addresses, you might already be acquainted with this process. However, many +enthusiasts and developers may have avoided this route, since the aptly named Dynamic Host Configuration Protocol (DHCP) +takes care of automating IP management. In the following section, I'll delve into configuring network interfaces on Linux, +assuming that you possess a fundamental understanding of networking concepts. Should you be new to setting up a static network, +or if you find yourself unfamiliar with terms like private IP address space, subnets, public IPs, routers, subnet masks, and +interfaces, I strongly recommend taking the time to acquaint yourself with these concepts before proceeding. + +### Revisiting a Simple Network + +WireGuard seamlessly integrates with the Linux networking stack, functioning as a networking interface. The Linux networking +system is indeed robust, supporting a wide array of features. However newcomers should be warned, the documentation often appears +to lag behind the pace of feature development. In this article, we'll explore the fundamentals of a simple Local Area Network (LAN) +and shed light on how packets are routed on contemporary Linux systems. This foundational knowledge is essential for understanding +how `wg-quick` interfaces with these routing constructs. + +Assigning a static IP address to a networking interface can be accomplished using the following command. This sets up a LAN with the +subnet `192.168.0.0-127` and assigns the networking interface the IP address `192.168.0.22`: + +``` +[#] ip -4 addr add 192.168.0.22/25 dev eth0 +``` + +Should you attempt to transmit data to a peer at `192.168.0.11` (e.g., using a UDP socket), how does the kernel determine where to +route these packets? Upon executing the command above, the kernel modifies a structure known as the routing table. Routing tables +function as a database, directing traffic to a specific interface by matching the destination IP address. Most routes are automatically +appended to the `main` routing table, which can be viewed with the command `ip route show`: + +``` +[#] ip -4 route show table main + +192.168.0.0/25 dev eth0 proto kernel scope link src 192.168.0.22 +``` + +In essence, packets destined for our subnet will be sent using eth0 with the source IP address `192.168.0.22`. The kernel +automatically adds this route for a subnet assigned to a link, known as a prefix route. If multiple competing routes +exist in a routing table, the route is selected using the longest prefix match algorithm. Essentially, the most specific +route, characterized by the longest subnet mask, is chosen. If you add a custom route specifying that `192.168.0.11/32` +should be sent using eth1, traffic will flow through this interface instead, since the 32-bit subnet mask is longer than +the 25-bit subnet mask: + +``` +[#] ip -4 route add 192.168.0.11/32 dev eth1 +[#] ip -4 route get 192.168.0.11 +192.168.0.11 dev eth1 src 192.168.0.12 uid 0 + cache +``` + +This brings up an intriguing question: If we send traffic to the current host at `192.168.0.22`, how does the routing +algorithm determine that the packets shouldn't leave the computer but instead be handled by local programs? The answer +lies in another table that precedes the `main` table, known as the `local` table. The `local` table is consulted before +the `main` table, and if a match is found, the packet is routed accordingly. The kernel also manipulated this table in +response to our earlier `ip addr add` command. + +``` +[#] ip -4 route show table local + +local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1 +local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 +broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1 +local 192.168.0.22 dev eth0 proto kernel scope host src 192.168.0.22 +broadcast 192.168.0.127 dev eth0 proto kernel scope link src 192.168.0.22 +``` + +The original ip addr add command added both a local route and a broadcast route. Specifically, the entry `local 192.168.0.22 dev eth0` +stipulates that packets addressed to `192.168.0.22` should be "looped back" and delivered to sockets listening locally on `eth0`, or +bound to the address `192.168.0.22`. Typically, IP traffic is unicast, meaning it originates with one host and is directed at another. +The `broadcast` rule informs the kernel that `192.168.0.127` is a broadcast address. By default, the kernel utilizes the highest IP +address in the subnet as the broadcast address. + +The above overview offers almost a complete picture, but there's another crucial layer of indirection in play: the Routing Policy Database +(RPDB). The RPDB acts as a guide, specifying how routing tables are selected and ordered. This can be queried with the ip rule command, and +the initial state of the RPDB might look something like this: + +``` +[#] ip rule + +0: from all lookup local +32766: from all lookup main +32767: from all lookup default +``` + + +Quoting directly from the manual provides a comprehensive explanation: + +> Each policy routing rule consists of a selector and an action predicate. The RPDB is scanned in order of decreasing priority (note that a lower number means +higher priority, see the description of PREFERENCE below). The selector of each rule is applied to {source address, destination address, incoming interface, +tos, fwmark} and, if the selector matches the packet, the action is performed. The action predicate may return with success. In this case, it will either give +a route or failure indication and the RPDB lookup is terminated. Otherwise, the RPDB program continues with the next rule. +> +> Semantically, the natural action is to select the nexthop and the output device. +> +> At startup time the kernel configures the default RPDB consisting of three rules: +> +> 1. Priority: 0, Selector: match anything, Action: lookup routing table local (ID 255). The local table is a special routing table containing high priority +> control routes for local and broadcast addresses. +> 2. Priority: 32766, Selector: match anything, Action: lookup routing table main (ID 254). The main table is the normal routing table containing all non- +> policy routes. This rule may be deleted and/or overridden with other ones by the administrator. +> 3. Priority: 32767, Selector: match anything, Action: lookup routing table default (ID 253). The default table is empty. It is reserved for some post-pro‐ + cessing if no previous default rules selected the packet. This rule may also be deleted. +> +> \- ip-rule(8) + +If you've been following along, you now understand that when a socket binds or connects to an address, the kernel +selects an appropriate route by querying the routing tables. This query is guided by the rules specified in the +routing policy database (RPDB). When a table contains multiple routes, the longest prefix match algorithm is used +to determine the most specific route, and that is the one selected. + +_Note:_ Policy routing can be bypassed using the `SO_BINDTODEVICE` option. This allows you to bind a socket directly +to a specific device, effectively sidestepping the standard routing process. + +### Bringing Up a Site-to-Site-Style Tunnel with `wg-quick(8)` + +> Use up to add and set up an interface, and use down to tear down and remove an interface. Running up +adds a WireGuard interface, brings up the interface with the supplied IP addresses, sets up mtu and +routes, and optionally runs pre/post up scripts. +> +> \- wg-quick(8) + +The command `wg-quick` first searches for a configuration file. If the first argument to `wg-quick up` matches +the pattern `[a-zA-Z0-9_=+.-]{1,15}`, which represents a valid interface name on Linux, the file is assumed +to exist in `/etc/wireguard` with the provided name and a `.conf` extension. Once a suitable file is found, +its contents are read. For example, it might read the following file, `wg0.conf`: + +``` +[Interface] +PrivateKey = yDdqzxdE66e64xy5Qu1PshT0ybQJLHbU9N+91PS1Dng= +Address = 192.168.42.1/24 + +[Peer] +PublicKey = o837llPmQ4t9cN0rmiLasp6SF54dAzS0Ea1p71c1jFA= +AllowedIPs = 192.168.42.0/32 +Endpoint = 19.216.242.139:16262 + +[Peer] +PublicKey = YIUKiCiw9+6an3HnDn7t3CwlF30ERQkhEQ6f3jRBUnk= +AllowedIPs = 10.1.0.0/16 +Endpoint = 19.216.242.138:16263 +``` + +The process begins identically to the steps taken in the quick start documentation. A WireGuard interface is +added to the system with the interface name from the corresponding file: + +``` +ip link add dev wg0 type wireguard +``` + +Then, the WireGuard configuration is obtained by stripping out the `wg-quick`-specific sections, passing the +rest to the `wg` command. This configuration can be produced using the `wg-quick strip` command. Running +`wg-quick strip` on the above example removes the Address section, and wg parses the configuration to pass it +on to the kernelspace driver [1]. + +[1] It can also communicate this information to the userspace implementation, if available. + +``` +[#] wg-quick strip wg0.conf +[Interface] +PrivateKey = yDdqzxdE66e64xy5Qu1PshT0ybQJLHbU9N+91PS1Dng= + +[Peer] +PublicKey = o837llPmQ4t9cN0rmiLasp6SF54dAzS0Ea1p71c1jFA= +AllowedIPs = 192.168.42.0/32 +Endpoint = 19.216.242.139:16262 + +[Peer] +PublicKey = YIUKiCiw9+6an3HnDn7t3CwlF30ERQkhEQ6f3jRBUnk= +AllowedIPs = 10.1.0.0/16 +Endpoint = 19.216.242.138:16263 +``` + +As the official documentation states: + +> The configuration file adds a few extra configuration values to the format understood by wg(8) in order +to configure additional attributes of an interface. It handles the values that it understands, and then +it passes the remaining ones directly to wg(8) for further processing. +> +> \- wg-quick(8) + +The way the interface configuration affects packet routing is well-covered by the +[original documentation](https://www.wireguard.com/#simple-network-interface). If you haven't read it, you +should explore the "Simple Network Interface" and "Cryptokey Routing" sections before continuing. + +``` +[#] wg-quick strip wg0.conf > wg0-stripped.conf +[#] ip setconf wg0 wg0-stripped.conf +``` + +Next, the IP addresses specified in Addresses are added to the interface. For the above configuration, +it assigns the IP address `192.168.42.1` to the WireGuard interface. This becomes the source IP address +for packets traveling to the peer VPN endpoint [3]. The system also automatically creates a prefix route +directing traffic from any IP address matching `192.168.42.0/24` to the interface `wg0`, and a local route +for `192.168.42.1`. + +[3] The source IP address, as defined in the Addresses section of the configuration, serves as the originating +address for packets sent to the peer VPN endpoint. However, this can be overridden using a "raw socket," in which +an application has the ability to define all fields of a packet, including the source IP. While this introduces +significant security considerations, WireGuard's Cryptorouting scheme substantially mitigates the risks. Malicious +peers are restricted to spoofing only those addresses listed in the `AllowedIPs` section of the `[Peer]` configuration, +and all unauthorized packets are promptly dropped. This built-in mechanism aids in containing potential threats. + +``` +[#] ip -4 addr add 192.168.42.1/24 dev wg0 +``` + +Afterward, the link is brought up, and the MTU (Maximum Transmission Unit) is set. The MTU represents the maximum size +of a packet that can be communicated on a link. If unspecified, the MTU of the underlying link is obtained, and the MTU +of the WireGuard tunnel is reduced by 80 bytes to exclude the overhead of the WireGuard encapsulation packets. I looked +into this and am still unsure as to why an 80 byte MTU reduction was chosen. My working theory is that this value was +chosen to cover both IPv4 and IPv6 with a factor of safety [4]. + +[4] The encapsulation method depends on whether the tunnel endpoint is an IPv4 address or an IPv6 address. The maximum +size of an IPv4 packet is 60 bytes. IPv6 packets are more challenging to quantify since they can have any number of +arbitrary header extensions. The base IPv6 header is only 40 bytes but additional headers can add up. Accordingly, 80 +bytes would cover an IPv6 header with a few extensions. + + +``` +[#] ip link set mtu up 1420 dev wg0 +``` + +Finally, routes are added for all the entries of `AllowedIPs` to the `main` routing table: + +``` +[#] ip -4 route add 192.168.42.0/32 dev wg0 +[#] ip -4 route add 10.1.0.0/16 dev wg0 +``` + +### Default-Route Handling + +The above example illustrates how WireGuard establishes static routes for specific segments of the IP space, which we +specified in `AllowedIPs`. However, these previous examples assumed that the peer endpoint falls outside any of the +`AllowedIP` ranges. If this were not true, the routes would create a routing loop, with packets exiting the WireGuard +interface being routed back into it. + +One common use case for a VPN is to route all traffic to an endpoint which functions as a NAT router. (If you are unfamiliar +with NAT, picture your home router home router.) This can enhance privacy, reduce the specificity of a user's public IP address +in fingerprinting systems, prevent ISPs from selling data to advertisers, or obscure the regional ISP an individual is using. +To route all traffic, we need a default route `0.0.0.0/0` and `::0/0` which match all destination IPs. This creates an issue +since our endpoint's IP will fall into this range. Fortunately, there are methods to make default routes work as intended. + +Linux policy routing can be used in various ways to solve this problem, and the +[Wireguard Routing and Namespace Documentation](https://www.wireguard.com/netns/#routing-all-your-traffic) outlines a few potential +solutions. `wg-quick` elegantly accomplishes this with the following rules, which require some explanation: + + +``` +[#] 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 +``` + +The Linux kernel routing system features `fwmark`, an integer marker ranging from `0` to `2^32 - 1` which can be attached to a packet. +It designates that the packet should be routed or filtered according to special rules. Packets with this mark can either be routed to +a specific table using policy routing (`ip-rule`) or filtered with the `nftables` framework. The `fwmark` can be set either within the +`netfilter` subsystem or by a program when making a network connection [4]. + +[4] `setsockopt` can be used with `SO_MARK` to set the outgoing mark as so long as the process has the `CAP_NET_ADMIN` capability. + +In this instance, wg-quick marks all packets leaving the tunnel with the `fwmark` 51820: + +``` +[#] wg set wg0 fwmark 51820 +``` + +Recall our previous discussion of routing tables. `wg-quick` creates a new routing table within which the default route directs all +traffic to the `wg0` interface, effectively serving as a "default gateway." With none of the mask bits set, all traffic is routed to `wg0`: + +``` +[#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820 +``` + +Traffic must then be directed to this routing table. Here, all traffic that has not exited the VPN interface (and thus does not have +this fwmark) is sent to the table with the default route: + +``` +[#] ip -4 rule add not fwmark 51820 table 51820 +``` + +By default, the IP command assigns this rule a higher priority than the rule querying the main table. + +``` +[#] ip rule +0: from all lookup local +32765: not from all fwmark 0xca6c lookup 51820 +32766: from all lookup main +32767: from all lookup default +``` + +We have established that traffic will be directed through the Wireguard interface if it does not match any local IP ranges. It will +then be sent through `wg0` before being routed according to the `main` table (and likely exit through a default gateway.) + +The `ip rule` command prints the mark in hex as `0xca6c`. + +#### Handling Local Network Traffic + +While VPN traffic will flow as expected, this configuration may have unintended consequences. All local LAN traffic will be directed +to the tunnel, preventing access to the local network (except for encrypted Wireguard packets directed to the default gateway). +Policy routing has one more trick: + +``` +[#] ip -4 rule add table main suppress_prefixlength 0 +``` + +The command `suppress_prefixlength` rejects all routes with prefixes equal to or less than the specified length. So, `suppress_prefixlength 0` +rejects all default gateways, causing the routing algorithm to query the main table first, which typically contains LAN routes but can also +contain routes setup by virtualisation software. + +These rules are set whenever the default IPv4 `0.0.0.0/0` or `IPv6 ::/0` routes appear in the `AllowedIPs`. In the case of IPv6, `ip -6` rules +are added as well. Finally, if `FwMark` is not specified, `wg-quick` searches for the next available mark, stopping when an empty routing table +is found. Finally, the `FwMark` is set to the routing table number. + +### The Default Firewall + +wg-quick configures a firewall if nftables is installed and a default route is specified. The following firewall rules are +setup when an IPv4 endpoint is specified: + +``` +table ip wg-quick-wg0 { + chain preraw { + type filter hook prerouting priority raw; policy accept; + iifname != "wg0" ip daddr 192.168.42.1 fib saddr type != local drop + } + + chain premangle { + type filter hook prerouting priority mangle; policy accept; + meta l4proto udp meta mark set ct mark + } + + chain postmangle { + type filter hook postrouting priority mangle; policy accept; + meta l4proto udp meta mark 0x0000ca6c ct mark set meta mark + } +} +``` + +The first rule in the preraw chain mitigates a potential vulnerability that could allow a network attacker to access servers +listening on `wg0`. It's easy to assume that local services listening on the WireGuard IP address are safe since they're only +accessible to those connected to the tunnel. However, this is not the case. + +Imagine an attacker connected on `eth0`, an adjacent ethernet interface, attempting to access the address assigned to our WireGuard +interface, `192.168.42.1`. Since a route for this address is in the `local` table, the attacker can send packets to `192.168.42.1` +and receive responses, even if the requests originate from another subnet. This could enable the attacker to exfiltrate sensitive +data or inject malicious packets. Therefore, the first rule enforces that services on `wg0` can only be accessed through addresses on +the same interface. + +#### Reverse Path Forwarding + +The last two rules in the code snippet above pertain to reverse path forwarding, a technique used to prevent spoofed packets from entering +a network. Consider a router with forwarding enabled and two links: + +``` +172.30.1.0/31 dev eth-01 proto kernel scope link src 172.30.1.0 +172.30.2.0/31 dev eth-02 proto kernel scope link src 172.30.2.0 +``` + +Now, think about a scenario where a router connected to `eth-02` with IP `172.30.2.1` and an attacker connected to this router attempt to perform +a denial-of-service attack against `1.1.1.1` by spoofing network ICMP packets with random destination IPs and the source address of their victim. +Classically, if one of these spoofed packets "from" `1.1.1.1` destined for `172.30.2.1` arrives on `eth-01`, it would be forwarded. However, this +would be suspicious since a packet from `172.30.2.1` destined for `1.1.1.1` would be dropped, not forwarded through `eth-02`. By considering if a +routable path exists in the reverse direction, a router can automatically drop spoofed packets. This concept is known as reverse path forwarding, +as defined by RFC 3704, known as "strict reverse path forwarding." + +To enable strict reverse path filtering in the Linux kernel, you can use the following sysctl switch: + +``` +[#] sysctl net.ipv4.conf.INTERFACE_NAME.rp_filter=1 +``` + +Most Linux distributions default to Loose Reverse Path Forwarding. In strict mode, traffic from `172.30.1.1` on eth-02 would be dropped, as it should +have routed through `eth-01`. This enforces a symmetrical routing policy but can disrupt asymmetric routing configurations. Loose Reverse Path Forwarding, +conversely, processes traffic if routable through any interface. + +Why does this matter for WireGuard? A firewall's secondary set of rules ensures that strict reverse path forwarding operates accurately. The `fwmark` is +set by the traffic traversing the reverse path, working in collaboration with Linux's conntrack subsystem, which persistently associates incoming traffic +with its corresponding outgoing traffic [5]. The second rule sets the "connection mark" labeled `ct mark` as the `fwmark` during connection establishment, +while the third rule copies it back when packets are received. + +`wg-quick` then instructs the kernel to use the `fwmark` for reverse path forwarding, since this is not enabled by default: + +``` +[#] sysctl -q net.ipv4.conf.all.src_valid_mark=1 +``` + +[5] For example, in tracking a UDP stream, both IPs (source and destination) and L4 headers (source and destination ports) can be tracked to identify +a connection. + +## Additional Thoughts + +When researching WireGuard and reading through the documentation, I found there were a few non-obvious +technical details that seemed undocumented, although potentially important. + +### UDP Socket Parameters + +WireGuard permits users to modify the UDP port on which an interface listens using `ListenPort`. Although, +it always binds its internal UDP socket to all available interfaces using `INADDR_ANY` [6]. This is an +important consideration reasoning about packet flows and writing netfilter rules. + +### Broadcast and Multicast Traffic + +Broadcast and multicast traffic are used in standard protocols, notably service autodiscovery. When considering how +to configure effective firewalls, I was led to ask whether WireGuard carried multicast traffic. Short answer: yes and +no. If the multicast ip range `224.0.0.0/4` or `ff00::/8` appear in the `AllowedIPs` for the tunnel, multicast traffic +could theoretically be broadcast from the other peer. Although, this works with at most two peers. Internally, WireGuard +uses a single prefix tree to select the endpoint to which a packet is sent. If the same `AllowedIPs` appear within +the configuration of multiple peers, the last peer configuration overwrites the routes configured on other peers [7]. +Thus Mutlicast traffic can be directed to, at most, one peer. Thus, if you want to pass broadcast or multicast traffic, +a secondary encapsulation method must be used on top of WireGuard. This does not mean peers with default routes in their +`AllowedIPs` may leak Multicast traffic. This seems to be technically possible. + +In practice, the interface flags are used by programs like `avahi` to determine on which interfaces they should broadcast [8]. +WireGuard is not started with the `IFF_MULTICAST` or `IFF_BROADCAST` flags [8]. According to comments in the Linux kernel, +links without `IFF_MULTICAST` can perform multicast but point-to-point devices cannot broadcast [9]. The exact meaning of these +flags seem ambiguous from the limited documentation, and I wouldn't be surprised if the kernel and userspace developers were +on different pages as to their exact technical meaning. + +> `IFF_MULTICAST` means that this media uses special encapsulation for multicast frames. Apparently, all `IFF_POINTOPOINT` and +`IFF_BROADCAST` devices are able to use multicasts too. +> +> \- [9] + +[6] [Kernel cGit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/wireguard/socket.c?id=e74216b8def3803e98ae536de78733e9d7f3b109#n361) + [Github](https://github.com/torvalds/linux/blob/89bf6209cad66214d3774dac86b6bbf2aec6a30d/drivers/net/wireguard/socket.c#L361) + +[7] [Github](https://github.com/lathiat/avahi/blob/55d783d9d11ced838d73a2757273c5f6958ccd5c/avahi-core/iface-linux.c#L104) + +[8] [Kernel cGit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/wireguard/device.c?id=b5cc3833f13ace75e26e3f7b51cd7b6da5e9cf17#n292) + [Github](https://github.com/torvalds/linux/blob/b5cc3833f13ace75e26e3f7b51cd7b6da5e9cf17/drivers/net/wireguard/device.c#L292) + +[9] [Kernel cGit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/if_link.h?id=706a741595047797872e669b3101429ab8d378ef#n423) + [Github](https://github.com/torvalds/linux/blob/706a741595047797872e669b3101429ab8d378ef/include/uapi/linux/if_link.h#L423) + diff --git a/posts/wg-quick-deep-dive/meta.json b/posts/wg-quick-deep-dive/meta.json new file mode 100644 index 0000000..226796e --- /dev/null +++ b/posts/wg-quick-deep-dive/meta.json @@ -0,0 +1,4 @@ +{ + "name": "wg-quick(8) on Linux - a deep dive", + "lastUpdated": "2023-09-08" +} |