Port forwading of a network-namespace'd container

I have a container in a network-namespace defined as:

age.identityPaths = [ "..." ];
age.secrets.protonvpn.file = ./protonvpn.age;
networking.wireguard.interfaces.protonvpn = {
  interfaceNamespace = "protonvpn";
  preSetup = "test -f /run/netns/protonvpn || ip netns add protonvpn || test -f /run/netns/protonvpn";

  privateKeyFile = config.age.secrets.protonvpn.path;
  ips = [ "10.2.0.2/32" ];

  peers = [{
    publicKey = "...";
    endpoint = "...";
    allowedIPs = [ "0.0.0.0/0" "::/0" ];
  }];
};

containers.vpn = let
  forwardPorts = [
    {
      hostPort = 31281;
      containerPort = 8888;
    } # tinyproxy
  ];
in {
  inherit forwardPorts;
  autoStart = true;
  # privateNetwork = true;
  hostAddress = "192.168.100.1";
  localAddress = "192.168.100.100";
  hostAddress6 = "fc00::1";
  localAddress6 = "fc00::2";
  timeoutStartSec = "5min";

  additionalCapabilities = [ "CAP_NET_ADMIN" ];
  extraFlags = [ "--network-namespace-path=/run/netns/protonvpn" ];
  config = { config, pkgs, lib, ... }: {
    system.stateVersion = "23.11";

    environment.systemPackages = with pkgs; [ curl ];
    networking = {
      firewall = {
        enable = true;
        allowedTCPPorts = map (x: x.containerPort) forwardPorts;
      };
      # Use systemd-resolved inside the container
      # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
      useHostResolvConf = lib.mkForce false;
    };

    services.resolved.enable = true;

    services.tinyproxy = {
      enable = true;
      settings = { ALLOWED = "0.0.0.0/0"; };
    };
  };
};

The good news is: any outbound connection inside the container is effectively going through the VPN.

The thing is, no port is open on my host:

$ sudo netstat -tulpn | grep LISTEN
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      992/sshd: /nix/stor 
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      1/systemd           
tcp6       0      0 :::22                   :::*                    LISTEN      992/sshd: /nix/stor 
tcp6       0      0 ::1:631                 :::*                    LISTEN      1027/cupsd          
tcp6       0      0 :::1716                 :::*                    LISTEN      1341/kdeconnectd 

Also, I don’t have a dedicate interface exposed to my host (although it should be done by privateNetwork, which is not compatible with --network-namespace-path):

$ sudo nixos-container root-login vpn

[root@vpn:~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
8: protonvpn: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none 
    inet 10.2.0.2/32 scope global protonvpn
       valid_lft forever preferred_lft forever

Are there any way I can access/expose tinyproxy to my localhost?

Thanks in advance for your help,

Regards.

What you’d probably want to do is to add a veth pair to the namespace.

Doing that looks like this:

ip link add veth1 type veth peer name br-vett1
ip link set veth1 netns NAMESPACE
ip -n NAMESPACE addr add 192.11.1.2/24 dev veth1
ip -n NAMESPACE link set veth1 up
ip addr add 192.11.1.1/24 dev br-veth1
ip link set br-veth1 up

  • veth1 device would be moved inside your netns namespace called NAMESPACE with address 192.11.1.2
  • veth1-br device stays in your hosts default namespace with address 192.11.1.1.

On the container you can have services listen to 192.11.1.2 and those would be accessible from the host (or you can use nginx or similar to forward traffic there if you want to expose them to other machines)

You can also use socat to punch a port from default namespace into the service’s one like so:

${pkgs.socat}/bin/socat tcp-listen:${toString <portInDefaultNamespace>},fork,reuseaddr,ignoreeof exec:'${pkgs.iproute2}/bin/ip netns exec ${<namespaceName>} socat STDIO "tcp-connect:127.0.0.1:${toString <portInsideNamespace>}"',nofork

I am running this as a systemd service that binds to the namespace producing service and is wanted by the service listening on portInsideNamespace