diff options
-rw-r--r-- | package/network/config/dsaconfig/Makefile | 40 | ||||
-rw-r--r-- | package/network/config/dsaconfig/files/dsaconfig.hotplug | 7 | ||||
-rwxr-xr-x | package/network/config/dsaconfig/files/dsaconfig.include | 11 | ||||
-rwxr-xr-x | package/network/config/dsaconfig/files/dsaconfig.sh | 306 |
4 files changed, 364 insertions, 0 deletions
diff --git a/package/network/config/dsaconfig/Makefile b/package/network/config/dsaconfig/Makefile new file mode 100644 index 0000000000..4069022224 --- /dev/null +++ b/package/network/config/dsaconfig/Makefile @@ -0,0 +1,40 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=dsaconfig +PKG_RELEASE:=1 +PKG_LICENSE:=GPL-2.0 + +include $(INCLUDE_DIR)/package.mk + +define Package/dsaconfig + SECTION:=net + CATEGORY:=Network + MAINTAINER:=Jo-Philipp Wich <jo@mein.io> + TITLE:=UCI configuration support for DSA switch VLAN filtering + DEPENDS:= +ip-bridge +ip-full + PKGARCH:=all +endef + +define Package/dsaconfig/description + This package provides UCI configuration abstraction for VLAN filter rules + on top of DSA switches. +endef + +define Build/Compile +endef + +define Build/Configure +endef + +define Package/dsaconfig/install + $(INSTALL_DIR) $(1)/etc/hotplug.d/iface + $(INSTALL_DATA) ./files/dsaconfig.hotplug $(1)/etc/hotplug.d/iface/01-dsaconfig + + $(INSTALL_DIR) $(1)/lib/network + $(INSTALL_DATA) ./files/dsaconfig.include $(1)/lib/network/dsaconfig.sh + + $(INSTALL_DIR) $(1)/sbin + $(INSTALL_BIN) ./files/dsaconfig.sh $(1)/sbin/dsaconfig +endef + +$(eval $(call BuildPackage,dsaconfig)) diff --git a/package/network/config/dsaconfig/files/dsaconfig.hotplug b/package/network/config/dsaconfig/files/dsaconfig.hotplug new file mode 100644 index 0000000000..fc2a489dea --- /dev/null +++ b/package/network/config/dsaconfig/files/dsaconfig.hotplug @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ "$ACTION" = ifup ] && [ "$INTERFACE" = loopback ]; then + . /lib/network/dsaconfig.sh + setup_switch +fi + diff --git a/package/network/config/dsaconfig/files/dsaconfig.include b/package/network/config/dsaconfig/files/dsaconfig.include new file mode 100755 index 0000000000..4ac11d8061 --- /dev/null +++ b/package/network/config/dsaconfig/files/dsaconfig.include @@ -0,0 +1,11 @@ +#!/bin/sh + +setup_switch() { + # Skip switch setup on network restart. The netifd process + # will be started afterwards and remove all interfaces again... + if [ "$initscript" = /etc/init.d/network ] && [ "$action" = restart ]; then + return 0 + fi + + /sbin/dsaconfig apply 2>&1 | logger -t dsaconfig +} diff --git a/package/network/config/dsaconfig/files/dsaconfig.sh b/package/network/config/dsaconfig/files/dsaconfig.sh new file mode 100755 index 0000000000..f5769bf495 --- /dev/null +++ b/package/network/config/dsaconfig/files/dsaconfig.sh @@ -0,0 +1,306 @@ +#!/bin/sh + +. /lib/functions.sh +. /lib/functions/network.sh + +switch_names="" + +warn() { + echo "$@" >&2 +} + +clear_port_vlans() { + local port=$1 + local self=$2 + local vlans=$(bridge vlan show dev "$port" | sed -ne 's#^[^ ]* \+\([0-9]\+\).*$#\1#p') + + local vlan + for vlan in $vlans; do + bridge vlan del vid "$vlan" dev "$port" $self + done +} + +lookup_switch() { + local cfg=$1 + local swname + + config_get swname "$cfg" switch + + # Auto-determine switch if not specified ... + if [ -z "$swname" ]; then + case "$switch_names" in + *\ *) + warn "VLAN section '$cfg' does not specify a switch but multiple switches present, using first one" + swname=${switch_names%% *} + ;; + *) + swname=${switch_names} + ;; + esac + + # ... otherwise check if the referenced switch is declared + else + case " $switch_names " in + *" $swname "*) : ;; + *) + warn "Switch '$swname' specified by VLAN section '$cfg' does not exist" + return 1 + ;; + esac + fi + + export -n "switch=$swname" +} + +validate_vid() { + local vid=$1 + + case "$vid" in + [1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-4][0-9][0-9][0-9]) + if [ $vid -gt 4096 ]; then + return 1 + fi + ;; + *) + return 1 + ;; + esac + + return 0 +} + +setup_switch() { + local cfg=$1 + local cpu + + # Read configured CPU port from uci ... + config_get cpu "$cfg" cpu_port + + # ... if unspecified, find first CPU port + if [ -z "$cpu" ]; then + local e + for e in /sys/class/net/*/dsa; do + if [ -d "$e" ]; then + cpu=${e%/dsa} + cpu=${cpu##*/} + break + fi + done + fi + + # Bail out if we cannot determine the CPU port + if [ -z "$cpu" ]; then + warn "Unable to determine CPU port for switch '$cfg'" + return 1 + fi + + append switch_names "$cfg" + + # Prevent netifd from picking up our switch bridge just yet + network_defer_device "$cfg" + + # Increase MTU of CPU port to 1508 to accomodate for VID + DSA tag + ip link set "$cpu" mtu 1508 + ip link set "$cpu" up + + # (Re)create switch bridge device in case it is not yet set up + local filtering=$(cat "/sys/class/net/$cfg/bridge/vlan_filtering" 2>/dev/null) + if [ ${filtering:-0} != 1 ]; then + ip link set "$cfg" down 2>/dev/null + ip link delete dev "$cfg" 2>/dev/null + ip link add name "$cfg" type bridge + echo 1 > "/sys/class/net/$cfg/bridge/vlan_filtering" + fi + + ip link set "$cfg" up + + # Unbridge DSA ports and flush any VLAN filters on them, they're added back later + local port + for port in /sys/class/net/*"/upper_${cfg}"; do + if [ -e "$port" ]; then + port=${port%/upper_*} + port=${port##*/} + + ip link set "$port" nomaster + + # Unbridging the port should already clear VLANs, but be safe + clear_port_vlans "$port" + fi + done + + # Clear any VLANs on the switch bridge, they're added back later + clear_port_vlans "$cfg" self +} + +setup_switch_vlan() { + local cfg=$1 + local switch vlan ports + + config_get switch "$cfg" switch + config_get vlan "$cfg" vlan + config_get ports "$cfg" ports + + lookup_switch "$cfg" || return 1 + validate_vid "$vlan" || { + warn "VLAN section '$cfg' specifies an invalid VLAN ID '$vlan'" + return 1 + } + + # Setup ports + local port tag pvid + for port in $ports; do + tag=${port#*.} + port=${port%.*} + pvid= + + if [ "$tag" != "$port" ] && [ "$tag" = t ]; then + tag=tagged + else + tag=untagged + fi + + # Add the port to the switch bridge and delete the default + # VLAN 1 if it is not yet joined to the switch. + if [ ! -e "/sys/class/net/$port/upper_$switch" ]; then + ip link set dev "$port" up + ip link set dev "$port" master "$switch" + + # Get rid of default VLAN 1 + bridge vlan del vid 1 dev "$port" + fi + + # Promote the first untagged VLAN of this port to the PVID + if [ "$tag" = untagged ] && ! bridge vlan show dev "$port" | grep -qi pvid; then + pvid=pvid + fi + + # Add VLAN filter entry for port + bridge vlan add dev "$port" vid $vlan $pvid $tag + done + + # Make the switch bridge itself handle the VLAN as well + bridge vlan add dev "$switch" self vid $vlan tagged +} + +setup_switch_port() { + local cfg=$1 + local switch port pvid tag + + config_get port "$cfg" port + config_get pvid "$cfg" pvid + + lookup_switch "$cfg" || return 1 + validate_vid "$pvid" || { + warn "Port section '$cfg' specifies an invalid PVID '$pvid'" + return 1 + } + + # Disallow setting the PVID of the switch bridge itself + [ "$port" != "$switch" ] || { + warn "Port section '$cfg' must not change PVID of the switch bridge" + return 1 + } + + # Determine existing VLAN config + local vlanspec=$(bridge vlan show dev "$port" vid "$pvid" 2>/dev/null | sed -ne2p) + echo "$vlanspec" | grep -qi untagged && tag=untagged || tag=tagged + + bridge vlan add vid "$pvid" dev "$port" pvid $tag +} + +apply_config() { + config_load network + config_foreach setup_switch dsa + + # If no switch is explicitely declared, synthesize switch0 + if [ -z "$switch_names" ] && ! setup_switch switch0; then + warn "No DSA switches found" + return 1 + fi + + config_foreach setup_switch_vlan dsa_vlan + config_foreach setup_switch_port dsa_port + + # Ready switch bridge devices + local switch + for switch in $switch_names; do + network_ready_device "$switch" + done +} + +show_switch() { + local switch=$1 + + printf "Switch: %s\n" "$switch" + printf "VLAN/" + + local port ports + for port in "/sys/class/net/$switch/lower_"*; do + port=${port##*/lower_} + + + printf " | %-5s" "$port" + append ports "$port" + done + + printf " |\nLink:" + + for port in $ports; do + local carrier=$(cat "/sys/class/net/$port/carrier") + local duplex=$(cat "/sys/class/net/$port/duplex") + local speed=$(cat "/sys/class/net/$port/speed") + + if [ ${carrier:-0} -eq 0 ]; then + printf " | %-5s" "down" + else + [ "$duplex" = "full" ] && duplex=F || duplex=H + printf " | %4d%s" "$speed" "$duplex" + fi + done + + local vlans=$(bridge vlan show dev "$switch" | sed -ne 's#^[^ ]* \+\([0-9]\+\).*$#\1#p') + local vlan + for vlan in $vlans; do + printf " |\n%4d " "$vlan" + + for port in $ports; do + local pvid="" utag="" word + for word in $(bridge vlan show dev "$port" vid "$vlan"); do + case "$word" in + PVID) pvid="*" ;; + "$vlan") utag="t" ;; + Untagged) utag="u" ;; + esac + done + + printf " | %-2s " "$utag$pvid" + done + done + + printf " |\n\n" +} + +add_switch() { + append switch_names "$1" +} + +show_config() { + config_load network + config_foreach add_switch dsa + + local switch + for switch in ${switch_names:-switch0}; do + show_switch "$switch" + done +} + + +case "$1" in + show) show_config ;; + apply) apply_config ;; + *) + echo "Usage: ${0##*/} show" + echo " ${0##*/} apply" + exit 1 + ;; +esac |