shithub: riscv

Download patch

ref: 4b637a24f9c2db696b6660bc7116441a43281302
parent: 7289f371a0b113c12add274c4d4aa84d0c147dee
author: cinap_lenrek <[email protected]>
date: Wed Feb 16 17:38:03 EST 2022

devbridge: implement experimental vlan support

--- a/sys/man/3/bridge
+++ b/sys/man/3/bridge
@@ -50,10 +50,8 @@
 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 @@
 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.
 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 @@
 .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 @@
 .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 @@
 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
--- a/sys/src/9/port/devbridge.c
+++ b/sys/src/9/port/devbridge.c
@@ -83,8 +83,11 @@
 #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 @@
 	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,15 +175,62 @@
 };
 
 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)
 {
 	int i;
@@ -266,10 +321,36 @@
 	}
 }
 
+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 @@
 				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 @@
 		error("unknown bridge option");
 }
 
-
 static long
 bridgewrite(Chan *c, void *a, long n, vlong off)
 {
@@ -371,6 +457,8 @@
 			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,30 +560,60 @@
 	}
 }
 
-// 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
 static void
 portbind(Bridge *b, int argc, char *argv[])
@@ -504,27 +622,29 @@
 	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 @@
 	}
 	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 @@
 	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 @@
 		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,12 +786,32 @@
 	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;
 
@@ -671,21 +818,14 @@
 	// 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;
@@ -692,42 +832,33 @@
 			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 @@
 		}
 	}
 	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 @@
 	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 @@
 		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 @@
 	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 @@
 
 	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,7 +966,7 @@
 		// 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];
 	}
@@ -842,7 +973,7 @@
 
 	// last write free block
 	if(oport)
-		etherwrite(oport, bp);
+		etherwrite(oport, bp, tag);
 	else
 		freeb(bp);
 }
@@ -947,6 +1078,7 @@
 	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 @@
 			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 @@
 				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 @@
 }
 
 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 @@
 		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);