Add Net Namespace support

This commit is contained in:
Baptiste Fouques 2022-01-19 14:46:12 +01:00
parent 1ee37b8e48
commit bf8e5c968f
1 changed files with 85 additions and 40 deletions

View File

@ -18,6 +18,7 @@ MTU=""
DNS=( ) DNS=( )
DNS_SEARCH=( ) DNS_SEARCH=( )
TABLE="" TABLE=""
NAMESPACE=""
PRE_UP=( ) PRE_UP=( )
POST_UP=( ) POST_UP=( )
PRE_DOWN=( ) PRE_DOWN=( )
@ -61,6 +62,7 @@ parse_options() {
[[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v ) [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v )
done; continue ;; done; continue ;;
Table) TABLE="$value"; continue ;; Table) TABLE="$value"; continue ;;
Namespace) NAMESPACE="$value"; continue ;;
PreUp) PRE_UP+=( "$value" ); continue ;; PreUp) PRE_UP+=( "$value" ); continue ;;
PreDown) PRE_DOWN+=( "$value" ); continue ;; PreDown) PRE_DOWN+=( "$value" ); continue ;;
PostUp) POST_UP+=( "$value" ); continue ;; PostUp) POST_UP+=( "$value" ); continue ;;
@ -85,6 +87,33 @@ auto_su() {
[[ $UID == 0 ]] || exec sudo -p "$PROGRAM must be run as root. Please enter the password for %u to continue: " -- "$BASH" -- "$SELF" "${ARGS[@]}" [[ $UID == 0 ]] || exec sudo -p "$PROGRAM must be run as root. Please enter the password for %u to continue: " -- "$BASH" -- "$SELF" "${ARGS[@]}"
} }
HAVE_SET_NAMESPACE=0
add_ns () {
[[ -z "$NAMESPACE" ]] && return 0
if ! ip netns |grep "$NAMESPACE"; then
cmd ip netns add "$NAMESPACE";
cmd ip -n "$NAMESPACE" link set dev lo up
HAVE_SET_NAMESPACE=1
fi
}
del_ns () {
[[ -z "$NAMESPACE" ]] && return 0
cmd ip netns del "$NAMESPACE"
}
option_ns () {
[[ -z "$NAMESPACE" ]] && echo "" && return 0
echo "-netns $NAMESPACE" && return 0
}
exec_ns () {
[[ -z "$NAMESPACE" ]] && echo "" && return 0
echo "ip netns exec $NAMESPACE" && return 0
}
add_if() { add_if() {
local ret local ret
if ! cmd ip link add "$INTERFACE" type wireguard; then if ! cmd ip link add "$INTERFACE" type wireguard; then
@ -100,45 +129,46 @@ del_if() {
[[ $HAVE_SET_DNS -eq 0 ]] || unset_dns [[ $HAVE_SET_DNS -eq 0 ]] || unset_dns
[[ $HAVE_SET_FIREWALL -eq 0 ]] || remove_firewall [[ $HAVE_SET_FIREWALL -eq 0 ]] || remove_firewall
if [[ -z $TABLE || $TABLE == auto ]] && get_fwmark table && [[ $(wg show "$INTERFACE" allowed-ips) =~ /0(\ |$'\n'|$) ]]; then if [[ -z $TABLE || $TABLE == auto ]] && get_fwmark table && [[ $(wg show "$INTERFACE" allowed-ips) =~ /0(\ |$'\n'|$) ]]; then
while [[ $(ip -4 rule show 2>/dev/null) == *"lookup $table"* ]]; do while [[ $(ip -4 $(op) rule show 2>/dev/null) == *"lookup $table"* ]]; do
cmd ip -4 rule delete table $table cmd ip -4 $(option_ns) rule delete table $table
done done
while [[ $(ip -4 rule show 2>/dev/null) == *"from all lookup main suppress_prefixlength 0"* ]]; do while [[ $(ip -4 $(option_ns) rule show 2>/dev/null) == *"from all lookup main suppress_prefixlength 0"* ]]; do
cmd ip -4 rule delete table main suppress_prefixlength 0 cmd ip -4 $(option_ns) rule delete table main suppress_prefixlength 0
done done
while [[ $(ip -6 rule show 2>/dev/null) == *"lookup $table"* ]]; do while [[ $(ip -6 $(option_ns) rule show 2>/dev/null) == *"lookup $table"* ]]; do
cmd ip -6 rule delete table $table cmd ip -6 $(option_ns) rule delete table $table
done done
while [[ $(ip -6 rule show 2>/dev/null) == *"from all lookup main suppress_prefixlength 0"* ]]; do while [[ $(ip -6 $(option_ns) rule show 2>/dev/null) == *"from all lookup main suppress_prefixlength 0"* ]]; do
cmd ip -6 rule delete table main suppress_prefixlength 0 cmd ip -6 $(option_ns) rule delete table main suppress_prefixlength 0
done done
fi fi
cmd ip link delete dev "$INTERFACE" cmd ip $(option_ns) link delete dev "$INTERFACE"
[[ $HAVE_SET_NAMESPACE -eq 0 ]] || del_ns
} }
add_addr() { add_addr() {
local proto=-4 local proto=-4
[[ $1 == *:* ]] && proto=-6 [[ $1 == *:* ]] && proto=-6
cmd ip $proto address add "$1" dev "$INTERFACE" cmd ip $(option_ns) $proto address add "$1" dev "$INTERFACE"
} }
set_mtu_up() { set_mtu_up() {
local mtu=0 endpoint output local mtu=0 endpoint output
if [[ -n $MTU ]]; then if [[ -n $MTU ]]; then
cmd ip link set mtu "$MTU" up dev "$INTERFACE" cmd ip $(option_ns) link set mtu "$MTU" up dev "$INTERFACE"
return return
fi fi
while read -r _ endpoint; do while read -r _ endpoint; do
[[ $endpoint =~ ^\[?([a-z0-9:.]+)\]?:[0-9]+$ ]] || continue [[ $endpoint =~ ^\[?([a-z0-9:.]+)\]?:[0-9]+$ ]] || continue
output="$(ip route get "${BASH_REMATCH[1]}" || true)" output="$(ip $(option_ns) route get "${BASH_REMATCH[1]}" || true)"
[[ ( $output =~ mtu\ ([0-9]+) || ( $output =~ dev\ ([^ ]+) && $(ip link show dev "${BASH_REMATCH[1]}") =~ mtu\ ([0-9]+) ) ) && ${BASH_REMATCH[1]} -gt $mtu ]] && mtu="${BASH_REMATCH[1]}" [[ ( $output =~ mtu\ ([0-9]+) || ( $output =~ dev\ ([^ ]+) && $(ip $(option_ns) link show dev "${BASH_REMATCH[1]}") =~ mtu\ ([0-9]+) ) ) && ${BASH_REMATCH[1]} -gt $mtu ]] && mtu="${BASH_REMATCH[1]}"
done < <(wg show "$INTERFACE" endpoints) done < <($(exec_ns) wg show "$INTERFACE" endpoints)
if [[ $mtu -eq 0 ]]; then if [[ $mtu -eq 0 ]]; then
read -r output < <(ip route show default || true) || true read -r output < <(ip $(option_ns) route show default || true) || true
[[ ( $output =~ mtu\ ([0-9]+) || ( $output =~ dev\ ([^ ]+) && $(ip link show dev "${BASH_REMATCH[1]}") =~ mtu\ ([0-9]+) ) ) && ${BASH_REMATCH[1]} -gt $mtu ]] && mtu="${BASH_REMATCH[1]}" [[ ( $output =~ mtu\ ([0-9]+) || ( $output =~ dev\ ([^ ]+) && $(ip $(option_ns) link show dev "${BASH_REMATCH[1]}") =~ mtu\ ([0-9]+) ) ) && ${BASH_REMATCH[1]} -gt $mtu ]] && mtu="${BASH_REMATCH[1]}"
fi fi
[[ $mtu -gt 0 ]] || mtu=1500 [[ $mtu -gt 0 ]] || mtu=1500
cmd ip link set mtu $(( mtu - 80 )) up dev "$INTERFACE" cmd ip $(option_ns) link set mtu $(( mtu - 80 )) up dev "$INTERFACE"
} }
resolvconf_iface_prefix() { resolvconf_iface_prefix() {
@ -153,15 +183,19 @@ resolvconf_iface_prefix() {
HAVE_SET_DNS=0 HAVE_SET_DNS=0
set_dns() { set_dns() {
[[ ${#DNS[@]} -gt 0 ]] || return 0 [[ ${#DNS[@]} -gt 0 ]] || return 0
[[ -n "$NAMESPACE" ]] && [[ -f "/etc/netns/$NAMESPACE/resolv.conf" ]] || cmd mkdir -p "/etc/netns/$NAMESPACE" && cmd touch "/etc/netns/$NAMESPACE/resolv.conf"
{ printf 'nameserver %s\n' "${DNS[@]}" { printf 'nameserver %s\n' "${DNS[@]}"
[[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}" [[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}"
} | cmd resolvconf -a "$(resolvconf_iface_prefix)$INTERFACE" -m 0 -x } | cmd $(exec_ns) resolvconf -a "$(resolvconf_iface_prefix)$INTERFACE" -m 0 -x
{ printf ' < nameserver %s\n' "${DNS[@]}"
[[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf ' < search %s\n' "${DNS_SEARCH[*]}"
} | cat
HAVE_SET_DNS=1 HAVE_SET_DNS=1
} }
unset_dns() { unset_dns() {
[[ ${#DNS[@]} -gt 0 ]] || return 0 [[ ${#DNS[@]} -gt 0 ]] || return 0
cmd resolvconf -d "$(resolvconf_iface_prefix)$INTERFACE" -f cmd $(exec_ns) resolvconf -d "$(resolvconf_iface_prefix)$INTERFACE" -f
} }
add_route() { add_route() {
@ -170,17 +204,17 @@ add_route() {
[[ $TABLE != off ]] || return 0 [[ $TABLE != off ]] || return 0
if [[ -n $TABLE && $TABLE != auto ]]; then if [[ -n $TABLE && $TABLE != auto ]]; then
cmd ip $proto route add "$1" dev "$INTERFACE" table "$TABLE" cmd ip $(option_ns) $proto route add "$1" dev "$INTERFACE" table "$TABLE"
elif [[ $1 == */0 ]]; then elif [[ $1 == */0 ]]; then
add_default "$1" add_default "$1"
else else
[[ -n $(ip $proto route show dev "$INTERFACE" match "$1" 2>/dev/null) ]] || cmd ip $proto route add "$1" dev "$INTERFACE" [[ -n $(ip $(option_ns) $proto route show dev "$INTERFACE" match "$1" 2>/dev/null) ]] || cmd ip $(option_ns) $proto route add "$1" dev "$INTERFACE"
fi fi
} }
get_fwmark() { get_fwmark() {
local fwmark local fwmark
fwmark="$(wg show "$INTERFACE" fwmark)" || return 1 fwmark="$($(exec_ns) wg show "$INTERFACE" fwmark)" || return 1
[[ -n $fwmark && $fwmark != off ]] || return 1 [[ -n $fwmark && $fwmark != off ]] || return 1
printf -v "$1" "%d" "$fwmark" printf -v "$1" "%d" "$fwmark"
return 0 return 0
@ -191,8 +225,8 @@ remove_firewall() {
local table nftcmd local table nftcmd
while read -r table; do while read -r table; do
[[ $table == *" wg-quick-$INTERFACE" ]] && printf -v nftcmd '%sdelete %s\n' "$nftcmd" "$table" [[ $table == *" wg-quick-$INTERFACE" ]] && printf -v nftcmd '%sdelete %s\n' "$nftcmd" "$table"
done < <(nft list tables 2>/dev/null) done < <($(exec_ns) nft list tables 2>/dev/null)
[[ -z $nftcmd ]] || cmd nft -f <(echo -n "$nftcmd") [[ -z $nftcmd ]] || cmd $(exec_ns) nft -f <(echo -n "$nftcmd")
fi fi
if type -p iptables >/dev/null; then if type -p iptables >/dev/null; then
local line iptables found restore local line iptables found restore
@ -202,8 +236,8 @@ remove_firewall() {
[[ $line == "*"* || $line == COMMIT || $line == "-A "*"-m comment --comment \"wg-quick(8) rule for $INTERFACE\""* ]] || continue [[ $line == "*"* || $line == COMMIT || $line == "-A "*"-m comment --comment \"wg-quick(8) rule for $INTERFACE\""* ]] || continue
[[ $line == "-A"* ]] && found=1 [[ $line == "-A"* ]] && found=1
printf -v restore '%s%s\n' "$restore" "${line/#-A/-D}" printf -v restore '%s%s\n' "$restore" "${line/#-A/-D}"
done < <($iptables-save 2>/dev/null) done < <($(exec_ns) $iptables-save 2>/dev/null)
[[ $found -ne 1 ]] || echo -n "$restore" | cmd $iptables-restore -n [[ $found -ne 1 ]] || echo -n "$restore" | cmd $(exec_ns) $iptables-restore -n
done done
fi fi
} }
@ -213,16 +247,16 @@ add_default() {
local table line local table line
if ! get_fwmark table; then if ! get_fwmark table; then
table=51820 table=51820
while [[ -n $(ip -4 route show table $table 2>/dev/null) || -n $(ip -6 route show table $table 2>/dev/null) ]]; do while [[ -n $(ip $(option_ns) -4 route show table $table 2>/dev/null) || -n $(ip $(option_ns) -6 route show table $table 2>/dev/null) ]]; do
((table++)) ((table++))
done done
cmd wg set "$INTERFACE" fwmark $table cmd $(exec_ns) wg set "$INTERFACE" fwmark $table
fi fi
local proto=-4 iptables=iptables pf=ip local proto=-4 iptables=iptables pf=ip
[[ $1 == *:* ]] && proto=-6 iptables=ip6tables pf=ip6 [[ $1 == *:* ]] && proto=-6 iptables=ip6tables pf=ip6
cmd ip $proto route add "$1" dev "$INTERFACE" table $table cmd ip $(option_ns) $proto route add "$1" dev "$INTERFACE" table $table
cmd ip $proto rule add not fwmark $table table $table cmd ip $(option_ns) $proto rule add not fwmark $table table $table
cmd ip $proto rule add table main suppress_prefixlength 0 cmd ip $(option_ns) $proto rule add table main suppress_prefixlength 0
local marker="-m comment --comment \"wg-quick(8) rule for $INTERFACE\"" restore=$'*raw\n' nftable="wg-quick-$INTERFACE" nftcmd local marker="-m comment --comment \"wg-quick(8) rule for $INTERFACE\"" restore=$'*raw\n' nftable="wg-quick-$INTERFACE" nftcmd
printf -v nftcmd '%sadd table %s %s\n' "$nftcmd" "$pf" "$nftable" printf -v nftcmd '%sadd table %s %s\n' "$nftcmd" "$pf" "$nftable"
@ -239,21 +273,24 @@ add_default() {
printf -v nftcmd '%sadd rule %s %s premangle meta l4proto udp meta mark set ct mark \n' "$nftcmd" "$pf" "$nftable" printf -v nftcmd '%sadd rule %s %s premangle meta l4proto udp meta mark set ct mark \n' "$nftcmd" "$pf" "$nftable"
[[ $proto == -4 ]] && cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1 [[ $proto == -4 ]] && cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1
if type -p nft >/dev/null; then if type -p nft >/dev/null; then
cmd nft -f <(echo -n "$nftcmd") cmd $(exec_ns) nft -f <(echo -n "$nftcmd")
else else
echo -n "$restore" | cmd $iptables-restore -n echo -n "$restore" | cmd $(exec_ns) $iptables-restore -n
fi fi
HAVE_SET_FIREWALL=1 HAVE_SET_FIREWALL=1
return 0 return 0
} }
set_config() { set_config() {
cmd wg setconf "$INTERFACE" <(echo "$WG_CONFIG") cmd wg setconf "$INTERFACE" <(echo "$WG_CONFIG")
if [[ -n "$NAMESPACE" ]]; then
cmd ip link set netns "$NAMESPACE" dev "$INTERFACE"
fi
} }
save_config() { save_config() {
local old_umask new_config current_config address cmd local old_umask new_config current_config address cmd
[[ $(ip -all -brief address show dev "$INTERFACE") =~ ^$INTERFACE\ +\ [A-Z]+\ +(.+)$ ]] || true [[ $(ip $(option_ns) -all -brief address show dev "$INTERFACE") =~ ^$INTERFACE\ +\ [A-Z]+\ +(.+)$ ]] || true
new_config=$'[Interface]\n' new_config=$'[Interface]\n'
for address in ${BASH_REMATCH[1]}; do for address in ${BASH_REMATCH[1]}; do
new_config+="Address = $address"$'\n' new_config+="Address = $address"$'\n'
@ -261,8 +298,9 @@ save_config() {
while read -r address; do while read -r address; do
[[ $address =~ ^nameserver\ ([a-zA-Z0-9_=+:%.-]+)$ ]] && new_config+="DNS = ${BASH_REMATCH[1]}"$'\n' [[ $address =~ ^nameserver\ ([a-zA-Z0-9_=+:%.-]+)$ ]] && new_config+="DNS = ${BASH_REMATCH[1]}"$'\n'
done < <(resolvconf -l "$(resolvconf_iface_prefix)$INTERFACE" 2>/dev/null || cat "/etc/resolvconf/run/interface/$(resolvconf_iface_prefix)$INTERFACE" 2>/dev/null) done < <(resolvconf -l "$(resolvconf_iface_prefix)$INTERFACE" 2>/dev/null || cat "/etc/resolvconf/run/interface/$(resolvconf_iface_prefix)$INTERFACE" 2>/dev/null)
[[ -n $MTU && $(ip link show dev "$INTERFACE") =~ mtu\ ([0-9]+) ]] && new_config+="MTU = ${BASH_REMATCH[1]}"$'\n' [[ -n $MTU && $(ip $(option_ns) link show dev "$INTERFACE") =~ mtu\ ([0-9]+) ]] && new_config+="MTU = ${BASH_REMATCH[1]}"$'\n'
[[ -n $TABLE ]] && new_config+="Table = $TABLE"$'\n' [[ -n $TABLE ]] && new_config+="Table = $TABLE"$'\n'
[[ -n "$NAMESPACE" ]] && new_config+="Namespace = $NAMESPACE"$'\n'
[[ $SAVE_CONFIG -eq 0 ]] || new_config+=$'SaveConfig = true\n' [[ $SAVE_CONFIG -eq 0 ]] || new_config+=$'SaveConfig = true\n'
for cmd in "${PRE_UP[@]}"; do for cmd in "${PRE_UP[@]}"; do
new_config+="PreUp = $cmd"$'\n' new_config+="PreUp = $cmd"$'\n'
@ -278,7 +316,7 @@ save_config() {
done done
old_umask="$(umask)" old_umask="$(umask)"
umask 077 umask 077
current_config="$(cmd wg showconf "$INTERFACE")" current_config="$(cmd $(exec_ns) wg showconf "$INTERFACE")"
trap 'rm -f "$CONFIG_FILE.tmp"; exit' INT TERM EXIT trap 'rm -f "$CONFIG_FILE.tmp"; exit' INT TERM EXIT
echo "${current_config/\[Interface\]$'\n'/$new_config}" > "$CONFIG_FILE.tmp" || die "Could not write configuration file" echo "${current_config/\[Interface\]$'\n'/$new_config}" > "$CONFIG_FILE.tmp" || die "Could not write configuration file"
sync "$CONFIG_FILE.tmp" sync "$CONFIG_FILE.tmp"
@ -313,6 +351,10 @@ cmd_usage() {
- Table: an optional routing table to which routes will be added; if - Table: an optional routing table to which routes will be added; if
unspecified or \`auto', the default table is used. If \`off', no routes unspecified or \`auto', the default table is used. If \`off', no routes
are added. are added.
- Namespace: an optional network namespace to which the interface is added;
following https://www.wireguard.com/netns/ "Ordinary Containerization", the
UDP socket lives in current namespace, then data is passed unecrypted from
targeted namespace to current namespace.
- PreUp, PostUp, PreDown, PostDown: script snippets which will be executed - PreUp, PostUp, PreDown, PostDown: script snippets which will be executed
by bash(1) at the corresponding phases of the link, most commonly used by bash(1) at the corresponding phases of the link, most commonly used
to configure DNS. The string \`%i' is expanded to INTERFACE. to configure DNS. The string \`%i' is expanded to INTERFACE.
@ -326,7 +368,9 @@ cmd_usage() {
cmd_up() { cmd_up() {
local i local i
[[ -z $(ip link show dev "$INTERFACE" 2>/dev/null) ]] || die "\`$INTERFACE' already exists" [[ -z $(ip link show dev "$INTERFACE" 2>/dev/null) ]] || die "\`$INTERFACE' already exists"
[[ -z $(ip $(option_ns) link show dev "$INTERFACE" 2>/dev/null) ]] || die "\`$INTERFACE' already exists in $NAMESPACE."
trap 'del_if; exit' INT TERM EXIT trap 'del_if; exit' INT TERM EXIT
add_ns
execute_hooks "${PRE_UP[@]}" execute_hooks "${PRE_UP[@]}"
add_if add_if
set_config set_config
@ -335,7 +379,7 @@ cmd_up() {
done done
set_mtu_up set_mtu_up
set_dns set_dns
for i in $(while read -r _ i; do for i in $i; do [[ $i =~ ^[0-9a-z:.]+/[0-9]+$ ]] && echo "$i"; done; done < <(wg show "$INTERFACE" allowed-ips) | sort -nr -k 2 -t /); do for i in $(while read -r _ i; do for i in $i; do [[ $i =~ ^[0-9a-z:.]+/[0-9]+$ ]] && echo "$i"; done; done < <($(exec_ns) wg show "$INTERFACE" allowed-ips) | sort -nr -k 2 -t /); do
add_route "$i" add_route "$i"
done done
execute_hooks "${POST_UP[@]}" execute_hooks "${POST_UP[@]}"
@ -343,17 +387,18 @@ cmd_up() {
} }
cmd_down() { cmd_down() {
[[ " $(wg show interfaces) " == *" $INTERFACE "* ]] || die "\`$INTERFACE' is not a WireGuard interface" [[ " $($(exec_ns) wg show interfaces) " == *" $INTERFACE "* ]] || die "\`$INTERFACE' is not a WireGuard interface"
execute_hooks "${PRE_DOWN[@]}" execute_hooks "${PRE_DOWN[@]}"
[[ $SAVE_CONFIG -eq 0 ]] || save_config [[ $SAVE_CONFIG -eq 0 ]] || save_config
del_if del_if
unset_dns || true unset_dns || true
remove_firewall || true remove_firewall || true
execute_hooks "${POST_DOWN[@]}" execute_hooks "${POST_DOWN[@]}"
del_ns
} }
cmd_save() { cmd_save() {
[[ " $(wg show interfaces) " == *" $INTERFACE "* ]] || die "\`$INTERFACE' is not a WireGuard interface" [[ " $($(exec_ns) wg show interfaces) " == *" $INTERFACE "* ]] || die "\`$INTERFACE' is not a WireGuard interface"
save_config save_config
} }