diff --git a/sys/net/route/nhop.h b/sys/net/route/nhop.h index bd3c3825ed86..ee4f79d2bb47 100644 --- a/sys/net/route/nhop.h +++ b/sys/net/route/nhop.h @@ -1,274 +1,275 @@ /*- * 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. * * $FreeBSD$ */ /* * This header file contains public definitions for the nexthop routing subsystem. */ #ifndef _NET_ROUTE_NHOP_H_ #define _NET_ROUTE_NHOP_H_ #include /* sockaddr_in && sockaddr_in6 */ #include enum nhop_type { NH_TYPE_IPV4_ETHER_RSLV = 1, /* IPv4 ethernet without GW */ NH_TYPE_IPV4_ETHER_NHOP = 2, /* IPv4 with pre-calculated ethernet encap */ NH_TYPE_IPV6_ETHER_RSLV = 3, /* IPv6 ethernet, without GW */ NH_TYPE_IPV6_ETHER_NHOP = 4 /* IPv6 with pre-calculated ethernet encap*/ }; #ifdef _KERNEL /* * Define shorter version of AF_LINK sockaddr. * * Currently the only use case of AF_LINK gateway is storing * interface index of the interface of the source IPv6 address. * This is used by the IPv6 code for the connections over loopback * interface. * * The structure below copies 'struct sockaddr_dl', reducing the * size of sdl_data buffer, as it is not used. This change * allows to store the AF_LINK gateways in the nhop gateway itself, * simplifying control plane handling. */ struct sockaddr_dl_short { u_char sdl_len; /* Total length of sockaddr */ u_char sdl_family; /* AF_LINK */ u_short sdl_index; /* if != 0, system given index for interface */ u_char sdl_type; /* interface type */ u_char sdl_nlen; /* interface name length, no trailing 0 reqd. */ u_char sdl_alen; /* link level address length */ u_char sdl_slen; /* link layer selector length */ char sdl_data[8]; /* unused */ }; #define NHOP_RELATED_FLAGS \ (RTF_GATEWAY | RTF_HOST | RTF_REJECT | RTF_BLACKHOLE | \ RTF_FIXEDMTU | RTF_LOCAL | RTF_BROADCAST | RTF_MULTICAST) struct nh_control; struct nhop_priv; /* * Struct 'nhop_object' field description: * * nh_flags: NHF_ flags used in the dataplane code. NHF_GATEWAY or NHF_BLACKHOLE * can be examples of such flags. * nh_mtu: ready-to-use nexthop mtu. Already accounts for the link-level header, * interface MTU and protocol-specific limitations. * nh_prepend_len: link-level prepend length. Currently unused. * nh_ifp: logical transmit interface. The one from which if_transmit() will be * called. Guaranteed to be non-NULL. * nh_aifp: ifnet of the source address. Same as nh_ifp except IPv6 loopback * routes. See the example below. * nh_ifa: interface address to use. Guaranteed to be non-NULL. * nh_pksent: counter(9) reflecting the number of packets transmitted. * * gw_: storage suitable to hold AF_INET, AF_INET6 or AF_LINK gateway. More * details ara available in the examples below. * * Examples: * * Direct routes (routes w/o gateway): * NHF_GATEWAY is NOT set. * nh_ifp denotes the logical transmit interface (). * nh_aifp is the same as nh_ifp * gw_sa contains AF_LINK sa with nh_aifp ifindex (compat) * Loopback routes: * NHF_GATEWAY is NOT set. * nh_ifp points to the loopback interface (lo0). * nh_aifp points to the interface where the destination address belongs to. * This is useful in IPv6 link-local-over-loopback communications. * gw_sa contains AF_LINK sa with nh_aifp ifindex (compat) * GW routes: * NHF_GATEWAY is set. * nh_ifp denotes the logical transmit interface. * nh_aifp is the same as nh_ifp * gw_sa contains L3 address (either AF_INET or AF_INET6). * * * Note: struct nhop_object fields are ordered in a way that * supports memcmp-based comparisons. * */ #define NHOP_END_CMP (__offsetof(struct nhop_object, nh_pksent)) struct nhop_object { uint16_t nh_flags; /* nhop flags */ uint16_t nh_mtu; /* nexthop mtu */ union { struct sockaddr_in gw4_sa; /* GW accessor as IPv4 */ struct sockaddr_in6 gw6_sa; /* GW accessor as IPv6 */ struct sockaddr gw_sa; struct sockaddr_dl_short gwl_sa; /* AF_LINK gw (compat) */ char gw_buf[28]; }; struct ifnet *nh_ifp; /* Logical egress interface. Always != NULL */ struct ifaddr *nh_ifa; /* interface address to use. Always != NULL */ struct ifnet *nh_aifp; /* ifnet of the source address. Always != NULL */ counter_u64_t nh_pksent; /* packets sent using this nhop */ /* 32 bytes + 4xPTR == 64(amd64) / 48(i386) */ uint8_t nh_prepend_len; /* length of prepend data */ uint8_t spare[3]; uint32_t spare1; /* alignment */ char nh_prepend[48]; /* L2 prepend */ struct nhop_priv *nh_priv; /* control plane data */ /* -- 128 bytes -- */ }; /* * Nhop validness. * * Currently we verify whether link is up or not on every packet, which can be * quite costy. * TODO: subscribe for the interface notifications and update the nexthops * with NHF_INVALID flag. */ #define NH_IS_VALID(_nh) RT_LINK_IS_UP((_nh)->nh_ifp) #define NH_IS_NHGRP(_nh) ((_nh)->nh_flags & NHF_MULTIPATH) #define NH_FREE(_nh) do { \ nhop_free(_nh); \ /* guard against invalid refs */ \ _nh = NULL; \ } while (0) struct weightened_nhop { struct nhop_object *nh; uint32_t weight; uint32_t storage; }; void nhop_free(struct nhop_object *nh); struct sysctl_req; struct sockaddr_dl; struct rib_head; /* flags that can be set using nhop_set_rtflags() */ #define RT_SET_RTFLAGS_MASK (RTF_PROTO1 | RTF_PROTO2 | RTF_PROTO3 | RTF_STATIC) #define RT_CHANGE_RTFLAGS_MASK RT_SET_RTFLAGS_MASK struct nhop_object *nhop_alloc(uint32_t fibnum, int family); void nhop_copy(struct nhop_object *nh, const struct nhop_object *nh_orig); struct nhop_object *nhop_get_nhop(struct nhop_object *nh, int *perror); void nhop_set_direct_gw(struct nhop_object *nh, struct ifnet *ifp); bool nhop_set_gw(struct nhop_object *nh, const struct sockaddr *sa, bool is_gw); void nhop_set_mtu(struct nhop_object *nh, uint32_t mtu, bool from_user); void nhop_set_rtflags(struct nhop_object *nh, int rt_flags); void nhop_set_pxtype_flag(struct nhop_object *nh, int nh_flag); void nhop_set_broadcast(struct nhop_object *nh, bool is_broadcast); void nhop_set_blackhole(struct nhop_object *nh, int blackhole_rt_flag); void nhop_set_pinned(struct nhop_object *nh, bool is_pinned); void nhop_set_redirect(struct nhop_object *nh, bool is_redirect); void nhop_set_type(struct nhop_object *nh, enum nhop_type nh_type); void nhop_set_src(struct nhop_object *nh, struct ifaddr *ifa); void nhop_set_transmit_ifp(struct nhop_object *nh, struct ifnet *ifp); uint32_t nhop_get_idx(const struct nhop_object *nh); enum nhop_type nhop_get_type(const struct nhop_object *nh); int nhop_get_rtflags(const struct nhop_object *nh); struct vnet *nhop_get_vnet(const struct nhop_object *nh); struct nhop_object *nhop_select_func(struct nhop_object *nh, uint32_t flowid); int nhop_get_upper_family(const struct nhop_object *nh); int nhop_get_neigh_family(const struct nhop_object *nh); uint32_t nhop_get_fibnum(const struct nhop_object *nh); +void nhop_set_fibnum(struct nhop_object *nh, uint32_t fibnum); uint32_t nhop_get_expire(const struct nhop_object *nh); void nhop_set_expire(struct nhop_object *nh, uint32_t expire); #endif /* _KERNEL */ /* Kernel <> userland structures */ /* Structure usage and layout are described in dump_nhop_entry() */ struct nhop_external { uint32_t nh_len; /* length of the datastructure */ uint32_t nh_idx; /* Nexthop index */ uint32_t nh_fib; /* Fib nexhop is attached to */ uint32_t ifindex; /* transmit interface ifindex */ uint32_t aifindex; /* address ifindex */ uint8_t prepend_len; /* length of the prepend */ uint8_t nh_family; /* address family */ uint16_t nh_type; /* nexthop type */ uint16_t nh_mtu; /* nexthop mtu */ uint16_t nh_flags; /* nhop flags */ struct in_addr nh_addr; /* GW/DST IPv4 address */ struct in_addr nh_src; /* default source IPv4 address */ uint64_t nh_pksent; /* control plane */ /* lookup key: address, family, type */ char nh_prepend[64]; /* L2 prepend */ uint64_t nh_refcount; /* number of references */ }; struct nhop_addrs { uint32_t na_len; /* length of the datastructure */ uint16_t gw_sa_off; /* offset of gateway SA */ uint16_t src_sa_off; /* offset of src address SA */ }; #define NHG_C_TYPE_CNHOPS 0x1 /* Control plane nhops list */ #define NHG_C_TYPE_DNHOPS 0x2 /* Dataplane nhops list */ struct nhgrp_container { uint32_t nhgc_len; /* container length */ uint16_t nhgc_count; /* number of items */ uint8_t nhgc_type; /* container type */ uint8_t nhgc_subtype; /* container subtype */ }; struct nhgrp_nhop_external { uint32_t nh_idx; uint32_t nh_weight; }; /* * Layout: * - nhgrp_external * - nhgrp_container (control plane nhops list) * - nhgrp_nhop_external * - nhgrp_nhop_external * .. * - nhgrp_container (dataplane nhops list) * - nhgrp_nhop_external * - nhgrp_nhop_external */ struct nhgrp_external { uint32_t nhg_idx; /* Nexthop group index */ uint32_t nhg_refcount; /* number of references */ }; #endif diff --git a/sys/net/route/nhop_ctl.c b/sys/net/route/nhop_ctl.c index 824bf12a903d..46a5c7befd65 100644 --- a/sys/net/route/nhop_ctl.c +++ b/sys/net/route/nhop_ctl.c @@ -1,1111 +1,1123 @@ /*- * 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 int finalize_nhop(struct nh_control *ctl, struct nhop_object *nh); 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_object *nh); static struct rib_head *nhop_get_rh(const struct nhop_object *nh); _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) nhop_set_mtu(nh, info->rti_rmx->rmx_mtu, true); } /* * 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]; MPASS(gw != NULL); bool is_gw = info->rti_flags & RTF_GATEWAY; if ((gw->sa_family == AF_LINK) && !is_gw) { /* * 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_DEBUG, nh, "error: invalid ifindex %d", sdl->sdl_index); return (EINVAL); } nhop_set_direct_gw(nh, 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 (!nhop_set_gw(nh, gw, is_gw)) return (EINVAL); } return (0); } static void set_nhop_expire_from_info(struct nhop_object *nh, const struct rt_addrinfo *info) { uint32_t nh_expire = 0; /* Kernel -> userland timebase conversion. */ if ((info->rti_mflags & RTV_EXPIRE) && (info->rti_rmx->rmx_expire > 0)) nh_expire = info->rti_rmx->rmx_expire - time_second + time_uptime; nhop_set_expire(nh, nh_expire); } /* * 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) { int error; NET_EPOCH_ASSERT(); MPASS(info->rti_ifa != NULL); MPASS(info->rti_ifp != NULL); if (info->rti_info[RTAX_GATEWAY] == NULL) { FIB_RH_LOG(LOG_DEBUG, rnh, "error: empty gateway"); return (EINVAL); } struct nhop_object *nh = nhop_alloc(rnh->rib_fibnum, rnh->rib_family); if (nh == NULL) return (ENOMEM); if ((error = set_nhop_gw_from_info(nh, info)) != 0) { nhop_free(nh); return (error); } nhop_set_transmit_ifp(nh, info->rti_ifp); nhop_set_blackhole(nh, info->rti_flags & (RTF_BLACKHOLE | RTF_REJECT)); error = rnh->rnh_set_nh_pfxflags(rnh->rib_fibnum, info->rti_info[RTAX_DST], info->rti_info[RTAX_NETMASK], nh); nhop_set_redirect(nh, info->rti_flags & RTF_DYNAMIC); nhop_set_pinned(nh, info->rti_flags & RTF_PINNED); set_nhop_expire_from_info(nh, info); nhop_set_rtflags(nh, info->rti_flags); set_nhop_mtu_from_info(nh, info); nhop_set_src(nh, info->rti_ifa); /* * The remaining fields are either set from nh_preadd hook * or are computed from the provided data */ *nh_ret = nhop_get_nhop(nh, &error); return (error); } /* * Gets linked nhop using the provided @nh 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. * Otherwise, errno is returned. */ struct nhop_object * nhop_get_nhop(struct nhop_object *nh, int *perror) +{ + struct rib_head *rnh = nhop_get_rh(nh); + + return (nhop_get_nhop_internal(rnh, nh, perror)); +} + +struct nhop_object * +nhop_get_nhop_internal(struct rib_head *rnh, struct nhop_object *nh, int *perror) { struct nhop_priv *tmp_priv; int error; nh->nh_aifp = get_aifp(nh); - struct rib_head *rnh = nhop_get_rh(nh); - /* Give the protocols chance to augment nexthop properties */ error = rnh->rnh_augment_nh(rnh->rib_fibnum, nh); if (error != 0) { nhop_free(nh); *perror = error; return (NULL); } tmp_priv = find_nhop(rnh->nh_control, nh->nh_priv); if (tmp_priv != NULL) { nhop_free(nh); *perror = 0; return (tmp_priv->nh); } /* * Existing nexthop not found, need to create new one. * Note: multiple simultaneous 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. */ *perror = finalize_nhop(rnh->nh_control, nh); return (*perror == 0 ? nh : NULL); } /* * 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 * 2) route flags (FLAG[123],STATIC) * 3) route MTU * * Returns: * 0 on success, errno otherwise */ static int alter_nhop_from_info(struct nhop_object *nh, struct rt_addrinfo *info) { struct sockaddr *info_gw; int error; /* Update MTU if set in the request*/ set_nhop_mtu_from_info(nh, info); /* Only RTF_FLAG[123] and RTF_STATIC */ uint32_t rt_flags = nhop_get_rtflags(nh) & ~RT_CHANGE_RTFLAGS_MASK; rt_flags |= info->rti_flags & RT_CHANGE_RTFLAGS_MASK; nhop_set_rtflags(nh, rt_flags); /* 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 (info->rti_ifa != NULL) nhop_set_src(nh, info->rti_ifa); if (info->rti_ifp != NULL) nhop_set_transmit_ifp(nh, info->rti_ifp); 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_object *nh; int error; NET_EPOCH_ASSERT(); nh = nhop_alloc(rnh->rib_fibnum, rnh->rib_family); if (nh == NULL) return (ENOMEM); nhop_copy(nh, nh_orig); error = alter_nhop_from_info(nh, info); if (error != 0) { nhop_free(nh); return (error); } *pnh = nhop_get_nhop(nh, &error); return (error); } 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_DEBUG2, nh, "nh_aifp: %s nh_ifp %s", if_name(nh->nh_aifp), if_name(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 nhop_object *nh) { /* Allocate per-cpu packet counter */ nh->nh_pksent = counter_u64_alloc(M_NOWAIT); if (nh->nh_pksent == NULL) { nhop_free(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); nhop_free(nh); RTSTAT_INC(rts_nh_alloc_failure); FIB_NH_LOG(LOG_WARNING, nh, "interface reference failed"); return (EAGAIN); } /* Save vnet to ease destruction */ nh->nh_priv->nh_vnet = curvnet; /* Please see nhop_free() comments on the initial value */ refcount_init(&nh->nh_priv->nh_linked, 2); nh->nh_priv->nh_fibnum = ctl->ctl_rh->rib_fibnum; if (link_nhop(ctl, nh->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[NHOP_PRINT_BUFSIZE]; FIB_NH_LOG(LOG_WARNING, nh, "failed to link %s", nhop_print_buf(nh, nhbuf, sizeof(nhbuf))); destroy_nhop(nh); return (ENOBUFS); } #if DEBUG_MAX_LEVEL >= LOG_DEBUG char nhbuf[NHOP_PRINT_BUFSIZE]; FIB_NH_LOG(LOG_DEBUG, nh, "finalized: %s", nhop_print_buf(nh, nhbuf, sizeof(nhbuf))); #endif return (0); } static void destroy_nhop(struct nhop_object *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->nh); } void nhop_ref_object(struct nhop_object *nh) { u_int old __diagused; 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; /* allows to use nhop_free() during nhop init */ if (__predict_false(nh_priv->nh_finalized == 0)) { uma_zfree(nhops_zone, nh); return; } #if DEBUG_MAX_LEVEL >= LOG_DEBUG char nhbuf[NHOP_PRINT_BUFSIZE]; 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[NHOP_PRINT_BUFSIZE]; 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 } /* Nhop-related methods */ /* * Allocates an empty unlinked nhop object. * Returns object pointer or NULL on failure */ struct nhop_object * nhop_alloc(uint32_t fibnum, int family) { struct nhop_object *nh; struct nhop_priv *nh_priv; nh = (struct nhop_object *)uma_zalloc(nhops_zone, M_NOWAIT | M_ZERO); if (__predict_false(nh == NULL)) return (NULL); nh_priv = (struct nhop_priv *)((char *)nh + NHOP_OBJECT_ALIGNED_SIZE); nh->nh_priv = nh_priv; nh_priv->nh = nh; nh_priv->nh_upper_family = family; nh_priv->nh_fibnum = fibnum; /* Setup refcount early to allow nhop_free() to work */ refcount_init(&nh_priv->nh_refcnt, 1); return (nh); } void nhop_copy(struct nhop_object *nh, const struct nhop_object *nh_orig) { struct nhop_priv *nh_priv = nh->nh_priv; nh->nh_flags = nh_orig->nh_flags; nh->nh_mtu = nh_orig->nh_mtu; memcpy(&nh->gw_sa, &nh_orig->gw_sa, nh_orig->gw_sa.sa_len); nh->nh_ifp = nh_orig->nh_ifp; nh->nh_ifa = nh_orig->nh_ifa; nh->nh_aifp = nh_orig->nh_aifp; 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->nh_type = nh_orig->nh_priv->nh_type; nh_priv->rt_flags = nh_orig->nh_priv->rt_flags; nh_priv->nh_fibnum = nh_orig->nh_priv->nh_fibnum; } void nhop_set_direct_gw(struct nhop_object *nh, struct ifnet *ifp) { nh->nh_flags &= ~NHF_GATEWAY; nh->nh_priv->rt_flags &= ~RTF_GATEWAY; nh->nh_priv->nh_neigh_family = nh->nh_priv->nh_upper_family; fill_sdl_from_ifp(&nh->gwl_sa, ifp); memset(&nh->gw_buf[nh->gw_sa.sa_len], 0, sizeof(nh->gw_buf) - nh->gw_sa.sa_len); } /* * Sets gateway for the nexthop. * It can be "normal" gateway with is_gw set or a special form of * adding interface route, refering to it by specifying local interface * address. In that case is_gw is set to false. */ bool nhop_set_gw(struct nhop_object *nh, const struct sockaddr *gw, bool is_gw) { if (gw->sa_len > sizeof(nh->gw_buf)) { FIB_NH_LOG(LOG_DEBUG, nh, "nhop SA size too big: AF %d len %u", gw->sa_family, gw->sa_len); return (false); } memcpy(&nh->gw_sa, gw, gw->sa_len); memset(&nh->gw_buf[gw->sa_len], 0, sizeof(nh->gw_buf) - gw->sa_len); if (is_gw) { nh->nh_flags |= NHF_GATEWAY; nh->nh_priv->rt_flags |= RTF_GATEWAY; nh->nh_priv->nh_neigh_family = gw->sa_family; } else { nh->nh_flags &= ~NHF_GATEWAY; nh->nh_priv->rt_flags &= ~RTF_GATEWAY; nh->nh_priv->nh_neigh_family = nh->nh_priv->nh_upper_family; } return (true); } void nhop_set_broadcast(struct nhop_object *nh, bool is_broadcast) { if (is_broadcast) { nh->nh_flags |= NHF_BROADCAST; nh->nh_priv->rt_flags |= RTF_BROADCAST; } else { nh->nh_flags &= ~NHF_BROADCAST; nh->nh_priv->rt_flags &= ~RTF_BROADCAST; } } void nhop_set_blackhole(struct nhop_object *nh, int blackhole_rt_flag) { nh->nh_flags &= ~(NHF_BLACKHOLE | NHF_REJECT); nh->nh_priv->rt_flags &= ~(RTF_BLACKHOLE | RTF_REJECT); switch (blackhole_rt_flag) { case RTF_BLACKHOLE: nh->nh_flags |= NHF_BLACKHOLE; nh->nh_priv->rt_flags |= RTF_BLACKHOLE; break; case RTF_REJECT: nh->nh_flags |= NHF_REJECT; nh->nh_priv->rt_flags |= RTF_REJECT; break; } } void nhop_set_redirect(struct nhop_object *nh, bool is_redirect) { if (is_redirect) { nh->nh_priv->rt_flags |= RTF_DYNAMIC; nh->nh_flags |= NHF_REDIRECT; } else { nh->nh_priv->rt_flags &= ~RTF_DYNAMIC; nh->nh_flags &= ~NHF_REDIRECT; } } void nhop_set_pinned(struct nhop_object *nh, bool is_pinned) { if (is_pinned) nh->nh_priv->rt_flags |= RTF_PINNED; else nh->nh_priv->rt_flags &= ~RTF_PINNED; } 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); } /* * Sets generic rtflags that are not covered by other functions. */ void nhop_set_rtflags(struct nhop_object *nh, int rt_flags) { nh->nh_priv->rt_flags &= ~RT_SET_RTFLAGS_MASK; nh->nh_priv->rt_flags |= (rt_flags & RT_SET_RTFLAGS_MASK); } /* * Sets flags that are specific to the prefix (NHF_HOST or NHF_DEFAULT). */ void nhop_set_pxtype_flag(struct nhop_object *nh, int nh_flag) { if (nh_flag == NHF_HOST) { nh->nh_flags |= NHF_HOST; nh->nh_flags &= ~NHF_DEFAULT; nh->nh_priv->rt_flags |= RTF_HOST; } else if (nh_flag == NHF_DEFAULT) { nh->nh_flags |= NHF_DEFAULT; nh->nh_flags &= ~NHF_HOST; nh->nh_priv->rt_flags &= ~RTF_HOST; } else { nh->nh_flags &= ~(NHF_HOST | NHF_DEFAULT); nh->nh_priv->rt_flags &= ~RTF_HOST; } } /* * Sets nhop MTU. Sets RTF_FIXEDMTU if mtu is explicitly * specified by userland. */ void nhop_set_mtu(struct nhop_object *nh, uint32_t mtu, bool from_user) { if (from_user) { if (mtu != 0) nh->nh_priv->rt_flags |= RTF_FIXEDMTU; else nh->nh_priv->rt_flags &= ~RTF_FIXEDMTU; } nh->nh_mtu = mtu; } void nhop_set_src(struct nhop_object *nh, struct ifaddr *ifa) { nh->nh_ifa = ifa; } void nhop_set_transmit_ifp(struct nhop_object *nh, struct ifnet *ifp) { nh->nh_ifp = ifp; } 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 +nhop_set_fibnum(struct nhop_object *nh, uint32_t fibnum) +{ + nh->nh_priv->nh_fibnum = fibnum; +} + uint32_t nhop_get_expire(const struct nhop_object *nh) { return (nh->nh_priv->nh_expire); } void nhop_set_expire(struct nhop_object *nh, uint32_t expire) { MPASS(!NH_IS_LINKED(nh)); nh->nh_priv->nh_expire = expire; } static struct rib_head * nhop_get_rh(const struct nhop_object *nh) { uint32_t fibnum = nhop_get_fibnum(nh); int family = nhop_get_neigh_family(nh); return (rt_tables_get_rnh(fibnum, family)); } 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); } char * nhop_print_buf_any(const struct nhop_object *nh, char *buf, size_t bufsize) { #ifdef ROUTE_MPATH if (NH_IS_NHGRP(nh)) return (nhgrp_print_buf((const struct nhgrp_object *)nh, buf, bufsize)); else #endif return (nhop_print_buf(nh, buf, bufsize)); } /* * 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); } diff --git a/sys/net/route/route_ctl.c b/sys/net/route/route_ctl.c index e2e48302a8c2..35c817cb93c5 100644 --- a/sys/net/route/route_ctl.c +++ b/sys/net/route/route_ctl.c @@ -1,1514 +1,1579 @@ /*- * 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 #include #include #include #include #define DEBUG_MOD_NAME route_ctl #define DEBUG_MAX_LEVEL LOG_DEBUG #include _DECLARE_DEBUG(LOG_INFO); /* * This file contains control plane routing tables functions. * * All functions assumes they are called in net epoch. */ union sockaddr_union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; char _buf[32]; }; static int add_route_byinfo(struct rib_head *rnh, struct rt_addrinfo *info, struct rib_cmd_info *rc); static int change_route_byinfo(struct rib_head *rnh, struct rtentry *rt, struct rt_addrinfo *info, struct route_nhop_data *nhd_orig, struct rib_cmd_info *rc); static int add_route_flags(struct rib_head *rnh, struct rtentry *rt, struct route_nhop_data *rnd_add, int op_flags, struct rib_cmd_info *rc); #ifdef ROUTE_MPATH static int add_route_flags_mpath(struct rib_head *rnh, struct rtentry *rt, struct route_nhop_data *rnd_add, struct route_nhop_data *rnd_orig, int op_flags, struct rib_cmd_info *rc); #endif static int add_route(struct rib_head *rnh, struct rtentry *rt, struct route_nhop_data *rnd, struct rib_cmd_info *rc); static int delete_route(struct rib_head *rnh, struct rtentry *rt, struct rib_cmd_info *rc); static int rt_delete_conditional(struct rib_head *rnh, struct rtentry *rt, int prio, rib_filter_f_t *cb, void *cbdata, struct rib_cmd_info *rc); static int get_prio_from_info(const struct rt_addrinfo *info); static int nhop_get_prio(const struct nhop_object *nh); #ifdef ROUTE_MPATH static bool rib_can_multipath(struct rib_head *rh); #endif /* Per-vnet multipath routing configuration */ SYSCTL_DECL(_net_route); #define V_rib_route_multipath VNET(rib_route_multipath) #ifdef ROUTE_MPATH #define _MP_FLAGS CTLFLAG_RW #else #define _MP_FLAGS CTLFLAG_RD #endif VNET_DEFINE(u_int, rib_route_multipath) = 1; SYSCTL_UINT(_net_route, OID_AUTO, multipath, _MP_FLAGS | CTLFLAG_VNET, &VNET_NAME(rib_route_multipath), 0, "Enable route multipath"); #undef _MP_FLAGS #ifdef ROUTE_MPATH VNET_DEFINE(u_int, fib_hash_outbound) = 0; SYSCTL_UINT(_net_route, OID_AUTO, hash_outbound, CTLFLAG_RD | CTLFLAG_VNET, &VNET_NAME(fib_hash_outbound), 0, "Compute flowid for locally-originated packets"); /* Default entropy to add to the hash calculation for the outbound connections*/ uint8_t mpath_entropy_key[MPATH_ENTROPY_KEY_LEN] = { 0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2, 0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0, 0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4, 0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c, 0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa, }; #endif #if defined(INET) && defined(INET6) FEATURE(ipv4_rfc5549_support, "Route IPv4 packets via IPv6 nexthops"); #define V_rib_route_ipv6_nexthop VNET(rib_route_ipv6_nexthop) VNET_DEFINE(u_int, rib_route_ipv6_nexthop) = 1; SYSCTL_UINT(_net_route, OID_AUTO, ipv6_nexthop, CTLFLAG_RW | CTLFLAG_VNET, &VNET_NAME(rib_route_ipv6_nexthop), 0, "Enable IPv4 route via IPv6 Next Hop address"); #endif /* Debug bits */ SYSCTL_NODE(_net_route, OID_AUTO, debug, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, ""); static struct rib_head * get_rnh(uint32_t fibnum, const struct rt_addrinfo *info) { struct rib_head *rnh; struct sockaddr *dst; KASSERT((fibnum < rt_numfibs), ("rib_add_route: bad fibnum")); dst = info->rti_info[RTAX_DST]; rnh = rt_tables_get_rnh(fibnum, dst->sa_family); return (rnh); } #if defined(INET) && defined(INET6) static bool rib_can_ipv6_nexthop_address(struct rib_head *rh) { int result; CURVNET_SET(rh->rib_vnet); result = !!V_rib_route_ipv6_nexthop; CURVNET_RESTORE(); return (result); } #endif #ifdef ROUTE_MPATH static bool rib_can_multipath(struct rib_head *rh) { int result; CURVNET_SET(rh->rib_vnet); result = !!V_rib_route_multipath; CURVNET_RESTORE(); return (result); } /* * Check is nhop is multipath-eligible. * Avoid nhops without gateways and redirects. * * Returns 1 for multipath-eligible nexthop, * 0 otherwise. */ bool nhop_can_multipath(const struct nhop_object *nh) { if ((nh->nh_flags & NHF_MULTIPATH) != 0) return (1); if ((nh->nh_flags & NHF_GATEWAY) == 0) return (0); if ((nh->nh_flags & NHF_REDIRECT) != 0) return (0); return (1); } #endif static int get_info_weight(const struct rt_addrinfo *info, uint32_t default_weight) { uint32_t weight; if (info->rti_mflags & RTV_WEIGHT) weight = info->rti_rmx->rmx_weight; else weight = default_weight; /* Keep upper 1 byte for adm distance purposes */ if (weight > RT_MAX_WEIGHT) weight = RT_MAX_WEIGHT; else if (weight == 0) weight = default_weight; return (weight); } /* * File-local concept for distingushing between the normal and * RTF_PINNED routes tha can override the "normal" one. */ #define NH_PRIORITY_HIGH 2 #define NH_PRIORITY_NORMAL 1 static int get_prio_from_info(const struct rt_addrinfo *info) { if (info->rti_flags & RTF_PINNED) return (NH_PRIORITY_HIGH); return (NH_PRIORITY_NORMAL); } static int nhop_get_prio(const struct nhop_object *nh) { if (NH_IS_PINNED(nh)) return (NH_PRIORITY_HIGH); return (NH_PRIORITY_NORMAL); } /* * Check if specified @gw matches gw data in the nexthop @nh. * * Returns true if matches, false otherwise. */ bool match_nhop_gw(const struct nhop_object *nh, const struct sockaddr *gw) { if (nh->gw_sa.sa_family != gw->sa_family) return (false); switch (gw->sa_family) { case AF_INET: return (nh->gw4_sa.sin_addr.s_addr == ((const struct sockaddr_in *)gw)->sin_addr.s_addr); case AF_INET6: { const struct sockaddr_in6 *gw6; gw6 = (const struct sockaddr_in6 *)gw; /* * Currently (2020-09) IPv6 gws in kernel have their * scope embedded. Once this becomes false, this code * has to be revisited. */ if (IN6_ARE_ADDR_EQUAL(&nh->gw6_sa.sin6_addr, &gw6->sin6_addr)) return (true); return (false); } case AF_LINK: { const struct sockaddr_dl *sdl; sdl = (const struct sockaddr_dl *)gw; return (nh->gwl_sa.sdl_index == sdl->sdl_index); } default: return (memcmp(&nh->gw_sa, gw, nh->gw_sa.sa_len) == 0); } /* NOTREACHED */ return (false); } struct gw_filter_data { const struct sockaddr *gw; int count; }; static int gw_fulter_func(const struct rtentry *rt, const struct nhop_object *nh, void *_data) { struct gw_filter_data *gwd = (struct gw_filter_data *)_data; /* Return only first match to make rtsock happy */ if (match_nhop_gw(nh, gwd->gw) && gwd->count++ == 0) return (1); return (0); } /* * Checks if data in @info matches nexhop @nh. * * Returns 0 on success, * ESRCH if not matched, * ENOENT if filter function returned false */ int check_info_match_nhop(const struct rt_addrinfo *info, const struct rtentry *rt, const struct nhop_object *nh) { const struct sockaddr *gw = info->rti_info[RTAX_GATEWAY]; if (info->rti_filter != NULL) { if (info->rti_filter(rt, nh, info->rti_filterdata) == 0) return (ENOENT); else return (0); } if ((gw != NULL) && !match_nhop_gw(nh, gw)) return (ESRCH); return (0); } /* * Runs exact prefix match based on @dst and @netmask. * Returns matched @rtentry if found or NULL. * If rtentry was found, saves nexthop / weight value into @rnd. */ static struct rtentry * lookup_prefix_bysa(struct rib_head *rnh, const struct sockaddr *dst, const struct sockaddr *netmask, struct route_nhop_data *rnd) { struct rtentry *rt; RIB_LOCK_ASSERT(rnh); rt = (struct rtentry *)rnh->rnh_lookup(dst, netmask, &rnh->head); if (rt != NULL) { rnd->rnd_nhop = rt->rt_nhop; rnd->rnd_weight = rt->rt_weight; } else { rnd->rnd_nhop = NULL; rnd->rnd_weight = 0; } return (rt); } struct rtentry * lookup_prefix_rt(struct rib_head *rnh, const struct rtentry *rt, struct route_nhop_data *rnd) { return (lookup_prefix_bysa(rnh, rt_key_const(rt), rt_mask_const(rt), rnd)); } /* * Runs exact prefix match based on dst/netmask from @info. * Assumes RIB lock is held. * Returns matched @rtentry if found or NULL. * If rtentry was found, saves nexthop / weight value into @rnd. */ struct rtentry * lookup_prefix(struct rib_head *rnh, const struct rt_addrinfo *info, struct route_nhop_data *rnd) { struct rtentry *rt; rt = lookup_prefix_bysa(rnh, info->rti_info[RTAX_DST], info->rti_info[RTAX_NETMASK], rnd); return (rt); } static bool fill_pxmask_family(int family, int plen, struct sockaddr *_dst, struct sockaddr **pmask) { if (plen == -1) { *pmask = NULL; return (true); } switch (family) { #ifdef INET case AF_INET: { struct sockaddr_in *mask = (struct sockaddr_in *)(*pmask); struct sockaddr_in *dst= (struct sockaddr_in *)_dst; memset(mask, 0, sizeof(*mask)); mask->sin_family = family; mask->sin_len = sizeof(*mask); if (plen == 32) *pmask = NULL; else if (plen > 32 || plen < 0) return (false); else { uint32_t daddr, maddr; maddr = htonl(plen ? ~((1 << (32 - plen)) - 1) : 0); mask->sin_addr.s_addr = maddr; daddr = dst->sin_addr.s_addr; daddr = htonl(ntohl(daddr) & ntohl(maddr)); dst->sin_addr.s_addr = daddr; } return (true); } break; #endif #ifdef INET6 case AF_INET6: { struct sockaddr_in6 *mask = (struct sockaddr_in6 *)(*pmask); struct sockaddr_in6 *dst = (struct sockaddr_in6 *)_dst; memset(mask, 0, sizeof(*mask)); mask->sin6_family = family; mask->sin6_len = sizeof(*mask); if (plen == 128) *pmask = NULL; else if (plen > 128 || plen < 0) return (false); else { ip6_writemask(&mask->sin6_addr, plen); IN6_MASK_ADDR(&dst->sin6_addr, &mask->sin6_addr); } return (true); } break; #endif } return (false); } /* * Attempts to add @dst/plen prefix with nexthop/nexhopgroup data @rnd * to the routing table. * * @fibnum: rtable id to insert route to * @dst: verified kernel-originated sockaddr, can be masked if plen non-empty * @plen: prefix length (or -1 if host route or not applicable for AF) * @op_flags: combination of RTM_F_ flags * @rc: storage to report operation result * * Returns 0 on success. */ int rib_add_route_px(uint32_t fibnum, struct sockaddr *dst, int plen, struct route_nhop_data *rnd, int op_flags, struct rib_cmd_info *rc) { union sockaddr_union mask_storage; struct sockaddr *netmask = &mask_storage.sa; struct rtentry *rt; NET_EPOCH_ASSERT(); bzero(rc, sizeof(struct rib_cmd_info)); rc->rc_cmd = RTM_ADD; struct rib_head *rnh = rt_tables_get_rnh(fibnum, dst->sa_family); if (rnh == NULL) return (EAFNOSUPPORT); if (!fill_pxmask_family(dst->sa_family, plen, dst, &netmask)) { FIB_RH_LOG(LOG_DEBUG, rnh, "error: invalid plen %d", plen); return (EINVAL); } if (op_flags & RTM_F_CREATE) { if ((rt = rt_alloc(rnh, dst, netmask)) == NULL) { FIB_RH_LOG(LOG_INFO, rnh, "rtentry allocation failed"); return (ENOMEM); } } else { struct route_nhop_data rnd_tmp; rt = lookup_prefix_bysa(rnh, dst, netmask, &rnd_tmp); if (rt == NULL) return (ESRCH); } #if DEBUG_MAX_LEVEL >= LOG_DEBUG2 { char nhbuf[NHOP_PRINT_BUFSIZE], rtbuf[NHOP_PRINT_BUFSIZE]; nhop_print_buf_any(rnd->rnd_nhop, nhbuf, sizeof(nhbuf)); rt_print_buf(rt, rtbuf, sizeof(rtbuf)); FIB_RH_LOG(LOG_DEBUG2, rnh, "request %s -> %s", rtbuf, nhbuf); } #endif return (add_route_flags(rnh, rt, rnd, op_flags, rc)); } /* * Attempts to delete @dst/plen prefix matching gateway @gw from the * routing rable. * * @fibnum: rtable id to remove route from * @dst: verified kernel-originated sockaddr, can be masked if plen non-empty * @plen: prefix length (or -1 if host route or not applicable for AF) * @gw: gateway to match * @op_flags: combination of RTM_F_ flags * @rc: storage to report operation result * * Returns 0 on success. */ int rib_del_route_px_gw(uint32_t fibnum, struct sockaddr *dst, int plen, const struct sockaddr *gw, int op_flags, struct rib_cmd_info *rc) { struct gw_filter_data gwd = { .gw = gw }; return (rib_del_route_px(fibnum, dst, plen, gw_fulter_func, &gwd, op_flags, rc)); } /* * Attempts to delete @dst/plen prefix matching @filter_func from the * routing rable. * * @fibnum: rtable id to remove route from * @dst: verified kernel-originated sockaddr, can be masked if plen non-empty * @plen: prefix length (or -1 if host route or not applicable for AF) * @filter_func: func to be called for each nexthop of the prefix for matching * @filter_arg: argument to pass to @filter_func * @op_flags: combination of RTM_F_ flags * @rc: storage to report operation result * * Returns 0 on success. */ int rib_del_route_px(uint32_t fibnum, struct sockaddr *dst, int plen, rib_filter_f_t *filter_func, void *filter_arg, int op_flags, struct rib_cmd_info *rc) { union sockaddr_union mask_storage; struct sockaddr *netmask = &mask_storage.sa; int error; NET_EPOCH_ASSERT(); bzero(rc, sizeof(struct rib_cmd_info)); rc->rc_cmd = RTM_DELETE; struct rib_head *rnh = rt_tables_get_rnh(fibnum, dst->sa_family); if (rnh == NULL) return (EAFNOSUPPORT); if (dst->sa_len > sizeof(mask_storage)) { FIB_RH_LOG(LOG_DEBUG, rnh, "error: dst->sa_len too big: %d", dst->sa_len); return (EINVAL); } if (!fill_pxmask_family(dst->sa_family, plen, dst, &netmask)) { FIB_RH_LOG(LOG_DEBUG, rnh, "error: invalid plen %d", plen); return (EINVAL); } int prio = (op_flags & RTM_F_FORCE) ? NH_PRIORITY_HIGH : NH_PRIORITY_NORMAL; RIB_WLOCK(rnh); struct route_nhop_data rnd; struct rtentry *rt = lookup_prefix_bysa(rnh, dst, netmask, &rnd); if (rt != NULL) { error = rt_delete_conditional(rnh, rt, prio, filter_func, filter_arg, rc); } else error = ESRCH; RIB_WUNLOCK(rnh); if (error != 0) return (error); rib_notify(rnh, RIB_NOTIFY_DELAYED, rc); if (rc->rc_cmd == RTM_DELETE) rt_free(rc->rc_rt); #ifdef ROUTE_MPATH else { /* * Deleting 1 path may result in RTM_CHANGE to * a different mpath group/nhop. * Free old mpath group. */ nhop_free_any(rc->rc_nh_old); } #endif return (0); } +/* + * Tries to copy route @rt from one rtable to the rtable specified by @dst_rh. + * @rt: route to copy. + * @rnd_src: nhop and weight. Multipath routes are not supported + * @rh_dst: target rtable. + * @rc: operation result storage + * + * Return 0 on success. + */ +int +rib_copy_route(struct rtentry *rt, const struct route_nhop_data *rnd_src, + struct rib_head *rh_dst, struct rib_cmd_info *rc) +{ + struct nhop_object *nh_src = rnd_src->rnd_nhop; + int error; + + MPASS((nh_src->nh_flags & NHF_MULTIPATH) == 0); + +#if DEBUG_MAX_LEVEL >= LOG_DEBUG2 + char nhbuf[NHOP_PRINT_BUFSIZE], rtbuf[NHOP_PRINT_BUFSIZE]; + nhop_print_buf_any(nh_src, nhbuf, sizeof(nhbuf)); + rt_print_buf(rt, rtbuf, sizeof(rtbuf)); + FIB_RH_LOG(LOG_DEBUG2, rh_dst, "copying %s -> %s from fib %u", + rtbuf, nhbuf, nhop_get_fibnum(nh_src)); +#endif + struct nhop_object *nh = nhop_alloc(rh_dst->rib_fibnum, rh_dst->rib_family); + if (nh == NULL) { + FIB_RH_LOG(LOG_INFO, rh_dst, "unable to allocate new nexthop"); + return (ENOMEM); + } + nhop_copy(nh, rnd_src->rnd_nhop); + nhop_set_fibnum(nh, rh_dst->rib_fibnum); + nh = nhop_get_nhop_internal(rh_dst, nh, &error); + if (error != 0) { + FIB_RH_LOG(LOG_INFO, rh_dst, + "unable to finalize new nexthop: error %d", error); + return (ENOMEM); + } + + struct rtentry *rt_new = rt_alloc(rh_dst, rt_key(rt), rt_mask(rt)); + if (rt_new == NULL) { + FIB_RH_LOG(LOG_INFO, rh_dst, "unable to create new rtentry"); + nhop_free(nh); + return (ENOMEM); + } + + struct route_nhop_data rnd = { + .rnd_nhop = nh, + .rnd_weight = rnd_src->rnd_weight + }; + int op_flags = RTM_F_CREATE | (NH_IS_PINNED(nh) ? RTM_F_FORCE : 0); + error = add_route_flags(rh_dst, rt_new, &rnd, op_flags, rc); + + if (error != 0) { +#if DEBUG_MAX_LEVEL >= LOG_DEBUG + char buf[NHOP_PRINT_BUFSIZE]; + rt_print_buf(rt_new, buf, sizeof(buf)); + FIB_RH_LOG(LOG_DEBUG, rh_dst, "Unable to add route %s: error %d", buf, error); +#endif + nhop_free(nh); + rt_free_immediate(rt_new); + } + return (error); +} + /* * Adds route defined by @info into the kernel table specified by @fibnum and * sa_family in @info->rti_info[RTAX_DST]. * * Returns 0 on success and fills in operation metadata into @rc. */ int rib_add_route(uint32_t fibnum, struct rt_addrinfo *info, struct rib_cmd_info *rc) { struct rib_head *rnh; int error; NET_EPOCH_ASSERT(); rnh = get_rnh(fibnum, info); if (rnh == NULL) return (EAFNOSUPPORT); /* * Check consistency between RTF_HOST flag and netmask * existence. */ if (info->rti_flags & RTF_HOST) info->rti_info[RTAX_NETMASK] = NULL; else if (info->rti_info[RTAX_NETMASK] == NULL) { FIB_RH_LOG(LOG_DEBUG, rnh, "error: no RTF_HOST and empty netmask"); return (EINVAL); } bzero(rc, sizeof(struct rib_cmd_info)); rc->rc_cmd = RTM_ADD; error = add_route_byinfo(rnh, info, rc); if (error == 0) rib_notify(rnh, RIB_NOTIFY_DELAYED, rc); return (error); } /* * Checks if @dst and @gateway is valid combination. * * Returns true if is valid, false otherwise. */ static bool check_gateway(struct rib_head *rnh, struct sockaddr *dst, struct sockaddr *gateway) { if (dst->sa_family == gateway->sa_family) return (true); else if (gateway->sa_family == AF_UNSPEC) return (true); else if (gateway->sa_family == AF_LINK) return (true); #if defined(INET) && defined(INET6) else if (dst->sa_family == AF_INET && gateway->sa_family == AF_INET6 && rib_can_ipv6_nexthop_address(rnh)) return (true); #endif else return (false); } static int add_route_byinfo(struct rib_head *rnh, struct rt_addrinfo *info, struct rib_cmd_info *rc) { struct route_nhop_data rnd_add; struct nhop_object *nh; struct rtentry *rt; struct sockaddr *dst, *gateway, *netmask; int error; dst = info->rti_info[RTAX_DST]; gateway = info->rti_info[RTAX_GATEWAY]; netmask = info->rti_info[RTAX_NETMASK]; if ((info->rti_flags & RTF_GATEWAY) && !gateway) { FIB_RH_LOG(LOG_DEBUG, rnh, "error: RTF_GATEWAY set with empty gw"); return (EINVAL); } if (dst && gateway && !check_gateway(rnh, dst, gateway)) { FIB_RH_LOG(LOG_DEBUG, rnh, "error: invalid dst/gateway family combination (%d, %d)", dst->sa_family, gateway->sa_family); return (EINVAL); } if (dst->sa_len > sizeof(((struct rtentry *)NULL)->rt_dstb)) { FIB_RH_LOG(LOG_DEBUG, rnh, "error: dst->sa_len too large: %d", dst->sa_len); return (EINVAL); } if (info->rti_ifa == NULL) { error = rt_getifa_fib(info, rnh->rib_fibnum); if (error) return (error); } if ((rt = rt_alloc(rnh, dst, netmask)) == NULL) return (ENOBUFS); error = nhop_create_from_info(rnh, info, &nh); if (error != 0) { rt_free_immediate(rt); return (error); } rnd_add.rnd_nhop = nh; rnd_add.rnd_weight = get_info_weight(info, RT_DEFAULT_WEIGHT); int op_flags = RTM_F_CREATE; if (get_prio_from_info(info) == NH_PRIORITY_HIGH) op_flags |= RTM_F_FORCE; else op_flags |= RTM_F_APPEND; return (add_route_flags(rnh, rt, &rnd_add, op_flags, rc)); } static int add_route_flags(struct rib_head *rnh, struct rtentry *rt, struct route_nhop_data *rnd_add, int op_flags, struct rib_cmd_info *rc) { struct route_nhop_data rnd_orig; struct nhop_object *nh; struct rtentry *rt_orig; int error = 0; nh = rnd_add->rnd_nhop; RIB_WLOCK(rnh); rt_orig = lookup_prefix_rt(rnh, rt, &rnd_orig); if (rt_orig == NULL) { if (op_flags & RTM_F_CREATE) error = add_route(rnh, rt, rnd_add, rc); else error = ENOENT; // no entry but creation was not required RIB_WUNLOCK(rnh); if (error != 0) goto out; return (0); } if (op_flags & RTM_F_EXCL) { /* We have existing route in the RIB but not allowed to replace. */ RIB_WUNLOCK(rnh); error = EEXIST; goto out; } /* Now either append or replace */ if (op_flags & RTM_F_REPLACE) { if (nhop_get_prio(rnd_orig.rnd_nhop) > nhop_get_prio(rnd_add->rnd_nhop)) { /* Old path is "better" (e.g. has PINNED flag set) */ error = EEXIST; goto out; } change_route(rnh, rt_orig, rnd_add, rc); RIB_WUNLOCK(rnh); nh = rc->rc_nh_old; goto out; } RIB_WUNLOCK(rnh); #ifdef ROUTE_MPATH if ((op_flags & RTM_F_APPEND) && rib_can_multipath(rnh) && nhop_can_multipath(rnd_add->rnd_nhop) && nhop_can_multipath(rnd_orig.rnd_nhop)) { for (int i = 0; i < RIB_MAX_RETRIES; i++) { error = add_route_flags_mpath(rnh, rt, rnd_add, &rnd_orig, op_flags, rc); if (error != EAGAIN) break; RTSTAT_INC(rts_add_retry); } /* * Original nhop reference is unused in any case. */ nhop_free_any(rnd_add->rnd_nhop); if (op_flags & RTM_F_CREATE) { if (error != 0 || rc->rc_cmd != RTM_ADD) rt_free_immediate(rt); } return (error); } #endif /* Out of options - free state and return error */ error = EEXIST; out: if (op_flags & RTM_F_CREATE) rt_free_immediate(rt); nhop_free_any(nh); return (error); } #ifdef ROUTE_MPATH static int add_route_flags_mpath(struct rib_head *rnh, struct rtentry *rt, struct route_nhop_data *rnd_add, struct route_nhop_data *rnd_orig, int op_flags, struct rib_cmd_info *rc) { RIB_RLOCK_TRACKER; struct route_nhop_data rnd_new; int error = 0; error = nhgrp_get_addition_group(rnh, rnd_orig, rnd_add, &rnd_new); if (error != 0) { if (error == EAGAIN) { /* * Group creation failed, most probably because * @rnd_orig data got scheduled for deletion. * Refresh @rnd_orig data and retry. */ RIB_RLOCK(rnh); lookup_prefix_rt(rnh, rt, rnd_orig); RIB_RUNLOCK(rnh); if (rnd_orig == NULL && !(op_flags & RTM_F_CREATE)) { /* In this iteration route doesn't exist */ error = ENOENT; } } return (error); } error = change_route_conditional(rnh, rt, rnd_orig, &rnd_new, rc); if (error != 0) return (error); if (V_fib_hash_outbound == 0 && NH_IS_NHGRP(rc->rc_nh_new)) { /* * First multipath route got installed. Enable local * outbound connections hashing. */ if (bootverbose) printf("FIB: enabled flowid calculation for locally-originated packets\n"); V_fib_hash_outbound = 1; } return (0); } #endif /* * Removes route defined by @info from the kernel table specified by @fibnum and * sa_family in @info->rti_info[RTAX_DST]. * * Returns 0 on success and fills in operation metadata into @rc. */ int rib_del_route(uint32_t fibnum, struct rt_addrinfo *info, struct rib_cmd_info *rc) { struct rib_head *rnh; struct sockaddr *dst, *netmask; struct sockaddr_storage mdst; int error; NET_EPOCH_ASSERT(); rnh = get_rnh(fibnum, info); if (rnh == NULL) return (EAFNOSUPPORT); bzero(rc, sizeof(struct rib_cmd_info)); rc->rc_cmd = RTM_DELETE; dst = info->rti_info[RTAX_DST]; netmask = info->rti_info[RTAX_NETMASK]; if (netmask != NULL) { /* Ensure @dst is always properly masked */ if (dst->sa_len > sizeof(mdst)) { FIB_RH_LOG(LOG_DEBUG, rnh, "error: dst->sa_len too large"); return (EINVAL); } rt_maskedcopy(dst, (struct sockaddr *)&mdst, netmask); dst = (struct sockaddr *)&mdst; } rib_filter_f_t *filter_func = NULL; void *filter_arg = NULL; struct gw_filter_data gwd = { .gw = info->rti_info[RTAX_GATEWAY] }; if (info->rti_filter != NULL) { filter_func = info->rti_filter; filter_arg = info->rti_filterdata; } else if (gwd.gw != NULL) { filter_func = gw_fulter_func; filter_arg = &gwd; } int prio = get_prio_from_info(info); RIB_WLOCK(rnh); struct route_nhop_data rnd; struct rtentry *rt = lookup_prefix_bysa(rnh, dst, netmask, &rnd); if (rt != NULL) { error = rt_delete_conditional(rnh, rt, prio, filter_func, filter_arg, rc); } else error = ESRCH; RIB_WUNLOCK(rnh); if (error != 0) return (error); rib_notify(rnh, RIB_NOTIFY_DELAYED, rc); if (rc->rc_cmd == RTM_DELETE) rt_free(rc->rc_rt); #ifdef ROUTE_MPATH else { /* * Deleting 1 path may result in RTM_CHANGE to * a different mpath group/nhop. * Free old mpath group. */ nhop_free_any(rc->rc_nh_old); } #endif return (0); } /* * Conditionally unlinks rtentry paths from @rnh matching @cb. * Returns 0 on success with operation result stored in @rc. * On error, returns: * ESRCH - if prefix was not found or filter function failed to match * EADDRINUSE - if trying to delete higher priority route. */ static int rt_delete_conditional(struct rib_head *rnh, struct rtentry *rt, int prio, rib_filter_f_t *cb, void *cbdata, struct rib_cmd_info *rc) { struct nhop_object *nh = rt->rt_nhop; #ifdef ROUTE_MPATH if (NH_IS_NHGRP(nh)) { struct nhgrp_object *nhg = (struct nhgrp_object *)nh; struct route_nhop_data rnd; int error; if (cb == NULL) return (ESRCH); error = nhgrp_get_filtered_group(rnh, rt, nhg, cb, cbdata, &rnd); if (error == 0) { if (rnd.rnd_nhgrp == nhg) { /* No match, unreference new group and return. */ nhop_free_any(rnd.rnd_nhop); return (ESRCH); } error = change_route(rnh, rt, &rnd, rc); } return (error); } #endif if (cb != NULL && !cb(rt, nh, cbdata)) return (ESRCH); if (prio < nhop_get_prio(nh)) return (EADDRINUSE); return (delete_route(rnh, rt, rc)); } int rib_change_route(uint32_t fibnum, struct rt_addrinfo *info, struct rib_cmd_info *rc) { RIB_RLOCK_TRACKER; struct route_nhop_data rnd_orig; struct rib_head *rnh; struct rtentry *rt; int error; NET_EPOCH_ASSERT(); rnh = get_rnh(fibnum, info); if (rnh == NULL) return (EAFNOSUPPORT); bzero(rc, sizeof(struct rib_cmd_info)); rc->rc_cmd = RTM_CHANGE; /* Check if updated gateway exists */ if ((info->rti_flags & RTF_GATEWAY) && (info->rti_info[RTAX_GATEWAY] == NULL)) { /* * route(8) adds RTF_GATEWAY flag if -interface is not set. * Remove RTF_GATEWAY to enforce consistency and maintain * compatibility.. */ info->rti_flags &= ~RTF_GATEWAY; } /* * route change is done in multiple steps, with dropping and * reacquiring lock. In the situations with multiple processes * changes the same route in can lead to the case when route * is changed between the steps. Address it by retrying the operation * multiple times before failing. */ RIB_RLOCK(rnh); rt = (struct rtentry *)rnh->rnh_lookup(info->rti_info[RTAX_DST], info->rti_info[RTAX_NETMASK], &rnh->head); if (rt == NULL) { RIB_RUNLOCK(rnh); return (ESRCH); } rnd_orig.rnd_nhop = rt->rt_nhop; rnd_orig.rnd_weight = rt->rt_weight; RIB_RUNLOCK(rnh); for (int i = 0; i < RIB_MAX_RETRIES; i++) { error = change_route_byinfo(rnh, rt, info, &rnd_orig, rc); if (error != EAGAIN) break; } return (error); } static int change_nhop(struct rib_head *rnh, struct rt_addrinfo *info, struct nhop_object *nh_orig, struct nhop_object **nh_new) { int error; /* * New gateway could require new ifaddr, ifp; * flags may also be different; ifp may be specified * by ll sockaddr when protocol address is ambiguous */ if (((nh_orig->nh_flags & NHF_GATEWAY) && info->rti_info[RTAX_GATEWAY] != NULL) || info->rti_info[RTAX_IFP] != NULL || (info->rti_info[RTAX_IFA] != NULL && !sa_equal(info->rti_info[RTAX_IFA], nh_orig->nh_ifa->ifa_addr))) { error = rt_getifa_fib(info, rnh->rib_fibnum); if (error != 0) { info->rti_ifa = NULL; return (error); } } error = nhop_create_from_nhop(rnh, nh_orig, info, nh_new); info->rti_ifa = NULL; return (error); } #ifdef ROUTE_MPATH static int change_mpath_route(struct rib_head *rnh, struct rtentry *rt, struct rt_addrinfo *info, struct route_nhop_data *rnd_orig, struct rib_cmd_info *rc) { int error = 0, found_idx = 0; struct nhop_object *nh_orig = NULL, *nh_new; struct route_nhop_data rnd_new = {}; const struct weightened_nhop *wn = NULL; struct weightened_nhop *wn_new; uint32_t num_nhops; wn = nhgrp_get_nhops(rnd_orig->rnd_nhgrp, &num_nhops); for (int i = 0; i < num_nhops; i++) { if (check_info_match_nhop(info, NULL, wn[i].nh) == 0) { nh_orig = wn[i].nh; found_idx = i; break; } } if (nh_orig == NULL) return (ESRCH); error = change_nhop(rnh, info, nh_orig, &nh_new); if (error != 0) return (error); wn_new = mallocarray(num_nhops, sizeof(struct weightened_nhop), M_TEMP, M_NOWAIT | M_ZERO); if (wn_new == NULL) { nhop_free(nh_new); return (EAGAIN); } memcpy(wn_new, wn, num_nhops * sizeof(struct weightened_nhop)); wn_new[found_idx].nh = nh_new; wn_new[found_idx].weight = get_info_weight(info, wn[found_idx].weight); error = nhgrp_get_group(rnh, wn_new, num_nhops, &rnd_new.rnd_nhgrp); nhop_free(nh_new); free(wn_new, M_TEMP); if (error != 0) return (error); error = change_route_conditional(rnh, rt, rnd_orig, &rnd_new, rc); return (error); } #endif static int change_route_byinfo(struct rib_head *rnh, struct rtentry *rt, struct rt_addrinfo *info, struct route_nhop_data *rnd_orig, struct rib_cmd_info *rc) { int error = 0; struct nhop_object *nh_orig; struct route_nhop_data rnd_new; nh_orig = rnd_orig->rnd_nhop; if (nh_orig == NULL) return (ESRCH); #ifdef ROUTE_MPATH if (NH_IS_NHGRP(nh_orig)) return (change_mpath_route(rnh, rt, info, rnd_orig, rc)); #endif rnd_new.rnd_weight = get_info_weight(info, rnd_orig->rnd_weight); error = change_nhop(rnh, info, nh_orig, &rnd_new.rnd_nhop); if (error != 0) return (error); error = change_route_conditional(rnh, rt, rnd_orig, &rnd_new, rc); return (error); } /* * Insert @rt with nhop data from @rnd_new to @rnh. * Returns 0 on success and stores operation results in @rc. */ static int add_route(struct rib_head *rnh, struct rtentry *rt, struct route_nhop_data *rnd, struct rib_cmd_info *rc) { struct radix_node *rn; RIB_WLOCK_ASSERT(rnh); rt->rt_nhop = rnd->rnd_nhop; rt->rt_weight = rnd->rnd_weight; rn = rnh->rnh_addaddr(rt_key(rt), rt_mask_const(rt), &rnh->head, rt->rt_nodes); if (rn != NULL) { if (!NH_IS_NHGRP(rnd->rnd_nhop) && nhop_get_expire(rnd->rnd_nhop)) tmproutes_update(rnh, rt, rnd->rnd_nhop); /* Finalize notification */ rib_bump_gen(rnh); rnh->rnh_prefixes++; rc->rc_cmd = RTM_ADD; rc->rc_rt = rt; rc->rc_nh_old = NULL; rc->rc_nh_new = rnd->rnd_nhop; rc->rc_nh_weight = rnd->rnd_weight; rib_notify(rnh, RIB_NOTIFY_IMMEDIATE, rc); return (0); } /* Existing route or memory allocation failure. */ return (EEXIST); } /* * Unconditionally deletes @rt from @rnh. */ static int delete_route(struct rib_head *rnh, struct rtentry *rt, struct rib_cmd_info *rc) { RIB_WLOCK_ASSERT(rnh); /* Route deletion requested. */ struct radix_node *rn; rn = rnh->rnh_deladdr(rt_key_const(rt), rt_mask_const(rt), &rnh->head); if (rn == NULL) return (ESRCH); rt = RNTORT(rn); rt->rte_flags &= ~RTF_UP; rib_bump_gen(rnh); rnh->rnh_prefixes--; rc->rc_cmd = RTM_DELETE; rc->rc_rt = rt; rc->rc_nh_old = rt->rt_nhop; rc->rc_nh_new = NULL; rc->rc_nh_weight = rt->rt_weight; rib_notify(rnh, RIB_NOTIFY_IMMEDIATE, rc); return (0); } /* * Switch @rt nhop/weigh to the ones specified in @rnd. * Returns 0 on success. */ int change_route(struct rib_head *rnh, struct rtentry *rt, struct route_nhop_data *rnd, struct rib_cmd_info *rc) { struct nhop_object *nh_orig; RIB_WLOCK_ASSERT(rnh); nh_orig = rt->rt_nhop; if (rnd->rnd_nhop == NULL) return (delete_route(rnh, rt, rc)); /* Changing nexthop & weight to a new one */ rt->rt_nhop = rnd->rnd_nhop; rt->rt_weight = rnd->rnd_weight; if (!NH_IS_NHGRP(rnd->rnd_nhop) && nhop_get_expire(rnd->rnd_nhop)) tmproutes_update(rnh, rt, rnd->rnd_nhop); /* Finalize notification */ rib_bump_gen(rnh); rc->rc_cmd = RTM_CHANGE; rc->rc_rt = rt; rc->rc_nh_old = nh_orig; rc->rc_nh_new = rnd->rnd_nhop; rc->rc_nh_weight = rnd->rnd_weight; rib_notify(rnh, RIB_NOTIFY_IMMEDIATE, rc); return (0); } /* * Conditionally update route nhop/weight IFF data in @nhd_orig is * consistent with the current route data. * Nexthop in @nhd_new is consumed. */ int change_route_conditional(struct rib_head *rnh, struct rtentry *rt, struct route_nhop_data *rnd_orig, struct route_nhop_data *rnd_new, struct rib_cmd_info *rc) { struct rtentry *rt_new; int error = 0; #if DEBUG_MAX_LEVEL >= LOG_DEBUG2 { char buf_old[NHOP_PRINT_BUFSIZE], buf_new[NHOP_PRINT_BUFSIZE]; nhop_print_buf_any(rnd_orig->rnd_nhop, buf_old, NHOP_PRINT_BUFSIZE); nhop_print_buf_any(rnd_new->rnd_nhop, buf_new, NHOP_PRINT_BUFSIZE); FIB_LOG(LOG_DEBUG2, rnh->rib_fibnum, rnh->rib_family, "trying change %s -> %s", buf_old, buf_new); } #endif RIB_WLOCK(rnh); struct route_nhop_data rnd; rt_new = lookup_prefix_rt(rnh, rt, &rnd); if (rt_new == NULL) { if (rnd_orig->rnd_nhop == NULL) error = add_route(rnh, rt, rnd_new, rc); else { /* * Prefix does not exist, which was not our assumption. * Update @rnd_orig with the new data and return */ rnd_orig->rnd_nhop = NULL; rnd_orig->rnd_weight = 0; error = EAGAIN; } } else { /* Prefix exists, try to update */ if (rnd_orig->rnd_nhop == rt_new->rt_nhop) { /* * Nhop/mpath group hasn't changed. Flip * to the new precalculated one and return */ error = change_route(rnh, rt_new, rnd_new, rc); } else { /* Update and retry */ rnd_orig->rnd_nhop = rt_new->rt_nhop; rnd_orig->rnd_weight = rt_new->rt_weight; error = EAGAIN; } } RIB_WUNLOCK(rnh); if (error == 0) { rib_notify(rnh, RIB_NOTIFY_DELAYED, rc); if (rnd_orig->rnd_nhop != NULL) nhop_free_any(rnd_orig->rnd_nhop); } else { if (rnd_new->rnd_nhop != NULL) nhop_free_any(rnd_new->rnd_nhop); } return (error); } /* * Performs modification of routing table specificed by @action. * Table is specified by @fibnum and sa_family in @info->rti_info[RTAX_DST]. * Needs to be run in network epoch. * * Returns 0 on success and fills in @rc with action result. */ int rib_action(uint32_t fibnum, int action, struct rt_addrinfo *info, struct rib_cmd_info *rc) { int error; switch (action) { case RTM_ADD: error = rib_add_route(fibnum, info, rc); break; case RTM_DELETE: error = rib_del_route(fibnum, info, rc); break; case RTM_CHANGE: error = rib_change_route(fibnum, info, rc); break; default: error = ENOTSUP; } return (error); } struct rt_delinfo { struct rib_head *rnh; struct rtentry *head; rib_filter_f_t *filter_f; void *filter_arg; int prio; struct rib_cmd_info rc; }; /* * Conditionally unlinks rtenties or paths from radix tree based * on the callback data passed in @arg. */ static int rt_checkdelroute(struct radix_node *rn, void *arg) { struct rt_delinfo *di = (struct rt_delinfo *)arg; struct rtentry *rt = (struct rtentry *)rn; if (rt_delete_conditional(di->rnh, rt, di->prio, di->filter_f, di->filter_arg, &di->rc) != 0) return (0); /* * Add deleted rtentries to the list to GC them * after dropping the lock. * * XXX: Delayed notifications not implemented * for nexthop updates. */ if (di->rc.rc_cmd == RTM_DELETE) { /* Add to the list and return */ rt->rt_chain = di->head; di->head = rt; #ifdef ROUTE_MPATH } else { /* * RTM_CHANGE to a different nexthop or nexthop group. * Free old multipath group. */ nhop_free_any(di->rc.rc_nh_old); #endif } return (0); } /* * Iterates over a routing table specified by @fibnum and @family and * deletes elements marked by @filter_f. * @fibnum: rtable id * @family: AF_ address family * @filter_f: function returning non-zero value for items to delete * @arg: data to pass to the @filter_f function * @report: true if rtsock notification is needed. */ void rib_walk_del(u_int fibnum, int family, rib_filter_f_t *filter_f, void *filter_arg, bool report) { struct rib_head *rnh; struct rtentry *rt; struct nhop_object *nh; struct epoch_tracker et; rnh = rt_tables_get_rnh(fibnum, family); if (rnh == NULL) return; struct rt_delinfo di = { .rnh = rnh, .filter_f = filter_f, .filter_arg = filter_arg, .prio = NH_PRIORITY_NORMAL, }; NET_EPOCH_ENTER(et); RIB_WLOCK(rnh); rnh->rnh_walktree(&rnh->head, rt_checkdelroute, &di); RIB_WUNLOCK(rnh); /* We might have something to reclaim. */ bzero(&di.rc, sizeof(di.rc)); di.rc.rc_cmd = RTM_DELETE; while (di.head != NULL) { rt = di.head; di.head = rt->rt_chain; rt->rt_chain = NULL; nh = rt->rt_nhop; di.rc.rc_rt = rt; di.rc.rc_nh_old = nh; rib_notify(rnh, RIB_NOTIFY_DELAYED, &di.rc); if (report) { #ifdef ROUTE_MPATH struct nhgrp_object *nhg; const struct weightened_nhop *wn; uint32_t num_nhops; if (NH_IS_NHGRP(nh)) { nhg = (struct nhgrp_object *)nh; wn = nhgrp_get_nhops(nhg, &num_nhops); for (int i = 0; i < num_nhops; i++) rt_routemsg(RTM_DELETE, rt, wn[i].nh, fibnum); } else #endif rt_routemsg(RTM_DELETE, rt, nh, fibnum); } rt_free(rt); } NET_EPOCH_EXIT(et); } static int rt_delete_unconditional(struct radix_node *rn, void *arg) { struct rtentry *rt = RNTORT(rn); struct rib_head *rnh = (struct rib_head *)arg; rn = rnh->rnh_deladdr(rt_key(rt), rt_mask(rt), &rnh->head); if (RNTORT(rn) == rt) rt_free(rt); return (0); } /* * Removes all routes from the routing table without executing notifications. * rtentres will be removed after the end of a current epoch. */ static void rib_flush_routes(struct rib_head *rnh) { RIB_WLOCK(rnh); rnh->rnh_walktree(&rnh->head, rt_delete_unconditional, rnh); RIB_WUNLOCK(rnh); } void rib_flush_routes_family(int family) { struct rib_head *rnh; for (uint32_t fibnum = 0; fibnum < rt_numfibs; fibnum++) { if ((rnh = rt_tables_get_rnh(fibnum, family)) != NULL) rib_flush_routes(rnh); } } const char * rib_print_family(int family) { switch (family) { case AF_INET: return ("inet"); case AF_INET6: return ("inet6"); case AF_LINK: return ("link"); } return ("unknown"); } diff --git a/sys/net/route/route_ifaddrs.c b/sys/net/route/route_ifaddrs.c index 15ee13201059..a456ffa28696 100644 --- a/sys/net/route/route_ifaddrs.c +++ b/sys/net/route/route_ifaddrs.c @@ -1,198 +1,243 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1980, 1986, 1991, 1993 * The Regents of the University of California. 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * @(#)route.c 8.3.1.1 (Berkeley) 2/23/95 * $FreeBSD$ */ #include "opt_route.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Control interface address fib propagation. * By default, interface address routes are added to the fib of the interface. * Once set to non-zero, adds interface address route to all fibs. */ VNET_DEFINE(u_int, rt_add_addr_allfibs) = 0; SYSCTL_UINT(_net, OID_AUTO, add_addr_allfibs, CTLFLAG_RWTUN | CTLFLAG_VNET, &VNET_NAME(rt_add_addr_allfibs), 0, ""); /* * Executes routing tables change specified by @cmd and @info for the fib * @fibnum. Generates routing message on success. * Note: it assumes there is only single route (interface route) for the * provided prefix. * Returns 0 on success or errno. */ static int rib_handle_ifaddr_one(uint32_t fibnum, int cmd, struct rt_addrinfo *info) { struct rib_cmd_info rc; struct nhop_object *nh; int error; error = rib_action(fibnum, cmd, info, &rc); if (error == 0) { if (cmd == RTM_ADD) nh = nhop_select(rc.rc_nh_new, 0); else nh = nhop_select(rc.rc_nh_old, 0); rt_routemsg(cmd, rc.rc_rt, nh, fibnum); } return (error); } /* * Adds/deletes interface prefix specified by @info to the routing table. * If V_rt_add_addr_allfibs is set, iterates over all existing routing * tables, otherwise uses fib in @fibnum. Generates routing message for * each table. * Returns 0 on success or errno. */ int rib_handle_ifaddr_info(uint32_t fibnum, int cmd, struct rt_addrinfo *info) { int error = 0, last_error = 0; bool didwork = false; if (V_rt_add_addr_allfibs == 0) { error = rib_handle_ifaddr_one(fibnum, cmd, info); didwork = (error == 0); } else { for (fibnum = 0; fibnum < V_rt_numfibs; fibnum++) { error = rib_handle_ifaddr_one(fibnum, cmd, info); if (error == 0) didwork = true; else last_error = error; } } if (cmd == RTM_DELETE) { if (didwork) { error = 0; } else { /* we only give an error if it wasn't in any table */ error = ((info->rti_flags & RTF_HOST) ? EHOSTUNREACH : ENETUNREACH); } } else { if (last_error != 0) { /* return an error if any of them failed */ error = last_error; } } return (error); } static int ifa_maintain_loopback_route(int cmd, const char *otype, struct ifaddr *ifa, struct sockaddr *ia) { struct rib_cmd_info rc; struct epoch_tracker et; int error; struct rt_addrinfo info; struct sockaddr_dl null_sdl; struct ifnet *ifp; ifp = ifa->ifa_ifp; NET_EPOCH_ENTER(et); bzero(&info, sizeof(info)); if (cmd != RTM_DELETE) info.rti_ifp = V_loif; if (cmd == RTM_ADD) { /* explicitly specify (loopback) ifa */ if (info.rti_ifp != NULL) info.rti_ifa = ifaof_ifpforaddr(ifa->ifa_addr, info.rti_ifp); } info.rti_flags = ifa->ifa_flags | RTF_HOST | RTF_STATIC | RTF_PINNED; info.rti_info[RTAX_DST] = ia; info.rti_info[RTAX_GATEWAY] = (struct sockaddr *)&null_sdl; link_init_sdl(ifp, (struct sockaddr *)&null_sdl, ifp->if_type); error = rib_action(ifp->if_fib, cmd, &info, &rc); NET_EPOCH_EXIT(et); if (error == 0 || (cmd == RTM_ADD && error == EEXIST) || (cmd == RTM_DELETE && (error == ENOENT || error == ESRCH))) return (error); log(LOG_DEBUG, "%s: %s failed for interface %s: %u\n", __func__, otype, if_name(ifp), error); return (error); } int ifa_add_loopback_route(struct ifaddr *ifa, struct sockaddr *ia) { return (ifa_maintain_loopback_route(RTM_ADD, "insertion", ifa, ia)); } int ifa_del_loopback_route(struct ifaddr *ifa, struct sockaddr *ia) { return (ifa_maintain_loopback_route(RTM_DELETE, "deletion", ifa, ia)); } int ifa_switch_loopback_route(struct ifaddr *ifa, struct sockaddr *ia) { return (ifa_maintain_loopback_route(RTM_CHANGE, "switch", ifa, ia)); } +static bool +match_kernel_route(const struct rtentry *rt, struct nhop_object *nh) +{ + if (!NH_IS_NHGRP(nh) && (nhop_get_rtflags(nh) & RTF_PINNED) && + nh->nh_aifp->if_fib == nhop_get_fibnum(nh)) + return (true); + return (false); +} + +static int +pick_kernel_route(struct rtentry *rt, void *arg) +{ + struct nhop_object *nh = rt->rt_nhop; + struct rib_head *rh_dst = (struct rib_head *)arg; + + if (match_kernel_route(rt, nh)) { + struct rib_cmd_info rc = {}; + struct route_nhop_data rnd = { + .rnd_nhop = nh, + .rnd_weight = rt->rt_weight, + }; + rib_copy_route(rt, &rnd, rh_dst, &rc); + } + return (0); +} + +/* + * Tries to copy kernel routes matching pattern from @rh_src to @rh_dst. + * + * Note: as this function acquires locks for both @rh_src and @rh_dst, + * it needs to be called under RTABLES_LOCK() to avoid deadlocking + * with multiple ribs. + */ +void +rib_copy_kernel_routes(struct rib_head *rh_src, struct rib_head *rh_dst) +{ + struct epoch_tracker et; + + if (V_rt_add_addr_allfibs == 0) + return; + + NET_EPOCH_ENTER(et); + rib_walk_ext_internal(rh_src, false, pick_kernel_route, NULL, rh_dst); + NET_EPOCH_EXIT(et); +} diff --git a/sys/net/route/route_tables.c b/sys/net/route/route_tables.c index 54b467f9801f..f5fe4b5d2a81 100644 --- a/sys/net/route/route_tables.c +++ b/sys/net/route/route_tables.c @@ -1,349 +1,365 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1980, 1986, 1991, 1993 * The Regents of the University of California. 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. */ /************************************************************************ * Note: In this file a 'fib' is a "forwarding information base" * * Which is the new name for an in kernel routing (next hop) table. * ***********************************************************************/ #include __FBSDID("$FreeBSD$"); #include "opt_route.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Kernel config default option. */ #ifdef ROUTETABLES #if ROUTETABLES <= 0 #error "ROUTETABLES defined too low" #endif #if ROUTETABLES > RT_MAXFIBS #error "ROUTETABLES defined too big" #endif #define RT_NUMFIBS ROUTETABLES #endif /* ROUTETABLES */ /* Initialize to default if not otherwise set. */ #ifndef RT_NUMFIBS #define RT_NUMFIBS 1 #endif static void grow_rtables(uint32_t num_fibs); VNET_DEFINE_STATIC(struct sx, rtables_lock); #define V_rtables_lock VNET(rtables_lock) #define RTABLES_LOCK() sx_xlock(&V_rtables_lock) #define RTABLES_UNLOCK() sx_xunlock(&V_rtables_lock) #define RTABLES_LOCK_INIT() sx_init(&V_rtables_lock, "rtables lock") #define RTABLES_LOCK_ASSERT() sx_assert(&V_rtables_lock, SA_LOCKED) VNET_DEFINE_STATIC(struct rib_head **, rt_tables); #define V_rt_tables VNET(rt_tables) VNET_DEFINE(uint32_t, _rt_numfibs) = RT_NUMFIBS; /* * Handler for net.my_fibnum. * Returns current fib of the process. */ static int sysctl_my_fibnum(SYSCTL_HANDLER_ARGS) { int fibnum; int error; fibnum = curthread->td_proc->p_fibnum; error = sysctl_handle_int(oidp, &fibnum, 0, req); return (error); } SYSCTL_PROC(_net, OID_AUTO, my_fibnum, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, 0, &sysctl_my_fibnum, "I", "default FIB of caller"); static uint32_t normalize_num_rtables(uint32_t num_rtables) { if (num_rtables > RT_MAXFIBS) num_rtables = RT_MAXFIBS; else if (num_rtables == 0) num_rtables = 1; return (num_rtables); } /* * Sets the number of fibs in the current vnet. * Function does not allow shrinking number of rtables. */ static int sysctl_fibs(SYSCTL_HANDLER_ARGS) { uint32_t new_fibs; int error; RTABLES_LOCK(); new_fibs = V_rt_numfibs; error = sysctl_handle_32(oidp, &new_fibs, 0, req); if (error == 0) { new_fibs = normalize_num_rtables(new_fibs); if (new_fibs < V_rt_numfibs) error = ENOTCAPABLE; if (new_fibs > V_rt_numfibs) grow_rtables(new_fibs); } RTABLES_UNLOCK(); return (error); } SYSCTL_PROC(_net, OID_AUTO, fibs, CTLFLAG_VNET | CTLTYPE_U32 | CTLFLAG_RWTUN | CTLFLAG_NOFETCH | CTLFLAG_MPSAFE, NULL, 0, &sysctl_fibs, "IU", "set number of fibs"); /* * Sets fib of a current process. */ int sys_setfib(struct thread *td, struct setfib_args *uap) { int error = 0; CURVNET_SET(TD_TO_VNET(td)); if (uap->fibnum >= 0 && uap->fibnum < V_rt_numfibs) td->td_proc->p_fibnum = uap->fibnum; else error = EINVAL; CURVNET_RESTORE(); return (error); } +/* + * If required, copy interface routes from existing tables to the + * newly-created routing table. + */ +static void +populate_kernel_routes(struct rib_head **new_rt_tables, struct rib_head *rh) +{ + for (int i = 0; i < V_rt_numfibs; i++) { + struct rib_head *rh_src = new_rt_tables[i * (AF_MAX + 1) + rh->rib_family]; + if ((rh_src != NULL) && (rh_src != rh)) + rib_copy_kernel_routes(rh_src, rh); + } +} + /* * Grows up the number of routing tables in the current fib. * Function creates new index array for all rtables and allocates * remaining routing tables. */ static void grow_rtables(uint32_t num_tables) { struct domain *dom; struct rib_head **prnh, *rh; struct rib_head **new_rt_tables, **old_rt_tables; int family; RTABLES_LOCK_ASSERT(); KASSERT(num_tables >= V_rt_numfibs, ("num_tables(%u) < rt_numfibs(%u)\n", num_tables, V_rt_numfibs)); new_rt_tables = mallocarray(num_tables * (AF_MAX + 1), sizeof(void *), M_RTABLE, M_WAITOK | M_ZERO); if ((num_tables > 1) && (V_rt_add_addr_allfibs == 0)) printf("WARNING: Adding ifaddrs to all fibs has been turned off " "by default. Consider tuning %s if needed\n", "net.add_addr_allfibs"); #ifdef FIB_ALGO fib_grow_rtables(num_tables); #endif /* * Current rt_tables layout: * fib0[af0, af1, af2, .., AF_MAX]fib1[af0, af1, af2, .., Af_MAX].. * this allows to copy existing tables data by using memcpy() */ if (V_rt_tables != NULL) memcpy(new_rt_tables, V_rt_tables, V_rt_numfibs * (AF_MAX + 1) * sizeof(void *)); /* Populate the remainders */ for (dom = domains; dom; dom = dom->dom_next) { if (dom->dom_rtattach == NULL) continue; family = dom->dom_family; for (int i = 0; i < num_tables; i++) { prnh = &new_rt_tables[i * (AF_MAX + 1) + family]; if (*prnh != NULL) continue; rh = dom->dom_rtattach(i); if (rh == NULL) log(LOG_ERR, "unable to create routing table for %d.%d\n", dom->dom_family, i); + else + populate_kernel_routes(new_rt_tables, rh); *prnh = rh; } } /* * Update rtables pointer. * Ensure all writes to new_rt_tables has been completed before * switching pointer. */ atomic_thread_fence_rel(); old_rt_tables = V_rt_tables; V_rt_tables = new_rt_tables; /* Wait till all cpus see new pointers */ atomic_thread_fence_rel(); epoch_wait_preempt(net_epoch_preempt); /* Set number of fibs to a new value */ V_rt_numfibs = num_tables; #ifdef FIB_ALGO /* Attach fib algo to the new rtables */ for (dom = domains; dom; dom = dom->dom_next) { if (dom->dom_rtattach != NULL) fib_setup_family(dom->dom_family, num_tables); } #endif if (old_rt_tables != NULL) free(old_rt_tables, M_RTABLE); } static void vnet_rtables_init(const void *unused __unused) { int num_rtables_base; if (IS_DEFAULT_VNET(curvnet)) { num_rtables_base = RT_NUMFIBS; TUNABLE_INT_FETCH("net.fibs", &num_rtables_base); V_rt_numfibs = normalize_num_rtables(num_rtables_base); } else V_rt_numfibs = 1; vnet_rtzone_init(); #ifdef FIB_ALGO vnet_fib_init(); #endif RTABLES_LOCK_INIT(); RTABLES_LOCK(); grow_rtables(V_rt_numfibs); RTABLES_UNLOCK(); } VNET_SYSINIT(vnet_rtables_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_FOURTH, vnet_rtables_init, 0); #ifdef VIMAGE static void rtables_destroy(const void *unused __unused) { struct rib_head *rnh; struct domain *dom; int family; RTABLES_LOCK(); for (dom = domains; dom; dom = dom->dom_next) { if (dom->dom_rtdetach == NULL) continue; family = dom->dom_family; for (int i = 0; i < V_rt_numfibs; i++) { rnh = rt_tables_get_rnh(i, family); dom->dom_rtdetach(rnh); } } RTABLES_UNLOCK(); /* * dom_rtdetach calls rt_table_destroy(), which * schedules deletion for all rtentries, nexthops and control * structures. Wait for the destruction callbacks to fire. * Note that this should result in freeing all rtentries, but * nexthops deletions will be scheduled for the next epoch run * and will be completed after vnet teardown. */ NET_EPOCH_DRAIN_CALLBACKS(); free(V_rt_tables, M_RTABLE); vnet_rtzone_destroy(); #ifdef FIB_ALGO vnet_fib_destroy(); #endif } VNET_SYSUNINIT(rtables_destroy, SI_SUB_PROTO_DOMAIN, SI_ORDER_FIRST, rtables_destroy, 0); #endif static inline struct rib_head * rt_tables_get_rnh_ptr(uint32_t table, sa_family_t family) { struct rib_head **prnh; KASSERT(table < V_rt_numfibs, ("%s: table out of bounds (%d < %d)", __func__, table, V_rt_numfibs)); KASSERT(family < (AF_MAX + 1), ("%s: fam out of bounds (%d < %d)", __func__, family, AF_MAX + 1)); /* rnh is [fib=0][af=0]. */ prnh = V_rt_tables; /* Get the offset to the requested table and fam. */ prnh += table * (AF_MAX + 1) + family; return (*prnh); } struct rib_head * rt_tables_get_rnh(uint32_t table, sa_family_t family) { return (rt_tables_get_rnh_ptr(table, family)); } u_int rt_tables_get_gen(uint32_t table, sa_family_t family) { struct rib_head *rnh; rnh = rt_tables_get_rnh_ptr(table, family); KASSERT(rnh != NULL, ("%s: NULL rib_head pointer table %d family %d", __func__, table, family)); return (rnh->rnh_gen); } diff --git a/sys/net/route/route_var.h b/sys/net/route/route_var.h index 2cc5ffb7b17d..2f21d959387b 100644 --- a/sys/net/route/route_var.h +++ b/sys/net/route/route_var.h @@ -1,331 +1,338 @@ /*- * Copyright (c) 2015-2016 * 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. * * $FreeBSD$ */ #ifndef _NET_ROUTE_VAR_H_ #define _NET_ROUTE_VAR_H_ #ifndef RNF_NORMAL #include #endif #include #include #include /* struct sockaddr_in */ #include #include struct nh_control; /* Sets prefix-specific nexthop flags (NHF_DEFAULT, RTF/NHF_HOST, RTF_BROADCAST,..) */ typedef int rnh_set_nh_pfxflags_f_t(u_int fibnum, const struct sockaddr *addr, const struct sockaddr *mask, struct nhop_object *nh); /* Fills in family-specific details that are not yet set up (mtu, nhop type, ..) */ typedef int rnh_augment_nh_f_t(u_int fibnum, struct nhop_object *nh); struct rib_head { struct radix_head head; rn_matchaddr_f_t *rnh_matchaddr; /* longest match for sockaddr */ rn_addaddr_f_t *rnh_addaddr; /* add based on sockaddr*/ rn_deladdr_f_t *rnh_deladdr; /* remove based on sockaddr */ rn_lookup_f_t *rnh_lookup; /* exact match for sockaddr */ rn_walktree_t *rnh_walktree; /* traverse tree */ rn_walktree_from_t *rnh_walktree_from; /* traverse tree below a */ rnh_set_nh_pfxflags_f_t *rnh_set_nh_pfxflags; /* hook to alter record prior to insertion */ rt_gen_t rnh_gen; /* datapath generation counter */ int rnh_multipath; /* multipath capable ? */ struct radix_node rnh_nodes[3]; /* empty tree for common case */ struct rmlock rib_lock; /* config/data path lock */ struct radix_mask_head rmhead; /* masks radix head */ struct vnet *rib_vnet; /* vnet pointer */ int rib_family; /* AF of the rtable */ u_int rib_fibnum; /* fib number */ struct callout expire_callout; /* Callout for expiring dynamic routes */ time_t next_expire; /* Next expire run ts */ uint32_t rnh_prefixes; /* Number of prefixes */ rt_gen_t rnh_gen_rib; /* fib algo: rib generation counter */ uint32_t rib_dying:1; /* rib is detaching */ uint32_t rib_algo_fixed:1;/* fixed algorithm */ uint32_t rib_algo_init:1;/* algo init done */ struct nh_control *nh_control; /* nexthop subsystem data */ rnh_augment_nh_f_t *rnh_augment_nh;/* hook to alter nexthop prior to insertion */ CK_STAILQ_HEAD(, rib_subscription) rnh_subscribers;/* notification subscribers */ }; #define RIB_RLOCK_TRACKER struct rm_priotracker _rib_tracker -#define RIB_LOCK_INIT(rh) rm_init(&(rh)->rib_lock, "rib head lock") +#define RIB_LOCK_INIT(rh) rm_init_flags(&(rh)->rib_lock, "rib head lock", RM_DUPOK) #define RIB_LOCK_DESTROY(rh) rm_destroy(&(rh)->rib_lock) #define RIB_RLOCK(rh) rm_rlock(&(rh)->rib_lock, &_rib_tracker) #define RIB_RUNLOCK(rh) rm_runlock(&(rh)->rib_lock, &_rib_tracker) #define RIB_WLOCK(rh) rm_wlock(&(rh)->rib_lock) #define RIB_WUNLOCK(rh) rm_wunlock(&(rh)->rib_lock) #define RIB_LOCK_ASSERT(rh) rm_assert(&(rh)->rib_lock, RA_LOCKED) #define RIB_WLOCK_ASSERT(rh) rm_assert(&(rh)->rib_lock, RA_WLOCKED) /* Constants */ #define RIB_MAX_RETRIES 3 #define RT_MAXFIBS UINT16_MAX #define RIB_MAX_MPATH_WIDTH 64 /* Macro for verifying fields in af-specific 'struct route' structures */ #define CHK_STRUCT_FIELD_GENERIC(_s1, _f1, _s2, _f2) \ _Static_assert(sizeof(((_s1 *)0)->_f1) == sizeof(((_s2 *)0)->_f2), \ "Fields " #_f1 " and " #_f2 " size differs"); \ _Static_assert(__offsetof(_s1, _f1) == __offsetof(_s2, _f2), \ "Fields " #_f1 " and " #_f2 " offset differs"); #define _CHK_ROUTE_FIELD(_route_new, _field) \ CHK_STRUCT_FIELD_GENERIC(struct route, _field, _route_new, _field) #define CHK_STRUCT_ROUTE_FIELDS(_route_new) \ _CHK_ROUTE_FIELD(_route_new, ro_nh) \ _CHK_ROUTE_FIELD(_route_new, ro_lle) \ _CHK_ROUTE_FIELD(_route_new, ro_prepend)\ _CHK_ROUTE_FIELD(_route_new, ro_plen) \ _CHK_ROUTE_FIELD(_route_new, ro_flags) \ _CHK_ROUTE_FIELD(_route_new, ro_mtu) \ _CHK_ROUTE_FIELD(_route_new, spare) #define CHK_STRUCT_ROUTE_COMPAT(_ro_new, _dst_new) \ CHK_STRUCT_ROUTE_FIELDS(_ro_new); \ _Static_assert(__offsetof(struct route, ro_dst) == __offsetof(_ro_new, _dst_new),\ "ro_dst and " #_dst_new " are at different offset") static inline void rib_bump_gen(struct rib_head *rnh) { #ifdef FIB_ALGO rnh->rnh_gen_rib++; #else rnh->rnh_gen++; #endif } struct rib_head *rt_tables_get_rnh(uint32_t table, sa_family_t family); int rt_getifa_fib(struct rt_addrinfo *info, u_int fibnum); struct rib_cmd_info; VNET_PCPUSTAT_DECLARE(struct rtstat, rtstat); #define RTSTAT_ADD(name, val) \ VNET_PCPUSTAT_ADD(struct rtstat, rtstat, name, (val)) #define RTSTAT_INC(name) RTSTAT_ADD(name, 1) /* * Convert a 'struct radix_node *' to a 'struct rtentry *'. * The operation can be done safely (in this code) because a * 'struct rtentry' starts with two 'struct radix_node''s, the first * one representing leaf nodes in the routing tree, which is * what the code in radix.c passes us as a 'struct radix_node'. * * But because there are a lot of assumptions in this conversion, * do not cast explicitly, but always use the macro below. */ #define RNTORT(p) ((struct rtentry *)(p)) struct rtentry { struct radix_node rt_nodes[2]; /* tree glue, and other values */ /* * XXX struct rtentry must begin with a struct radix_node (or two!) * because the code does some casts of a 'struct radix_node *' * to a 'struct rtentry *' */ #define rt_key(r) (*((struct sockaddr **)(&(r)->rt_nodes->rn_key))) #define rt_mask(r) (*((struct sockaddr **)(&(r)->rt_nodes->rn_mask))) #define rt_key_const(r) (*((const struct sockaddr * const *)(&(r)->rt_nodes->rn_key))) #define rt_mask_const(r) (*((const struct sockaddr * const *)(&(r)->rt_nodes->rn_mask))) /* * 2 radix_node structurs above consists of 2x6 pointers, leaving * 4 pointers (32 bytes) of the second cache line on amd64. * */ struct nhop_object *rt_nhop; /* nexthop data */ union { /* * Destination address storage. * sizeof(struct sockaddr_in6) == 28, however * the dataplane-relevant part (e.g. address) lies * at offset 8..24, making the address not crossing * cacheline boundary. */ struct sockaddr_in rt_dst4; struct sockaddr_in6 rt_dst6; struct sockaddr rt_dst; char rt_dstb[28]; }; int rte_flags; /* up/down?, host/net */ u_long rt_weight; /* absolute weight */ struct rtentry *rt_chain; /* pointer to next rtentry to delete */ struct epoch_context rt_epoch_ctx; /* net epoch tracker */ }; /* * With the split between the routing entry and the nexthop, * rt_flags has to be split between these 2 entries. As rtentry * mostly contains prefix data and is thought to be generic enough * so one can transparently change the nexthop pointer w/o requiring * any other rtentry changes, most of rt_flags shifts to the particular nexthop. * / * * RTF_UP: rtentry, as an indication that it is linked. * RTF_HOST: rtentry, nhop. The latter indication is needed for the datapath * RTF_DYNAMIC: nhop, to make rtentry generic. * RTF_MODIFIED: nhop, to make rtentry generic. (legacy) * -- "native" path (nhop) properties: * RTF_GATEWAY, RTF_STATIC, RTF_PROTO1, RTF_PROTO2, RTF_PROTO3, RTF_FIXEDMTU, * RTF_PINNED, RTF_REJECT, RTF_BLACKHOLE, RTF_BROADCAST */ /* rtentry rt flag mask */ #define RTE_RT_FLAG_MASK (RTF_UP | RTF_HOST) /* route_temporal.c */ void tmproutes_update(struct rib_head *rnh, struct rtentry *rt, struct nhop_object *nh); void tmproutes_init(struct rib_head *rh); void tmproutes_destroy(struct rib_head *rh); /* route_ctl.c */ struct route_nhop_data; int change_route(struct rib_head *rnh, struct rtentry *rt, struct route_nhop_data *rnd, struct rib_cmd_info *rc); int change_route_conditional(struct rib_head *rnh, struct rtentry *rt, struct route_nhop_data *nhd_orig, struct route_nhop_data *nhd_new, struct rib_cmd_info *rc); struct rtentry *lookup_prefix(struct rib_head *rnh, const struct rt_addrinfo *info, struct route_nhop_data *rnd); struct rtentry *lookup_prefix_rt(struct rib_head *rnh, const struct rtentry *rt, struct route_nhop_data *rnd); +int rib_copy_route(struct rtentry *rt, const struct route_nhop_data *rnd_src, + struct rib_head *rh_dst, struct rib_cmd_info *rc); bool nhop_can_multipath(const struct nhop_object *nh); bool match_nhop_gw(const struct nhop_object *nh, const struct sockaddr *gw); int check_info_match_nhop(const struct rt_addrinfo *info, const struct rtentry *rt, const struct nhop_object *nh); /* route_rtentry.c */ void vnet_rtzone_init(void); void vnet_rtzone_destroy(void); void rt_free(struct rtentry *rt); void rt_free_immediate(struct rtentry *rt); struct rtentry *rt_alloc(struct rib_head *rnh, const struct sockaddr *dst, struct sockaddr *netmask); /* subscriptions */ void rib_init_subscriptions(struct rib_head *rnh); void rib_destroy_subscriptions(struct rib_head *rnh); +/* route_ifaddrs.c */ +void rib_copy_kernel_routes(struct rib_head *rh_src, struct rib_head *rh_dst); + /* Nexhops */ void nhops_init(void); int nhops_init_rib(struct rib_head *rh); void nhops_destroy_rib(struct rib_head *rh); void nhop_ref_object(struct nhop_object *nh); int nhop_try_ref_object(struct nhop_object *nh); void nhop_ref_any(struct nhop_object *nh); void nhop_free_any(struct nhop_object *nh); +struct nhop_object *nhop_get_nhop_internal(struct rib_head *rnh, + struct nhop_object *nh, int *perror); int nhop_create_from_info(struct rib_head *rnh, struct rt_addrinfo *info, struct nhop_object **nh_ret); int nhop_create_from_nhop(struct rib_head *rnh, const struct nhop_object *nh_orig, struct rt_addrinfo *info, struct nhop_object **pnh_priv); void nhops_update_ifmtu(struct rib_head *rh, struct ifnet *ifp, uint32_t mtu); int nhops_dump_sysctl(struct rib_head *rh, struct sysctl_req *w); /* MULTIPATH */ #define MPF_MULTIPATH 0x08 /* need to be consistent with NHF_MULTIPATH */ struct nhgrp_object { uint16_t nhg_flags; /* nexthop group flags */ uint8_t nhg_size; /* dataplain group size */ uint8_t spare; struct nhop_object *nhops[0]; /* nhops */ }; static inline struct nhop_object * nhop_select(struct nhop_object *nh, uint32_t flowid) { #ifdef ROUTE_MPATH if (NH_IS_NHGRP(nh)) { struct nhgrp_object *nhg = (struct nhgrp_object *)nh; nh = nhg->nhops[flowid % nhg->nhg_size]; } #endif return (nh); } struct weightened_nhop; /* mpath_ctl.c */ int add_route_mpath(struct rib_head *rnh, struct rt_addrinfo *info, struct rtentry *rt, struct route_nhop_data *rnd_add, struct route_nhop_data *rnd_orig, struct rib_cmd_info *rc); /* nhgrp.c */ int nhgrp_ctl_init(struct nh_control *ctl); void nhgrp_ctl_free(struct nh_control *ctl); void nhgrp_ctl_unlink_all(struct nh_control *ctl); /* nhgrp_ctl.c */ int nhgrp_dump_sysctl(struct rib_head *rh, struct sysctl_req *w); int nhgrp_get_group(struct rib_head *rh, struct weightened_nhop *wn, int num_nhops, struct nhgrp_object **pnhg); int nhgrp_get_filtered_group(struct rib_head *rh, const struct rtentry *rt, const struct nhgrp_object *src, rib_filter_f_t flt_func, void *flt_data, struct route_nhop_data *rnd); int nhgrp_get_addition_group(struct rib_head *rnh, struct route_nhop_data *rnd_orig, struct route_nhop_data *rnd_add, struct route_nhop_data *rnd_new); void nhgrp_ref_object(struct nhgrp_object *nhg); uint32_t nhgrp_get_idx(const struct nhgrp_object *nhg); void nhgrp_free(struct nhgrp_object *nhg); /* rtsock */ int rtsock_routemsg(int cmd, struct rtentry *rt, struct nhop_object *nh, int fibnum); int rtsock_routemsg_info(int cmd, struct rt_addrinfo *info, int fibnum); int rtsock_addrmsg(int cmd, struct ifaddr *ifa, int fibnum); /* lookup_framework.c */ void fib_grow_rtables(uint32_t new_num_tables); void fib_setup_family(int family, uint32_t num_tables); void fib_destroy_rib(struct rib_head *rh); void vnet_fib_init(void); void vnet_fib_destroy(void); /* Entropy data used for outbound hashing */ #define MPATH_ENTROPY_KEY_LEN 40 extern uint8_t mpath_entropy_key[MPATH_ENTROPY_KEY_LEN]; #endif