![cinap_lenrek](/assets/img/avatar_default.png)
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.
642 lines
12 KiB
C
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);
|
|
}
|
|
}
|