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.
This commit is contained in:
cinap_lenrek 2023-07-16 16:15:09 +00:00
parent 2273121e1c
commit c690c0b9f3
7 changed files with 139 additions and 24 deletions

75
rc/bin/ethermultilink Executable file
View file

@ -0,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 ''

View file

@ -67,6 +67,12 @@ argument is explained in
.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

View file

@ -54,6 +54,7 @@ rc
bin
fstype
diskparts
ethermultilink
srvtls
nusbrc 555 sys sys ../boot/nusbrc
bootrc 555 sys sys ../boot/bootrc

View file

@ -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 @@ fn confignet{
*=(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) @{

View file

@ -69,11 +69,13 @@ static Logflag logflags[] = {
enum {
Tether,
Tethermac,
Ttun,
};
static char *typstr[] = {
"ether",
"ethermac",
"tunnel",
};
@ -649,6 +651,7 @@ portbind(Bridge *b, int argc, char *argv[])
default:
error(usage);
case Tether:
case Tethermac:
if(argc > 4)
vlan = argv[4];
break;
@ -682,6 +685,7 @@ portbind(Bridge *b, int argc, char *argv[])
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 @@ portbind(Bridge *b, int argc, char *argv[])
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);

View file

@ -204,6 +204,8 @@ ethermux(Ether *ether, Block *bp, Netfile **from)
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 @@ ethermux(Ether *ether, Block *bp, Netfile **from)
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 @@ etheriq(Ether* ether, Block* bp)
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);

View file

@ -785,14 +785,14 @@ ethermux(Block *bp, Conn *from)
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;
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 @@ etheriq(Block *bp)
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)