ethermultilink: add "bypass" ctl for ethernet

Instead of having a ethersink with the same mac, implement
a "bypass" mechanism that lets us get access to the interfaces
frames and bypass the physical link.

Now no extra ethersink is required and a pre-existing
interface can be bridged transparently.
This commit is contained in:
cinap_lenrek 2023-07-22 17:23:06 +00:00
parent 06ba01b816
commit 216ecc5460
9 changed files with 103 additions and 66 deletions

View file

@ -3,11 +3,6 @@
# 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
@ -30,44 +25,36 @@ for(i){
# 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
new=$old
while(){
# interfaces are in increasing priority order
for(i){
if(! ~ $i $primary && grep -s 'link: 1' $i/stats)
secondary=$i
if(grep -s 'link: 1' $i/stats)
new=$i
}
if(! ~ $secondary $old){
echo $primary is switching from $old to $secondary >[1=2]
if(! ~ $type none){
echo unbind $type secondary 0
if(! ~ $new $old){
if(! ~ $old /dev/null){
if(! ~ $old $primary) {
echo unbind bypass primary 0
echo unbind ether secondary 0
}
}
if(! ~ $new $primary){
echo bind bypass primary 0 $primary
echo bind ether secondary 0 $new
}
# 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=$new
}
old=$secondary
sleep 1
}
} </dev/null &

View file

@ -67,11 +67,12 @@ argument is explained in
.B vlan
command below.
.TP
.BI "bind ethermac " "name ownhash path [pvid[#prio][,vlans...]]"
.BI "bind bypass " "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.
above, but bypasses the physical interface, ignoring all received
frames from the interface and diverts all frames
transmitted on that interface to the bridge.
.TP
.BI "bind tunnel " "name ownhash path path2 [pvid[#prio][,vlans...]]"
Treat the device mounted at

View file

@ -15,13 +15,13 @@ Ethermultilink takes a
ethernet interface and a list of
.I secondary
interfaces in increasing priority order.
It outputs
It checks the link status of the
secondary interfaces and if any are
link-active, bridges the primary
and the highest priority secondary
interface together by outputting
.IR bridge (4)
commands that add the
.I primary
and the highest priority link-active
.I secondary
interface to the bridge.
commands on standard output.
The link status of all the secondary interfaces
is checked once per second and the commands to
change the active secondary are output as needed.
@ -33,22 +33,12 @@ bind -a '#B' /net
bind -a '#l0' /net
# mount the wifi
bind -a '#l1' /net
# create the new virtual interface with same mac as wifi
bind -a '#l2:sink ea='^`{cat /net/ether1/addr} /net
# roam between wifi and ethernet
ethermultilink /net/ether2 /net/ether1 /net/ether0 > /net/bridge0/ctl
# use the new virtual interface...
ip/ipconfig ether /net/ether2
ethermultilink /net/ether1 /net/ether0 > /net/bridge0/ctl
# setup ip on the primary
ip/ipconfig ether /net/ether1
.EE
.SH SOURCE
.B /rc/bin/ethermultilink
.SH "SEE ALSO"
.IR bridge (3).
.SH BUGS
The
.I secondary
interfaces should not
be bound to an IP stack,
only the
.I primary
interface should be used.

View file

@ -44,9 +44,8 @@ fn confignet{
# ...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
ethermultilink $e > /net/bridge15/ctl
e=$e(1)
}
*=($t $e $*)
}

View file

@ -69,11 +69,13 @@ static Logflag logflags[] = {
enum {
Tether,
Tbypass,
Ttun,
};
static char *typstr[] = {
"ether",
"bypass",
"tunnel",
};
@ -649,6 +651,7 @@ portbind(Bridge *b, int argc, char *argv[])
default:
error(usage);
case Tether:
case Tbypass:
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 Tbypass:
snprint(path, sizeof(path), "%s/clone", dev);
ctl = namec(path, Aopen, ORDWR, 0);
if(waserror()) {
@ -699,8 +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);
if(type == Tbypass){
snprint(buf, sizeof(buf), "bypass");
devtab[ctl->type]->write(ctl, buf, strlen(buf), 0);
} else {
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);

View file

@ -172,6 +172,7 @@ ethermux(Ether *ether, Block *bp, Netfile **from)
len = BLEN(bp);
if(len < ETHERHDRSIZE)
goto Drop;
pkt = (Etherpkt*)bp->rp;
if(!(multi = pkt->d[0] & 1)){
tome = memcmp(pkt->d, ether->ea, Eaddrlen) == 0;
@ -210,7 +211,7 @@ ethermux(Ether *ether, Block *bp, Netfile **from)
continue;
if(!tome && !multi && !f->prom)
continue;
if(f->bridge){
if(f->bridge || f->bypass){
if(tome || fp == from)
continue;
if(port >= 0 && port != 1+(fp - ether->f))
@ -247,6 +248,10 @@ Drop: freeb(bp);
void
etheriq(Ether* ether, Block* bp)
{
if(ether->bypass != nil){
freeb(bp);
return;
}
ether->inpackets++;
ethermux(ether, bp, nil);
}
@ -254,13 +259,19 @@ etheriq(Ether* ether, Block* bp)
static void
etheroq(Ether* ether, Block* bp, Netfile **from)
{
Netfile *x;
if((*from)->bridge == 0)
memmove(((Etherpkt*)bp->rp)->s, ether->ea, Eaddrlen);
bp = ethermux(ether, bp, from);
if(bp == nil)
return;
if((x = ether->bypass) != nil){
if(qpass(x->in, bp) < 0)
ether->soverflows++;
return;
}
ether->outpackets++;
qbwrite(ether->oq, bp);
if(ether->transmit != nil)

View file

@ -334,6 +334,11 @@ netifwrite(Netif *nif, Chan *c, void *a, long n)
}
} else if(matchtoken(buf, "bridge")){
f->bridge = 1;
} else if(matchtoken(buf, "bypass")){
if(nif->bypass != nil)
error(Einuse);
f->bypass = 1;
nif->bypass = f;
} else if(matchtoken(buf, "headersonly")){
f->headersonly = 1;
} else if((p = matchtoken(buf, "addmulti")) != 0){
@ -408,6 +413,12 @@ netifclose(Netif *nif, Chan *c)
f = nif->f[NETID(c->qid.path)];
qlock(f);
if(--(f->inuse) == 0){
if(f->bypass){
qlock(nif);
nif->bypass = nil;
qunlock(nif);
f->bypass = 0;
}
if(f->prom){
qlock(nif);
if(--(nif->prom) == 0 && nif->promiscuous != nil)

View file

@ -38,10 +38,13 @@ struct Netfile
char owner[KNAMELEN];
int type; /* multiplexor type */
int prom; /* promiscuous mode */
int scan; /* base station scanning interval */
int bridge; /* bridge mode */
int headersonly; /* headers only - no data */
char prom; /* promiscuous mode */
char scan; /* base station scanning interval */
char bridge; /* bridge mode */
char bypass; /* bypass transmission */
char headersonly; /* headers only - no data */
uchar maddr[8]; /* bitmask of multicast addresses requested */
int nmaddr; /* number of multicast addresses */
@ -70,6 +73,7 @@ struct Netif
char name[KNAMELEN]; /* for top level directory */
int nfile; /* max number of Netfiles */
Netfile **f;
Netfile *bypass;
/* about net */
int limit; /* flow control */

View file

@ -66,9 +66,11 @@ struct Conn
int used;
int type;
int prom;
int bridge;
int headersonly;
char prom;
char bridge;
char bypass;
char headersonly;
Dq *dq;
};
@ -83,6 +85,7 @@ uchar bcast[Eaddrlen] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
Stats stats;
Conn conn[32];
Conn *bypass;
int nconn = 0;
int nprom = 0;
@ -397,6 +400,13 @@ fswrite(Req *r)
p = (char*)r->ifcall.data;
if(n >= 6 && memcmp(p, "bridge", 6)==0){
conn[NUM(path)].bridge = 1;
} else if(n >= 6 && memcmp(p, "bypass", 6)==0){
if(bypass != nil){
respond(r, "bypass in use");
return;
}
conn[NUM(path)].bypass = 1;
bypass = &conn[NUM(path)];
} else if(n >= 11 && memcmp(p, "headersonly", 11)==0){
conn[NUM(path)].headersonly = 1;
} else if(n >= 11 && memcmp(p, "promiscuous", 11)==0){
@ -506,6 +516,7 @@ fsopen(Req *r)
c->type = 0;
c->prom = 0;
c->bridge = 0;
c->bypass = 0;
c->headersonly = 0;
}
if(d != nil){
@ -581,7 +592,10 @@ fsdestroyfid(Fid *fid)
}
if(TYPE(fid->qid.path) == Qdata && c->bridge)
memset(mactab, 0, sizeof(mactab));
c->used--;
if(--c->used == 0){
if(c->bypass)
bypass = nil;
}
qunlock(c);
}
}
@ -791,7 +805,7 @@ ethermux(Block *bp, Conn *from)
continue;
if(!tome && !multi && !c->prom)
continue;
if(c->bridge){
if(c->bridge || c->bypass){
if(tome || c == from)
continue;
if(port >= 0 && port != 1+(c - conn))
@ -821,6 +835,10 @@ Drop: freeb(bp);
void
etheriq(Block *bp)
{
if(bypass != nil){
freeb(bp);
return;
}
stats.in++;
ethermux(bp, nil);
}
@ -828,11 +846,17 @@ etheriq(Block *bp)
static void
etheroq(Block *bp, Conn *from)
{
Conn *x;
if(!from->bridge)
memmove(((Etherpkt*)bp->rp)->s, macaddr, Eaddrlen);
bp = ethermux(bp, from);
if(bp == nil)
return;
if((x = bypass) != nil){
cpass(x, bp);
return;
}
stats.out++;
/* transmit frees buffer */
(*eptransmit)(epout, bp);