# #
###############################################################################
-function stp_init() {
- module_load stp
+# The default mode.
+# We default to RSTP, because it has the better user experience and
+# faster convergence times. Despite of that, it completely downgradeable
+# to plain STP.
+STP_DEFAULT_MODE="rstp"
- assert binary_exists brctl
- assert binary_exists rstpctl
-}
-
-init_register stp_init
+# Allowed modes of the spanning tree protocol.
+STP_ALLOWED_MODES="rstp stp"
-function __rstpctl_bridge_get() {
+function stp_enable() {
local bridge=${1}
- local param=${2}
-
assert isset bridge
- assert isset param
-
- local key
- local val
- rstpctl dumpbridge ${bridge} | \
- while read bridge key val; do
- if [ "${key}" = "${param}" ]; then
- echo "${val}"
- return ${EXIT_OK}
- fi
- done
- return ${EXIT_ERROR}
+ # Tell the kernel to enable STP.
+ print 1 > ${SYS_CLASS_NET}/${bridge}/bridge/stp_state
}
-function __rstpctl_port_get() {
+function stp_disable() {
local bridge=${1}
- local port=${2}
- local param=${3}
-
assert isset bridge
- assert isset port
- assert isset param
-
- local key
- local val
- rstpctl dumpports ${bridge} | \
- while read por key val; do
- if [ "${port}" = "${por}" -a "${key}" = "${param}" ]; then
- echo "${val}"
- return ${EXIT_OK}
- fi
- done
- return ${EXIT_ERROR}
+ # Tell the kernel to disable STP.
+ print 0 > ${SYS_CLASS_NET}/${bridge}/bridge/stp_state
}
-function stp_enable() {
+function stp_is_enabled() {
local bridge=${1}
+ assert isset bridge
+ local state=$(__device_get_file ${bridge} bridge/stp_state)
+
+ case "${state}" in
+ 0)
+ return ${EXIT_FALSE}
+ ;;
+ *)
+ return ${EXIT_TRUE}
+ ;;
+ esac
+}
+
+function stp_is_userspace() {
+ local bridge=${1}
assert isset bridge
- assert zone_exists ${bridge}
- brctl stp ${bridge} on
+ local state=$(__device_get_file ${bridge} bridge/stp_state)
+ case "${state}" in
+ 2)
+ return ${EXIT_TRUE}
+ ;;
+ *)
+ return ${EXIT_FALSE}
+ ;;
+ esac
+}
- local mode=$(zone_config_get ${bridge} STP_MODE)
+function stp_get_name() {
+ local proto=${1}
- case "${mode}" in
+ case "${proto}" in
stp)
- rstpctl setforcevers ${bridge} slow
+ echo "Spanning Tree Protocol"
;;
rstp)
- rstpctl setforcevers ${bridge} normal
+ echo "Rapid Spanning Tree Protocol"
;;
- *)
- log WARNING "Unknown protocol version: ${mode}."
- log WARNING "Using default mode."
+ mstp)
+ echo "Multiple Spanning Tree Protocol"
;;
esac
+
+ return ${EXIT_OK}
}
-function stp_disable() {
+function stp_bridge_set_protocol() {
local bridge=${1}
-
assert isset bridge
- assert zone_exists ${bridge}
- brctl stp ${bridge} off
+ local mode=${2}
+ assert isset mode
+
+ if ! listmatch ${mode} ${STP_ALLOWED_MODES}; then
+ log WARNING "Unknown protocol version: ${mode}."
+ log WARNING "Using default mode."
+
+ mode="${STP_DEFAULT_MODE}"
+ fi
+
+ cmd mstpctl setforcevers ${bridge} ${mode}
+ assert [ $? -eq 0 ]
}
function stp_bridge_get_protocol() {
assert isset bridge
- local enabled=$(__device_get_file ${bridge} "bridge/stp_state")
- if ! enabled ${enabled}; then
- return ${EXIT_OK}
- fi
-
- local mode=$(__rstpctl_bridge_get ${bridge} protocol_version)
+ # Let's check what the kernel is telling us about it's STP state.
+ local state=$(__device_get_file ${bridge} "bridge/stp_state")
- case "${mode}" in
+ case "${state}" in
0)
+ # STP is disabled.
+ return ${EXIT_OK}
+ ;;
+ 1)
+ # Kernel mode STP is running.
echo "stp"
+ return ${EXIT_OK}
;;
2)
- echo "rstp"
+ # User-space STP is running.
;;
- # When rstpctl has an error, we assume that rstpd is not running and
- # return the slow mode.
- "")
- echo "stp"
+ *)
+ log ERROR "Kernel is running in an unknown STP state."
+ return ${EXIT_ERROR}
;;
esac
-}
-function stp_bridge_set_protocol() {
- : XXX WANTED
+ # We get here, when STP is running in user-space mode.
+
+ # Get the current protocol version.
+ mstpctl showbridge ${bridge} force-protocol-version 2>/dev/null
+
+ return ${EXIT_OK}
}
function stp_bridge_get_id() {
local bridge=${1}
-
assert isset bridge
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_bridge_get ${bridge} "id"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "bridge/bridge_id"
- return ${EXIT_OK}
- ;;
- esac
+ __device_get_file ${bridge} "bridge/bridge_id"
- return ${EXIT_ERROR}
+ return $?
}
function stp_bridge_get_forward_delay() {
local bridge=${1}
+ assert isset bridge
+
+ if stp_is_userspace ${bridge}; then
+ cmd mstpctl showbridge ${bridge} forward-delay
+ else
+ local output=$(__device_get_file ${bridge} bridge/forward_delay)
+ __stp_div_100 ${output}
+ fi
+
+ return ${EXIT_OK}
+}
+function stp_bridge_set_forward_delay() {
+ local bridge=${1}
assert isset bridge
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_bridge_get ${bridge} "bridge_forward_delay"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "bridge/forward_delay"
- return ${EXIT_OK}
- ;;
- esac
+ local fdelay=${2}
+ assert isinteger fdelay
- return ${EXIT_ERROR}
+ # Check if the setting we want is already set.
+ local current_fdelay=$(stp_bridge_get_forward_delay ${bridge})
+ [ ${fdelay} -eq ${current_fdelay} ] && return ${EXIT_OK}
+
+ # The smallest value that may be set is 2.
+ if [ ${fdelay} -lt 2 ]; then
+ fdelay=2
+ fi
+
+ # Set the new value.
+ log INFO "Changing forward delay for '${bridge}': ${current_fdelay} --> ${fdelay}"
+ print "$(( ${fdelay} * 100 ))" > ${SYS_CLASS_NET}/${bridge}/bridge/forward_delay
+
+ return ${EXIT_OK}
}
function stp_bridge_get_hello_time() {
local bridge=${1}
+ assert isset bridge
+
+ local ht=$(__device_get_file ${bridge} bridge/hello_time)
+ __stp_div_100 ${ht}
+ return ${EXIT_OK}
+}
+
+function stp_bridge_set_hello_time() {
+ local bridge=${1}
assert isset bridge
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_bridge_get ${bridge} "bridge_hello_time"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "bridge/hello_time"
- return ${EXIT_OK}
- ;;
- esac
+ local hello=${2}
+ assert isinteger hello
- return ${EXIT_ERROR}
+ # Check if the setting we want is already set.
+ local current_hello=$(stp_bridge_get_hello_time ${bridge})
+ [ ${hello} -eq ${current_hello} ] && return ${EXIT_OK}
+
+ # Set the new value.
+ log INFO "Changing hello time for '${bridge}': ${current_hello} --> ${hello}"
+ print "$(( ${hello} * 100 ))" > ${SYS_CLASS_NET}/${bridge}/bridge/hellow_time
+
+ return ${EXIT_OK}
}
function stp_bridge_get_max_age() {
local bridge=${1}
+ assert isset bridge
+
+ local maxage=$(__device_get_file ${bridge} "bridge/max_age")
+ __stp_div_100 ${maxage}
+
+ return ${EXIT_OK}
+}
+function stp_bridge_set_max_age() {
+ local bridge=${1}
assert isset bridge
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_bridge_get ${bridge} "bridge_max_age"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "bridge/max_age"
- return ${EXIT_OK}
- ;;
- esac
+ local maxage=${2}
+ assert isinteger maxage
- return ${EXIT_ERROR}
+ # Check if the setting we want is already set.
+ local current_maxage=$(stp_bridge_get_max_age ${bridge})
+ [ ${maxage} -eq ${current_maxage} ] && return ${EXIT_OK}
+
+ # Set the new value.
+ log INFO "Changing max age for '${bridge}': ${current_maxage} --> ${maxage}"
+ print "$(( ${maxage} * 100 ))" > ${SYS_CLASS_NET}/${bridge}/bridge/max_age
+
+ return ${EXIT_OK}
}
-function stp_bridge_get_designated_root() {
+function stp_bridge_get_priority() {
local bridge=${1}
- local output
+ assert isset bridge
+ __device_get_file ${bridge} "bridge/priority"
+ return ${EXIT_OK}
+}
+
+function stp_bridge_set_priority() {
+ local bridge=${1}
assert isset bridge
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- output=$(__rstpctl_bridge_get ${bridge} "designated_root")
- ;;
- stp)
- output=$(__device_get_file ${bridge} "bridge/root_id")
- ;;
- esac
+ local priority=${2}
+ assert isinteger priority
- if ! isset output; then
- return ${EXIT_ERROR}
- fi
+ # Check if the setting we want is already set.
+ local current_priority=$(stp_bridge_get_priority ${bridge})
+ [ ${priority} -eq ${current_priority} ] && return ${EXIT_OK}
- mac_format "${output:5:12}"
+ # Set the new value.
+ log INFO "Changing priority for '${bridge}': ${current_priority} --> ${priority}"
+ print "${priority}" > ${SYS_CLASS_NET}/${bridge}/bridge/priority
return ${EXIT_OK}
}
-function stp_bridge_get_root_path_cost() {
+function stp_bridge_get_designated_root() {
local bridge=${1}
+ assert isset bridge
+
+ local output
+
+ if stp_is_userspace ${bridge}; then
+ output=$(cmd mstpctl showbridge ${bridge} designated-root)
+ else
+ output=$(__device_get_file ${bridge} bridge/root_id)
+ fi
+ output=${output:6}
+ # Print output (lowercase).
+ print "${output,,}"
+
+ if isset output; then
+ return ${EXIT_OK}
+ else
+ return ${EXIT_ERROR}
+ fi
+}
+
+function stp_bridge_get_root_path_cost() {
+ local bridge=${1}
assert isset bridge
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_bridge_get ${bridge} "root_path_cost"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "bridge/root_path_cost"
- return ${EXIT_OK}
- ;;
- esac
+ if stp_is_userspace ${bridge}; then
+ cmd mstpctl showbridge ${bridge} path-cost
+ else
+ __device_get_file ${bridge} bridge/root_path_cost
+ fi
- return ${EXIT_ERROR}
+ return ${EXIT_OK}
}
function stp_bridge_get_root_port_id() {
local bridge=${1}
-
assert isset bridge
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_bridge_get ${bridge} "root_port"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "bridge/root_port"
- return ${EXIT_OK}
- ;;
- esac
+ if stp_is_userspace ${bridge}; then
+ local root_port=$(cmd mstpctl showbridge ${bridge} root-port)
- return ${EXIT_ERROR}
+ # Return error, when there is no root port.
+ if [ "${root_port}" = "none" ]; then
+ return ${EXIT_ERROR}
+ fi
+
+ print "${root_port}"
+ else
+ __device_get_file ${bridge} bridge/root_port_id
+ fi
+
+ return ${EXIT_OK}
}
function stp_bridge_get_root_port() {
local bridge=${1}
-
assert isset bridge
local id=$(stp_bridge_get_root_port_id ${bridge})
- local member
- local member_id
+ local member member_id
for member in $(bridge_get_members ${bridge}); do
member_id=$(stp_port_get_id ${bridge} ${member})
if [ "${id}" = "${member_id}" ]; then
- echo "${member}"
+ print "${member}"
return ${EXIT_OK}
fi
done
function stp_bridge_is_root() {
local bridge=${1}
-
assert isset bridge
- [ -n "$(stp_bridge_get_root_port ${bridge})" ]
-}
+ local root_path_cost=$(stp_bridge_get_root_path_cost ${bridge})
-function stp_bridge_get_priority() {
- local bridge=${1}
-
- assert isset bridge
-
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- local output=$(__rstpctl_bridge_get ${bridge} "root_path_cost")
- dec "${output:0:4}"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "bridge/priority"
- return ${EXIT_OK}
- ;;
- esac
+ if [ "${root_path_cost}" = "0" ]; then
+ return ${EXIT_TRUE}
+ fi
- return ${EXIT_ERROR}
+ return ${EXIT_FALSE}
}
function stp_bridge_get_topology_change_count() {
local bridge=${1}
-
assert isset bridge
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_bridge_get ${bridge} "topology_change_count"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "bridge/topology_change"
- return ${EXIT_OK}
- ;;
- esac
+ if stp_is_userspace ${bridge}; then
+ cmd mstpctl showbridge ${bridge} topology-change-count
+ else
+ __device_get_file ${bridge} bridge/topology_change
+ fi
- return ${EXIT_ERROR}
+ return ${EXIT_OK}
}
function stp_bridge_get_topology_change_timer() {
local bridge=${1}
-
assert isset bridge
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_bridge_get ${bridge} "time_since_topology_change"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "bridge/topology_change_timer"
- return ${EXIT_OK}
- ;;
- esac
+ if stp_is_userspace ${bridge}; then
+ cmd mstpctl showbridge ${bridge} time-since-topology-change
+ else
+ __device_get_file ${bridge} bridge/topology_change_timer
+ fi
- return ${EXIT_ERROR}
+ return ${EXIT_OK}
}
function stp_bridge_get_topology_change_detected() {
local bridge=${1}
-
assert isset bridge
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_bridge_get ${bridge} "topology_change"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "bridge/topology_change_detected"
- return ${EXIT_OK}
- ;;
- esac
+ local change
- return ${EXIT_ERROR}
-}
+ if stp_is_userspace ${bridge}; then
+ change=$(mstpctl showbridge ${bridge} topology-change)
+ else
+ change=$(__device_get_file ${bridge} bridge/topology_change_detected)
+ fi
-# STP states
-STP_STATE[0]="disabled"
-STP_STATE[1]="listening"
-STP_STATE[2]="learning"
-STP_STATE[3]="forwarding"
-STP_STATE[4]="blocking"
+ if enabled change; then
+ print "yes"
+ return ${EXIT_TRUE}
+ else
+ print "no"
+ return ${EXIT_FALSE}
+ fi
+}
function stp_port_get_state() {
local bridge=${1}
- local port=${2}
- local output
-
assert isset bridge
- assert isset port
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- output=$(__rstpctl_port_get ${bridge} ${port} "state")
- ;;
- stp)
- output=$(__device_get_file ${bridge} "brif/${port}/state")
-
- # Translate int to name
- output="${STP_STATE[${output}]}"
- ;;
- esac
+ local port=${2}
+ assert isset port
- if ! isset output; then
- return ${EXIT_ERROR}
+ local space
+ if stp_is_userspace ${bridge}; then
+ state=$(mstpctl showportdetail ${bridge} ${port} state)
+ print "${state^^}"
+ else
+ state=$(__device_get_file ${bridge} brif/${port}/state)
+
+ case "${state}" in
+ 0)
+ print "DISABLED"
+ ;;
+ 1)
+ print "LISTENING"
+ ;;
+ 2)
+ print "LEARNING"
+ ;;
+ 3)
+ print "FORWARDING"
+ ;;
+ 4)
+ print "BLOCKING"
+ ;;
+ *)
+ return ${EXIT_ERROR}
+ ;;
+ esac
fi
- echo "${output^^}"
-
return ${EXIT_OK}
}
function stp_port_get_id() {
local bridge=${1}
- local port=${2}
-
assert isset bridge
- assert isset port
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_port_get ${bridge} ${port} "id"
- return ${EXIT_OK}
- ;;
- stp)
- dec $(__device_get_file ${bridge} "brif/${port}/port_no")
- return ${EXIT_OK}
- ;;
- esac
+ local port=${2}
+ assert isset port
- return ${EXIT_ERROR}
+ dec $(__device_get_file ${bridge} "brif/${port}/port_no")
+ return ${EXIT_OK}
}
function stp_port_get_cost() {
local bridge=${1}
- local port=${2}
-
assert isset bridge
+
+ local port=${2}
assert isset port
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- __rstpctl_port_get ${bridge} ${port} "path_cost"
- return ${EXIT_OK}
- ;;
- stp)
- __device_get_file ${bridge} "brif/${port}/path_cost"
- return ${EXIT_OK}
- ;;
- esac
+ if stp_is_userspace ${bridge}; then
+ cmd mstpctl showportdetail ${bridge} ${port} external-port-cost
+ else
+ __device_get_file ${bridge} brif/${port}/path_cost
+ fi
return ${EXIT_ERROR}
}
function stp_port_get_designated_root() {
local bridge=${1}
- local port=${2}
- local output
-
assert isset bridge
+
+ local port=${2}
assert isset port
- case "$(stp_bridge_get_protocol ${bridge})" in
- rstp)
- output=$(__rstpctl_port_get ${bridge} ${port} "designated_root")
- ;;
- stp)
- output=$(__device_get_file ${bridge} "brif/${port}/designated_root")
- ;;
- esac
+ local output
+
+ if stp_is_userspace ${bridge}; then
+ output=$(cmd mstpctl showportdetail ${bridge} ${port} designated-root)
+ output=${output:6}
+ else
+ output=$(__device_get_file ${bridge} brif/${port}/designated_root)
+ output=${output:5}
+ fi
- if [ -n "${output}" ]; then
- mac_format ${output:5:12}
+ if isset output; then
+ mac_format ${output}
return ${EXIT_OK}
fi
return ${EXIT_ERROR}
}
+
+function __stp_div_100() {
+ local val=${1}
+
+ local split=$((${#val} - 2))
+ val="${val:0:${split}}.${val:${split}:2}"
+
+ # Round the output.
+ print "%.0f" "${val}"
+}