vuls: init at 0.27.0 (#348530)
[NixPkgs.git] / nixos / tests / sing-box.nix
bloba8a287586af2dc49c50d52e464db9d8143ddfdef
1 import ./make-test-python.nix (
2   { lib, pkgs, ... }:
3   let
4     wg-keys = import ./wireguard/snakeoil-keys.nix;
6     target_host = "acme.test";
7     server_host = "sing-box.test";
9     hosts = {
10       "${target_host}" = "1.1.1.1";
11       "${server_host}" = "1.1.1.2";
12     };
13     hostsEntries = lib.mapAttrs' (k: v: {
14       name = v;
15       value = lib.singleton k;
16     }) hosts;
18     vmessPort = 1080;
19     vmessUUID = "bf000d23-0752-40b4-affe-68f7707a9661";
20     vmessInbound = {
21       type = "vmess";
22       tag = "inbound:vmess";
23       listen = "0.0.0.0";
24       listen_port = vmessPort;
25       users = [
26         {
27           name = "sekai";
28           uuid = vmessUUID;
29           alterId = 0;
30         }
31       ];
32     };
33     vmessOutbound = {
34       type = "vmess";
35       tag = "outbound:vmess";
36       server = server_host;
37       server_port = vmessPort;
38       uuid = vmessUUID;
39       security = "auto";
40       alter_id = 0;
41     };
43     tunInbound = {
44       type = "tun";
45       tag = "inbound:tun";
46       interface_name = "tun0";
47       address = [
48         "172.16.0.1/30"
49         "fd00::1/126"
50       ];
51       auto_route = true;
52       iproute2_table_index = 2024;
53       iproute2_rule_index = 9001;
54       route_address = [
55         "${hosts."${target_host}"}/32"
56       ];
57       route_exclude_address = [
58         "${hosts."${server_host}"}/32"
59       ];
60       strict_route = false;
61       sniff = true;
62       sniff_override_destination = false;
63     };
65     tproxyPort = 1081;
66     tproxyPost = pkgs.writeShellApplication {
67       name = "exe";
68       runtimeInputs = with pkgs; [
69         iproute2
70         iptables
71       ];
72       text = ''
73         ip route add local default dev lo table 100
74         ip rule add fwmark 1 table 100
76         iptables -t mangle -N SING_BOX
77         iptables -t mangle -A SING_BOX -d 100.64.0.0/10 -j RETURN
78         iptables -t mangle -A SING_BOX -d 127.0.0.0/8 -j RETURN
79         iptables -t mangle -A SING_BOX -d 169.254.0.0/16 -j RETURN
80         iptables -t mangle -A SING_BOX -d 172.16.0.0/12 -j RETURN
81         iptables -t mangle -A SING_BOX -d 192.0.0.0/24 -j RETURN
82         iptables -t mangle -A SING_BOX -d 224.0.0.0/4 -j RETURN
83         iptables -t mangle -A SING_BOX -d 240.0.0.0/4 -j RETURN
84         iptables -t mangle -A SING_BOX -d 255.255.255.255/32 -j RETURN
86         iptables -t mangle -A SING_BOX -d ${hosts."${server_host}"}/32 -p tcp -j RETURN
87         iptables -t mangle -A SING_BOX -d ${hosts."${server_host}"}/32 -p udp -j RETURN
89         iptables -t mangle -A SING_BOX -d ${hosts."${target_host}"}/32 -p tcp -j TPROXY --on-port ${toString tproxyPort} --tproxy-mark 1
90         iptables -t mangle -A SING_BOX -d ${hosts."${target_host}"}/32 -p udp -j TPROXY --on-port ${toString tproxyPort} --tproxy-mark 1
91         iptables -t mangle -A PREROUTING -j SING_BOX
93         iptables -t mangle -N SING_BOX_SELF
94         iptables -t mangle -A SING_BOX_SELF -d 100.64.0.0/10 -j RETURN
95         iptables -t mangle -A SING_BOX_SELF -d 127.0.0.0/8 -j RETURN
96         iptables -t mangle -A SING_BOX_SELF -d 169.254.0.0/16 -j RETURN
97         iptables -t mangle -A SING_BOX_SELF -d 172.16.0.0/12 -j RETURN
98         iptables -t mangle -A SING_BOX_SELF -d 192.0.0.0/24 -j RETURN
99         iptables -t mangle -A SING_BOX_SELF -d 224.0.0.0/4 -j RETURN
100         iptables -t mangle -A SING_BOX_SELF -d 240.0.0.0/4 -j RETURN
101         iptables -t mangle -A SING_BOX_SELF -d 255.255.255.255/32 -j RETURN
102         iptables -t mangle -A SING_BOX_SELF  -j RETURN -m mark --mark 1234
104         iptables -t mangle -A SING_BOX_SELF -d ${hosts."${server_host}"}/32 -p tcp -j RETURN
105         iptables -t mangle -A SING_BOX_SELF -d ${hosts."${server_host}"}/32 -p udp -j RETURN
106         iptables -t mangle -A SING_BOX_SELF -p tcp -j MARK --set-mark 1
107         iptables -t mangle -A SING_BOX_SELF -p udp -j MARK --set-mark 1
108         iptables -t mangle -A OUTPUT -j SING_BOX_SELF
109       '';
110     };
111   in
112   {
114     name = "sing-box";
116     meta = {
117       maintainers = with lib.maintainers; [ nickcao ];
118     };
120     nodes = {
121       target =
122         { pkgs, ... }:
123         {
124           networking = {
125             firewall.enable = false;
126             hosts = hostsEntries;
127             useDHCP = false;
128             interfaces.eth1 = {
129               ipv4.addresses = [
130                 {
131                   address = hosts."${target_host}";
132                   prefixLength = 24;
133                 }
134               ];
135             };
136           };
138           services.dnsmasq.enable = true;
140           services.nginx = {
141             enable = true;
142             package = pkgs.nginxQuic;
144             virtualHosts."${target_host}" = {
145               onlySSL = true;
146               sslCertificate = ./common/acme/server/acme.test.cert.pem;
147               sslCertificateKey = ./common/acme/server/acme.test.key.pem;
148               http2 = true;
149               http3 = true;
150               http3_hq = false;
151               quic = true;
152               reuseport = true;
153               locations."/" = {
154                 extraConfig = ''
155                   default_type text/plain;
156                   return 200 "$server_protocol $remote_addr";
157                   allow ${hosts."${server_host}"}/32;
158                   deny all;
159                 '';
160               };
161             };
162           };
163         };
165       server =
166         { pkgs, ... }:
167         {
168           boot.kernel.sysctl = {
169             "net.ipv4.conf.all.forwarding" = 1;
170           };
172           networking = {
173             firewall.enable = false;
174             hosts = hostsEntries;
175             useDHCP = false;
176             interfaces.eth1 = {
177               ipv4.addresses = [
178                 {
179                   address = hosts."${server_host}";
180                   prefixLength = 24;
181                 }
182               ];
183             };
184           };
186           systemd.network.wait-online.ignoredInterfaces = [ "wg0" ];
188           networking.wg-quick.interfaces.wg0 = {
189             address = [
190               "10.23.42.1/24"
191             ];
192             listenPort = 2408;
193             mtu = 1500;
195             inherit (wg-keys.peer0) privateKey;
197             peers = lib.singleton {
198               allowedIPs = [
199                 "10.23.42.2/32"
200               ];
202               inherit (wg-keys.peer1) publicKey;
203             };
205             postUp = ''
206               ${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -j ACCEPT
207               ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.23.42.0/24 -o eth1 -j MASQUERADE
208             '';
209           };
211           services.sing-box = {
212             enable = true;
213             settings = {
214               inbounds = [
215                 vmessInbound
216               ];
217               outbounds = [
218                 {
219                   type = "direct";
220                   tag = "outbound:direct";
221                 }
222               ];
223             };
224           };
225         };
227       tun =
228         { pkgs, ... }:
229         {
230           networking = {
231             firewall.enable = false;
232             hosts = hostsEntries;
233             useDHCP = false;
234             interfaces.eth1 = {
235               ipv4.addresses = [
236                 {
237                   address = "1.1.1.3";
238                   prefixLength = 24;
239                 }
240               ];
241             };
242           };
244           security.pki.certificates = [
245             (builtins.readFile ./common/acme/server/ca.cert.pem)
246           ];
248           environment.systemPackages = [
249             pkgs.curlHTTP3
250             pkgs.iproute2
251           ];
253           services.sing-box = {
254             enable = true;
255             settings = {
256               inbounds = [
257                 tunInbound
258               ];
259               outbounds = [
260                 {
261                   type = "block";
262                   tag = "outbound:block";
263                 }
264                 {
265                   type = "direct";
266                   tag = "outbound:direct";
267                 }
268                 vmessOutbound
269               ];
270               route = {
271                 final = "outbound:block";
272                 rules = [
273                   {
274                     inbound = [
275                       "inbound:tun"
276                     ];
277                     outbound = "outbound:vmess";
278                   }
279                 ];
280               };
281             };
282           };
283         };
285       wireguard =
286         { pkgs, ... }:
287         {
288           networking = {
289             firewall.enable = false;
290             hosts = hostsEntries;
291             useDHCP = false;
292             interfaces.eth1 = {
293               ipv4.addresses = [
294                 {
295                   address = "1.1.1.4";
296                   prefixLength = 24;
297                 }
298               ];
299             };
300           };
302           security.pki.certificates = [
303             (builtins.readFile ./common/acme/server/ca.cert.pem)
304           ];
306           environment.systemPackages = [
307             pkgs.curlHTTP3
308             pkgs.iproute2
309           ];
311           services.sing-box = {
312             enable = true;
313             settings = {
314               outbounds = [
315                 {
316                   type = "block";
317                   tag = "outbound:block";
318                 }
319                 {
320                   type = "direct";
321                   tag = "outbound:direct";
322                 }
323                 {
324                   detour = "outbound:direct";
325                   type = "wireguard";
326                   tag = "outbound:wireguard";
327                   interface_name = "wg0";
328                   local_address = [ "10.23.42.2/32" ];
329                   mtu = 1280;
330                   private_key = wg-keys.peer1.privateKey;
331                   peer_public_key = wg-keys.peer0.publicKey;
332                   server = server_host;
333                   server_port = 2408;
334                   system_interface = true;
335                 }
336               ];
337               route = {
338                 final = "outbound:block";
339               };
340             };
341           };
342         };
344       tproxy =
345         { pkgs, ... }:
346         {
347           networking = {
348             firewall.enable = false;
349             hosts = hostsEntries;
350             useDHCP = false;
351             interfaces.eth1 = {
352               ipv4.addresses = [
353                 {
354                   address = "1.1.1.5";
355                   prefixLength = 24;
356                 }
357               ];
358             };
359           };
361           security.pki.certificates = [
362             (builtins.readFile ./common/acme/server/ca.cert.pem)
363           ];
365           environment.systemPackages = [ pkgs.curlHTTP3 ];
367           systemd.services.sing-box.serviceConfig.ExecStartPost = [
368             "+${tproxyPost}/bin/exe"
369           ];
371           services.sing-box = {
372             enable = true;
373             settings = {
374               inbounds = [
375                 {
376                   tag = "inbound:tproxy";
377                   type = "tproxy";
378                   listen = "0.0.0.0";
379                   listen_port = tproxyPort;
380                   udp_fragment = true;
381                   sniff = true;
382                   sniff_override_destination = false;
383                 }
384               ];
385               outbounds = [
386                 {
387                   type = "block";
388                   tag = "outbound:block";
389                 }
390                 {
391                   type = "direct";
392                   tag = "outbound:direct";
393                 }
394                 vmessOutbound
395               ];
396               route = {
397                 final = "outbound:block";
398                 rules = [
399                   {
400                     inbound = [
401                       "inbound:tproxy"
402                     ];
403                     outbound = "outbound:vmess";
404                   }
405                 ];
406               };
407             };
408           };
409         };
411       fakeip =
412         { pkgs, ... }:
413         {
414           networking = {
415             firewall.enable = false;
416             hosts = hostsEntries;
417             useDHCP = false;
418             interfaces.eth1 = {
419               ipv4.addresses = [
420                 {
421                   address = "1.1.1.6";
422                   prefixLength = 24;
423                 }
424               ];
425             };
426           };
428           environment.systemPackages = [ pkgs.dnsutils ];
430           services.sing-box = {
431             enable = true;
432             settings = {
433               dns = {
434                 final = "dns:default";
435                 independent_cache = true;
436                 fakeip = {
437                   enabled = true;
438                   "inet4_range" = "198.18.0.0/16";
439                 };
440                 servers = [
441                   {
442                     detour = "outbound:direct";
443                     tag = "dns:default";
444                     address = hosts."${target_host}";
445                   }
446                   {
447                     tag = "dns:fakeip";
448                     address = "fakeip";
449                   }
450                 ];
451                 rules = [
452                   {
453                     outbound = [ "any" ];
454                     server = "dns:default";
455                   }
456                   {
457                     query_type = [
458                       "A"
459                       "AAAA"
460                     ];
461                     server = "dns:fakeip";
463                   }
464                 ];
465               };
466               inbounds = [
467                 tunInbound
468               ];
469               outbounds = [
470                 {
471                   type = "block";
472                   tag = "outbound:block";
473                 }
474                 {
475                   type = "direct";
476                   tag = "outbound:direct";
477                 }
478                 {
479                   type = "dns";
480                   tag = "outbound:dns";
481                 }
482               ];
483               route = {
484                 final = "outbound:direct";
485                 rules = [
486                   {
487                     protocol = "dns";
488                     outbound = "outbound:dns";
489                   }
490                 ];
491               };
492             };
493           };
494         };
495     };
497     testScript = ''
498       target.wait_for_unit("nginx.service")
499       target.wait_for_open_port(443)
500       target.wait_for_unit("dnsmasq.service")
501       target.wait_for_open_port(53)
503       server.wait_for_unit("sing-box.service")
504       server.wait_for_open_port(1080)
505       server.wait_for_unit("wg-quick-wg0.service")
506       server.wait_for_file("/sys/class/net/wg0")
508       def test_curl(machine, extra_args=""):
509         assert (
510           machine.succeed(f"curl --fail --max-time 10 --http2 https://${target_host} {extra_args}")
511           == "HTTP/2.0 ${hosts.${server_host}}"
512         )
513         assert (
514           machine.succeed(f"curl --fail --max-time 10 --http3-only https://${target_host} {extra_args}")
515           == "HTTP/3.0 ${hosts.${server_host}}"
516         )
518       with subtest("tun"):
519         tun.wait_for_unit("sing-box.service")
520         tun.wait_for_unit("sys-devices-virtual-net-${tunInbound.interface_name}.device")
521         tun.wait_until_succeeds("ip route get ${hosts."${target_host}"} | grep 'dev ${tunInbound.interface_name}'")
522         tun.succeed("ip addr show ${tunInbound.interface_name}")
523         tun.succeed("ip route show table ${toString tunInbound.iproute2_table_index} | grep ${tunInbound.interface_name}")
524         assert (
525           tun.succeed("ip rule list table ${toString tunInbound.iproute2_table_index} | sort | head -1 | awk -F: '{print $1}' | tr -d '\n'")
526           == "${toString tunInbound.iproute2_rule_index}"
527         )
528         test_curl(tun)
530       with subtest("wireguard"):
531         wireguard.wait_for_unit("sing-box.service")
532         wireguard.wait_for_unit("sys-devices-virtual-net-wg0.device")
533         wireguard.succeed("ip addr show wg0")
534         test_curl(wireguard, "--interface wg0")
536       with subtest("tproxy"):
537         tproxy.wait_for_unit("sing-box.service")
538         test_curl(tproxy)
540       with subtest("fakeip"):
541         fakeip.wait_for_unit("sing-box.service")
542         fakeip.wait_for_unit("sys-devices-virtual-net-${tunInbound.interface_name}.device")
543         fakeip.wait_until_succeeds("ip route get ${hosts."${target_host}"} | grep 'dev ${tunInbound.interface_name}'")
544         fakeip.succeed("dig +short A ${target_host} @${target_host} | grep '^198.18.'")
545     '';
547   }