networking/netns: init
This commit is contained in:
parent
bbecec247d
commit
bb65636b38
|
@ -4,6 +4,7 @@
|
|||
}:
|
||||
let
|
||||
overlays = [
|
||||
inputs.rebmit.overlays.default
|
||||
inputs.ranet.overlays.default
|
||||
|
||||
(final: prev: {
|
||||
|
|
473
nixos/modules/networking/netns.nix
Normal file
473
nixos/modules/networking/netns.nix
Normal file
|
@ -0,0 +1,473 @@
|
|||
{
|
||||
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-${fp.netns}-${fp.protocol}-${toString index}" (
|
||||
mkPortForwardService cfg fp
|
||||
)
|
||||
) cfg.forwardPorts)
|
||||
) allNetns
|
||||
)
|
||||
++ flatten (
|
||||
mapAttrsToList (
|
||||
_name: cfg:
|
||||
(imap (
|
||||
index: ev:
|
||||
nameValuePair "netns-${cfg.netns}-extra-veth-${ev.netns}-${toString index}" (
|
||||
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;
|
||||
};
|
||||
}
|
|
@ -118,17 +118,5 @@ in
|
|||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
})
|
||||
|
||||
(mkIf (cfg.users != { }) {
|
||||
environment.systemPackages = with pkgs; [
|
||||
(pkgs.writeShellApplication {
|
||||
name = "netns-run-default";
|
||||
runtimeInputs = with pkgs; [ util-linux ];
|
||||
text = ''
|
||||
pkexec nsenter -t $$ -e --mount=/proc/1/ns/mnt --net=/proc/1/ns/net -S "$(id -u)" -G "$(id -g)" --wdns="$PWD" "$@"
|
||||
'';
|
||||
})
|
||||
];
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue