#!/bin/bash
# Stripped down version of wg-quick that allows moving the wg device into a
# network namespace.
#
# SPDX-License-Identifier: GPL-2.0 AND GPL-2.0-or-later
#
# Copyright (C) 2020 Daniel Gröber <dxld@darkboxed.org>
# Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>.  All Rights
# Reserved.

set -eu
shopt -s extglob
export LC_ALL=C

usage () {
	echo "$0 (up | down) (INTERFACE|file.conf) TARGET_NS [SOURCE_NS]" >&2
	exit 1
}

del_if () {
	ip "$@" link delete dev "$INTERFACE"
}

COMMAND="${1:-}"; shift || usage
CONFIG_FILE="${1:-}"; shift || usage
TARGETNS=${1:-}; shift || usage
SOURCENS=${1:-}

WG_CONFIG=""
ADDRESSES=( )
MTU=""
DNS=( )
DNS_SEARCH=( )

declare interface_section=0 line key value stripped v
if [[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]]; then
	CONFIG_FILE="/etc/wireguard/$CONFIG_FILE.conf"
fi

if ! [[ -e $CONFIG_FILE ]]; then
	die "\`$CONFIG_FILE' does not exist"
fi

if ! [[ $CONFIG_FILE =~ (^|/)([a-zA-Z0-9_=+.-]{1,15})\.conf$ ]]; then
	die "The config file must be a valid interface name, followed by .conf"
fi

CONFIG_FILE="$(readlink -f "$CONFIG_FILE")"

cfg_mode=$(stat -c '0%#a' "$CONFIG_FILE")
dir_mode=$(stat -c '0%#a' "${CONFIG_FILE%/*}")
if (( (cfg_mode & dir_mode & 0007) != 0 )); then
	echo "Warning: \`$CONFIG_FILE' is world accessible" >&2
fi

INTERFACE="${BASH_REMATCH[2]}"
shopt -s nocasematch
while read -r line || [[ -n $line ]]; do
	stripped="${line%%\#*}"
	key="${stripped%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}"
	value="${stripped#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}"
	[[ $key == "["* ]] && interface_section=0
	[[ $key == "[Interface]" ]] && interface_section=1
	if [[ $interface_section -eq 1 ]]; then
		case "$key" in
			Address)
				ADDRESSES+=( ${value//,/ } )
				continue ;;
			MTU)
				MTU="$value"
				continue ;;
			DNS)
				for v in ${value//,/ }; do
					if [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]]
					then
						DNS+=( $v )
					else
						DNS_SEARCH+=( $v )
					fi
				done
				continue ;;
		esac
	fi
	WG_CONFIG+="$line"$'\n'
done < "$CONFIG_FILE"
shopt -u nocasematch

if [ "$COMMAND" = down ]; then
	del_if -netns "$TARGETNS"
	exit 0
fi

sourcens=( )
if [ -n "$SOURCENS" ]; then
        sourcens=( -netns "$SOURCENS" )
fi

trap 'del_if "${sourcens[@]}"; exit 1' INT TERM EXIT

ip "${sourcens[@]}" link add "$INTERFACE" type wireguard

if [ -n "$SOURCENS" ]; then
	ip netns exec "$SOURCENS" wg setconf "$INTERFACE" <(echo "$WG_CONFIG")
else
	wg setconf "$INTERFACE" <(echo "$WG_CONFIG")
fi

trap 'del_if -netns "$TARGETNS"; exit 1' INT TERM EXIT

# Move the wireguard interface to the target namespace.
ip "${sourcens[@]}" link set "$INTERFACE" netns "$TARGETNS"

ip -netns "$TARGETNS" link set dev lo up
ip -netns "$TARGETNS" link set dev "$INTERFACE" up

for addr in "${ADDRESSES[@]}"; do
	proto=-4
	[[ $addr != *:* ]] || proto=-6
    	ip $proto -netns "$TARGETNS" addr add dev "$INTERFACE" "$addr"
done

ip -netns "$TARGETNS" route add default dev "$INTERFACE"
ip -netns "$TARGETNS" -6 route add default dev "$INTERFACE"

trap - INT TERM EXIT
