/* SPDX-License-Identifier: LGPL-2.1-only */ /* * Copyright (c) 2022 Stanislav Zaikin */ #include "nl-default.h" #include #include #include #include #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); }