networking/netns: clean up

This commit is contained in:
Lu Wang 2024-12-07 02:08:39 +08:00
parent b7566b7975
commit 85cd4e61e5
Signed by: rebmit
SSH key fingerprint: SHA256:3px8QV1zEerIrEWHaqtH5rR9kjetyRST5EipOPrd+bU
11 changed files with 641 additions and 550 deletions

View file

@ -1,473 +0,0 @@
{
config,
lib,
pkgs,
mylib,
...
}:
with lib;
let
allNetns = config.networking.netns;
nonDefaultNetns = filterAttrs (_name: cfg: cfg.netns != "default") allNetns;
mkService =
cfg:
let
inherit (cfg)
netns
interface
address
;
enableIPv4Forwarding = if cfg.enableIPv4Forwarding then "1" else "0";
enableIPv6Forwarding = if cfg.enableIPv6Forwarding then "1" else "0";
in
{
path = with pkgs; [
coreutils
iproute2
procps
];
script = ''
ip netns add ${netns}
ip -n ${netns} link add ${interface} type dummy
ip -n ${netns} link set lo up
ip -n ${netns} link set ${interface} up
ip netns exec ${netns} sysctl -w net.ipv4.conf.default.forwarding=${enableIPv4Forwarding}
ip netns exec ${netns} sysctl -w net.ipv4.conf.all.forwarding=${enableIPv4Forwarding}
ip netns exec ${netns} sysctl -w net.ipv6.conf.default.forwarding=${enableIPv6Forwarding}
ip netns exec ${netns} sysctl -w net.ipv6.conf.all.forwarding=${enableIPv6Forwarding}
${concatMapStringsSep "\n" (addr: "ip -n ${netns} addr add ${addr} dev ${interface}") address}
'';
preStop = ''
ip netns del ${netns}
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
};
# https://flokli.de/posts/2022-11-18-nsncd
mkNscdService =
cfg:
let
inherit (cfg) netns netnsPath;
in
{
serviceConfig = mylib.misc.serviceHardened // {
NetworkNamespacePath = netnsPath;
BindReadOnlyPaths = [
"/etc/netns/${netns}/resolv.conf:/etc/resolv.conf:norbind"
"/etc/netns/${netns}/nsswitch.conf:/etc/nsswitch.conf:norbind"
];
BindPaths = [ "/run/netns-${netns}/nscd:/run/nscd:norbind" ];
Type = "notify";
Restart = "always";
RestartSec = 5;
DynamicUser = true;
RuntimeDirectory = "netns-${netns}/nscd";
ExecStart = "${pkgs.nsncd}/bin/nsncd";
};
environment.LD_LIBRARY_PATH = config.system.nssModules.path;
after = [
"netns-${netns}.service"
"network.target"
];
partOf = [ "netns-${netns}.service" ];
wantedBy = [
"multi-user.target"
"netns-${netns}.service"
];
};
mkAuxMntnsService =
cfg:
let
inherit (cfg)
netns
mntnsPath
;
in
{
path = with pkgs; [
coreutils
util-linux
];
script = ''
touch ${mntnsPath} || echo "${mntnsPath} already exists"
unshare --mount=${mntnsPath} --propagation slave mount --bind --read-only /etc/netns/${netns}/resolv.conf /etc/resolv.conf
nsenter --mount=${mntnsPath} mount --bind --read-only /etc/netns/${netns}/nsswitch.conf /etc/nsswitch.conf
nsenter --mount=${mntnsPath} mount --bind --read-only /run/netns-${netns}/nscd /run/nscd
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
after = [
"netns-${netns}.service"
"netns-${netns}-nscd.service"
"network.target"
];
partOf = [ "netns-${netns}.service" ];
wantedBy = [
"multi-user.target"
"netns-${netns}.service"
];
};
mkPortForwardService =
cfg: fp:
let
inherit (fp) protocol source target;
sourceNetns = fp.netns;
sourceNetnsPath = config.networking.netns.${sourceNetns}.netnsPath;
targetNetns = cfg.netns;
targetNetnsConfig = cfg.serviceConfig;
serviceDeps = map (ns: "netns-${ns}.service") (
filter (ns: ns != "default") [
sourceNetns
targetNetns
]
);
in
{
serviceConfig =
mylib.misc.serviceHardened
// targetNetnsConfig
// {
Type = "simple";
Restart = "on-failure";
RestartSec = 5;
DynamicUser = true;
ExecStart = "${pkgs.netns-proxy}/bin/netns-proxy ${sourceNetnsPath} ${source} -b ${target} -p ${protocol} -v";
ProtectProc = false;
RestrictNamespaces = "net";
AmbientCapabilities = [
"CAP_SYS_ADMIN"
"CAP_SYS_PTRACE"
];
CapabilityBoundingSet = [
"CAP_SYS_ADMIN"
"CAP_SYS_PTRACE"
];
};
after = [
"network.target"
] ++ serviceDeps;
partOf = serviceDeps;
wantedBy = [
"multi-user.target"
] ++ serviceDeps;
};
mkExtraVethService =
cfg: ev:
let
inherit (ev)
sourceInterface
targetInterface
;
sourceNetns = cfg.netns;
sourceNetnsPath = cfg.netnsPath;
targetNetns = ev.netns;
targetNetnsPath = config.networking.netns.${targetNetns}.netnsPath;
serviceDeps = map (ns: "netns-${ns}.service") (
filter (ns: ns != "default") [
sourceNetns
targetNetns
]
);
mkSetup =
netns: _netnsPath: interface:
if netns == "default" then "ip link set ${interface} up" else "ip -n ${netns} link set ${interface} up";
mkDrop =
netns: _netnsPath: interface:
if netns == "default" then "ip link del ${interface}" else "ip -n ${netns} link del ${interface}";
in
{
path = with pkgs; [
coreutils
iproute2
procps
];
script = ''
ip link add ${sourceInterface} mtu 1400 address 02:00:00:00:00:01 netns ${sourceNetnsPath} type veth \
peer ${targetInterface} mtu 1400 address 02:00:00:00:00:00 netns ${targetNetnsPath}
${mkSetup sourceNetns sourceNetnsPath sourceInterface}
${mkSetup targetNetns targetNetnsPath targetInterface}
'';
preStop = ''
${mkDrop sourceNetns sourceNetnsPath sourceInterface}
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
after = [
"network.target"
] ++ serviceDeps;
partOf = serviceDeps;
wantedBy = [
"multi-user.target"
] ++ serviceDeps;
};
defaultResolv = pkgs.writeText "netns-default-resolv-conf" ''
nameserver 2606:4700:4700::1111
nameserver 2001:4860:4860::8888
nameserver 1.1.1.1
nameserver 8.8.8.8
'';
defaultNsswitch = pkgs.writeText "netns-default-nsswitch-conf" ''
passwd: ${concatStringsSep " " config.system.nssDatabases.passwd}
group: ${concatStringsSep " " config.system.nssDatabases.group}
shadow: ${concatStringsSep " " config.system.nssDatabases.shadow}
sudoers: ${concatStringsSep " " config.system.nssDatabases.sudoers}
hosts: ${concatStringsSep " " (remove "resolve [!UNAVAIL=return]" config.system.nssDatabases.hosts)}
networks: files
ethers: files
services: ${concatStringsSep " " config.system.nssDatabases.services}
protocols: files
rpc: files
'';
in
{
options.networking.netns = mkOption {
type = types.attrsOf (
types.submodule (
{ name, config, ... }:
let
netnsConfig = config;
in
{
options = {
netns = mkOption {
type = types.str;
default = name;
readOnly = true;
description = ''
Name of the network namespace.
'';
};
netnsPath = mkOption {
type = types.str;
default = if name == "default" then "/proc/1/ns/net" else "/run/netns/${name}";
readOnly = true;
description = ''
Path to the network namespace.
'';
};
mntnsPath = mkOption {
type = types.str;
default = if name == "default" then "/proc/1/ns/mnt" else "/run/netns-${name}/mntns";
readOnly = true;
description = ''
Path to the auxiliary mount namespace.
'';
};
serviceConfig = mkOption {
type = types.attrs;
default =
if config.netns == "default" then
{ }
else
{
NetworkNamespacePath = config.netnsPath;
BindReadOnlyPaths = [
"/etc/netns/${config.netns}/resolv.conf:/etc/resolv.conf:norbind"
"/etc/netns/${config.netns}/nsswitch.conf:/etc/nsswitch.conf:norbind"
"/run/netns-${config.netns}/nscd:/run/nscd:norbind"
];
};
readOnly = true;
description = ''
Systemd service configuration for entering the network namespace.
'';
};
interface = mkOption {
type = types.str;
default = name;
description = ''
Name of the dummy interface to add the address.
'';
};
address = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
Address to be added into the network namespace as source address.
'';
};
enableIPv4Forwarding = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable IPv4 packet forwarding in the network namespace.
'';
};
enableIPv6Forwarding = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable IPv6 packet forwarding in the network namespace.
'';
};
forwardPorts = mkOption {
type = types.listOf (
types.submodule {
options = {
protocol = mkOption {
type = types.enum [
"tcp"
"udp"
];
default = "tcp";
description = ''
The protocol specifier for port forwarding between network namespaces.
'';
};
netns = mkOption {
type = types.str;
default = "default";
description = ''
The network namespace to forward ports from.
'';
};
source = mkOption {
type = types.str;
description = ''
The source endpoint in the specified network namespace to forward.
'';
};
target = mkOption {
type = types.str;
description = ''
The target endpoint in the current network namespace to listen on.
'';
};
};
}
);
default = [ ];
description = ''
List of forwarded ports from another network namespace to this
network namespace.
'';
};
extraVeths = mkOption {
type = types.listOf (
types.submodule (
{ config, ... }:
{
options = {
netns = mkOption {
type = types.str;
default = "default";
description = ''
The network namespace to connect to.
'';
};
sourceInterface = mkOption {
type = types.str;
default = if config.netns == "default" then "host" else config.netns;
description = ''
The interface name in current network namespace;
'';
};
targetInterface = mkOption {
type = types.str;
default = if netnsConfig.netns == "default" then "host" else netnsConfig.netns;
description = ''
The interface name in the other network namespace;
'';
};
};
}
)
);
default = [ ];
description = ''
Extra veth-pairs to be created for enabling link-scope connectivity
between inter-network namespaces.
Note that a veth-pair only needs to be defined on one end.
'';
};
};
}
)
);
description = ''
Network namespace configuration.
'';
};
config = {
networking.netns.default = { };
systemd.services = listToAttrs (
mapAttrsToList (_name: cfg: nameValuePair "netns-${cfg.netns}" (mkService cfg)) nonDefaultNetns
++ mapAttrsToList (
_name: cfg: nameValuePair "netns-${cfg.netns}-nscd" (mkNscdService cfg)
) nonDefaultNetns
++ mapAttrsToList (
_name: cfg: nameValuePair "netns-${cfg.netns}-mntns" (mkAuxMntnsService cfg)
) nonDefaultNetns
++ flatten (
mapAttrsToList (
_name: cfg:
(imap (
index: fp:
nameValuePair "netns-${cfg.netns}-port-forward-${toString index}-${fp.netns}-${fp.protocol}" (
mkPortForwardService cfg fp
)
) cfg.forwardPorts)
) allNetns
)
++ flatten (
mapAttrsToList (
_name: cfg:
(imap (
index: ev:
nameValuePair "netns-${cfg.netns}-extra-veth-${toString index}-${ev.netns}" (
mkExtraVethService cfg ev
)
) cfg.extraVeths)
) allNetns
)
);
environment.etc = listToAttrs (
mapAttrsToList (
_name: cfg:
nameValuePair "netns/${cfg.netns}/resolv.conf" {
source = mkDefault defaultResolv;
}
) nonDefaultNetns
++ mapAttrsToList (
_name: cfg:
nameValuePair "netns/${cfg.netns}/nsswitch.conf" {
source = mkDefault defaultNsswitch;
}
) nonDefaultNetns
);
environment.systemPackages = mapAttrsToList (
name: cfg:
let
inherit (cfg) netns netnsPath mntnsPath;
in
pkgs.writeShellApplication {
name = "netns-run-${netns}";
runtimeInputs = with pkgs; [ util-linux ];
text = ''
pkexec nsenter -t $$ -e --mount=${mntnsPath} --net=${netnsPath} -S "$(id -u)" -G "$(id -g)" --wdns="$PWD" "$@"
'';
}
) allNetns;
};
}

