shithub: riscv

Download patch

ref: d43d79bda454212f92fcec1ad4d049ecdc66e043
parent: 1cff923af4dbcaaab515cc04ea40c559eab7830f
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Sep 26 14:43:29 EDT 2021

devip: implement ipv4 arp timeout with icmp host unreachable notification

The IPv4 ARP cache used to indefinitely buffer packets in the Arpent hold list.
This is bad in case of a router, because it opens a 1 second
(retransmit time) window to leak all the to be forwarded packets.

This change makes the ipv4 arp code path similar to the IPv6 neighbour
solicitation path, using the retransmit process to time out old entries
(after 3 arp retransmits => 3 seconds).

A new function arpcontinue() has been added that unifies the point when
we schedule the (ipv6 sol retransmit) / (ipv4 arp timeout) and reduce
the hold queue to the last packet and unlock the cache.

As a bonus, we also now send a icmp host unreachable notification
for the dropped packets.

--- a/sys/src/9/ip/arp.c
+++ b/sys/src/9/ip/arp.c
@@ -37,7 +37,7 @@
 	Fs	*f;
 	Arpent	*hash[NHASH];
 	Arpent	cache[NCACHE];
-	Arpent	*rxmt;
+	Arpent	*rxmt[2];
 	Proc	*rxmitp;	/* neib sol re-transmit proc */
 	Rendez	rxmtq;
 	Block 	*dropf, *dropl;
@@ -47,16 +47,15 @@
 
 #define haship(s) ((s)[IPaddrlen-1]%NHASH)
 
-int 	ReTransTimer = RETRANS_TIMER;
+static void 	rxmitproc(void*);
 
-static void 	rxmitproc(void *v);
-
 void
 arpinit(Fs *f)
 {
 	f->arp = smalloc(sizeof(Arp));
 	f->arp->f = f;
-	f->arp->rxmt = nil;
+	f->arp->rxmt[0] = nil;
+	f->arp->rxmt[1] = nil;
 	f->arp->dropf = f->arp->dropl = nil;
 	kproc("rxmitproc", rxmitproc, f->arp);
 }
