diff --git a/sys/net/if_llatbl.c b/sys/net/if_llatbl.c index c61a19f39de2..73e20eecf378 100644 --- a/sys/net/if_llatbl.c +++ b/sys/net/if_llatbl.c @@ -1,1115 +1,1117 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 __FBSDID("$FreeBSD$"); #include "opt_ddb.h" #include "opt_inet.h" #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DDB #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include MALLOC_DEFINE(M_LLTABLE, "lltable", "link level address tables"); VNET_DEFINE_STATIC(SLIST_HEAD(, lltable), lltables) = SLIST_HEAD_INITIALIZER(lltables); #define V_lltables VNET(lltables) static struct rwlock lltable_list_lock; RW_SYSINIT(lltable_list_lock, &lltable_list_lock, "lltable_list_lock"); #define LLTABLE_LIST_RLOCK() rw_rlock(&lltable_list_lock) #define LLTABLE_LIST_RUNLOCK() rw_runlock(&lltable_list_lock) #define LLTABLE_LIST_WLOCK() rw_wlock(&lltable_list_lock) #define LLTABLE_LIST_WUNLOCK() rw_wunlock(&lltable_list_lock) #define LLTABLE_LIST_LOCK_ASSERT() rw_assert(&lltable_list_lock, RA_LOCKED) static void lltable_unlink(struct lltable *llt); static void llentries_unlink(struct lltable *llt, struct llentries *head); /* * Dump lle state for a specific address family. */ static int lltable_dump_af(struct lltable *llt, struct sysctl_req *wr) { struct epoch_tracker et; int error; LLTABLE_LIST_LOCK_ASSERT(); if (llt->llt_ifp->if_flags & IFF_LOOPBACK) return (0); error = 0; NET_EPOCH_ENTER(et); error = lltable_foreach_lle(llt, (llt_foreach_cb_t *)llt->llt_dump_entry, wr); NET_EPOCH_EXIT(et); return (error); } /* * 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_LIST_RLOCK(); SLIST_FOREACH(llt, &V_lltables, llt_link) { if (llt->llt_af == af) { error = lltable_dump_af(llt, wr); if (error != 0) goto done; } } done: LLTABLE_LIST_RUNLOCK(); return (error); } /* * Common function helpers for chained hash table. */ /* * Runs specified callback for each entry in @llt. * Caller does the locking. * */ static int htable_foreach_lle(struct lltable *llt, llt_foreach_cb_t *f, void *farg) { struct llentry *lle, *next; int i, error; error = 0; for (i = 0; i < llt->llt_hsize; i++) { CK_LIST_FOREACH_SAFE(lle, &llt->lle_head[i], lle_next, next) { error = f(llt, lle, farg); if (error != 0) break; } } return (error); } /* * The htable_[un]link_entry() functions return: * 0 if the entry was (un)linked already and nothing changed, * 1 if the entry was added/removed to/from the table, and * -1 on error (e.g., not being able to add the entry due to limits reached). * While the "unlink" operation should never error, callers of * lltable_link_entry() need to check for errors and handle them. */ static int htable_link_entry(struct lltable *llt, struct llentry *lle) { struct llentries *lleh; uint32_t hashidx; if ((lle->la_flags & LLE_LINKED) != 0) return (0); IF_AFDATA_WLOCK_ASSERT(llt->llt_ifp); if (llt->llt_maxentries > 0 && llt->llt_entries >= llt->llt_maxentries) return (-1); hashidx = llt->llt_hash(lle, llt->llt_hsize); lleh = &llt->lle_head[hashidx]; lle->lle_tbl = llt; lle->lle_head = lleh; lle->la_flags |= LLE_LINKED; CK_LIST_INSERT_HEAD(lleh, lle, lle_next); llt->llt_entries++; return (1); } static int htable_unlink_entry(struct llentry *lle) { struct lltable *llt; if ((lle->la_flags & LLE_LINKED) == 0) return (0); llt = lle->lle_tbl; IF_AFDATA_WLOCK_ASSERT(llt->llt_ifp); KASSERT(llt->llt_entries > 0, ("%s: lltable %p (%s) entries %d <= 0", __func__, llt, if_name(llt->llt_ifp), llt->llt_entries)); CK_LIST_REMOVE(lle, lle_next); lle->la_flags &= ~(LLE_VALID | LLE_LINKED); #if 0 lle->lle_tbl = NULL; lle->lle_head = NULL; #endif llt->llt_entries--; return (1); } struct prefix_match_data { const struct sockaddr *addr; const struct sockaddr *mask; struct llentries dchain; u_int flags; }; static int htable_prefix_free_cb(struct lltable *llt, struct llentry *lle, void *farg) { struct prefix_match_data *pmd; pmd = (struct prefix_match_data *)farg; if (llt->llt_match_prefix(pmd->addr, pmd->mask, pmd->flags, lle)) { LLE_WLOCK(lle); CK_LIST_INSERT_HEAD(&pmd->dchain, lle, lle_chain); } return (0); } static void htable_prefix_free(struct lltable *llt, const struct sockaddr *addr, const struct sockaddr *mask, u_int flags) { struct llentry *lle, *next; struct prefix_match_data pmd; bzero(&pmd, sizeof(pmd)); pmd.addr = addr; pmd.mask = mask; pmd.flags = flags; CK_LIST_INIT(&pmd.dchain); IF_AFDATA_WLOCK(llt->llt_ifp); /* Push matching lles to chain */ lltable_foreach_lle(llt, htable_prefix_free_cb, &pmd); llentries_unlink(llt, &pmd.dchain); IF_AFDATA_WUNLOCK(llt->llt_ifp); CK_LIST_FOREACH_SAFE(lle, &pmd.dchain, lle_chain, next) lltable_free_entry(llt, lle); } static void htable_free_tbl(struct lltable *llt) { free(llt->lle_head, M_LLTABLE); free(llt, M_LLTABLE); } static void llentries_unlink(struct lltable *llt, struct llentries *head) { struct llentry *lle, *next; CK_LIST_FOREACH_SAFE(lle, head, lle_chain, next) llt->llt_unlink_entry(lle); } /* * Helper function used to drop all mbufs in hold queue. * * Returns the number of held packets, if any, that were dropped. */ size_t lltable_drop_entry_queue(struct llentry *lle) { size_t pkts_dropped; struct mbuf *next; LLE_WLOCK_ASSERT(lle); pkts_dropped = 0; while ((lle->la_numheld > 0) && (lle->la_hold != NULL)) { next = lle->la_hold->m_nextpkt; 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)); return (pkts_dropped); } void lltable_set_entry_addr(struct ifnet *ifp, struct llentry *lle, const char *linkhdr, size_t linkhdrsize, int lladdr_off) { memcpy(lle->r_linkdata, linkhdr, linkhdrsize); lle->r_hdrlen = linkhdrsize; lle->ll_addr = &lle->r_linkdata[lladdr_off]; lle->la_flags |= LLE_VALID; lle->r_flags |= RLLE_VALID; } /* * Acquires lltable write lock. * * Returns true on success, with both lltable and lle lock held. * On failure, false is returned and lle wlock is still held. */ bool lltable_acquire_wlock(struct ifnet *ifp, struct llentry *lle) { NET_EPOCH_ASSERT(); /* Perform real LLE update */ /* use afdata WLOCK to update fields */ LLE_WUNLOCK(lle); IF_AFDATA_WLOCK(ifp); LLE_WLOCK(lle); /* * Since we droppped LLE lock, other thread might have deleted * this lle. Check and return */ if ((lle->la_flags & LLE_DELETED) != 0) { IF_AFDATA_WUNLOCK(ifp); return (false); } return (true); } /* * Tries to update @lle link-level address. * Since update requires AFDATA WLOCK, function * drops @lle lock, acquires AFDATA lock and then acquires * @lle lock to maintain lock order. * * Returns 1 on success. */ int lltable_try_set_entry_addr(struct ifnet *ifp, struct llentry *lle, const char *linkhdr, size_t linkhdrsize, int lladdr_off) { if (!lltable_acquire_wlock(ifp, lle)) return (0); /* Update data */ lltable_set_entry_addr(ifp, lle, linkhdr, linkhdrsize, lladdr_off); IF_AFDATA_WUNLOCK(ifp); return (1); } /* * Helper function used to pre-compute full/partial link-layer * header data suitable for feeding into if_output(). */ int lltable_calc_llheader(struct ifnet *ifp, int family, char *lladdr, char *buf, size_t *bufsize, int *lladdr_off) { struct if_encap_req ereq; int error; bzero(buf, *bufsize); bzero(&ereq, sizeof(ereq)); ereq.buf = buf; ereq.bufsize = *bufsize; ereq.rtype = IFENCAP_LL; ereq.family = family; ereq.lladdr = lladdr; ereq.lladdr_len = ifp->if_addrlen; error = ifp->if_requestencap(ifp, &ereq); if (error == 0) { *bufsize = ereq.bufsize; *lladdr_off = ereq.lladdr_off; } return (error); } /* * Searches for the child entry matching @family inside @lle. * Returns the entry or NULL. */ struct llentry * llentry_lookup_family(struct llentry *lle, int family) { struct llentry *child_lle; if (lle == NULL) return (NULL); CK_SLIST_FOREACH(child_lle, &lle->lle_children, lle_child_next) { if (child_lle->r_family == family) return (child_lle); } return (NULL); } /* * Retrieves upper protocol family for the llentry. * By default, all "normal" (e.g. upper_family == transport_family) * llentries have r_family set to 0. * Thus, use @default_family in that regard, otherwise use r_family. * * Returns upper protocol family */ int llentry_get_upper_family(const struct llentry *lle, int default_family) { return (lle->r_family == 0 ? default_family : lle->r_family); } /* * Prints llentry @lle data into provided buffer. * Example: lle/inet/valid/em0/1.2.3.4 * * Returns @buf. */ char * llentry_print_buf(const struct llentry *lle, struct ifnet *ifp, int family, char *buf, size_t bufsize) { +#if defined(INET) || defined(INET6) char abuf[INET6_ADDRSTRLEN]; +#endif const char *valid = (lle->r_flags & RLLE_VALID) ? "valid" : "no_l2"; const char *upper_str = rib_print_family(llentry_get_upper_family(lle, family)); switch (family) { #ifdef INET case AF_INET: inet_ntop(AF_INET, &lle->r_l3addr.addr4, abuf, sizeof(abuf)); snprintf(buf, bufsize, "lle/%s/%s/%s/%s", upper_str, valid, if_name(ifp), abuf); break; #endif #ifdef INET6 case AF_INET6: inet_ntop(AF_INET6, &lle->r_l3addr.addr6, abuf, sizeof(abuf)); snprintf(buf, bufsize, "lle/%s/%s/%s/%s", upper_str, valid, if_name(ifp), abuf); break; #endif default: snprintf(buf, bufsize, "lle/%s/%s/%s/????", upper_str, valid, if_name(ifp)); break; } return (buf); } char * llentry_print_buf_lltable(const struct llentry *lle, char *buf, size_t bufsize) { struct lltable *tbl = lle->lle_tbl; return (llentry_print_buf(lle, lltable_get_ifp(tbl), lltable_get_af(tbl), buf, bufsize)); } /* * Requests feedback from the datapath. * First packet using @lle should result in * setting r_skip_req back to 0 and updating * lle_hittime to the current time_uptime. */ void llentry_request_feedback(struct llentry *lle) { struct llentry *child_lle; LLE_REQ_LOCK(lle); lle->r_skip_req = 1; LLE_REQ_UNLOCK(lle); CK_SLIST_FOREACH(child_lle, &lle->lle_children, lle_child_next) { LLE_REQ_LOCK(child_lle); child_lle->r_skip_req = 1; LLE_REQ_UNLOCK(child_lle); } } /* * Updates the lle state to mark it has been used * and record the time. * Used by the llentry_provide_feedback() wrapper. */ void llentry_mark_used(struct llentry *lle) { LLE_REQ_LOCK(lle); lle->r_skip_req = 0; lle->lle_hittime = time_uptime; LLE_REQ_UNLOCK(lle); } /* * Fetches the time when lle was used. * Return 0 if the entry was not used, relevant time_uptime * otherwise. */ static time_t llentry_get_hittime_raw(struct llentry *lle) { time_t lle_hittime = 0; LLE_REQ_LOCK(lle); if ((lle->r_skip_req == 0) && (lle_hittime < lle->lle_hittime)) lle_hittime = lle->lle_hittime; LLE_REQ_UNLOCK(lle); return (lle_hittime); } time_t llentry_get_hittime(struct llentry *lle) { time_t lle_hittime = 0; struct llentry *child_lle; lle_hittime = llentry_get_hittime_raw(lle); CK_SLIST_FOREACH(child_lle, &lle->lle_children, lle_child_next) { time_t hittime = llentry_get_hittime_raw(child_lle); if (hittime > lle_hittime) lle_hittime = hittime; } return (lle_hittime); } /* * Update link-layer header for given @lle after * interface lladdr was changed. */ static int llentry_update_ifaddr(struct lltable *llt, struct llentry *lle, void *farg) { struct ifnet *ifp; u_char linkhdr[LLE_MAX_LINKHDR]; size_t linkhdrsize; u_char *lladdr; int lladdr_off; ifp = (struct ifnet *)farg; lladdr = lle->ll_addr; LLE_WLOCK(lle); if ((lle->la_flags & LLE_VALID) == 0) { LLE_WUNLOCK(lle); return (0); } if ((lle->la_flags & LLE_IFADDR) != 0) lladdr = IF_LLADDR(ifp); linkhdrsize = sizeof(linkhdr); lltable_calc_llheader(ifp, llt->llt_af, lladdr, linkhdr, &linkhdrsize, &lladdr_off); memcpy(lle->r_linkdata, linkhdr, linkhdrsize); LLE_WUNLOCK(lle); return (0); } /* * Update all calculated headers for given @llt */ void lltable_update_ifaddr(struct lltable *llt) { if (llt->llt_ifp->if_flags & IFF_LOOPBACK) return; IF_AFDATA_WLOCK(llt->llt_ifp); lltable_foreach_lle(llt, llentry_update_ifaddr, llt->llt_ifp); IF_AFDATA_WUNLOCK(llt->llt_ifp); } /* * * Performs generic cleanup routines and frees lle. * * Called for non-linked entries, with callouts and * other AF-specific cleanups performed. * * @lle must be passed WLOCK'ed * * Returns the number of held packets, if any, that were dropped. */ size_t llentry_free(struct llentry *lle) { size_t pkts_dropped; LLE_WLOCK_ASSERT(lle); KASSERT((lle->la_flags & LLE_LINKED) == 0, ("freeing linked lle")); pkts_dropped = lltable_drop_entry_queue(lle); /* cancel timer */ if (callout_stop(&lle->lle_timer) > 0) LLE_REMREF(lle); LLE_FREE_LOCKED(lle); return (pkts_dropped); } /* * Free all entries from given table and free itself. */ static int lltable_free_cb(struct lltable *llt, struct llentry *lle, void *farg) { struct llentries *dchain; dchain = (struct llentries *)farg; LLE_WLOCK(lle); CK_LIST_INSERT_HEAD(dchain, lle, lle_chain); return (0); } /* * Free all entries from given table and free itself. */ void lltable_free(struct lltable *llt) { struct llentry *lle, *next; struct llentries dchain; KASSERT(llt != NULL, ("%s: llt is NULL", __func__)); lltable_unlink(llt); CK_LIST_INIT(&dchain); IF_AFDATA_WLOCK(llt->llt_ifp); /* Push all lles to @dchain */ lltable_foreach_lle(llt, lltable_free_cb, &dchain); llentries_unlink(llt, &dchain); IF_AFDATA_WUNLOCK(llt->llt_ifp); CK_LIST_FOREACH_SAFE(lle, &dchain, lle_chain, next) { llentry_free(lle); } KASSERT(llt->llt_entries == 0, ("%s: lltable %p (%s) entires not 0: %d", __func__, llt, llt->llt_ifp->if_xname, llt->llt_entries)); llt->llt_free_tbl(llt); } /* * Deletes an address from given lltable. * Used for userland interaction to remove * individual entries. Skips entries added by OS. */ int lltable_delete_addr(struct lltable *llt, u_int flags, const struct sockaddr *l3addr) { struct llentry *lle; struct ifnet *ifp; ifp = llt->llt_ifp; IF_AFDATA_WLOCK(ifp); lle = lla_lookup(llt, LLE_SF(l3addr->sa_family, LLE_EXCLUSIVE), l3addr); if (lle == NULL) { IF_AFDATA_WUNLOCK(ifp); return (ENOENT); } if ((lle->la_flags & LLE_IFADDR) != 0 && (flags & LLE_IFADDR) == 0) { IF_AFDATA_WUNLOCK(ifp); LLE_WUNLOCK(lle); return (EPERM); } lltable_unlink_entry(llt, lle); IF_AFDATA_WUNLOCK(ifp); llt->llt_delete_entry(llt, lle); return (0); } void lltable_prefix_free(int af, struct sockaddr *addr, struct sockaddr *mask, u_int flags) { struct lltable *llt; LLTABLE_LIST_RLOCK(); SLIST_FOREACH(llt, &V_lltables, llt_link) { if (llt->llt_af != af) continue; llt->llt_prefix_free(llt, addr, mask, flags); } LLTABLE_LIST_RUNLOCK(); } struct lltable * lltable_allocate_htbl(uint32_t hsize) { struct lltable *llt; int i; llt = malloc(sizeof(struct lltable), M_LLTABLE, M_WAITOK | M_ZERO); llt->llt_hsize = hsize; llt->lle_head = malloc(sizeof(struct llentries) * hsize, M_LLTABLE, M_WAITOK | M_ZERO); for (i = 0; i < llt->llt_hsize; i++) CK_LIST_INIT(&llt->lle_head[i]); /* Set some default callbacks */ llt->llt_link_entry = htable_link_entry; llt->llt_unlink_entry = htable_unlink_entry; llt->llt_prefix_free = htable_prefix_free; llt->llt_foreach_entry = htable_foreach_lle; llt->llt_free_tbl = htable_free_tbl; return (llt); } /* * Links lltable to global llt list. */ void lltable_link(struct lltable *llt) { LLTABLE_LIST_WLOCK(); SLIST_INSERT_HEAD(&V_lltables, llt, llt_link); LLTABLE_LIST_WUNLOCK(); } static void lltable_unlink(struct lltable *llt) { LLTABLE_LIST_WLOCK(); SLIST_REMOVE(&V_lltables, llt, lltable, llt_link); LLTABLE_LIST_WUNLOCK(); } /* * Gets interface @ifp lltable for the specified @family */ struct lltable * lltable_get(struct ifnet *ifp, int family) { switch (family) { case AF_INET: return (in_lltable_get(ifp)); case AF_INET6: return (in6_lltable_get(ifp)); } return (NULL); } /* * External methods used by lltable consumers */ int lltable_foreach_lle(struct lltable *llt, llt_foreach_cb_t *f, void *farg) { return (llt->llt_foreach_entry(llt, f, farg)); } struct llentry * lltable_alloc_entry(struct lltable *llt, u_int flags, const struct sockaddr *l3addr) { return (llt->llt_alloc_entry(llt, flags, l3addr)); } void lltable_free_entry(struct lltable *llt, struct llentry *lle) { llt->llt_free_entry(llt, lle); } int lltable_link_entry(struct lltable *llt, struct llentry *lle) { return (llt->llt_link_entry(llt, lle)); } void lltable_link_child_entry(struct llentry *lle, struct llentry *child_lle) { child_lle->lle_parent = lle; child_lle->lle_tbl = lle->lle_tbl; child_lle->la_flags |= LLE_LINKED; CK_SLIST_INSERT_HEAD(&lle->lle_children, child_lle, lle_child_next); } void lltable_unlink_child_entry(struct llentry *child_lle) { struct llentry *lle = child_lle->lle_parent; child_lle->la_flags &= ~LLE_LINKED; child_lle->lle_parent = NULL; CK_SLIST_REMOVE(&lle->lle_children, child_lle, llentry, lle_child_next); } int lltable_unlink_entry(struct lltable *llt, struct llentry *lle) { return (llt->llt_unlink_entry(lle)); } void lltable_fill_sa_entry(const struct llentry *lle, struct sockaddr *sa) { struct lltable *llt; llt = lle->lle_tbl; llt->llt_fill_sa_entry(lle, sa); } struct ifnet * lltable_get_ifp(const struct lltable *llt) { return (llt->llt_ifp); } int lltable_get_af(const struct lltable *llt) { return (llt->llt_af); } /* * Called in route_output when rtm_flags contains RTF_LLDATA. */ 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, *lle_tmp; uint8_t linkhdr[LLE_MAX_LINKHDR]; size_t linkhdrsize; int lladdr_off; u_int laflags = 0; int error; if (dl == NULL || dl->sdl_family != AF_LINK) return (EINVAL); /* XXX: should be ntohs() */ 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; } llt = lltable_get(ifp, dst->sa_family); if (llt == NULL) return (ESRCH); error = 0; switch (rtm->rtm_type) { case RTM_ADD: /* Add static LLE */ laflags = 0; if (rtm->rtm_rmx.rmx_expire == 0) laflags = LLE_STATIC; lle = lltable_alloc_entry(llt, laflags, dst); if (lle == NULL) return (ENOMEM); linkhdrsize = sizeof(linkhdr); if (lltable_calc_llheader(ifp, dst->sa_family, LLADDR(dl), linkhdr, &linkhdrsize, &lladdr_off) != 0) return (EINVAL); lltable_set_entry_addr(ifp, lle, linkhdr, linkhdrsize, lladdr_off); if ((rtm->rtm_flags & RTF_ANNOUNCE)) lle->la_flags |= LLE_PUB; lle->la_expire = rtm->rtm_rmx.rmx_expire; laflags = lle->la_flags; /* Try to link new entry */ lle_tmp = NULL; IF_AFDATA_WLOCK(ifp); LLE_WLOCK(lle); lle_tmp = lla_lookup(llt, LLE_EXCLUSIVE, dst); if (lle_tmp != NULL) { /* Check if we are trying to replace immutable entry */ if ((lle_tmp->la_flags & LLE_IFADDR) != 0) { IF_AFDATA_WUNLOCK(ifp); LLE_WUNLOCK(lle_tmp); lltable_free_entry(llt, lle); return (EPERM); } /* Unlink existing entry from table */ lltable_unlink_entry(llt, lle_tmp); } lltable_link_entry(llt, lle); IF_AFDATA_WUNLOCK(ifp); if (lle_tmp != NULL) { EVENTHANDLER_INVOKE(lle_event, lle_tmp,LLENTRY_EXPIRED); lltable_free_entry(llt, lle_tmp); } /* * By invoking LLE handler here we might get * two events on static LLE entry insertion * in routing socket. However, since we might have * other subscribers we need to generate this event. */ EVENTHANDLER_INVOKE(lle_event, lle, LLENTRY_RESOLVED); 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, (u_char *)LLADDR(dl)); #endif break; case RTM_DELETE: return (lltable_delete_addr(llt, 0, dst)); default: error = EINVAL; } return (error); } #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.cle_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_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, 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(" lle_timer=%p\n", &lle->lle_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); 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); 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 \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 < llt->llt_hsize; i++) { CK_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 \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 diff --git a/sys/net/route/nhop_ctl.c b/sys/net/route/nhop_ctl.c index 233a2a677678..16b8ca46739a 100644 --- a/sys/net/route/nhop_ctl.c +++ b/sys/net/route/nhop_ctl.c @@ -1,982 +1,984 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2020 Alexander V. Chernikov * * 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 THE 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 THE 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 __FBSDID("$FreeBSD$"); #include "opt_inet.h" #include "opt_inet6.h" #include "opt_route.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_MOD_NAME nhop_ctl #define DEBUG_MAX_LEVEL LOG_DEBUG #include _DECLARE_DEBUG(LOG_INFO); /* * This file contains core functionality for the nexthop ("nhop") route subsystem. * The business logic needed to create nexhop objects is implemented here. * * Nexthops in the original sense are the objects containing all the necessary * information to forward the packet to the selected destination. * In particular, nexthop is defined by a combination of * ifp, ifa, aifp, mtu, gw addr(if set), nh_type, nh_upper_family, mask of rt_flags and * NHF_DEFAULT * * Additionally, each nexthop gets assigned its unique index (nexthop index). * It serves two purposes: first one is to ease the ability of userland programs to * reference nexthops by their index. The second one allows lookup algorithms to * to store index instead of pointer (2 bytes vs 8) as a lookup result. * All nexthops are stored in the resizable hash table. * * Basically, this file revolves around supporting 3 functions: * 1) nhop_create_from_info / nhop_create_from_nhop, which contains all * business logic on filling the nexthop fields based on the provided request. * 2) nhop_get(), which gets a usable referenced nexthops. * * Conventions: * 1) non-exported functions start with verb * 2) exported function starts with the subsystem prefix: "nhop" */ static int dump_nhop_entry(struct rib_head *rh, struct nhop_object *nh, struct sysctl_req *w); static struct nhop_priv *alloc_nhop_structure(void); static int get_nhop(struct rib_head *rnh, struct rt_addrinfo *info, struct nhop_priv **pnh_priv); static int finalize_nhop(struct nh_control *ctl, struct rt_addrinfo *info, struct nhop_priv *nh_priv); static struct ifnet *get_aifp(const struct nhop_object *nh); static void fill_sdl_from_ifp(struct sockaddr_dl_short *sdl, const struct ifnet *ifp); static void destroy_nhop_epoch(epoch_context_t ctx); static void destroy_nhop(struct nhop_priv *nh_priv); _Static_assert(__offsetof(struct nhop_object, nh_ifp) == 32, "nhop_object: wrong nh_ifp offset"); _Static_assert(sizeof(struct nhop_object) <= 128, "nhop_object: size exceeds 128 bytes"); static uma_zone_t nhops_zone; /* Global zone for each and every nexthop */ #define NHOP_OBJECT_ALIGNED_SIZE roundup2(sizeof(struct nhop_object), \ 2 * CACHE_LINE_SIZE) #define NHOP_PRIV_ALIGNED_SIZE roundup2(sizeof(struct nhop_priv), \ 2 * CACHE_LINE_SIZE) void nhops_init(void) { nhops_zone = uma_zcreate("routing nhops", NHOP_OBJECT_ALIGNED_SIZE + NHOP_PRIV_ALIGNED_SIZE, NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); } /* * Fetches the interface of source address used by the route. * In all cases except interface-address-route it would be the * same as the transmit interfaces. * However, for the interface address this function will return * this interface ifp instead of loopback. This is needed to support * link-local IPv6 loopback communications. * * Returns found ifp. */ static struct ifnet * get_aifp(const struct nhop_object *nh) { struct ifnet *aifp = NULL; /* * Adjust the "outgoing" interface. If we're going to loop * the packet back to ourselves, the ifp would be the loopback * interface. However, we'd rather know the interface associated * to the destination address (which should probably be one of * our own addresses). */ if ((nh->nh_ifp->if_flags & IFF_LOOPBACK) && nh->gw_sa.sa_family == AF_LINK) { aifp = ifnet_byindex(nh->gwl_sa.sdl_index); if (aifp == NULL) { FIB_NH_LOG(LOG_WARNING, nh, "unable to get aifp for %s index %d", if_name(nh->nh_ifp), nh->gwl_sa.sdl_index); } } if (aifp == NULL) aifp = nh->nh_ifp; return (aifp); } int cmp_priv(const struct nhop_priv *_one, const struct nhop_priv *_two) { if (memcmp(_one->nh, _two->nh, NHOP_END_CMP) != 0) return (0); if (memcmp(_one, _two, NH_PRIV_END_CMP) != 0) return (0); return (1); } /* * Conditionally sets @nh mtu data based on the @info data. */ static void set_nhop_mtu_from_info(struct nhop_object *nh, const struct rt_addrinfo *info) { if (info->rti_mflags & RTV_MTU) { if (info->rti_rmx->rmx_mtu != 0) { /* * MTU was explicitly provided by user. * Keep it. */ nh->nh_priv->rt_flags |= RTF_FIXEDMTU; } else { /* * User explicitly sets MTU to 0. * Assume rollback to default. */ nh->nh_priv->rt_flags &= ~RTF_FIXEDMTU; } nh->nh_mtu = info->rti_rmx->rmx_mtu; } } /* * Fills in shorted link-level sockadd version suitable to be stored inside the * nexthop gateway buffer. */ static void fill_sdl_from_ifp(struct sockaddr_dl_short *sdl, const struct ifnet *ifp) { bzero(sdl, sizeof(struct sockaddr_dl_short)); sdl->sdl_family = AF_LINK; sdl->sdl_len = sizeof(struct sockaddr_dl_short); sdl->sdl_index = ifp->if_index; sdl->sdl_type = ifp->if_type; } static int set_nhop_gw_from_info(struct nhop_object *nh, struct rt_addrinfo *info) { struct sockaddr *gw; gw = info->rti_info[RTAX_GATEWAY]; KASSERT(gw != NULL, ("gw is NULL")); if ((gw->sa_family == AF_LINK) && !(info->rti_flags & RTF_GATEWAY)) { /* * Interface route with interface specified by the interface * index in sockadd_dl structure. It is used in the IPv6 loopback * output code, where we need to preserve the original interface * to maintain proper scoping. * Despite the fact that nexthop code stores original interface * in the separate field (nh_aifp, see below), write AF_LINK * compatible sa with shorter total length. */ struct sockaddr_dl *sdl = (struct sockaddr_dl *)gw; struct ifnet *ifp = ifnet_byindex(sdl->sdl_index); if (ifp == NULL) { FIB_NH_LOG(LOG_WARNING, nh, "invalid ifindex %d", sdl->sdl_index); return (EINVAL); } fill_sdl_from_ifp(&nh->gwl_sa, ifp); } else { /* * Multiple options here: * * 1) RTF_GATEWAY with IPv4/IPv6 gateway data * 2) Interface route with IPv4/IPv6 address of the * matching interface. Some routing daemons do that * instead of specifying ifindex in AF_LINK. * * In both cases, save the original nexthop to make the callers * happy. */ if (gw->sa_len > sizeof(struct sockaddr_in6)) { FIB_NH_LOG(LOG_WARNING, nh, "nhop SA size too big: AF %d len %u", gw->sa_family, gw->sa_len); return (ENOMEM); } memcpy(&nh->gw_sa, gw, gw->sa_len); } return (0); } static uint16_t convert_rt_to_nh_flags(int rt_flags) { uint16_t res; res = (rt_flags & RTF_REJECT) ? NHF_REJECT : 0; res |= (rt_flags & RTF_HOST) ? NHF_HOST : 0; res |= (rt_flags & RTF_BLACKHOLE) ? NHF_BLACKHOLE : 0; res |= (rt_flags & (RTF_DYNAMIC|RTF_MODIFIED)) ? NHF_REDIRECT : 0; res |= (rt_flags & RTF_BROADCAST) ? NHF_BROADCAST : 0; res |= (rt_flags & RTF_GATEWAY) ? NHF_GATEWAY : 0; return (res); } static int fill_nhop_from_info(struct nhop_priv *nh_priv, struct rt_addrinfo *info) { int error, rt_flags; struct nhop_object *nh; nh = nh_priv->nh; rt_flags = info->rti_flags & NHOP_RT_FLAG_MASK; nh->nh_priv->rt_flags = rt_flags; nh_priv->nh_upper_family = info->rti_info[RTAX_DST]->sa_family; nh_priv->nh_type = 0; // hook responsibility to set nhop type nh->nh_flags = convert_rt_to_nh_flags(rt_flags); set_nhop_mtu_from_info(nh, info); if ((error = set_nhop_gw_from_info(nh, info)) != 0) return (error); if (nh->gw_sa.sa_family == AF_LINK) nh_priv->nh_neigh_family = nh_priv->nh_upper_family; else nh_priv->nh_neigh_family = nh->gw_sa.sa_family; nh->nh_ifp = (info->rti_ifp != NULL) ? info->rti_ifp : info->rti_ifa->ifa_ifp; nh->nh_ifa = info->rti_ifa; /* depends on the gateway */ nh->nh_aifp = get_aifp(nh); /* * Note some of the remaining data is set by the * per-address-family pre-add hook. */ return (0); } /* * Creates a new nexthop based on the information in @info. * * Returns: * 0 on success, filling @nh_ret with the desired nexthop object ptr * errno otherwise */ int nhop_create_from_info(struct rib_head *rnh, struct rt_addrinfo *info, struct nhop_object **nh_ret) { struct nhop_priv *nh_priv; int error; NET_EPOCH_ASSERT(); if (info->rti_info[RTAX_GATEWAY] == NULL) return (EINVAL); nh_priv = alloc_nhop_structure(); error = fill_nhop_from_info(nh_priv, info); if (error != 0) { uma_zfree(nhops_zone, nh_priv->nh); return (error); } error = get_nhop(rnh, info, &nh_priv); if (error == 0) *nh_ret = nh_priv->nh; return (error); } /* * Gets linked nhop using the provided @pnh_priv nexhop data. * If linked nhop is found, returns it, freeing the provided one. * If there is no such nexthop, attaches the remaining data to the * provided nexthop and links it. * * Returns 0 on success, storing referenced nexthop in @pnh_priv. * Otherwise, errno is returned. */ static int get_nhop(struct rib_head *rnh, struct rt_addrinfo *info, struct nhop_priv **pnh_priv) { const struct sockaddr *dst, *gateway, *netmask; struct nhop_priv *nh_priv, *tmp_priv; int error; nh_priv = *pnh_priv; /* Give the protocols chance to augment the request data */ dst = info->rti_info[RTAX_DST]; netmask = info->rti_info[RTAX_NETMASK]; gateway = info->rti_info[RTAX_GATEWAY]; error = rnh->rnh_preadd(rnh->rib_fibnum, dst, netmask, nh_priv->nh); if (error != 0) { uma_zfree(nhops_zone, nh_priv->nh); return (error); } tmp_priv = find_nhop(rnh->nh_control, nh_priv); if (tmp_priv != NULL) { uma_zfree(nhops_zone, nh_priv->nh); *pnh_priv = tmp_priv; return (0); } /* * Existing nexthop not found, need to create new one. * Note: multiple simultaneous get_nhop() requests * can result in multiple equal nexhops existing in the * nexthop table. This is not a not a problem until the * relative number of such nexthops is significant, which * is extremely unlikely. */ error = finalize_nhop(rnh->nh_control, info, nh_priv); if (error != 0) return (error); return (0); } /* * Update @nh with data supplied in @info. * This is a helper function to support route changes. * * It limits the changes that can be done to the route to the following: * 1) all combination of gateway changes (gw, interface, blackhole/reject) * 2) route flags (FLAG[123],STATIC,BLACKHOLE,REJECT) * 3) route MTU * * Returns: * 0 on success */ static int alter_nhop_from_info(struct nhop_object *nh, struct rt_addrinfo *info) { struct nhop_priv *nh_priv = nh->nh_priv; struct sockaddr *info_gw; int error; /* Update MTU if set in the request*/ set_nhop_mtu_from_info(nh, info); /* XXX: allow only one of BLACKHOLE,REJECT,GATEWAY */ /* Allow some flags (FLAG1,STATIC,BLACKHOLE,REJECT) to be toggled on change. */ nh_priv->rt_flags &= ~RTF_FMASK; nh_priv->rt_flags |= info->rti_flags & RTF_FMASK; /* Consider gateway change */ info_gw = info->rti_info[RTAX_GATEWAY]; if (info_gw != NULL) { error = set_nhop_gw_from_info(nh, info); if (error != 0) return (error); if (nh->gw_sa.sa_family == AF_LINK) nh_priv->nh_neigh_family = nh_priv->nh_upper_family; else nh_priv->nh_neigh_family = nh->gw_sa.sa_family; /* Update RTF_GATEWAY flag status */ nh_priv->rt_flags &= ~RTF_GATEWAY; nh_priv->rt_flags |= (RTF_GATEWAY & info->rti_flags); } /* Update datapath flags */ nh->nh_flags = convert_rt_to_nh_flags(nh_priv->rt_flags); if (info->rti_ifa != NULL) nh->nh_ifa = info->rti_ifa; if (info->rti_ifp != NULL) nh->nh_ifp = info->rti_ifp; nh->nh_aifp = get_aifp(nh); return (0); } /* * Creates new nexthop based on @nh_orig and augmentation data from @info. * Helper function used in the route changes, please see * alter_nhop_from_info() comments for more details. * * Returns: * 0 on success, filling @nh_ret with the desired nexthop object * errno otherwise */ int nhop_create_from_nhop(struct rib_head *rnh, const struct nhop_object *nh_orig, struct rt_addrinfo *info, struct nhop_object **pnh) { struct nhop_priv *nh_priv; struct nhop_object *nh; int error; NET_EPOCH_ASSERT(); nh_priv = alloc_nhop_structure(); nh = nh_priv->nh; /* Start with copying data from original nexthop */ nh_priv->nh_upper_family = nh_orig->nh_priv->nh_upper_family; nh_priv->nh_neigh_family = nh_orig->nh_priv->nh_neigh_family; nh_priv->rt_flags = nh_orig->nh_priv->rt_flags; nh_priv->nh_type = nh_orig->nh_priv->nh_type; nh->nh_ifp = nh_orig->nh_ifp; nh->nh_ifa = nh_orig->nh_ifa; nh->nh_aifp = nh_orig->nh_aifp; nh->nh_mtu = nh_orig->nh_mtu; nh->nh_flags = nh_orig->nh_flags; memcpy(&nh->gw_sa, &nh_orig->gw_sa, nh_orig->gw_sa.sa_len); error = alter_nhop_from_info(nh, info); if (error != 0) { uma_zfree(nhops_zone, nh_priv->nh); return (error); } error = get_nhop(rnh, info, &nh_priv); if (error == 0) *pnh = nh_priv->nh; return (error); } /* * Allocates memory for public/private nexthop structures. * * Returns pointer to nhop_priv or NULL. */ static struct nhop_priv * alloc_nhop_structure() { struct nhop_object *nh; struct nhop_priv *nh_priv; nh = (struct nhop_object *)uma_zalloc(nhops_zone, M_NOWAIT | M_ZERO); if (nh == NULL) return (NULL); nh_priv = (struct nhop_priv *)((char *)nh + NHOP_OBJECT_ALIGNED_SIZE); nh->nh_priv = nh_priv; nh_priv->nh = nh; return (nh_priv); } static bool reference_nhop_deps(struct nhop_object *nh) { if (!ifa_try_ref(nh->nh_ifa)) return (false); nh->nh_aifp = get_aifp(nh); if (!if_try_ref(nh->nh_aifp)) { ifa_free(nh->nh_ifa); return (false); } FIB_NH_LOG(LOG_DEBUG, nh, "AIFP: %p nh_ifp %p", nh->nh_aifp, nh->nh_ifp); if (!if_try_ref(nh->nh_ifp)) { ifa_free(nh->nh_ifa); if_rele(nh->nh_aifp); return (false); } return (true); } /* * Alocates/references the remaining bits of nexthop data and links * it to the hash table. * Returns 0 if successful, * errno otherwise. @nh_priv is freed in case of error. */ static int finalize_nhop(struct nh_control *ctl, struct rt_addrinfo *info, struct nhop_priv *nh_priv) { struct nhop_object *nh = nh_priv->nh; /* Allocate per-cpu packet counter */ nh->nh_pksent = counter_u64_alloc(M_NOWAIT); if (nh->nh_pksent == NULL) { uma_zfree(nhops_zone, nh); RTSTAT_INC(rts_nh_alloc_failure); FIB_NH_LOG(LOG_WARNING, nh, "counter_u64_alloc() failed"); return (ENOMEM); } if (!reference_nhop_deps(nh)) { counter_u64_free(nh->nh_pksent); uma_zfree(nhops_zone, nh); RTSTAT_INC(rts_nh_alloc_failure); FIB_NH_LOG(LOG_WARNING, nh, "interface reference failed"); return (EAGAIN); } /* Save vnet to ease destruction */ nh_priv->nh_vnet = curvnet; refcount_init(&nh_priv->nh_refcnt, 1); /* Please see nhop_free() comments on the initial value */ refcount_init(&nh_priv->nh_linked, 2); nh_priv->nh_fibnum = ctl->ctl_rh->rib_fibnum; if (link_nhop(ctl, nh_priv) == 0) { /* * Adding nexthop to the datastructures * failed. Call destructor w/o waiting for * the epoch end, as nexthop is not used * and return. */ char nhbuf[48]; FIB_NH_LOG(LOG_WARNING, nh, "failed to link %s", nhop_print_buf(nh, nhbuf, sizeof(nhbuf))); destroy_nhop(nh_priv); return (ENOBUFS); } #if DEBUG_MAX_LEVEL >= LOG_DEBUG char nhbuf[48]; FIB_NH_LOG(LOG_DEBUG, nh, "finalized: %s", nhop_print_buf(nh, nhbuf, sizeof(nhbuf))); #endif return (0); } static void destroy_nhop(struct nhop_priv *nh_priv) { struct nhop_object *nh; nh = nh_priv->nh; if_rele(nh->nh_ifp); if_rele(nh->nh_aifp); ifa_free(nh->nh_ifa); counter_u64_free(nh->nh_pksent); uma_zfree(nhops_zone, nh); } /* * Epoch callback indicating nhop is safe to destroy */ static void destroy_nhop_epoch(epoch_context_t ctx) { struct nhop_priv *nh_priv; nh_priv = __containerof(ctx, struct nhop_priv, nh_epoch_ctx); destroy_nhop(nh_priv); } void nhop_ref_object(struct nhop_object *nh) { u_int old; old = refcount_acquire(&nh->nh_priv->nh_refcnt); KASSERT(old > 0, ("%s: nhop object %p has 0 refs", __func__, nh)); } int nhop_try_ref_object(struct nhop_object *nh) { return (refcount_acquire_if_not_zero(&nh->nh_priv->nh_refcnt)); } void nhop_free(struct nhop_object *nh) { struct nh_control *ctl; struct nhop_priv *nh_priv = nh->nh_priv; struct epoch_tracker et; if (!refcount_release(&nh_priv->nh_refcnt)) return; #if DEBUG_MAX_LEVEL >= LOG_DEBUG char nhbuf[48]; FIB_NH_LOG(LOG_DEBUG, nh, "deleting %s", nhop_print_buf(nh, nhbuf, sizeof(nhbuf))); #endif /* * There are only 2 places, where nh_linked can be decreased: * rib destroy (nhops_destroy_rib) and this function. * nh_link can never be increased. * * Hence, use initial value of 2 to make use of * refcount_release_if_not_last(). * * There can be two scenarious when calling this function: * * 1) nh_linked value is 2. This means that either * nhops_destroy_rib() has not been called OR it is running, * but we are guaranteed that nh_control won't be freed in * this epoch. Hence, nexthop can be safely unlinked. * * 2) nh_linked value is 1. In that case, nhops_destroy_rib() * has been called and nhop unlink can be skipped. */ NET_EPOCH_ENTER(et); if (refcount_release_if_not_last(&nh_priv->nh_linked)) { ctl = nh_priv->nh_control; if (unlink_nhop(ctl, nh_priv) == NULL) { /* Do not try to reclaim */ char nhbuf[48]; FIB_NH_LOG(LOG_WARNING, nh, "failed to unlink %s", nhop_print_buf(nh, nhbuf, sizeof(nhbuf))); NET_EPOCH_EXIT(et); return; } } NET_EPOCH_EXIT(et); epoch_call(net_epoch_preempt, destroy_nhop_epoch, &nh_priv->nh_epoch_ctx); } void nhop_ref_any(struct nhop_object *nh) { #ifdef ROUTE_MPATH if (!NH_IS_NHGRP(nh)) nhop_ref_object(nh); else nhgrp_ref_object((struct nhgrp_object *)nh); #else nhop_ref_object(nh); #endif } void nhop_free_any(struct nhop_object *nh) { #ifdef ROUTE_MPATH if (!NH_IS_NHGRP(nh)) nhop_free(nh); else nhgrp_free((struct nhgrp_object *)nh); #else nhop_free(nh); #endif } /* Helper functions */ uint32_t nhop_get_idx(const struct nhop_object *nh) { return (nh->nh_priv->nh_idx); } enum nhop_type nhop_get_type(const struct nhop_object *nh) { return (nh->nh_priv->nh_type); } void nhop_set_type(struct nhop_object *nh, enum nhop_type nh_type) { nh->nh_priv->nh_type = nh_type; } int nhop_get_rtflags(const struct nhop_object *nh) { return (nh->nh_priv->rt_flags); } void nhop_set_rtflags(struct nhop_object *nh, int rt_flags) { nh->nh_priv->rt_flags = rt_flags; } struct vnet * nhop_get_vnet(const struct nhop_object *nh) { return (nh->nh_priv->nh_vnet); } struct nhop_object * nhop_select_func(struct nhop_object *nh, uint32_t flowid) { return (nhop_select(nh, flowid)); } /* * Returns address family of the traffic uses the nexthop. */ int nhop_get_upper_family(const struct nhop_object *nh) { return (nh->nh_priv->nh_upper_family); } /* * Returns address family of the LLE or gateway that is used * to forward the traffic to. */ int nhop_get_neigh_family(const struct nhop_object *nh) { return (nh->nh_priv->nh_neigh_family); } uint32_t nhop_get_fibnum(const struct nhop_object *nh) { return (nh->nh_priv->nh_fibnum); } void nhops_update_ifmtu(struct rib_head *rh, struct ifnet *ifp, uint32_t mtu) { struct nh_control *ctl; struct nhop_priv *nh_priv; struct nhop_object *nh; ctl = rh->nh_control; NHOPS_WLOCK(ctl); CHT_SLIST_FOREACH(&ctl->nh_head, nhops, nh_priv) { nh = nh_priv->nh; if (nh->nh_ifp == ifp) { if ((nh_priv->rt_flags & RTF_FIXEDMTU) == 0 || nh->nh_mtu > mtu) { /* Update MTU directly */ nh->nh_mtu = mtu; } } } CHT_SLIST_FOREACH_END; NHOPS_WUNLOCK(ctl); } /* * Prints nexthop @nh data in the provided @buf. * Example: nh#33/inet/em0/192.168.0.1 */ char * nhop_print_buf(const struct nhop_object *nh, char *buf, size_t bufsize) { +#if defined(INET) || defined(INET6) char abuf[INET6_ADDRSTRLEN]; +#endif struct nhop_priv *nh_priv = nh->nh_priv; const char *upper_str = rib_print_family(nh->nh_priv->nh_upper_family); switch (nh->gw_sa.sa_family) { #ifdef INET case AF_INET: inet_ntop(AF_INET, &nh->gw4_sa.sin_addr, abuf, sizeof(abuf)); snprintf(buf, bufsize, "nh#%d/%s/%s/%s", nh_priv->nh_idx, upper_str, if_name(nh->nh_ifp), abuf); break; #endif #ifdef INET6 case AF_INET6: inet_ntop(AF_INET6, &nh->gw6_sa.sin6_addr, abuf, sizeof(abuf)); snprintf(buf, bufsize, "nh#%d/%s/%s/%s", nh_priv->nh_idx, upper_str, if_name(nh->nh_ifp), abuf); break; #endif case AF_LINK: snprintf(buf, bufsize, "nh#%d/%s/%s/resolve", nh_priv->nh_idx, upper_str, if_name(nh->nh_ifp)); break; default: snprintf(buf, bufsize, "nh#%d/%s/%s/????", nh_priv->nh_idx, upper_str, if_name(nh->nh_ifp)); break; } return (buf); } /* * Dumps a single entry to sysctl buffer. * * Layout: * rt_msghdr - generic RTM header to allow users to skip non-understood messages * nhop_external - nexhop description structure (with length) * nhop_addrs - structure encapsulating GW/SRC sockaddrs */ static int dump_nhop_entry(struct rib_head *rh, struct nhop_object *nh, struct sysctl_req *w) { struct { struct rt_msghdr rtm; struct nhop_external nhe; struct nhop_addrs na; } arpc; struct nhop_external *pnhe; struct sockaddr *gw_sa, *src_sa; struct sockaddr_storage ss; size_t addrs_len; int error; memset(&arpc, 0, sizeof(arpc)); arpc.rtm.rtm_msglen = sizeof(arpc); arpc.rtm.rtm_version = RTM_VERSION; arpc.rtm.rtm_type = RTM_GET; //arpc.rtm.rtm_flags = RTF_UP; arpc.rtm.rtm_flags = nh->nh_priv->rt_flags; /* nhop_external */ pnhe = &arpc.nhe; pnhe->nh_len = sizeof(struct nhop_external); pnhe->nh_idx = nh->nh_priv->nh_idx; pnhe->nh_fib = rh->rib_fibnum; pnhe->ifindex = nh->nh_ifp->if_index; pnhe->aifindex = nh->nh_aifp->if_index; pnhe->nh_family = nh->nh_priv->nh_upper_family; pnhe->nh_type = nh->nh_priv->nh_type; pnhe->nh_mtu = nh->nh_mtu; pnhe->nh_flags = nh->nh_flags; memcpy(pnhe->nh_prepend, nh->nh_prepend, sizeof(nh->nh_prepend)); pnhe->prepend_len = nh->nh_prepend_len; pnhe->nh_refcount = nh->nh_priv->nh_refcnt; pnhe->nh_pksent = counter_u64_fetch(nh->nh_pksent); /* sockaddr container */ addrs_len = sizeof(struct nhop_addrs); arpc.na.gw_sa_off = addrs_len; gw_sa = (struct sockaddr *)&nh->gw4_sa; addrs_len += gw_sa->sa_len; src_sa = nh->nh_ifa->ifa_addr; if (src_sa->sa_family == AF_LINK) { /* Shorten structure */ memset(&ss, 0, sizeof(struct sockaddr_storage)); fill_sdl_from_ifp((struct sockaddr_dl_short *)&ss, nh->nh_ifa->ifa_ifp); src_sa = (struct sockaddr *)&ss; } arpc.na.src_sa_off = addrs_len; addrs_len += src_sa->sa_len; /* Write total container length */ arpc.na.na_len = addrs_len; arpc.rtm.rtm_msglen += arpc.na.na_len - sizeof(struct nhop_addrs); error = SYSCTL_OUT(w, &arpc, sizeof(arpc)); if (error == 0) error = SYSCTL_OUT(w, gw_sa, gw_sa->sa_len); if (error == 0) error = SYSCTL_OUT(w, src_sa, src_sa->sa_len); return (error); } uint32_t nhops_get_count(struct rib_head *rh) { struct nh_control *ctl; uint32_t count; ctl = rh->nh_control; NHOPS_RLOCK(ctl); count = ctl->nh_head.items_count; NHOPS_RUNLOCK(ctl); return (count); } int nhops_dump_sysctl(struct rib_head *rh, struct sysctl_req *w) { struct nh_control *ctl; struct nhop_priv *nh_priv; int error; ctl = rh->nh_control; NHOPS_RLOCK(ctl); #if DEBUG_MAX_LEVEL >= LOG_DEBUG FIB_LOG(LOG_DEBUG, rh->rib_fibnum, rh->rib_family, "dump %u items", ctl->nh_head.items_count); #endif CHT_SLIST_FOREACH(&ctl->nh_head, nhops, nh_priv) { error = dump_nhop_entry(rh, nh_priv->nh, w); if (error != 0) { NHOPS_RUNLOCK(ctl); return (error); } } CHT_SLIST_FOREACH_END; NHOPS_RUNLOCK(ctl); return (0); }