Subject: Re: [PATCH iproute2-next] Introduce ip-brctl shell script

From: Stefano Brivio <[email-masked]>
Date: Thu, 31 Jan 2019 13:49:05 +0100

Hi,

For your information: I recently submitted a patch that implements
brctl as a wrapper using ip-link and 'bridge' from iproute2, that
allows to drop bridge-utils while still providing brctl functionality:

	https://patchwork.ozlabs.org/patch/1027627/

This wasn't exactly met with enthusiasm upstream (full discussion at:
https://marc.info/?l=linux-netdev&m=154783087917432), but feel free to
carry it downstream if you're interested.

Thanks,

-- 
Stefano

On Fri, 18 Jan 2019 18:00:45 +0100
Stefano Brivio <[email-masked]> wrote:

> This script wraps 'ip' and 'bridge' tools to provide a drop-in replacement
> of the standalone 'brctl' utility.
> 
> It's bug-to-bug compatible with brctl as of bridge-utils version 1.6,
> has no dependencies other than a POSIX shell, and it's less than half
> the binary size of brctl on x86_64.
> 
> As many users (including myself) seem to find brctl usage vastly more
> intuitive than ip-link, possibly due to habit, this might be a lightweight
> approach to provide brctl syntax without the need to maintain bridge-utils
> any longer.
> 
> Signed-off-by: Stefano Brivio <[email-masked]>
> Acked-by: Phil Sutter <[email-masked]>
> ---
>  man/man8/Makefile   |   5 +-
>  man/man8/ip-brctl.8 | 187 +++++++++++++++
>  misc/Makefile       |   9 +-
>  misc/ip-brctl.in    | 572 ++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 770 insertions(+), 3 deletions(-)
>  create mode 100644 man/man8/ip-brctl.8
>  create mode 100755 misc/ip-brctl.in
> 
> diff --git a/man/man8/Makefile b/man/man8/Makefile
> index 932ba1f3c488..26d2370a9cbe 100644
> --- a/man/man8/Makefile
> +++ b/man/man8/Makefile
> @@ -1,5 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0
> -TARGETS = ip-address.8 ip-link.8 ip-route.8
> +TARGETS = ip-address.8 ip-link.8 ip-route.8 brctl.8
>  
>  MAN8PAGES = $(TARGETS) $(filter-out $(TARGETS),$(wildcard *.8))
>  
> @@ -14,6 +14,9 @@ ip-link.8: ip-link.8.in
>  ip-route.8: ip-route.8.in
>  	sed "s|@SYSCONFDIR@|$(CONFDIR)|g" $< > $@
>  
> +brctl.8: ip-brctl.8
> +	echo '.so man8/ip-brctl.8' >$@
> +
>  distclean: clean
>  
>  clean:
> diff --git a/man/man8/ip-brctl.8 b/man/man8/ip-brctl.8
> new file mode 100644
> index 000000000000..63c1bcdebe9f
> --- /dev/null
> +++ b/man/man8/ip-brctl.8
> @@ -0,0 +1,187 @@
> +.\"
> +.\"	This program is free software; you can redistribute it and/or modify
> +.\"	it under the terms of the GNU General Public License as published by
> +.\"	the Free Software Foundation; either version 2 of the License, or
> +.\"	(at your option) any later version.
> +.\"
> +.\"	This program is distributed in the hope that it will be useful,
> +.\"	but WITHOUT ANY WARRANTY; without even the implied warranty of
> +.\"	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +.\"	GNU General Public License for more details.
> +.\"
> +.\"	You should have received a copy of the GNU General Public License
> +.\"	along with this program; if not, write to the Free Software
> +.\"	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> +.\"
> +.\"
> +.TH IP-BRCTL 8 "January 18, 2019" "" ""
> +.SH NAME
> +ip-brctl \- ethernet bridge administration
> +.SH SYNOPSIS
> +.BR "brctl [command]"
> +.SH DESCRIPTION
> +.B ip-brctl
> +is a reimplementation of the traditional
> +.B brctl
> +utility in shell making use of
> +.BR ip " and " bridge
> +tools internally. It is supposed to behave identically to
> +.BR brctl ,
> +hence the remainder of this document will use that name instead of
> +.BR ip-brctl .
> +
> +.B brctl
> +is used to set up, maintain, and inspect the ethernet bridge
> +configuration in the Linux kernel.
> +
> +An ethernet bridge is a device commonly used to connect different
> +networks of ethernets together, so that these ethernets will appear as
> +one ethernet to the participants.
> +
> +Each of the ethernets being connected corresponds to one physical
> +interface in the bridge. These individual ethernets are bundled into
> +one bigger ('logical') ethernet, this bigger ethernet corresponds to
> +the bridge network interface.
> +
> +
> +.SH INSTANCES
> +The command
> +.B brctl addbr <name>
> +creates a new instance of the ethernet bridge. The network interface
> +corresponding to the bridge will be called <name>.
> +
> +The command
> +.B brctl delbr <name>
> +deletes the instance <name> of the ethernet bridge. The network
> +interface corresponding to the bridge must be down before it can be
> +deleted!
> +
> +The command
> +.B brctl show
> +shows all current instances of the ethernet bridge.
> +
> +
> +.SH PORTS
> +Each bridge has a number of ports attached to it. Network traffic
> +coming in on any of these ports will be forwarded to the other ports
> +transparently, so that the bridge is invisible to the rest of the
> +network (i.e. it will not show up in
> +.IR traceroute(8)
> +).
> +
> +The command
> +.B brctl addif <brname> <ifname>
> +will make the interface <ifname> a port of the bridge <brname>. This
> +means that all frames received on <ifname> will be processed as if
> +destined for the bridge. Also, when sending frames on <brname>,
> +<ifname> will be considered as a potential output interface.
> +
> +The command
> +.B brctl delif <brname> <ifname>
> +will detach the interface <ifname> from the bridge <brname>.
> +
> +The command
> +.B brctl show <brname>
> +will show some information on the bridge and its attached ports.
> +
> +
> +.SH AGEING
> +The bridge keeps track of ethernet addresses seen on each port. When
> +it needs to forward a frame, and it happens to know on which port the
> +destination ethernet address (specified in the frame) is located, it
> +can 'cheat' by forwarding the frame to that port only, thus saving a
> +lot of redundant copies and transmits.
> +
> +However, the ethernet address location data is not static
> +data. Machines can move to other ports, network cards can be replaced
> +(which changes the machine's ethernet address), etc.
> +
> +.B brctl showmacs <brname>
> +shows a list of learned MAC addresses for this bridge.
> +
> +.B brctl setageing <brname> <time>
> +sets the ethernet (MAC) address ageing time, in seconds. After <time>
> +seconds of not having seen a frame coming from a certain address, the
> +bridge will time out (delete) that address from the Forwarding
> +DataBase (fdb).
> +
> +.B brctl setgcint <brname> <time>
> +sets the garbage collection interval for the bridge <brname> to <time>
> +seconds. This means that the bridge will check the forwarding database
> +for timed out entries every <time> seconds.
> +
> +
> +.SH SPANNING TREE PROTOCOL
> +Multiple ethernet bridges can work together to create even larger
> +networks of ethernets using the IEEE 802.1d spanning tree
> +protocol. This protocol is used for finding the shortest path between
> +two ethernets, and for eliminating loops from the topology. As this
> +protocol is a standard, Linux bridges will interwork properly with
> +other third party bridge products. Bridges communicate with each other
> +by sending and receiving BPDUs (Bridge Protocol Data Units). These
> +BPDUs can be recognised by an ethernet destination address of
> +01:80:c2:00:00:00.
> +
> +The spanning tree protocol can also be turned off (for those
> +situations where it just doesn't make sense, for example when this
> +Linux box is the only bridge on the LAN, or when you know that there
> +are no loops in the topology.)
> +
> +.IR brctl(8)
> +can be used for configuring certain spanning tree protocol
> +parameters. For an explanation of these parameters, see the IEEE
> +802.1d specification (or send me an email). The default values should
> +be just fine. If you don't know what these parameters mean, you
> +probably won't feel the desire to tweak them.
> +
> +.B brctl stp <bridge> <state>
> +controls this bridge instance's participation in the spanning tree
> +protocol. If <state> is "on" or "yes" the STP will be turned on,
> +otherwise it will be turned off.  When turned off, the bridge will not
> +send or receive BPDUs, and will thus not participate in the spanning
> +tree protocol. If your bridge isn't the only bridge on the LAN, or if
> +there are loops in the LAN's topology, DO NOT turn this option off. If
> +you turn this option off, please know what you are doing.
> +
> +
> +.B brctl setbridgeprio <bridge> <priority>
> +sets the bridge's priority to <priority>. The priority value is an
> +unsigned 16-bit quantity (a number between 0 and 65535), and has no
> +dimension. Lower priority values are 'better'. The bridge with the
> +lowest priority will be elected 'root bridge'.
> +
> +.B brctl setfd <bridge> <time>
> +sets the bridge's 'bridge forward delay' to <time> seconds.
> +
> +.B brctl sethello <bridge> <time>
> +sets the bridge's 'bridge hello time' to <time> seconds.
> +
> +.B brctl setmaxage <bridge> <time>
> +sets the bridge's 'maximum message age' to <time> seconds.
> +
> +.B brctl setpathcost <bridge> <port> <cost>
> +sets the port cost of the port <port> to <cost>. This is a
> +dimensionless metric.
> +
> +.B brctl setportprio <bridge> <port> <priority>
> +sets the port <port>'s priority to <priority>. The priority value is
> +an unsigned 8-bit quantity (a number between 0 and 255), and has no
> +dimension. This metric is used in the designated port and root port
> +selection algorithms.
> +
> +.SH NOTES
> +.BR brctl(8)
> +is obsolete. Some features such as STP guard, harpin mode, fastleave and root
> +block are intentionally not implemented in this command.
> +Instead use
> +.B bridge
> +command from
> +.B iproute2
> +package for a more full set of features.
> +
> +.SH SEE ALSO
> +.BR iptables(8)
> +
> +.SH AUTHOR
> +Lennert Buytenhek <[email-masked]>
> +Stephen Hemminger <[email-masked]>
> diff --git a/misc/Makefile b/misc/Makefile
> index 6a849af4be22..79c6be31b874 100644
> --- a/misc/Makefile
> +++ b/misc/Makefile
> @@ -3,6 +3,7 @@ SSOBJ=ss.o ssfilter.o
>  LNSTATOBJ=lnstat.o lnstat_util.o
>  
>  TARGETS=ss nstat ifstat rtacct lnstat
> +SCRIPTS=ip-brctl
>  
>  include ../config.mk
>  
> @@ -10,7 +11,7 @@ ifeq ($(HAVE_BERKELEY_DB),y)
>  	TARGETS += arpd
>  endif
>  
> -all: $(TARGETS)
> +all: $(TARGETS) $(SCRIPTS)
>  
>  ss: $(SSOBJ)
>  	$(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
> @@ -33,10 +34,14 @@ ssfilter.c: ssfilter.y
>  lnstat: $(LNSTATOBJ)
>  	$(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
>  
> +ip-brctl: ip-brctl.in
> +	@sed -e "s|^\(IP=\"\).*\"|\1$(DESTDIR)$(SBINDIR)\/ip\"|" -e "s|^\(BRIDGE=\"\).*\"|\1$(DESTDIR)$(SBINDIR)\/bridge\"|" $< > $@
> +
>  install: all
> -	install -m 0755 $(TARGETS) $(DESTDIR)$(SBINDIR)
> +	install -m 0755 $(TARGETS) $(SCRIPTS) $(DESTDIR)$(SBINDIR)
>  	ln -sf lnstat $(DESTDIR)$(SBINDIR)/rtstat
>  	ln -sf lnstat $(DESTDIR)$(SBINDIR)/ctstat
> +	ln -sf ip-brctl $(DESTDIR)$(SBINDIR)/brctl
>  
>  clean:
>  	rm -f *.o $(TARGETS) ssfilter.c
> diff --git a/misc/ip-brctl.in b/misc/ip-brctl.in
> new file mode 100755
> index 000000000000..7e7059aa2c06
> --- /dev/null
> +++ b/misc/ip-brctl.in
> @@ -0,0 +1,572 @@
> +#!/bin/sh
> +#
> +# ip-brctl: Implementation of brctl from bridge-utils as iproute2 wrapper
> +#
> +# Copyright (c) 2019 Red Hat, Inc.
> +# Author: Stefano Brivio <[email-masked]>
> +#
> +# SPDX-License-Identifier: GPL-2.0
> +
> +# Global variables #############################################################
> +
> +# These will be replaced by Makefile on install with ip and bridge install paths
> +IP="../ip/ip"
> +BRIDGE="../bridge/bridge"
> +
> +usage_text="Usage: brctl [commands]
> +commands:
> +	addbr     	<bridge>		add bridge
> +	delbr     	<bridge>		delete bridge
> +	addif     	<bridge> <device>	add interface to bridge
> +	delif     	<bridge> <device>	delete interface from bridge
> +	hairpin   	<bridge> <port> {on|off}	turn hairpin on/off
> +	setageing 	<bridge> <time>		set ageing time
> +	setbridgeprio	<bridge> <prio>		set bridge priority
> +	setfd     	<bridge> <time>		set bridge forward delay
> +	sethello  	<bridge> <time>		set hello time
> +	setmaxage 	<bridge> <time>		set max message age
> +	setpathcost	<bridge> <port> <cost>	set path cost
> +	setportprio	<bridge> <port> <prio>	set port priority
> +	show      	[ <bridge> ]		show a list of bridges
> +	showmacs  	<bridge>		show a list of mac addrs
> +	showstp   	<bridge>		show bridge stp info
> +	stp       	<bridge> {on|off}	turn stp on/off"
> +
> +# List of commands prefixed by minimum number of arguments
> +commands="1 addbr 1 delbr 2 addif 2 delif 3 hairpin 2 setageing 2 setbridgeprio
> +	  2 setfd 2 sethello 2 setmaxage 3 setpathcost 3 setportprio 0 show
> +	  1 showmacs 1 showstp 2 stp"
> +
> +
> +# Helper functions #############################################################
> +
> +usage() {
> +	echo "${usage_text}"
> +	exit "${1:-1}"
> +}
> +
> +# Print a single line from usage for given command
> +usage_one() {
> +	command="${1}"
> +
> +	ifs="${IFS}"
> +	IFS='
> +'
> +	for line in ${usage_text}; do
> +		case ${line} in
> +		"	${command}"*)
> +			line="${line#	${command}*}"
> +			while [ "${line}" != "${line#[[:blank:]]*}" ]; do
> +				line="${line#[[:blank:]]*}"
> +			done
> +			echo "Usage: brctl ${command} ${line}"
> +			break
> +			;;
> +		esac
> +	done
> +	IFS="${ifs}"
> +
> +	exit 1
> +}
> +
> +# Print to standard error and exit
> +err() {
> +	echo "${@}" > /dev/stderr
> +	exit 1
> +}
> +
> +# Output token following the given one, from a space-separated string
> +parse_next() {
> +	needle=${1}
> +	str=${2}
> +
> +	next=0
> +	ifs="${IFS}"
> +	IFS=' '
> +	for token in ${str}; do
> +		[ ${next} -eq 1 ] && echo "${token}" && break
> +		[ "${token}" = "${needle}" ] && next=1
> +	done
> +	IFS="${ifs}"
> +}
> +
> +# Output value for given ip-link attribute, for the given device
> +parse_iplink() {
> +	attr="${1}"
> +	dev="${2}"
> +
> +	out="$(${IP} -d link show dev "${dev}" 2>/dev/null)"
> +	parse_next "${attr}" "${out}"
> +}
> +
> +# Once starting token is matched, for each token x with index n = 2 * k, assign
> +# value of token with index n + 1 to a variable named by the value of x, using
> +# state variables parse_assign_start and parse_assign_prev. Pass one token at
> +# a time.
> +parse_assign() {
> +	token="${1}"
> +	start_token="${2}"
> +
> +	if [ "${token}" = "${start_token}" ]; then
> +		parse_assign_start=1
> +		parse_assign_prev=
> +	elif [ ${parse_assign_start} -eq 0 ]; then
> +		:
> +	elif [ -z "${parse_assign_prev}" ]; then
> +		parse_assign_prev="${token}"
> +	else
> +		eval "${parse_assign_prev}"="${token}"
> +		parse_assign_prev=
> +	fi
> +}
> +
> +# Execute ip-link command with given arguments. On failure, print returned error
> +# message prefixed by given string and exit.
> +exec_iplink() {
> +	args="${1}"
> +	err_prefix="${2}"
> +
> +	err_out="$(${IP} link "${args}" 2>&1)" || err "${err_prefix}: ${err_out#*:}"
> +}
> +
> +# Print passed error string and exit if the given device does or does not exist,
> +# depending on the condition given. Device type matching is optional.
> +err_dev_exists() {
> +	cond="${1}"
> +	dev="${2}"
> +	err_string="${3}"
> +	opt_type="${4}"
> +
> +	[ -n "${opt_type}" ] && opt_type="type ${opt_type}" || opt_type=
> +
> +	if [ -n "$(${IP} link show dev "${dev}" ${opt_type} 2>/dev/null)" ]; then
> +		[ "${cond}" = "y" ] && err "${err_string}"
> +	else
> +		[ "${cond}" = "n" ] && err "${err_string}"
> +	fi
> +}
> +
> +
> +# Type checks and conversions ##################################################
> +
> +# Don't attempt operations on values exceeding limits for a signed long. From
> +# POSIX.1-2017, XCU, par. 2.6.4 Arithmetic Expansion:
> +#
> +#	Only signed long integer arithmetic is required.
> +#
> +# On failure, print returned error message prefixed by given string and exit.
> +check_long() {
> +	in="${1}"
> +	err_prefix="${2}"
> +
> +	# Comparing x >= 2^31 may fail, allow up to one digit shorter than that
> +	long_max=2147483647
> +	if [ ${#in} -ge ${#long_max} ]; then
> +		err "${err_prefix}: Numerical result out of range"
> +	fi
> +}
> +
> +# On failure, print passed error string and exit
> +check_float() {
> +	in="${1}"
> +	err_string="${2}"
> +
> +	case ${in} in
> +	[0-9.]*) ;;
> +	*) err "${err_string}" ;;
> +	esac
> +}
> +
> +# Convert values allowed as boolean to given strings for false and true values
> +make_bool() {
> +	in="${1}"
> +	str_false="${2}"
> +	str_true="${2}"
> +
> +	case ${in} in
> +	off|no|0) echo "${str_false}" ;;
> +	on|yes|1) echo "${str_true}" ;;
> +	*) err "expect on/off for argument" ;;
> +	esac
> +}
> +
> +# Divide by 100, output number with two fractional digits
> +make_float() {
> +	in="${1}"
> +
> +	int=$((in / 100))
> +	frac=$((in % 100))
> +	[ ${#frac} -eq 1 ] && frac="0${frac}"
> +	echo "${int}.${frac}"
> +}
> +
> +# Multiply given float by 100. Multiple decimal separators are allowed, anything
> +# after the second one is discarded.
> +brctl_timeval() {
> +	in="${1}"
> +
> +	# Drop second decimal separator and following digits, if any
> +	drop=${in#[0-9]*.[0-9]*.}
> +	time=${in%%.${drop}}
> +
> +	int=${time%.*}
> +
> +	# Take up to two digits from fractional part
> +	frac=${time#*.}
> +	drop=${frac#[0-9][0-9]*}
> +	frac=${frac%%${drop}}
> +	frac=${frac:-0}
> +	[ "${frac}" -lt 10 ] && frac=$((frac * 10))
> +
> +	echo "$((int * 100 + frac))"
> +}
> +
> +# Strip colons from MAC address in bridge identifiers, pad bytes with 0
> +fixup_id() {
> +	in="${1}"
> +
> +	ifs="${IFS}"
> +	IFS=':'
> +	out=
> +	for byte in ${in}; do
> +		[ ${#byte} -eq 1 ] && byte="0${byte}"
> +		out="${out}${byte}"
> +	done
> +	IFS="${ifs}"
> +	echo "${out}"
> +}
> +
> +
> +# Display functions ############################################################
> +
> +# Pad string to given width -- if not given, width is 7 if float, 4 otherwise
> +pad_width() {
> +	str="${1}"
> +	[ "${str}" != "${str%.*}" ] && width=${2:-7} || width=${2:-4}
> +
> +	len=${#str}
> +	while [ "${len}" -lt "${width}" ]; do
> +		str=" ${str}"
> +		len=$((len + 1))
> +	done
> +	echo "${str}"
> +}
> +
> +# Dump bridge information from set variables in 'brctl showstp' format
> +#
> +# shellcheck disable=SC2154 # shellcheck won't see variables we set under eval
> +dump_bridge() {
> +	name="${1}"
> +
> +	for i in bridge_id designated_root; do
> +		eval ${i}="\$(fixup_id \$${i})"
> +	done
> +	for i in max_age hello_time forward_delay ageing_time; do
> +		eval ${i}="\$(make_float \$${i})"
> +	done
> +	for i in root_port root_path_cost max_age hello_time forward_delay \
> +		 ageing_time hello_timer tcn_timer topology_change_timer \
> +		 gc_timer; do
> +		eval ${i}=\""\$(pad_width \$${i})"\"
> +	done
> +	flags=
> +	[ "${topology_change}" != "0" ] && flags="TOPOLOGY_CHANGE "
> +	[ "${topology_change_detected}" -ne "0" ] && flags="${flags}TOPOLOGY_CHANGE_DETECTED "
> +
> +	echo "${name}"
> +	echo " bridge id		${bridge_id}"
> +	echo " designated root	${designated_root}"
> +	echo " root port		${root_port}			path cost		${root_path_cost}"
> +	echo " max age		${max_age}			bridge max age		${max_age}"
> +	echo " hello time		${hello_time}			bridge hello time	${hello_time}"
> +	echo " forward delay		${forward_delay}			bridge forward delay	${forward_delay}"
> +	echo " ageing time		${ageing_time}"
> +	echo " hello timer		${hello_timer}			tcn timer		${tcn_timer}"
> +	echo " topology change timer	${topology_change_timer}			gc timer		${gc_timer}"
> +	echo " flags			${flags}"
> +	echo
> +	echo
> +}
> +
> +# Dump port information from set variables in 'brctl showstp' format
> +#
> +# shellcheck disable=SC2154 # shellcheck won't see variables we set under eval
> +dump_port() {
> +	for i in designated_root designated_bridge; do
> +		eval ${i}="\$(fixup_id \$${i})"
> +	done
> +
> +	for i in message_age_timer cost message_age_timer forward_delay_timer \
> +		 designated_cost hold_timer; do
> +		eval ${i}=\""\$(pad_width \$${i})"\"
> +	done
> +
> +	flags=
> +	[ "${config_pending}" != "0" ] && flags="CONFIG_PENDING "
> +	[ "${topology_change_ack}" -ne "0" ] && flags="${flags}TOPOLOGY_CHANGE_ACK "
> +
> +	echo "${port_name%*:} (${port_no#0x*})"
> +	echo " port id		${port_id#0x*}			state		$(pad_width "${state}" 15)"
> +	echo " designated root	${designated_root}	path cost		${cost}"
> +	echo " designated bridge	${designated_bridge}	message age timer	${message_age_timer}"
> +	echo " designated port	$((8000 + designated_port % 32768))			forward delay timer	${forward_delay_timer}"
> +	echo " designated cost	${designated_cost}			hold timer		${hold_timer}"
> +	echo " flags ${flags}"
> +	[ "${hairpin}" != "off" ] && echo " hairpin mode		$(pad_width 1)"
> +	echo
> +}
> +
> +
> +# Commands #####################################################################
> +
> +cmd_addbr() {
> +	err_dev_exists y "${1}" "device ${1} already exists; can't create bridge with the same name"
> +
> +	exec_iplink "add ${1} type bridge" "add bridge failed"
> +}
> +
> +cmd_delbr() {
> +	err_dev_exists n "${1}" "bridge ${1} doesn't exist; can't delete it"
> +	if [ "$(parse_iplink state "${1}")" != "DOWN" ]; then
> +		err "bridge ${1} is still up; can't delete it"
> +	fi
> +
> +	exec_iplink "del ${1}" "can't delete bridge ${1}"
> +}
> +
> +cmd_addif() {
> +	err_dev_exists n "${1}" "bridge ${1} does not exist!"
> +	err_dev_exists n "${2}" "interface ${2} does not exist!"
> +	if [ -n "$(parse_iplink master "${1}")" ]; then
> +		err "device ${2} is already a member of a bridge; can't enslave it to bridge ${1}."
> +	fi
> +	err_dev_exists y "${1}" "device ${1} is a bridge device itself; can't enslave a bridge device to a bridge device." bridge
> +
> +	exec_iplink "set ${2} master ${1}" "can't add ${2} to bridge ${1}"
> +}
> +
> +cmd_delif() {
> +	err_dev_exists n "${1}" "bridge ${1} does not exist!"
> +	err_dev_exists n "${2}" "interface ${2} does not exist!"
> +	if [ "$(parse_iplink master "${2}")" != "${1}" ]; then
> +		err "device ${1} is not a slave of ${2}"
> +	fi
> +
> +	exec_iplink "set ${2} nomaster" "can't delete ${2} from ${1}"
> +}
> +
> +cmd_hairpin() {
> +	hairpin="$(make_bool "${3}" off on)"
> +	[ -z "${hairpin}" ] && exit 1
> +	err_dev_exists n "${2}" "interface ${2} does not exist!"
> +	err_dev_exists n "${1}" "bridge ${1} does not exist!"
> +
> +	exec_iplink "set ${2} type bridge_slave ${hairpin}" "can't set ${2} to hairpin on bridge ${1}"
> +}
> +
> +cmd_setageing() {
> +	check_long "${2}" "set ageing time failed"
> +	check_float "${2}" "bad ageing time value"
> +	err_dev_exists n "${1}" "set ageing time failed: No such device"
> +
> +	exec_iplink "set ${1} type bridge ageing_time $(brctl_timeval "${2}")" "set ageing time failed"
> +}
> +
> +cmd_setbridgeprio() {
> +	check_long "${2}" "set bridge priority failed"
> +	check_float "${2}" "bad priority"
> +	err_dev_exists n "${1}" "set bridge priority failed: No such device"
> +
> +	prio=${2%%.*}
> +	prio=$((prio % 65536))
> +
> +	exec_iplink "set ${1} type bridge priority ${prio}" "set bridge priority failed"
> +}
> +
> +cmd_setfd() {
> +	check_long "${2}" "set forward delay failed"
> +	check_float "${2}" "bad ageing time value"
> +	err_dev_exists n "${1}" "set forward delay failed: No such device"
> +
> +	exec_iplink "set ${1} type bridge forward_delay $(brctl_timeval "${2}")" "set forward delay failed"
> +}
> +
> +cmd_sethello() {
> +	check_long "${2}" "set ageing time failed"
> +	check_float "${2}" "bad hello timer value"
> +	err_dev_exists n "${1}" "set hello timer failed: No such device"
> +
> +	exec_iplink "set ${1} type bridge hello_time $(brctl_timeval "${2}")" "set hello timer failed"
> +}
> +
> +cmd_setmaxage() {
> +	check_long "${2}" "set max age failed"
> +	check_float "${2}" "bad max age value"
> +	err_dev_exists n "${1}" "set max age failed: No such device"
> +
> +	exec_iplink "set ${1} max_age $(brctl_timeval "${2}")" "set max age failed"
> +}
> +
> +cmd_setpathcost() {
> +	check_long "${3}" "set path cost failed"
> +	check_float "${3}" "bad path cost value"
> +	err_dev_exists n "${2}" "set path cost failed: No such device"
> +
> +	cost=${3%%.*}
> +
> +	exec_iplink "set ${2} type bridge_slave cost ${cost}" "set path cost failed"
> +}
> +
> +cmd_setportprio() {
> +	check_long "${3}" "set port priority failed"
> +	check_float "${3}" "bad path priority value"
> +	err_dev_exists n "${2}" "set port priority failed: No such device"
> +
> +	prio=${3%%.*}
> +	[ "${prio}" -ge 64 ] && err "set port priority failed: Numerical result out of range"
> +
> +	exec_iplink "set ${2} type bridge_slave priority ${prio}" "set port priority failed"
> +}
> +
> +cmd_stp() {
> +	stp="$(make_bool "${2}" 0 1)"
> +	[ -z "${stp}" ] && exit 1
> +	err_dev_exists n "${1}" "set stp status failed: No such device"
> +
> +	exec_iplink "set ${1} type bridge stp_state ${stp}" "set stp status failed"
> +}
> +
> +cmd_showstp() {
> +	parse_assign_start=0
> +	for t in $(${IP} -d link show "${1}" 2>/dev/null); do
> +		parse_assign "${t}" bridge
> +	done
> +	[ ${parse_assign_start} -eq 0 ] && err "${1}: can't get info No such device"
> +
> +	dump_bridge "${1}"
> +
> +	parse_assign_start=0
> +	for t in $(${IP} -d link show type bridge_slave master "${1}" 2>/dev/null); do
> +		case ${t} in
> +		[0-9]*:)
> +			[ -n "${port_name}" ] && dump_port
> +			port_name=
> +			parse_assign_start=0
> +			continue
> +			;;
> +		esac
> +		[ -z "${port_name}" ] && port_name="${t}"
> +
> +		parse_assign "${t}" bridge_slave
> +	done
> +
> +	[ -n "${port_name}" ] && dump_port
> +}
> +
> +cmd_show() {
> +	dev="${1}"
> +
> +	if [ -n "${dev}" ]; then
> +		err_dev_exists n "${dev}" "bridge ${dev} does not exist!"
> +		err_dev_exists n "${dev}" "device ${dev} is not a bridge!" bridge
> +	fi
> +
> +	echo "bridge name	bridge id		STP enabled	interfaces"
> +	ifs="${IFS}"
> +	IFS='
> +'
> +	for line in $(${IP} -br link show type bridge "${dev}" 2>/dev/null); do
> +		name="${line%% *}"
> +		id="$(fixup_id "$(parse_iplink bridge_id "${name}")")"
> +		[ "$(parse_iplink stp_state "${name}")" = "0" ] && stp="no" || stp="yes"
> +		first=1
> +		for s in $(${IP} -br link show type bridge_slave master "${name}"); do
> +			if [ ${first} -eq 1 ]; then
> +				echo "${name}		${id}	${stp}		${s%% *}"
> +				first=0
> +			else
> +				echo "							${s%% *}"
> +			fi
> +		done
> +		[ ${first} -eq 1 ] && echo "${name}		${id}	${stp}"
> +	done
> +	IFS="${ifs}"
> +}
> +
> +cmd_showmacs() {
> +	err_dev_exists n "${1}" "read of forward table failed: No such device"
> +
> +	echo "port no	mac addr		is local?	ageing timer"
> +	ifs="${IFS}"
> +	IFS='
> +'
> +	for s in $(${IP} -br link show type bridge_slave master "${1}"); do
> +		for fdb_entry in $(${BRIDGE} -s fdb show dev "${s%% *}"); do
> +			case ${fdb_entry} in
> +			*" ${1} "*) ;;
> +			*) continue ;;
> +			esac
> +
> +			case ${fdb_entry} in
> +			*" permanent"*)
> +				is_local="yes"
> +				timer="$(pad_width "0.00")"
> +				;;
> +			*)
> +				is_local="no"
> +				timer="$(parse_next used "${fdb_entry}")"
> +				timer="$(pad_width "${timer#*/}.00")"
> +				;;
> +			esac
> +
> +			port_no="$(parse_iplink port_no "${s%% *}")"
> +			port_no="${port_no#0x*}"
> +
> +			echo "$(pad_width "${port_no}" 3)	${fdb_entry%% *}	${is_local}		${timer}"
> +		done
> +	done
> +	IFS="${ifs}"
> +}
> +
> +
> +# Start here ###################################################################
> +
> +cmdname="${1}"
> +[ -z "${cmdname}" ] && usage
> +
> +for arg do
> +	case ${arg} in
> +	"-V"|"--v"*)
> +		echo "ip-link wrapper, compatible with bridge-utils, 1.6"
> +		exit 0
> +		;;
> +	"-h"|"--h"*)
> +		usage 0
> +		;;
> +	"--")
> +		usage
> +		;;
> +	"-")
> +		break
> +		;;
> +	"-"*)
> +		echo "${0}: unrecognized option '${arg}'" >/dev/stderr
> +		echo "Unknown option '?'" >/dev/stderr
> +		usage
> +		;;
> +	esac
> +done
> +
> +found=0
> +min_arg=
> +for cmd in ${commands}; do
> +	[ -n "${min_arg}" ] && [ "${cmd}" = "${cmdname}" ] && found=1 && break
> +	min_arg="${cmd}"
> +done
> +[ ${found} -eq 0 ] && echo "never heard of command [${cmdname}]" && usage
> +[ ${#} -le "${min_arg}" ] && echo "Incorrect number of arguments for command" && usage_one "${cmd}"
> +
> +shift
> +eval "cmd_${cmdname}" "${@}"
> +
> +exit 0