/wg-netns

WireGuard with Linux Network Namespaces

Primary LanguagePythonMIT LicenseMIT

wg-netns

wg-quick with support for Linux network namespaces. A simple Python script that implements the steps described at wireguard.com/netns.

Setup

Requirements:

  • Python 3.7 or newer
  • ip from iproute2
  • wg from wireguard-tools
  • optional: pyyaml python package for configuration files in YAML format, otherwise only JSON is supported

Installation:

a) With pipx.

pipx install git+https://github.com/dadevel/wg-netns.git@main

b) With pip.

pip install --user git+https://github.com/dadevel/wg-netns.git@main

c) As standalone script.

curl -o ~/.local/bin/wg-netns https://raw.githubusercontent.com/dadevel/wg-netns/main/wgnetns/main.py
chmod +x ~/.local/bin/wg-netns

Usage

First, create a configuration profile. JSON and YAML file formats are supported.

Minimal JSON example:

{
  "name": "ns-example",
  "interfaces": [
    {
      "name": "wg-example",
      "address": ["10.10.10.192/32", "fc00:dead:beef::192/128"],
      "private-key": "4bvaEZHI...",
      "peers": [
        {
          "public-key": "bELgMXGt...",
          "endpoint": "vpn.example.com:51820",
          "allowed-ips": ["0.0.0.0/0", "::/0"]
        }
      ]
    }
  ]
}

Full YAML example:

# name of the network namespace where the interface is moved into, if null the default namespace is used
name: ns-example
# namespace where the interface is initialized, if null the default namespace is used
base_netns: null
# if false, the netns itself won't be created or deleted, just the interfaces inside it
managed: true
# list of dns servers, if empty dns servers from default netns will be used
dns-server: [10.10.10.1, 10.10.10.2]
# shell hooks, e.g. to set firewall rules, two formats are supported
pre-up: echo pre-up from managed netns
post-up:
- host-namespace: true
  command: echo post-up from host netns
- host-namespace: false
  command: echo post-up from managed netns
pre-down: echo pre-down from managed netns
post-down: echo post-down from managed netns
# list of wireguard interfaces inside the netns
interfaces:
  # interface name, required
- name: wg-site-a
  # list of ip addresses, at least one entry required
  address:
  - 10.10.11.172/32
  - fc00:dead:beef:1::172/128
  # can also be set via "wg set wg-site-a $key"
  private-key: nFkQQjN+...
  # optional settings
  listen-port: 51821
  fwmark: 21
  mtu: 1420
  # list of wireguard peers
  peers:
    # public key is required
  - public-key: Kx+wpJpj...
    # optional settings
    preshared-key: 5daskLoW...
    endpoint: a.example.com:51821
    persistent-keepalive: 25
    # list of ips the peer is allowed to use, at least one entry required
    allowed-ips:
    - 10.10.11.0/24
    - fc00:dead:beef:1::/64
    # by default the networks specified in 'allowed-ips' are routed over the interface, 'routes' can be used to overwrite this behaivor
    routes:
    - 10.10.11.0/24
    - fc00:dead:beef:1::/64
- name: wg-site-b
  address:
  - 10.10.12.172/32
  - fc00:dead:beef:2::172/128
  private-key: guYPuE3X...
  listen-port: 51822
  fwmark: 22
  peers:
  - public-key: NvZMoyrg...
    preshared-key: cFQuyIX/...
    endpoint: b.example.com:51822
    persistent-keepalive: 25
    allowed-ips:
    - 10.10.12.0/24
    - fc00:dead:beef:2::/64

Now it's time to setup your new network namespace and all associated wireguard interfaces.

wg-netns up ./example.yaml

Profiles stored under /etc/wireguard/ can be referenced by their name.

wg-netns up example

You can verify the success with a combination of ip and wg.

ip netns exec ns-example wg show

You can also spawn a shell inside the netns.

ip netns exec ns-example bash -i

Systemd Service

You can find a wg-quick@.service equivalent at wg-netns@.service. Place your profile in /etc/wireguard/, e.g. example.json, then start the service.

curl -o /etc/systemd/system/wg-netns@.service https://raw.githubusercontent.com/dadevel/wg-netns/main/extras/wg-netns@.service
systemctl enable --now wg-netns@example.service

If you are using SELinux, you have to change the SELinux context label, e.g. to bin_t, otherwise the service will not find the executable.

chcon -t bin_t /root/.local/bin/wg-netns

Podman Integration

A podman container can be easily attached to a network namespace created by wg-netns. The example below starts a container connected to a netns named ns-example.

podman run -it --rm --network ns:/run/netns/ns-example docker.io/library/alpine wget -q -O - https://ipinfo.io

Port Forwarding with Socat

netns-publish is a small wrapper around socat that can forward TCP traffic from outside a network namespace to a port inside a network namespace.

Example: All connections to port 1234/tcp in the main/default netns are forwarded to port 5678/tcp in the ns-example namespace.

# terminal 1, create netns and start http server inside
wg-netns up ns-example
echo 'Hello from ns-example!' > ./hello.txt
ip netns exec ns-example python3 -m http.server 5678
# terminal 2, setup port forwarding
./extras/netns-publish.sh 1234 ns-example 127.0.0.1:5678
# terminal 3, test access
curl http://127.0.0.1:1234/hello.txt

WireGuard with DynDNS

If your WireGuard server endpoint is a DynDNS domain you can use the wg-resolve script to periodically check the connectivity and re-resolve the endpoint if necessary.

Firefox in Network Namespace

Start a dedicated Firefox profile with working audio inside the netns created by wg-netns.

sudo ip netns exec ns-example sudo -u "$USER" "HOME=$HOME" "PULSE_SERVER=/run/user/$(id -u)/pulse/native" "PULSE_COOKIE=$HOME/.config/pulse/cookie" firefox -P vpn