shithub: riscv

Download patch

ref: c690c0b9f3074708f33c4d982e26b67de1dfcb95
parent: 2273121e1cfe765f30467bdab842cae8b0d88cd3
author: cinap_lenrek <[email protected]>
date: Sun Jul 16 12:15:09 EDT 2023

ethermultilink: switch between different physical interfaces based on link status

This adds the capability of specifying multiple
interfaces on bootargs like:

bootargs=tls!ether /net/ether1 ether /net/ether0 ...

which will be combined into a bridge and the new
ethermultilink script can dynamically add/remove
the interfaces based on link status.

a ethersink interface is used as the primary,
taking the mac address of the first secondary
interface.

this required some changes in how ethernets
and bridges interact, as bridge mode on a
ethernet interface would only forward
frames not desinted to the interfaces mac address.

we make promisc mode ethernet connections never
loop-back the frames written them
and we add a new "ethermac" type to devbridge
that uses promisc mode only without setting
bridge flag.

that way, we can attach a ethernet to a bridge
and get all its frames.

the result is that we can specify the wifi
interface as the first interface and ethernet
as the second interface and the system
will roam to ethernet transparently when the
ethernet cable is plugged in and switch back
to wifi when ethernet cable is disconnected.

--- /dev/null
+++ b/rc/bin/ethermultilink
@@ -1,0 +1,75 @@
+#!/bin/rc
+
+# ethermultilink outpus bridge(3) commands to switch
+# between multiple ethernet (or wifi) interfaces
+# depending on their link status.
+# the first argument is the primary interface,
+# which is permanently added to the bridge
+# while the following arguments are for secondary
+# interfaces in increasing priority order.
+# only the highest priority active interface is bound.
+
+rfork e
+
+fn usage {
+	echo 'Usage: ' $0 'primaryether secondaryether1 [secondaryether2 ....] > /net/bridgeX/ctl' >[1=2]
+	exit 'usage'
+}
+fn missing {
+	echo 'missing: ' $1 >[1=2]
+	exit 'missing'
+}
+
+
+~ $#* 0 1 && usage
+
+# make sure arguments are ethernets
+for(i){
+	test -r $i/stats || missing $i/stats
+}
+
+# first interface is the primary
+primary=$1
+shift
+
+ea=`{cat $primary/addr} || missing $primary/addr
+net=`{echo $primary | sed 's!/*[^/]*$!!g'}
+test -r $net/arp || missing $net/arp
+
+# insert the primary to bridge
+echo bind ether primary 0 $primary || exit
+
+# now select secondary from the list depending on link status
+@{
+type=none
+old=/dev/null
+while(){
+	# interfaces are in increasing priority order
+	for(i){
+		if(! ~ $i $primary && grep -s 'link: 1' $i/stats)
+			secondary=$i
+	}
+	if(! ~ $secondary $old){
+		echo $primary is switching from $old to $secondary >[1=2]
+
+		if(! ~ $type none){
+			echo unbind $type secondary 0
+		}
+
+		# if the secondary has the same ea as the primary,
+		# we need to bind it in non-bridge mode
+		type=ether
+		if(~ $ea `{cat $secondary/addr})
+			type=ethermac
+
+		echo bind $type secondary 0 $secondary
+
+		# make switches aware of the new path
+		echo flush > $net/arp
+	}
+	old=$secondary
+	sleep 1
+}
+} </dev/null &
+
+exit ''
--- a/sys/man/3/bridge
+++ b/sys/man/3/bridge
@@ -67,6 +67,12 @@
 .B vlan
 command below.
 .TP
+.BI "bind ethermac " "name ownhash path [pvid[#prio][,vlans...]]"
+This is the same as
+.I ether
+above, but forwards all frames, including frames destined to the
+interfaces mac address.
+.TP
 .BI "bind tunnel " "name ownhash path path2 [pvid[#prio][,vlans...]]"
 Treat the device mounted at
 .I path
--- a/sys/src/9/boot/bootfs.proto
+++ b/sys/src/9/boot/bootfs.proto
@@ -54,6 +54,7 @@
 	bin
 		fstype
 		diskparts
+		ethermultilink
 		srvtls
 		nusbrc 555 sys sys ../boot/nusbrc
 		bootrc 555 sys sys ../boot/bootrc
--- a/sys/src/9/boot/net.rc
+++ b/sys/src/9/boot/net.rc
@@ -1,5 +1,25 @@
 #!/bin/rc
 
+fn wifi{
+	if(grep -s '^essid: ' $1/ifstats){
+		if(~ $#essid 0)
+			essid=`{grep '^essid: ' $1/ifstats >[2]/dev/null | sed 's/^essid: //; q'}
+		if(! ~ $#essid 0){
+			x=(aux/wpa -s $"essid)
+			if(! ~ $#wpapsk 0){
+				echo 'key proto=wpapsk' `{!password=$"wpapsk whatis essid !password} > /mnt/factotum/ctl
+				wpapsk=()
+			}
+			if not {
+				x=($x -p)		
+			}
+			$x $1
+		}
+		essid=()
+		rm -f /env/^(essid wpapsk)
+	}
+}
+
 fn confignet{
 	# get primary default interface if not specified
 	if(~ $#* 0){
@@ -8,24 +28,27 @@
 			*=(ether $e(1))
 	}
 
-	# setup wifi encryption if any
-	if(~ $1 ether && test -x /bin/aux/wpa){
-		essid=`{grep '^essid: ' $2/ifstats >[2]/dev/null | sed 's/^essid: //; q'}
-		if(! ~ $#essid 0){
-			if(! ~ $#wpapsk 0 || grep -s '^status: need authentication' $2/ifstats >[2]/dev/null){
-				x=(aux/wpa -s $"essid)
-				if(! ~ $#wpapsk 0){
-					echo 'key proto=wpapsk' `{!password=$"wpapsk whatis essid !password} > /mnt/factotum/ctl
-					wpapsk=()
-				}
-				if not {
-					x=($x -p)		
-				}
-				$x $2
-			}
-			essid=()
+	# if ethernet, handle wifi and multilink
+	if( ~ $1 ether gbe){
+		t=$1
+		shift
+		wifi $1
+		e=$1
+		shift
+		# if multiple ethernets specified...
+		while(~ $1 ether gbe){
+			shift
+			e=($e $1)
+			shift
 		}
-		rm -f /env/^(essid wpapsk)
+		# ...make them into a multilink bridge
+		if(! ~ $#e 1){
+			bind -a '#B15' /net
+			bind -a '#l15:sink ea='^`{cat $e(1)^/addr} /net
+			ethermultilink /net/ether15 $e > /net/bridge15/ctl
+			e=/net/ether15
+		}
+		*=($t $e $*)
 	}
 
 	if(~ $1 ether gbe && ~ $#* 2) @{
--- a/sys/src/9/port/devbridge.c
+++ b/sys/src/9/port/devbridge.c
@@ -69,11 +69,13 @@
 
 enum {
 	Tether,
+	Tethermac,
 	Ttun,
 };
 
 static char *typstr[] = {
 	"ether",
+	"ethermac",
 	"tunnel",
 };
 
@@ -649,6 +651,7 @@
 	default:
 		error(usage);
 	case Tether:
+	case Tethermac:
 		if(argc > 4)
 			vlan = argv[4];
 		break;
@@ -682,6 +685,7 @@
 	default:
 		panic("portbind: unknown port type: %d", type);
 	case Tether:
+	case Tethermac:
 		snprint(path, sizeof(path), "%s/clone", dev);
 		ctl = namec(path, Aopen, ORDWR, 0);
 		if(waserror()) {
@@ -699,10 +703,14 @@
 		devtab[ctl->type]->write(ctl, buf, strlen(buf), 0);
 		snprint(buf, sizeof(buf), "nonblocking");
 		devtab[ctl->type]->write(ctl, buf, strlen(buf), 0);
+
 		snprint(buf, sizeof(buf), "promiscuous");
 		devtab[ctl->type]->write(ctl, buf, strlen(buf), 0);
-		snprint(buf, sizeof(buf), "bridge");
-		devtab[ctl->type]->write(ctl, buf, strlen(buf), 0);
+
+		if(port->type != Tethermac){
+			snprint(buf, sizeof(buf), "bridge");
+			devtab[ctl->type]->write(ctl, buf, strlen(buf), 0);
+		}
 
 		/* open data port */
 		port->data[0] = namec(path, Aopen, ORDWR, 0);
--- a/sys/src/9/port/devether.c
+++ b/sys/src/9/port/devether.c
@@ -204,6 +204,8 @@
 	dispose = tome || from == nil || port > 0;
 
 	for(fp = ether->f; fp < &ether->f[Ntypes]; fp++){
+		if(fp == from)
+			continue;
 		if((f = *fp) == nil)
 			continue;
 		if(f->type != type && f->type >= 0)
@@ -211,7 +213,7 @@
 		if(!tome && !multi && !f->prom)
 			continue;
 		if(f->bridge){
-			if(tome || fp == from)
+			if(tome)
 				continue;
 			if(port >= 0 && port != 1+(fp - ether->f))
 				continue;
@@ -254,7 +256,7 @@
 static void
 etheroq(Ether* ether, Block* bp, Netfile **from)
 {
-	if((*from)->bridge == 0)
+	if((*from)->prom == 0)
 		memmove(((Etherpkt*)bp->rp)->s, ether->ea, Eaddrlen);
 
 	bp = ethermux(ether, bp, from);
--- a/sys/src/cmd/nusb/ether/ether.c
+++ b/sys/src/cmd/nusb/ether/ether.c
@@ -785,7 +785,7 @@
 	dispose = tome || from == nil || port > 0;
 
 	for(c = conn; c < &conn[nconn]; c++){
-		if(!c->used)
+		if(!c->used || c == from)
 			continue;
 		if(c->type != type && c->type >= 0)
 			continue;
@@ -792,7 +792,7 @@
 		if(!tome && !multi && !c->prom)
 			continue;
 		if(c->bridge){
-			if(tome || c == from)
+			if(tome)
 				continue;
 			if(port >= 0 && port != 1+(c - conn))
 				continue;
@@ -828,7 +828,7 @@
 static void
 etheroq(Block *bp, Conn *from)
 {
-	if(!from->bridge)
+	if(!from->prom)
 		memmove(((Etherpkt*)bp->rp)->s, macaddr, Eaddrlen);
 	bp = ethermux(bp, from);
 	if(bp == nil)