#!/bin/sh -e

# Dynamic Multipoint VPN setup script for Alpine Linux
# Copyright (c) 2017-2020 Kaarle Ritvanen
# See LICENSE file for license details


. /lib/libalpine.sh

if [ -z "$1" ]; then
	echo "Usage: $0 <pfx_file>" >&2
	exit 1
fi


ATTRS=$(/usr/libexec/dmvpn-pfx-decode "$1")
eval $ATTRS

for attr in GRE_IPV4_ADDRESS HUBS VPNC_TYPE; do
	eval "[ \"\$$attr\" ]" || die "attribute not defined: $attr"
done


NFLOG_GROUP=
if [ $VPNC_TYPE = hub ]; then
	CONF_FILE=/etc/dmvpn.conf
	[ -e $CONF_FILE ] && . $CONF_FILE

	get_param() {
		eval "[ \"\$$1\" ]" && return
		resp=
		while ! expr "$resp" : '[1-9][0-9]\?[0-9]\?$' > /dev/null; do
			ask "$2" $3
		done
		eval $1=$resp
		echo $1=$resp >> $CONF_FILE
	}

	get_param NFLOG_GROUP "NFLOG group" 1
	get_param SITE_PREFIX_LEN_IPV4 "DMVPN site IPv4 prefix length" 16
	[ "$GRE_IPV6_ADDRESS" ] && \
		get_param SITE_PREFIX_LEN_IPV6 "DMVPN site IPv6 prefix length" \
			48
fi


GRE_MODULE=nf_conntrack_proto_gre
PMTU_SYSCTL=net.ipv4.ip_forward_use_pmtu


get_dev() {
	sed -E "s/^$* (.+ )?dev ([^ ]+)( .+)?\$/\\2/;ta;d;:a"
}

get_local_dev() {
	ip route list table local | get_dev local $1
}

is_active() {
	rc-service $1 status > /dev/null
}

enable_service() {
	if is_active $1; then
		rc-service $1 restart
	else
		rc-update add $1
		rc-service $1 start
	fi
}

enable_firewall() {
	augtool -s <<EOF
set /files/etc/conf.d/$1/IPFORWARD yes
set /files/etc/conf.d/$1/SAVE_ON_STOP no
EOF
	enable_service $1
}

