From 3d46c64c14dda6463f6899b77097b9613394280b Mon Sep 17 00:00:00 2001 From: Stanislav Zaikin Date: Wed, 5 Oct 2022 10:49:55 +0200 Subject: [PATCH] route: add nexthop type --- .gitignore | 1 + Makefile.am | 7 + include/linux-private/linux/neighbour.h | 2 + include/linux-private/linux/nexthop.h | 104 +++++ include/linux-private/linux/rtnetlink.h | 11 +- include/netlink-private/nl-auto.h | 5 + include/netlink-private/types.h | 29 ++ include/netlink/cli/nh.h | 23 ++ include/netlink/route/neighbour.h | 3 + include/netlink/route/nh.h | 47 +++ lib/route/neigh.c | 25 ++ lib/route/nh.c | 529 ++++++++++++++++++++++++ lib/route/route_utils.c | 1 + libnl-cli-3.sym | 2 + libnl-route-3.sym | 14 + src/lib/nh.c | 42 ++ src/nl-nh-list.c | 58 +++ 17 files changed, 902 insertions(+), 1 deletion(-) create mode 100644 include/linux-private/linux/nexthop.h create mode 100644 include/netlink/cli/nh.h create mode 100644 include/netlink/route/nh.h create mode 100644 lib/route/nh.c create mode 100644 src/lib/nh.c create mode 100644 src/nl-nh-list.c diff --git a/.gitignore b/.gitignore index da3eb81d2..896618a9c 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,7 @@ test-suite.log /src/nl-neigh-delete /src/nl-neigh-list /src/nl-neightbl-list +/src/nl-nh-list /src/nl-pktloc-lookup /src/nl-qdisc-add /src/nl-qdisc-delete diff --git a/Makefile.am b/Makefile.am index ef0f82d0c..8b18ec363 100644 --- a/Makefile.am +++ b/Makefile.am @@ -110,6 +110,7 @@ libnlinclude_netlink_route_HEADERS = \ include/netlink/route/neightbl.h \ include/netlink/route/netconf.h \ include/netlink/route/nexthop.h \ + include/netlink/route/nh.h \ include/netlink/route/pktloc.h \ include/netlink/route/qdisc.h \ include/netlink/route/route.h \ @@ -252,6 +253,7 @@ noinst_HEADERS = \ include/linux-private/linux/netfilter/nfnetlink_log.h \ include/linux-private/linux/netfilter/nfnetlink_queue.h \ include/linux-private/linux/netlink.h \ + include/linux-private/linux/nexthop.h \ include/linux-private/linux/pkt_cls.h \ include/linux-private/linux/pkt_sched.h \ include/linux-private/linux/rtnetlink.h \ @@ -434,6 +436,7 @@ lib_libnl_route_3_la_SOURCES = \ lib/route/neightbl.c \ lib/route/netconf.c \ lib/route/nexthop.c \ + lib/route/nh.c \ lib/route/nexthop_encap.c \ lib/route/nh_encap_mpls.c \ lib/route/pktloc.c \ @@ -629,6 +632,7 @@ src_lib_libnl_cli_3_la_SOURCES = \ src/lib/exp.c \ src/lib/link.c \ src/lib/neigh.c \ + src/lib/nh.c \ src/lib/qdisc.c \ src/lib/route.c \ src/lib/rule.c \ @@ -712,6 +716,7 @@ cli_programs = \ src/nl-neigh-delete \ src/nl-neigh-list \ src/nl-neightbl-list \ + src/nl-nh-list \ src/nl-pktloc-lookup \ src/nl-qdisc-add \ src/nl-qdisc-delete \ @@ -811,6 +816,8 @@ src_nl_neigh_list_CPPFLAGS = $(src_cppflags) src_nl_neigh_list_LDADD = $(src_ldadd) src_nl_neightbl_list_CPPFLAGS = $(src_cppflags) src_nl_neightbl_list_LDADD = $(src_ldadd) +src_nl_nh_list_CPPFLAGS = $(src_cppflags) +src_nl_nh_list_LDADD = $(src_ldadd) src_nl_pktloc_lookup_CPPFLAGS = $(src_cppflags) src_nl_pktloc_lookup_LDADD = $(src_ldadd) src_nl_qdisc_add_CPPFLAGS = $(src_cppflags) diff --git a/include/linux-private/linux/neighbour.h b/include/linux-private/linux/neighbour.h index 904db6148..6007c900b 100644 --- a/include/linux-private/linux/neighbour.h +++ b/include/linux-private/linux/neighbour.h @@ -28,6 +28,8 @@ enum { NDA_MASTER, NDA_LINK_NETNSID, NDA_SRC_VNI, + NDA_PROTOCOL, /* Originator of entry */ + NDA_NH_ID, __NDA_MAX }; diff --git a/include/linux-private/linux/nexthop.h b/include/linux-private/linux/nexthop.h new file mode 100644 index 000000000..37b14b4ea --- /dev/null +++ b/include/linux-private/linux/nexthop.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_NEXTHOP_H +#define _LINUX_NEXTHOP_H + +#include + +struct nhmsg { + unsigned char nh_family; + unsigned char nh_scope; /* return only */ + unsigned char nh_protocol; /* Routing protocol that installed nh */ + unsigned char resvd; + unsigned int nh_flags; /* RTNH_F flags */ +}; + +/* entry in a nexthop group */ +struct nexthop_grp { + __u32 id; /* nexthop id - must exist */ + __u8 weight; /* weight of this nexthop */ + __u8 resvd1; + __u16 resvd2; +}; + +enum { + NEXTHOP_GRP_TYPE_MPATH, /* hash-threshold nexthop group + * default type if not specified + */ + NEXTHOP_GRP_TYPE_RES, /* resilient nexthop group */ + __NEXTHOP_GRP_TYPE_MAX, +}; + +#define NEXTHOP_GRP_TYPE_MAX (__NEXTHOP_GRP_TYPE_MAX - 1) + +enum { + NHA_UNSPEC, + NHA_ID, /* u32; id for nexthop. id == 0 means auto-assign */ + + NHA_GROUP, /* array of nexthop_grp */ + NHA_GROUP_TYPE, /* u16 one of NEXTHOP_GRP_TYPE */ + /* if NHA_GROUP attribute is added, no other attributes can be set */ + + NHA_BLACKHOLE, /* flag; nexthop used to blackhole packets */ + /* if NHA_BLACKHOLE is added, OIF, GATEWAY, ENCAP can not be set */ + + NHA_OIF, /* u32; nexthop device */ + NHA_GATEWAY, /* be32 (IPv4) or in6_addr (IPv6) gw address */ + NHA_ENCAP_TYPE, /* u16; lwt encap type */ + NHA_ENCAP, /* lwt encap data */ + + /* NHA_OIF can be appended to dump request to return only + * nexthops using given device + */ + NHA_GROUPS, /* flag; only return nexthop groups in dump */ + NHA_MASTER, /* u32; only return nexthops with given master dev */ + + NHA_FDB, /* flag; nexthop belongs to a bridge fdb */ + /* if NHA_FDB is added, OIF, BLACKHOLE, ENCAP cannot be set */ + + /* nested; resilient nexthop group attributes */ + NHA_RES_GROUP, + /* nested; nexthop bucket attributes */ + NHA_RES_BUCKET, + + __NHA_MAX, +}; + +#define NHA_MAX (__NHA_MAX - 1) + +enum { + NHA_RES_GROUP_UNSPEC, + /* Pad attribute for 64-bit alignment. */ + NHA_RES_GROUP_PAD = NHA_RES_GROUP_UNSPEC, + + /* u16; number of nexthop buckets in a resilient nexthop group */ + NHA_RES_GROUP_BUCKETS, + /* clock_t as u32; nexthop bucket idle timer (per-group) */ + NHA_RES_GROUP_IDLE_TIMER, + /* clock_t as u32; nexthop unbalanced timer */ + NHA_RES_GROUP_UNBALANCED_TIMER, + /* clock_t as u64; nexthop unbalanced time */ + NHA_RES_GROUP_UNBALANCED_TIME, + + __NHA_RES_GROUP_MAX, +}; + +#define NHA_RES_GROUP_MAX (__NHA_RES_GROUP_MAX - 1) + +enum { + NHA_RES_BUCKET_UNSPEC, + /* Pad attribute for 64-bit alignment. */ + NHA_RES_BUCKET_PAD = NHA_RES_BUCKET_UNSPEC, + + /* u16; nexthop bucket index */ + NHA_RES_BUCKET_INDEX, + /* clock_t as u64; nexthop bucket idle time */ + NHA_RES_BUCKET_IDLE_TIME, + /* u32; nexthop id assigned to the nexthop bucket */ + NHA_RES_BUCKET_NH_ID, + + __NHA_RES_BUCKET_MAX, +}; + +#define NHA_RES_BUCKET_MAX (__NHA_RES_BUCKET_MAX - 1) + +#endif diff --git a/include/linux-private/linux/rtnetlink.h b/include/linux-private/linux/rtnetlink.h index 8c1d600bf..e7b35fa44 100644 --- a/include/linux-private/linux/rtnetlink.h +++ b/include/linux-private/linux/rtnetlink.h @@ -157,7 +157,14 @@ enum { RTM_GETCHAIN, #define RTM_GETCHAIN RTM_GETCHAIN - __RTM_MAX, + RTM_NEWNEXTHOP = 104, +#define RTM_NEWNEXTHOP RTM_NEWNEXTHOP + RTM_DELNEXTHOP, +#define RTM_DELNEXTHOP RTM_DELNEXTHOP + RTM_GETNEXTHOP, +#define RTM_GETNEXTHOP RTM_GETNEXTHOP + + __RTM_MAX, #define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) }; @@ -702,6 +709,8 @@ enum rtnetlink_groups { #define RTNLGRP_IPV4_MROUTE_R RTNLGRP_IPV4_MROUTE_R RTNLGRP_IPV6_MROUTE_R, #define RTNLGRP_IPV6_MROUTE_R RTNLGRP_IPV6_MROUTE_R + RTNLGRP_NEXTHOP, +#define RTNLGRP_NEXTHOP RTNLGRP_NEXTHOP __RTNLGRP_MAX }; #define RTNLGRP_MAX (__RTNLGRP_MAX - 1) diff --git a/include/netlink-private/nl-auto.h b/include/netlink-private/nl-auto.h index 4092782b5..8c9b7829a 100644 --- a/include/netlink-private/nl-auto.h +++ b/include/netlink-private/nl-auto.h @@ -69,6 +69,11 @@ void rtnl_route_nh_free(struct rtnl_nexthop *); #define _nl_auto_rtnl_nexthop _nl_auto(_nl_auto_rtnl_nexthop_fcn) _NL_AUTO_DEFINE_FCN_TYPED0(struct rtnl_nexthop *, _nl_auto_rtnl_nexthop_fcn, rtnl_route_nh_free); +struct rtnl_nh; +void rtnl_nh_put(struct rtnl_nh *); +#define _nl_auto_rtnl_nh _nl_auto(_nl_auto_rtnl_nh_fcn) +_NL_AUTO_DEFINE_FCN_TYPED0(struct rtnl_nh *, _nl_auto_rtnl_nh_fcn, rtnl_nh_put); + struct nl_cache; void nl_cache_put(struct nl_cache *); #define _nl_auto_nl_cache _nl_auto(_nl_auto_nl_cache_fcn) diff --git a/include/netlink-private/types.h b/include/netlink-private/types.h index b8f785af6..a501726e3 100644 --- a/include/netlink-private/types.h +++ b/include/netlink-private/types.h @@ -254,6 +254,7 @@ struct rtnl_neigh uint8_t n_type; struct nl_addr *n_lladdr; struct nl_addr *n_dst; + uint32_t n_nhid; uint32_t n_probes; struct rtnl_ncacheinfo n_cacheinfo; uint32_t n_state_mask; @@ -323,6 +324,34 @@ struct rtnl_nexthop struct rtnl_nh_encap * rtnh_encap; }; +typedef struct nl_nh_group_info { + uint32_t nh_id; + uint8_t weight; +} nl_nh_group_info_t; + +typedef struct nl_nh_group { + int ce_refcnt; + int size; + nl_nh_group_info_t * entries; +} nl_nh_group_t; + +struct rtnl_nh +{ + NLHDR_COMMON + + uint8_t nh_family; + uint8_t nh_scope; + uint8_t nh_protocol; + 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; + uint32_t nh_master; +}; + struct rtnl_route { NLHDR_COMMON diff --git a/include/netlink/cli/nh.h b/include/netlink/cli/nh.h new file mode 100644 index 000000000..9879dd6e1 --- /dev/null +++ b/include/netlink/cli/nh.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ +/* + * Copyright (c) 2022 Stanislav Zaikin + */ + +#ifndef __NETLINK_CLI_NH_H_ +#define __NETLINK_CLI_NH_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct rtnl_nh *nl_cli_nh_alloc(void); +extern struct nl_cache *nl_cli_nh_alloc_cache(struct nl_sock *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/netlink/route/neighbour.h b/include/netlink/route/neighbour.h index 376041496..326a81297 100644 --- a/include/netlink/route/neighbour.h +++ b/include/netlink/route/neighbour.h @@ -79,6 +79,9 @@ extern int rtnl_neigh_get_vlan(struct rtnl_neigh *); extern void rtnl_neigh_set_master(struct rtnl_neigh *, int); extern int rtnl_neigh_get_master(struct rtnl_neigh *); +extern void rtnl_neigh_set_nhid(struct rtnl_neigh *, uint32_t); +extern uint32_t rtnl_neigh_get_nhid(struct rtnl_neigh *); + #ifdef __cplusplus } #endif diff --git a/include/netlink/route/nh.h b/include/netlink/route/nh.h new file mode 100644 index 000000000..f7779e0e6 --- /dev/null +++ b/include/netlink/route/nh.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ +/* + * Copyright (c) 2022 Stanislav Zaikin + */ + +#ifndef NETLINK_ROUTE_NH_H_ +#define NETLINK_ROUTE_NH_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct rtnl_nh; + +extern int rtnl_nh_alloc_cache(struct nl_sock *sk, int family, struct nl_cache **result); +extern struct rtnl_nh * rtnl_nh_alloc(void); +extern struct rtnl_nh * rtnl_nh_clone(struct rtnl_nh *); +extern void rtnl_nh_put(struct rtnl_nh *); + +extern struct rtnl_nh *rtnl_nh_get(struct nl_cache *cache, int nhid); + +extern int rtnl_nh_compare(struct rtnl_nh *, struct rtnl_nh *, + uint32_t, int); + +extern void rtnl_nh_dump(struct rtnl_nh *, struct nl_dump_params *); + +extern int rtnl_nh_set_gateway(struct rtnl_nh *, struct nl_addr *); +extern struct nl_addr * rtnl_nh_get_gateway(struct rtnl_nh *); + +extern int rtnl_nh_set_fdb(struct rtnl_nh *, int value); +extern int rtnl_nh_get_fdb(struct rtnl_nh *); + +extern uint32_t rtnl_nh_get_group_entry(struct rtnl_nh *, int n); +extern int rtnl_nh_get_group_size(struct rtnl_nh *); + +extern uint32_t rtnl_nh_get_id(struct rtnl_nh *); +extern int rtnl_nh_get_oif(struct rtnl_nh *); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/lib/route/neigh.c b/lib/route/neigh.c index e1ef6a14c..8cc60b56e 100644 --- a/lib/route/neigh.c +++ b/lib/route/neigh.c @@ -164,6 +164,7 @@ #define NEIGH_ATTR_PROBES 0x100 #define NEIGH_ATTR_MASTER 0x200 #define NEIGH_ATTR_VLAN 0x400 +#define NEIGH_ATTR_NHID 0x800 static struct nl_cache_ops rtnl_neigh_ops; static struct nl_object_ops neigh_obj_ops; @@ -274,6 +275,7 @@ static uint64_t neigh_compare(struct nl_object *_a, struct nl_object *_b, diff |= NEIGH_DIFF(DST, nl_addr_cmp(a->n_dst, b->n_dst)); diff |= NEIGH_DIFF(MASTER, a->n_master != b->n_master); diff |= NEIGH_DIFF(VLAN, a->n_vlan != b->n_vlan); + diff |= NEIGH_DIFF(NHID, a->n_nhid != b->n_nhid); if (flags & LOOSE_COMPARISON) { diff |= NEIGH_DIFF(STATE, @@ -302,6 +304,7 @@ static const struct trans_tbl neigh_attrs[] = { __ADD(NEIGH_ATTR_PROBES, probes), __ADD(NEIGH_ATTR_MASTER, master), __ADD(NEIGH_ATTR_VLAN, vlan), + __ADD(NEIGH_ATTR_NHID, nhid), }; static char *neigh_attrs2str(int attrs, char *buf, size_t len) @@ -318,6 +321,7 @@ static uint32_t neigh_id_attrs_get(struct nl_object *obj) if (neigh->n_flags & NTF_SELF) return (NEIGH_ATTR_LLADDR | NEIGH_ATTR_FAMILY | NEIGH_ATTR_IFINDEX | ((neigh->ce_mask & NEIGH_ATTR_DST) ? NEIGH_ATTR_DST: 0) | + ((neigh->ce_mask & NEIGH_ATTR_NHID) ? NEIGH_ATTR_NHID: 0) | ((neigh->ce_mask & NEIGH_ATTR_VLAN) ? NEIGH_ATTR_VLAN : 0)); else return (NEIGH_ATTR_LLADDR | NEIGH_ATTR_FAMILY | NEIGH_ATTR_MASTER | NEIGH_ATTR_VLAN); @@ -419,6 +423,11 @@ int rtnl_neigh_parse(struct nlmsghdr *n, struct rtnl_neigh **result) neigh->ce_mask |= NEIGH_ATTR_VLAN; } + if (tb[NDA_NH_ID]) { + neigh->n_nhid = nla_get_u32(tb[NDA_NH_ID]); + neigh->ce_mask |= NEIGH_ATTR_NHID; + } + /* * Get the bridge index for AF_BRIDGE family entries */ @@ -511,6 +520,9 @@ static void neigh_dump_line(struct nl_object *a, struct nl_dump_params *p) if (n->ce_mask & NEIGH_ATTR_VLAN) nl_dump(p, "vlan %d ", n->n_vlan); + if (n->ce_mask & NEIGH_ATTR_NHID) + nl_dump(p, "nhid %d ", n->n_nhid); + if (n->ce_mask & NEIGH_ATTR_MASTER) { if (link_cache) nl_dump(p, "%s ", rtnl_link_i2name(link_cache, n->n_master, @@ -1066,6 +1078,19 @@ int rtnl_neigh_get_master(struct rtnl_neigh *neigh) { return neigh->n_master; } +void rtnl_neigh_set_nhid(struct rtnl_neigh *neigh, uint32_t nhid) +{ + neigh->n_nhid = nhid; + neigh->ce_mask |= NEIGH_ATTR_NHID; +} + +uint32_t rtnl_neigh_get_nhid(struct rtnl_neigh *neigh) { + if (neigh->ce_mask & NEIGH_ATTR_NHID) + return neigh->n_nhid; + else + return 0; +} + /** @} */ static struct nl_object_ops neigh_obj_ops = { diff --git a/lib/route/nh.c b/lib/route/nh.c new file mode 100644 index 000000000..e3701eb51 --- /dev/null +++ b/lib/route/nh.c @@ -0,0 +1,529 @@ +/* SPDX-License-Identnhier: LGPL-2.1-only */ +/* + * Copyright (c) 2022 Stanislav Zaikin + */ + +#include +#include +#include +#include + +/** @cond SKIP */ +#define NH_ATTR_FLAGS (1 << 0) +#define NH_ATTR_ID (1 << 1) +#define NH_ATTR_GATEWAY (1 << 2) +#define NH_ATTR_OIF (1 << 3) +#define NH_ATTR_GROUP (1 << 4) +#define NH_ATTR_MASTER (1 << 5) +#define NH_ATTR_FLAG_FDB (1 << 6) +#define NH_ATTR_FLAG_BLACKHOLE (1 << 7) +#define NH_ATTR_FLAG_GROUPS (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 }, + [NHA_MASTER] = { .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(int len) +{ + nl_nh_group_t *nhg; + + if (!(nhg = calloc(1, sizeof(*nhg)))) + return NULL; + + if (!(nhg->entries = calloc(len, sizeof(*nhg->entries)))) + { + free (nhg); + return NULL; + } + + nhg->ce_refcnt = 1; + + NL_DBG(4, "Allocated new NHG object %p\n", nhg); + + return nhg; +} + +static void rtnl_nh_grp_free(nl_nh_group_t *nhg) +{ + if (!nhg) + return; + + if (nhg->ce_refcnt > 0) + NL_DBG(1, "Warning: Freeing NHG object in use...\n"); + + NL_DBG(4, "Freed NHG object %p\n", nhg); + free(nhg); + + return; +} + +static void rtnl_nh_grp_put(nl_nh_group_t *nhg) +{ + if (!nhg) + return; + + nhg->ce_refcnt--; + NL_DBG(4, "Returned NHG object reference %p, %i remaining\n", + nhg, nhg->ce_refcnt); + + if (nhg->ce_refcnt < 0) + BUG(); + + if (nhg->ce_refcnt <= 0) + rtnl_nh_grp_free(nhg); + + return; +} + +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->nh_master = src->nh_master; + dst->ce_mask = src->ce_mask; + + 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 nhkey_sz; + struct nexthop_hash_key { + uint32_t nh_id; + } __attribute__((packed)) nhkey; + + nhkey_sz = sizeof(nhkey); + nhkey.nh_id = nexthop->nh_id; + + *hashkey = nl_hash(&nhkey, nhkey_sz, 0) % table_sz; + + NL_DBG(5, "nexthop %p key (nhid %d) keysz %d, hash 0x%x\n", + nexthop, nhkey.nh_id, nhkey_sz, *hashkey); + + 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; +} + +uint32_t rtnl_nh_get_group_entry(struct rtnl_nh *nexthop, int n) +{ + if (!(nexthop->ce_mask & NH_ATTR_GROUP) || !nexthop->nh_group) + return 0; + + if (n >= nexthop->nh_group->size) + return 0; + + 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; + + return nexthop->nh_group->size; +} + +static int rtnl_nh_group_info(int len, struct nexthop_grp **vi, + nl_nh_group_t **nvi) +{ + int cur = 0; + nl_nh_group_t *ret; + + if (len <= 0) + return 0; + + if (!(ret = rtnl_nh_grp_alloc(len))) + return -1; + + ret->size = len; + cur = 0; + while (cur < len) { + ret->entries[cur].nh_id = vi[cur]->id; + ret->entries[cur].weight = vi[cur]->weight; + cur++; + } + + *nvi = ret; + return 0; +} + +uint32_t rtnl_nh_get_id(struct rtnl_nh *nh) +{ + if (nh->ce_mask & NH_ATTR_ID) + return nh->nh_id; + + return 0; +} + +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 *nhi; + struct nlattr *tb[NHA_MAX+1]; + struct nexthop_grp *nexthop_grp_info[2]; + int err, family, list_len; + nl_nh_group_t *nh_group = NULL; + + nexthop = rtnl_nh_alloc(); + if (nexthop == NULL) + return -NLE_NOMEM; + + nexthop->ce_msgtype = n->nlmsg_type; + + if (!nlmsg_valid_hdr(n, sizeof(*nhi))) + return -NLE_MSG_TOOSHORT; + + nhi = nlmsg_data(n); + nexthop->nh_family = family = nhi->nh_family; + nexthop->nh_flags = nhi->nh_flags; + nexthop->nh_scope = nhi->nh_scope; + nexthop->nh_protocol = nhi->nh_protocol; + nexthop->ce_mask = (NH_ATTR_FLAGS); + + err = nlmsg_parse(n, sizeof(*nhi), 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]) { + list_len = 0; + uint8_t *data = nla_data (tb[NHA_GROUP]); + size_t len = nla_len (tb[NHA_GROUP]); + size_t cnt = len / sizeof (struct nexthop_grp); + if( cnt * sizeof (struct nexthop_grp ) != len ) { + return err; + } + + for (list_len = 0; list_len < cnt; list_len++) { + nexthop_grp_info[list_len] = (void*)data; + data += sizeof (struct nexthop_grp); + } + + err = rtnl_nh_group_info(list_len, nexthop_grp_info, + &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 0; + + return 0; +} + +static void dump_nh_group(nl_nh_group_t *group, + struct nl_dump_params *dp) +{ + nl_dump(dp, " nh_grp:"); + for (int 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) +{ + char buf[128]; + struct rtnl_nh *nh = nl_object_priv (obj); + + if (nh->ce_mask & NH_ATTR_ID) + nl_dump(dp, "id %u", nh->nh_id); + + 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_OIF) + nl_dump(dp, " oif %d", nh->nh_oif); + + if (nh->ce_mask & NH_ATTR_GROUP) + dump_nh_group (nh->nh_group, dp); + + nl_dump(dp, " scope %s", + rtnl_scope2str(nh->nh_scope, buf, sizeof(buf))); + + nl_dump(dp, " protocol %s", + rtnl_route_proto2str(nh->nh_protocol, buf, sizeof(buf))); + + if (nh->ce_mask & NH_ATTR_FLAG_BLACKHOLE) + nl_dump(dp, " blackhole"); + + if (nh->ce_mask & NH_ATTR_FLAG_FDB) + nl_dump(dp, " fdb"); + + if (nh->ce_mask & NH_ATTR_FLAG_GROUPS) + nl_dump(dp, " groups"); + + nl_dump(dp, "\n"); +} + +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 NH_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, NH_ATTR_##ATTR, a, b, EXPR) + + diff |= NH_DIFF(ID, src->nh_id != dst->nh_id); + diff |= NH_DIFF(GATEWAY, nl_addr_cmp(src->nh_gateway, + dst->nh_gateway)); + diff |= NH_DIFF(OIF, src->nh_oif != dst->nh_oif); +#undef NH_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 == nhid) { + nl_object_get((struct nl_object *) nh); + return nh; + } + } + + return NULL; +} + +/** + * Allocate nexthop cache and fill in all configured nexthops. + * @arg sk Netlink 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 netlink + * 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. + * + * @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 Netlink 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 netlink + * 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. + * + * @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/nexthop", + .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/nexthop", + .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 __init nexthop_init(void) +{ + nl_cache_mngt_register(&rtnl_nh_ops); +} + +static void __exit nexthop_exit(void) +{ + nl_cache_mngt_unregister(&rtnl_nh_ops); +} + + diff --git a/lib/route/route_utils.c b/lib/route/route_utils.c index 2a196f22b..07fa58b20 100644 --- a/lib/route/route_utils.c +++ b/lib/route/route_utils.c @@ -102,6 +102,7 @@ static void __init init_proto_names(void) add_proto_name(RTPROT_KERNEL, "kernel"); add_proto_name(RTPROT_BOOT, "boot"); add_proto_name(RTPROT_STATIC, "static"); + add_proto_name(RTPROT_ZEBRA, "zebra"); } static void __exit release_proto_names(void) diff --git a/libnl-cli-3.sym b/libnl-cli-3.sym index 71ff2ebeb..be8a9ecb7 100644 --- a/libnl-cli-3.sym +++ b/libnl-cli-3.sym @@ -114,4 +114,6 @@ global: nl_cli_alloc_cache_flags; nl_cli_link_alloc_cache_flags; nl_cli_link_alloc_cache_family_flags; + nl_cli_nh_alloc; + nl_cli_nh_alloc_cache; } libnl_3; diff --git a/libnl-route-3.sym b/libnl-route-3.sym index e54bc6dd7..21cf381d0 100644 --- a/libnl-route-3.sym +++ b/libnl-route-3.sym @@ -1267,4 +1267,18 @@ global: rtnl_nat_set_mask; rtnl_nat_set_new_addr; rtnl_nat_set_old_addr; + rtnl_nh_alloc_cache; + rtnl_nh_alloc; + rtnl_nh_clone; + rtnl_nh_put; + rtnl_nh_get; + rtnl_nh_compare; + rtnl_nh_dump; + rtnl_nh_set_gateway; + rtnl_nh_get_gateway; + rtnl_nh_set_fdb; + rtnl_nh_set_fdb; + rtnl_nh_get_group_entry; + rtnl_nh_get_group_size; + rtnl_nh_get_id; } libnl_3_6; diff --git a/src/lib/nh.c b/src/lib/nh.c new file mode 100644 index 000000000..9d7059d35 --- /dev/null +++ b/src/lib/nh.c @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ +/* + * Copyright (c) 2022 Stanislav Zaikin + */ + +/** + * @ingroup cli + * @defgroup cli_nh Nexthops + * + * @{ + */ + +#include +#include + +struct rtnl_nh *nl_cli_nh_alloc(void) +{ + struct rtnl_nh *nexthop; + + nexthop = rtnl_nh_alloc(); + if (!nexthop) + nl_cli_fatal(ENOMEM, "Unable to allocate nexthop object"); + + return nexthop; +} + +struct nl_cache *nl_cli_nh_alloc_cache(struct nl_sock *sock) +{ + struct nl_cache *cache; + int err; + + if ((err = rtnl_nh_alloc_cache(sock, AF_UNSPEC, &cache)) < 0) + nl_cli_fatal(err, "Unable to allocate nexthop cache: %s", + nl_geterror(err)); + + nl_cache_mngt_provide(cache); + + return cache; +} + + +/** @} */ diff --git a/src/nl-nh-list.c b/src/nl-nh-list.c new file mode 100644 index 000000000..6cc0b6acf --- /dev/null +++ b/src/nl-nh-list.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ +/* + * Copyright (c) 2022 Stanislav Zaikin + */ + +#include +#include + +#include + +static void print_usage(void) +{ + printf( +"Usage: nl-nh-list [OPTIONS]... \n" +"\n" +"OPTIONS\n" +" -h, --help Show this help text.\n" +" -v, --version Show versioning information.\n" + ); + exit(0); +} + +int main(int argc, char *argv[]) +{ + struct nl_sock *sock; + struct nl_cache *link_cache; + struct nl_dump_params params = { + .dp_type = NL_DUMP_LINE, + .dp_fd = stdout, + }; + + sock = nl_cli_alloc_socket(); + nl_cli_connect(sock, NETLINK_ROUTE); + + for (;;) { + int c, optidx = 0; + static struct option long_opts[] = { + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "hv", long_opts, &optidx); + if (c == -1) + break; + + switch (c) { + case 'h': print_usage(); break; + case 'v': nl_cli_print_version(); break; + } + } + + link_cache = nl_cli_nh_alloc_cache(sock); + + nl_cache_dump(link_cache, ¶ms); + + return 0; +}