| /* 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(); |
| } |