get_config_cmds() {
	local p=$1
	shift
	while [ $# -gt 0 ]; do
		[ "$1" ] && echo $p $1
		shift
	done
}

get_nhrp_config() {
	(
		IFS=$'\n'
		get_config_cmds "$1 nhrp" \
			"network-id 1" \
			shortcut \
			"registration no-unique" \
			"${NFLOG_GROUP:+redirect}" \
			$(get_config_cmds "nhs dynamic nbma" $HUBS)
	)
}

get_route_map_config() {
	cat <<EOF
	route-map RTT-$1 permit 10
		set metric $2rtt
	exit
EOF
}

get_peer_config() {
	local group=$1
	local map=RTT-$2
	shift 2
	get_config_cmds "neighbor $group" \
		peer-group \
		"ebgp-multihop 1" \
		disable-connected-check \
		"timers 10 30" \
		"next-hop-self all" \
		"soft-reconfiguration inbound" \
		"route-map $map in" \
		"$@"
}

get_spoke_config() {
	local group=spoke-$1
	shift
	get_peer_config $group SET "$@" \
		passive \
		"advertisement-interval 1" \
		"prefix-list no-hosts out"
}

get_quagga_config() {
	cat <<EOF
	configure terminal
		nhrp event socket /var/run/nhrp-events.sock
		${NFLOG_GROUP:+nhrp nflog-group $NFLOG_GROUP}
		interface $GRE_IFACE
			tunnel protection vici profile dmvpn
EOF
	get_nhrp_config ip
	[ "$GRE_IPV6_ADDRESS" ] && get_nhrp_config ipv6
	cat <<EOF
		exit
		no router bgp $AS_NUMBER
		router bgp $AS_NUMBER
			bgp router-id $GRE_IPV4_ADDRESS
EOF
	get_config_cmds network $IPV4_PREFIXES
	if [ "$IPV6_PREFIXES" ]; then
		echo address-family ipv6
		get_config_cmds network $IPV6_PREFIXES
		echo exit
	fi
	echo exit
	if [ $VPNC_TYPE = hub ]; then
		get_route_map_config ADD +
		get_route_map_config SET
		cat <<EOF
		no ip prefix-list dmvpn
		no ip prefix-list no-hosts
EOF
		for p in $IPV4_PREFIXES; do
			echo "ip prefix-list dmvpn permit $p le 32"
			echo "ip prefix-list no-hosts permit $p le 30"
		done
		cat <<EOF
		router bgp $AS_NUMBER
			redistribute nhrp
			redistribute kernel
EOF
		get_peer_config hubs ADD \
			"remote-as $AS_NUMBER" \
			"timers connect 10" \
			"prefix-list dmvpn out"
		get_spoke_config ebgp "attribute-unchanged med"
		get_spoke_config ibgp \
			"remote-as $AS_NUMBER" \
			route-reflector-client
		echo exit
	fi
	cat <<EOF
	exit
	write memory
EOF
}


GRE_IFACE=$(get_local_dev $GRE_IPV4_ADDRESS)

if [ -z "$GRE_IFACE" ]; then
	ask_which interface "shall be used for GRE transport" \
		"$(ls /sys/class/net)" \
		$(for h in $HUBS; do
			if expr ${h##*.} : '[a-zA-Z][a-zA-Z0-9]*$' \
				> /dev/null; then

				host $h | sed -E 's/^.+ has address //;ta;d;:a'
			else
				echo $h
			fi
		done | while read addr; do

			if [ -z "$(get_local_dev $addr)" ]; then
				ip route get $addr | get_dev $addr
				break
			fi
		done)
	TRANSPORT_IFACE=$resp

	i=1
	while [ -d /sys/class/net/gre$i ]; do
		: $(( i++ ))
	done

	ask "GRE tunnel interface" gre$i
	GRE_IFACE=$resp

	echo "$PMTU_SYSCTL = 1" > /etc/sysctl.d/dmvpn.conf
	sysctl -w $PMTU_SYSCTL=1

	cat >> /etc/network/interfaces <<EOF

auto $GRE_IFACE
iface $GRE_IFACE inet static
	address $GRE_IPV4_ADDRESS
	netmask 255.255.255.255
	tunnel-mode gre
	tunnel-dev $TRANSPORT_IFACE
	tunnel-ttl 64
EOF

	if [ "$GRE_IPV6_ADDRESS" ]; then
		cat >> /etc/network/interfaces <<EOF
iface $GRE_IFACE inet6 static
	address $GRE_IPV6_ADDRESS
	netmask 128
EOF
	fi

	ifup $GRE_IFACE
fi


augtool -s "set /files/etc/conf.d/nhrpd/rc_need '\"charon nhrp-events\"'"

for serv in charon zebra; do
	is_active $serv && rc-service $serv stop
done

for serv in bgpd nhrpd zebra; do
	file=/etc/quagga/$serv.conf
	touch $file
	chown quagga $file
done

enable_service nhrpd

vtysh -c "$(get_quagga_config)"


if [ -f /etc/iptables/awall-save -o "$NFLOG_GROUP" ]; then
	apk add awall
	echo "{ \"variable\": { \"dmvpn_gre_iface\": \"$GRE_IFACE\" } }" > \
		/etc/awall/dmvpn-config.json

	if [ "$NFLOG_GROUP" ]; then
		cat > /etc/nhrp-events.conf <<EOF
max-prefix-length:
  ip: $SITE_PREFIX_LEN_IPV4
EOF
		[ "$SITE_PREFIX_LEN_IPV6" ] && \
			cat >> /etc/nhrp-events.conf <<EOF
  ipv6: $SITE_PREFIX_LEN_IPV6
EOF

		(
			PREFIX="set /files/etc/awall/dmvpn-config.json/dict/entry/dict/entry"
			cat <<EOF
$PREFIX[2] dmvpn_nflog_group
$PREFIX[2]/number $NFLOG_GROUP
$PREFIX[3] dmvpn_site_mask
$PREFIX[3]/dict/entry inet
$PREFIX[3]/dict/entry/number $SITE_PREFIX_LEN_IPV4
EOF
			[ "$SITE_PREFIX_LEN_IPV6" ] && cat <<EOF
$PREFIX[3]/dict/entry[2] inet6
$PREFIX[3]/dict/entry[2]/number $SITE_PREFIX_LEN_IPV6
EOF
		) | augtool -s
		awall enable dmvpn-hub
	else
		awall enable dmvpn
	fi

	awall translate

	if modprobe $GRE_MODULE 2>/dev/null; then
		echo $GRE_MODULE > /etc/modules-load.d/dmvpn.conf
	fi

	enable_firewall iptables
	[ -f /etc/iptables/rules6-save -o "$SITE_PREFIX_LEN_IPV6" ] && \
		enable_firewall ip6tables
fi