View file

@ -0,0 +1,187 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
allNetns = config.networking.netns;
nonDefaultNetns = filterAttrs (name: _cfg: name != "default") allNetns;
in
{
options.networking.netns = mkOption {
type = types.attrsOf (
types.submodule (
{ name, config, ... }:
{
options = {
netnsPath = mkOption {
type = types.str;
default = if name == "default" then "/proc/1/ns/net" else "/run/netns/${name}";
readOnly = true;
description = ''
Path to the network namespace.
'';
};
mntnsPath = mkOption {
type = types.str;
default = if name == "default" then "/proc/1/ns/mnt" else "/run/netns-${name}/mntns/${name}";
readOnly = true;
description = ''
Path to the auxiliary mount namespace.
'';
};
interface = mkOption {
type = types.str;
default = name;
description = ''
Name of the dummy interface to add the address.
'';
};
address = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
Address to be added into the network namespace as source address.
'';
};
enableIPv4Forwarding = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable IPv4 packet forwarding in the network namespace.
'';
};
enableIPv6Forwarding = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable IPv6 packet forwarding in the network namespace.
'';
};
serviceConfig = mkOption {
type = types.attrs;
default =
if name == "default" then
{ }
else
{
NetworkNamespacePath = config.netnsPath;
BindReadOnlyPaths = optionals config.enableDNSIsolation [
"/etc/netns/${name}/resolv.conf:/etc/resolv.conf:norbind"
"/etc/netns/${name}/nsswitch.conf:/etc/nsswitch.conf:norbind"
"/run/netns-${name}/nscd:/run/nscd:norbind"
];
};
readOnly = true;
description = ''
Systemd service configuration for entering the network namespace.
'';
};
};
}
)
);
description = ''
Network namespace configuration.
'';
};
config = {
networking.netns.default = { };
systemd.services = listToAttrs (
mapAttrsToList (
name: cfg:
let
inherit (cfg) interface address;
enableIPv4Forwarding = if cfg.enableIPv4Forwarding then "1" else "0";
enableIPv6Forwarding = if cfg.enableIPv6Forwarding then "1" else "0";
in
nameValuePair "netns-${name}" {
path = with pkgs; [
coreutils
iproute2
procps
];
script = ''
ip netns add ${name}
ip -n ${name} link add ${interface} type dummy
ip -n ${name} link set lo up
ip -n ${name} link set ${interface} up
ip netns exec ${name} sysctl -w net.ipv4.conf.default.forwarding=${enableIPv4Forwarding}
ip netns exec ${name} sysctl -w net.ipv4.conf.all.forwarding=${enableIPv4Forwarding}
ip netns exec ${name} sysctl -w net.ipv6.conf.default.forwarding=${enableIPv6Forwarding}
ip netns exec ${name} sysctl -w net.ipv6.conf.all.forwarding=${enableIPv6Forwarding}
${concatMapStringsSep "\n" (addr: "ip -n ${name} addr add ${addr} dev ${interface}") address}
'';
preStop = ''
ip netns del ${name}
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
}
) nonDefaultNetns
++ mapAttrsToList (
name: cfg:
let
inherit (cfg) mntnsPath enableDNSIsolation;
in
nameValuePair "netns-${name}-mntns" {
path = with pkgs; [
coreutils
util-linux
];
script = ''
touch ${mntnsPath} || echo "${mntnsPath} already exists"
unshare --mount=${mntnsPath} --propagation slave true
${optionalString enableDNSIsolation ''
nsenter --mount=${mntnsPath} mount --bind --read-only /etc/netns/${name}/resolv.conf /etc/resolv.conf
nsenter --mount=${mntnsPath} mount --bind --read-only /etc/netns/${name}/nsswitch.conf /etc/nsswitch.conf
nsenter --mount=${mntnsPath} mount --bind --read-only /run/netns-${name}/nscd /run/nscd
''}
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
RuntimeDirectory = "netns-${name}/mntns";
};
after =
[
"netns-${name}.service"
"network.target"
]
++ optionals enableDNSIsolation [
"netns-${name}-nscd.service"
];
partOf = [ "netns-${name}.service" ];
wantedBy = [
"multi-user.target"
"netns-${name}.service"
];
}
) nonDefaultNetns
);
environment.systemPackages = mkIf (nonDefaultNetns != { }) (
mapAttrsToList (
name: cfg:
let
inherit (cfg) netnsPath mntnsPath;
in
pkgs.writeShellApplication {
name = "netns-run-${name}";
runtimeInputs = with pkgs; [ util-linux ];
text = ''
pkexec nsenter -t $$ -e --mount=${mntnsPath} --net=${netnsPath} -S "$(id -u)" -G "$(id -g)" --wdns="$PWD" "$@"
'';
}
) allNetns
);
};
}

