diff --git a/README.md b/README.md index 05d7f8f..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,14 @@ 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 + enable. - `TUNNEL_INIT_NAME`: Name to assign to the created tunnel (veth) network interface in the default (init) network namespace. @@ -94,6 +140,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 @@ -107,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 @@ -162,7 +215,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. diff --git a/bin/namespaced-wireguard-vpn-interface b/bin/namespaced-wireguard-vpn-interface index d4d310d..1dfbf9e 100755 --- a/bin/namespaced-wireguard-vpn-interface +++ b/bin/namespaced-wireguard-vpn-interface @@ -1,40 +1,68 @@ #!/usr/bin/env bash +set -o xtrace + die() { echo "${BASH_SOURCE[1]}: line ${BASH_LINENO[0]}: ${FUNCNAME[1]}: ${1:-Died}" >&2 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 + 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]}" mtu $mtu type wireguard || die + # reduction for worse-case scenario of IPv6 + mtu=$(($mtu - 80)) - wg set "$WIREGUARD_NAME" \ - private-key <(echo "$WIREGUARD_PRIVATE_KEY") \ - peer "$WIREGUARD_VPN_PUBLIC_KEY" \ - endpoint "$WIREGUARD_ENDPOINT" \ - allowed-ips "$WIREGUARD_ALLOWED_IPS" || die + if [ $(( $i + 1 )) -eq ${#netns_names[*]} ] + then + allowed_ips="$WIREGUARD_ALLOWED_IPS" + fi - ip link set "$WIREGUARD_NAME" netns "$NETNS_NAME" || die + ip $pre_netns 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_NAME" address add '{}' dev "$WIREGUARD_NAME" || 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]}" \ + allowed-ips "$allowed_ips" || 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 - ;; + # 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 + 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 + ;; 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[*]} - 1)); 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..6accf34 100755 --- a/bin/namespaced-wireguard-vpn-netns +++ b/bin/namespaced-wireguard-vpn-netns @@ -7,19 +7,26 @@ die() { case "$1" in up) - ip netns add "$NETNS_NAME" || die + 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 - - ip -n "$NETNS_NAME" link set lo up || die ;; 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..0b4b029 100755 --- a/bin/namespaced-wireguard-vpn-tunnel +++ b/bin/namespaced-wireguard-vpn-tunnel @@ -5,10 +5,13 @@ die() { exit 1 } +netns_names=($NETNS_NAME) +main_netns=${netns_names[-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 diff --git a/conf/namespaced-wireguard-vpn.conf b/conf/namespaced-wireguard-vpn.conf index 367f557..3fff4b5 100644 --- a/conf/namespaced-wireguard-vpn.conf +++ b/conf/namespaced-wireguard-vpn.conf @@ -20,6 +20,15 @@ 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 + # 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