#!/bin/sh

set -e

# Copyright (C) 2012, 2020 Natanael Copa <ncopa@alpinelinux.org>
# Copyright (C) 2020 Ariadne Conill <ariadne@dereferenced.org>
# Copyright (C) 2020 Maximilian Wilhelm <max@sdn.clinic>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# This software is provided 'as is' and without any warranty, express or
# implied.  In no event shall the authors be liable for any damages arising
# from the use of this software.

all_ports_exist() {
	local i=
	for i in "$@"; do
		[ -d /sys/class/net/$i ] || return 1
	done
	return 0
}

wait_ports() {
	local timeout= waitports=
	[ -z "$IF_BRIDGE_WAITPORT" ] && return 0
	set -- $IF_BRIDGE_WAITPORT
	timeout="$1"
	shift
	waitports="$@"
	[ -z "$waitports" ] && waitports="$PORTS"
	while ! all_ports_exist $waitports; do
		[ "$timeout" -eq 0 ] && return 0
		timeout=$(($timeout - 1))
		sleep 1
	done
}

all_ports() {
	local i=
	for i in /sys/class/net/*/ifindex; do
		i=${i%/*}
		i=${i##*/}
		case "$i" in
		lo|$IFACE) continue;;
		*) echo $i;;
		esac
	done
}

add_ports() {
	local port=
	for port in $PORTS; do
		if [ -n "$IF_BRIDGE_HW" ]; then
			ip link set dev $port addr $IF_BRIDGE_HW
		fi
		ip link set dev $port master $IFACE && ip link set dev $port up
	done
}

del_ports() {
	local port=
	for port in $PORTS; do
		ip link set dev $port down
		ip link set dev $port nomaster
	done
}

set_bridge_opts_brctl() {
	[ -n "$IF_BRIDGE_AGEING" ] \
		&& brctl setageing $IFACE $IF_BRIDGE_AGEING
	[ -n "$IF_BRIDGE_BRIDGEPRIO" ] \
		&& brctl setbridgeprio $IFACE $IF_BRIDGE_BRIDGEPRIO
	[ -n "$IF_BRIDGE_FD" ] \
		&& brctl setfd $IFACE $IF_BRIDGE_FD
	[ -n "$IF_BRIDGE_HELLO" ] \
		&& brctl sethello $IFACE $IF_BRIDGE_HELLO
	[ -n "$IF_BRIDGE_MAXAGE" ] \
		&& brctl setmaxage $IFACE $IF_BRIDGE_MAXAGE
	[ -n "$IF_BRIDGE_PATHCOST" ] \
		&& brctl setpathcost $IFACE $IF_BRIDGE_PATHCOST
	[ -n "$IF_BRIDGE_PORTPRIO" ] \
		&& brctl setportprio $IFACE $IF_BRIDGE_PORTPRIO
	[ -n "$IF_BRIDGE_STP" ] \
		&& brctl stp $IFACE $IF_BRIDGE_STP
}

yesno() {
	case "$1" in
	yes|YES|true|TRUE|1)
		echo 1
		;;
	*)
		echo 0
		;;
	esac
}

set_bridge_opts_iproute2() {
	[ -n "$IF_BRIDGE_AGEING" ] \
		&& ip link set dev $IFACE type bridge ageing_time $IF_BRIDGE_AGEING
	[ -n "$IF_BRIDGE_BRIDGEPRIO" ] \
		&& ip link set dev $IFACE type bridge priority $IF_BRIDGE_BRIDGEPRIO
	[ -n "$IF_BRIDGE_FD" ] \
		&& ip link set dev $IFACE type bridge forward_delay $IF_BRIDGE_FD
	[ -n "$IF_BRIDGE_HELLO" ] \
		&& ip link set dev $IFACE type bridge hello_time $IF_BRIDGE_HELLO
	[ -n "$IF_BRIDGE_MAXAGE" ] \
		&& ip link set dev $IFACE type bridge max_age $IF_BRIDGE_MAXAGE
	[ -n "$IF_BRIDGE_PATHCOST" ] \
		&& bridge link set dev $IFACE cost $IF_BRIDGE_PATHCOST
	[ -n "$IF_BRIDGE_PORTPRIO" ] \
		&& bridge link set dev $IFACE priority $IF_BRIDGE_PORTPRIO
	[ -n "$IF_BRIDGE_STP" ] \
		&& ip link set dev $IFACE type bridge stp $IF_BRIDGE_STP
	[ -n "$IF_BRIDGE_VLAN_AWARE" ] \
		&& ip link set dev $IFACE type bridge vlan_filtering $(yesno $IF_BRIDGE_VLAN_AWARE)
}

set_bridge_opts() {
	[ -x /sbin/bridge ] && set_bridge_opts_iproute2 && return 0
	[ -x /sbin/brctl ] && set_bridge_opts_brctl && return 0
}

set_bridge_default_vlans() {
	[ -x /sbin/bridge ] || return 0

	[ -z "$IF_BRIDGE_PORTS" ] && return 0
	for port in $IF_BRIDGE_PORTS; do
		vids=$(ifquery -p bridge-vids $port)
		[ -z "$vids" ] && vids="$IF_BRIDGE_VIDS"

		for vid in $vids; do
			bridge vlan add vid $vid dev $port
		done
	done
}

set_bridge_access_vlans() {
	[ -z "$IF_BRIDGE_PORTS" ] || return 0
	[ -z "$IF_BRIDGE_ACCESS" ] && return 0
	[ -x /sbin/bridge ] || return 0

	if [ "$IF_BRIDGE_PVID" = "$IF_BRIDGE_ACCESS" ]; then
		PVID="pvid"
	else
		PVID=""
	fi

	bridge vlan add vid $IF_BRIDGE_ACCESS $PVID untagged dev $IFACE
}

all_ports_ready() {
	local port=
	for port in $PORTS; do
		case $(cat /sys/class/net/$IFACE/brif/$port/state) in
		""|0|3)	;; # 0 = disabled, 3 = forwarding
		[12]) 	return 1;;
		esac
	done
	return 0
}

find_maxwait() {
	awk '{printf("%.f\n", 2 * $0 / 100); }' \
		/sys/class/net/$IFACE/bridge/forward_delay
}

wait_bridge() {
	local timeout=$IF_BRIDGE_MAXWAIT
	if [ -z "$timeout" ]; then
		timeout=$(find_maxwait)
	fi
	ip link set dev $IFACE up
	while ! all_ports_ready; do
		[ $timeout -eq 0 ] && break
		timeout=$(($timeout - 1))
		sleep 1
	done
}

case "$IF_BRIDGE_PORTS" in
"")	;;
none)	PORTS="";;
all)	PORTS=$(all_ports);;
*)	PORTS="$IF_BRIDGE_PORTS";;
esac

[ -z "$PORTS" ] && ! env | grep -q "^IF_BRIDGE" && exit

case "$PHASE" in
depend)
	echo "$PORTS"
	;;
create)
	if [ ! -d "/sys/class/net/${IFACE}" ]; then
		ip link add "${IFACE}" type bridge
	fi
	;;
pre-up)
	wait_ports
	set_bridge_opts
	set_bridge_default_vlans
	set_bridge_access_vlans
	add_ports
	wait_bridge
	;;
post-down)
	del_ports
	ip link set dev $IFACE down
	;;
destroy)
	if [ -d "/sys/class/net/${IFACE}" ]; then
		ip link del "${IFACE}"
	fi
	;;
esac