From patchwork Tue Jul 7 13:34:53 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jo-Philipp Wich X-Patchwork-Id: 1324350 X-Patchwork-Delegate: jow@openwrt.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.openwrt.org (client-ip=2001:8b0:10b:1231::1; helo=merlin.infradead.org; envelope-from=openwrt-devel-bounces+incoming=patchwork.ozlabs.org@lists.openwrt.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=mein.io Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; secure) header.d=lists.infradead.org header.i=@lists.infradead.org header.a=rsa-sha256 header.s=merlin.20170209 header.b=KMFSYcWA; dkim-atps=neutral Received: from merlin.infradead.org (merlin.infradead.org [IPv6:2001:8b0:10b:1231::1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4B1Nkz60RLz9s1x for ; Tue, 7 Jul 2020 23:36:43 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=merlin.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe :List-Id:MIME-Version:References:In-Reply-To:Message-Id:Date:Subject:To:From: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=w+3gDp4uctDu9fjrbK8Bl3+GGO38CmQNMhkIh22uu3k=; b=KMFSYcWARRQxsu1R79gYmE3PWk zn0yqh30tMiSsu4dpE1YTRiQAGe4Bdb7f7WxDuQlBB2Mjdha6KRri2nO8E/Xdal4EWxi81wqBDeYx DJiEUCTIQrykJKNQwCxRn+CMKsKqQNnhU3QWS9RTbYmcVNgzQggXNhnAcqlxvzsJ7x6181h14jXr8 mchLhy1RGPV5CKqjRKggG3SKsAQQHrBLdDvHTUEzRVV5Tm4vrkF2ZFiQZB89L8NV5102gkewfHRCG niKefrV9uVaWiqESSSl3JB51uU0aScJ5lXRBT8tvFCNDu+qGhVBs7o6obg6RL1FP9eBD9teqcrwKR WnMqxOpQ==; Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.92.3 #3 (Red Hat Linux)) id 1jsnkT-0003qR-Oo; Tue, 07 Jul 2020 13:35:01 +0000 Received: from mxout01.bytecamp.net ([212.204.60.217]) by merlin.infradead.org with esmtps (Exim 4.92.3 #3 (Red Hat Linux)) id 1jsnkQ-0003pC-J4 for openwrt-devel@lists.openwrt.org; Tue, 07 Jul 2020 13:35:00 +0000 Received: by mxout01.bytecamp.net (Postfix, from userid 1001) id 081D76EB75; Tue, 7 Jul 2020 15:34:56 +0200 (CEST) Received: from mail.bytecamp.net (mail.bytecamp.net [212.204.60.9]) by mxout01.bytecamp.net (Postfix) with ESMTP id C4CC66EB71 for ; Tue, 7 Jul 2020 15:34:55 +0200 (CEST) Received: (qmail 28023 invoked from network); 7 Jul 2020 15:34:55 +0200 Received: from unknown (HELO j460.lan) (jo%wwsnet.net@24.134.185.161) by mail.bytecamp.net with ESMTPS (DHE-RSA-AES128-GCM-SHA256 encrypted); 7 Jul 2020 15:34:55 +0200 From: Jo-Philipp Wich To: jo@mein.io, openwrt-devel@lists.openwrt.org Subject: [PATCH 1/1] dsaconfig: introduce package for UCI configuration of VLAN filter rules Date: Tue, 7 Jul 2020 15:34:53 +0200 Message-Id: <20200707133453.20440-2-jo@mein.io> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200707133453.20440-1-jo@mein.io> References: <20200707133453.20440-1-jo@mein.io> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20200707_093458_769479_78F05B80 X-CRM114-Status: GOOD ( 17.79 ) X-Spam-Score: 0.0 (/) X-Spam-Report: SpamAssassin version 3.4.4 on merlin.infradead.org summary: Content analysis details: (0.0 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [212.204.60.217 listed in list.dnswl.org] 0.0 RCVD_IN_MSPIKE_H4 RBL: Very Good reputation (+4) [212.204.60.217 listed in wl.mailspike.net] 0.0 SPF_NONE SPF: sender does not publish an SPF Record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.0 RCVD_IN_MSPIKE_WL Mailspike good senders X-BeenThere: openwrt-devel@lists.openwrt.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: OpenWrt Development List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "openwrt-devel" Errors-To: openwrt-devel-bounces+incoming=patchwork.ozlabs.org@lists.openwrt.org This package provides the necessary files to translate `config switch_vlan` and `config switch_port` sections of `/etc/config/network` into appropriate bridge vlan filter rules. The approach of the configuration is to bridge all DSA ports into a logical bridge device, called "switch0" by default, and to set VLAN port membership, tagging state and PVID as specified by UCI on each port and on the switch bridge device itself, allowing logical interfaces to reference port VLAN groups by using "switch0.N" as ifname, where N denotes the VLAN ID. Signed-off-by: Jo-Philipp Wich --- package/network/config/dsaconfig/Makefile | 40 +++ .../config/dsaconfig/files/dsaconfig.hotplug | 7 + .../config/dsaconfig/files/dsaconfig.include | 11 + .../config/dsaconfig/files/dsaconfig.sh | 296 ++++++++++++++++++ 4 files changed, 354 insertions(+) create mode 100644 package/network/config/dsaconfig/Makefile create mode 100644 package/network/config/dsaconfig/files/dsaconfig.hotplug create mode 100755 package/network/config/dsaconfig/files/dsaconfig.include create mode 100755 package/network/config/dsaconfig/files/dsaconfig.sh 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 + 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..0182f340ba --- /dev/null +++ b/package/network/config/dsaconfig/files/dsaconfig.sh @@ -0,0 +1,296 @@ +#!/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 + [0-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/master" ]; 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 switch + + # 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 switch_vlan + config_foreach setup_switch_port switch_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="t" word + for word in $(bridge vlan show dev "$port" vid "$vlan"); do + case "$word" in + PVID) pvid="*" ;; + Untagged) utag="u" ;; + esac + done + + printf " | %-2s " "$utag$pvid" + done + done + + printf " |\n\n" +} + +show_config() { + config_load network + config_foreach show_switch switch +} + + +case "$1" in + show) show_config ;; + apply) apply_config ;; + *) + echo "Usage: ${0##*/} show" + echo " ${0##*/} apply" + exit 1 + ;; +esac