devbridge: implement experimental vlan support

This commit is contained in:
cinap_lenrek 2022-02-16 22:38:03 +00:00
parent 7289f371a0
commit 4b637a24f9
2 changed files with 332 additions and 106 deletions

View file

@ -50,10 +50,8 @@ alters aspects of the interface.
The possible
.I ctl
messages are:
.TF cacheflush
.PD
.TP
.BI "bind ether " "name ownhash path"
.BI "bind ether " "name ownhash path [pvid[#prio][,vlans...]]"
Treat the device mounted at
.I path
(e.g.,
@ -63,8 +61,13 @@ and associate it with this bridge (forward its packets to the
other interfaces associated with this bridge).
.I Ownhash
is an `owner hash'.
The optional
.I [pvid[#prio][,vlans...]]
argument is explained in
.B vlan
command below.
.TP
.BI "bind tunnel " "name ownhash path path2
.BI "bind tunnel " "name ownhash path path2 [pvid[#prio][,vlans...]]"
Treat the device mounted at
.I path
as a network tunnel carrying Ethernet packets,
@ -81,9 +84,60 @@ interface and write them to the
interface.
Such tunnels have an MTU of 1400 bytes.
.TP
.BI "unbind " "type address [ownhash]"
.BI "vlan " "pvid[#prio][,vlans...] type name [ownhash]"
Change the 802.1Q VLAN configuration of a port identified by
.IR type ,
.I name
and
.I ownhash.
The
.I pvid
is the "Native" VLAN id associated with untagged packets
on a port.
Packets with a VLAN id of
.I pvid
will be send out untagged on the port.
Untagged packets received on the port will be tagged with the
.IR pvid .
When
.I pvid
was not specified in a
.B bind
command above,
ports default to a
.I pvid
of
.BR "1" .
The
.I pvid
can be specified as 0 when untagged packets should
be ignored on a port.
The optional
.I prio
argument specifies the default priority to assign to a
incoming untagged packet on the port between 0 (lowest,
default) and 7 (highest).
The
.I vlans...
argument specifies comma separated ranges of other
VLAN id's that the port is a member of (for trunk ports).
Membership means the port is accepting
tagged packets with VLAN id's listed here and
ignores VLAN id's not listed here. Once accepted,
packets are forwarded only to other ports that are
members of the same (accepted) VLAN id.
Unless
.I pvid
is
.BR "0" ,
a port is always a member of its own
.I pvid
VLAN id,
so it does not need to be listed twice.
.TP
.BI "unbind " "type name [ownhash]"
Disassociate the interface associated with
.I address
.I name
from this bridge.
.I Type
must be
@ -92,7 +146,7 @@ or
.LR tunnel .
.TP
.B cacheflush
Clear the cache of (destination MAC address, port) tuples.
Clear the cache of (destination MAC address, VLAN id, port) tuples.
.TP
.BI "delay " "delay0 delayn"
Set the
@ -129,10 +183,11 @@ and will block at end of file awaiting new data.
.PP
Reading the
.B cache
file prints the cache of (destination MAC address, port) tuples,
file prints the cache of (destination MAC address, VLAN id, port) tuples,
one entry per line.
The format is:
the destination MAC (e.g., Ethernet) address in hex,
VLAN id,
port number,
count of packets from this address,
count of packets to this address,
@ -163,7 +218,27 @@ bind -a '#l1' /net
echo 'bind ether outer 0 /net/ether0' >/net/bridge0/ctl
echo 'bind ether inner 0 /net/ether1' >/net/bridge0/ctl
.EE
.PP
Set up bridge1 with 3 access ports and a trunk port.
.IP
.EX
# create bridge1
bind -a '#B1' /net
# nic for trunk carrying only tagged vlans 10,20 and 30
bind -a '#l0' /net
echo 'bind ether trunk 0 /net/ether0 0,10,20,30' >/net/bridge1/ctl
# create virtual nics for access ports
bind -a '#l1:sink ea=001122334401' /net
bind -a '#l2:sink ea=001122334402' /net
bind -a '#l3:sink ea=001122334403' /net
echo 'bind ether port1 0 /net/ether1 10' >/net/bridge1/ctl
echo 'bind ether port2 0 /net/ether2 20' >/net/bridge1/ctl
echo 'bind ether port3 0 /net/ether3 30' >/net/bridge1/ctl
.EE
.SH "SEE ALSO"
.IR ip (3)
.IR ip (3),
.IR ether (3)
.SH SOURCE
.B /sys/src/9/port/devbridge.c

View file

@ -83,8 +83,11 @@ static Dirtab *dirtab[MaxQ];
#define PORT(x) ((((ulong)(x).path) >> 8)&(Maxport-1))
#define QID(x, y) (((x)<<8) | (y))
#define VID(tag) ((tag) & 0xFFF)
struct Centry
{
ushort vid;
uchar d[Eaddrlen];
int port;
long expire; // entry expires this many seconds after bootime
@ -135,6 +138,11 @@ struct Port
int outunknown; // unknown address
int outfrag; // fragmented the packet
int nentry; // number of cache entries for this port
// 802.1q
ushort pvid;
ushort prio;
uchar member[0x1000/8];
};
enum {
@ -167,13 +175,60 @@ static int m2p[] = {
};
static int bridgegen(Chan *c, char*, Dirtab*, int, int s, Dir *dp);
static void portbind(Bridge *b, int argc, char *argv[]);
static void portunbind(Bridge *b, int argc, char *argv[]);
static void portvlan(Bridge *b, int argc, char *argv[]);
static void etherread(void *a);
static char *cachedump(Bridge *b);
static void portfree(Port *port);
static void cacheflushport(Bridge *b, int port);
static void etherwrite(Port *port, Block *bp);
static void etherwrite(Port *port, Block *bp, ushort tag);
static void
initmember(Port *port)
{
memset(port->member, 0, sizeof(port->member));
}
static void
addmember(Port *port, ushort vid)
{
/* vlan ids 0 and 4095 are reserved */
if(vid == 0 || vid >= 0xFFF)
return;
port->member[vid/8] |= 1 << (vid % 8);
}
static int
ismember(Port *port, ushort vid)
{
return port->member[vid/8] & (1 << (vid%8));
}
static Block*
tagpkt(Block *bp, ushort tag)
{
uchar *h;
bp = padblock(bp, 4);
memmove(bp->rp, bp->rp+4, 2*Eaddrlen);
h = bp->rp + 2*Eaddrlen;
h[0] = 0x81;
h[1] = 0x00;
h[2] = tag>>8;
h[3] = tag;
return bp;
}
static ushort
untagpkt(Block *bp)
{
uchar *h = bp->rp + 2*Eaddrlen;
ushort tag = h[2]<<8 | h[3];
memmove(bp->rp+4, bp->rp, 2*Eaddrlen);
bp->rp += 4;
return tag;
}
static void
bridgeinit(void)
@ -266,10 +321,36 @@ bridgeclose(Chan* c)
}
}
static int
getvlancfg(Port *port, char *buf, int nbuf)
{
char *s = buf, *e = buf + nbuf;
int i, j;
s = seprint(s, e, "%d", (int)port->pvid);
if(port->prio)
s = seprint(s, e, "#%d", (int)port->prio>>12);
i = 0;
for(j = 1; j <= 0xFFF; j++){
if(ismember(port, j)){
if(i == 0)
i = j;
continue;
} else if(i == 0)
continue;
if(i == j-1)
s = seprint(s, e, ",%d", i);
else
s = seprint(s, e, ",%d-%d", i, j-1);
i = 0;
}
return s - buf;
}
static long
bridgeread(Chan *c, void *a, long n, vlong off)
{
char buf[256];
char buf[512];
Bridge *b = bridgetab + c->dev;
Port *port;
int i, ingood, outgood;
@ -308,13 +389,19 @@ bridgeread(Chan *c, void *a, long n, vlong off)
i += snprint(buf+i, sizeof(buf)-i, "tunnel %s: ", port->name);
break;
}
i += snprint(buf+i, sizeof(buf)-i, "vlan=");
i += getvlancfg(port, buf+i, sizeof(buf)-i);
i += snprint(buf+i, sizeof(buf)-i, " ");
ingood = port->in - port->inmulti - port->inunknown;
outgood = port->out - port->outmulti - port->outunknown;
snprint(buf+i, sizeof(buf)-i,
i += snprint(buf+i, sizeof(buf)-i,
"in=%d(%d:%d:%d) out=%d(%d:%d:%d:%d)\n",
port->in, ingood, port->inmulti, port->inunknown,
port->out, outgood, port->outmulti,
port->outunknown, port->outfrag);
USED(i);
}
poperror();
qunlock(b);
@ -344,7 +431,6 @@ bridgeoption(Bridge *b, char *option, int value)
error("unknown bridge option");
}
static long
bridgewrite(Chan *c, void *a, long n, vlong off)
{
@ -371,6 +457,8 @@ bridgewrite(Chan *c, void *a, long n, vlong off)
portbind(b, cb->nf-1, cb->f+1);
} else if(strcmp(arg0, "unbind") == 0) {
portunbind(b, cb->nf-1, cb->f+1);
} else if(strcmp(arg0, "vlan") == 0) {
portvlan(b, cb->nf-1, cb->f+1);
} else if(strcmp(arg0, "cacheflush") == 0) {
log(b, Logcache, "cache flush\n");
memset(b->cache, 0, CacheSize*sizeof(Centry));
@ -472,28 +560,58 @@ bridgegen(Chan *c, char *, Dirtab*, int, int s, Dir *dp)
}
}
// parse mac address; also in netif.c
static int
parseaddr(uchar *to, char *from, int alen)
static char*
vlanrange(char *s, int *i, int *j)
{
char nip[4];
char *p;
int i;
char *x;
p = from;
for(i = 0; i < alen; i++){
if(*p == 0)
return -1;
nip[0] = *p++;
if(*p == 0)
return -1;
nip[1] = *p++;
nip[2] = 0;
to[i] = strtoul(nip, 0, 16);
if(*p == ':')
p++;
*j = -1;
*i = strtol(s, &x, 10);
if(x <= s)
return x;
if(*i < 0) {
/* -nnn */
*j = -(*i);
*i = 1;
} else if(*s == '-') {
/* nnn- */
s = x;
*j = -strtol(s, &x, 10);
if(x <= s || *j <= 0)
*j = 0xFFE;
} else {
/* nnn */
*j = *i;
}
return 0;
return x;
}
// set the vlan configuration of a port.
// first number is the pvid (port vlan id)
// followed by zero or more other vlan members.
// members can be specified as comma separated ranges:
// -10,13,50-60,1000- => [1..10],13,[50-60],[1000-4094]
static void
setvlancfg(Port *port, char *cfg)
{
int i, j;
initmember(port);
port->pvid = strtol(cfg, &cfg, 10);
if(port->pvid >= 0xFFF)
port->pvid = 0;
if(*cfg == '#'){
cfg++;
port->prio = strtol(cfg, &cfg, 10)<<12;
} else {
port->prio = 0<<12;
}
while(*cfg == ','){
cfg = vlanrange(++cfg, &i, &j);
for(; i <= j; i++)
addmember(port, i);
}
addmember(port, port->pvid);
}
// assumes b is locked
@ -504,27 +622,29 @@ portbind(Bridge *b, int argc, char *argv[])
Chan *ctl;
int type = 0, i, n;
ulong ownhash;
char *dev, *dev2 = nil;
char *dev, *dev2, *vlan;
char buf[100], name[KNAMELEN], path[8*KNAMELEN];
static char usage[] = "usage: bind ether|tunnel name ownhash dev [dev2]";
static char usage[] = "usage: bind ether|tunnel name ownhash dev [dev2] [pvid[,vlans...]]";
dev2 = nil;
vlan = "1"; // default vlan configuration
memset(name, 0, KNAMELEN);
if(argc < 4)
error(usage);
if(strcmp(argv[0], "ether") == 0) {
if(argc != 4)
error(usage);
if(argc > 4)
vlan = argv[4];
type = Tether;
strncpy(name, argv[1], KNAMELEN);
name[KNAMELEN-1] = 0;
// parseaddr(addr, argv[1], Eaddrlen);
} else if(strcmp(argv[0], "tunnel") == 0) {
if(argc != 5)
if(argc < 5)
error(usage);
if(argc > 5)
vlan = argv[5];
type = Ttun;
strncpy(name, argv[1], KNAMELEN);
name[KNAMELEN-1] = 0;
// parseip(addr, argv[1]);
dev2 = argv[4];
} else
error(usage);
@ -552,6 +672,8 @@ portbind(Bridge *b, int argc, char *argv[])
}
port->type = type;
memmove(port->name, name, KNAMELEN);
setvlancfg(port, vlan);
switch(port->type) {
default:
panic("portbind: unknown port type: %d", type);
@ -609,29 +731,26 @@ portbind(Bridge *b, int argc, char *argv[])
kproc(buf, etherread, port);
}
// assumes b is locked
static void
portunbind(Bridge *b, int argc, char *argv[])
static int
getport(Bridge *b, int argc, char **argv)
{
static char usage[] = "usage: ... ether|tunnel name [ownhash]";
int type = 0, i;
char name[KNAMELEN];
ulong ownhash;
Port *port = nil;
static char usage[] = "usage: unbind ether|tunnel addr [ownhash]";
memset(name, 0, KNAMELEN);
if(argc < 2 || argc > 3)
if(argc < 2)
error(usage);
if(strcmp(argv[0], "ether") == 0) {
type = Tether;
strncpy(name, argv[1], KNAMELEN);
name[KNAMELEN-1] = 0;
// parseaddr(addr, argv[1], Eaddrlen);
} else if(strcmp(argv[0], "tunnel") == 0) {
type = Ttun;
strncpy(name, argv[1], KNAMELEN);
name[KNAMELEN-1] = 0;
// parseip(addr, argv[1]);
} else
error(usage);
if(argc == 3)
@ -648,7 +767,15 @@ portunbind(Bridge *b, int argc, char *argv[])
error("port not found");
if(ownhash != 0 && port->ownhash != 0 && ownhash != port->ownhash)
error("bad owner hash");
return i;
}
// assumes b is locked
static void
portunbind(Bridge *b, int argc, char *argv[])
{
int i = getport(b, argc, argv);
Port *port = b->port[i];
port->closed = 1;
b->port[i] = nil; // port is now unbound
cacheflushport(b, i);
@ -659,75 +786,79 @@ portunbind(Bridge *b, int argc, char *argv[])
portfree(port);
}
static void
portvlan(Bridge *b, int argc, char *argv[])
{
int i = getport(b, argc-1, argv+1);
cacheflushport(b, i);
setvlancfg(b->port[i], argv[0]);
}
static Centry *
cachehash(Bridge *b, uchar d[Eaddrlen], ushort vid)
{
uint h = (uint)vid*587;
int i;
for(i=0; i<Eaddrlen; i++) {
h *= 7;
h += d[i];
}
return &b->cache[h % CacheHash];
}
// assumes b is locked
static Centry *
cachelookup(Bridge *b, uchar d[Eaddrlen])
cachelookup(Bridge *b, uchar d[Eaddrlen], ushort vid)
{
int i;
uint h;
Centry *p;
long sec;
// dont cache multicast or broadcast
if(d[0] & 1)
return 0;
h = 0;
for(i=0; i<Eaddrlen; i++) {
h *= 7;
h += d[i];
}
h %= CacheHash;
p = b->cache + h;
p = cachehash(b, d, vid);
sec = TK2SEC(m->ticks);
for(i=0; i<CacheLook; i++,p++) {
if(memcmp(d, p->d, Eaddrlen) == 0) {
if(p->vid == vid && memcmp(d, p->d, Eaddrlen) == 0) {
p->dst++;
if(sec >= p->expire) {
log(b, Logcache, "expired cache entry: %E %d\n",
d, p->port);
log(b, Logcache, "expired cache entry: %E %d %d\n",
d, (int)vid, p->port);
return nil;
}
p->expire = sec + CacheTimeout;
return p;
}
}
log(b, Logcache, "cache miss: %E\n", d);
log(b, Logcache, "cache miss: %E %d\n", d, (int)vid);
return nil;
}
// assumes b is locked
static void
cacheupdate(Bridge *b, uchar d[Eaddrlen], int port)
cacheupdate(Bridge *b, uchar d[Eaddrlen], int port, ushort vid)
{
int i;
uint h;
Centry *p, *pp;
long sec;
// dont cache multicast or broadcast
if(d[0] & 1) {
log(b, Logcache, "bad source address: %E\n", d);
log(b, Logcache, "bad source address: %E %d %d\n", d, (int)vid, port);
return;
}
h = 0;
for(i=0; i<Eaddrlen; i++) {
h *= 7;
h += d[i];
}
h %= CacheHash;
p = b->cache + h;
pp = p;
p = pp = cachehash(b, d, vid);
sec = p->expire;
// look for oldest entry
for(i=0; i<CacheLook; i++,p++) {
if(memcmp(p->d, d, Eaddrlen) == 0) {
if(p->vid == vid && memcmp(p->d, d, Eaddrlen) == 0) {
p->expire = TK2SEC(m->ticks) + CacheTimeout;
if(p->port != port) {
log(b, Logcache, "NIC changed port %d->%d: %E\n",
p->port, port, d);
log(b, Logcache, "NIC changed port: %E %d %d->%d\n",
d, (int)vid, p->port, port);
p->port = port;
}
p->src++;
@ -739,13 +870,14 @@ cacheupdate(Bridge *b, uchar d[Eaddrlen], int port)
}
}
if(pp->expire != 0)
log(b, Logcache, "bumping from cache: %E %d\n", pp->d, pp->port);
log(b, Logcache, "bumping from cache: %E %d %d\n", pp->d, (int)pp->vid, pp->port);
log(b, Logcache, "adding to cache: %E %d %d\n", d, (int)vid, port);
pp->expire = TK2SEC(m->ticks) + CacheTimeout;
pp->vid = vid;
memmove(pp->d, d, Eaddrlen);
pp->port = port;
pp->src = 1;
pp->dst = 0;
log(b, Logcache, "adding to cache: %E %d\n", pp->d, pp->port);
}
// assumes b is locked
@ -782,8 +914,8 @@ cachedump(Bridge *b)
for(i=0; i<CacheSize; i++)
if(b->cache[i].expire != 0)
n++;
n *= 51; // change if print format is changed
// change if print format is changed
n *= (13+5+3+11+11+11+2);
n += 10; // some slop at the end
buf = malloc(n);
if(buf == nil)
@ -796,8 +928,9 @@ cachedump(Bridge *b)
if(ce->expire == 0)
continue;
c = (sec < ce->expire)?'v':'e';
p += snprint(p, ep-p, "%E %2d %10ld %10ld %10ld %c\n", ce->d,
ce->port, ce->src, ce->dst, ce->expire+off, c);
p += snprint(p, ep-p, "%E %4d %2d %10ld %10ld %10ld %c\n",
ce->d, (int)ce->vid, ce->port,
ce->src, ce->dst, ce->expire+off, c);
}
*p = 0;
poperror();
@ -806,11 +939,9 @@ cachedump(Bridge *b)
return buf;
}
// assumes b is locked, no error return
static void
ethermultiwrite(Bridge *b, Block *bp, Port *port)
ethermultiwrite(Bridge *b, Block *bp, Port *port, ushort tag)
{
Port *oport;
Etherpkt *ep;
@ -821,7 +952,7 @@ ethermultiwrite(Bridge *b, Block *bp, Port *port)
oport = nil;
for(i=0; i<b->nport; i++) {
if(i == port->id || b->port[i] == nil)
if(i == port->id || b->port[i] == nil || !ismember(b->port[i], VID(tag)))
continue;
/*
* we need to forward multicast packets for ipv6,
@ -835,14 +966,14 @@ ethermultiwrite(Bridge *b, Block *bp, Port *port)
// delay one so that the last write does not copy
if(oport != nil) {
b->copy++;
etherwrite(oport, copyblock(bp, BLEN(bp)));
etherwrite(oport, copyblock(bp, BLEN(bp)), tag);
}
oport = b->port[i];
}
// last write free block
if(oport)
etherwrite(oport, bp);
etherwrite(oport, bp, tag);
else
freeb(bp);
}
@ -947,6 +1078,7 @@ etherread(void *a)
Etherpkt *ep;
Centry *ce;
long md, n;
ushort type, tag;
qlock(b);
port->readp = up; /* hide identity under a rock for unbind */
@ -969,15 +1101,31 @@ etherread(void *a)
freeb(bp);
continue;
}
if(waserror()) {
// print("etherread bridge error\n");
freeb(bp);
continue;
}
port->in++;
ep = (Etherpkt*)bp->rp;
cacheupdate(b, ep->s, port->id);
type = ep->type[0]<<8|ep->type[1];
if(type != 0x8100) {
tag = port->pvid;
if(tag == 0){
freeb(bp);
continue;
}
tag |= port->prio;
} else {
tag = untagpkt(bp);
if(!ismember(port, VID(tag))) {
if(VID(tag) != 0 || port->pvid == 0){
freeb(bp);
continue;
}
tag |= port->pvid;
}
ep = (Etherpkt*)bp->rp;
type = ep->type[0]<<8|ep->type[1];
}
cacheupdate(b, ep->s, port->id, VID(tag));
if(b->tcpmss)
tcpmsshack(ep, n);
@ -990,22 +1138,20 @@ etherread(void *a)
microdelay(md);
}
poperror(); /* must now dispose of bp */
if(ep->d[0] & 1) {
log(b, Logmcast, "multicast: port=%d src=%E dst=%E type=%#.4ux\n",
port->id, ep->s, ep->d, ep->type[0]<<8|ep->type[1]);
log(b, Logmcast, "multicast: port=%d tag=%#.4ux src=%E dst=%E type=%#.4ux\n",
port->id, tag, ep->s, ep->d, type);
port->inmulti++;
ethermultiwrite(b, bp, port);
ethermultiwrite(b, bp, port, tag);
} else {
ce = cachelookup(b, ep->d);
ce = cachelookup(b, ep->d, VID(tag));
if(ce == nil) {
b->miss++;
port->inunknown++;
ethermultiwrite(b, bp, port);
ethermultiwrite(b, bp, port, tag);
}else if(ce->port != port->id){
b->hit++;
etherwrite(b->port[ce->port], bp);
etherwrite(b->port[ce->port], bp, tag);
}else
freeb(bp);
}
@ -1043,21 +1189,23 @@ fragment(Etherpkt *epkt, int n)
}
static void
etherwrite(Port *port, Block *bp)
etherwrite(Port *port, Block *bp, ushort tag)
{
Ip4hdr *eh, *feh;
Etherpkt *epkt;
int n, lid, len, seglen, dlen, blklen, mf;
int lid, len, seglen, dlen, blklen, mf;
Block *nb;
ushort fragoff, frag;
port->out++;
n = BLEN(bp);
epkt = (Etherpkt*)bp->rp;
if(port->type != Ttun || !fragment(epkt, n)) {
if(port->type != Ttun || !fragment(epkt, BLEN(bp))) {
if(!waserror()){
if(VID(tag) != port->pvid)
bp = tagpkt(bp, tag);
/* don't generate small packets */
if(n < ETHERMINTU)
if(BLEN(bp) < ETHERMINTU)
bp = adjustblock(bp, ETHERMINTU);
devtab[port->data[1]->type]->bwrite(port->data[1], bp, 0);
poperror();
@ -1114,6 +1262,9 @@ etherwrite(Port *port, Block *bp)
feh->cksum[1] = 0;
hnputs(feh->cksum, ipcsum(&feh->vihl));
if(VID(tag) != port->pvid)
nb = tagpkt(nb, tag);
/* don't generate small packets */
if(BLEN(nb) < ETHERMINTU)
nb = adjustblock(nb, ETHERMINTU);