blob: cff13bb9ea7d8e24b2b16ccbf7b3c150da87def9 [file] [log] [blame]
/* Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
* Portions Copyright © 1997-1999 Vita Nuova Limited
* Portions Copyright © 2000-2007 Vita Nuova Holdings Limited
* (www.vitanuova.com)
* Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
*
* Modified for the Akaros operating system:
* Copyright (c) 2013-2014 The Regents of the University of California
* Copyright (c) 2013-2015 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. */
#include <slab.h>
#include <kmalloc.h>
#include <kref.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <error.h>
#include <cpio.h>
#include <pmap.h>
#include <smp.h>
#include <net/ip.h>
/*
* address resolution tables
*/
enum {
NHASH = (1 << 6),
NCACHE = 256,
AOK = 1,
AWAIT = 2,
};
char *arpstate[] = {
"UNUSED",
"OK",
"WAIT",
};
/*
* one per Fs
*/
struct arp {
qlock_t qlock;
struct Fs *f;
struct arpent *hash[NHASH];
struct arpent cache[NCACHE];
struct arpent *rxmt;
struct proc *rxmitp; /* neib sol re-transmit proc */
struct rendez rxmtq;
struct block *dropf, *dropl;
};
#define haship(s) ((s)[IPaddrlen-1]%NHASH)
int ReTransTimer = RETRANS_TIMER;
static void rxmitproc(void *v);
void arpinit(struct Fs *f)
{
f->arp = kzmalloc(sizeof(struct arp), MEM_WAIT);
qlock_init(&f->arp->qlock);
rendez_init(&f->arp->rxmtq);
f->arp->f = f;
f->arp->rxmt = NULL;
f->arp->dropf = f->arp->dropl = NULL;
ktask("rxmitproc", rxmitproc, f->arp);
}
/*
* create a new arp entry for an ip address.
*/
static struct arpent *newarp6(struct arp *arp, uint8_t *ip, struct Ipifc *ifc,
int addrxt)
{
unsigned int t;
struct block *next, *xp;
struct arpent *a, *e, *f, **l;
struct medium *m = ifc->m;
int empty;
/* find oldest entry */
e = &arp->cache[NCACHE];
a = arp->cache;
t = a->utime;
for (f = a; f < e; f++) {
if (f->utime < t) {
t = f->utime;
a = f;
}
}
/* dump waiting packets */
xp = a->hold;
a->hold = NULL;
if (isv4(a->ip)) {
while (xp) {
next = xp->list;
freeblist(xp);
xp = next;
}
} else {
/* queue icmp unreachable for rxmitproc later, w/o arp lock */
if (xp) {
if (arp->dropl == NULL)
arp->dropf = xp;
else
arp->dropl->list = xp;
for (next = xp->list; next; next = next->list)
xp = next;
arp->dropl = xp;
rendez_wakeup(&arp->rxmtq);
}
}
/* take out of current chain */
l = &arp->hash[haship(a->ip)];
for (f = *l; f; f = f->hash) {
if (f == a) {
*l = a->hash;
break;
}
l = &f->hash;
}
/* insert into new chain */
l = &arp->hash[haship(ip)];
a->hash = *l;
*l = a;
memmove(a->ip, ip, sizeof(a->ip));
a->utime = NOW;
a->ctime = 0; /* somewhat of a "last sent time". 0, to trigger a send. */
a->type = m;
a->rtime = NOW + ReTransTimer;
a->rxtsrem = MAX_MULTICAST_SOLICIT;
a->ifc = ifc;
a->ifcid = ifc->ifcid;
/* put to the end of re-transmit chain; addrxt is 0 when isv4(a->ip) */
if (!ipismulticast(a->ip) && addrxt) {
l = &arp->rxmt;
empty = (*l == NULL);
for (f = *l; f; f = f->nextrxt) {
if (f == a) {
*l = a->nextrxt;
break;
}
l = &f->nextrxt;
}
for (f = *l; f; f = f->nextrxt) {
l = &f->nextrxt;
}
*l = a;
if (empty)
rendez_wakeup(&arp->rxmtq);
}
a->nextrxt = NULL;
return a;
}
/* called with arp qlocked */
void cleanarpent(struct arp *arp, struct arpent *a)
{
struct arpent *f, **l;
a->utime = 0;
a->ctime = 0;
a->type = 0;
a->state = 0;
/* take out of current chain */
l = &arp->hash[haship(a->ip)];
for (f = *l; f; f = f->hash) {
if (f == a) {
*l = a->hash;
break;
}
l = &f->hash;
}
/* take out of re-transmit chain */
l = &arp->rxmt;
for (f = *l; f; f = f->nextrxt) {
if (f == a) {
*l = a->nextrxt;
break;
}
l = &f->nextrxt;
}
a->nextrxt = NULL;
a->hash = NULL;
a->hold = NULL;
a->last = NULL;
a->ifc = NULL;
}
/*
* 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.
*/
struct arpent *arpget(struct arp *arp, struct block *bp, int version,
struct Ipifc *ifc, uint8_t *ip, uint8_t *mac)
{
int hash, len;
struct arpent *a;
struct medium *type = ifc->m;
uint8_t v6ip[IPaddrlen];
uint16_t *s, *d;
if (version == V4) {
v4tov6(v6ip, ip);
ip = v6ip;
}
qlock(&arp->qlock);
hash = haship(ip);
for (a = arp->hash[hash]; a; a = a->hash) {
if (ipcmp(ip, a->ip) == 0)
if (type == a->type)
break;
}
if (a == NULL) {
a = newarp6(arp, ip, ifc, (version != V4));
a->state = AWAIT;
}
a->utime = NOW;
if (a->state == AWAIT) {
if (bp != NULL) {
if (a->hold)
a->last->list = bp;
else
a->hold = bp;
a->last = bp;
bp->list = NULL;
}
return a; /* return with arp qlocked */
}
s = (uint16_t *)a->mac;
d = (uint16_t *)mac;
len = a->type->maclen / 2;
while (len) {
*d++ = *s++;
len--;
}
/* remove old entries */
if (NOW - a->ctime > 15 * 60 * 1000)
cleanarpent(arp, a);
qunlock(&arp->qlock);
return NULL;
}
/*
* called with arp locked
*/
void arprelease(struct arp *arp, struct arpent *a)
{
qunlock(&arp->qlock);
}
/*
* Copy out the mac address from the arpent. Return the
* block waiting to get sent to this mac address.
*
* called with arp locked
*/
struct block *arpresolve(struct arp *arp, struct arpent *a, struct medium *type,
uint8_t *mac)
{
struct block *bp;
struct arpent *f, **l;
if (!isv4(a->ip)) {
l = &arp->rxmt;
for (f = *l; f; f = f->nextrxt) {
if (f == a) {
*l = a->nextrxt;
break;
}
l = &f->nextrxt;
}
}
memmove(a->mac, mac, type->maclen);
a->type = type;
a->state = AOK;
a->utime = NOW;
bp = a->hold;
a->hold = NULL;
/* brho: it looks like we return the entire hold list, though it might
* be purged by now via some other crazy arp list management. our
* callers can't handle the arp's b->list stuff. */
assert(!bp->list);
qunlock(&arp->qlock);
return bp;
}
void arpenter(struct Fs *fs, int version, uint8_t *ip, uint8_t *mac, int n,
int refresh)
{
ERRSTACK(1);
struct arp *arp;
struct route *r;
struct arpent *a, *f, **l;
struct Ipifc *ifc;
struct medium *type;
struct block *bp, *next;
uint8_t v6ip[IPaddrlen];
arp = fs->arp;
if (n != 6) {
return;
}
switch (version) {
case V4:
r = v4lookup(fs, ip, NULL);
v4tov6(v6ip, ip);
ip = v6ip;
break;
case V6:
r = v6lookup(fs, ip, NULL);
break;
default:
panic("arpenter: version %d", version);
return; /* to supress warnings */
}
if (r == NULL) {
return;
}
ifc = r->rt.ifc;
type = ifc->m;
qlock(&arp->qlock);
for (a = arp->hash[haship(ip)]; a; a = a->hash) {
if (a->type != type || (a->state != AWAIT && a->state != AOK))
continue;
if (ipcmp(a->ip, ip) == 0) {
a->state = AOK;
memmove(a->mac, mac, type->maclen);
if (version == V6) {
/* take out of re-transmit chain */
l = &arp->rxmt;
for (f = *l; f; f = f->nextrxt) {
if (f == a) {
*l = a->nextrxt;
break;
}
l = &f->nextrxt;
}
}
a->ifc = ifc;
a->ifcid = ifc->ifcid;
bp = a->hold;
a->hold = NULL;
if (version == V4)
ip += IPv4off;
a->utime = NOW;
a->ctime = a->utime;
qunlock(&arp->qlock);
while (bp) {
next = bp->list;
if (ifc != NULL) {
rlock(&ifc->rwlock);
if (waserror()) {
runlock(&ifc->rwlock);
nexterror();
}
if (ifc->m != NULL)
ifc->m->bwrite(ifc, bp, version,
ip);
else
freeb(bp);
runlock(&ifc->rwlock);
poperror();
} else
freeb(bp);
bp = next;
}
return;
}
}
if (refresh == 0) {
a = newarp6(arp, ip, ifc, 0);
a->state = AOK;
a->type = type;
a->ctime = NOW;
memmove(a->mac, mac, type->maclen);
}
qunlock(&arp->qlock);
}
int arpwrite(struct Fs *fs, char *s, long len)
{
int n;
struct route *r;
struct arp *arp;
struct block *bp;
struct arpent *a, *fl, **l;
struct medium *m;
char *f[4], buf[256];
uint8_t ip[IPaddrlen], mac[MAClen];
arp = fs->arp;
if (len <= 0)
error(EINVAL, ERROR_FIXME);
if (len > sizeof(buf))
len = sizeof(buf);
strlcpy(buf, s, sizeof(buf));
if (len > 0 && buf[len - 2] == '\n')
buf[len - 2] = 0;
n = getfields(buf, f, 4, 1, " ");
if (strcmp(f[0], "flush") == 0) {
qlock(&arp->qlock);
for (a = arp->cache; a < &arp->cache[NCACHE]; a++) {
memset(a->ip, 0, sizeof(a->ip));
memset(a->mac, 0, sizeof(a->mac));
a->hash = NULL;
a->state = 0;
a->utime = 0;
while (a->hold != NULL) {
bp = a->hold->list;
freeblist(a->hold);
a->hold = bp;
}
}
memset(arp->hash, 0, sizeof(arp->hash));
/* clear all pkts on these lists (rxmt, dropf/l) */
arp->rxmt = NULL;
arp->dropf = NULL;
arp->dropl = NULL;
qunlock(&arp->qlock);
} else if (strcmp(f[0], "add") == 0) {
switch (n) {
default:
error(EINVAL, ERROR_FIXME);
case 3:
parseip(ip, f[1]);
if (isv4(ip))
r = v4lookup(fs, ip + IPv4off, NULL);
else
r = v6lookup(fs, ip, NULL);
if (r == NULL)
error(EHOSTUNREACH,
"Destination unreachable");
m = r->rt.ifc->m;
n = parsemac(mac, f[2], m->maclen);
break;
case 4:
m = ipfindmedium(f[1]);
if (m == NULL)
error(EINVAL, ERROR_FIXME);
parseip(ip, f[2]);
n = parsemac(mac, f[3], m->maclen);
break;
}
if (m->ares == NULL)
error(EINVAL, ERROR_FIXME);
m->ares(fs, V6, ip, mac, n, 0);
} else if (strcmp(f[0], "del") == 0) {
if (n != 2)
error(EINVAL, ERROR_FIXME);
parseip(ip, f[1]);
qlock(&arp->qlock);
l = &arp->hash[haship(ip)];
for (a = *l; a; a = a->hash) {
if (memcmp(ip, a->ip, sizeof(a->ip)) == 0) {
*l = a->hash;
break;
}
l = &a->hash;
}
if (a) {
/* take out of re-transmit chain */
l = &arp->rxmt;
for (fl = *l; fl; fl = fl->nextrxt) {
if (fl == a) {
*l = a->nextrxt;
break;
}
l = &fl->nextrxt;
}
a->nextrxt = NULL;
a->hash = NULL;
a->hold = NULL;
a->last = NULL;
a->ifc = NULL;
memset(a->ip, 0, sizeof(a->ip));
memset(a->mac, 0, sizeof(a->mac));
}
qunlock(&arp->qlock);
} else
error(EINVAL, ERROR_FIXME);
return len;
}
enum {
Alinelen = 90,
};
static char *aformat = "%-6.6s %-8.8s %-40.40I %E\n";
int arpread(struct arp *arp, char *p, uint32_t offset, int len)
{
struct arpent *a;
int n;
int left = len;
int amt;
if (offset % Alinelen)
return 0;
offset = offset / Alinelen;
len = len / Alinelen;
n = 0;
for (a = arp->cache; len > 0 && a < &arp->cache[NCACHE]; a++) {
if (a->state == 0)
continue;
if (offset > 0) {
offset--;
continue;
}
len--;
left--;
qlock(&arp->qlock);
amt = snprintf(p + n, left, aformat, a->type->name,
arpstate[a->state],
a->ip, a->mac);
n += amt;
left -= amt;
qunlock(&arp->qlock);
}
return n;
}
static uint64_t rxmitsols(struct arp *arp)
{
unsigned int sflag;
struct block *next, *xp;
struct arpent *a, *b, **l;
struct Fs *f;
uint8_t ipsrc[IPaddrlen];
struct Ipifc *ifc = NULL;
uint64_t nrxt;
qlock(&arp->qlock);
f = arp->f;
a = arp->rxmt;
if (a == NULL) {
nrxt = 0;
goto dodrops; /* return nrxt; */
}
nrxt = a->rtime - NOW;
if (nrxt > 3 * ReTransTimer / 4)
goto dodrops; /* return nrxt; */
for (; a; a = a->nextrxt) {
ifc = a->ifc;
assert(ifc != NULL);
if ((a->rxtsrem <= 0) || !(canrlock(&ifc->rwlock))
|| (a->ifcid != ifc->ifcid)) {
xp = a->hold;
a->hold = NULL;
if (xp) {
if (arp->dropl == NULL)
arp->dropf = xp;
else
arp->dropl->list = xp;
}
cleanarpent(arp, a);
} else
break;
}
if (a == NULL)
goto dodrops;
qunlock(&arp->qlock); /* for icmpns */
if ((sflag = ipv6anylocal(ifc, ipsrc)) != SRC_UNSPEC)
icmpns(f, ipsrc, sflag, a->ip, TARG_MULTI, ifc->mac);
runlock(&ifc->rwlock);
qlock(&arp->qlock);
/* put to the end of re-transmit chain */
l = &arp->rxmt;
for (b = *l; b; b = b->nextrxt) {
if (b == a) {
*l = a->nextrxt;
break;
}
l = &b->nextrxt;
}
for (b = *l; b; b = b->nextrxt) {
l = &b->nextrxt;
}
*l = a;
a->rxtsrem--;
a->nextrxt = NULL;
a->rtime = NOW + ReTransTimer;
a = arp->rxmt;
if (a == NULL)
nrxt = 0;
else
nrxt = a->rtime - NOW;
dodrops:
xp = arp->dropf;
arp->dropf = NULL;
arp->dropl = NULL;
qunlock(&arp->qlock);
for (; xp; xp = next) {
next = xp->list;
icmphostunr(f, ifc, xp, icmp6_adr_unreach, 1);
}
return nrxt;
}
static int rxready(void *v)
{
struct arp *arp = (struct arp *)v;
int x;
x = ((arp->rxmt != NULL) || (arp->dropf != NULL));
return x;
}
static void rxmitproc(void *v)
{
ERRSTACK(2);
struct arp *arp = v;
uint64_t wakeupat;
arp->rxmitp = current;
if (waserror()) {
arp->rxmitp = 0;
poperror();
warn("arp rxmit ktask exited");
return;
}
for (;;) {
wakeupat = rxmitsols(arp);
if (wakeupat == 0)
rendez_sleep(&arp->rxmtq, rxready, v);
else if (wakeupat > ReTransTimer / 4)
kthread_usleep(wakeupat * 1000);
}
poperror();
}