networking/netns: prefer ipv6 by introducing gai.conf

This commit is contained in:
Lu Wang 2024-12-10 20:54:41 +08:00
parent c7edeb703d
commit 52600dbb00
Signed by: rebmit
SSH key fingerprint: SHA256:3px8QV1zEerIrEWHaqtH5rR9kjetyRST5EipOPrd+bU
3 changed files with 243 additions and 137 deletions

View file

@ -13,7 +13,7 @@ in
options.networking.netns = mkOption { options.networking.netns = mkOption {
type = types.attrsOf ( type = types.attrsOf (
types.submodule ( types.submodule (
{ name, config, ... }: { name, ... }:
{ {
options = { options = {
netnsPath = mkOption { netnsPath = mkOption {
@ -24,14 +24,6 @@ in
Path to the network namespace. Path to the network namespace.
''; '';
}; };
mntnsPath = mkOption {
type = types.str;
default = if name == "default" then "/proc/1/ns/mnt" else "/run/${name}/mntns/${name}";
readOnly = true;
description = ''
Path to the auxiliary mount namespace.
'';
};
interface = mkOption { interface = mkOption {
type = types.str; type = types.str;
default = name; default = name;
@ -60,25 +52,6 @@ in
Whether to enable IPv6 packet forwarding in the network namespace. 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/${name}/nscd:/run/nscd:norbind"
];
};
readOnly = true;
description = ''
Systemd service configuration for entering the network namespace.
'';
};
}; };
} }
) )
@ -91,8 +64,7 @@ in
config = { config = {
networking.netns.default = { }; networking.netns.default = { };
systemd.services = listToAttrs ( systemd.services = mapAttrs' (
mapAttrsToList (
name: cfg: name: cfg:
let let
inherit (cfg) interface address; inherit (cfg) interface address;
@ -126,47 +98,7 @@ in
after = [ "network.target" ]; after = [ "network.target" ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
} }
) nonDefaultNetns ) 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/${name}/nscd /run/nscd
''}
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
RuntimeDirectory = "${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 != { }) ( environment.systemPackages = mkIf (nonDefaultNetns != { }) (
mapAttrsToList ( mapAttrsToList (

View file

@ -0,0 +1,149 @@
{
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 = {
mntnsPath = mkOption {
type = types.str;
default = if name == "default" then "/proc/1/ns/mnt" else "/run/${name}/mntns/${name}";
readOnly = true;
description = ''
Path to the auxiliary mount namespace.
'';
};
bindMounts = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
options = {
mountPoint = mkOption {
type = types.str;
default = name;
description = ''
Mount point on the auxiliary mount namespace.
'';
};
hostPath = mkOption {
type = types.str;
description = ''
Location of the path to be mounted in the default mount namespace.
'';
};
isReadOnly = mkOption {
type = types.bool;
default = true;
description = ''
Determine whether the mounted path will be accessed in read-only mode.
'';
};
};
}
)
);
default = { };
description = ''
A extra list of bind mounts that is bound to the network namespace.
'';
};
serviceConfig = mkOption {
type = types.attrs;
default =
if name == "default" then
{ }
else
let
rwBinds = filter (d: d.isReadOnly == false) (attrValues config.bindMounts);
roBinds = filter (d: d.isReadOnly == true) (attrValues config.bindMounts);
in
{
NetworkNamespacePath = config.netnsPath;
BindPaths = map (d: "${d.hostPath}:${d.mountPoint}:norbind") rwBinds;
BindReadOnlyPaths = map (d: "${d.hostPath}:${d.mountPoint}:norbind") roBinds;
};
readOnly = true;
description = ''
Systemd service configuration for entering the network namespace.
'';
};
};
}
)
);
};
config = {
systemd.services = mapAttrs' (
name: cfg:
let
inherit (cfg) mntnsPath bindMounts;
in
nameValuePair "netns-${name}-mntns" {
path = with pkgs; [
coreutils
util-linux
bash
];
script = ''
[ ! -e "${mntnsPath}" ] && touch ${mntnsPath}
unshare --mount=${mntnsPath} --propagation slave true
nsenter --mount=${mntnsPath} bash ${pkgs.writeShellScript "netns-${name}-mntns-bind-mount" ''
declare -A bind_mounts=(
${
concatMapStringsSep "\n" (d: ''
["${d.mountPoint}"]="${d.hostPath}:${if d.isReadOnly then "ro" else "rw"}"
'') (attrValues bindMounts)
}
)
for mount_point in "''${!bind_mounts[@]}"; do
IFS=':' read -r host_path mount_option <<< "''${bind_mounts[$mount_point]}"
if [ -f "$host_path" ]; then
[ ! -e "$mount_point" ] && touch "$mount_point"
elif [ -d "$host_path" ]; then
[ ! -e "$mount_point" ] && mkdir -p "$mount_point"
else
echo "Error: $host_path is neither a file nor a directory"
continue
fi
if [ "$mount_option" = "ro" ]; then
mount --bind --read-only "$host_path" "$mount_point"
else
mount --bind "$host_path" "$mount_point"
fi
done
''}
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
RuntimeDirectory = "${name}/mntns";
};
after = [
"netns-${name}.service"
"network.target"
];
partOf = [ "netns-${name}.service" ];
wantedBy = [
"multi-user.target"
"netns-${name}.service"
];
}
) nonDefaultNetns;
};
}

