From e3ed95962087e75b2d3d310c75bba7749f8cab3b Mon Sep 17 00:00:00 2001 From: htngr <124245785+htngr@users.noreply.github.com> Date: Thu, 6 Mar 2025 14:08:23 +0100 Subject: [PATCH] feat: add netns on WSL --- codchi/src/config/codchi.rs | 10 +++++ codchi/src/platform/machine.rs | 16 ++++++- codchi/src/platform/windows/mod.rs | 9 +++- codchi/src/tray/mod.rs | 5 +++ nix/container/consts.nix | 6 ++- nix/container/machine/default.nix | 7 ++- nix/container/machine/wsl.nix | 70 ++++++++++++++++++++++++++++++ nix/container/store/default.nix | 2 + nix/container/store/wsl.nix | 21 +++++++++ 9 files changed, 141 insertions(+), 5 deletions(-) diff --git a/codchi/src/config/codchi.rs b/codchi/src/config/codchi.rs index 73deccd3..e975bea4 100644 --- a/codchi/src/config/codchi.rs +++ b/codchi/src/config/codchi.rs @@ -20,6 +20,11 @@ pub struct CodchiConfig { #[serde(default)] pub enable_wsl_vpnkit: bool, + /// Isolate the network of each code machine + #[cfg(target_os = "windows")] + #[serde(default = "def_true")] + pub enable_wsl_netns: bool, + // $XDG_DATA_HOME/codchi by default pub data_dir: Option, } @@ -104,6 +109,11 @@ impl ConfigMut { self.doc["enable_wsl_vpnkit"] = value(enable); } + #[cfg(target_os = "windows")] + pub fn enable_wsl_netns(&mut self, enable: bool) { + self.doc["enable_wsl_netns"] = value(enable); + } + #[cfg(target_os = "windows")] pub fn vcxsrv_enable(&mut self, enable: bool) { self.doc["vcxsrv"]["enable"] = value(enable); diff --git a/codchi/src/platform/machine.rs b/codchi/src/platform/machine.rs index 1faa6b13..bf84fa74 100644 --- a/codchi/src/platform/machine.rs +++ b/codchi/src/platform/machine.rs @@ -1,7 +1,7 @@ use super::{platform::HostImpl, Host, LinuxCommandBuilder, LinuxCommandTarget, LinuxUser}; use crate::{ cli::{CODCHI_DRIVER_MODULE, DEBUG}, - config::{ConfigResult, FlakeLocation, MachineConfig}, + config::{CodchiConfig, ConfigResult, FlakeLocation, MachineConfig}, consts::{self, host, ToPath}, logging::{hide_progress, log_progress, set_progress_status}, platform::{self, CommandExt, Driver, Store}, @@ -277,6 +277,18 @@ fi if *DEBUG { "1" } else { "" }.to_string(), ); env.insert("MACHINE_NAME".to_string(), self.config.name.clone()); + #[cfg(target_os = "windows")] + { + env.insert( + "ENABLE_NETNS".to_string(), + if CodchiConfig::get().enable_wsl_netns { + "1" + } else { + "" + } + .to_string(), + ); + } let mut env_file = File::options() .write(true) @@ -346,7 +358,7 @@ if [ ! -e system ]; then ndd $NIX_VERBOSITY profile install --option warn-dirty false --profile system \ '.#nixosConfigurations.default.config.system.build.toplevel' else - ndd $NIX_VERBOSITY profile upgrade --option warn-dirty false --profile system '.*' + ndd $NIX_VERBOSITY profile upgrade --option warn-dirty false --profile system --all fi pwd git add flake.* diff --git a/codchi/src/platform/windows/mod.rs b/codchi/src/platform/windows/mod.rs index 55da34a9..c842e7b3 100644 --- a/codchi/src/platform/windows/mod.rs +++ b/codchi/src/platform/windows/mod.rs @@ -249,13 +249,19 @@ impl MachineDriver for Machine { fn start(&self) -> Result<()> { { + let cfg = CodchiConfig::get(); let mut env = self.config.secrets.clone(); + // TODO consolidate with Machine::write_env_file env.insert( "DEBUG".to_string(), if *DEBUG { "1" } else { "" }.to_string(), ); env.insert("MACHINE_NAME".to_string(), self.config.name.clone()); + env.insert( + "ENABLE_NETNS".to_string(), + if cfg.enable_wsl_netns { "1" } else { "" }.to_string(), + ); // machine must run to write env file into it... let env_path = machine::CODCHI_ENV_TMP.to_host_path(&machine_name(&self.config.name)); @@ -564,6 +570,7 @@ impl LinuxCommandTarget for LinuxCommandDriver { cwd: &Option, _env: &HashMap, ) -> std::process::Command { + let cfg = CodchiConfig::get(); let mut cmd = wsl_command(); cmd.args(["-d", &self.instance_name]); cmd.args(["--cd", &cwd.clone().map(|p| p.0).unwrap_or("/".to_string())]); @@ -585,7 +592,7 @@ impl LinuxCommandTarget for LinuxCommandDriver { wslenv.push( "CODCHI_DEBUG:CODCHI_MACHINE_NAME:CODCHI_IS_STORE:WSL_CODCHI_DIR_CONFIG/up:WSL_CODCHI_DIR_DATA/up", ); - if CodchiConfig::get().vcxsrv.enable { + if cfg.vcxsrv.enable { cmd.env("CODCHI_WSL_USE_VCXSRV", "1"); wslenv.push(":CODCHI_WSL_USE_VCXSRV"); } diff --git a/codchi/src/tray/mod.rs b/codchi/src/tray/mod.rs index c1ec77ac..4f6c08a3 100644 --- a/codchi/src/tray/mod.rs +++ b/codchi/src/tray/mod.rs @@ -213,6 +213,11 @@ pub fn run() -> Result<()> { }, ], }); + settings.push(mk_checkbox( + "Isolate the network of each code machine (requires restarting machines)", + |config| config.enable_wsl_netns, + |cfg, enabled| cfg.enable_wsl_vpnkit(enabled), + )); settings.push(mk_checkbox( "Enable wsl-vpnkit", |config| config.enable_wsl_vpnkit, diff --git a/nix/container/consts.nix b/nix/container/consts.nix index dc7ff2ad..730ae436 100644 --- a/nix/container/consts.nix +++ b/nix/container/consts.nix @@ -1,7 +1,7 @@ { consts, ... }: { _module.args.consts = { - store = { + store = rec { DIR_CONFIG = "/config"; DIR_CONFIG_STORE = "${consts.store.DIR_CONFIG}/store"; PROFILE_STORE = "${consts.store.DIR_CONFIG_STORE}/profile"; @@ -17,11 +17,15 @@ # WSL tricks DIR_MACHINE_DATA = "/machine-data"; DIR_MACHINE_DATA_MACHINE = "${consts.store.DIR_MACHINE_DATA}/machine/$CODCHI_MACHINE_NAME"; + + NETNS_SUBNET_BASE = "10.6.3"; + NETNS_BRIDGE_ADDR = "${NETNS_SUBNET_BASE}.1"; }; machine = { USER = "codchi"; INIT_ENV = "/etc/codchi-env"; }; + }; diff --git a/nix/container/machine/default.nix b/nix/container/machine/default.nix index 92741eab..5b386ad9 100644 --- a/nix/container/machine/default.nix +++ b/nix/container/machine/default.nix @@ -51,6 +51,11 @@ in '' set -euo pipefail + # allows ip netns exec in WSL + exec_systemd() { + exec "$@" + } + # Use config.system.binPackages and PATH from host export LANG="C.UTF-8" HOME=/root PATH="/bin:$PATH" ${cfg.init.hostSetup} @@ -81,7 +86,7 @@ in # TODO ?? Reset the logging file descriptors. echo "starting systemd..." - exec /run/current-system/systemd/lib/systemd/systemd "--log-target=kmsg" "$@" + exec_systemd /run/current-system/systemd/lib/systemd/systemd "--log-target=kmsg" "$@" ''; }; diff --git a/nix/container/machine/wsl.nix b/nix/container/machine/wsl.nix index a0898951..53b04eed 100644 --- a/nix/container/machine/wsl.nix +++ b/nix/container/machine/wsl.nix @@ -58,6 +58,9 @@ in systemctl is-system-running | grep -E "running|degraded" ''; + # for netns + binPackages = with pkgs.pkgsStatic; [ iproute2 fping ]; + machine.init.hostSetup = /* bash */ '' [ -d /etc ] || mkdir /etc @@ -87,6 +90,73 @@ in exec 1> >(tee -i "${INIT_LOG}" >&2) 2>&1 + setup_namespace() { + echo "Searching for unused IP..." >&2 + BR_ADDR="${consts.store.NETNS_BRIDGE_ADDR}" + VPEER_ADDR="$(fping -c 1 -i 1 -r 0 -u -t 100 -g "${consts.store.NETNS_SUBNET_BASE}.0/24" 2>/dev/null | + grep "timed out" | + grep -v "$BR_ADDR" | + head -n1 | + cut -d' ' -f1)" + + if [ -n "$VPEER_ADDR" ]; then + BR_DEV="codchibr" + + NS="cns$(echo "$VPEER_ADDR" | cut -d'.' -f4)" + VETH="veth$NS" + VPEER="vpeer$NS" + + echo "Using IP $VPEER_ADDR for namespace $NS" >&2 + + if [ ! -d /var/run/netns ]; then + mkdir -p /var/run + mount -t tmpfs -o size=10m tmpfs /var/run + mkdir /var/run/netns || true + fi + + # setup namespace + ip netns del "$NS" &>/dev/null || true + ip netns add "$NS" + + # setup veth link + ip link add "$VETH" type veth peer name "$VPEER" + ip link set "$VETH" up + + # assign veth pairs to bridge + ip link set "$VETH" master "$BR_DEV" + + # add peers to ns + ip link set "$VPEER" netns "$NS" + + # setup loopback interface + ip netns exec "$NS" ip link set lo up + + # setup peer ns interface + ip netns exec "$NS" ip link set "$VPEER" up + + # assign ip address to ns interfaces + ip netns exec "$NS" ip addr add "$VPEER_ADDR/24" dev "$VPEER" + + # add default routes for ns + ip netns exec "$NS" ip route add default via "$BR_ADDR" + + exec_systemd() { + exec ip netns exec "$NS" "$@" + } + else + echo "Couldn't find IP. Disabling network namespace" >&2 + return 1 + fi + } + + if [ -n "''${CODCHI_ENABLE_NETNS:-}" ]; then + echo "Enabling network namespaces for this machine" >&2 + setup_namespace || echo "Failed creating namespace. Continuing without network isolation..." >&2 + else + env >&2 + echo "NOT enabling network namespaces for this machine" >&2 + fi + mkMnt() { src="$1" target="$2" diff --git a/nix/container/store/default.nix b/nix/container/store/default.nix index 3525772f..19c40c49 100644 --- a/nix/container/store/default.nix +++ b/nix/container/store/default.nix @@ -72,6 +72,8 @@ in # user & groups required for minimal linux + `nix daemon` "/etc/group" = ./etc/group; "/etc/passwd" = ./etc/passwd; + "/etc/protocols" = "${pkgs.iana-etc}/etc/protocols"; + "/etc/services" = "${pkgs.iana-etc}/etc/services"; # required for dns / other information lookup systems (mainly glibc) "/etc/nsswitch.conf" = ./etc/nsswitch.conf; # nix settings diff --git a/nix/container/store/wsl.nix b/nix/container/store/wsl.nix index 728695d5..84ba6fd9 100644 --- a/nix/container/store/wsl.nix +++ b/nix/container/store/wsl.nix @@ -89,6 +89,27 @@ in runtimePackages = with pkgs; [ daemonize ]; store.init.services = lib.mkForce /* bash */ '' + setup_bridge() { + echo "Creating codchibr..." >&2 + BR_ADDR="${consts.store.NETNS_BRIDGE_ADDR}" + BR_DEV="codchibr" + + # setup bridge + ip link delete "$BR_DEV" &>/dev/null || true + ip link add "$BR_DEV" type bridge + ip link set "$BR_DEV" up + ip addr add "$BR_ADDR/24" dev "$BR_DEV" + + # enable ip forwarding + echo 1 > /proc/sys/net/ipv4/ip_forward + + # flush & apply nat rules + nix run nixpkgs#iptables -- -t nat -F + nix run nixpkgs#iptables -- -t nat -A POSTROUTING -s "$BR_ADDR/24" ! -o "$BR_DEV" -j MASQUERADE + } + + setup_bridge || echo "Failed to setup codchibr. Network namespaces will be disabled." >&2 + daemonize $(which nix) daemon ''; };