Changeset View
Changeset View
Standalone View
Standalone View
sys/netlink/netlink_nhop.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright (c) 2022 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 <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include "opt_inet.h" | |||||
#include "opt_inet6.h" | |||||
#include <sys/types.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/rmlock.h> | |||||
#include <sys/socket.h> | |||||
#include <sys/ck.h> | |||||
#include <net/if.h> | |||||
#include <net/if_dl.h> | |||||
#include <net/route.h> | |||||
#include <net/route/nhop.h> | |||||
#include <net/route/nhop_utils.h> | |||||
#include <net/route/route_ctl.h> | |||||
#include <net/route/route_var.h> | |||||
#include <netlink/netlink.h> | |||||
#include <netlink/netlink_ctl.h> | |||||
#include <netlink/netlink_var.h> | |||||
#include <netlink/netlink_route.h> | |||||
#define DEBUG_MOD_NAME nl_nhop | |||||
#define DEBUG_MAX_LEVEL LOG_DEBUG3 | |||||
#include <net/route/route_debug.h> | |||||
_DECLARE_DEBUG(LOG_DEBUG3); | |||||
/* | |||||
* idx -> {n:, d:, h:} | |||||
* | |||||
* | |||||
* | |||||
*/ | |||||
struct user_nhop { | |||||
uint32_t un_idx; /* Userland-provided index */ | |||||
struct nhop_object * un_nhop[3]; /* Normal, host, default */ | |||||
struct user_nhop * un_next; | |||||
struct epoch_context un_epoch_ctx; /* epoch ctl helper */ | |||||
}; | |||||
/* produce hash value for an object */ | |||||
#define unhop_hash_obj(_obj) (hash_unhop(_obj)) | |||||
/* compare two objects */ | |||||
#define unhop_cmp(_one, _two) (cmp_unhop(_one, _two)) | |||||
/* next object accessor */ | |||||
#define unhop_next(_obj) (_obj)->un_next | |||||
CHT_SLIST_DEFINE(unhop, struct user_nhop); | |||||
VNET_DEFINE_STATIC(struct unhop_head *, nl_nhop_head) = NULL; | |||||
#define V_nl_nhop_head VNET(nl_nhop_head) | |||||
static void consider_resize(uint32_t new_gr_buckets); | |||||
static int clone_unhop(const struct nhop_object *nh_base, int nh_flags, | |||||
struct nhop_object **pnh); | |||||
static int cmp_unhop(const struct user_nhop *a, const struct user_nhop *b); | |||||
static unsigned int hash_unhop(const struct user_nhop *obj); | |||||
static int | |||||
cmp_unhop(const struct user_nhop *a, const struct user_nhop *b) | |||||
{ | |||||
return (a->un_idx == b->un_idx); | |||||
} | |||||
/* | |||||
* Hash callback: calculate hash of an object | |||||
*/ | |||||
static unsigned int | |||||
hash_unhop(const struct user_nhop *obj) | |||||
{ | |||||
return (obj->un_idx); | |||||
} | |||||
/* | |||||
* Returns object referenced and unlocked | |||||
*/ | |||||
static int | |||||
find_unhop(uint32_t uidx, int nh_flags, struct nhop_object **pnhop) | |||||
{ | |||||
struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl); | |||||
NLCTL_TRACKER; | |||||
int error = 0; | |||||
struct user_nhop key= { .un_idx = uidx }, *unhop; | |||||
nh_flags = nh_flags & (NHF_HOST | NHF_DEFAULT); | |||||
NLCTL_RLOCK(ctl); | |||||
CHT_SLIST_FIND_BYOBJ(V_nl_nhop_head, unhop, &key, unhop); | |||||
if (unhop != NULL) { | |||||
int off = 0; | |||||
switch (nh_flags) { | |||||
case NHF_HOST: | |||||
off = 1; | |||||
break; | |||||
case NHF_DEFAULT: | |||||
off = 2; | |||||
break; | |||||
} | |||||
if (unhop->un_nhop[off] != NULL) { | |||||
*pnhop = unhop->un_nhop[off]; | |||||
goto done; | |||||
} | |||||
/* Nexthop with the required flags does not exist yet. */ | |||||
struct nhop_object *nhop = NULL; | |||||
error = clone_unhop(unhop->un_nhop[0], nh_flags, &nhop); | |||||
if (error != 0) | |||||
goto done; | |||||
/* | |||||
* Nexhops remains constant once set and get dereferenced | |||||
* only when unhop is deleted. | |||||
*/ | |||||
if (!atomic_cmpset_ptr((uintptr_t *)&unhop->un_nhop[off], | |||||
(uintptr_t)NULL, (uintptr_t)nhop)) { | |||||
nhop_free_any(nhop); | |||||
nhop = atomic_load_ptr(&unhop->un_nhop[off]); | |||||
} | |||||
*pnhop = unhop->un_nhop[off]; | |||||
} else | |||||
error = ESRCH; | |||||
done: | |||||
NLCTL_RUNLOCK(ctl); | |||||
return (error); | |||||
} | |||||
static struct rib_head * | |||||
nhop_get_rnh(const struct nhop_object *nh) | |||||
{ | |||||
return (rt_tables_get_rnh(nhop_get_fibnum(nh), nhop_get_upper_family(nh))); | |||||
} | |||||
#define MAX_STACK_NHOPS 4 | |||||
static int | |||||
clone_unhop(const struct nhop_object *nh_base, int nh_flags, struct nhop_object **pnh) | |||||
{ | |||||
const struct weightened_nhop *wn; | |||||
struct weightened_nhop *wn_new, wn_base[MAX_STACK_NHOPS]; | |||||
uint32_t num_nhops; | |||||
int error; | |||||
if (!NH_IS_NHGRP(nh_base)) { | |||||
struct nhop_object *nh; | |||||
nh = nhop_alloc(nhop_get_fibnum(nh_base), | |||||
nhop_get_upper_family(nh_base)); | |||||
if (nh == NULL) | |||||
return (ENOMEM); | |||||
nhop_copy(nh, nh_base); | |||||
nhop_set_uidx(nh, nhop_get_uidx(nh_base)); | |||||
nhop_set_pxtype_flag(nh, nh_flags); | |||||
*pnh = nhop_get_nhop(nh, &error); | |||||
return (error); | |||||
} | |||||
const struct nhgrp_object *nhg_base = (const struct nhgrp_object *)nh_base; | |||||
wn = nhgrp_get_nhops(nhg_base, &num_nhops); | |||||
if (num_nhops > MAX_STACK_NHOPS) { | |||||
wn_new = malloc(num_nhops * sizeof(struct weightened_nhop), M_TEMP, M_NOWAIT); | |||||
if (wn_new == NULL) | |||||
return (ENOMEM); | |||||
} else | |||||
wn_new = wn_base; | |||||
for (int i = 0; i < num_nhops; i++) { | |||||
uint32_t uidx = nhop_get_uidx(wn[i].nh); | |||||
if (uidx == 0) { | |||||
error = ESRCH; | |||||
break; | |||||
} | |||||
error = find_unhop(uidx, nh_flags, &wn_new[i].nh); | |||||
if (error != 0) | |||||
break; | |||||
wn_new[i].weight = wn[i].weight; | |||||
} | |||||
if (error == 0) { | |||||
struct rib_head *rh = nhop_get_rnh(wn_new[0].nh); | |||||
error = nhgrp_get_group(rh, wn_new, num_nhops, | |||||
(struct nhgrp_object **)pnh); | |||||
} | |||||
if (wn_new != wn_base) | |||||
free(wn_new, M_TEMP); | |||||
return (error); | |||||
} | |||||
static void | |||||
destroy_unhop_epoch(epoch_context_t ctx) | |||||
{ | |||||
struct user_nhop *unhop; | |||||
unhop = __containerof(ctx, struct user_nhop, un_epoch_ctx); | |||||
for (int i = 0; i < 3; i++) | |||||
nhop_free_any(unhop->un_nhop[i]); | |||||
free(unhop, M_NETLINK); | |||||
} | |||||
static void | |||||
delete_unhop(struct user_nhop *unhop) | |||||
{ | |||||
struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl); | |||||
struct user_nhop *unhop_ret; | |||||
NLCTL_WLOCK(ctl); | |||||
CHT_SLIST_REMOVE(V_nl_nhop_head, unhop, unhop, unhop_ret); | |||||
NLCTL_WUNLOCK(ctl); | |||||
if (unhop_ret == NULL) { | |||||
RT_LOG(LOG_DEBUG, "unable to find unhop %u", unhop->un_idx); | |||||
} | |||||
MPASS(unhop == unhop_ret); | |||||
epoch_call(net_epoch_preempt, destroy_unhop_epoch, | |||||
&unhop->un_epoch_ctx); | |||||
} | |||||
static void | |||||
consider_resize(uint32_t new_gr_bucket) | |||||
{ | |||||
struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl); | |||||
void *gr_ptr = NULL; | |||||
size_t alloc_size; | |||||
if (new_gr_bucket == 0) | |||||
return; | |||||
if (new_gr_bucket != 0) { | |||||
alloc_size = CHT_SLIST_GET_RESIZE_SIZE(new_gr_bucket); | |||||
gr_ptr = malloc(alloc_size, M_NETLINK, M_NOWAIT | M_ZERO); | |||||
if (gr_ptr == NULL) | |||||
return; | |||||
} | |||||
NLCTL_WLOCK(ctl); | |||||
if (gr_ptr != NULL) { | |||||
CHT_SLIST_RESIZE(V_nl_nhop_head, unhop, gr_ptr, new_gr_bucket); | |||||
} | |||||
NLCTL_WUNLOCK(ctl); | |||||
if (gr_ptr != NULL) | |||||
free(gr_ptr, M_NETLINK); | |||||
} | |||||
static bool __noinline | |||||
init_unhops() | |||||
{ | |||||
struct nl_control *ctl = atomic_load_ptr(&V_nl_ctl); | |||||
uint32_t num_buckets = 16; | |||||
size_t alloc_size = CHT_SLIST_GET_RESIZE_SIZE(num_buckets); | |||||
struct unhop_head *phead = malloc(sizeof(struct unhop_head), M_NETLINK, | |||||
M_NOWAIT | M_ZERO); | |||||
if (phead == NULL) | |||||
return (NULL); | |||||
void *ptr = malloc(alloc_size, M_NETLINK, M_NOWAIT | M_ZERO); | |||||
if (ptr == NULL) | |||||
return (false); | |||||
CHT_SLIST_INIT(phead, ptr, num_buckets); | |||||
NLCTL_WLOCK(ctl); | |||||
if (V_nl_nhop_head == NULL) | |||||
V_nl_nhop_head = phead; | |||||
else { | |||||
free(ptr, M_NETLINK); | |||||
free(phead, M_NETLINK); | |||||
} | |||||
NLCTL_WUNLOCK(ctl); | |||||
return (true); | |||||
} | |||||
int | |||||
rtnl_handle_newnhop(struct nlmsghdr *hdr, struct nlpcb *nlp, | |||||
struct netlink_parse_tracker *npt) | |||||
{ | |||||
if ((__predict_false(V_nl_nhop_head == NULL)) && (!init_unhops())) | |||||
return (ENOMEM); | |||||
return (0); | |||||
} | |||||