summaryrefslogtreecommitdiff
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;