add vfconfig
authorJo-Philipp Wich <jo@mein.io>
Thu, 23 Jul 2020 10:50:48 +0000 (12:50 +0200)
committerJo-Philipp Wich <jo@mein.io>
Thu, 6 Aug 2020 06:34:19 +0000 (08:34 +0200)
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
package/network/config/vfconfig/Makefile [new file with mode: 0644]
package/network/config/vfconfig/files/vfconfig.hotplug [new file with mode: 0644]
package/network/config/vfconfig/files/vfconfig.include [new file with mode: 0755]
package/network/config/vfconfig/files/vfconfig.sh [new file with mode: 0755]

diff --git a/package/network/config/vfconfig/Makefile b/package/network/config/vfconfig/Makefile
new file mode 100644 (file)
index 0000000..1fb62d7
--- /dev/null
@@ -0,0 +1,40 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=vfconfig
+PKG_RELEASE:=1
+PKG_LICENSE:=GPL-2.0
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/vfconfig
+  SECTION:=net
+  CATEGORY:=Network
+  MAINTAINER:=Jo-Philipp Wich <jo@mein.io>
+  TITLE:=UCI configuration support for VLAN filtering rules
+  DEPENDS:= +ip-bridge +ip-full
+  PKGARCH:=all
+endef
+
+define Package/vfconfig/description
+ This package provides UCI configuration abstraction for VLAN filter rules
+ on top of Linux bridge interfaces.
+endef
+
+define Build/Compile
+endef
+
+define Build/Configure
+endef
+
+define Package/vfconfig/install
+       $(INSTALL_DIR) $(1)/etc/hotplug.d/iface
+       $(INSTALL_DATA) ./files/vfconfig.hotplug $(1)/etc/hotplug.d/iface/01-vfconfig
+
+       $(INSTALL_DIR) $(1)/lib/network
+       $(INSTALL_DATA) ./files/vfconfig.include $(1)/lib/network/vfconfig.sh
+
+       $(INSTALL_DIR) $(1)/sbin
+       $(INSTALL_BIN) ./files/vfconfig.sh $(1)/sbin/vfconfig
+endef
+
+$(eval $(call BuildPackage,vfconfig))
diff --git a/package/network/config/vfconfig/files/vfconfig.hotplug b/package/network/config/vfconfig/files/vfconfig.hotplug
new file mode 100644 (file)
index 0000000..fc2a489
--- /dev/null
@@ -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/vfconfig/files/vfconfig.include b/package/network/config/vfconfig/files/vfconfig.include
new file mode 100755 (executable)
index 0000000..4ac11d8
--- /dev/null
@@ -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/vfconfig/files/vfconfig.sh b/package/network/config/vfconfig/files/vfconfig.sh
new file mode 100755 (executable)
index 0000000..5a73c62
--- /dev/null
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+. /lib/functions.sh
+. /lib/functions/network.sh
+
+bridge_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
+}
+
+add_bridge() {
+       local cfg=$1
+       local brname
+
+       config_get brname "$cfg" bridge "switch0"
+
+       case " $bridge_names " in
+               *" $brname "*) return 1 ;;
+       esac
+
+       append bridge_names "$brname"
+
+       export -n "bridge=$brname"
+}
+
+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_bridge() {
+       local cfg=$1
+       local bridge
+
+       add_bridge "$cfg" || return 0
+
+       # Prevent netifd from picking up our switch bridge just yet
+       network_defer_device "$bridge"
+
+       # (Re)create switch bridge device in case it is not yet set up
+       local filtering=$(cat "/sys/class/net/$bridge/bridge/vlan_filtering" 2>/dev/null)
+       if [ ${filtering:-0} != 1 ]; then
+               ip link set "$bridge" down 2>/dev/null
+               ip link delete dev "$bridge" 2>/dev/null
+               ip link add name "$bridge" type bridge
+               echo 1 > "/sys/class/net/$bridge/bridge/vlan_filtering"
+       fi
+
+       ip link set "$bridge" 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 "$bridge" self
+}
+
+setup_bridge_vlan() {
+       local cfg=$1
+       local bridge vlan ports
+
+       config_get bridge "$cfg" bridge "switch0"
+       config_get vlan "$cfg" vlan
+       config_get ports "$cfg" ports
+
+       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=untagged
+               pvid=
+
+               case "$port" in
+                       *"*")
+                               pvid=pvid
+                               port=${port%\*}
+                       ;;
+               esac
+
+               case "$port" in
+                       *:u)
+                               port=${port%:u}
+                       ;;
+                       *:t)
+                               tag=tagged
+                               port=${port%:t}
+                       ;;
+               esac
+
+               # Add the port to the switch bridge and delete the default
+               # VLAN 1 if it is not yet joined to the bridge.
+               if [ ! -e "/sys/class/net/$port/upper_$bridge" ]; then
+                       ip link set dev "$port" up
+                       ip link set dev "$port" master "$bridge"
+
+                       # 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 "$bridge" self vid $vlan tagged
+}
+
+apply_config() {
+       config_load network
+       config_foreach setup_bridge vlan_filter
+       config_foreach setup_bridge_vlan vlan_filter
+
+       # Ready switch bridge devices
+       local bridge
+       for bridge in $bridge_names; do
+               network_ready_device "$bridge"
+       done
+}
+
+show_bridge() {
+       local cfg=$1
+       local bridge
+
+       add_bridge "$cfg" || return 0
+
+       printf "Bridge: %s\n" "$bridge"
+       printf "VLAN/"
+
+       local port ports
+       for port in "/sys/class/net/$bridge/lower_"*; do
+               [ -e "$port" ] || continue
+
+               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 "$bridge" | 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"
+}
+
+show_config() {
+       config_load network
+       config_foreach show_bridge vlan_filter
+}
+
+
+case "$1" in
+       show) show_config ;;
+       apply) apply_config ;;
+       *)
+               echo "Usage: ${0##*/} show"
+               echo "       ${0##*/} apply"
+               exit 1
+       ;;
+esac