175 lines
5.9 KiB
Nix
175 lines
5.9 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.tailscale-netns;
|
|
|
|
instanceOptions = {
|
|
options.extraDaemonFlags = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ ];
|
|
description = "Extra flags passed to `tailscaled`.";
|
|
};
|
|
};
|
|
|
|
mkNetnsSetup =
|
|
index: name: instanceCfg:
|
|
let
|
|
ns = "ts-${name}ns";
|
|
hostAddr = "10.201.${toString index}.1";
|
|
hostIP = "${hostAddr}/24";
|
|
nsIP = "10.201.${toString index}.2/24";
|
|
vethHost = "veth-ts-${name}";
|
|
vethNS = "veth-ts-${name}-ns";
|
|
fallbackLines = concatMapStrings (n: " echo nameserver ${n}\n") config.networking.nameservers;
|
|
in
|
|
{
|
|
"netns@ts-${name}" = {
|
|
description = "Network namespace for Tailscale instance ${name}";
|
|
before = [ "network.target" ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
ExecStart = pkgs.writers.writeBash "ts-${name}-netns-up" ''
|
|
set -euo pipefail
|
|
${pkgs.coreutils}/bin/mkdir -p /etc/netns/${ns}
|
|
|
|
{
|
|
dns_found=0
|
|
if ${pkgs.systemd}/bin/resolvectl dns 2>/dev/null; then
|
|
saved_ifs="$IFS"
|
|
IFS=$'\n'
|
|
for line in $(${pkgs.systemd}/bin/resolvectl dns 2>/dev/null); do
|
|
case "$line" in
|
|
*:*)
|
|
server=$(echo "$line" | ${pkgs.gnused}/bin/sed 's/^[^:]*: //')
|
|
case "$server" in
|
|
127.*|::1|"") ;;
|
|
*) echo "nameserver $server"; dns_found=1 ;;
|
|
esac
|
|
;;
|
|
esac
|
|
done
|
|
IFS="$saved_ifs"
|
|
fi
|
|
|
|
if [ "$dns_found" -eq 0 ]; then
|
|
while IFS= read -r rline; do
|
|
case "$rline" in
|
|
nameserver*)
|
|
ip=$(echo "$rline" | ${pkgs.gnused}/bin/sed 's/^nameserver //')
|
|
case "$ip" in
|
|
127.*|::1) ;;
|
|
*) echo "$rline"; dns_found=1 ;;
|
|
esac
|
|
;;
|
|
esac
|
|
done < /etc/resolv.conf
|
|
fi
|
|
${optionalString (fallbackLines != "") ''
|
|
if [ "$dns_found" -eq 0 ]; then
|
|
${fallbackLines} fi
|
|
''}
|
|
} > /etc/netns/${ns}/resolv.conf
|
|
|
|
${pkgs.iproute2}/bin/ip netns add ${ns}
|
|
${pkgs.iproute2}/bin/ip link add ${vethHost} type veth peer name ${vethNS}
|
|
${pkgs.iproute2}/bin/ip link set ${vethNS} netns ${ns}
|
|
|
|
${pkgs.iproute2}/bin/ip addr add ${hostIP} dev ${vethHost}
|
|
${pkgs.iproute2}/bin/ip link set ${vethHost} up
|
|
|
|
${pkgs.iproute2}/bin/ip -n ${ns} addr add ${nsIP} dev ${vethNS}
|
|
${pkgs.iproute2}/bin/ip -n ${ns} link set ${vethNS} up
|
|
${pkgs.iproute2}/bin/ip -n ${ns} link set lo up
|
|
${pkgs.iproute2}/bin/ip -n ${ns} route add default via ${hostAddr}
|
|
|
|
${pkgs.iptables}/bin/iptables -t nat -C POSTROUTING -s ${nsIP} -j MASQUERADE 2>/dev/null \
|
|
|| ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s ${nsIP} -j MASQUERADE
|
|
'';
|
|
ExecStop = pkgs.writers.writeBash "ts-${name}-netns-down" ''
|
|
set -euo pipefail
|
|
${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s ${nsIP} -j MASQUERADE 2>/dev/null || true
|
|
${pkgs.iproute2}/bin/ip link del ${vethHost} 2>/dev/null || true
|
|
${pkgs.iproute2}/bin/ip netns del ${ns} 2>/dev/null || true
|
|
'';
|
|
};
|
|
};
|
|
|
|
"tailscaled-${name}" = {
|
|
description = "Tailscale daemon for instance ${name}";
|
|
bindsTo = [ "netns@ts-${name}.service" ];
|
|
after = [ "netns@ts-${name}.service" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
serviceConfig = {
|
|
ExecStart = pkgs.writers.writeBash "tailscaled-${name}-start" ''
|
|
exec ${pkgs.iproute2}/bin/ip netns exec ${ns} \
|
|
${pkgs.tailscale}/bin/tailscaled \
|
|
--statedir=/var/lib/tailscale-${name} \
|
|
--socket=/run/tailscale-${name}/tailscaled.sock \
|
|
--tun=ts-${name} \
|
|
--port=41641 \
|
|
${escapeShellArgs instanceCfg.extraDaemonFlags}
|
|
'';
|
|
ExecStopPost = pkgs.writers.writeBash "tailscaled-${name}-stop" ''
|
|
${pkgs.iproute2}/bin/ip netns exec ${ns} \
|
|
${pkgs.tailscale}/bin/tailscaled --cleanup \
|
|
--statedir=/var/lib/tailscale-${name} \
|
|
--socket=/run/tailscale-${name}/tailscaled.sock \
|
|
2>/dev/null || true
|
|
'';
|
|
Restart = "on-failure";
|
|
RuntimeDirectory = "tailscale-${name}";
|
|
RuntimeDirectoryMode = "0755";
|
|
StateDirectory = "tailscale-${name}";
|
|
StateDirectoryMode = "0700";
|
|
CacheDirectory = "tailscale-${name}";
|
|
CacheDirectoryMode = "0750";
|
|
|
|
AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_RAW";
|
|
LimitNOFILE = "infinity";
|
|
};
|
|
};
|
|
};
|
|
|
|
in
|
|
{
|
|
options.services.tailscale-netns = {
|
|
enable = mkEnableOption "Tailscale instances in isolated network namespaces";
|
|
|
|
instances = mkOption {
|
|
type = types.attrsOf (types.submodule instanceOptions);
|
|
default = { };
|
|
description = "Tailscale instances configured in separate network namespaces.";
|
|
example = literalExpression ''
|
|
{
|
|
work = {};
|
|
}
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
systemd.services = listToAttrs (
|
|
flatten (
|
|
imap1 (
|
|
index: elem:
|
|
let
|
|
name = elem.name;
|
|
units = mkNetnsSetup index name elem.value;
|
|
in
|
|
[
|
|
{ name = "netns@ts-${name}"; value = units."netns@ts-${name}"; }
|
|
{ name = "tailscaled-${name}"; value = units."tailscaled-${name}"; }
|
|
]
|
|
) (attrsToList cfg.instances)
|
|
)
|
|
);
|
|
};
|
|
}
|