nixos/sing-box: test distribution specific features (#343641)
This commit is contained in:
commit
89284118a9
@ -1,48 +1,539 @@
|
||||
import ./make-test-python.nix ({ lib, pkgs, ... }: {
|
||||
import ./make-test-python.nix (
|
||||
{ lib, pkgs, ... }:
|
||||
let
|
||||
wg-keys = import ./wireguard/snakeoil-keys.nix;
|
||||
|
||||
name = "sing-box";
|
||||
target_host = "acme.test";
|
||||
server_host = "sing-box.test";
|
||||
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ nickcao ];
|
||||
};
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
environment.systemPackages = [ pkgs.curl ];
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
statusPage = true;
|
||||
hosts = {
|
||||
"${target_host}" = "1.1.1.1";
|
||||
"${server_host}" = "1.1.1.2";
|
||||
};
|
||||
services.sing-box = {
|
||||
enable = true;
|
||||
settings = {
|
||||
inbounds = [{
|
||||
type = "mixed";
|
||||
tag = "inbound";
|
||||
listen = "127.0.0.1";
|
||||
listen_port = 1080;
|
||||
users = [{
|
||||
username = "user";
|
||||
password = { _secret = pkgs.writeText "password" "supersecret"; };
|
||||
}];
|
||||
}];
|
||||
outbounds = [{
|
||||
type = "direct";
|
||||
tag = "outbound";
|
||||
}];
|
||||
};
|
||||
hostsEntries = lib.mapAttrs' (k: v: {
|
||||
name = v;
|
||||
value = lib.singleton k;
|
||||
}) hosts;
|
||||
|
||||
vmessPort = 1080;
|
||||
vmessUUID = "bf000d23-0752-40b4-affe-68f7707a9661";
|
||||
vmessInbound = {
|
||||
type = "vmess";
|
||||
tag = "inbound:vmess";
|
||||
listen = "0.0.0.0";
|
||||
listen_port = vmessPort;
|
||||
users = [
|
||||
{
|
||||
name = "sekai";
|
||||
uuid = vmessUUID;
|
||||
alterId = 0;
|
||||
}
|
||||
];
|
||||
};
|
||||
vmessOutbound = {
|
||||
type = "vmess";
|
||||
tag = "outbound:vmess";
|
||||
server = server_host;
|
||||
server_port = vmessPort;
|
||||
uuid = vmessUUID;
|
||||
security = "auto";
|
||||
alter_id = 0;
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("nginx.service")
|
||||
machine.wait_for_unit("sing-box.service")
|
||||
tunInbound = {
|
||||
type = "tun";
|
||||
tag = "inbound:tun";
|
||||
interface_name = "tun0";
|
||||
inet4_address = "172.16.0.1/30";
|
||||
inet6_address = "fd00::1/126";
|
||||
auto_route = true;
|
||||
inet4_route_address = [
|
||||
"${hosts."${target_host}"}/32"
|
||||
];
|
||||
inet4_route_exclude_address = [
|
||||
"${hosts."${server_host}"}/32"
|
||||
];
|
||||
strict_route = false;
|
||||
sniff = true;
|
||||
sniff_override_destination = false;
|
||||
};
|
||||
|
||||
machine.wait_for_open_port(80)
|
||||
machine.wait_for_open_port(1080)
|
||||
tproxyPort = 1081;
|
||||
tproxyPost = pkgs.writeShellApplication {
|
||||
name = "exe";
|
||||
runtimeInputs = with pkgs; [
|
||||
iproute2
|
||||
iptables
|
||||
];
|
||||
text = ''
|
||||
ip route add local default dev lo table 100
|
||||
ip rule add fwmark 1 table 100
|
||||
|
||||
machine.succeed("curl --fail --max-time 10 --proxy http://user:supersecret@localhost:1080 http://localhost")
|
||||
machine.fail("curl --fail --max-time 10 --proxy http://user:supervillain@localhost:1080 http://localhost")
|
||||
machine.succeed("curl --fail --max-time 10 --proxy socks5://user:supersecret@localhost:1080 http://localhost")
|
||||
'';
|
||||
iptables -t mangle -N SING_BOX
|
||||
iptables -t mangle -A SING_BOX -d 100.64.0.0/10 -j RETURN
|
||||
iptables -t mangle -A SING_BOX -d 127.0.0.0/8 -j RETURN
|
||||
iptables -t mangle -A SING_BOX -d 169.254.0.0/16 -j RETURN
|
||||
iptables -t mangle -A SING_BOX -d 172.16.0.0/12 -j RETURN
|
||||
iptables -t mangle -A SING_BOX -d 192.0.0.0/24 -j RETURN
|
||||
iptables -t mangle -A SING_BOX -d 224.0.0.0/4 -j RETURN
|
||||
iptables -t mangle -A SING_BOX -d 240.0.0.0/4 -j RETURN
|
||||
iptables -t mangle -A SING_BOX -d 255.255.255.255/32 -j RETURN
|
||||
|
||||
})
|
||||
iptables -t mangle -A SING_BOX -d ${hosts."${server_host}"}/32 -p tcp -j RETURN
|
||||
iptables -t mangle -A SING_BOX -d ${hosts."${server_host}"}/32 -p udp -j RETURN
|
||||
|
||||
iptables -t mangle -A SING_BOX -d ${hosts."${target_host}"}/32 -p tcp -j TPROXY --on-port ${toString tproxyPort} --tproxy-mark 1
|
||||
iptables -t mangle -A SING_BOX -d ${hosts."${target_host}"}/32 -p udp -j TPROXY --on-port ${toString tproxyPort} --tproxy-mark 1
|
||||
iptables -t mangle -A PREROUTING -j SING_BOX
|
||||
|
||||
iptables -t mangle -N SING_BOX_SELF
|
||||
iptables -t mangle -A SING_BOX_SELF -d 100.64.0.0/10 -j RETURN
|
||||
iptables -t mangle -A SING_BOX_SELF -d 127.0.0.0/8 -j RETURN
|
||||
iptables -t mangle -A SING_BOX_SELF -d 169.254.0.0/16 -j RETURN
|
||||
iptables -t mangle -A SING_BOX_SELF -d 172.16.0.0/12 -j RETURN
|
||||
iptables -t mangle -A SING_BOX_SELF -d 192.0.0.0/24 -j RETURN
|
||||
iptables -t mangle -A SING_BOX_SELF -d 224.0.0.0/4 -j RETURN
|
||||
iptables -t mangle -A SING_BOX_SELF -d 240.0.0.0/4 -j RETURN
|
||||
iptables -t mangle -A SING_BOX_SELF -d 255.255.255.255/32 -j RETURN
|
||||
iptables -t mangle -A SING_BOX_SELF -j RETURN -m mark --mark 1234
|
||||
|
||||
iptables -t mangle -A SING_BOX_SELF -d ${hosts."${server_host}"}/32 -p tcp -j RETURN
|
||||
iptables -t mangle -A SING_BOX_SELF -d ${hosts."${server_host}"}/32 -p udp -j RETURN
|
||||
iptables -t mangle -A SING_BOX_SELF -p tcp -j MARK --set-mark 1
|
||||
iptables -t mangle -A SING_BOX_SELF -p udp -j MARK --set-mark 1
|
||||
iptables -t mangle -A OUTPUT -j SING_BOX_SELF
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
name = "sing-box";
|
||||
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ nickcao ];
|
||||
};
|
||||
|
||||
nodes = {
|
||||
target =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
networking = {
|
||||
firewall.enable = false;
|
||||
hosts = hostsEntries;
|
||||
useDHCP = false;
|
||||
interfaces.eth1 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = hosts."${target_host}";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
services.dnsmasq.enable = true;
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
package = pkgs.nginxQuic;
|
||||
|
||||
virtualHosts."${target_host}" = {
|
||||
onlySSL = true;
|
||||
sslCertificate = ./common/acme/server/acme.test.cert.pem;
|
||||
sslCertificateKey = ./common/acme/server/acme.test.key.pem;
|
||||
http2 = true;
|
||||
http3 = true;
|
||||
http3_hq = false;
|
||||
quic = true;
|
||||
reuseport = true;
|
||||
locations."/" = {
|
||||
extraConfig = ''
|
||||
default_type text/plain;
|
||||
return 200 "$server_protocol $remote_addr";
|
||||
allow ${hosts."${server_host}"}/32;
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
server =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
boot.kernel.sysctl = {
|
||||
"net.ipv4.conf.all.forwarding" = 1;
|
||||
};
|
||||
|
||||
networking = {
|
||||
firewall.enable = false;
|
||||
hosts = hostsEntries;
|
||||
useDHCP = false;
|
||||
interfaces.eth1 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = hosts."${server_host}";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.network.wait-online.ignoredInterfaces = [ "wg0" ];
|
||||
|
||||
networking.wg-quick.interfaces.wg0 = {
|
||||
address = [
|
||||
"10.23.42.1/24"
|
||||
];
|
||||
listenPort = 2408;
|
||||
mtu = 1500;
|
||||
|
||||
inherit (wg-keys.peer0) privateKey;
|
||||
|
||||
peers = lib.singleton {
|
||||
allowedIPs = [
|
||||
"10.23.42.2/32"
|
||||
];
|
||||
|
||||
inherit (wg-keys.peer1) publicKey;
|
||||
};
|
||||
|
||||
postUp = ''
|
||||
${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -j ACCEPT
|
||||
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.23.42.0/24 -o eth1 -j MASQUERADE
|
||||
'';
|
||||
};
|
||||
|
||||
services.sing-box = {
|
||||
enable = true;
|
||||
settings = {
|
||||
inbounds = [
|
||||
vmessInbound
|
||||
];
|
||||
outbounds = [
|
||||
{
|
||||
type = "direct";
|
||||
tag = "outbound:direct";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
tun =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
networking = {
|
||||
firewall.enable = false;
|
||||
hosts = hostsEntries;
|
||||
useDHCP = false;
|
||||
interfaces.eth1 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "1.1.1.3";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
security.pki.certificates = [
|
||||
(builtins.readFile ./common/acme/server/ca.cert.pem)
|
||||
];
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.curlHTTP3
|
||||
pkgs.iproute2
|
||||
];
|
||||
|
||||
services.sing-box = {
|
||||
enable = true;
|
||||
settings = {
|
||||
inbounds = [
|
||||
tunInbound
|
||||
];
|
||||
outbounds = [
|
||||
{
|
||||
type = "block";
|
||||
tag = "outbound:block";
|
||||
}
|
||||
{
|
||||
type = "direct";
|
||||
tag = "outbound:direct";
|
||||
}
|
||||
vmessOutbound
|
||||
];
|
||||
route = {
|
||||
final = "outbound:block";
|
||||
rules = [
|
||||
{
|
||||
inbound = [
|
||||
"inbound:tun"
|
||||
];
|
||||
outbound = "outbound:vmess";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
wireguard =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
networking = {
|
||||
firewall.enable = false;
|
||||
hosts = hostsEntries;
|
||||
useDHCP = false;
|
||||
interfaces.eth1 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "1.1.1.4";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
security.pki.certificates = [
|
||||
(builtins.readFile ./common/acme/server/ca.cert.pem)
|
||||
];
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.curlHTTP3
|
||||
pkgs.iproute2
|
||||
];
|
||||
|
||||
services.sing-box = {
|
||||
enable = true;
|
||||
settings = {
|
||||
outbounds = [
|
||||
{
|
||||
type = "block";
|
||||
tag = "outbound:block";
|
||||
}
|
||||
{
|
||||
type = "direct";
|
||||
tag = "outbound:direct";
|
||||
}
|
||||
{
|
||||
detour = "outbound:direct";
|
||||
type = "wireguard";
|
||||
tag = "outbound:wireguard";
|
||||
interface_name = "wg0";
|
||||
local_address = [ "10.23.42.2/32" ];
|
||||
mtu = 1280;
|
||||
private_key = wg-keys.peer1.privateKey;
|
||||
peer_public_key = wg-keys.peer0.publicKey;
|
||||
server = server_host;
|
||||
server_port = 2408;
|
||||
system_interface = true;
|
||||
}
|
||||
];
|
||||
route = {
|
||||
final = "outbound:block";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
tproxy =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
networking = {
|
||||
firewall.enable = false;
|
||||
hosts = hostsEntries;
|
||||
useDHCP = false;
|
||||
interfaces.eth1 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "1.1.1.5";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
security.pki.certificates = [
|
||||
(builtins.readFile ./common/acme/server/ca.cert.pem)
|
||||
];
|
||||
|
||||
environment.systemPackages = [ pkgs.curlHTTP3 ];
|
||||
|
||||
systemd.services.sing-box.serviceConfig.ExecStartPost = [
|
||||
"+${tproxyPost}/bin/exe"
|
||||
];
|
||||
|
||||
services.sing-box = {
|
||||
enable = true;
|
||||
settings = {
|
||||
inbounds = [
|
||||
{
|
||||
tag = "inbound:tproxy";
|
||||
type = "tproxy";
|
||||
listen = "0.0.0.0";
|
||||
listen_port = tproxyPort;
|
||||
udp_fragment = true;
|
||||
sniff = true;
|
||||
sniff_override_destination = false;
|
||||
}
|
||||
];
|
||||
outbounds = [
|
||||
{
|
||||
type = "block";
|
||||
tag = "outbound:block";
|
||||
}
|
||||
{
|
||||
type = "direct";
|
||||
tag = "outbound:direct";
|
||||
}
|
||||
vmessOutbound
|
||||
];
|
||||
route = {
|
||||
final = "outbound:block";
|
||||
rules = [
|
||||
{
|
||||
inbound = [
|
||||
"inbound:tproxy"
|
||||
];
|
||||
outbound = "outbound:vmess";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fakeip =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
networking = {
|
||||
firewall.enable = false;
|
||||
hosts = hostsEntries;
|
||||
useDHCP = false;
|
||||
interfaces.eth1 = {
|
||||
ipv4.addresses = [
|
||||
{
|
||||
address = "1.1.1.6";
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.dnsutils ];
|
||||
|
||||
services.sing-box = {
|
||||
enable = true;
|
||||
settings = {
|
||||
dns = {
|
||||
final = "dns:default";
|
||||
independent_cache = true;
|
||||
fakeip = {
|
||||
enabled = true;
|
||||
"inet4_range" = "198.18.0.0/16";
|
||||
};
|
||||
servers = [
|
||||
{
|
||||
detour = "outbound:direct";
|
||||
tag = "dns:default";
|
||||
address = hosts."${target_host}";
|
||||
}
|
||||
{
|
||||
tag = "dns:fakeip";
|
||||
address = "fakeip";
|
||||
}
|
||||
];
|
||||
rules = [
|
||||
{
|
||||
outbound = [ "any" ];
|
||||
server = "dns:default";
|
||||
}
|
||||
{
|
||||
query_type = [
|
||||
"A"
|
||||
"AAAA"
|
||||
];
|
||||
server = "dns:fakeip";
|
||||
|
||||
}
|
||||
];
|
||||
};
|
||||
inbounds = [
|
||||
tunInbound
|
||||
];
|
||||
outbounds = [
|
||||
{
|
||||
type = "block";
|
||||
tag = "outbound:block";
|
||||
}
|
||||
{
|
||||
type = "direct";
|
||||
tag = "outbound:direct";
|
||||
}
|
||||
{
|
||||
type = "dns";
|
||||
tag = "outbound:dns";
|
||||
}
|
||||
];
|
||||
route = {
|
||||
final = "outbound:direct";
|
||||
rules = [
|
||||
{
|
||||
protocol = "dns";
|
||||
outbound = "outbound:dns";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
target.wait_for_unit("nginx.service")
|
||||
target.wait_for_open_port(443)
|
||||
target.wait_for_unit("dnsmasq.service")
|
||||
target.wait_for_open_port(53)
|
||||
|
||||
server.wait_for_unit("sing-box.service")
|
||||
server.wait_for_open_port(1080)
|
||||
server.wait_for_unit("wg-quick-wg0.service")
|
||||
server.wait_for_file("/sys/class/net/wg0")
|
||||
|
||||
def test_curl(machine, extra_args=""):
|
||||
assert (
|
||||
machine.succeed(f"curl --fail --max-time 10 --http2 https://${target_host} {extra_args}")
|
||||
== "HTTP/2.0 ${hosts.${server_host}}"
|
||||
)
|
||||
assert (
|
||||
machine.succeed(f"curl --fail --max-time 10 --http3-only https://${target_host} {extra_args}")
|
||||
== "HTTP/3.0 ${hosts.${server_host}}"
|
||||
)
|
||||
|
||||
with subtest("tun"):
|
||||
tun.wait_for_unit("sing-box.service")
|
||||
tun.wait_for_unit("sys-devices-virtual-net-tun0.device")
|
||||
tun.wait_until_succeeds("ip route get ${hosts."${target_host}"} | grep 'dev tun0'")
|
||||
tun.succeed("ip addr show tun0")
|
||||
test_curl(tun)
|
||||
|
||||
with subtest("wireguard"):
|
||||
wireguard.wait_for_unit("sing-box.service")
|
||||
wireguard.wait_for_unit("sys-devices-virtual-net-wg0.device")
|
||||
wireguard.succeed("ip addr show wg0")
|
||||
test_curl(wireguard, "--interface wg0")
|
||||
|
||||
with subtest("tproxy"):
|
||||
tproxy.wait_for_unit("sing-box.service")
|
||||
test_curl(tproxy)
|
||||
|
||||
with subtest("fakeip"):
|
||||
fakeip.wait_for_unit("sing-box.service")
|
||||
fakeip.wait_for_unit("sys-devices-virtual-net-tun0.device")
|
||||
fakeip.wait_until_succeeds("ip route get ${hosts."${target_host}"} | grep 'dev tun0'")
|
||||
fakeip.succeed("dig +short A ${target_host} @${target_host} | grep '^198.18.'")
|
||||
'';
|
||||
|
||||
}
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user