| /* |
| * 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/bsd_cdefs.h> |
| //__FBSDID("$FreeBSD: src/sys/net/if_llatbl.c,v 1.8.2.5.2.1 2009/10/25 01:10:29 kensmith Exp $"); |
| |
| #include "bsd_opt_inet.h" |
| #include "bsd_opt_inet6.h" |
| |
| #include <sys/bsd_param.h> |
| #include <sys/bsd_systm.h> |
| #include <sys/bsd_malloc.h> |
| #include <sys/bsd_mbuf.h> |
| //#include <sys/bsd_syslog.h> |
| //#include <sys/bsd_sysctl.h> |
| #include <sys/bsd_socket.h> |
| #include <sys/bsd_kernel.h> |
| #include <sys/bsd_lock.h> |
| #include <sys/bsd_mutex.h> |
| #include <sys/bsd_rwlock.h> |
| |
| #include <vm/bsd_uma.h> |
| |
| #include <netinet/bsd_in.h> |
| #include <net/bsd_if_llatbl.h> |
| #include <net/bsd_if.h> |
| #include <net/bsd_if_dl.h> |
| #include <net/bsd_if_var.h> |
| #include <net/bsd_route.h> |
| #include <net/bsd_vnet.h> |
| #include <netinet/bsd_if_ether.h> |
| #include <netinet6/bsd_in6_var.h> |
| #include <netinet6/bsd_nd6.h> |
| |
| MALLOC_DEFINE(M_LLTABLE, "lltable", "link level address tables"); |
| |
| static SLIST_HEAD(, lltable) lltables = SLIST_HEAD_INITIALIZER(lltables); |
| |
| extern void arprequest(struct ifnet *, struct in_addr *, struct in_addr *, |
| u_char *); |
| |
| struct rwlock lltable_rwlock; |
| RW_SYSINIT(lltable_rwlock, &lltable_rwlock, "lltable_rwlock"); |
| |
| #if 0 |
| /* |
| * Dump arp state for a specific address family. |
| */ |
| int |
| lltable_sysctl_dumparp(int af, struct sysctl_req *wr) |
| { |
| struct lltable *llt; |
| int error = 0; |
| |
| LLTABLE_RLOCK(); |
| SLIST_FOREACH(llt, &lltables, llt_link) { |
| if (llt->llt_af == af) { |
| error = llt->llt_dump(llt, wr); |
| if (error != 0) |
| goto done; |
| } |
| } |
| done: |
| LLTABLE_RUNLOCK(); |
| return (error); |
| } |
| #endif |
| |
| /* |
| * 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. |
| */ |
| void |
| llentry_free(struct llentry *lle) |
| { |
| |
| LLE_WLOCK_ASSERT(lle); |
| LIST_REMOVE(lle, lle_next); |
| |
| if (lle->la_hold != NULL) |
| m_freem(lle->la_hold); |
| |
| LLE_FREE_LOCKED(lle); |
| } |
| |
| /* |
| * Update an llentry for address dst (equivalent to rtalloc for new-arp) |
| * Caller must pass in a valid struct llentry * |
| * |
| * if found the llentry * is returned referenced and unlocked |
| */ |
| int |
| llentry_update(struct llentry **llep, struct lltable *lt, |
| struct sockaddr *dst, struct ifnet *ifp) |
| { |
| struct llentry *la; |
| |
| IF_AFDATA_RLOCK(ifp); |
| la = lla_lookup(lt, LLE_EXCLUSIVE, |
| (struct sockaddr *)dst); |
| IF_AFDATA_RUNLOCK(ifp); |
| if ((la == NULL) && |
| (ifp->if_flags & (IFF_NOARP | IFF_STATICARP)) == 0) { |
| IF_AFDATA_WLOCK(ifp); |
| la = lla_lookup(lt, |
| (LLE_CREATE | LLE_EXCLUSIVE), |
| (struct sockaddr *)dst); |
| IF_AFDATA_WUNLOCK(ifp); |
| } |
| if (la != NULL && (*llep != la)) { |
| if (*llep != NULL) |
| LLE_FREE(*llep); |
| LLE_ADDREF(la); |
| LLE_WUNLOCK(la); |
| *llep = la; |
| } else if (la != NULL) |
| LLE_WUNLOCK(la); |
| |
| if (la == NULL) |
| return (ENOENT); |
| |
| return (0); |
| } |
| |
| /* |
| * Free all entries from given table and free itself. |
| */ |
| void |
| lltable_free(struct lltable *llt) |
| { |
| struct llentry *lle, *next; |
| int i; |
| |
| KASSERT(llt != NULL, ("%s: llt is NULL", __func__)); |
| |
| LLTABLE_WLOCK(); |
| SLIST_REMOVE(&lltables, llt, lltable, llt_link); |
| LLTABLE_WUNLOCK(); |
| |
| for (i=0; i < LLTBL_HASHTBL_SIZE; i++) { |
| LIST_FOREACH_SAFE(lle, &llt->lle_head[i], lle_next, next) { |
| |
| callout_drain(&lle->la_timer); |
| LLE_WLOCK(lle); |
| llentry_free(lle); |
| } |
| } |
| |
| bsd_free(llt, M_LLTABLE); |
| } |
| |
| void |
| lltable_drain(int af) |
| { |
| struct lltable *llt; |
| struct llentry *lle; |
| register int i; |
| |
| LLTABLE_RLOCK(); |
| SLIST_FOREACH(llt, &lltables, llt_link) { |
| if (llt->llt_af != af) |
| continue; |
| |
| for (i=0; i < LLTBL_HASHTBL_SIZE; i++) { |
| LIST_FOREACH(lle, &llt->lle_head[i], lle_next) { |
| if (lle->la_hold) { |
| m_freem(lle->la_hold); |
| lle->la_hold = NULL; |
| } |
| } |
| } |
| } |
| LLTABLE_RUNLOCK(); |
| } |
| |
| void |
| lltable_prefix_free(int af, struct sockaddr *prefix, struct sockaddr *mask) |
| { |
| struct lltable *llt; |
| |
| LLTABLE_RLOCK(); |
| SLIST_FOREACH(llt, &lltables, llt_link) { |
| if (llt->llt_af != af) |
| continue; |
| |
| llt->llt_prefix_free(llt, prefix, mask); |
| } |
| LLTABLE_RUNLOCK(); |
| } |
| |
| |
| |
| /* |
| * Create a new lltable. |
| */ |
| struct lltable * |
| lltable_init(struct ifnet *ifp, int af) |
| { |
| struct lltable *llt; |
| register int i; |
| |
| llt = bsd_malloc(sizeof(struct lltable), M_LLTABLE, M_WAITOK); |
| if (llt == NULL) |
| return (NULL); |
| |
| 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(&lltables, llt, llt_link); |
| LLTABLE_WUNLOCK(); |
| |
| return (llt); |
| } |
| |
| /* |
| * Called in route_output when adding/deleting a route to an interface. |
| */ |
| 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, flags = 0; |
| int error = 0; |
| |
| if (dl == NULL || dl->sdl_family != AF_LINK) { |
| //bsd_log(LOG_INFO, "%s: invalid dl\n", __func__); |
| return EINVAL; |
| } |
| ifp = ifnet_byindex(dl->sdl_index); |
| if (ifp == NULL) { |
| //bsd_log(LOG_INFO, "%s: invalid ifp (sdl_index %d)\n", |
| // __func__, dl->sdl_index); |
| return EINVAL; |
| } |
| |
| switch (rtm->rtm_type) { |
| case RTM_ADD: |
| if (rtm->rtm_flags & RTF_ANNOUNCE) { |
| flags |= LLE_PUB; |
| #ifdef INET |
| if (dst->sa_family == AF_INET && |
| ((struct sockaddr_inarp *)dst)->sin_other != 0) { |
| struct rtentry *rt = rtalloc1(dst, 0, 0); |
| if (rt == NULL || !(rt->rt_flags & RTF_HOST)) { |
| //bsd_log(LOG_INFO, "%s: RTM_ADD publish " |
| // "(proxy only) is invalid\n", |
| // __func__); |
| if (rt) |
| RTFREE_LOCKED(rt); |
| return EINVAL; |
| } |
| RTFREE_LOCKED(rt); |
| |
| flags |= LLE_PROXY; |
| } |
| #endif |
| } |
| flags |= LLE_CREATE; |
| break; |
| |
| case RTM_DELETE: |
| flags |= LLE_DELETE; |
| break; |
| |
| case RTM_CHANGE: |
| break; |
| |
| default: |
| return EINVAL; /* XXX not implemented yet */ |
| } |
| |
| /* XXX linked list may be too expensive */ |
| LLTABLE_RLOCK(); |
| SLIST_FOREACH(llt, &lltables, llt_link) { |
| if (llt->llt_af == dst->sa_family && |
| llt->llt_ifp == ifp) |
| break; |
| } |
| LLTABLE_RUNLOCK(); |
| KASSERT(llt != NULL, ("Yep, ugly hacks are bad\n")); |
| |
| if (flags && LLE_CREATE) |
| flags |= LLE_EXCLUSIVE; |
| |
| IF_AFDATA_LOCK(ifp); |
| lle = lla_lookup(llt, flags, dst); |
| IF_AFDATA_UNLOCK(ifp); |
| if (LLE_IS_VALID(lle)) { |
| if (flags & LLE_CREATE) { |
| /* |
| * If we delay the delete, then a subsequent |
| * "arp add" should look up this entry, reset the |
| * LLE_DELETED flag, and reset the expiration timer |
| */ |
| bcopy(LLADDR(dl), &lle->ll_addr, ifp->if_addrlen); |
| lle->la_flags |= LLE_VALID; |
| lle->la_flags &= ~LLE_DELETED; |
| #ifdef INET6 |
| /* |
| * ND6 |
| */ |
| if (dst->sa_family == AF_INET6) |
| lle->ln_state = ND6_LLINFO_REACHABLE; |
| #endif |
| /* |
| * NB: arp and ndp always set (RTF_STATIC | RTF_HOST) |
| */ |
| |
| if (rtm->rtm_rmx.rmx_expire == 0) { |
| lle->la_flags |= LLE_STATIC; |
| lle->la_expire = 0; |
| } else |
| lle->la_expire = rtm->rtm_rmx.rmx_expire; |
| laflags = lle->la_flags; |
| LLE_WUNLOCK(lle); |
| #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, |
| ((laflags & LLE_PROXY) ? |
| (u_char *)IF_LLADDR(ifp) : |
| (u_char *)LLADDR(dl))); |
| } |
| #endif |
| } else { |
| if (flags & LLE_EXCLUSIVE) |
| LLE_WUNLOCK(lle); |
| else |
| LLE_RUNLOCK(lle); |
| } |
| } else if ((lle == NULL) && (flags & LLE_DELETE)) |
| error = EINVAL; |
| |
| |
| return (error); |
| } |