plan9fox/sys/src/9/ip/arp.c
cinap_lenrek 1a6324970d devip: cache arp entry in Routehint
Instead of having to do an arp hash table lookup for each
outgoing ip packet, forward the Routehint pointer to the
medium's bwrite() function and let it cache the arp entry
pointer.

This avoids route and arp hash table lookups for tcp, il
and connection oriented udp.

It also allows us to avoid multiple route and arp table
lookups for the retransmits once an arp/neighbour solicitation
response arrives.
2021-10-09 18:26:16 +00:00

642 lines
12 KiB
C

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "ip.h"
#include "ipv6.h"
/*
* address resolution tables
*/
enum
{
NHASH = (1<<6),
NCACHE = 256,
AOK = 1,
AWAIT = 2,
MAXAGE_TIMER = 15*60*1000,
};
char *arpstate[] =
{
"UNUSED",
"OK",
"WAIT",
};
/*
* one per Fs
*/
struct Arp
{
RWlock;
Fs *f;
Arpent *hash[NHASH];
Arpent cache[NCACHE];
Arpent *rxmt[2];
Proc *rxmitp; /* neib sol re-transmit proc */
Rendez rxmtq;
Block *dropf, *dropl;
};
char *Ebadarp = "bad arp";
/* quick hash for ip addresses */
#define hashipa(a) (((a)[IPaddrlen-2] + (a)[IPaddrlen-1])%NHASH)
static void rxmitproc(void*);
void
arpinit(Fs *f)
{
f->arp = smalloc(sizeof(Arp));
f->arp->f = f;
f->arp->rxmt[0] = nil;
f->arp->rxmt[1] = nil;
f->arp->dropf = f->arp->dropl = nil;
kproc("rxmitproc", rxmitproc, f->arp);
}
static void
freeblistchain(Block *bp)
{
Block *next;
while(bp != nil){
next = bp->list;
freeblist(bp);
bp = next;
}
}
/* take out of re-transmit chain */
static Arpent**
rxmtunchain(Arp *arp, Arpent *a)
{
Arpent **l;
for(l = &arp->rxmt[isv4(a->ip) != 0]; *l != nil; l = &((*l)->nextrxt)){
if(*l == a){
*l = a->nextrxt;
break;
}
}
a->nextrxt = nil;
return l;
}
static void
cleanarpent(Arp *arp, Arpent *a)
{
Arpent **l;
Block *bp;
/* take out of current chain */
for(l = &arp->hash[hashipa(a->ip)]; *l != nil; l = &((*l)->hash)){
if(*l == a){
*l = a->hash;
break;
}
}
a->hash = nil;
/* remove from retransmit / timout chain */
rxmtunchain(arp, a);
/* 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;
a->ifcid = 0;
a->state = 0;
a->rxtsrem = 0;
a->utime = 0;
a->ctime = 0;
}
/*
* create a new arp entry for an ip address on ifc.
*/
static Arpent*
newarpent(Arp *arp, uchar *ip, Ipifc *ifc)
{
Arpent *a, *e, *f, **l;
ulong t;
/* find oldest entry */
e = &arp->cache[NCACHE];
a = arp->cache;
t = a->utime;
for(f = a+1; t > 0 && f < e; f++){
if(f->utime < t){
t = f->utime;
a = f;
}
}
cleanarpent(arp, a);
ipmove(a->ip, ip);
a->ifc = ifc;
a->ifcid = ifc->ifcid;
/* insert into new chain */
l = &arp->hash[hashipa(ip)];
a->hash = *l;
*l = a;
return a;
}
static Arpent*
arplookup(Arp *arp, Ipifc *ifc, uchar *ip)
{
Arpent *a;
for(a = arp->hash[hashipa(ip)]; a != nil; a = a->hash){
if(a->ifc == ifc && a->ifcid == ifc->ifcid && ipcmp(ip, a->ip) == 0)
return a;
}
return nil;
}
/*
* fill in the media address if we have it. Otherwise return an
* Arpent that represents the state of the address resolution FSM
* for ip. Add the packet to be sent onto the list of packets
* waiting for ip->mac to be resolved.
*/
Arpent*
arpget(Arp *arp, Block *bp, int version, Ipifc *ifc, uchar *ip, uchar *mac, Routehint *rh)
{
uchar v6ip[IPaddrlen];
Arpent *a;
if(version == V4){
v4tov6(v6ip, ip);
ip = v6ip;
}
if(rh != nil
&& (a = rh->a) != nil
&& a->state == AOK
&& a->ifc == ifc
&& a->ifcid == ifc->ifcid
&& ipcmp(ip, a->ip) == 0){
memmove(mac, a->mac, ifc->m->maclen);
a->utime = NOW;
if(a->utime - a->ctime < MAXAGE_TIMER)
return nil;
}
rlock(arp);
if((a = arplookup(arp, ifc, ip)) != nil && a->state == AOK){
memmove(mac, a->mac, ifc->m->maclen);
a->utime = NOW;
if(a->utime - a->ctime < MAXAGE_TIMER){
if(rh != nil)
rh->a = a;
runlock(arp);
return nil;
}
}
if(rh != nil)
rh->a = nil;
runlock(arp);
wlock(arp);
if((a = arplookup(arp, ifc, ip)) == nil)
a = newarpent(arp, ip, ifc);
a->state = AWAIT;
a->utime = NOW;
if(bp != nil){
/* needs to be a single block */
assert(bp->list == nil);
if(a->hold == nil)
a->hold = bp;
else
a->last->list = bp;
a->last = bp;
}
return a; /* return with arp locked */
}
/*
* 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;
/* remove all but the last message */
while((bp = a->hold) != nil){
if(bp == a->last)
break;
a->hold = bp->list;
freeblist(bp);
}
/* try to keep it around for a second more */
a->ctime = a->utime = NOW;
/* 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);
wunlock(arp);
}
/*
* called with arp locked
*/
void
arprelease(Arp *arp, Arpent*)
{
wunlock(arp);
}
/*
* Copy out the mac address from the Arpent. Return the
* block waiting to get sent to this mac address.
*
* called with arp locked
*/
Block*
arpresolve(Arp *arp, Arpent *a, uchar *mac, Routehint *rh)
{
Block *bp;
memmove(a->mac, mac, a->ifc->m->maclen);
if(a->state == AWAIT) {
rxmtunchain(arp, a);
a->rxtsrem = 0;
}
bp = a->hold;
a->hold = a->last = nil;
a->ctime = a->utime = NOW;
a->state = AOK;
if(rh != nil)
rh->a = a;
wunlock(arp);
return bp;
}
int
arpenter(Fs *fs, int version, uchar *ip, uchar *mac, int n, uchar *ia, Ipifc *ifc, int refresh)
{
Routehint rh;
uchar v6ip[IPaddrlen];
Block *bp, *next;
Arpent *a;
Route *r;
Arp *arp;
if(ifc->m == nil || ifc->m->maclen != n || ifc->m->maclen == 0)
return -1;
rh.r = nil;
rh.a = nil;
switch(version){
case V4:
r = v4lookup(fs, ip, ia, &rh);
v4tov6(v6ip, ip);
ip = v6ip;
break;
case V6:
r = v6lookup(fs, ip, ia, &rh);
break;
default:
panic("arpenter: version %d", version);
return -1; /* to supress warnings */
}
if(r == nil || r->ifc != ifc || (r->type & (Rbcast|Rmulti)) != 0)
return -1;
arp = fs->arp;
wlock(arp);
if((a = arplookup(arp, ifc, ip)) == nil){
if(refresh){
wunlock(arp);
return 0;
}
a = newarpent(arp, ip, ifc);
}
bp = arpresolve(arp, a, mac, &rh); /* unlocks arp */
if(version == V4)
ip += IPv4off;
for(; bp != nil; bp = next){
next = bp->list;
bp->list = nil;
if(waserror()){
freeblistchain(next);
break;
}
ipifcoput(ifc, bp, version, ip, &rh);
poperror();
}
return 1;
}
int
arpwrite(Fs *fs, char *s, int len)
{
int n;
Arp *arp;
Arpent *a, *x;
Medium *m;
Ipifc *ifc;
char *f[5], buf[256];
uchar ip[IPaddrlen], ia[IPaddrlen], mac[MAClen];
arp = fs->arp;
if(len == 0)
error(Ebadarp);
if(len >= sizeof(buf))
len = sizeof(buf)-1;
strncpy(buf, s, len);
buf[len] = 0;
if(len > 0 && buf[len-1] == '\n')
buf[len-1] = 0;
n = getfields(buf, f, nelem(f), 1, " ");
if(strcmp(f[0], "flush") == 0){
wlock(arp);
for(a = arp->cache; a < &arp->cache[NCACHE]; a++){
a->hash = nil;
a->nextrxt = nil;
a->ifc = nil;
a->ifcid = 0;
a->state = 0;
a->rxtsrem = 0;
a->ctime = 0;
a->utime = 0;
freeblistchain(a->hold);
a->hold = a->last = nil;
}
memset(arp->hash, 0, sizeof(arp->hash));
freeblistchain(arp->dropf);
arp->dropf = arp->dropl = nil;
arp->rxmt[0] = arp->rxmt[1] = nil;
wunlock(arp);
} else if(strcmp(f[0], "add") == 0){
switch(n){
default:
error(Ebadarg);
case 3:
if(parseip(ip, f[1]) == -1)
error(Ebadip);
if((n = parsemac(mac, f[2], sizeof(mac))) <= 0)
error(Ebadarp);
findlocalip(fs, ia, ip);
break;
case 4:
m = ipfindmedium(f[1]);
if(m == nil || m->maclen == 0)
error(Ebadarp);
if(parseip(ip, f[2]) == -1)
error(Ebadip);
if((n = parsemac(mac, f[3], sizeof(mac))) != m->maclen)
error(Ebadarp);
findlocalip(fs, ia, ip);
break;
case 5:
m = ipfindmedium(f[1]);
if(m == nil || m->maclen == 0)
error(Ebadarp);
if(parseip(ip, f[2]) == -1)
error(Ebadip);
if((n = parsemac(mac, f[3], sizeof(mac))) != m->maclen)
error(Ebadarp);
if(parseip(ia, f[4]) == -1)
error(Ebadip);
break;
}
if((ifc = findipifc(fs, ia, ia, Runi)) == nil)
error("no interface");
rlock(ifc);
if(!ipv6local(ifc, ia, 0, ip) || arpenter(fs, V6, ip, mac, n, ia, ifc, 0) < 0){
runlock(ifc);
error("destination unreachable");
}
runlock(ifc);
} else if(strcmp(f[0], "del") == 0){
if (n != 2)
error(Ebadarg);
if (parseip(ip, f[1]) == -1)
error(Ebadip);
wlock(arp);
for(a = arp->hash[hashipa(ip)]; a != nil; a = x){
x = a->hash;
if(ipcmp(ip, a->ip) == 0)
cleanarpent(arp, a);
}
wunlock(arp);
} else
error(Ebadarp);
return len;
}
static void
convmac(char *p, uchar *mac, int n)
{
while(n-- > 0)
p += sprint(p, "%2.2ux", *mac++);
}
int
arpread(Arp *arp, char *s, ulong offset, int len)
{
char mac[2*MAClen+1], *state, *mname, *p;
uchar ip[IPaddrlen], ia[IPaddrlen];
Ipifc *ifc;
Arpent *a;
long n, o;
p = s;
o = -offset;
for(a = arp->cache; len > 0 && a < &arp->cache[NCACHE]; a++){
if(a->state == 0 || (ifc = a->ifc) == nil)
continue;
rlock(ifc);
rlock(arp);
ipmove(ip, a->ip);
if(ifc->m == nil || a->state == 0 || a->ifc != ifc || a->ifcid != ifc->ifcid || !ipv6local(ifc, ia, 0, ip)){
runlock(arp);
runlock(ifc);
continue;
}
mname = ifc->m->name;
state = arpstate[a->state];
convmac(mac, a->mac, ifc->m->maclen);
runlock(arp);
runlock(ifc);
n = snprint(up->genbuf, sizeof up->genbuf,
"%-6.6s %-4.4s %-40.40I %-16.16s %I\n",
mname, state, ip, mac, ia);
o += n;
if(o <= 0)
continue;
if(n > len)
break;
memmove(p, up->genbuf, n);
len -= n;
p += n;
}
return p - s;
}
void
ndpsendsol(Fs *f, Arpent *a)
{
Ipifc *ifc = a->ifc;
uchar targ[IPaddrlen], src[IPaddrlen];
if(a->rxtsrem == 0)
a->rxtsrem = MAX_MULTICAST_SOLICIT;
else
a->rxtsrem--;
/* try to use source address of original packet */
ipmove(targ, a->ip);
if(a->last != nil){
ipmove(src, ((Ip6hdr*)a->last->rp)->src);
arpcontinue(f->arp, a);
if(iplocalonifc(ifc, src) != nil || ipproxyifc(f, ifc, src))
goto send;
} else {
arpcontinue(f->arp, a);
}
if(!ipv6local(ifc, src, 0, targ))
return;
send:
if(!waserror()){
icmpns(f, src, SRC_UNI, targ, TARG_MULTI, ifc->mac);
poperror();
}
}
static Block*
rxmt(Arp *arp)
{
Arpent *a;
Block *bp;
Ipifc *ifc;
wlock(arp);
/* 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, a); /* unlocks arp */
runlock(ifc);
wlock(arp);
continue;
}
runlock(ifc);
}
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;
wunlock(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;
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()){
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);
}
freeblist(bp);
}
}
static int
rxready(void *v)
{
Arp *arp = (Arp *)v;
return arp->rxmt[0] != nil || arp->rxmt[1] != nil || arp->dropf != nil;
}
static void
rxmitproc(void *v)
{
Arp *arp = v;
arp->rxmitp = up;
if(waserror()){
arp->rxmitp = nil;
pexit("hangup", 1);
}
for(;;){
sleep(&arp->rxmtq, rxready, v);
drop(arp->f, rxmt(arp));
tsleep(&arp->rxmtq, return0, nil, RETRANS_TIMER/4);
}
}