View file

@ -0,0 +1,127 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
allNetns = config.networking.netns;
allExtraVeths = flatten (mapAttrsToList (_name: cfg: cfg.extraVeths) allNetns);
in
{
options.networking.netns = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
options.extraVeths = mkOption {
type = types.listOf (
types.submodule (
{ config, ... }:
{
options = {
sourceNetns = mkOption {
type = types.str;
default = name;
readOnly = true;
description = ''
The current network namespace.
'';
};
targetNetns = mkOption {
type = types.str;
description = ''
The network namespace to connect to.
'';
};
sourceInterface = mkOption {
type = types.str;
default = if config.targetNetns == "default" then "host" else config.targetNetns;
description = ''
The interface name in the current network namespace;
'';
};
targetInterface = mkOption {
type = types.str;
default = if config.sourceNetns == "default" then "host" else config.sourceNetns;
description = ''
The interface name in the other network namespace;
'';
};
};
}
)
);
default = [ ];
description = ''
Extra veth-pairs to be created for enabling link-scope connectivity
between inter-network namespaces.
Note that a veth-pair only needs to be defined on one end.
'';
};
}
)
);
};
config = {
systemd.services = listToAttrs (
map (
ev:
let
inherit (ev)
sourceNetns
targetNetns
sourceInterface
targetInterface
;
sourceNetnsPath = config.networking.netns.${sourceNetns}.netnsPath;
targetNetnsPath = config.networking.netns.${targetNetns}.netnsPath;
serviceDeps = map (ns: "netns-${ns}.service") (
filter (ns: ns != "default") [
sourceNetns
targetNetns
]
);
mkSetup =
netns: _netnsPath: interface:
if netns == "default" then
"ip link set ${interface} up"
else
"ip -n ${netns} link set ${interface} up";
mkDrop =
netns: _netnsPath: interface:
if netns == "default" then "ip link del ${interface}" else "ip -n ${netns} link del ${interface}";
in
nameValuePair "netns-extra-veth-1-${sourceNetns}-${targetNetns}" {
path = with pkgs; [
coreutils
iproute2
procps
];
script = ''
ip link add ${sourceInterface} mtu 1400 address 02:00:00:00:00:01 netns ${sourceNetnsPath} type veth \
peer ${targetInterface} mtu 1400 address 02:00:00:00:00:00 netns ${targetNetnsPath}
${mkSetup sourceNetns sourceNetnsPath sourceInterface}
${mkSetup targetNetns targetNetnsPath targetInterface}
'';
preStop = ''
${mkDrop sourceNetns sourceNetnsPath sourceInterface}
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
after = [
"network.target"
] ++ serviceDeps;
partOf = serviceDeps;
wantedBy = [
"multi-user.target"
] ++ serviceDeps;
}
) allExtraVeths
);
};
}