@@ -79,7 +78,7 @@
 {
 	Arpent **l;
 
-	for(l = &arp->rxmt; *l != nil; l = &((*l)->nextrxt)){
+	for(l = &arp->rxmt[isv4(a->ip) != 0]; *l != nil; l = &((*l)->nextrxt)){
 		if(*l == a){
 			*l = a->nextrxt;
 			break;
@@ -104,26 +103,20 @@
 	}
 	a->hash = nil;
 
-	/* dump waiting packets */
-	bp = a->hold;
-	a->hold = nil;
-	if(isv4(a->ip))
-		freeblistchain(bp);
-	else {
-		rxmtunchain(arp, a);
+	/* remove from retransmit / timout chain */
+	rxmtunchain(arp, a);
 
-		/* queue icmp unreachable for rxmitproc later on, w/o arp lock */
-		if(bp != nil){
-			if(arp->dropf == nil)
-				arp->dropf = bp;
-			else
-				arp->dropl->list = bp;
-			arp->dropl = a->last;
-
-			if(bp == arp->dropf)
-				wakeup(&arp->rxmtq);
-		}
+	/* queue packets for icmp unreachable for rxmitproc later on, w/o arp lock */
+	if((bp = a->hold) != nil){
+		if(arp->dropf == nil)
+			arp->dropf = bp;
+		else
+			arp->dropl->list = bp;
+		arp->dropl = a->last;
+		if(bp == arp->dropf)
+			wakeup(&arp->rxmtq);
 	}
+	a->hold = nil;
 	a->last = nil;
 
 	a->ifc = nil;
@@ -152,7 +145,7 @@
 	e = &arp->cache[NCACHE];
 	a = arp->cache;
 	t = a->utime;
-	for(f = a; f < e; f++){
+	for(f = a+1; t > 0 && f < e; f++){
 		if(f->utime < t){
 			t = f->utime;
 			a = f;
@@ -182,7 +175,6 @@
 Arpent*
 arpget(Arp *arp, Block *bp, int version, Ipifc *ifc, uchar *ip, uchar *mac)
 {
-	int hash;
 	Arpent *a;
 	uchar v6ip[IPaddrlen];
 
@@ -192,8 +184,7 @@
 	}
 
 	qlock(arp);
-	hash = haship(ip);
-	for(a = arp->hash[hash]; a != nil; a = a->hash){
+	for(a = arp->hash[haship(ip)]; a != nil; a = a->hash){
 		if(a->ifc == ifc && a->ifcid == ifc->ifcid && ipcmp(ip, a->ip) == 0)
 			break;
 	}
@@ -225,6 +216,39 @@
 }
 
 /*
+ * continue address resolution for the entry,
+ * schedule it on the retransmit / timeout chains
+ * and unlock the arp cache.
+ */
+void
+arpcontinue(Arp *arp, Arpent *a)
+{
+	Arpent **l;
+	Block *bp;
+
+	/* try to keep it around for a second more */
+	a->ctime = NOW;
+
+	/* remove all but the last message */
+	while((bp = a->hold) != nil){
+		if(bp == a->last)
+			break;
+		a->hold = bp->list;
+		freeblist(bp);
+	}
+
+	/* put on end of re-transmit / timeout chain */
+	for(l = rxmtunchain(arp, a); *l != nil; l = &(*l)->nextrxt)
+		;
+	*l = a;
+
+	if(l == &arp->rxmt[0] || l == &arp->rxmt[1])
+		wakeup(&arp->rxmtq);
+
+	qunlock(arp);
+}
+
+/*
  * called with arp locked
  */
 void
@@ -233,6 +257,7 @@
 	qunlock(arp);
 }
 
+
 /*
  * Copy out the mac address from the Arpent.  Return the
  * block waiting to get sent to this mac address.
@@ -245,7 +270,7 @@
 	Block *bp;
 
 	memmove(a->mac, mac, type->maclen);
-	if(a->state == AWAIT && !isv4(a->ip)){
+	if(a->state == AWAIT) {
 		rxmtunchain(arp, a);
 		a->rxtsrem = 0;
 	}
@@ -363,7 +388,7 @@
 		memset(arp->hash, 0, sizeof(arp->hash));
 		freeblistchain(arp->dropf);
 		arp->dropf = arp->dropl = nil;
-		arp->rxmt = nil;
+		arp->rxmt[0] = arp->rxmt[1] = nil;
 		qunlock(arp);
 	} else if(strcmp(f[0], "add") == 0){
 		switch(n){
@@ -480,32 +505,22 @@
 ndpsendsol(Fs *f, Ipifc *ifc, Arpent *a)
 {
 	uchar targ[IPaddrlen], src[IPaddrlen];
-	Arpent **l;
 
-	a->ctime = NOW;
 	if(a->rxtsrem == 0)
 		a->rxtsrem = MAX_MULTICAST_SOLICIT;
 	else
 		a->rxtsrem--;
 
-	/* put on end of re-transmit chain */
-	for(l = rxmtunchain(f->arp, a); *l != nil; l = &(*l)->nextrxt)
-		;
-	*l = a;
-
-	if(l == &f->arp->rxmt)
-		wakeup(&f->arp->rxmtq);
-
 	/* try to use source address of original packet */
 	ipmove(targ, a->ip);
 	if(a->last != nil){
 		ipmove(src, ((Ip6hdr*)a->last->rp)->src);
-		arprelease(f->arp, a);
+		arpcontinue(f->arp, a);
 
 		if(iplocalonifc(ifc, src) != nil || ipproxyifc(f, ifc, src))
 			goto send;
 	} else {
-		arprelease(f->arp, a);
+		arpcontinue(f->arp, a);
 	}
 	if(!ipv6local(ifc, src, 0, targ))
 		return;
@@ -516,16 +531,17 @@
 	}
 }
 
-static void
-rxmitsols(Arp *arp)
+static Block*
+rxmt(Arp *arp)
 {
-	Block *next, *bp;
 	Arpent *a;
+	Block *bp;
 	Ipifc *ifc;
-	Route *r;
 
 	qlock(arp);
-	while((a = arp->rxmt) != nil && NOW - a->ctime > 3*ReTransTimer/4){
+
+	/* retransmit ipv6 solicitations */
+	while((a = arp->rxmt[0]) != nil && NOW - a->ctime > 3*RETRANS_TIMER/4){
 		if(a->rxtsrem > 0 && (ifc = a->ifc) != nil && canrlock(ifc)){
 			if(a->ifcid == ifc->ifcid){
 				ndpsendsol(arp->f, ifc, a);	/* unlocks arp */
@@ -537,17 +553,40 @@
 		}
 		cleanarpent(arp, a);
 	}
+
+	/* timeout waiting ipv4 arp entries */
+	while((a = arp->rxmt[1]) != nil && NOW - a->ctime > 3*RETRANS_TIMER)
+		cleanarpent(arp, a);
+
 	bp = arp->dropf;
 	arp->dropf = arp->dropl = nil;
+
 	qunlock(arp);
 
+	return bp;
+}
+
+static void
+drop(Fs *f, Block *bp)
+{
+	Block *next;
+	Ipifc *ifc;
+	Route *r;
+
 	for(; bp != nil; bp = next){
 		next = bp->list;
 		bp->list = nil;
-		r = v6lookup(arp->f, ((Ip6hdr*)bp->rp)->src, ((Ip6hdr*)bp->rp)->dst, nil);
+
+		if((bp->rp[0]&0xF0) == IP_VER4)
+			r = v4lookup(f, ((Ip4hdr*)bp->rp)->src, ((Ip4hdr*)bp->rp)->dst, nil);
+		else
+			r = v6lookup(f, ((Ip6hdr*)bp->rp)->src, ((Ip6hdr*)bp->rp)->dst, nil);
 		if(r != nil && (ifc = r->ifc) != nil && canrlock(ifc)){
 			if(!waserror()){
-				icmphostunr6(arp->f, ifc, bp, Icmp6_adr_unreach, (r->type & Runi) != 0);
+				if((bp->rp[0]&0xF0) == IP_VER4)
+					icmpnohost(f, ifc, bp);
+				else
+					icmphostunr6(f, ifc, bp, Icmp6_adr_unreach, (r->type & Runi) != 0);
 				poperror();
 			}
 			runlock(ifc);
@@ -561,7 +600,7 @@
 {
 	Arp *arp = (Arp *)v;
 
-	return arp->rxmt != nil || arp->dropf != nil;
+	return arp->rxmt[0] != nil || arp->rxmt[1] != nil || arp->dropf != nil;
 }
 
 static void
@@ -576,7 +615,7 @@
 	}
 	for(;;){
 		sleep(&arp->rxmtq, rxready, v);
-		rxmitsols(arp);
-		tsleep(&arp->rxmtq, return0, nil, ReTransTimer/4);
+		drop(arp->f, rxmt(arp));
+		tsleep(&arp->rxmtq, return0, nil, RETRANS_TIMER/4);
 	}
 }
--- a/sys/src/9/ip/ethermedium.c
+++ b/sys/src/9/ip/ethermedium.c
@@ -17,15 +17,6 @@
 	uchar	t[2];
 };
 
-static uchar ipbroadcast[IPaddrlen] = {
-	0xff,0xff,0xff,0xff,
-	0xff,0xff,0xff,0xff,
-	0xff,0xff,0xff,0xff,
-	0xff,0xff,0xff,0xff,
-};
-
-static uchar etherbroadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
-
 static void	etherread4(void *a);
 static void	etherread6(void *a);
 static void	etherbind(Ipifc *ifc, int argc, char **argv);
@@ -35,8 +26,7 @@
 static void	etherremmulti(Ipifc *ifc, uchar *a, uchar *ia);
 static void	etherareg(Fs *f, Ipifc *ifc, Iplifc *lifc, uchar *ip);
 static Block*	multicastarp(Fs *f, Arpent *a, Medium*, uchar *mac);
-static void	sendarp(Ipifc *ifc, Arpent *a);
-static void	sendndp(Ipifc *ifc, Arpent *a);
+static void	sendarpreq(Ipifc *ifc, Arpent *a);
 static int	multicastea(uchar *ea, uchar *ip);
 static void	recvarpproc(void*);
 static void	etherpref2addr(uchar *pref, uchar *ea);
@@ -276,12 +266,17 @@
 		/* check for broadcast or multicast */
 		bp = multicastarp(er->f, a, ifc->m, mac);
 		if(bp == nil){
+			/* don't do anything if it's been less than a second since the last */
+			if(NOW - a->ctime < RETRANS_TIMER){
+				arprelease(er->f->arp, a);
+				return;
+			}
 			switch(version){
 			case V4:
-				sendarp(ifc, a);
+				sendarpreq(ifc, a);		/* unlocks arp */
 				break;
 			case V6:
-				sendndp(ifc, a);
+				ndpsendsol(er->f, ifc, a);	/* unlocks arp */
 				break;
 			default:
 				panic("etherbwrite: version %d", version);
@@ -442,7 +437,7 @@
  *  (only v4, v6 uses the neighbor discovery, rfc1970)
  */
 static void
-sendarp(Ipifc *ifc, Arpent *a)
+sendarpreq(Ipifc *ifc, Arpent *a)
 {
 	int n;
 	Block *bp;
@@ -450,25 +445,8 @@
 	Etherrock *er = ifc->arg;
 	uchar targ[IPv4addrlen], src[IPv4addrlen];
 
-	/* don't do anything if it's been less than a second since the last */
-	if(NOW - a->ctime < 1000){
-		arprelease(er->f->arp, a);
-		return;
-	}
-
-	/* try to keep it around for a second more */
-	a->ctime = NOW;
-
-	/* remove all but the last message */
-	while((bp = a->hold) != nil){
-		if(bp == a->last)
-			break;
-		a->hold = bp->list;
-		freeblist(bp);
-	}
-
 	memmove(targ, a->ip+IPv4off, IPv4addrlen);
-	arprelease(er->f->arp, a);
+	arpcontinue(er->f->arp, a);
 
 	if(!ipv4local(ifc, src, 0, targ))
 		return;
@@ -477,8 +455,8 @@
 	if(n < ifc->m->mintu)
 		n = ifc->m->mintu;
 	bp = allocb(n);
-	memset(bp->rp, 0, n);
 	e = (Etherarp*)bp->rp;
+	memset(e, 0, n);
 	memmove(e->tpa, targ, sizeof(e->tpa));
 	memmove(e->spa, src, sizeof(e->spa));
 	memmove(e->sha, ifc->mac, sizeof(e->sha));
@@ -496,29 +474,6 @@
 	devtab[er->achan->type]->bwrite(er->achan, bp, 0);
 }
 
-static void
-sendndp(Ipifc *ifc, Arpent *a)
-{
-	Block *bp;
-	Etherrock *er = ifc->arg;
-
-	/* don't do anything if it's been less than a second since the last */
-	if(NOW - a->ctime < ReTransTimer){
-		arprelease(er->f->arp, a);
-		return;
-	}
-
-	/* remove all but the last message */
-	while((bp = a->hold) != nil){
-		if(bp == a->last)
-			break;
-		a->hold = bp->list;
-		freeblist(bp);
-	}
-
-	ndpsendsol(er->f, ifc, a);	/* unlocks arp */
-}
-
 /*
  *  send a gratuitous arp to refresh arp caches
  */
@@ -534,8 +489,8 @@
 	if(n < ifc->m->mintu)
 		n = ifc->m->mintu;
 	bp = allocb(n);
-	memset(bp->rp, 0, n);
 	e = (Etherarp*)bp->rp;
+	memset(e, 0, n);
 	memmove(e->tpa, ip+IPv4off, sizeof(e->tpa));
 	memmove(e->spa, ip+IPv4off, sizeof(e->spa));
 	memmove(e->sha, ifc->mac, sizeof(e->sha));
--- a/sys/src/9/ip/icmp.c
+++ b/sys/src/9/ip/icmp.c
@@ -242,22 +242,34 @@
 }
 
 static void
-icmpunreachable(Fs *f, Block *bp, int code, int seq)
+icmpunreachable(Fs *f, Ipifc *ifc, Block *bp, int code, int seq)
 {
 	Block	*nbp;
 	Icmp	*p, *np;
+	uchar	ia[IPv4addrlen];
 
 	p = (Icmp *)bp->rp;
-	if(!ip4me(f, p->dst) || !ip4reply(f, p->src))
+	if(!ip4reply(f, p->src))
 		return;
 
-	netlog(f, Logicmp, "sending icmpnoconv -> %V\n", p->src);
+	if(ifc == nil){
+		if(!ipforme(f, p->dst))
+			return;
+		memmove(ia, p->dst, sizeof(p->dst));
+	} else {
+		if(!ipv4local(ifc, ia, 0, p->src))
+			return;
+	}
+
+	netlog(f, Logicmp, "sending icmpunreachable %V -> src %V dst %V\n",
+		ia, p->src, p->dst);
+
 	nbp = allocb(ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8);
 	nbp->wp += ICMP_IPSIZE + ICMP_HDRSIZE + ICMP_IPSIZE + 8;
 	np = (Icmp *)nbp->rp;
 	np->vihl = IP_VER4;
+	memmove(np->src, ia, sizeof(np->src));
 	memmove(np->dst, p->src, sizeof(np->dst));
-	memmove(np->src, p->dst, sizeof(np->src));
 	memmove(np->data, bp->rp, ICMP_IPSIZE + 8);
 	np->type = Unreachable;
 	np->code = code;
@@ -270,15 +282,21 @@
 }
 
 void
+icmpnohost(Fs *f, Ipifc *ifc, Block *bp)
+{
+	icmpunreachable(f, ifc, bp, 1, 0);
+}
+
+void
 icmpnoconv(Fs *f, Block *bp)
 {
-	icmpunreachable(f, bp, 3, 0);
+	icmpunreachable(f, nil, bp, 3, 0);
 }
 
 void
 icmpcantfrag(Fs *f, Block *bp, int mtu)
 {
-	icmpunreachable(f, bp, 4, mtu);
+	icmpunreachable(f, nil, bp, 4, mtu);
 }
 
 static void
--- a/sys/src/9/ip/ip.h
+++ b/sys/src/9/ip/ip.h
@@ -613,6 +613,7 @@
 extern int	arpwrite(Fs*, char*, int);
 extern Arpent*	arpget(Arp*, Block *bp, int version, Ipifc *ifc, uchar *ip, uchar *h);
 extern void	arprelease(Arp*, Arpent *a);
+extern void	arpcontinue(Arp*, Arpent *a);
 extern Block*	arpresolve(Arp*, Arpent *a, Medium *type, uchar *mac);
 extern int	arpenter(Fs*, int version, uchar *ip, uchar *mac, int n, uchar *ia, Ipifc *ifc, int refresh);
 extern void	ndpsendsol(Fs*, Ipifc*, Arpent*);
@@ -682,6 +683,7 @@
  *  ip.c
  */
 extern void	iprouting(Fs*, int);
+extern void	icmpnohost(Fs*, Ipifc*, Block*);
 extern void	icmpnoconv(Fs*, Block*);
 extern void	icmpcantfrag(Fs*, Block*, int);
 extern void	icmpttlexceeded(Fs*, Ipifc*, Block*);
--- a/sys/src/9/ip/ipv6.c
+++ b/sys/src/9/ip/ipv6.c
@@ -32,7 +32,7 @@
 	v6p->rp.ttl		= MAXTTL;
 	v6p->rp.routerlt	= (3 * v6p->rp.maxraint) / 1000;
 
-	v6p->hp.rxmithost	= 1000;		/* v6 RETRANS_TIMER */
+	v6p->hp.rxmithost	= RETRANS_TIMER;
 
 	f->v6p			= v6p;
 }
--- a/sys/src/9/ip/ipv6.h
+++ b/sys/src/9/ip/ipv6.h
@@ -178,8 +178,6 @@
 extern int v6aNpreflen;
 extern int v6aLpreflen;
 
-extern int ReTransTimer;
-
 void ipv62smcast(uchar *, uchar *);
 void icmpns(Fs *f, uchar* src, int suni, uchar* targ, int tuni, uchar* mac);
 void icmpna(Fs *f, uchar* src, uchar* dst, uchar* targ, uchar* mac, uchar flags);