View file

@ -14,7 +14,7 @@ in
options.networking.netns = mkOption { options.networking.netns = mkOption {
type = types.attrsOf ( type = types.attrsOf (
types.submodule ( types.submodule (
{ ... }: { name, config, ... }:
{ {
options.enableDNSIsolation = mkOption { options.enableDNSIsolation = mkOption {
type = types.bool; type = types.bool;
@ -24,6 +24,15 @@ in
DNS requests in this namespace may be exposed to other namespaces. DNS requests in this namespace may be exposed to other namespaces.
''; '';
}; };
config = mkIf config.enableDNSIsolation {
bindMounts = {
"/run/nscd".hostPath = "/run/${name}/nscd";
"/etc/resolv.conf".hostPath = "/etc/netns/${name}/resolv.conf";
"/etc/nsswitch.conf".hostPath = "/etc/netns/${name}/nsswitch.conf";
"/etc/gai.conf".hostPath = "/etc/netns/${name}/gai.conf";
};
};
} }
) )
); );
@ -42,6 +51,7 @@ in
BindReadOnlyPaths = [ BindReadOnlyPaths = [
"/etc/netns/${name}/resolv.conf:/etc/resolv.conf:norbind" "/etc/netns/${name}/resolv.conf:/etc/resolv.conf:norbind"
"/etc/netns/${name}/nsswitch.conf:/etc/nsswitch.conf:norbind" "/etc/netns/${name}/nsswitch.conf:/etc/nsswitch.conf:norbind"
"/etc/netns/${name}/gai.conf:/etc/gai.conf:norbind"
]; ];
BindPaths = [ "/run/${name}/nscd:/run/nscd:norbind" ]; BindPaths = [ "/run/${name}/nscd:/run/nscd:norbind" ];
Type = "notify"; Type = "notify";
@ -53,6 +63,7 @@ in
ExecStart = "${pkgs.nsncd}/bin/nsncd"; ExecStart = "${pkgs.nsncd}/bin/nsncd";
}; };
environment.LD_LIBRARY_PATH = config.system.nssModules.path; environment.LD_LIBRARY_PATH = config.system.nssModules.path;
before = [ "netns-${name}-mntns.service" ];
after = [ after = [
"netns-${name}.service" "netns-${name}.service"
"network.target" "network.target"
@ -76,9 +87,9 @@ in
users.groups = mapAttrs' (name: _cfg: nameValuePair "${name}-nscd" { }) dnsIsolatedNetns; users.groups = mapAttrs' (name: _cfg: nameValuePair "${name}-nscd" { }) dnsIsolatedNetns;
environment.etc = listToAttrs ( environment.etc = listToAttrs (
mapAttrsToList ( flatten (
name: _cfg: mapAttrsToList (name: _cfg: [
nameValuePair "netns/${name}/resolv.conf" { (nameValuePair "netns/${name}/resolv.conf" {
source = mkDefault ( source = mkDefault (
pkgs.writeText "netns-default-resolv-conf" '' pkgs.writeText "netns-default-resolv-conf" ''
nameserver 2606:4700:4700::1111 nameserver 2606:4700:4700::1111
@ -87,11 +98,8 @@ in
nameserver 8.8.8.8 nameserver 8.8.8.8
'' ''
); );
} })
) dnsIsolatedNetns (nameValuePair "netns/${name}/nsswitch.conf" {
++ mapAttrsToList (
name: _cfg:
nameValuePair "netns/${name}/nsswitch.conf" {
source = mkDefault ( source = mkDefault (
pkgs.writeText "netns-default-nsswitch-conf" '' pkgs.writeText "netns-default-nsswitch-conf" ''
passwd: ${concatStringsSep " " config.system.nssDatabases.passwd} passwd: ${concatStringsSep " " config.system.nssDatabases.passwd}
@ -108,8 +116,25 @@ in
rpc: files rpc: files
'' ''
); );
} })
) dnsIsolatedNetns (nameValuePair "netns/${name}/gai.conf" {
source = mkDefault (
pkgs.writeText "netns-default-gai-conf" ''
label ::1/128 0
label ::/0 1
label 2002::/16 2
label ::/96 3
label ::ffff:0:0/96 4
precedence ::1/128 50
precedence ::/0 40
precedence 2002::/16 30
precedence ::/96 20
precedence ::ffff:0:0/96 10
''
);
})
]) dnsIsolatedNetns
)
); );
}; };
} }