From 518bf2f248e336da296d8a4c49b57e6c65104f26 Mon Sep 17 00:00:00 2001 From: David Vazgenovich Shakaryan Date: Tue, 27 Oct 2026 01:59:57 -0700 Subject: initial import --- COPYING | 19 +++++++ wg-netns-exec | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 COPYING create mode 100755 wg-netns-exec 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}" "$@" -- cgit v1.3.1