View file

@ -0,0 +1,115 @@
{
config,
lib,
pkgs,
mylib,
...
}:
with lib;
let
allNetns = config.networking.netns;
dnsIsolatedNetns = filterAttrs (name: cfg: name != "default" && cfg.enableDNSIsolation) allNetns;
in
{
options.networking.netns = mkOption {
type = types.attrsOf (
types.submodule (
{ ... }:
{
options.enableDNSIsolation = mkOption {
type = types.bool;
default = true;
description = ''
Whether to enable DNS isolation between network namespaces. When disabled,
DNS requests in this namespace may be exposed to other namespaces.
'';
};
}
)
);
};
config = {
# https://flokli.de/posts/2022-11-18-nsncd
systemd.services = mapAttrs' (
name: cfg:
let
inherit (cfg) netnsPath;
in
nameValuePair "netns-${name}-nscd" {
serviceConfig = mylib.misc.serviceHardened // {
NetworkNamespacePath = netnsPath;
BindReadOnlyPaths = [
"/etc/netns/${name}/resolv.conf:/etc/resolv.conf:norbind"
"/etc/netns/${name}/nsswitch.conf:/etc/nsswitch.conf:norbind"
];
BindPaths = [ "/run/netns-${name}/nscd:/run/nscd:norbind" ];
Type = "notify";
Restart = "on-failure";
RestartSec = 5;
User = "${name}-nscd";
RuntimeDirectory = "netns-${name}/nscd";
RuntimeDirectoryPreserve = true;
ExecStart = "${pkgs.nsncd}/bin/nsncd";
};
environment.LD_LIBRARY_PATH = config.system.nssModules.path;
after = [
"netns-${name}.service"
"network.target"
];
partOf = [ "netns-${name}.service" ];
wantedBy = [
"multi-user.target"
"netns-${name}.service"
];
}
) dnsIsolatedNetns;
users.users = mapAttrs' (
name: _cfg:
nameValuePair "${name}-nscd" {
isSystemUser = true;
group = "${name}-nscd";
}
) dnsIsolatedNetns;
users.groups = mapAttrs' (name: _cfg: nameValuePair "${name}-nscd" { }) dnsIsolatedNetns;
environment.etc = listToAttrs (
mapAttrsToList (
name: _cfg:
nameValuePair "netns/${name}/resolv.conf" {
source = mkDefault (
pkgs.writeText "netns-default-resolv-conf" ''
nameserver 2606:4700:4700::1111
nameserver 2001:4860:4860::8888
nameserver 1.1.1.1
nameserver 8.8.8.8
''
);
}
) dnsIsolatedNetns
++ mapAttrsToList (
name: _cfg:
nameValuePair "netns/${name}/nsswitch.conf" {
source = mkDefault (
pkgs.writeText "netns-default-nsswitch-conf" ''
passwd: ${concatStringsSep " " config.system.nssDatabases.passwd}
group: ${concatStringsSep " " config.system.nssDatabases.group}
shadow: ${concatStringsSep " " config.system.nssDatabases.shadow}
sudoers: ${concatStringsSep " " config.system.nssDatabases.sudoers}
hosts: ${concatStringsSep " " (remove "resolve [!UNAVAIL=return]" config.system.nssDatabases.hosts)}
networks: files
ethers: files
services: ${concatStringsSep " " config.system.nssDatabases.services}
protocols: files
rpc: files
''
);
}
) dnsIsolatedNetns
);
};
}

