From 25dff2d3f8acb9ea28d953d10715f67623757715 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Sun, 21 Jul 2024 13:13:05 -0500 Subject: [PATCH] Migrate 'WireGuard Packet Forwarding' to hugo --- content/wireguard-packet-forwarding/index.md | 327 ++++++++++++++++++ .../point-to-point.png | Bin 0 -> 3327 bytes .../star-network.png | Bin 0 -> 8919 bytes 3 files changed, 327 insertions(+) create mode 100644 content/wireguard-packet-forwarding/index.md create mode 100644 content/wireguard-packet-forwarding/point-to-point.png create mode 100644 content/wireguard-packet-forwarding/star-network.png diff --git a/content/wireguard-packet-forwarding/index.md b/content/wireguard-packet-forwarding/index.md new file mode 100644 index 0000000..651fdbb --- /dev/null +++ b/content/wireguard-packet-forwarding/index.md @@ -0,0 +1,327 @@ +--- +title: WireGuard Packet Forwarding +date: 2023-08-21 +sn_id: 230179 +--- + +# introduction + +In my [previous blog post](/demystifying-wireguard-and-iptables), I have shown you how to setup your own VPN with [WireGuard](https://wireguard.com/) and [`iptables`](https://wiki.archlinux.org/title/iptables). We have established a point-to-point connection between two peers where one peer (10.172.16.1) was reachable from the internet: + +![point-to-point.png](./point-to-point.png) + +Today, I will explain how more peers can be added to our VPN. +One peer ("the router") will be configured to forward packets between all other peers ("the end devices"). +Therefore, our VPN will become a star network: + +![star-network.png](./star-network.png) + +You could then [install WireGuard on your mobile dev](https://www.wireguard.com/install/#android-play-store-direct-apk-file) and reach all other machines in your VPN from anywhere with internet connection. + +To get a deeper understanding how this forwarding works, we will take a brief look at the network traffic with `tcpdump` after we configured our network. + +# initial configuration + +The end devices will start with a point-to-point connection to the router with 10.172.16.1 as its internal IP address. + +The WireGuard and firewall configuration for these could therefore look like this: + +_WireGuard configuration for end device:_ + +``` +[Interface] +Address = 10.172.16.x/32 +PrivateKey = + +[Peer] +AllowedIPs = 10.172.16.1/32 +PublicKey = +Endpoint = 139.162.153.133:51913 +PersistentKeepalive = 25 +``` + +_firewall configuration for end device:_ + +``` +-P INPUT DROP +-P FORWARD DROP +-P OUTPUT DROP +-A INPUT -m state --state ESTABLISHED -j ACCEPT +-A INPUT -i wg0 -j ACCEPT +-A INPUT -s 139.162.153.133 -i enp3s0 -p udp -m udp --sport 51913 -j ACCEPT +-A OUTPUT -d 139.162.153.133/32 -p tcp -m tcp --dport 22 -j ACCEPT +-A OUTPUT -o wg0 -j ACCEPT +-A OUTPUT -d 139.162.153.133/32 -o enp3s0 -p udp -m udp --dport 51913 -j ACCEPT +``` + +Since the router is connected to multiple peers, it will have multiple `[Peer]` sections in its configuration: + +_WireGuard configuration for router:_ + +``` +[Interface] +Address = 10.172.16.1/32 +PrivateKey = small +ListenPort = 51913 + +[Peer] +AllowedIPs = 10.172.16.2/32 +PublicKey = + +[Peer] +AllowedIPs = 10.172.16.4/32 +PublicKey = + +... + +[Peer] +AllowedIPs = 10.172.16.25/32 +PublicKey = +``` + +_firewall configuration for router:_ + + +``` +-P INPUT DROP +-P FORWARD DROP +-P OUTPUT DROP +-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT +-A INPUT -i wg0 -j ACCEPT +-A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT +-A OUTPUT -m state --state ESTABLISHED -j ACCEPT +-A OUTPUT -o wg0 -j ACCEPT +``` + + + +_If these configurations are confusing to you, read my [previous blog post](/demystifying-wireguard-and-iptables)._ + +# end device configuration + +The only change we have to do on the end devices is to route all IP addresses within the VPN to the router peer. We configure this in the WireGuard configuration file at _/etc/wireguard/wg0.conf_: + +```diff + [Peer] +- AllowedIPs = 10.172.16.1/32 ++ AllowedIPs = 10.172.16.0/24 + PublicKey = <PUBLIC KEY OF ROUTER> + Endpoint = 139.162.153.133:51913 + PersistentKeepalive = 25 +``` + +To apply these changes, we run this command[^1]: + +``` +$ wg syncconf wg0 <(wg-quick strip wg0) +``` + +[^1]: `wg-quick strip wg0` returns the config in the format that `wg` can parse. + This is necessary because `wg-quick` "adds a few extra configuration values to the format understood by `wg` in order to + configure additional attributes of an interface". ([source](https://man.archlinux.org/man/wg-quick.8.en#CONFIGURATION)) + +# router configuration + +## firewall + +The router requires no additional WireGuard configuration. + +However, the router firewall needs to allow forwarding packets inside the VPN: + +```diff + -P INPUT DROP + -P FORWARD DROP + -P OUTPUT DROP + -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT + -A INPUT -i wg0 -j ACCEPT + -A INPUT -i eth0 -p udp -m udp --dport 51913 -j ACCEPT + -A OUTPUT -m state --state ESTABLISHED -j ACCEPT + -A OUTPUT -o wg0 -j ACCEPT ++ -A FORWARD -i wg0 -o wg0 -j ACCEPT +``` + +## kernel + +We also need to allow IP forwarding in the kernel. We can configure kernel parameters with `sysctl`. + +To see the current value for IP forwarding: + +``` +$ sysctl net.ipv4.ip_forward +net.ipv4.ip_forward = 0 +``` + +This basically does the same as checking the content of the file _/proc/sys/net/ipv4/ip\_forward_: + +``` +$ cat /proc/sys/net/ipv4/ip_forward +0 +``` + +To change the setting, we can use `sysctl -w`: + +``` +$ sysctl -w net.ipv4.ip_forward=1 +``` + +However, this change is not persistent. To make sure the new setting is kept after reboot, we need to modify _/etc/sysctl.conf_. We can do this by appending `net.ipv4.ip_forward = 1` to the file: + +``` +$ echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf +``` + +To reload settings: + +``` +$ sysctl -p +``` + +# inspecting the network traffic with tcpdump + +We now should have a connection between every peer via the router. + +To confirm this, we ping one machine (10.172.16.25) from another machine (10.172.16.6): + +``` +$ ping 10.172.16.25 +PING 10.172.16.25 (10.172.16.25) 56(84) bytes of data. +64 bytes from 10.172.16.25: icmp_seq=1 ttl=63 time=138 ms +64 bytes from 10.172.16.25: icmp_seq=2 ttl=63 time=165 ms +64 bytes from 10.172.16.25: icmp_seq=3 ttl=63 time=182 ms +64 bytes from 10.172.16.25: icmp_seq=4 ttl=63 time=206 ms +64 bytes from 10.172.16.25: icmp_seq=5 ttl=63 time=229 ms +``` + +We will inspect this traffic on 10.172.16.1 (the router) and 10.172.16.6 (the pinging machine) with `tcpdump` now.[^2] + +[^2]: Since 10.172.16.25 is a mobile device, I didn't include `tcpdump` output from that device. It also wouldn't include anything interesting. + +On the pinging machine, we will get the following output for the virtual network interface[^3]: + +[^3]: I used `-t` to not include timestamps and `-n` to not resolve IP addresses to hostnames. `-i ` selects the interface we want to tap. + +``` +$ tcpdump -tni wg0 +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on wg0, link-type RAW (Raw IP), snapshot length 262144 bytes +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 19, seq 1, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 19, seq 1, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 19, seq 2, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 19, seq 2, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 19, seq 3, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 19, seq 3, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 19, seq 4, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 19, seq 4, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 19, seq 5, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 19, seq 5, length 64 +``` + +and this for the physical network interface (filtered by UDP packets from/to port 51913): + +``` +$ tcpdump -tni enp3s0 'udp and port 51913' +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on enp3s0, link-type EN10MB (Ethernet), snapshot length 262144 bytes +IP 192.168.178.146.51941 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 192.168.178.146.51941: UDP, length 128 +IP 192.168.178.146.51941 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 192.168.178.146.51941: UDP, length 128 +IP 192.168.178.146.51941 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 192.168.178.146.51941: UDP, length 128 +IP 192.168.178.146.51941 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 192.168.178.146.51941: UDP, length 128 +IP 192.168.178.146.51941 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 192.168.178.146.51941: UDP, length 128 +``` + +On the router, we get this output for the virtual network interface: + +``` +$ tcpdump -tni wg0 +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on wg0, link-type RAW (Raw IP), snapshot length 262144 bytes +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 1, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 1, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 1, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 1, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 2, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 2, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 2, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 2, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 3, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 3, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 3, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 3, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 4, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 4, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 4, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 4, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 5, length 64 +IP 10.172.16.6 > 10.172.16.25: ICMP echo request, id 21, seq 5, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 5, length 64 +IP 10.172.16.25 > 10.172.16.6: ICMP echo reply, id 21, seq 5, length 64 +``` + +and this for the physical network interface: + +``` +$ tcpdump -tni eth0 'udp and port 51913' +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes +IP 87.161.X.X.51941 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 109.43.X.X.11007: UDP, length 128 +IP 109.43.X.X.11007 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 87.161.X.X.51941: UDP, length 128 +IP 87.161.X.X.51941 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 109.43.X.X.11007: UDP, length 128 +IP 109.43.X.X.11007 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 87.161.X.X.51941: UDP, length 128 +IP 87.161.X.X.51941 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 109.43.X.X.11007: UDP, length 128 +IP 109.43.X.X.11007 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 87.161.X.X.51941: UDP, length 128 +IP 87.161.X.X.51941 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 109.43.X.X.11007: UDP, length 128 +IP 109.43.X.X.11007 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 87.161.X.X.51941: UDP, length 128 +IP 87.161.X.X.51941 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 109.43.X.X.11007: UDP, length 128 +IP 109.43.X.X.11007 > 139.144.78.247.51913: UDP, length 128 +IP 139.144.78.247.51913 > 87.161.X.X.51941: UDP, length 128 +``` + +As you can see, for every ICMP echo request, we get an ICMP echo reply from 10.172.16.25 in the `tcpdump` output of 10.172.16.6. We also see that we receive a response for every UDP packet sent to port 51913 of 139.144.78.247. + +However, in the `tcpdump` output of 10.172.16.1 for `wg0`, we see every packet twice. Why is that? + +The reason for this is that `tcpdump` captures every incoming and outgoing packet.[^4] + +[^4]: To be precise, `tcpdump` captures incoming packets _before_ firewall processing while outgoing packets will be captured _after_ firewall processing. This means `tcpdump` will capture incoming packets that will be dropped by the firewall whereas outgoing packets that were dropped will not show up. See [here](https://wiki.archlinux.org/title/Network_Debugging#Tcpdump) and [here](https://superuser.com/a/925332) for more information. + +Since we are forwarding packets on the virtual network interface, the packet for 10.172.16.25 from 10.172.16.6 will be captured on 10.172.16.1 when it enters the interface from 10.172.16.6 and when it leaves to 10.172.16.25. + +The same happens when the response from 10.172.16.25 arrives at 10.172.16.1 and is forwarded to 10.172.16.6. + +We can see this forwarding happening better in the `tcpdump` output for the physical network interface. There, we see that for every ICMP echo request, we see one UDP packet from 87.161.X.X to 139.144.78.247 and then another from 139.144.78.247 to 109.43.X.X. + +As you also can see, the forwarding does not change internal IP addresses. The packets still contain the same source and destination IP addresses. This works because we have configured the interfaces of all peers to route all IP addresses towards the router peer. +So when 10.172.16.25 sees the ICMP echo request from 10.172.16.6 via 10.172.16.1, it will just respond to 10.172.16.6 again via 10.172.16.1. + +The UDP packet IP addresses do seem changed, though. However, this is not network address translation (NAT) but just packet encapsulation. As mentioned in my [previous blog post](/demystifying-wireguard-and-iptables), the physical network interfaces have no notion of internal IP addresses thus every UDP packet is sent using publicly routable IP addresses over the wire. + +This means we are not doing any network address translation here hence the name forwarding. + +Another important detail is that we do not need the following FORWARD rules in the router: + +``` +-A FORWARD -s 10.172.16.0/24 -i wg0 -o eth0 -j ACCEPT +-A FORWARD -d 10.172.16.0/24 -i eth0 -o wg0 -j ACCEPT +``` + +These rules are only required if we want to forward VPN traffic to `eth0` which faces the public internet. Therefore, these rules would be part of a configuration to give internet access via 10.172.16.1 to peers inside the VPN. + +--- + +Thanks for reading my second blog post! If you want to read more content like this, please consider subscribing via [RSS](https://dev.ekzyis.com/blog/rss.xml). + +In the next blog post, we will use network address translation with the SNAT and DNAT targets in `iptables` for port forwarding. This will make it possible to expose an internal service (like an HTTP server for example) to the public internet. \ No newline at end of file diff --git a/content/wireguard-packet-forwarding/point-to-point.png b/content/wireguard-packet-forwarding/point-to-point.png new file mode 100644 index 0000000000000000000000000000000000000000..9bee5f1e2ba8c7e9a77193b6a1905511a4aad111 GIT binary patch literal 3327 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 literal 0 HcmV?d00001