summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2026-10-27 01:59:57 -0700
committerDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2026-10-27 01:59:57 -0700
commit518bf2f248e336da296d8a4c49b57e6c65104f26 (patch)
treefca221e42c56ae4c4821e72e4d25296a5e2396fb
downloadwg-netns-exec-518bf2f248e336da296d8a4c49b57e6c65104f26.tar.gz
wg-netns-exec-518bf2f248e336da296d8a4c49b57e6c65104f26.tar.xz
initial import
-rw-r--r--COPYING19
-rwxr-xr-xwg-netns-exec167
2 files changed, 186 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..cb0c729
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,19 @@
+Copyright 2026 David Vazgenovich Shakaryan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/wg-netns-exec b/wg-netns-exec
new file mode 100755
index 0000000..c8e8d94
--- /dev/null
+++ b/wg-netns-exec
@@ -0,0 +1,167 @@
+#!/usr/bin/env bash
+#
+# Copyright 2026 David Vazgenovich Shakaryan
+
+die() {
+ printf '%s\n' "${0##*/}: $*" >&2
+ exit 1
+}
+
+[[ -n "${SUDO_USER}" ]] || die 'SUDO_USER is not set'
+
+run() {
+ if [[ -t 1 ]]; then
+ printf '\e[90m[*] %s\e[0m\n' "$*"
+ else
+ printf '[*] %s\n' "$*"
+ fi
+
+ "$@"
+}
+
+wg_conf=
+netns=
+enable_tap=
+tap_addrs=()
+dnat_rules=()
+while (($#)); do
+ case "$1" in
+ -c|--config)
+ wg_conf="${2}"
+ shift 2 ;;
+ -n|--netns)
+ netns="${2}"
+ shift 2 ;;
+ --tap)
+ enable_tap=1
+ shift ;;
+ --tap-addr)
+ tap_addrs+=("${2}")
+ shift 2 ;;
+ --dnat)
+ dnat_rules+=("${2}")
+ shift 2 ;;
+ --)
+ shift
+ break ;;
+ -*)
+ die "invalid option: ${1}" ;;
+ *)
+ break ;;
+ esac
+done
+
+: ${wg_conf:=default}
+[[ "${wg_conf}" =~ ^[A-Za-z0-9+.=_-]{1,15}$ ]] &&
+ wg_conf="/etc/wg-netns-exec/${wg_conf}.conf"
+[[ -e "${wg_conf}" ]] || die "config file does not exist: ${wg_conf}"
+echo "config: $wg_conf"
+: ${netns:=$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c 8)}
+wg_if="wg-${netns}"
+
+((!${#tap_addrs[@]})) && tap_addrs=(10.0.0.1/32 fd00::1/64)
+def_daddr=
+def_daddr6=
+for addr in "${tap_addrs[@]}"; do
+ addr="${addr%%/*}"
+ if [[ "${addr}" == *:* ]]; then
+ [[ -z "${def_daddr6}" ]] && def_daddr6="${addr%:1}:2" &&
+ [[ -n "${def_daddr}" ]] && break
+ else
+ [[ -z "${def_daddr}" ]] && def_daddr="${addr%.1}.2" &&
+ [[ -n "${def_daddr6}" ]] && break
+ fi
+done
+
+run ip netns del "${netns}" 2>/dev/null
+run ip netns add "${netns}"
+trap 'run ip netns del "${netns}"' EXIT
+
+run ip link add "${wg_if}" type wireguard
+run ip link set "${wg_if}" netns "${netns}"
+for i in $(awk '
+ /^\[Interface\]$/ { m = 1; next } /^\[/ { m = 0 }
+ m && sub(/^Address\s*=\s*/, "") {
+ gsub(/,\s*/, "\n"); print }
+ ' "${wg_conf}"); do
+ run ip -n "${netns}" addr add "${i}" dev "${wg_if}"
+done
+run ip netns exec "${netns}" wg setconf "${wg_if}" <(
+ wg-quick strip "${wg_conf}")
+run ip -n "${netns}" link set "${wg_if}" up
+run ip -n "${netns}" -4 route add default dev "${wg_if}"
+run ip -n "${netns}" -6 route add default dev "${wg_if}"
+run ip netns exec "${netns}" curl -s4 -m 10 ifconfig.co
+run ip netns exec "${netns}" curl -s6 -m 10 ifconfig.co
+
+if ((enable_tap)); then
+ run ip netns exec "${netns}" ip tuntap add dev tap0 mode tap user \
+ "${SUDO_USER}"
+ run ip netns exec "${netns}" ip link set tap0 up
+ for addr in "${tap_addrs[@]}"; do
+ run ip netns exec "${netns}" ip addr add "${addr}" dev tap0
+ done
+
+ run ip netns exec "${netns}" sysctl -wq net.ipv4.conf.all.forwarding=1
+ run ip netns exec "${netns}" sysctl -wq net.ipv6.conf.all.forwarding=1
+
+ pre=
+ if ((${#dnat_rules[@]})); then
+ pre='chain prerouting {
+ type nat hook prerouting priority dstnat
+ policy accept'
+ for saddr in "${dnat_rules[@]}"; do
+ sport= proto= daddr= dport=
+ if [[ "${saddr}" =~ ^(.*)\ +to\ +(.*) ]]; then
+ saddr="${BASH_REMATCH[1]}"
+ daddr="${BASH_REMATCH[2]}"
+ fi
+
+ ip_re='(([^:]*\[[^]]*\]([^:]*\[[^]]*\])*)?[^:]*)'
+ [[ "${saddr}" =~ ^${ip_re}:?([^/]*)/?(.*) ]]
+ saddr="${BASH_REMATCH[1]}"
+ sport="${BASH_REMATCH[4]}"
+ proto="${BASH_REMATCH[5]:-tcp}"
+
+ if [[ -n "${daddr}" ]]; then
+ [[ "${daddr}" =~ ^${ip_re}:?(.*) ]]
+ daddr="${BASH_REMATCH[1]}"
+ dport="${BASH_REMATCH[4]}"
+ fi
+
+ [[ "${saddr}" == *:* || "${daddr}" == *:* ]] &&
+ ip6=1 || ip6=
+
+ r="iifname ${wg_if}"
+ [[ -n "${saddr}" ]] && r+=" ip${ip6:+6} saddr ${saddr}"
+ [[ -n "${sport}" ]] &&
+ r+=" meta l4proto {${proto}} th dport ${sport}"
+ dport="${dport:+:${dport}}"
+
+ ((!ip6)) &&
+ d="${daddr:-${def_daddr}}" &&
+ [[ -n "${d}" ]] &&
+ pre+=$'\n'"${r} dnat ip to ${d}${dport}"
+
+ ((ip6)) || [[ -z "${saddr}" && -z "${daddr}" ]] &&
+ d="${daddr:-${def_daddr6}}" &&
+ [[ -n "${d}" ]] &&
+ pre+=$'\n'"${r} dnat ip6 to ${d}${dport}"
+ done
+ pre+=$'\n}'
+ fi
+ run ip netns exec "${netns}" nft -f - <<-EOF
+ table inet nat {
+ ${pre}
+ chain postrouting {
+ type nat hook postrouting priority srcnat
+ policy accept
+ oifname ${wg_if} masquerade
+ }
+ }
+ EOF
+ run ip netns exec "${netns}" nft list ruleset
+fi
+
+(($#)) || set -- sleep infinity
+run ip netns exec "${netns}" sudo -u "${SUDO_USER}" "$@"