From 8c92473100863fe37fac23584260a51f2f42c945 Mon Sep 17 00:00:00 2001 From: Chris Bouchard Date: Mon, 21 Feb 2022 16:16:22 -0500 Subject: [PATCH 1/7] Add a TUNNEL_ENABLE variable This will control whether the tunnel is created (1) or not (0). --- conf/namespaced-wireguard-vpn.conf | 3 +++ systemd/namespaced-wireguard-vpn-tunnel.service | 2 ++ 2 files changed, 5 insertions(+) diff --git a/conf/namespaced-wireguard-vpn.conf b/conf/namespaced-wireguard-vpn.conf index 367f557..62e512c 100644 --- a/conf/namespaced-wireguard-vpn.conf +++ b/conf/namespaced-wireguard-vpn.conf @@ -20,6 +20,9 @@ WIREGUARD_ALLOWED_IPS=0.0.0.0/0,::0/0 # interface WIREGUARD_IP_ADDRESSES=10.0.0.1/32,fd12:3456:789a:1::1/128 +# Enable the tunnel interface +TUNNEL_ENABLE=1 + # Name of the init-facing tunnel interface TUNNEL_INIT_NAME=veth-vpn0 diff --git a/systemd/namespaced-wireguard-vpn-tunnel.service b/systemd/namespaced-wireguard-vpn-tunnel.service index d8117ff..b44dc43 100644 --- a/systemd/namespaced-wireguard-vpn-tunnel.service +++ b/systemd/namespaced-wireguard-vpn-tunnel.service @@ -10,6 +10,8 @@ RemainAfterExit=yes EnvironmentFile=/etc/namespaced-wireguard-vpn/namespaced-wireguard-vpn.conf +ExecCondition=/usr/bin/test "$TUNNEL_ENABLE" -ne 0 + ExecStart=/usr/sbin/namespaced-wireguard-vpn-tunnel up ExecStopPost=/usr/sbin/namespaced-wireguard-vpn-tunnel down From 1b4517937c4276effd5660cdb2d3cb06856ed2ba Mon Sep 17 00:00:00 2001 From: Chris Bouchard Date: Mon, 21 Feb 2022 16:49:31 -0500 Subject: [PATCH 2/7] Document new TUNNEL_ENABLE variable in the README --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 05d7f8f..9ad9823 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,10 @@ expected values are set by default, most with dummy default values. Comma-separated list of static IP addresses to assign to the WireGuard interface. As far as I know, WireGuard does not currently support DHCP or any other form of dynamic IP address assignment. +- `TUNNEL_ENABLE`: + Whether to create the tunnel (veth) network interface between the default + (init) and VPN network namespaces. Set to zero to disable or nonzero to + enable. - `TUNNEL_INIT_NAME`: Name to assign to the created tunnel (veth) network interface in the default (init) network namespace. @@ -94,6 +98,12 @@ expected values are set by default, most with dummy default values. - `TUNNEL_VPN_IP_ADDRESSES`: Comma-separated list of static IP addresses to assign to the tunnel interface in the VPN network namespace. + +#### Tunnel + +This package provides a tunnel between the init namesapce and the created VPN +namespace so, e.g., you can control services inside the VPN namespace from +outside. If you don't need or want the tunnel, just set `TUNNEL_ENABLE=0`. #### Namespace Overlay @@ -162,7 +172,6 @@ PrivateNetwork=yes ## Future Work/TODO - Consider using `sd_notify` for service scripts to provide a status. -- Provide a way to disable the tunnel if desired. - Once systemd 247 is widely available (probably when Fedora 34 is released), switch to using `LoadCredentials=` for the WireGuard private key. From 45686f59cb927057f176f8fdb0a6069dea905889 Mon Sep 17 00:00:00 2001 From: Jendrik Weise Date: Mon, 8 Aug 2022 17:57:55 +0200 Subject: [PATCH 3/7] add multi layer --- bin/namespaced-wireguard-vpn-interface | 65 +++++++++++++++++--------- bin/namespaced-wireguard-vpn-netns | 15 ++++-- bin/namespaced-wireguard-vpn-tunnel | 11 +++-- 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/bin/namespaced-wireguard-vpn-interface b/bin/namespaced-wireguard-vpn-interface index d4d310d..f641555 100755 --- a/bin/namespaced-wireguard-vpn-interface +++ b/bin/namespaced-wireguard-vpn-interface @@ -5,36 +5,55 @@ die() { exit 1 } +netns_names=($NETNS_NAME) +wg_names=($WIREGUARD_NAME) +wg_private_keys=($WIREGUARD_PRIVATE_KEY) +wg_vpn_public_keys=($WIREGUARD_VPN_PUBLIC_KEY) +wg_endpoints=($WIREGUARD_ENDPOINT) + case "$1" in up) - ip link add "$WIREGUARD_NAME" type wireguard || die - - wg set "$WIREGUARD_NAME" \ - private-key <(echo "$WIREGUARD_PRIVATE_KEY") \ - peer "$WIREGUARD_VPN_PUBLIC_KEY" \ - endpoint "$WIREGUARD_ENDPOINT" \ - allowed-ips "$WIREGUARD_ALLOWED_IPS" || die - - ip link set "$WIREGUARD_NAME" netns "$NETNS_NAME" || die - - # Addresses are comma-separated, so to split them. - tr ',' '\n' <<<"$WIREGUARD_IP_ADDRESSES" | - xargs -I '{}' \ - ip -n "$NETNS_NAME" address add '{}' dev "$WIREGUARD_NAME" || die - - ip -n "$NETNS_NAME" link set "$WIREGUARD_NAME" up || die - - # Add default routes for IPv4 and IPv6 - ip -n "$NETNS_NAME" -4 route add default dev "$WIREGUARD_NAME" || die - ip -n "$NETNS_NAME" -6 route add default dev "$WIREGUARD_NAME" || die + pre_netns="" + allowed_ips="0.0.0.0/0,::0/0" + for (( i=0; i<${#netns_names[*]}; ++i)) + do + ip $pre_netns link add "${wg_names[$i]}" type wireguard || die + + if [ $(( $i + 1 )) -eq ${#netns_names[*]} ] + then + allowed_ips="$WIREGUARD_ALLOWED_IPS" + fi + + wg set "${wg_names[$i]}" \ + private-key <(echo "${wg_private_keys[$i]}") \ + peer "${wg_vpn_public_keys[$i]}" \ + endpoint "${wg_endpoints[$i}" \ + allowed-ips "$allowed_ips" || die + + ip link set "${wg_names[$i]}" netns "${netns_names[$i]}" || die + + # Addresses are comma-separated, so to split them. + tr ',' '\n' <<<"$WIREGUARD_IP_ADDRESSES" | + xargs -I '{}' \ + ip -n "${netns_names[$i]}" address add '{}' dev "${wg_names[$i]}" || die + + ip -n "${netns_names[$i]}" link set "${wg_names[$i]}" up || die + + # Add default routes for IPv4 and IPv6 + ip -n "${netns_names[$i]}" -4 route add default dev "${wg_names[$i]}" || die + ip -n "${netns_names[$i]}" -6 route add default dev "${wg_names[$i]}" || die + pre_netns="-n ${netns_names[$i]}" + done ;; - down) # We need to delete the WireGuard interface. It's initially created in # the init network namespace, then moved to the VPN namespace. # Depending how well the "up" operation went, it might be in either. - ip -n "$NETNS_NAME" link delete "$WIREGUARD_NAME" || - ip link delete "$WIREGUARD_NAME" || die + for (( i=${#netns_names[*]}; i>=0; --i)) + do + ip -n "${netns_names[$i]}" link delete "${wg_names[$i]}" || + ip link delete "${wg_names[$i]}" || die + done ;; esac diff --git a/bin/namespaced-wireguard-vpn-netns b/bin/namespaced-wireguard-vpn-netns index 9873b03..caaef9d 100755 --- a/bin/namespaced-wireguard-vpn-netns +++ b/bin/namespaced-wireguard-vpn-netns @@ -7,7 +7,10 @@ die() { case "$1" in up) - ip netns add "$NETNS_NAME" || die + for name in $NETNS_NAME + do + ip netns add $name || die + done if [[ -n "$PRIVATE_NETNS_BIND_MOUNT" ]] then @@ -15,11 +18,17 @@ case "$1" in mount --bind "$PRIVATE_NETNS_BIND_MOUNT" "/var/run/netns/$NETNS_NAME" || die fi - ip -n "$NETNS_NAME" link set lo up || die + for name in $NETNS_NAME + do + ip -n $name link set lo up || die + done ;; down) - ip netns delete "$NETNS_NAME" || die + for name in $NETNS_NAME + do + ip netns delete $name || die + done ;; esac diff --git a/bin/namespaced-wireguard-vpn-tunnel b/bin/namespaced-wireguard-vpn-tunnel index c01b325..8692416 100755 --- a/bin/namespaced-wireguard-vpn-tunnel +++ b/bin/namespaced-wireguard-vpn-tunnel @@ -5,10 +5,13 @@ die() { exit 1 } +arr=($NETNS_NAME) +main_netns=${arr[-1]} + case "$1" in up) ip link add "$TUNNEL_INIT_NAME" type veth \ - peer "$TUNNEL_VPN_NAME" netns "$NETNS_NAME" || die + peer "$TUNNEL_VPN_NAME" netns "$main_netns" || die # Addresses are comma-separated, so to split them. tr ',' '\n' <<<"$TUNNEL_INIT_IP_ADDRESSES" | @@ -16,16 +19,16 @@ case "$1" in ip address add '{}' dev "$TUNNEL_INIT_NAME" || die tr ',' '\n' <<<"$TUNNEL_VPN_IP_ADDRESSES" | xargs -I '{}' \ - ip -n "$NETNS_NAME" address add '{}' dev "$TUNNEL_VPN_NAME" || die + ip -n "$main_netns" address add '{}' dev "$TUNNEL_VPN_NAME" || die ip link set "$TUNNEL_INIT_NAME" up || die - ip -n "$NETNS_NAME" link set "$TUNNEL_VPN_NAME" up || die + ip -n "$main_netns" link set "$TUNNEL_VPN_NAME" up || die ;; down) EXIT_CODE=0 ip link delete "$TUNNEL_INIT_NAME" || EXIT_CODE=$? - ip -n "$NETNS_NAME" link delete "$TUNNEL_VPN_NAME" || EXIT_CODE=$? + ip -n "$main_netns" link delete "$TUNNEL_VPN_NAME" || EXIT_CODE=$? [[ EXIT_CODE -eq 0 ]] || die ;; esac From 6770a8a6ffbe61c76c53247b77512c5e3501c2bd Mon Sep 17 00:00:00 2001 From: Jendrik Weise Date: Wed, 24 Aug 2022 19:00:58 +0200 Subject: [PATCH 4/7] fix bugs --- bin/namespaced-wireguard-vpn-interface | 9 ++++++--- bin/namespaced-wireguard-vpn-netns | 12 +++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bin/namespaced-wireguard-vpn-interface b/bin/namespaced-wireguard-vpn-interface index f641555..dc3873c 100755 --- a/bin/namespaced-wireguard-vpn-interface +++ b/bin/namespaced-wireguard-vpn-interface @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -o xtrace + die() { echo "${BASH_SOURCE[1]}: line ${BASH_LINENO[0]}: ${FUNCNAME[1]}: ${1:-Died}" >&2 exit 1 @@ -24,13 +26,14 @@ case "$1" in allowed_ips="$WIREGUARD_ALLOWED_IPS" fi - wg set "${wg_names[$i]}" \ + ip $pre_netns link set "${wg_names[$i]}" netns "${netns_names[$i]}" || die + + ip netns exec "${netns_names[$i]}" wg set "${wg_names[$i]}" \ private-key <(echo "${wg_private_keys[$i]}") \ peer "${wg_vpn_public_keys[$i]}" \ - endpoint "${wg_endpoints[$i}" \ + endpoint "${wg_endpoints[$i]}" \ allowed-ips "$allowed_ips" || die - ip link set "${wg_names[$i]}" netns "${netns_names[$i]}" || die # Addresses are comma-separated, so to split them. tr ',' '\n' <<<"$WIREGUARD_IP_ADDRESSES" | diff --git a/bin/namespaced-wireguard-vpn-netns b/bin/namespaced-wireguard-vpn-netns index caaef9d..6accf34 100755 --- a/bin/namespaced-wireguard-vpn-netns +++ b/bin/namespaced-wireguard-vpn-netns @@ -10,18 +10,16 @@ case "$1" in for name in $NETNS_NAME do ip netns add $name || die + + ip -n $name link set lo up || die + last=$name done if [[ -n "$PRIVATE_NETNS_BIND_MOUNT" ]] then - umount "/var/run/netns/$NETNS_NAME" || die - mount --bind "$PRIVATE_NETNS_BIND_MOUNT" "/var/run/netns/$NETNS_NAME" || die + umount "/var/run/netns/$last" || die + mount --bind "$PRIVATE_NETNS_BIND_MOUNT" "/var/run/netns/$last" || die fi - - for name in $NETNS_NAME - do - ip -n $name link set lo up || die - done ;; down) From eae70a84617b22f6b826181a563e5e2109aa37a6 Mon Sep 17 00:00:00 2001 From: Jendrik Weise Date: Wed, 31 Aug 2022 15:55:17 +0200 Subject: [PATCH 5/7] only use ipv6 if available, tune mtus --- bin/namespaced-wireguard-vpn-interface | 10 ++++++++-- conf/namespaced-wireguard-vpn.conf | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bin/namespaced-wireguard-vpn-interface b/bin/namespaced-wireguard-vpn-interface index dc3873c..8990dcf 100755 --- a/bin/namespaced-wireguard-vpn-interface +++ b/bin/namespaced-wireguard-vpn-interface @@ -17,9 +17,12 @@ case "$1" in up) pre_netns="" allowed_ips="0.0.0.0/0,::0/0" + mtu="$WIREGUARD_INITIAL_MTU" for (( i=0; i<${#netns_names[*]}; ++i)) do - ip $pre_netns link add "${wg_names[$i]}" type wireguard || die + ip $pre_netns link add "${wg_names[$i]}" mtu $mtu type wireguard || die + # reduction for worse-case scenario of IPv6 + mtu=$(($mtu - 80)) if [ $(( $i + 1 )) -eq ${#netns_names[*]} ] then @@ -44,7 +47,10 @@ case "$1" in # Add default routes for IPv4 and IPv6 ip -n "${netns_names[$i]}" -4 route add default dev "${wg_names[$i]}" || die - ip -n "${netns_names[$i]}" -6 route add default dev "${wg_names[$i]}" || die + if ip -o -6 -a | grep "${wg_names[$i]}" + then + ip -n "${netns_names[$i]}" -6 route add default dev "${wg_names[$i]}" || die + fi pre_netns="-n ${netns_names[$i]}" done ;; diff --git a/conf/namespaced-wireguard-vpn.conf b/conf/namespaced-wireguard-vpn.conf index 62e512c..3fff4b5 100644 --- a/conf/namespaced-wireguard-vpn.conf +++ b/conf/namespaced-wireguard-vpn.conf @@ -20,6 +20,12 @@ WIREGUARD_ALLOWED_IPS=0.0.0.0/0,::0/0 # interface WIREGUARD_IP_ADDRESSES=10.0.0.1/32,fd12:3456:789a:1::1/128 +# Assuming a sane VPN provider: +# IPv4: 1440 +# IPv6: 1420 +# If using PPPoE(typically DSL) -=8 +WIREGUARD_INITIAL_MTU=1420 + # Enable the tunnel interface TUNNEL_ENABLE=1 From 0a3c8537eadb23f0d15968cfe54e129607346a94 Mon Sep 17 00:00:00 2001 From: Jendrik Weise Date: Wed, 31 Aug 2022 16:52:56 +0200 Subject: [PATCH 6/7] amend documentation --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9ad9823..adb0e47 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,39 @@ Rough illustration of the intended setup. See [Routing & Network Namespace Integration][wireguard-namespace] for a more thorough explanation of how WireGuard works across network namespaces. +If multiple layered VPN connections are used, a layout such as the one below +will be created. This is useful for additional privacy, as multiple VPN providers +would need to be compromised for any information to be leaked. Assuming none of +the VPN providers is a bottleneck, each additional layer only reduces the +performance achievable by about 10% due to protocol overheads. +``` + I N T E R N E T + Λ : + | : + | : ++------------ | : ------------+ +-------------------+ +| Init NS | : | | VPN NS1 | +| -- V -- | | -------- | +| / enp0s3 \ .................. / wg-vpn1 \..................... +| \ 1.2.3.4 <--------------------> 10.0.0.1 <-------------------+: +| ------- | | -------- | |: +| | | | |: +| O---------------------O | +-------------------+ |: +| | transmission-remote | | |: +| O---------:-----------O | +-----------------------------------|:--------+ +| : | | VPN NS2 |: | +| ---------- | | ---------- ----v--- | +| / veth─init <------------------> veth-vpn \ / wg-vpn2 \ | +| \ 10.127.0.1 / .................\ 10.127.0.2 / \ 10.0.0.1 / | +| ---------- | | ---------- -------- | +| | | : : | ++-----------------------------+ | O : --------------- : O | + | | transmission-daemon | | + | O---------------------O | + | | + +---------------------------------------------+ +``` + ## Installation This package is available from the @@ -61,18 +94,23 @@ expected values are set by default, most with dummy default values. - `NETNS_NAME`: Name to assign to the created network namespace. Network namespace names are system global, so it's important that this name be unique. + For using multiple layers, use multiple names separated by spaces. - `WIREGUARD_NAME`: Name to assign to the created WireGuard network interface. The interface is created in the default (init) namespace then moved to the VPN namespace, so the interface name must be unique in both. + For using multiple layers, use multiple names separated by spaces. - `WIREGUARD_PRIVATE_KEY`: Private key assigned by the VPN provider for your WireGuard connection. _This is sensitive,_ so by default the configuration directory and file are only readable by root. + For using multiple layers, set multiple private keys separated by spaces. - `WIREGUARD_ENDPOINT`: The endpoint of the VPN provider's WireGuard server. + For using multiple layers, set multiple endpoints separated by spaces. - `WIREGUARD_VPN_PUBLIC_KEY`: The public key of the VPN provider's WireGuard peer. + For using multiple layers, set multiple public keys separated by spaces. - `WIREGUARD_ALLOWED_IPS`: Comma-separated list of IP addresses that may be contacted using the WireGuard interface. For a namespaced VPN, where the goal is to force all @@ -82,6 +120,10 @@ expected values are set by default, most with dummy default values. Comma-separated list of static IP addresses to assign to the WireGuard interface. As far as I know, WireGuard does not currently support DHCP or any other form of dynamic IP address assignment. +- `WIREGUARD_INITIAL_MTU`: + MTU of the wireguard interface. Only applies to the initial layer if using + multiple layers. Subsequent layers will have their MTUs reduced by 80 such + as to avoid fragmentation or packet loss. - `TUNNEL_ENABLE`: Whether to create the tunnel (veth) network interface between the default (init) and VPN network namespaces. Set to zero to disable or nonzero to @@ -117,8 +159,9 @@ leak information. Linux network namespaces allow you to add configuration files in `/etc/netns/$NETNS_NAME`, which will replace the existing configuration file for processes running inside the namespace. I'd recommend overriding -`nsswitch.conf` and `resolv.conf` to use your VPN provider's name servers. See -[DNS Leaks with Network Namespaces][dns-leaks-with-netns] for more detail. +`nsswitch.conf` and `resolv.conf` to use your VPN provider's name servers. +If using multiple namespaces, this must be done for each seperately. +See [DNS Leaks with Network Namespaces][dns-leaks-with-netns] for more detail. ## Running From 3bfadca65d3cf9022c9895591e0400685f363645 Mon Sep 17 00:00:00 2001 From: Jendrik Weise Date: Fri, 2 Sep 2022 22:53:36 +0200 Subject: [PATCH 7/7] apply suggestions --- bin/namespaced-wireguard-vpn-interface | 2 +- bin/namespaced-wireguard-vpn-tunnel | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/namespaced-wireguard-vpn-interface b/bin/namespaced-wireguard-vpn-interface index 8990dcf..1dfbf9e 100755 --- a/bin/namespaced-wireguard-vpn-interface +++ b/bin/namespaced-wireguard-vpn-interface @@ -58,7 +58,7 @@ case "$1" in # We need to delete the WireGuard interface. It's initially created in # the init network namespace, then moved to the VPN namespace. # Depending how well the "up" operation went, it might be in either. - for (( i=${#netns_names[*]}; i>=0; --i)) + for (( i=$((${#netns_names[*]} - 1)); i>=0; --i)) do ip -n "${netns_names[$i]}" link delete "${wg_names[$i]}" || ip link delete "${wg_names[$i]}" || die diff --git a/bin/namespaced-wireguard-vpn-tunnel b/bin/namespaced-wireguard-vpn-tunnel index 8692416..0b4b029 100755 --- a/bin/namespaced-wireguard-vpn-tunnel +++ b/bin/namespaced-wireguard-vpn-tunnel @@ -5,8 +5,8 @@ die() { exit 1 } -arr=($NETNS_NAME) -main_netns=${arr[-1]} +netns_names=($NETNS_NAME) +main_netns=${netns_names[-1]} case "$1" in up)