From 4ebc3d66640634de71e50931125ea75283abc5f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20Schr=C3=B6ter?= <dev@felschr.com>
Date: Sat, 7 Jun 2025 00:21:50 +0200
Subject: [PATCH] feat(vpn): exclude LANs from tailscale subnet routing

---
 system/vpn.nix | 41 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 34 insertions(+), 7 deletions(-)

diff --git a/system/vpn.nix b/system/vpn.nix
index 1ea1231..753708c 100644
--- a/system/vpn.nix
+++ b/system/vpn.nix
@@ -26,13 +26,40 @@ in
 
   services.networkd-dispatcher = {
     enable = true;
-    rules."50-tailscale" = {
-      onState = [ "routable" ];
-      script = ''
-        for dev in $(${pkgs.iproute2}/bin/ip route show 0/0 | cut -f5 -d' '); do
-          ${lib.getExe pkgs.ethtool} -K "$dev" rx-udp-gro-forwarding on rx-gro-list off
-        done
-      '';
+    rules = {
+      # exclude LANs from tailscale subnet routes (when using `--accept-routes`)
+      "50-tailscale-exclude-lan-routes" = {
+        onState = [ "routable" ];
+        script = ''
+          #!${pkgs.runtimeShell}
+          # shellcheck disable=SC2010
+
+          lan_interfaces=$(ls /sys/class/net | grep -E '^(enp|eth|wlp)')
+          if [[ "$lan_interfaces" == "" ]]; then exit 0; fi
+          echo "$lan_interfaces" | while IFS= read -r lan_if; do
+            for ipv in 4 6; do
+              subnets=$(${pkgs.iproute2}/bin/ip -"$ipv" route show dev "$lan_if" proto kernel | cut -f1 -d' ' | grep '/')
+              if [[ "$subnets" == "" ]]; then break; fi
+              echo "$subnets" | while IFS= read -r subnet; do
+                if ${pkgs.iproute2}/bin/ip -"$ipv" route show table 52 | grep -q "$subnet dev tailscale0"; then
+                  ${pkgs.iproute2}/bin/ip -"$ipv" route del "$subnet" dev tailscale0 table 52
+                  ${pkgs.iproute2}/bin/ip -"$ipv" route add throw "$subnet" table 52
+                fi
+              done
+            done
+          done
+        '';
+      };
+      # UDP throughput improvements
+      # https://tailscale.com/kb/1320/performance-best-practices?q=gro#linux-optimizations-for-subnet-routers-and-exit-nodes
+      "50-tailscale-rx-udp-gro-forwarding" = {
+        onState = [ "routable" ];
+        script = ''
+          for dev in $(${pkgs.iproute2}/bin/ip route show 0/0 | cut -f5 -d' '); do
+            ${lib.getExe pkgs.ethtool} -K "$dev" rx-udp-gro-forwarding on rx-gro-list off
+          done
+        '';
+      };
     };
   };