#!/bin/sh
#
# Configuration is in /etc/bridge-stp.conf
#
# `/sbin/bridge-stp <bridge> <start|stop>` is called by the kernel when STP is
# enabled/disabled on a bridge (via `brctl stp <bridge> <on|off>` or
# `ip link set <bridge> type bridge stp_state <0|1>`).  The kernel
# enables user_stp mode if that command returns 0, or enables kernel_stp mode if
# that command returns any other value.
#
# If called with the above arguments, this script determines whether MSTP should
# be used for the specified bridge (based on the "MSTP_BRIDGES" configuration
# value), starts/stops mstpd if necessary, and calls
# `mstpctl <addbridge|delbridge> <bridge>` to add/remove the bridge from mstpd
# (possibly in a background job after a delay; see the comments in the code
# below).  No further configuration is performed automatically by this script at
# that time.  Additional configuration is usually performed by
# /lib/mstpctl-utils/ifupdown.sh (which calls `ip link set <bridge> type bridge stp_state 1`
# to trigger this script to start mstpd if necessary).
#
# This script is not intended to be called with the above arguments directly
# (not via the kernel).  However, this script may be called directly as
# `mstpctl_restart_config` or `/sbin/bridge-stp restart_config` to reconfigure
# (using `/lib/mstpctl-utils/mstp_config_bridge <bridge>` or an alternative command specified using
# a "config_cmd" configuration value) all existing bridges that are using mstpd,
# or called as `mstp_restart` or `/sbin/bridge-stp restart` to restart mstpd and
# then reconfigure all bridges that are using it.
#
# To avoid kernel deadlocks, this script (and any foreground processes it runs)
# must not make any changes (using brctl, ifconfig, ip, /sys/..., etc) to the
# bridge or any associated kernel network interfaces in any code paths that are
# used when this script is called by the kernel.

# Parse arguments.
CalledAs="$(basename "$0")"
if [ "$CalledAs" = 'mstpctl_restart_config' ]; then
    action='restart_config'
elif [ $# -eq 1 ] && [ "$1" = 'restart_config' ]; then
    action='restart_config'
elif [ "$CalledAs" = 'mstp_restart' ]; then
    action='restart'
elif [ $# -eq 1 ] && [ "$1" = 'restart' ]; then
    action='restart'
elif [ $# -eq 2 ] && [ "$2" = 'start' ]; then
    action='start'
    bridge="$1"
elif [ $# -eq 2 ] && [ "$2" = 'stop' ]; then
    action='stop'
    bridge="$1"
else
    echo "Usage: $0 <bridge> {start|stop}" >&2
    echo "   or: $0 {restart|restart_config}" >&2
    exit 1
fi

# Make sure this script is being run as root.
if [ "$(id -u)" != '0' ]; then
    echo 'This script must be run as root' >&2
    exit 1
fi

# Ensure that we have a sane umask.
umask 022

# Ensure that we have a sane PATH.
PATH='/sbin:/usr/sbin:/bin:/usr/bin'
export PATH

# Define some relevant paths.

net_dir='/sys/class/net'
config_dir='/sys/kernel/debug/rtl838x'

# A space-separated list of bridges for which MSTP should be used in place of
# the kernel's STP implementation.  If empty, MSTP will be used for all bridges.
MSTP_BRIDGES=''
LOGGER='logger -t bridge-stp -s'

# Read the config.
if [ -e '/etc/bridge-stp.conf' ]; then
    . '/etc/bridge-stp.conf'
fi

errmsg () {
  if [ -n "$LOGGER" ]; then
    $LOGGER "$*" || { echo >&2 "$*"; LOGGER=; }
  else
    echo >&2 "$*"
  fi
}



# Determine whether mstpd should manage STP for the specified bridge.
# Returns 0 if mstpd should manage STP for the specified bridge, or 1 if mstpd
# should not manage STP for the specified bridge.
is_mstp_bridge()
{
    if [ -z "$MSTP_BRIDGES" ]; then
        return 0
    fi
    for b in $MSTP_BRIDGES; do
        if [ "$1" = "$b" ]; then
            return 0
        fi
    done
    return 1
}

case "$action" in
    start)
        # Make sure the specified bridge is valid.
        if [ ! -d "$net_dir/$bridge/bridge" ]; then
            errmsg "'$bridge' is not a bridge"
            exit 1
        fi

        # Determine whether the specified bridge should use MSTP.
        if ! is_mstp_bridge "$bridge"; then
            echo "Ignoring bridge '$bridge' that is not listed in \$MSTP_BRIDGES"
            exit 10
        fi



        # Add bridge
        logger -t RSTP/STP "Adding/configuring bridge '$bridge'"
        ubus call tswconfig.rstp add_bridge  "{\"bridge\":\"$bridge\"}" || exit 3
        ;;
    stop)
        # Remove bridge from mstpd.
        logger -t RSTP/STP "Removing bridge '$bridge'"
        ubus call tswconfig.rstp remove_bridge  "{\"bridge\":\"$bridge\"}" || exit 3

        # Exit if mstpd should not be stopped when it is no longer used.
        exit 0


        # Exit if any other bridges are using mstpd.
        for xbridge in $(ls "$net_dir"); do
            # Ignore this bridge
            if [ "$bridge" = "$xbridge" ]; then
                continue
            fi

            # Ignore interfaces that are not bridges.
            if [ ! -e "$net_dir/$xbridge/bridge/stp_state" ]; then
                continue
            fi

            # Ignore bridges that should not use MSTP.
            if ! is_mstp_bridge "$xbridge"; then
                continue
            fi

            # If bridge is in user_stp mode, then it is probably using MSTP.
            read State < "$net_dir/$xbridge/bridge/stp_state"
            if [ "$State" = '2' ]; then
                exit 0
            fi
        done


        ;;
    restart|restart_config)
        if [ "$action" = 'restart' ]; then
            exit 0
        fi
        # Reconfigure bridges.
        for bridge in $(ls "$net_dir"); do
            # Ignore interfaces that are not bridges.
            if [ ! -e "$net_dir/$bridge/bridge/stp_state" ]; then
                continue
            fi

            # Ignore bridges that should not use MSTP.
            if ! is_mstp_bridge "$bridge"; then
                continue
            fi

            # Remove bridges that have STP disabled.
            read State < "$net_dir/$bridge/bridge/stp_state"
            if [ "$State" = '0' ]; then
                logger -t RSTP/STP "Removing bridge '$bridge'"
                ubus call tswconfig.rstp remove_bridge "{\"bridge\":\"$bridge\"}" 
                continue
            fi

            # Skip bridges that are not in user_stp mode.
            if [ "$State" != '2' ]; then
                echo
                echo "Skipping bridge '$bridge' that is not in user_stp mode."
                echo "To reconfigure this bridge to use MSTP, run:"
                echo "ip link set '$bridge' type bridge stp_state 0 ; \\"
                echo "  ip link set '$bridge' type bridge stp_state 1"
                logger -t RSTP/STP "Skipping bridge '$bridge'"
                continue
            fi

            # Add bridge to mstpd and configure.
            logger -t RSTP/STP "Adding/configuring bridge '$bridge'"
            ubus call tswconfig.rstp add_bridge "{\"bridge\":\"$bridge\"}" 

        done
        ;;
esac
