diff --git a/.gitignore b/.gitignore index 15979570b..e3761a517 100644 --- a/.gitignore +++ b/.gitignore @@ -97,6 +97,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 0ac63d5fc..b0e47ce11 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 \ @@ -212,6 +213,7 @@ libnlinclude_netlink_cli_HEADERS = \ include/netlink/cli/link.h \ include/netlink/cli/mdb.h \ include/netlink/cli/neigh.h \ + include/netlink/cli/nh.h \ include/netlink/cli/qdisc.h \ include/netlink/cli/route.h \ include/netlink/cli/rule.h \ @@ -253,6 +255,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 \ @@ -436,6 +439,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 \ @@ -631,6 +635,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 \ @@ -714,6 +719,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 \ @@ -813,6 +819,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..5e67a7eaf 100644 --- a/include/linux-private/linux/neighbour.h +++ b/include/linux-private/linux/neighbour.h @@ -28,6 +28,12 @@ enum { NDA_MASTER, NDA_LINK_NETNSID, NDA_SRC_VNI, + NDA_PROTOCOL, /* Originator of entry */ + NDA_NH_ID, + NDA_FDB_EXT_ATTRS, + NDA_FLAGS_EXT, + NDA_NDM_STATE_MASK, + NDA_NDM_FLAGS_MASK, __NDA_MAX }; @@ -37,13 +43,17 @@ enum { * Neighbor Cache Entry Flags */ -#define NTF_USE 0x01 -#define NTF_SELF 0x02 -#define NTF_MASTER 0x04 -#define NTF_PROXY 0x08 /* == ATF_PUBL */ -#define NTF_EXT_LEARNED 0x10 -#define NTF_OFFLOADED 0x20 -#define NTF_ROUTER 0x80 +#define NTF_USE (1 << 0) +#define NTF_SELF (1 << 1) +#define NTF_MASTER (1 << 2) +#define NTF_PROXY (1 << 3) /* == ATF_PUBL */ +#define NTF_EXT_LEARNED (1 << 4) +#define NTF_OFFLOADED (1 << 5) +#define NTF_STICKY (1 << 6) +#define NTF_ROUTER (1 << 7) +/* Extended flags under NDA_FLAGS_EXT: */ +#define NTF_EXT_MANAGED (1 << 0) +#define NTF_EXT_LOCKED (1 << 1) /* * Neighbor Cache Entry States. @@ -61,9 +71,27 @@ enum { #define NUD_PERMANENT 0x80 #define NUD_NONE 0x00 -/* NUD_NOARP & NUD_PERMANENT are pseudostates, they never change - and make no address resolution or NUD. - NUD_PERMANENT also cannot be deleted by garbage collectors. +/* NUD_NOARP & NUD_PERMANENT are pseudostates, they never change and make no + * address resolution or NUD. + * + * NUD_PERMANENT also cannot be deleted by garbage collectors. This holds true + * for dynamic entries with NTF_EXT_LEARNED flag as well. However, upon carrier + * down event, NUD_PERMANENT entries are not flushed whereas NTF_EXT_LEARNED + * flagged entries explicitly are (which is also consistent with the routing + * subsystem). + * + * When NTF_EXT_LEARNED is set for a bridge fdb entry the different cache entry + * states don't make sense and thus are ignored. Such entries don't age and + * can roam. + * + * NTF_EXT_MANAGED flagged neigbor entries are managed by the kernel on behalf + * of a user space control plane, and automatically refreshed so that (if + * possible) they remain in NUD_REACHABLE state. + * + * NTF_EXT_LOCKED flagged bridge FDB entries are entries generated by the + * bridge in response to a host trying to communicate via a locked bridge port + * with MAB enabled. Their purpose is to notify user space that a host requires + * authentication. */ struct nda_cacheinfo { @@ -132,6 +160,7 @@ enum { NDTPA_QUEUE_LENBYTES, /* u32 */ NDTPA_MCAST_REPROBES, /* u32 */ NDTPA_PAD, + NDTPA_INTERVAL_PROBE_TIME_MS, /* u64, msecs */ __NDTPA_MAX }; #define NDTPA_MAX (__NDTPA_MAX - 1) @@ -169,4 +198,27 @@ enum { }; #define NDTA_MAX (__NDTA_MAX - 1) + /* FDB activity notification bits used in NFEA_ACTIVITY_NOTIFY: + * - FDB_NOTIFY_BIT - notify on activity/expire for any entry + * - FDB_NOTIFY_INACTIVE_BIT - mark as inactive to avoid multiple notifications + */ +enum { + FDB_NOTIFY_BIT = (1 << 0), + FDB_NOTIFY_INACTIVE_BIT = (1 << 1) +}; + +/* embedded into NDA_FDB_EXT_ATTRS: + * [NDA_FDB_EXT_ATTRS] = { + * [NFEA_ACTIVITY_NOTIFY] + * ... + * } + */ +enum { + NFEA_UNSPEC, + NFEA_ACTIVITY_NOTIFY, + NFEA_DONT_REFRESH, + __NFEA_MAX +}; +#define NFEA_MAX (__NFEA_MAX - 1) + #endif 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..2132e941b 100644 --- a/include/linux-private/linux/rtnetlink.h +++ b/include/linux-private/linux/rtnetlink.h @@ -146,6 +146,8 @@ enum { #define RTM_NEWSTATS RTM_NEWSTATS RTM_GETSTATS = 94, #define RTM_GETSTATS RTM_GETSTATS + RTM_SETSTATS, +#define RTM_SETSTATS RTM_SETSTATS RTM_NEWCACHEREPORT = 96, #define RTM_NEWCACHEREPORT RTM_NEWCACHEREPORT @@ -157,6 +159,41 @@ enum { RTM_GETCHAIN, #define RTM_GETCHAIN RTM_GETCHAIN + RTM_NEWNEXTHOP = 104, +#define RTM_NEWNEXTHOP RTM_NEWNEXTHOP + RTM_DELNEXTHOP, +#define RTM_DELNEXTHOP RTM_DELNEXTHOP + RTM_GETNEXTHOP, +#define RTM_GETNEXTHOP RTM_GETNEXTHOP + + RTM_NEWLINKPROP = 108, +#define RTM_NEWLINKPROP RTM_NEWLINKPROP + RTM_DELLINKPROP, +#define RTM_DELLINKPROP RTM_DELLINKPROP + RTM_GETLINKPROP, +#define RTM_GETLINKPROP RTM_GETLINKPROP + + RTM_NEWVLAN = 112, +#define RTM_NEWNVLAN RTM_NEWVLAN + RTM_DELVLAN, +#define RTM_DELVLAN RTM_DELVLAN + RTM_GETVLAN, +#define RTM_GETVLAN RTM_GETVLAN + + RTM_NEWNEXTHOPBUCKET = 116, +#define RTM_NEWNEXTHOPBUCKET RTM_NEWNEXTHOPBUCKET + RTM_DELNEXTHOPBUCKET, +#define RTM_DELNEXTHOPBUCKET RTM_DELNEXTHOPBUCKET + RTM_GETNEXTHOPBUCKET, +#define RTM_GETNEXTHOPBUCKET RTM_GETNEXTHOPBUCKET + + RTM_NEWTUNNEL = 120, +#define RTM_NEWTUNNEL RTM_NEWTUNNEL + RTM_DELTUNNEL, +#define RTM_DELTUNNEL RTM_DELTUNNEL + RTM_GETTUNNEL, +#define RTM_GETTUNNEL RTM_GETTUNNEL + __RTM_MAX, #define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) }; @@ -236,12 +273,12 @@ enum { /* rtm_protocol */ -#define RTPROT_UNSPEC 0 -#define RTPROT_REDIRECT 1 /* Route installed by ICMP redirects; - not used by current IPv4 */ -#define RTPROT_KERNEL 2 /* Route installed by kernel */ -#define RTPROT_BOOT 3 /* Route installed during boot */ -#define RTPROT_STATIC 4 /* Route installed by administrator */ +#define RTPROT_UNSPEC 0 +#define RTPROT_REDIRECT 1 /* Route installed by ICMP redirects; + not used by current IPv4 */ +#define RTPROT_KERNEL 2 /* Route installed by kernel */ +#define RTPROT_BOOT 3 /* Route installed during boot */ +#define RTPROT_STATIC 4 /* Route installed by administrator */ /* Values of protocol >= RTPROT_STATIC are not interpreted by kernel; they are just passed from user and back as is. @@ -250,22 +287,24 @@ enum { avoid conflicts. */ -#define RTPROT_GATED 8 /* Apparently, GateD */ -#define RTPROT_RA 9 /* RDISC/ND router advertisements */ -#define RTPROT_MRT 10 /* Merit MRT */ -#define RTPROT_ZEBRA 11 /* Zebra */ -#define RTPROT_BIRD 12 /* BIRD */ -#define RTPROT_DNROUTED 13 /* DECnet routing daemon */ -#define RTPROT_XORP 14 /* XORP */ -#define RTPROT_NTK 15 /* Netsukuku */ -#define RTPROT_DHCP 16 /* DHCP client */ -#define RTPROT_MROUTED 17 /* Multicast daemon */ -#define RTPROT_BABEL 42 /* Babel daemon */ -#define RTPROT_BGP 186 /* BGP Routes */ -#define RTPROT_ISIS 187 /* ISIS Routes */ -#define RTPROT_OSPF 188 /* OSPF Routes */ -#define RTPROT_RIP 189 /* RIP Routes */ -#define RTPROT_EIGRP 192 /* EIGRP Routes */ +#define RTPROT_GATED 8 /* Apparently, GateD */ +#define RTPROT_RA 9 /* RDISC/ND router advertisements */ +#define RTPROT_MRT 10 /* Merit MRT */ +#define RTPROT_ZEBRA 11 /* Zebra */ +#define RTPROT_BIRD 12 /* BIRD */ +#define RTPROT_DNROUTED 13 /* DECnet routing daemon */ +#define RTPROT_XORP 14 /* XORP */ +#define RTPROT_NTK 15 /* Netsukuku */ +#define RTPROT_DHCP 16 /* DHCP client */ +#define RTPROT_MROUTED 17 /* Multicast daemon */ +#define RTPROT_KEEPALIVED 18 /* Keepalived daemon */ +#define RTPROT_BABEL 42 /* Babel daemon */ +#define RTPROT_OPENR 99 /* Open Routing (Open/R) Routes */ +#define RTPROT_BGP 186 /* BGP Routes */ +#define RTPROT_ISIS 187 /* ISIS Routes */ +#define RTPROT_OSPF 188 /* OSPF Routes */ +#define RTPROT_RIP 189 /* RIP Routes */ +#define RTPROT_EIGRP 192 /* EIGRP Routes */ /* rtm_scope @@ -295,6 +334,13 @@ enum rt_scope_t { #define RTM_F_PREFIX 0x800 /* Prefix addresses */ #define RTM_F_LOOKUP_TABLE 0x1000 /* set rtm_table to FIB lookup result */ #define RTM_F_FIB_MATCH 0x2000 /* return full fib lookup match */ +#define RTM_F_OFFLOAD 0x4000 /* route is offloaded */ +#define RTM_F_TRAP 0x8000 /* route is trapping packets */ +#define RTM_F_OFFLOAD_FAILED 0x20000000 /* route offload failed, this value + * is chosen to avoid conflicts with + * other flags defined in + * include/uapi/linux/ipv6_route.h + */ /* Reserved table identifiers */ @@ -342,6 +388,7 @@ enum rtattr_type_t { RTA_IP_PROTO, RTA_SPORT, RTA_DPORT, + RTA_NH_ID, __RTA_MAX }; @@ -371,11 +418,13 @@ struct rtnexthop { #define RTNH_F_DEAD 1 /* Nexthop is dead (used by multipath) */ #define RTNH_F_PERVASIVE 2 /* Do recursive gateway lookup */ #define RTNH_F_ONLINK 4 /* Gateway is forced on link */ -#define RTNH_F_OFFLOAD 8 /* offloaded route */ +#define RTNH_F_OFFLOAD 8 /* Nexthop is offloaded */ #define RTNH_F_LINKDOWN 16 /* carrier-down on nexthop */ #define RTNH_F_UNRESOLVED 32 /* The entry is unresolved (ipmr) */ +#define RTNH_F_TRAP 64 /* Nexthop is trapping packets */ -#define RTNH_COMPARE_MASK (RTNH_F_DEAD | RTNH_F_LINKDOWN | RTNH_F_OFFLOAD) +#define RTNH_COMPARE_MASK (RTNH_F_DEAD | RTNH_F_LINKDOWN | \ + RTNH_F_OFFLOAD | RTNH_F_TRAP) /* Macros to handle hexthops */ @@ -391,7 +440,7 @@ struct rtnexthop { /* RTA_VIA */ struct rtvia { __kernel_sa_family_t rtvia_family; - __u8 rtvia_addr[0]; + __u8 rtvia_addr[]; }; /* RTM_CACHEINFO */ @@ -585,11 +634,18 @@ enum { TCA_HW_OFFLOAD, TCA_INGRESS_BLOCK, TCA_EGRESS_BLOCK, + TCA_DUMP_FLAGS, + TCA_EXT_WARN_MSG, __TCA_MAX }; #define TCA_MAX (__TCA_MAX - 1) +#define TCA_DUMP_FLAGS_TERSE (1 << 0) /* Means that in dump user gets only basic + * data necessary to identify the objects + * (handle, cookie, etc.) and stats. + */ + #define TCA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcmsg)))) #define TCA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcmsg)) @@ -702,6 +758,16 @@ 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_BRVLAN, +#define RTNLGRP_BRVLAN RTNLGRP_BRVLAN + RTNLGRP_MCTP_IFADDR, +#define RTNLGRP_MCTP_IFADDR RTNLGRP_MCTP_IFADDR + RTNLGRP_TUNNEL, +#define RTNLGRP_TUNNEL RTNLGRP_TUNNEL + RTNLGRP_STATS, +#define RTNLGRP_STATS RTNLGRP_STATS __RTNLGRP_MAX }; #define RTNLGRP_MAX (__RTNLGRP_MAX - 1) @@ -721,6 +787,7 @@ enum { TCA_ROOT_FLAGS, TCA_ROOT_COUNT, TCA_ROOT_TIME_DELTA, /* in msecs */ + TCA_ROOT_EXT_WARN_MSG, __TCA_ROOT_MAX, #define TCA_ROOT_MAX (__TCA_ROOT_MAX - 1) }; @@ -729,18 +796,28 @@ enum { #define TA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcamsg)) /* tcamsg flags stored in attribute TCA_ROOT_FLAGS * - * TCA_FLAG_LARGE_DUMP_ON user->kernel to request for larger than TCA_ACT_MAX_PRIO - * actions in a dump. All dump responses will contain the number of actions - * being dumped stored in for user app's consumption in TCA_ROOT_COUNT + * TCA_ACT_FLAG_LARGE_DUMP_ON user->kernel to request for larger than + * TCA_ACT_MAX_PRIO actions in a dump. All dump responses will contain the + * number of actions being dumped stored in for user app's consumption in + * TCA_ROOT_COUNT + * + * TCA_ACT_FLAG_TERSE_DUMP user->kernel to request terse (brief) dump that only + * includes essential action info (kind, index, etc.) * */ #define TCA_FLAG_LARGE_DUMP_ON (1 << 0) +#define TCA_ACT_FLAG_LARGE_DUMP_ON TCA_FLAG_LARGE_DUMP_ON +#define TCA_ACT_FLAG_TERSE_DUMP (1 << 1) /* New extended info filters for IFLA_EXT_MASK */ #define RTEXT_FILTER_VF (1 << 0) #define RTEXT_FILTER_BRVLAN (1 << 1) #define RTEXT_FILTER_BRVLAN_COMPRESSED (1 << 2) #define RTEXT_FILTER_SKIP_STATS (1 << 3) +#define RTEXT_FILTER_MRP (1 << 4) +#define RTEXT_FILTER_CFM_CONFIG (1 << 5) +#define RTEXT_FILTER_CFM_STATUS (1 << 6) +#define RTEXT_FILTER_MST (1 << 7) /* End of information exported to user level */ diff --git a/include/netlink-private/nl-auto.h b/include/netlink-private/nl-auto.h index 48951697a..9e09776a2 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..c36e01a84 100644 --- a/include/netlink-private/types.h +++ b/include/netlink-private/types.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -254,6 +255,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 +325,28 @@ struct rtnl_nexthop struct rtnl_nh_encap * rtnh_encap; }; +struct rtnl_nexthop_group +{ + struct nl_list_head nexthop_list; + int ce_refcnt; + uint32_t id; + uint8_t weight; +}; + +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; +}; + 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..ee08c8e6e --- /dev/null +++ b/include/netlink/cli/nh.h @@ -0,0 +1,30 @@ +/* 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_family(struct nl_sock *, int); +extern struct nl_cache *nl_cli_nh_alloc_cache_family_flags(struct nl_sock *, + int, unsigned int); +extern struct nl_cache *nl_cli_nh_alloc_cache(struct nl_sock *); +extern struct nl_cache *nl_cli_nh_alloc_cache_flags(struct nl_sock *, + unsigned int); + +extern void nl_cli_nh_parse_family(struct rtnl_nh *, char *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/netlink/route/neighbour.h b/include/netlink/route/neighbour.h index 376041496..657db5352 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 int rtnl_neigh_get_nhid(struct rtnl_neigh *, uint32_t *); + #ifdef __cplusplus } #endif diff --git a/include/netlink/route/nh.h b/include/netlink/route/nh.h new file mode 100644 index 000000000..fec82dd95 --- /dev/null +++ b/include/netlink/route/nh.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ +/* + * Copyright (c) 2022 Stanislav Zaikin + */ + +#ifndef NETLINK_ROUTE_NEXTHOP2_H_ +#define NETLINK_ROUTE_NEXTHOP2_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct rtnl_nh; + +typedef struct nl_nh_group_info { + uint32_t nh_id; /*!< VLAN number */ + uint8_t weight; /*!< VLAN QOS value */ +} nl_nh_group_info_t; + +typedef struct nl_nh_group { + int ce_refcnt; + unsigned size; + nl_nh_group_info_t *entries; +} nl_nh_group_t; + +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 int rtnl_nh_get_group_entry(struct rtnl_nh *, int n); +extern int rtnl_nh_get_group_size(struct rtnl_nh *); + +extern int 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 205415028..782f6a5d9 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; @@ -273,6 +274,7 @@ static uint64_t neigh_compare(struct nl_object *_a, struct nl_object *_b, diff |= _DIFF(NEIGH_ATTR_DST, nl_addr_cmp(a->n_dst, b->n_dst)); diff |= _DIFF(NEIGH_ATTR_MASTER, a->n_master != b->n_master); diff |= _DIFF(NEIGH_ATTR_VLAN, a->n_vlan != b->n_vlan); + diff |= _DIFF(NEIGH_ATTR_NHID, a->n_nhid != b->n_nhid); if (flags & LOOSE_COMPARISON) { diff |= _DIFF(NEIGH_ATTR_STATE, @@ -300,6 +302,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) @@ -316,6 +319,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); @@ -417,6 +421,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 */ @@ -509,6 +518,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 %u ", 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, @@ -719,6 +731,9 @@ static int build_neigh_msg(struct rtnl_neigh *tmpl, int cmd, int flags, if (tmpl->ce_mask & NEIGH_ATTR_VLAN) NLA_PUT_U16(msg, NDA_VLAN, tmpl->n_vlan); + if (tmpl->ce_mask & NEIGH_ATTR_NHID) + NLA_PUT_U32(msg, NDA_NH_ID, tmpl->n_nhid); + *result = msg; return 0; @@ -1064,6 +1079,20 @@ 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; +} + +int rtnl_neigh_get_nhid(struct rtnl_neigh *neigh, uint32_t *out_val) { + if (!(neigh->ce_mask & NEIGH_ATTR_NHID)) + return -NLE_NOATTR; + + *out_val = neigh->n_nhid; + return NLE_SUCCESS; +} + /** @} */ 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..84b0af5d6 --- /dev/null +++ b/lib/route/nh.c @@ -0,0 +1,568 @@ +/* SPDX-License-Identifier: 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_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; + } __attribute__((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 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); + diff |= NH_DIFF(GROUP, rtnh_nh_grp_cmp(src->nh_group, dst->nh_group)); + diff |= NH_DIFF(FLAG_FDB, false); + diff |= NH_DIFF(FLAG_GROUPS, false); + diff |= NH_DIFF(FLAG_BLACKHOLE, false); +#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 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); +} diff --git a/libnl-cli-3.sym b/libnl-cli-3.sym index 71523aeb0..75f03cd15 100644 --- a/libnl-cli-3.sym +++ b/libnl-cli-3.sym @@ -121,3 +121,9 @@ global: nl_cli_link_alloc_cache_flags; nl_cli_link_alloc_cache_family_flags; } libnl_3; + +libnl_3_8 { +global: + nl_cli_nh_alloc; + nl_cli_nh_alloc_cache; +} libnl_3_2_28; diff --git a/libnl-route-3.sym b/libnl-route-3.sym index 35161f7c0..275b61662 100644 --- a/libnl-route-3.sym +++ b/libnl-route-3.sym @@ -1286,4 +1286,20 @@ global: rtnl_link_bridge_set_vlan_protocol; rtnl_link_bridge_set_vlan_stats_enabled; rtnl_link_inet6_get_conf; + rtnl_neigh_get_nhid; + rtnl_neigh_set_nhid; + rtnl_nh_alloc; + rtnl_nh_alloc_cache; + rtnl_nh_clone; + rtnl_nh_compare; + rtnl_nh_dump; + rtnl_nh_get; + rtnl_nh_get_fdb; + rtnl_nh_get_gateway; + rtnl_nh_get_group_entry; + rtnl_nh_get_group_size; + rtnl_nh_get_id; + rtnl_nh_put; + rtnl_nh_set_fdb; + rtnl_nh_set_gateway; } libnl_3_7; diff --git a/src/lib/nh.c b/src/lib/nh.c new file mode 100644 index 000000000..7f9722bf6 --- /dev/null +++ b/src/lib/nh.c @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ +/* + * Copyright (c) 2022 Stanislav Zaikin + */ + +/** + * @ingroup cli + * @defgroup cli_nh nhs + * + * @{ + */ + +#include +#include +#include +#include + +struct rtnl_nh *nl_cli_nh_alloc(void) +{ + struct rtnl_nh *nh; + + nh = rtnl_nh_alloc(); + if (!nh) + nl_cli_fatal(ENOMEM, "Unable to allocate nh object"); + + return nh; +} + +struct nl_cache *nl_cli_nh_alloc_cache_family_flags(struct nl_sock *sock, + int family, + unsigned int flags) +{ + struct nl_cache *cache; + int err; + + if ((err = rtnl_nh_alloc_cache(sock, family, &cache)) < 0) + nl_cli_fatal(err, "Unable to allocate nh cache: %s", + nl_geterror(err)); + + nl_cache_mngt_provide(cache); + + return cache; +} + +struct nl_cache *nl_cli_nh_alloc_cache_family(struct nl_sock *sock, int family) +{ + return nl_cli_nh_alloc_cache_family_flags(sock, family, 0); +} + +struct nl_cache *nl_cli_nh_alloc_cache(struct nl_sock *sock) +{ + return nl_cli_nh_alloc_cache_family(sock, AF_UNSPEC); +} + +struct nl_cache *nl_cli_nh_alloc_cache_flags(struct nl_sock *sock, + unsigned int flags) +{ + return nl_cli_nh_alloc_cache_family_flags(sock, AF_UNSPEC, flags); +} + +/** @} */ diff --git a/src/nl-nh-list.c b/src/nl-nh-list.c new file mode 100644 index 000000000..17568df6f --- /dev/null +++ b/src/nl-nh-list.c @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: LGPL-2.1-only */ +/* + * Copyright (c) 2022 Stanislav Zaikin + */ + +#include +#include + +#include + +#include + +static void print_usage(void) +{ + printf("Usage: nl-nh-list [OPTIONS]... \n" + "\n" + "OPTIONS\n" + " --details Show detailed information of each link\n" + " -h, --help Show this help text.\n" + " -v, --version Show versioning information.\n" + "\n" + " -n, --name=NAME Name of link\n" + " -i, --index Interface index (unique identifier)\n" + " --family=NAME Link address family\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; + enum { + ARG_FAMILY = 257, + ARG_DETAILS, + }; + static struct option long_opts[] = { { "details", 0, 0, + ARG_DETAILS }, + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { "name", 1, 0, 'n' }, + { 0, 0, 0, 0 } }; + + c = getopt_long(argc, argv, "hvn:i:", long_opts, &optidx); + if (c == -1) + break; + + switch (c) { + case ARG_DETAILS: + params.dp_type = NL_DUMP_DETAILS; + break; + 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; +}