2024-09-22 02:45:09 +00:00
|
|
|
import ./make-test-python.nix (
|
|
|
|
{ lib, pkgs, ... }:
|
|
|
|
let
|
|
|
|
wg-keys = import ./wireguard/snakeoil-keys.nix;
|
2023-06-27 04:58:10 +00:00
|
|
|
|
2024-09-22 02:45:09 +00:00
|
|
|
target_host = "acme.test";
|
|
|
|
server_host = "sing-box.test";
|
2023-06-27 04:58:10 +00:00
|
|
|
|
2024-09-22 02:45:09 +00:00
|
|
|
hosts = {
|
|
|
|
"${target_host}" = "1.1.1.1";
|
|
|
|
"${server_host}" = "1.1.1.2";
|
|
|
|
};
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
|
|
|
tunInbound = {
|
|
|
|
type = "tun";
|
|
|
|
tag = "inbound:tun";
|
|
|
|
interface_name = "tun0";
|
2024-10-16 13:28:06 +00:00
|
|
|
address = [
|
|
|
|
"172.16.0.1/30"
|
|
|
|
"fd00::1/126"
|
|
|
|
];
|
2024-09-22 02:45:09 +00:00
|
|
|
auto_route = true;
|
2024-10-16 13:55:46 +00:00
|
|
|
iproute2_table_index = 2024;
|
|
|
|
iproute2_rule_index = 9001;
|
2024-10-16 13:28:06 +00:00
|
|
|
route_address = [
|
2024-09-22 02:45:09 +00:00
|
|
|
"${hosts."${target_host}"}/32"
|
|
|
|
];
|
2024-10-16 13:28:06 +00:00
|
|
|
route_exclude_address = [
|
2024-09-22 02:45:09 +00:00
|
|
|
"${hosts."${server_host}"}/32"
|
|
|
|
];
|
|
|
|
strict_route = false;
|
|
|
|
sniff = true;
|
|
|
|
sniff_override_destination = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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";
|
2023-06-27 04:58:10 +00:00
|
|
|
|
2024-09-22 02:45:09 +00:00
|
|
|
meta = {
|
|
|
|
maintainers = with lib.maintainers; [ nickcao ];
|
2023-08-01 04:44:28 +00:00
|
|
|
};
|
2024-09-22 02:45:09 +00:00
|
|
|
|
|
|
|
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";
|
|
|
|
}
|
|
|
|
];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
2023-06-27 04:58:10 +00:00
|
|
|
};
|
|
|
|
|
2024-09-22 02:45:09 +00:00
|
|
|
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")
|
2024-10-16 13:55:46 +00:00
|
|
|
tun.wait_for_unit("sys-devices-virtual-net-${tunInbound.interface_name}.device")
|
|
|
|
tun.wait_until_succeeds("ip route get ${hosts."${target_host}"} | grep 'dev ${tunInbound.interface_name}'")
|
|
|
|
tun.succeed("ip addr show ${tunInbound.interface_name}")
|
|
|
|
tun.succeed("ip route show table ${toString tunInbound.iproute2_table_index} | grep ${tunInbound.interface_name}")
|
|
|
|
assert (
|
|
|
|
tun.succeed("ip rule list table ${toString tunInbound.iproute2_table_index} | sort | head -1 | awk -F: '{print $1}' | tr -d '\n'")
|
|
|
|
== "${toString tunInbound.iproute2_rule_index}"
|
|
|
|
)
|
2024-09-22 02:45:09 +00:00
|
|
|
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")
|
2023-06-27 04:58:10 +00:00
|
|
|
|
2024-09-22 02:45:09 +00:00
|
|
|
with subtest("tproxy"):
|
|
|
|
tproxy.wait_for_unit("sing-box.service")
|
|
|
|
test_curl(tproxy)
|
2023-06-27 04:58:10 +00:00
|
|
|
|
2024-09-22 02:45:09 +00:00
|
|
|
with subtest("fakeip"):
|
|
|
|
fakeip.wait_for_unit("sing-box.service")
|
2024-10-16 13:55:46 +00:00
|
|
|
fakeip.wait_for_unit("sys-devices-virtual-net-${tunInbound.interface_name}.device")
|
|
|
|
fakeip.wait_until_succeeds("ip route get ${hosts."${target_host}"} | grep 'dev ${tunInbound.interface_name}'")
|
2024-09-22 02:45:09 +00:00
|
|
|
fakeip.succeed("dig +short A ${target_host} @${target_host} | grep '^198.18.'")
|
|
|
|
'';
|
2023-06-27 04:58:10 +00:00
|
|
|
|
2024-09-22 02:45:09 +00:00
|
|
|
}
|
|
|
|
)
|