blob: 3dfe5586dcac6f2e2fb0e02139a2c77b47795bda [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-only */
/*
* Copyright (c) 2022 Stanislav Zaikin <zstaseg@gmail.com>
*/
#include "nl-default.h"
#include <linux/nexthop.h>
#include <netlink/route/nh.h>
#include <netlink/hashtable.h>
#include <netlink/route/nexthop.h>
#include "nl-aux-route/nl-route.h"
#include "nl-route.h"
#include "nl-priv-dynamic-core/nl-core.h"
#include "nl-priv-dynamic-core/cache-api.h"
/** @cond SKIP */
struct rtnl_nh {
NLHDR_COMMON
uint8_t nh_family;
uint32_t nh_flags;
uint32_t nh_id;
uint32_t nh_group_type;
nl_nh_group_t *nh_group;
uint32_t nh_oif;
struct nl_addr *nh_gateway;
};
#define NH_ATTR_FLAGS (1 << 0)
#define NH_ATTR_ID (1 << 1)
#define NH_ATTR_GROUP (1 << 2)
#define NH_ATTR_FLAG_BLACKHOLE (1 << 3)
#define NH_ATTR_OIF (1 << 4)
#define NH_ATTR_GATEWAY (1 << 5)
#define NH_ATTR_FLAG_GROUPS (1 << 6)
#define NH_ATTR_FLAG_FDB (1 << 8)
/** @endcond */
struct nla_policy rtnl_nh_policy[NHA_MAX + 1] = {
[NHA_UNSPEC] = { .type = NLA_UNSPEC },
[NHA_ID] = { .type = NLA_U32 },
[NHA_GROUP] = { .type = NLA_NESTED },
[NHA_GROUP_TYPE] = { .type = NLA_U16 },
[NHA_BLACKHOLE] = { .type = NLA_UNSPEC },
[NHA_OIF] = { .type = NLA_U32 },
};
static struct nl_cache_ops rtnl_nh_ops;
static struct nl_object_ops nh_obj_ops;
static nl_nh_group_t *rtnl_nh_grp_alloc(unsigned size)
{
nl_nh_group_t *nhg;
_nl_assert(size <= (unsigned)INT_MAX);
if (!(nhg = calloc(1, sizeof(*nhg))))
return NULL;
nhg->size = size;
if (!(nhg->entries = calloc(size, sizeof(*nhg->entries)))) {
free(nhg);
return NULL;
}
nhg->ce_refcnt = 1;
return nhg;
}
static void rtnl_nh_grp_put(nl_nh_group_t *nhg)
{
if (!nhg)
return;
_nl_assert(nhg->ce_refcnt > 0);
nhg->ce_refcnt--;
if (nhg->ce_refcnt > 0)
return;
free(nhg);
}
static int rtnh_nh_grp_cmp(const nl_nh_group_t *a, const nl_nh_group_t *b)
{
unsigned i;
_NL_CMP_SELF(a, b);
_NL_CMP_DIRECT(a->size, b->size);
for (i = 0; i < a->size; i++) {
_NL_CMP_DIRECT(a->entries[i].nh_id, b->entries[i].nh_id);
_NL_CMP_DIRECT(a->entries[i].weight, b->entries[i].weight);
}
return 0;
}
static int rtnh_nh_grp_clone(nl_nh_group_t *src, nl_nh_group_t **dst)
{
nl_nh_group_t *ret;
unsigned i;
ret = rtnl_nh_grp_alloc(src->size);
if (!ret)
return -NLE_NOMEM;
for (i = 0; i < src->size; i++) {
ret->entries[i].nh_id = src->entries[i].nh_id;
ret->entries[i].weight = src->entries[i].weight;
}
*dst = ret;
return NLE_SUCCESS;
}
struct rtnl_nh *rtnl_nh_alloc(void)
{
return (struct rtnl_nh *)nl_object_alloc(&nh_obj_ops);
}
static int nh_clone(struct nl_object *_src, struct nl_object *_dst)
{
struct rtnl_nh *dst = nl_object_priv(_dst);
struct rtnl_nh *src = nl_object_priv(_src);
dst->nh_flags = src->nh_flags;
dst->nh_family = src->nh_family;
dst->nh_id = src->nh_id;
dst->nh_oif = src->nh_oif;
dst->ce_mask = src->ce_mask;
if (src->nh_gateway) {
dst->nh_gateway = nl_addr_clone(src->nh_gateway);
if (!dst->nh_gateway) {
return -NLE_NOMEM;
}
}
if (src->nh_group) {
if (rtnh_nh_grp_clone(src->nh_group, &dst->nh_group) < 0) {
return -NLE_NOMEM;
}
}
return 0;
}
static void nh_free(struct nl_object *obj)
{
struct rtnl_nh *nh = nl_object_priv(obj);
nl_addr_put(nh->nh_gateway);
if (nh->nh_group)
rtnl_nh_grp_put(nh->nh_group);
}
void rtnl_nh_put(struct rtnl_nh *nh)
{
struct nl_object *obj = (struct nl_object *)nh;
nl_object_put(obj);
}
static void nexthop_keygen(struct nl_object *obj, uint32_t *hashkey,
uint32_t table_sz)
{
struct rtnl_nh *nexthop = nl_object_priv(obj);
unsigned int lkey_sz;
struct nexthop_hash_key {
uint32_t nh_id;
} _nl_packed lkey;
lkey_sz = sizeof(lkey);
lkey.nh_id = nexthop->nh_id;
*hashkey = nl_hash(&lkey, lkey_sz, 0) % table_sz;
return;
}
int rtnl_nh_set_gateway(struct rtnl_nh *nexthop, struct nl_addr *addr)
{
if (nexthop->ce_mask & NH_ATTR_GATEWAY) {
nl_addr_put(nexthop->nh_gateway);
}
nexthop->nh_gateway = nl_addr_clone(addr);
nexthop->ce_mask |= NH_ATTR_GATEWAY;
return 0;
}
struct nl_addr *rtnl_nh_get_gateway(struct rtnl_nh *nexthop)
{
return nexthop->nh_gateway;
}
int rtnl_nh_set_fdb(struct rtnl_nh *nexthop, int value)
{
if (value)
nexthop->ce_mask |= NH_ATTR_FLAG_FDB;
else
nexthop->ce_mask &= ~NH_ATTR_FLAG_FDB;
return 0;
}
int rtnl_nh_get_oif(struct rtnl_nh *nexthop)
{
if (nexthop->ce_mask & NH_ATTR_OIF)
return nexthop->nh_oif;
return 0;
}
int rtnl_nh_get_fdb(struct rtnl_nh *nexthop)
{
return nexthop->ce_mask & NH_ATTR_FLAG_FDB;
}
int rtnl_nh_get_group_entry(struct rtnl_nh *nexthop, int n)
{
if (!(nexthop->ce_mask & NH_ATTR_GROUP) || !nexthop->nh_group)
return -NLE_MISSING_ATTR;
if (n < 0 || ((unsigned)n) >= nexthop->nh_group->size)
return -NLE_INVAL;
return nexthop->nh_group->entries[n].nh_id;
}
int rtnl_nh_get_group_size(struct rtnl_nh *nexthop)
{
if (!(nexthop->ce_mask & NH_ATTR_GROUP) || !nexthop->nh_group)
return -NLE_MISSING_ATTR;
_nl_assert(nexthop->nh_group->size <= INT_MAX);
return (int)nexthop->nh_group->size;
}
static int rtnl_nh_grp_info(unsigned size, const struct nexthop_grp *vi,
nl_nh_group_t **nvi)
{
nl_nh_group_t *ret;
unsigned i;
if (!(ret = rtnl_nh_grp_alloc(size)))
return -NLE_NOMEM;
for (i = 0; i < size; i++) {
ret->entries[i].nh_id = vi[i].id;
ret->entries[i].weight = vi[i].weight;
}
*nvi = ret;
return NLE_SUCCESS;
}
int rtnl_nh_get_id(struct rtnl_nh *nh)
{
if (nh->ce_mask & NH_ATTR_ID)
return nh->nh_id;
return -NLE_INVAL;
}
static int nexthop_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
struct nlmsghdr *n, struct nl_parser_param *pp)
{
_nl_auto_rtnl_nh struct rtnl_nh *nexthop = NULL;
struct nhmsg *ifi;
struct nlattr *tb[NHA_MAX + 1];
int err;
int family;
nexthop = rtnl_nh_alloc();
if (nexthop == NULL)
return -NLE_NOMEM;
nexthop->ce_msgtype = n->nlmsg_type;
if (!nlmsg_valid_hdr(n, sizeof(*ifi)))
return -NLE_MSG_TOOSHORT;
ifi = nlmsg_data(n);
family = ifi->nh_family;
nexthop->nh_family = family;
nexthop->nh_flags = ifi->nh_flags;
nexthop->ce_mask = (NH_ATTR_FLAGS);
err = nlmsg_parse(n, sizeof(*ifi), tb, NHA_MAX, rtnl_nh_policy);
if (err < 0)
return err;
if (tb[NHA_ID]) {
nexthop->nh_id = nla_get_u32(tb[NHA_ID]);
nexthop->ce_mask |= NH_ATTR_ID;
}
if (tb[NHA_OIF]) {
nexthop->nh_oif = nla_get_u32(tb[NHA_OIF]);
nexthop->ce_mask |= NH_ATTR_OIF;
}
if (tb[NHA_GATEWAY]) {
nexthop->nh_gateway =
nl_addr_alloc_attr(tb[NHA_GATEWAY], family);
nexthop->ce_mask |= NH_ATTR_GATEWAY;
}
if (tb[NHA_BLACKHOLE]) {
nexthop->ce_mask |= NH_ATTR_FLAG_BLACKHOLE;
}
if (tb[NHA_GROUPS]) {
nexthop->ce_mask |= NH_ATTR_FLAG_GROUPS;
}
if (tb[NHA_FDB]) {
nexthop->ce_mask |= NH_ATTR_FLAG_FDB;
}
if (tb[NHA_GROUP]) {
nl_nh_group_t *nh_group = NULL;
const void *data;
unsigned size;
unsigned len;
data = nla_data(tb[NHA_GROUP]);
len = _nla_len(tb[NHA_GROUP]);
size = len / sizeof(struct nexthop_grp);
err = rtnl_nh_grp_info(size, (const struct nexthop_grp *)data,
&nh_group);
if (err < 0) {
return err;
}
nexthop->nh_group = nh_group;
nexthop->ce_mask |= NH_ATTR_GROUP;
}
return pp->pp_cb((struct nl_object *)nexthop, pp);
}
static int nexthop_request_update(struct nl_cache *cache, struct nl_sock *sk)
{
_nl_auto_nl_msg struct nl_msg *msg = NULL;
int family = cache->c_iarg1;
struct nhmsg hdr = { .nh_family = family };
int err;
msg = nlmsg_alloc_simple(RTM_GETNEXTHOP, NLM_F_DUMP);
if (!msg)
return -NLE_NOMEM;
if (nlmsg_append(msg, &hdr, sizeof(hdr), NLMSG_ALIGNTO) < 0)
return -NLE_MSGSIZE;
err = nl_send_auto(sk, msg);
if (err < 0)
return err;
return NLE_SUCCESS;
}
static void dump_nh_group(nl_nh_group_t *group, struct nl_dump_params *dp)
{
unsigned i;
nl_dump(dp, " nh_grp:");
for (i = 0; i < group->size; i++) {
nl_dump(dp, " %u", group->entries[i].nh_id);
}
}
static void nh_dump_line(struct nl_object *obj, struct nl_dump_params *dp)
{
struct nl_cache *cache;
char buf[128];
struct rtnl_nh *nh = nl_object_priv(obj);
cache = nl_cache_mngt_require_safe("route/nh");
if (nh->ce_mask & NH_ATTR_ID)
nl_dump(dp, "nhid %u", nh->nh_id);
if (nh->ce_mask & NH_ATTR_OIF)
nl_dump(dp, " oif %d", nh->nh_oif);
if (nh->ce_mask & NH_ATTR_GATEWAY)
nl_dump(dp, " via %s",
nl_addr2str(nh->nh_gateway, buf, sizeof(buf)));
if (nh->ce_mask & NH_ATTR_FLAG_BLACKHOLE)
nl_dump(dp, " blackhole");
if (nh->ce_mask & NH_ATTR_FLAG_GROUPS)
nl_dump(dp, " groups");
if (nh->ce_mask & NH_ATTR_GROUP)
dump_nh_group(nh->nh_group, dp);
if (nh->ce_mask & NH_ATTR_FLAG_FDB)
nl_dump(dp, " fdb");
nl_dump(dp, "\n");
if (cache)
nl_cache_put(cache);
}
static void nh_dump_details(struct nl_object *nh, struct nl_dump_params *dp)
{
nh_dump_line(nh, dp);
}
static uint64_t nh_compare(struct nl_object *a, struct nl_object *b,
uint64_t attrs, int loose)
{
int diff = 0;
struct rtnl_nh *src = nl_object_priv(a);
struct rtnl_nh *dst = nl_object_priv(b);
#define _DIFF(ATTR, EXPR) ATTR_DIFF(attrs, ATTR, a, b, EXPR)
diff |= _DIFF(NH_ATTR_ID, src->nh_id != dst->nh_id);
diff |= _DIFF(NH_ATTR_GATEWAY,
nl_addr_cmp(src->nh_gateway, dst->nh_gateway));
diff |= _DIFF(NH_ATTR_OIF, src->nh_oif != dst->nh_oif);
diff |= _DIFF(NH_ATTR_GROUP,
rtnh_nh_grp_cmp(src->nh_group, dst->nh_group));
diff |= _DIFF(NH_ATTR_FLAG_FDB, false);
diff |= _DIFF(NH_ATTR_FLAG_GROUPS, false);
diff |= _DIFF(NH_ATTR_FLAG_BLACKHOLE, false);
#undef _DIFF
return diff;
}
struct rtnl_nh *rtnl_nh_get(struct nl_cache *cache, int nhid)
{
struct rtnl_nh *nh;
if (cache->c_ops != &rtnl_nh_ops)
return NULL;
nl_list_for_each_entry(nh, &cache->c_items, ce_list) {
if (nh->nh_id == ((unsigned)nhid)) {
nl_object_get((struct nl_object *)nh);
return nh;
}
}
return NULL;
}
/**
* Allocate nexthop cache and fill in all configured nexthops.
* @arg sk Netnexthop socket.
* @arg family nexthop address family or AF_UNSPEC
* @arg result Pointer to store resulting cache.
* @arg flags Flags to set in nexthop cache before filling
*
* Allocates and initializes a new nexthop cache. If \c sk is valid, a netnexthop
* message is sent to the kernel requesting a full dump of all configured
* nexthops. The returned messages are parsed and filled into the cache. If
* the operation succeeds, the resulting cache will contain a nexthop object for
* each nexthop configured in the kernel. If \c sk is NULL, returns 0 but the
* cache is still empty.
*
* If \c family is set to an address family other than \c AF_UNSPEC the
* contents of the cache can be limited to a specific address family.
* Currently the following address families are supported:
* - AF_BRIDGE
* - AF_INET6
*
* @route_doc{nexthop_list, Get List of nexthops}
* @see rtnl_nh_get()
* @see rtnl_nh_get_by_name()
* @return 0 on success or a negative error code.
*/
static int rtnl_nh_alloc_cache_flags(struct nl_sock *sk, int family,
struct nl_cache **result,
unsigned int flags)
{
struct nl_cache *cache;
int err;
cache = nl_cache_alloc(&rtnl_nh_ops);
if (!cache)
return -NLE_NOMEM;
cache->c_iarg1 = family;
if (flags)
nl_cache_set_flags(cache, flags);
if (sk && (err = nl_cache_refill(sk, cache)) < 0) {
nl_cache_free(cache);
return err;
}
*result = cache;
return 0;
}
/**
* Allocate nexthop cache and fill in all configured nexthops.
* @arg sk Netnexthop socket.
* @arg family nexthop address family or AF_UNSPEC
* @arg result Pointer to store resulting cache.
*
* Allocates and initializes a new nexthop cache. If \c sk is valid, a netnexthop
* message is sent to the kernel requesting a full dump of all configured
* nexthops. The returned messages are parsed and filled into the cache. If
* the operation succeeds, the resulting cache will contain a nexthop object for
* each nexthop configured in the kernel. If \c sk is NULL, returns 0 but the
* cache is still empty.
*
* If \c family is set to an address family other than \c AF_UNSPEC the
* contents of the cache can be limited to a specific address family.
* Currently the following address families are supported:
* - AF_BRIDGE
* - AF_INET6
*
* @route_doc{nexthop_list, Get List of nexthops}
* @see rtnl_nh_get()
* @see rtnl_nh_get_by_name()
* @return 0 on success or a negative error code.
*/
int rtnl_nh_alloc_cache(struct nl_sock *sk, int family,
struct nl_cache **result)
{
return rtnl_nh_alloc_cache_flags(sk, family, result, 0);
}
static struct nl_object_ops nh_obj_ops = {
.oo_name = "route/nh",
.oo_size = sizeof(struct rtnl_nh),
.oo_free_data = nh_free,
.oo_clone = nh_clone,
.oo_dump = {
[NL_DUMP_LINE] = nh_dump_line,
[NL_DUMP_DETAILS] = nh_dump_details,
},
.oo_compare = nh_compare,
.oo_keygen = nexthop_keygen,
.oo_attrs2str = rtnl_route_nh_flags2str,
.oo_id_attrs = NH_ATTR_ID,
};
static struct nl_af_group nh_groups[] = {
{ AF_UNSPEC, RTNLGRP_NEXTHOP },
{ END_OF_GROUP_LIST },
};
static struct nl_cache_ops rtnl_nh_ops = {
.co_name = "route/nh",
.co_hdrsize = sizeof(struct nhmsg),
.co_msgtypes = {
{ RTM_NEWNEXTHOP, NL_ACT_NEW, "new" },
{ RTM_DELNEXTHOP, NL_ACT_DEL, "del" },
{ RTM_GETNEXTHOP, NL_ACT_GET, "get" },
END_OF_MSGTYPES_LIST,
},
.co_protocol = NETLINK_ROUTE,
.co_groups = nh_groups,
.co_request_update = nexthop_request_update,
.co_msg_parser = nexthop_msg_parser,
.co_obj_ops = &nh_obj_ops,
};
static void _nl_init nexthop_init(void)
{
nl_cache_mngt_register(&rtnl_nh_ops);
}
static void _nl_exit nexthop_exit(void)
{
nl_cache_mngt_unregister(&rtnl_nh_ops);
}