aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorflu0r1ne <flu0r1ne@flu0r1ne.net>2023-09-08 01:06:02 -0500
committerflu0r1ne <flu0r1ne@flu0r1ne.net>2023-09-08 01:06:02 -0500
commit007359aa36fbf83050aada6446d7fb661eb6847e (patch)
treeb516668222bb78cbdda100de0ff5e2645e650177
parent7a6357b97f038b6624bf47119935024f0c339968 (diff)
downloadhomepage-master.tar.xz
homepage-master.zip
Add wg-quick deep dive postHEADmaster
-rw-r--r--posts/wg-quick-deep-dive/main.md511
-rw-r--r--posts/wg-quick-deep-dive/meta.json4
-rw-r--r--public/img/results.gifbin0 -> 5086674 bytes
3 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"
+}
diff --git a/public/img/results.gif b/public/img/results.gif
new file mode 100644
index 0000000..33f334c
--- /dev/null
+++ b/public/img/results.gif
Binary files differ