at 25.11-pre 16 kB view raw
1import ./make-test-python.nix ( 2 { lib, pkgs, ... }: 3 let 4 wg-keys = import ./wireguard/snakeoil-keys.nix; 5 6 target_host = "acme.test"; 7 server_host = "sing-box.test"; 8 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; 17 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 }; 42 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 }; 64 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 75 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 85 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 88 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 92 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 103 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 { 113 114 name = "sing-box"; 115 116 meta = { 117 maintainers = with lib.maintainers; [ nickcao ]; 118 }; 119 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 }; 137 138 services.dnsmasq.enable = true; 139 140 services.nginx = { 141 enable = true; 142 package = pkgs.nginxQuic; 143 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 }; 164 165 server = 166 { pkgs, ... }: 167 { 168 boot.kernel.sysctl = { 169 "net.ipv4.conf.all.forwarding" = 1; 170 }; 171 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 }; 185 186 systemd.network.wait-online.ignoredInterfaces = [ "wg0" ]; 187 188 networking.wg-quick.interfaces.wg0 = { 189 address = [ 190 "10.23.42.1/24" 191 ]; 192 listenPort = 2408; 193 mtu = 1500; 194 195 inherit (wg-keys.peer0) privateKey; 196 197 peers = lib.singleton { 198 allowedIPs = [ 199 "10.23.42.2/32" 200 ]; 201 202 inherit (wg-keys.peer1) publicKey; 203 }; 204 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 }; 210 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 }; 226 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 }; 243 244 security.pki.certificates = [ 245 (builtins.readFile ./common/acme/server/ca.cert.pem) 246 ]; 247 248 environment.systemPackages = [ 249 pkgs.curlHTTP3 250 pkgs.iproute2 251 ]; 252 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 }; 284 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 }; 301 302 security.pki.certificates = [ 303 (builtins.readFile ./common/acme/server/ca.cert.pem) 304 ]; 305 306 environment.systemPackages = [ 307 pkgs.curlHTTP3 308 pkgs.iproute2 309 ]; 310 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 }; 343 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 }; 360 361 security.pki.certificates = [ 362 (builtins.readFile ./common/acme/server/ca.cert.pem) 363 ]; 364 365 environment.systemPackages = [ pkgs.curlHTTP3 ]; 366 367 systemd.services.sing-box.serviceConfig.ExecStartPost = [ 368 "+${tproxyPost}/bin/exe" 369 ]; 370 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 }; 410 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 }; 427 428 environment.systemPackages = [ pkgs.dnsutils ]; 429 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"; 462 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 }; 496 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) 502 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") 507 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 ) 517 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) 529 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") 535 536 with subtest("tproxy"): 537 tproxy.wait_for_unit("sing-box.service") 538 test_curl(tproxy) 539 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 ''; 546 547 } 548)