View file

@ -0,0 +1,122 @@
{
config,
lib,
pkgs,
mylib,
...
}:
with lib;
let
allNetns = config.networking.netns;
in
{
options.networking.netns = mkOption {
type = types.attrsOf (
types.submodule (
{ ... }:
{
options.forwardPorts = mkOption {
type = types.listOf (
types.submodule {
options = {
protocol = mkOption {
type = types.enum [
"tcp"
"udp"
];
default = "tcp";
description = ''
The protocol specifier for port forwarding between network namespaces.
'';
};
netns = mkOption {
type = types.str;
default = "default";
description = ''
The network namespace to forward ports from.
'';
};
source = mkOption {
type = types.str;
description = ''
The source endpoint in the specified network namespace to forward.
'';
};
target = mkOption {
type = types.str;
description = ''
The target endpoint in the current network namespace to listen on.
'';
};
};
}
);
default = [ ];
description = ''
List of forwarded ports from another network namespace to this
network namespace.
'';
};
}
)
);
};
config = {
systemd.services = listToAttrs (
flatten (
mapAttrsToList (
name: cfg:
(imap (
index: fp:
let
inherit (fp)
protocol
source
target
netns
;
netnsPath = config.networking.netns.${netns}.netnsPath;
serviceDeps = map (ns: "netns-${ns}.service") (
filter (ns: ns != "default") [
name
netns
]
);
in
nameValuePair "netns-${name}-port-forward-${toString index}" {
serviceConfig =
mylib.misc.serviceHardened
// cfg.serviceConfig
// {
Type = "simple";
Restart = "on-failure";
RestartSec = 5;
DynamicUser = true;
User = "${name}-port-forward-${toString index}";
ExecStart = "${pkgs.netns-proxy}/bin/netns-proxy ${netnsPath} ${source} -b ${target} -p ${protocol} -v";
ProtectProc = false;
RestrictNamespaces = "net";
AmbientCapabilities = [
"CAP_SYS_ADMIN"
"CAP_SYS_PTRACE"
];
CapabilityBoundingSet = [
"CAP_SYS_ADMIN"
"CAP_SYS_PTRACE"
];
};
after = [
"network.target"
] ++ serviceDeps;
partOf = serviceDeps;
wantedBy = [
"multi-user.target"
] ++ serviceDeps;
}
) cfg.forwardPorts)
) allNetns
)
);
};
}

