summary refs log tree commit diff
path: root/modules
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-02 00:15:10 +0200
committerMel <mel@rnrd.eu>2026-05-02 00:15:10 +0200
commitdd72fc0e8bb6d0b528baa53781f4ea274c1f34d3 (patch)
treeb3446926b4596e0d6a96048355cd5c821de34730 /modules
parent79486b312a0d4d79e908b74669a8e6257ebb2f55 (diff)
downloadnetwork-dd72fc0e8bb6d0b528baa53781f4ea274c1f34d3.tar.zst
network-dd72fc0e8bb6d0b528baa53781f4ea274c1f34d3.zip
Change Xray + Nginx ordering, hide Xray behind Nginx streaming
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'modules')
-rw-r--r--modules/tunnel/egress.nix60
1 files changed, 39 insertions, 21 deletions
diff --git a/modules/tunnel/egress.nix b/modules/tunnel/egress.nix
index 7211da8..c0a057b 100644
--- a/modules/tunnel/egress.nix
+++ b/modules/tunnel/egress.nix
@@ -7,12 +7,7 @@
 }:
 
 let
-  inherit (lib) findFirst;
-
-  # this is the https port, we use it to try to trick dpi into thinking
-  # we are just serving normal encrypted web traffic, nothing interesting! :)
-  # requests which fail vless authentication are forwarded to nginx via fallbacks.
-  port = 443;
+  inherit (lib) findFirst concatStringsSep;
 
   # supposedly the current gold-standard protocol for circumventing dpi!
   # both xray (egress-side) and sing-box (ingress-side) support various
@@ -22,8 +17,12 @@ let
   inboundTag = "vless-in";
   outboundTag = "direct-out";
 
-  # xray falls back to nginx on this port for non-vless requests.
+  # nginx stream listens on port 443 and routes by sni:
+  # * mask domains (microsoft.com) go to xray for vless/reality.
+  # * everything else goes to good old nginx!
+  # both backends receive proxy protocol v1 with the real client ip.
   nginxPort = 8443;
+  xrayPort = 8444;
 
   definition = import ./definition.nix;
   inherit (definition) paths mask;
@@ -32,10 +31,17 @@ let
     p: p.egress == me.name
   ) (throw "no egress information found for this server!") paths;
 
+  maskDomains = [
+    "www.${mask}"
+    mask
+  ];
+
   xrayConfig = {
     inbounds = [
       {
-        inherit port protocol;
+        port = xrayPort;
+        listen = "127.0.0.1";
+        inherit protocol;
         tag = inboundTag;
 
         settings = {
@@ -46,9 +52,6 @@ let
             }
           ];
           decryption = "none";
-          fallbacks = [
-            { dest = "127.0.0.1:${toString nginxPort}"; xver = 1; }
-          ];
         };
 
         streamSettings = {
@@ -57,14 +60,13 @@ let
           realitySettings = {
             show = false;
             dest = "www.${mask}:443";
-            serverNames = [
-              "www.${mask}"
-              mask
-              "${me.name}.rnrd.eu"
-            ];
+            serverNames = maskDomains;
             privateKey = "@PRIVATE_KEY@";
             shortIds = [ path.info.short ];
           };
+          sockopt = {
+            acceptProxyProtocol = true;
+          };
         };
       }
     ];
@@ -95,8 +97,6 @@ let
   config-file = pkgs.writeText "xray.json" (builtins.toJSON xrayConfig);
 in
 {
-  networking.firewall.allowedTCPPorts = [ port ];
-
   age.secrets.egress-key = {
     file = path.info.keySecret;
   };
@@ -137,7 +137,7 @@ in
     settingsFile = "/run/xray-configuration/xray.json";
   };
 
-  # don't unwrap cloudflare addresses
+  # don't unwrap cloudflare addresses, we get the real ip via proxy protocol.
   foundation.www.behindCloudflare = false;
 
   services.nginx = {
@@ -145,8 +145,26 @@ in
       { addr = "127.0.0.1"; port = nginxPort; ssl = true; proxyProtocol = true; }
     ];
 
-    # xray sends proxy protocol v1 to nginx, allowing us to
-    # extract the real client ip from the proxy protocol header.
+    # nginx stream sits on port 443 and routes traffic by sni to the two
+    # backends we have.
+    streamConfig = ''
+      map $ssl_preread_server_name $backend {
+        ${concatStringsSep "\n" (map (d: "${d} xray;") maskDomains)}
+        default web;
+      }
+
+      upstream xray { server 127.0.0.1:${toString xrayPort}; }
+      upstream web { server 127.0.0.1:${toString nginxPort}; }
+
+      server {
+        listen 443;
+        listen [::]:443;
+        ssl_preread on;
+        proxy_pass $backend;
+        proxy_protocol on;
+      }
+    '';
+
     commonHttpConfig = ''
       set_real_ip_from 127.0.0.1;
       real_ip_header proxy_protocol;