# Copyright (c) 2011 by Gentoo Foundation
# Released under the 2-clause BSD license.
# shellcheck shell=sh disable=SC1008

_config_vars="$_config_vars link prefix suffix ipv4mask relay"

ip6rd_depend()
{
	program ip
	after interface
}

ip6rd_pre_start()
{
	# ALL interfaces run pre_start blocks, not just those with something
	# assigned, so we must check if we need to run on this interface before we
	# do so.
	local config
	eval config=\$config_${IFVAR}
	[ "$config" = "ip6rd" ] || return 0

	case "${MODULES}" in
		*" ifconfig "*)
			eerror "ifconfig is not supported for 6rd"
			eerror "Please emerge sys-apps/iproute2"
			return 1
			;;
	esac

	local host= suffix= relay= addr= iface=${IFACE} config_ip6rd= localip= ipv4mask=
	eval host=\$link_${IFVAR}
	if [ -z "${host}" ]; then
		eerror "link_${IFVAR} not set"
		return 1
	fi

	eval host=\${link_${IFVAR}}
	eval ipv4mask=\${ipv4mask_${IFVAR}:-0}
	eval suffix=\${suffix_${IFVAR}:-1}
	eval relay=\${relay_${IFVAR}}
	eval prefix=\${prefix_${IFVAR}}

	IFACE=${host}
	addrs=$(_get_inet_addresses)
	IFACE=${iface}
	if [ -z "${addrs}" ]; then
		eerror "${host} is not configured with an IPv4 address"
		return 1
	fi
	# TODO: Get this settings from DHCP (Option 212)
	if [ -z "${prefix}" ]; then
		eerror "prefix_${IFVAR} not set"
		return 1
	fi
	if [ -z "${relay}" ]; then
		eerror "relay_${IFVAR} not set"
		return 1
	fi
	for addr in ${addrs}; do
		# Strip the subnet
		local ip="${addr%/*}" subnet="${addr#*/}"
		# We don't work on private IPv4 addresses
		if _ip6rd_inet_is_private_network "${ip}"
		then
			continue
		fi

		local ip6= ip6_prefix="${prefix%::/*}" ip6_subnet="${prefix#*/}"
		ip6_subnet=$((ip6_subnet + (32-ipv4mask)))
		eval ip6="$(printf "${ip6_prefix}:%s::%s" \
		$(_ip6rd_prefix_shave_bits  ${ip} ${ipv4mask}) ${suffix})"
		veinfo "Derived IPv6 address: ${ip6}"

		# Now apply our IPv6 address to our config
		config_ip6rd="${config_ip6rd}${config_ip6rd:+ }${ip6}/${ip6_subnet}"

		if [ -n "${localip}" ]; then
			localip="any"
		else
			localip="${ip}"
		fi
	done

	if [ -z "${config_ip6rd}" ]; then
		eerror "No global IPv4 addresses found on interface ${host}"
		return 1
	fi

	ebegin "Creating 6rd tunnel ${IFACE}"
	if [ "${IFACE}" != "sit0" ]; then
		_tunnel add "${IFACE}" mode sit ttl 255 remote any local "${localip}"
	fi
	_tunnel 6rd dev "${IFACE}" 6rd-prefix "${prefix}"
	eend $? || return 1
	_up

	routes_ip6rd="2003::/3 via ::${relay} metric 2147483647"
	service_set_value "config_ip6rd_$IFVAR" "$config_ip6rd"
	service_set_value "routes_ip6rd_$IFVAR" "$routes_ip6rd"
}

ip6rd_start()
{
	local config_ip6rd=$(service_get_value "config_ip6rd_$IFVAR")
	local routes_ip6rd=$(service_get_value "routes_ip6rd_$IFVAR")

	# Now apply our config
	eval config_${config_index}=\'"${config_ip6rd}"\'
	: $(( config_index -= 1 ))

	# Add a route for us, ensuring we don't delete anything else
	local routes="$(_get_array "routes_${IFVAR}")
$routes_ip6rd"
	eval routes_${IFVAR}=\$routes
}

_ip6rd_inet_atoi()
{
	local IFS="${IFS}." ipi=0 j=3
	for i in $1 ; do
		# post-decrement isn't valid
		ipi=$(( ipi | (i << (8*j))  ))
		j=$(( j - 1 ))
	done
	echo ${ipi}
}

_ip6rd_inet_itoa()
{
	local ipi=$1 bitmask v
	bitmask=$(( (1 << 24)-1 ))
	for i in 0 1 2 3; do
		v=$(( (ipi & ~bitmask) >> 24 ))
		ipi=$(( (ipi & bitmask) << 8 ))
		if [ $i != 3 ] ; then
			printf "%d." $v
		else
			printf "%d\n" $v
		fi
	done
}

_ip6rd_inet_get_network()
{
	local a=$(_ip6rd_inet_atoi $1)
	local net=$(( a & ( (1<<$2)-1 ) ))
	local cidr=$(( 32 - $2 ))
	echo $(_ip6rd_inet_itoa $(( (net << cidr ) )) )
}

_ip6rd_inet_is_private_network()
{
	if [ "$(_ip6rd_inet_get_network $1 16)" = "192.168.0.0" ]\
	  || [ "$(_ip6rd_inet_get_network $1 8)" = "10.0.0.0" ]\
	  || [ "$(_ip6rd_inet_get_network $1 12)" = "172.16.0.0" ]\
	  || [ "$(_ip6rd_inet_get_network $1 16)" = "169.254.0.0" ]
	then
		return 0;
	fi
	return 1;
}

_ip6rd_prefix_shave_bits()
{
	local ipi=
	ipi=$((  ($(_ip6rd_inet_atoi $1) & (1<<(32-$2))-1) << $2))
	if [ $2 -le 16 ]
	then
		printf "%04x:%0$(( (16-$2>>2)+(($2%4)?1:0) ))x" \
		$((ipi >> 16)) $((ipi & (1<<(16-$2))-1))
	elif [ $2 -lt 32 ]
	then
		printf "%0$(( (32-$2>>2)+(($2%4)?1:0) ))x" \
		$((ipi >> 16))
	fi
}
