freebsd-skq/sys/net/if_llatbl.c

607 lines
14 KiB
C
Raw Normal View History

2008-12-15 06:53:09 +00:00
/*
* Copyright (c) 2004 Luigi Rizzo, Alessandro Cerri. All rights reserved.
* Copyright (c) 2004-2008 Qing Li. All rights reserved.
* Copyright (c) 2008 Kip Macy. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "opt_ddb.h"
2008-12-15 06:53:09 +00:00
#include "opt_inet.h"
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/rwlock.h>
#include <sys/rmlock.h>
2008-12-15 06:53:09 +00:00
#ifdef DDB
#include <ddb/ddb.h>
#endif
2008-12-15 06:53:09 +00:00
#include <vm/uma.h>
#include <netinet/in.h>
#include <net/if_llatbl.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_var.h>
#include <net/route.h>
#include <net/vnet.h>
2008-12-15 06:53:09 +00:00
#include <netinet/if_ether.h>
#include <netinet6/in6_var.h>
#include <netinet6/nd6.h>
MALLOC_DEFINE(M_LLTABLE, "lltable", "link level address tables");
static VNET_DEFINE(SLIST_HEAD(, lltable), lltables);
#define V_lltables VNET(lltables)
2008-12-15 06:53:09 +00:00
static void vnet_lltable_init(void);
struct rwlock lltable_rwlock;
RW_SYSINIT(lltable_rwlock, &lltable_rwlock, "lltable_rwlock");
2008-12-15 06:53:09 +00:00
/*
* Dump lle state for a specific address family.
2008-12-15 06:53:09 +00:00
*/
static int
lltable_dump_af(struct lltable *llt, struct sysctl_req *wr)
{
struct llentry *lle;
int i, error;
LLTABLE_LOCK_ASSERT();
if (llt->llt_ifp->if_flags & IFF_LOOPBACK)
return (0);
error = 0;
IF_AFDATA_CFG_RLOCK(llt->llt_ifp);
for (i = 0; i < LLTBL_HASHTBL_SIZE; i++) {
LIST_FOREACH(lle, &llt->lle_head[i], lle_next) {
error = llt->llt_dump_entry(llt, lle, wr);
if (error != 0)
break;
}
}
IF_AFDATA_CFG_RUNLOCK(llt->llt_ifp);
return (error);
}
2008-12-15 06:53:09 +00:00
int
lltable_sysctl_dumparp(int af, struct sysctl_req *wr)
{
struct lltable *llt;
int error = 0;
LLTABLE_RLOCK();
SLIST_FOREACH(llt, &V_lltables, llt_link) {
2008-12-15 06:53:09 +00:00
if (llt->llt_af == af) {
error = lltable_dump_af(llt, wr);
2008-12-15 06:53:09 +00:00
if (error != 0)
goto done;
}
}
done:
LLTABLE_RUNLOCK();
2008-12-15 06:53:09 +00:00
return (error);
}
void
llentry_link(struct lltable *llt, struct llentry *lle)
{
struct llentries *lleh;
uint32_t hashkey;
if ((lle->la_flags & LLE_LINKED) != 0)
return;
IF_AFDATA_RUN_WLOCK_ASSERT(llt->llt_ifp);
hashkey = llt->llt_hash(lle);
lleh = &llt->lle_head[LLATBL_HASH(hashkey, LLTBL_HASHMASK)];
lle->lle_tbl = llt;
lle->lle_head = lleh;
lle->la_flags |= LLE_LINKED;
LIST_INSERT_HEAD(lleh, lle, lle_next);
}
void
llentry_unlink(struct llentry *lle)
{
if ((lle->la_flags & LLE_LINKED) != 0) {
IF_AFDATA_RUN_WLOCK_ASSERT(lle->lle_tbl->llt_ifp);
LIST_REMOVE(lle, lle_next);
lle->la_flags &= ~(LLE_VALID | LLE_LINKED);
lle->lle_tbl = NULL;
lle->lle_head = NULL;
}
}
void
llentries_unlink(struct llentries *head)
{
struct llentry *lle, *next;
LIST_FOREACH_SAFE(lle, head, lle_chain, next) {
llentry_unlink(lle);
}
}
2008-12-15 06:53:09 +00:00
/*
* Deletes an address from the address table.
* This function is called by the timer functions
* such as arptimer() and nd6_llinfo_timer(), and
* the caller does the locking.
*
* Returns the number of held packets, if any, that were dropped.
2008-12-15 06:53:09 +00:00
*/
size_t
2008-12-15 06:53:09 +00:00
llentry_free(struct llentry *lle)
{
size_t pkts_dropped;
struct mbuf *next;
2008-12-15 06:53:09 +00:00
LLE_WLOCK_ASSERT(lle);
KASSERT((lle->la_flags & LLE_LINKED) == 0, ("Freeing linked lle"));
2008-12-15 06:53:09 +00:00
pkts_dropped = 0;
while ((lle->la_numheld > 0) && (lle->la_hold != NULL)) {
next = lle->la_hold->m_nextpkt;
2008-12-15 06:53:09 +00:00
m_freem(lle->la_hold);
lle->la_hold = next;
lle->la_numheld--;
pkts_dropped++;
}
KASSERT(lle->la_numheld == 0,
("%s: la_numheld %d > 0, pkts_droped %zd", __func__,
lle->la_numheld, pkts_dropped));
2008-12-15 06:53:09 +00:00
LLE_FREE_LOCKED(lle);
return (pkts_dropped);
2008-12-15 06:53:09 +00:00
}
/*
* (al)locate an llentry for address dst (equivalent to rtalloc for new-arp).
*
* If found the llentry * is returned referenced and unlocked.
*
* XXX: Remove after converting flowtable
*/
struct llentry *
llentry_alloc(struct ifnet *ifp, struct lltable *lt,
struct sockaddr_storage *dst)
{
struct llentry *la;
2012-08-01 09:00:26 +00:00
IF_AFDATA_RLOCK(ifp);
la = lt->llt_lookup(lt, LLE_EXCLUSIVE, (struct sockaddr *)dst);
IF_AFDATA_RUNLOCK(ifp);
if ((la == NULL) &&
(ifp->if_flags & (IFF_NOARP | IFF_STATICARP)) == 0) {
IF_AFDATA_CFG_WLOCK(ifp);
la = lt->llt_create(lt, 0, (struct sockaddr *)dst);
if (la != NULL) {
IF_AFDATA_RUN_WLOCK(ifp);
llentry_link(lt, la);
IF_AFDATA_RUN_WUNLOCK(ifp);
}
IF_AFDATA_CFG_WUNLOCK(ifp);
}
if (la != NULL) {
LLE_ADDREF(la);
LLE_WUNLOCK(la);
}
return (la);
}
2008-12-15 06:53:09 +00:00
/*
* Free all entries from given table and free itself.
*/
void
lltable_free(struct lltable *llt)
{
struct llentry *lle, *next;
struct llentries dchain;
2008-12-15 06:53:09 +00:00
int i;
KASSERT(llt != NULL, ("%s: llt is NULL", __func__));
LLTABLE_WLOCK();
SLIST_REMOVE(&V_lltables, llt, lltable, llt_link);
LLTABLE_WUNLOCK();
2008-12-15 06:53:09 +00:00
LIST_INIT(&dchain);
IF_AFDATA_CFG_WLOCK(llt->llt_ifp);
for (i = 0; i < LLTBL_HASHTBL_SIZE; i++) {
2008-12-15 06:53:09 +00:00
LIST_FOREACH_SAFE(lle, &llt->lle_head[i], lle_next, next) {
LLE_WLOCK(lle);
LIST_INSERT_HEAD(&dchain, lle, lle_chain);
2008-12-15 06:53:09 +00:00
}
}
IF_AFDATA_RUN_WLOCK(llt->llt_ifp);
llentries_unlink(&dchain);
IF_AFDATA_RUN_WUNLOCK(llt->llt_ifp);
IF_AFDATA_CFG_WUNLOCK(llt->llt_ifp);
2008-12-15 06:53:09 +00:00
LIST_FOREACH_SAFE(lle, &dchain, lle_chain, next)
llt->llt_clear_entry(llt, lle);
2008-12-15 06:53:09 +00:00
free(llt, M_LLTABLE);
}
static void
lltable_prefix_free_af(struct lltable *llt, const struct sockaddr *prefix,
const struct sockaddr *mask, u_int flags)
{
struct llentries dchain;
struct llentry *lle, *next;
int i;
LIST_INIT(&dchain);
IF_AFDATA_CFG_WLOCK(llt->llt_ifp);
for (i = 0; i < LLTBL_HASHTBL_SIZE; i++) {
LIST_FOREACH_SAFE(lle, &llt->lle_head[i], lle_next, next) {
if (llt->llt_match_prefix(prefix, mask, flags, lle)) {
LLE_WLOCK(lle);
LIST_INSERT_HEAD(&dchain, lle, lle_chain);
}
}
}
IF_AFDATA_RUN_WLOCK(llt->llt_ifp);
llentries_unlink(&dchain);
IF_AFDATA_RUN_WUNLOCK(llt->llt_ifp);
IF_AFDATA_CFG_WUNLOCK(llt->llt_ifp);
LIST_FOREACH_SAFE(lle, &dchain, lle_chain, next)
llt->llt_clear_entry(llt, lle);
}
#if 0
2008-12-15 06:53:09 +00:00
void
lltable_drain(int af)
{
struct lltable *llt;
struct llentry *lle;
register int i;
LLTABLE_RLOCK();
SLIST_FOREACH(llt, &V_lltables, llt_link) {
2008-12-15 06:53:09 +00:00
if (llt->llt_af != af)
continue;
for (i=0; i < LLTBL_HASHTBL_SIZE; i++) {
LIST_FOREACH(lle, &llt->lle_head[i], lle_next) {
LLE_WLOCK(lle);
2008-12-15 06:53:09 +00:00
if (lle->la_hold) {
m_freem(lle->la_hold);
lle->la_hold = NULL;
}
LLE_WUNLOCK(lle);
2008-12-15 06:53:09 +00:00
}
}
}
LLTABLE_RUNLOCK();
2008-12-15 06:53:09 +00:00
}
#endif
2008-12-15 06:53:09 +00:00
void
lltable_prefix_free(int af, struct sockaddr *prefix, struct sockaddr *mask,
u_int flags)
{
struct lltable *llt;
LLTABLE_RLOCK();
SLIST_FOREACH(llt, &V_lltables, llt_link) {
if (llt->llt_af != af)
continue;
lltable_prefix_free_af(llt, prefix, mask, flags);
}
LLTABLE_RUNLOCK();
}
2008-12-15 06:53:09 +00:00
/*
* Create a new lltable.
*/
struct lltable *
lltable_init(struct ifnet *ifp, int af)
{
struct lltable *llt;
register int i;
llt = malloc(sizeof(struct lltable), M_LLTABLE, M_WAITOK);
llt->llt_af = af;
llt->llt_ifp = ifp;
for (i = 0; i < LLTBL_HASHTBL_SIZE; i++)
LIST_INIT(&llt->lle_head[i]);
LLTABLE_WLOCK();
SLIST_INSERT_HEAD(&V_lltables, llt, llt_link);
LLTABLE_WUNLOCK();
2008-12-15 06:53:09 +00:00
return (llt);
}
/*
* Called in route_output when rtm_flags contains RTF_LLDATA.
2008-12-15 06:53:09 +00:00
*/
int
lla_rt_output(struct rt_msghdr *rtm, struct rt_addrinfo *info)
{
struct sockaddr_dl *dl =
(struct sockaddr_dl *)info->rti_info[RTAX_GATEWAY];
struct sockaddr *dst = (struct sockaddr *)info->rti_info[RTAX_DST];
struct ifnet *ifp;
struct lltable *llt;
struct llentry *lle;
u_int laflags = 0;
int error;
2008-12-15 06:53:09 +00:00
KASSERT(dl != NULL && dl->sdl_family == AF_LINK,
("%s: invalid dl\n", __func__));
2008-12-15 06:53:09 +00:00
ifp = ifnet_byindex(dl->sdl_index);
if (ifp == NULL) {
log(LOG_INFO, "%s: invalid ifp (sdl_index %d)\n",
__func__, dl->sdl_index);
return EINVAL;
}
/* XXX linked list may be too expensive */
LLTABLE_RLOCK();
SLIST_FOREACH(llt, &V_lltables, llt_link) {
2008-12-15 06:53:09 +00:00
if (llt->llt_af == dst->sa_family &&
llt->llt_ifp == ifp)
break;
}
LLTABLE_RUNLOCK();
2008-12-15 06:53:09 +00:00
KASSERT(llt != NULL, ("Yep, ugly hacks are bad\n"));
error = 0;
switch (rtm->rtm_type) {
case RTM_ADD:
/* Add static LLE */
IF_AFDATA_CFG_WLOCK(ifp);
lle = llt->llt_create(llt, 0, dst);
if (lle == NULL) {
IF_AFDATA_CFG_WUNLOCK(ifp);
return (ENOMEM);
}
IF_AFDATA_RUN_WLOCK(ifp);
bcopy(LLADDR(dl), &lle->ll_addr, ifp->if_addrlen);
if ((rtm->rtm_flags & RTF_ANNOUNCE))
lle->la_flags |= LLE_PUB;
lle->la_flags |= LLE_VALID;
2008-12-15 06:53:09 +00:00
#ifdef INET6
/*
* ND6
*/
if (dst->sa_family == AF_INET6)
lle->ln_state = ND6_LLINFO_REACHABLE;
2008-12-15 06:53:09 +00:00
#endif
/*
* NB: arp and ndp always set (RTF_STATIC | RTF_HOST)
*/
if (rtm->rtm_rmx.rmx_expire == 0) {
lle->la_flags |= LLE_STATIC;
lle->r_flags |= RLLE_VALID;
lle->la_expire = 0;
} else
lle->la_expire = rtm->rtm_rmx.rmx_expire;
llentry_link(llt, lle);
IF_AFDATA_RUN_WUNLOCK(ifp);
laflags = lle->la_flags;
LLE_WUNLOCK(lle);
IF_AFDATA_CFG_WUNLOCK(ifp);
2008-12-15 06:53:09 +00:00
#ifdef INET
/* gratuitous ARP */
if ((laflags & LLE_PUB) && dst->sa_family == AF_INET)
arprequest(ifp,
&((struct sockaddr_in *)dst)->sin_addr,
&((struct sockaddr_in *)dst)->sin_addr,
(u_char *)LLADDR(dl));
2008-12-15 06:53:09 +00:00
#endif
break;
case RTM_DELETE:
IF_AFDATA_CFG_WLOCK(ifp);
error = (llt->llt_delete(llt, 0, dst));
IF_AFDATA_CFG_WUNLOCK(ifp);
return (error == 0 ? 0 : ENOENT);
default:
error = EINVAL;
}
2008-12-15 06:53:09 +00:00
return (error);
}
static void
vnet_lltable_init()
{
SLIST_INIT(&V_lltables);
}
VNET_SYSINIT(vnet_lltable_init, SI_SUB_PSEUDO, SI_ORDER_FIRST,
vnet_lltable_init, NULL);
#ifdef DDB
struct llentry_sa {
struct llentry base;
struct sockaddr l3_addr;
};
static void
llatbl_lle_show(struct llentry_sa *la)
{
struct llentry *lle;
uint8_t octet[6];
lle = &la->base;
db_printf("lle=%p\n", lle);
db_printf(" lle_next=%p\n", lle->lle_next.le_next);
db_printf(" lle_lock=%p\n", &lle->lle_lock);
db_printf(" lle_tbl=%p\n", lle->lle_tbl);
db_printf(" lle_head=%p\n", lle->lle_head);
db_printf(" la_hold=%p\n", lle->la_hold);
db_printf(" la_numheld=%d\n", lle->la_numheld);
db_printf(" la_expire=%ju\n", (uintmax_t)lle->la_expire);
db_printf(" la_flags=0x%04x\n", lle->la_flags);
db_printf(" la_asked=%u\n", lle->la_asked);
db_printf(" la_preempt=%u\n", lle->la_preempt);
db_printf(" ln_byhint=%u\n", lle->ln_byhint);
db_printf(" ln_state=%d\n", lle->ln_state);
db_printf(" ln_router=%u\n", lle->ln_router);
db_printf(" ln_ntick=%ju\n", (uintmax_t)lle->ln_ntick);
db_printf(" lle_refcnt=%d\n", lle->lle_refcnt);
bcopy(&lle->ll_addr.mac16, octet, sizeof(octet));
db_printf(" ll_addr=%02x:%02x:%02x:%02x:%02x:%02x\n",
octet[0], octet[1], octet[2], octet[3], octet[4], octet[5]);
db_printf(" la_timer=%p\n", &lle->la_timer);
switch (la->l3_addr.sa_family) {
#ifdef INET
case AF_INET:
{
struct sockaddr_in *sin;
char l3s[INET_ADDRSTRLEN];
sin = (struct sockaddr_in *)&la->l3_addr;
inet_ntoa_r(sin->sin_addr, l3s);
2012-08-01 09:00:26 +00:00
db_printf(" l3_addr=%s\n", l3s);
break;
}
#endif
#ifdef INET6
case AF_INET6:
{
struct sockaddr_in6 *sin6;
char l3s[INET6_ADDRSTRLEN];
sin6 = (struct sockaddr_in6 *)&la->l3_addr;
ip6_sprintf(l3s, &sin6->sin6_addr);
2012-08-01 09:00:26 +00:00
db_printf(" l3_addr=%s\n", l3s);
break;
}
#endif
default:
db_printf(" l3_addr=N/A (af=%d)\n", la->l3_addr.sa_family);
break;
}
}
DB_SHOW_COMMAND(llentry, db_show_llentry)
{
if (!have_addr) {
db_printf("usage: show llentry <struct llentry *>\n");
return;
}
llatbl_lle_show((struct llentry_sa *)addr);
}
static void
llatbl_llt_show(struct lltable *llt)
{
int i;
struct llentry *lle;
db_printf("llt=%p llt_af=%d llt_ifp=%p\n",
llt, llt->llt_af, llt->llt_ifp);
for (i = 0; i < LLTBL_HASHTBL_SIZE; i++) {
LIST_FOREACH(lle, &llt->lle_head[i], lle_next) {
llatbl_lle_show((struct llentry_sa *)lle);
if (db_pager_quit)
return;
}
}
}
DB_SHOW_COMMAND(lltable, db_show_lltable)
{
if (!have_addr) {
db_printf("usage: show lltable <struct lltable *>\n");
return;
}
llatbl_llt_show((struct lltable *)addr);
}
DB_SHOW_ALL_COMMAND(lltables, db_show_all_lltables)
{
VNET_ITERATOR_DECL(vnet_iter);
struct lltable *llt;
VNET_FOREACH(vnet_iter) {
CURVNET_SET_QUIET(vnet_iter);
#ifdef VIMAGE
db_printf("vnet=%p\n", curvnet);
#endif
SLIST_FOREACH(llt, &V_lltables, llt_link) {
db_printf("llt=%p llt_af=%d llt_ifp=%p(%s)\n",
llt, llt->llt_af, llt->llt_ifp,
(llt->llt_ifp != NULL) ?
llt->llt_ifp->if_xname : "?");
if (have_addr && addr != 0) /* verbose */
llatbl_llt_show(llt);
if (db_pager_quit) {
CURVNET_RESTORE();
return;
}
}
CURVNET_RESTORE();
}
}
#endif