View file

@ -8,13 +8,17 @@ in
options.networking.ports = mkOption { options.networking.ports = mkOption {
type = with types; attrsOf port; type = with types; attrsOf port;
default = { default = {
# standard ports
http = 80; http = 80;
https = 443; https = 443;
socks = 1080;
ssh = 2222; ssh = 2222;
# enthalpy # local ports
enthalpy-gost = 3000;
# public ports
enthalpy-ipsec = 13000; enthalpy-ipsec = 13000;
enthalpy-gost = 1080;
}; };
readOnly = true; readOnly = true;
description = '' description = ''

View file

@ -4,6 +4,7 @@
config, config,
lib, lib,
pkgs, pkgs,
mylib,
... ...
}: }:
with lib; with lib;
@ -15,7 +16,7 @@ in
enable = mkEnableOption "bird for site-scope connectivity"; enable = mkEnableOption "bird for site-scope connectivity";
socket = mkOption { socket = mkOption {
type = types.str; type = types.str;
default = "/run/enthalpy/bird2/bird.ctl"; default = "/run/netns-${cfg.netns}/bird/bird.ctl";
description = '' description = ''
Path to the bird control socket. Path to the bird control socket.
''; '';
@ -42,23 +43,26 @@ in
}; };
config = mkIf (cfg.enable && cfg.bird.enable) { config = mkIf (cfg.enable && cfg.bird.enable) {
environment.etc."enthalpy/bird2.conf".source = pkgs.writeTextFile { environment.etc."netns/${cfg.netns}/bird.conf".source = pkgs.writeTextFile {
name = "bird2"; name = "bird";
text = cfg.bird.config; text = cfg.bird.config;
checkPhase = optionalString cfg.bird.checkConfig '' checkPhase = optionalString cfg.bird.checkConfig ''
ln -s $out bird2.conf ln -s $out bird.conf
${pkgs.buildPackages.bird}/bin/bird -d -p -c bird2.conf ${pkgs.buildPackages.bird}/bin/bird -d -p -c bird.conf
''; '';
}; };
systemd.services.enthalpy-bird2 = { systemd.services.enthalpy-bird = {
serviceConfig = { serviceConfig =
mylib.misc.serviceHardened
// config.networking.netns.${cfg.netns}.serviceConfig
// {
Type = "forking"; Type = "forking";
Restart = "on-failure"; Restart = "on-failure";
RestartSec = 5; RestartSec = 5;
DynamicUser = true; DynamicUser = true;
RuntimeDirectory = "enthalpy/bird2"; RuntimeDirectory = "netns-${cfg.netns}/bird";
ExecStart = "${pkgs.bird}/bin/bird -s ${cfg.bird.socket} -c /etc/enthalpy/bird2.conf"; ExecStart = "${pkgs.bird}/bin/bird -s ${cfg.bird.socket} -c /etc/netns/${cfg.netns}/bird.conf";
ExecReload = "${pkgs.bird}/bin/birdc -s ${cfg.bird.socket} configure"; ExecReload = "${pkgs.bird}/bin/birdc -s ${cfg.bird.socket} configure";
ExecStop = "${pkgs.bird}/bin/birdc -s ${cfg.bird.socket} down"; ExecStop = "${pkgs.bird}/bin/birdc -s ${cfg.bird.socket} down";
CapabilityBoundingSet = [ CapabilityBoundingSet = [
@ -71,21 +75,22 @@ in
"CAP_NET_BIND_SERVICE" "CAP_NET_BIND_SERVICE"
"CAP_NET_RAW" "CAP_NET_RAW"
]; ];
ProtectSystem = "full"; RestrictAddressFamilies = [
ProtectHome = "yes"; "AF_UNIX"
ProtectKernelTunables = true; "AF_INET"
ProtectControlGroups = true; "AF_INET6"
PrivateTmp = true; "AF_NETLINK"
PrivateDevices = true; ];
SystemCallFilter = "~@cpu-emulation @debug @keyring @module @mount @obsolete @raw-io";
MemoryDenyWriteExecute = "yes";
}; };
wantedBy = [ "multi-user.target" ]; after = [ "netns-${cfg.netns}.service" ];
reloadTriggers = [ config.environment.etc."enthalpy/bird2.conf".source ]; partOf = [ "netns-${cfg.netns}.service" ];
wantedBy = [
"multi-user.target"
"netns-${cfg.netns}.service"
];
reloadTriggers = [ config.environment.etc."netns/${cfg.netns}/bird.conf".source ];
}; };
services.enthalpy.services.enthalpy-bird2 = { };
services.enthalpy.bird.config = mkBefore '' services.enthalpy.bird.config = mkBefore ''
router id ${toString cfg.bird.routerId}; router id ${toString cfg.bird.routerId};
ipv6 sadr table sadr6; ipv6 sadr table sadr6;

View file

@ -11,6 +11,7 @@ with lib;
let let
inherit (mylib.network) cidr; inherit (mylib.network) cidr;
cfg = config.services.enthalpy; cfg = config.services.enthalpy;
interface = config.networking.netns.${cfg.netns}.interface;
in in
{ {
options.services.enthalpy.clat = { options.services.enthalpy.clat = {
@ -38,7 +39,7 @@ in
]; ];
preStart = '' preStart = ''
ip -6 route replace 64:ff9b::/96 from ${cfg.clat.address} encap seg6 mode encap \ ip -6 route replace 64:ff9b::/96 from ${cfg.clat.address} encap seg6 mode encap \
segs ${concatStringsSep "," cfg.clat.segment} dev enthalpy mtu 1280 segs ${concatStringsSep "," cfg.clat.segment} dev ${interface} mtu 1280
''; '';
script = '' script = ''
exec tayga --config ${pkgs.writeText "tayga.conf" '' exec tayga --config ${pkgs.writeText "tayga.conf" ''
@ -56,28 +57,32 @@ in
''; '';
preStop = '' preStop = ''
ip -6 route del 64:ff9b::/96 from ${cfg.clat.address} encap seg6 mode encap \ ip -6 route del 64:ff9b::/96 from ${cfg.clat.address} encap seg6 mode encap \
segs ${concatStringsSep "," cfg.clat.segment} dev enthalpy mtu 1280 segs ${concatStringsSep "," cfg.clat.segment} dev ${interface} mtu 1280
''; '';
serviceConfig = { serviceConfig =
mylib.misc.serviceHardened
// config.networking.netns.${cfg.netns}.serviceConfig
// {
Type = "forking"; Type = "forking";
Restart = "on-failure"; Restart = "on-failure";
RestartSec = 5; RestartSec = 5;
DynamicUser = true; DynamicUser = true;
CapabilityBoundingSet = [ "CAP_NET_ADMIN" ]; CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
AmbientCapabilities = [ "CAP_NET_ADMIN" ]; AmbientCapabilities = [ "CAP_NET_ADMIN" ];
ProtectSystem = "full"; RestrictAddressFamilies = [
ProtectHome = "yes"; "AF_UNIX"
ProtectKernelTunables = true; "AF_INET"
ProtectControlGroups = true; "AF_INET6"
PrivateTmp = true; "AF_NETLINK"
SystemCallFilter = "~@cpu-emulation @debug @keyring @module @mount @obsolete @raw-io"; ];
MemoryDenyWriteExecute = "yes"; PrivateDevices = false;
}; };
wants = [ "network.target" ]; after = [ "netns-${cfg.netns}.service" ];
after = [ "network.target" ]; partOf = [ "netns-${cfg.netns}.service" ];
wantedBy = [ "multi-user.target" ]; wantedBy = [
"multi-user.target"
"netns-${cfg.netns}.service"
];
}; };
services.enthalpy.services.enthalpy-clat = { };
}; };
} }

