ref: c690c0b9f3074708f33c4d982e26b67de1dfcb95
parent: 2273121e1cfe765f30467bdab842cae8b0d88cd3
author: cinap_lenrek <cinap_lenrek@felloff.net>
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 < ðer->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)