#!/bin/sh [ -n "$VERBOSE" ] && set -x # Copyright (C) 2012, 2020 Natanael Copa # Copyright (C) 2020 Ariadne Conill # Copyright (C) 2020 Maximilian Wilhelm # # 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. ################################################################################ # Bridge management functions # ################################################################################ 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 $(yesno $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 } 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 } ################################################################################ # Bridge port management functions # ################################################################################ configure_access_port() { port="$1" vlan="$2" self="$3" # Cleans all existing VLANs (probably at least VLAN 1) bridge vlan show dev ${port} | tail -n +2 | grep -v '^$' | sed -e "s/^${port}//" | while read vid flags; do bridge vlan del vid "${vid}" dev "${port}" ${self} done bridge vlan add vid "${vlan}" pvid untagged dev "${port}" ${self} } configure_trunk_port() { port="$1" self="$2" # Working on the bridge itself? if [ "${self}" ]; then allow_untagged="${IF_BRIDGE_ALLOW_UNTAGGED}" pvid="${IF_BRIDGE_PVID}" vids="${IF_BRIDGE_VIDS}" else allow_untagged=$(ifquery -p bridge-allow-untagged ${port} 2>/dev/null || true) pvid=$(ifquery -p bridge-pvid ${port} 2>/dev/null || true) vids=$(ifquery -p bridge-vids ${port} 2>/dev/null || true) fi # If bridge-allow-untagged if set to off, remove untagged VLAN. If it's # one of our bridge-vids, it will be set again later. if [ "${allow_untagged}" -a "$(yesno ${allow_untagged})" = 0 ]; then untagged_vid=$(bridge vlan show dev ${port} | tail -n +2 | grep -v '^$' | sed -e "s/^${port}//" | awk '/Untagged/ { print $1 }') if [ "${untagged_vid}" ]; then bridge vlan del vid "${untagged_vid}" dev "${port}" ${self} fi fi # The vlan specified is to be considered a PVID at ingress. # Any untagged frames will be assigned to this VLAN. if [ "${pvid}" ]; then cur_pvid=$(bridge vlan show dev ${port} | tail -n +2 | grep -v '^$' | sed -e "s/^${port}//" | awk '/PVID/ { print $1 }') if [ "${cur_pvid}" ]; then bridge vlan del vid ${cur_pvid} dev "${port}" ${self} fi bridge vlan add vid "${pvid}" dev "${port}" pvid untagged ${self} fi # Add regular tagged VLANs for vid in ${vids}; do bridge vlan add vid $vid dev "${port}" ${self} done } # Configure VLANs on the bridge interface itself set_bridge_vlans() { # Shall the bridge interface be an untagged port? if [ "${IF_BRIDGE_ACCESS}" ]; then configure_access_port "${IFACE}" "${IF_BRIDGE_ACCESS}" "self" # Configure bridge interface as trunk port else configure_trunk_port "${IFACE}" "self" fi } # Configure VLANs on the bridge-ports set_bridge_port_vlans() { for port in ${PORTS}; do access_vlan=$(ifquery -p bridge-access ${port} 2>/dev/null || true) # Shall this prot interface be an untagged port? if [ "${access_vlan}" ]; then configure_access_port "${port}" "${access_vlan}" # Configure port as trunk else configure_trunk_port "${port}" fi done } case "$IF_BRIDGE_PORTS" in "") ;; none) PORTS="";; all) PORTS=$(all_ports);; *) PORTS="$IF_BRIDGE_PORTS";; esac case "$PHASE" in depend) # Called for the bridge interface if [ "${IF_BRIDGE_PORTS}" ]; then echo "$PORTS" fi ;; create) # Called for the bridge interface if [ "${IF_BRIDGE_PORTS}" -a ! -d "/sys/class/net/${IFACE}" ]; then ip link add "${IFACE}" type bridge fi ;; pre-up) # Called for the bridge interface if [ "${IF_BRIDGE_PORTS}" ]; then wait_ports set_bridge_opts set_bridge_vlans add_ports set_bridge_port_vlans wait_bridge # Called for a bridge member port elif [ "${IF_BRIDGE_VIDS}" -o "${IF_BRIDGE_PVID}" -o "${IF_BRIDGE_ACCESS}" -o "${IF_BRIDGE_ALLOW_UNTAGGED}" ]; then # Eventually we want to configure VLAN settings of member ports here. # The current execution model does not allow this, so this is a no-op # for now and we work around this by configuring ports while configuring # the bridge. true fi ;; post-down) # Called for the bridge interface if [ "${IF_BRIDGE_PORTS}" ]; then del_ports ip link set dev $IFACE down fi ;; destroy) # Called for the bridge interface if [ "${IF_BRIDGE_PORTS}" -a -d "/sys/class/net/${IFACE}" ]; then ip link del "${IFACE}" fi ;; esac