View file

@ -3,7 +3,6 @@
{ {
config, config,
lib, lib,
pkgs,
mylib, mylib,
... ...
}: }:

View file

@ -10,7 +10,6 @@
with lib; with lib;
let let
cfg = config.services.enthalpy; cfg = config.services.enthalpy;
gostPort = config.networking.ports.enthalpy-gost;
in in
{ {
options.services.enthalpy.gost = { options.services.enthalpy.gost = {
@ -21,22 +20,24 @@ in
systemd.services.enthalpy-gost = { systemd.services.enthalpy-gost = {
serviceConfig = mylib.misc.serviceHardened // { serviceConfig = mylib.misc.serviceHardened // {
Type = "simple"; Type = "simple";
Restart = "always"; Restart = "on-failure";
RestartSec = 5; RestartSec = 5;
DynamicUser = true; DynamicUser = true;
ExecStart = "${pkgs.gost}/bin/gost -L=socks5://[::1]:${toString gostPort}"; ExecStart = "${pkgs.gost}/bin/gost -L=socks5://[::1]:${toString config.networking.ports.enthalpy-gost}";
}; };
after = [ "network-online.target" ]; after = [ "netns-${cfg.netns}.service" ];
wantedBy = [ "network-online.target" ]; partOf = [ "netns-${cfg.netns}.service" ];
}; wantedBy = [
"multi-user.target"
networking.netns."${cfg.netns}".forwardPorts = [ "netns-${cfg.netns}.service"
{
protocol = "tcp";
netns = "default";
source = "[::1]:${toString gostPort}";
target = "[::1]:${toString gostPort}";
}
]; ];
}; };
networking.netns."${cfg.netns}".forwardPorts = singleton {
protocol = "tcp";
netns = "default";
source = "[::1]:${toString config.networking.ports.enthalpy-gost}";
target = "[::1]:${toString config.networking.ports.socks}";
};
};
} }

View file

@ -3,7 +3,6 @@
{ {
config, config,
lib, lib,
pkgs,
... ...
}: }:
with lib; with lib;