aboutsummaryrefslogtreecommitdiff
path: root/sys/netlink/route
diff options
context:
space:
mode:
Diffstat (limited to 'sys/netlink/route')
-rw-r--r--sys/netlink/route/common.h213
-rw-r--r--sys/netlink/route/iface.c857
-rw-r--r--sys/netlink/route/iface_drivers.c165
-rw-r--r--sys/netlink/route/ifaddrs.h90
-rw-r--r--sys/netlink/route/interface.h245
-rw-r--r--sys/netlink/route/neigh.c571
-rw-r--r--sys/netlink/route/neigh.h105
-rw-r--r--sys/netlink/route/nexthop.c1000
-rw-r--r--sys/netlink/route/nexthop.h102
-rw-r--r--sys/netlink/route/route.c972
-rw-r--r--sys/netlink/route/route.h366
-rw-r--r--sys/netlink/route/route_var.h101
12 files changed, 4787 insertions, 0 deletions
diff --git a/sys/netlink/route/common.h b/sys/netlink/route/common.h
new file mode 100644
index 000000000000..1bfb888b34c0
--- /dev/null
+++ b/sys/netlink/route/common.h
@@ -0,0 +1,213 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Common defines for all parts of the NETLINK_ROUTE family
+ */
+#ifndef _NETLINK_ROUTE_COMMON_H_
+#define _NETLINK_ROUTE_COMMON_H_
+
+/* Defined NETLINK_ROUTE messages */
+enum {
+ NL_RTM_BASE = 16,
+ NL_RTM_NEWLINK = 16, /* creates new interface */
+ NL_RTM_DELLINK = 17, /* deletes matching interface */
+ NL_RTM_GETLINK = 18, /* lists matching interfaces */
+ NL_RTM_SETLINK = 19, /* not supported */
+ NL_RTM_NEWADDR = 20, /* not supported */
+ NL_RTM_DELADDR = 21, /* not supported */
+ NL_RTM_GETADDR = 22, /* lists matching ifaddrs */
+ NL_RTM_NEWROUTE = 24, /* adds or changes a route */
+ NL_RTM_DELROUTE = 25, /* deletes matching route */
+ NL_RTM_GETROUTE = 26, /* lists matching routes */
+ NL_RTM_NEWNEIGH = 28, /* creates new arp/ndp entry */
+ NL_RTM_DELNEIGH = 29, /* deletes matching arp/ndp entry */
+ NL_RTM_GETNEIGH = 30, /* lists matching arp/ndp entry */
+ NL_RTM_NEWRULE = 32, /* not supported */
+ NL_RTM_DELRULE = 33, /* not supported */
+ NL_RTM_GETRULE = 34, /* not supported */
+ NL_RTM_NEWQDISC = 36, /* not supported */
+ NL_RTM_DELQDISC = 37, /* not supported */
+ NL_RTM_GETQDISC = 38, /* not supported */
+ NL_RTM_NEWTCLASS = 40, /* not supported */
+ NL_RTM_DELTCLASS = 41, /* not supported */
+ NL_RTM_GETTCLASS = 42, /* not supported */
+ NL_RTM_NEWTFILTER = 44, /* not supported */
+ NL_RTM_DELTFILTER = 45, /* not supported */
+ NL_RTM_GETTFILTER = 46, /* not supported */
+ NL_RTM_NEWACTION = 48, /* not supported */
+ NL_RTM_DELACTION = 49, /* not supported */
+ NL_RTM_GETACTION = 50, /* not supported */
+ NL_RTM_NEWPREFIX = 52, /* not supported */
+ NL_RTM_GETMULTICAST = 58, /* not supported */
+ NL_RTM_GETANYCAST = 62, /* not supported */
+ NL_RTM_NEWNEIGHTBL = 64, /* not supported */
+ NL_RTM_GETNEIGHTBL = 66, /* not supported */
+ NL_RTM_SETNEIGHTBL = 67, /* not supported */
+ NL_RTM_NEWNDUSEROPT = 68, /* not supported */
+ NL_RTM_NEWADDRLABEL = 72, /* not supported */
+ NL_RTM_DELADDRLABEL = 73, /* not supported */
+ NL_RTM_GETADDRLABEL = 74, /* not supported */
+ NL_RTM_GETDCB = 78, /* not supported */
+ NL_RTM_SETDCB = 79, /* not supported */
+ NL_RTM_NEWNETCONF = 80, /* not supported */
+ NL_RTM_GETNETCONF = 82, /* not supported */
+ NL_RTM_NEWMDB = 84, /* not supported */
+ NL_RTM_DELMDB = 85, /* not supported */
+ NL_RTM_GETMDB = 86, /* not supported */
+ NL_RTM_NEWNSID = 88, /* not supported */
+ NL_RTM_DELNSID = 89, /* not supported */
+ NL_RTM_GETNSID = 90, /* not supported */
+ NL_RTM_NEWSTATS = 92, /* not supported */
+ NL_RTM_GETSTATS = 94, /* not supported */
+ NL_RTM_NEWNEXTHOP = 104, /* creates new user nexhtop */
+ NL_RTM_DELNEXTHOP = 105, /* deletes matching nexthop */
+ NL_RTM_GETNEXTHOP = 106, /* lists created user nexthops */
+ __NL_RTM_MAX,
+};
+#define NL_RTM_MAX (((__NL_RTM_MAX + 3) & ~3) - 1)
+
+#ifndef _KERNEL
+/*
+ * RTM_* namespace clashes with BSD rtsock namespace.
+ * Use NL_RTM_ prefix in the kernel and map it to RTM_
+ * for userland.
+ */
+#define RTM_BASE NL_RTM_BASE
+#define RTM_NEWLINK NL_RTM_NEWLINK
+#define RTM_DELLINK NL_RTM_DELLINK
+#define RTM_GETLINK NL_RTM_GETLINK
+#define RTM_SETLINK NL_RTM_SETLINK
+#define RTM_NEWADDR NL_RTM_NEWADDR
+#define RTM_DELADDR NL_RTM_DELADDR
+#define RTM_GETADDR NL_RTM_GETADDR
+#define RTM_NEWROUTE NL_RTM_NEWROUTE
+#define RTM_DELROUTE NL_RTM_DELROUTE
+#define RTM_GETROUTE NL_RTM_GETROUTE
+#define RTM_NEWNEXTHOP NL_RTM_NEWNEXTHOP
+#define RTM_DELNEXTHOP NL_RTM_DELNEXTHOP
+#define RTM_GETNEXTHOP NL_RTM_GETNEXTHOP
+#endif
+
+#ifndef _KERNEL
+/* rtnetlink multicast groups - backwards compatibility for userspace */
+#define RTMGRP_LINK 0x01
+#define RTMGRP_NOTIFY 0x02
+#define RTMGRP_NEIGH 0x04
+#define RTMGRP_TC 0x08
+
+#define RTMGRP_IPV4_IFADDR 0x10
+#define RTMGRP_IPV4_MROUTE 0x20
+#define RTMGRP_IPV4_ROUTE 0x40
+#define RTMGRP_IPV4_RULE 0x80
+
+#define RTMGRP_IPV6_IFADDR 0x100
+#define RTMGRP_IPV6_MROUTE 0x200
+#define RTMGRP_IPV6_ROUTE 0x400
+#define RTMGRP_IPV6_IFINFO 0x800
+
+#define RTMGRP_DECnet_IFADDR 0x1000
+#define RTMGRP_DECnet_ROUTE 0x4000
+
+#define RTMGRP_IPV6_PREFIX 0x20000
+#endif
+
+/* Defined NETLINK_ROUTE multicast groups */
+enum rtnetlink_groups {
+ RTNLGRP_NONE,
+#define RTNLGRP_NONE RTNLGRP_NONE
+ RTNLGRP_LINK,
+#define RTNLGRP_LINK RTNLGRP_LINK
+ RTNLGRP_NOTIFY,
+#define RTNLGRP_NOTIFY RTNLGRP_NOTIFY
+ RTNLGRP_NEIGH,
+#define RTNLGRP_NEIGH RTNLGRP_NEIGH
+ RTNLGRP_TC,
+#define RTNLGRP_TC RTNLGRP_TC
+ RTNLGRP_IPV4_IFADDR,
+#define RTNLGRP_IPV4_IFADDR RTNLGRP_IPV4_IFADDR
+ RTNLGRP_IPV4_MROUTE,
+#define RTNLGRP_IPV4_MROUTE RTNLGRP_IPV4_MROUTE
+ RTNLGRP_IPV4_ROUTE,
+#define RTNLGRP_IPV4_ROUTE RTNLGRP_IPV4_ROUTE
+ RTNLGRP_IPV4_RULE,
+#define RTNLGRP_IPV4_RULE RTNLGRP_IPV4_RULE
+ RTNLGRP_IPV6_IFADDR,
+#define RTNLGRP_IPV6_IFADDR RTNLGRP_IPV6_IFADDR
+ RTNLGRP_IPV6_MROUTE,
+#define RTNLGRP_IPV6_MROUTE RTNLGRP_IPV6_MROUTE
+ RTNLGRP_IPV6_ROUTE,
+#define RTNLGRP_IPV6_ROUTE RTNLGRP_IPV6_ROUTE
+ RTNLGRP_IPV6_IFINFO,
+#define RTNLGRP_IPV6_IFINFO RTNLGRP_IPV6_IFINFO
+ RTNLGRP_DECnet_IFADDR,
+#define RTNLGRP_DECnet_IFADDR RTNLGRP_DECnet_IFADDR
+ RTNLGRP_NOP2,
+ RTNLGRP_DECnet_ROUTE,
+#define RTNLGRP_DECnet_ROUTE RTNLGRP_DECnet_ROUTE
+ RTNLGRP_DECnet_RULE,
+#define RTNLGRP_DECnet_RULE RTNLGRP_DECnet_RULE
+ RTNLGRP_NOP4,
+ RTNLGRP_IPV6_PREFIX,
+#define RTNLGRP_IPV6_PREFIX RTNLGRP_IPV6_PREFIX
+ RTNLGRP_IPV6_RULE,
+#define RTNLGRP_IPV6_RULE RTNLGRP_IPV6_RULE
+ RTNLGRP_ND_USEROPT,
+#define RTNLGRP_ND_USEROPT RTNLGRP_ND_USEROPT
+ RTNLGRP_PHONET_IFADDR,
+#define RTNLGRP_PHONET_IFADDR RTNLGRP_PHONET_IFADDR
+ RTNLGRP_PHONET_ROUTE,
+#define RTNLGRP_PHONET_ROUTE RTNLGRP_PHONET_ROUTE
+ RTNLGRP_DCB,
+#define RTNLGRP_DCB RTNLGRP_DCB
+ RTNLGRP_IPV4_NETCONF,
+#define RTNLGRP_IPV4_NETCONF RTNLGRP_IPV4_NETCONF
+ RTNLGRP_IPV6_NETCONF,
+#define RTNLGRP_IPV6_NETCONF RTNLGRP_IPV6_NETCONF
+ RTNLGRP_MDB,
+#define RTNLGRP_MDB RTNLGRP_MDB
+ RTNLGRP_MPLS_ROUTE,
+#define RTNLGRP_MPLS_ROUTE RTNLGRP_MPLS_ROUTE
+ RTNLGRP_NSID,
+#define RTNLGRP_NSID RTNLGRP_NSID
+ RTNLGRP_MPLS_NETCONF,
+#define RTNLGRP_MPLS_NETCONF RTNLGRP_MPLS_NETCONF
+ RTNLGRP_IPV4_MROUTE_R,
+#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_MAX
+};
+#define RTNLGRP_MAX (__RTNLGRP_MAX - 1)
+
+
+#endif
+
diff --git a/sys/netlink/route/iface.c b/sys/netlink/route/iface.c
new file mode 100644
index 000000000000..8db24b5507e4
--- /dev/null
+++ b/sys/netlink/route/iface.c
@@ -0,0 +1,857 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include "opt_inet.h"
+#include "opt_inet6.h"
+#include <sys/types.h>
+#include <sys/malloc.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/syslog.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_var.h>
+#include <net/if_clone.h>
+#include <net/route.h>
+#include <net/route/nhop.h>
+#include <net/route/route_ctl.h>
+#include <netlink/netlink.h>
+#include <netlink/netlink_ctl.h>
+#include <netlink/netlink_route.h>
+#include <netlink/route/route_var.h>
+
+#include <netinet6/scope6_var.h> /* scope deembedding */
+
+#define DEBUG_MOD_NAME nl_iface
+#define DEBUG_MAX_LEVEL LOG_DEBUG3
+#include <netlink/netlink_debug.h>
+_DECLARE_DEBUG(LOG_DEBUG);
+
+struct netlink_walkargs {
+ struct nl_writer *nw;
+ struct nlmsghdr hdr;
+ struct nlpcb *so;
+ uint32_t fibnum;
+ int family;
+ int error;
+ int count;
+ int dumped;
+};
+
+static eventhandler_tag ifdetach_event, ifattach_event, ifaddr_event;
+
+static SLIST_HEAD(, nl_cloner) nl_cloners = SLIST_HEAD_INITIALIZER(nl_cloners);
+
+static struct sx rtnl_cloner_lock;
+SX_SYSINIT(rtnl_cloner_lock, &rtnl_cloner_lock, "rtnl cloner lock");
+
+/*
+ * RTM_GETLINK request
+ * sendto(3, {{len=32, type=RTM_GETLINK, flags=NLM_F_REQUEST|NLM_F_DUMP, seq=1641940952, pid=0},
+ * {ifi_family=AF_INET, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0}}, 32, 0, NULL, 0) = 32
+ *
+ * Reply:
+ * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_ETHER, ifi_index=if_nametoindex("enp0s31f6"), ifi_flags=IFF_UP|IFF_BROADCAST|IFF_RUNNING|IFF_MULTICAST|IFF_LOWER_UP, ifi_change=0},
+{{nla_len=10, nla_type=IFLA_ADDRESS}, "\xfe\x54\x00\x52\x3e\x90"}
+
+[
+{{nla_len=14, nla_type=IFLA_IFNAME}, "enp0s31f6"},
+{{nla_len=8, nla_type=IFLA_TXQLEN}, 1000},
+{{nla_len=5, nla_type=IFLA_OPERSTATE}, 6},
+{{nla_len=5, nla_type=IFLA_LINKMODE}, 0},
+{{nla_len=8, nla_type=IFLA_MTU}, 1500},
+{{nla_len=8, nla_type=IFLA_MIN_MTU}, 68},
+ {{nla_len=8, nla_type=IFLA_MAX_MTU}, 9000},
+{{nla_len=8, nla_type=IFLA_GROUP}, 0},
+{{nla_len=8, nla_type=IFLA_PROMISCUITY}, 0},
+{{nla_len=8, nla_type=IFLA_NUM_TX_QUEUES}, 1},
+{{nla_len=8, nla_type=IFLA_GSO_MAX_SEGS}, 65535},
+{{nla_len=8, nla_type=IFLA_GSO_MAX_SIZE}, 65536},
+{{nla_len=8, nla_type=IFLA_NUM_RX_QUEUES}, 1},
+{{nla_len=5, nla_type=IFLA_CARRIER}, 1},
+{{nla_len=13, nla_type=IFLA_QDISC}, "fq_codel"},
+{{nla_len=8, nla_type=IFLA_CARRIER_CHANGES}, 2},
+{{nla_len=5, nla_type=IFLA_PROTO_DOWN}, 0},
+{{nla_len=8, nla_type=IFLA_CARRIER_UP_COUNT}, 1},
+{{nla_len=8, nla_type=IFLA_CARRIER_DOWN_COUNT}, 1},
+ */
+
+struct if_state {
+ uint8_t ifla_operstate;
+ uint8_t ifla_carrier;
+};
+
+static void
+get_operstate_ether(struct ifnet *ifp, struct if_state *pstate)
+{
+ struct ifmediareq ifmr = {};
+ int error;
+ error = (*ifp->if_ioctl)(ifp, SIOCGIFMEDIA, (void *)&ifmr);
+
+ if (error != 0) {
+ NL_LOG(LOG_DEBUG, "error calling SIOCGIFMEDIA on %s: %d",
+ if_name(ifp), error);
+ return;
+ }
+
+ switch (IFM_TYPE(ifmr.ifm_active)) {
+ case IFM_ETHER:
+ if (ifmr.ifm_status & IFM_ACTIVE) {
+ pstate->ifla_carrier = 1;
+ if (ifp->if_flags & IFF_MONITOR)
+ pstate->ifla_operstate = IF_OPER_DORMANT;
+ else
+ pstate->ifla_operstate = IF_OPER_UP;
+ } else
+ pstate->ifla_operstate = IF_OPER_DOWN;
+ }
+}
+
+static bool
+get_stats(struct nl_writer *nw, struct ifnet *ifp)
+{
+ struct rtnl_link_stats64 *stats;
+
+ int nla_len = sizeof(struct nlattr) + sizeof(*stats);
+ struct nlattr *nla = nlmsg_reserve_data(nw, nla_len, struct nlattr);
+ if (nla == NULL)
+ return (false);
+ nla->nla_type = IFLA_STATS64;
+ nla->nla_len = nla_len;
+ stats = (struct rtnl_link_stats64 *)(nla + 1);
+
+ stats->rx_packets = ifp->if_get_counter(ifp, IFCOUNTER_IPACKETS);
+ stats->tx_packets = ifp->if_get_counter(ifp, IFCOUNTER_OPACKETS);
+ stats->rx_bytes = ifp->if_get_counter(ifp, IFCOUNTER_IBYTES);
+ stats->tx_bytes = ifp->if_get_counter(ifp, IFCOUNTER_OBYTES);
+ stats->rx_errors = ifp->if_get_counter(ifp, IFCOUNTER_IERRORS);
+ stats->tx_errors = ifp->if_get_counter(ifp, IFCOUNTER_OERRORS);
+ stats->rx_dropped = ifp->if_get_counter(ifp, IFCOUNTER_IQDROPS);
+ stats->tx_dropped = ifp->if_get_counter(ifp, IFCOUNTER_OQDROPS);
+ stats->multicast = ifp->if_get_counter(ifp, IFCOUNTER_IMCASTS);
+ stats->rx_nohandler = ifp->if_get_counter(ifp, IFCOUNTER_NOPROTO);
+
+ return (true);
+}
+
+static void
+get_operstate(struct ifnet *ifp, struct if_state *pstate)
+{
+ pstate->ifla_operstate = IF_OPER_UNKNOWN;
+ pstate->ifla_carrier = 0; /* no carrier */
+
+ switch (ifp->if_type) {
+ case IFT_ETHER:
+ get_operstate_ether(ifp, pstate);
+ break;
+ case IFT_LOOP:
+ if (ifp->if_flags & IFF_UP) {
+ pstate->ifla_operstate = IF_OPER_UP;
+ pstate->ifla_carrier = 1;
+ } else
+ pstate->ifla_operstate = IF_OPER_DOWN;
+ break;
+ }
+}
+
+static unsigned
+ifp_flags_to_netlink(const struct ifnet *ifp)
+{
+ return (ifp->if_flags | ifp->if_drv_flags);
+}
+
+#define LLADDR_CONST(s) ((const void *)((s)->sdl_data + (s)->sdl_nlen))
+static bool
+dump_sa(struct nl_writer *nw, int attr, const struct sockaddr *sa)
+{
+ uint32_t addr_len = 0;
+ const void *addr_data = NULL;
+ struct in6_addr addr6;
+
+ if (sa == NULL)
+ return (true);
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ addr_len = sizeof(struct in_addr);
+ addr_data = &((const struct sockaddr_in *)sa)->sin_addr;
+ break;
+ case AF_INET6:
+ in6_splitscope(&((const struct sockaddr_in6 *)sa)->sin6_addr, &addr6, &addr_len);
+ addr_len = sizeof(struct in6_addr);
+ addr_data = &addr6;
+ break;
+ case AF_LINK:
+ addr_len = ((const struct sockaddr_dl *)sa)->sdl_alen;
+ addr_data = LLADDR_CONST((const struct sockaddr_dl *)sa);
+ break;
+ default:
+ NL_LOG(LOG_DEBUG, "unsupported family: %d, skipping", sa->sa_family);
+ return (true);
+ }
+
+ return (nlattr_add(nw, attr, addr_len, addr_data));
+}
+
+/*
+ * Dumps interface state, properties and metrics.
+ * @nw: message writer
+ * @ifp: target interface
+ * @hdr: template header
+ *
+ * This function is called without epoch and MAY sleep.
+ */
+static bool
+dump_iface(struct nl_writer *nw, struct ifnet *ifp, const struct nlmsghdr *hdr)
+{
+ struct ifinfomsg *ifinfo;
+
+ NL_LOG(LOG_DEBUG3, "dumping interface %s data", if_name(ifp));
+
+ if (!nlmsg_reply(nw, hdr, sizeof(struct ifinfomsg)))
+ goto enomem;
+
+ ifinfo = nlmsg_reserve_object(nw, struct ifinfomsg);
+ ifinfo->ifi_family = AF_UNSPEC;
+ ifinfo->__ifi_pad = 0;
+ ifinfo->ifi_type = ifp->if_type;
+ ifinfo->ifi_index = ifp->if_index;
+ ifinfo->ifi_flags = ifp_flags_to_netlink(ifp);
+ ifinfo->ifi_change = 0;
+
+ nlattr_add_string(nw, IFLA_IFNAME, if_name(ifp));
+
+ struct if_state ifs = {};
+ get_operstate(ifp, &ifs);
+
+ nlattr_add_u8(nw, IFLA_OPERSTATE, ifs.ifla_operstate);
+ nlattr_add_u8(nw, IFLA_CARRIER, ifs.ifla_carrier);
+
+/*
+ nlattr_add_u8(nw, IFLA_PROTO_DOWN, val);
+ nlattr_add_u8(nw, IFLA_LINKMODE, val);
+*/
+ if ((ifp->if_addr != NULL)) {
+ dump_sa(nw, IFLA_ADDRESS, ifp->if_addr->ifa_addr);
+ }
+
+ if ((ifp->if_broadcastaddr != NULL)) {
+ nlattr_add(nw, IFLA_BROADCAST, ifp->if_addrlen,
+ ifp->if_broadcastaddr);
+ }
+
+ nlattr_add_u32(nw, IFLA_MTU, ifp->if_mtu);
+/*
+ nlattr_add_u32(nw, IFLA_MIN_MTU, 60);
+ nlattr_add_u32(nw, IFLA_MAX_MTU, 9000);
+ nlattr_add_u32(nw, IFLA_GROUP, 0);
+*/
+ get_stats(nw, ifp);
+
+ uint32_t val = (ifp->if_flags & IFF_PROMISC) != 0;
+ nlattr_add_u32(nw, IFLA_PROMISCUITY, val);
+
+ if (nlmsg_end(nw))
+ return (true);
+
+enomem:
+ NL_LOG(LOG_DEBUG, "unable to dump interface %s state (ENOMEM)", if_name(ifp));
+ nlmsg_abort(nw);
+ return (false);
+}
+
+static bool
+check_ifmsg(void *hdr, struct nl_pstate *npt)
+{
+ struct ifinfomsg *ifm = hdr;
+
+ if (ifm->__ifi_pad != 0 || ifm->ifi_type != 0 ||
+ ifm->ifi_flags != 0 || ifm->ifi_change != 0) {
+ nlmsg_report_err_msg(npt,
+ "strict checking: non-zero values in ifinfomsg header");
+ return (false);
+ }
+
+ return (true);
+}
+
+#define _IN(_field) offsetof(struct ifinfomsg, _field)
+#define _OUT(_field) offsetof(struct nl_parsed_link, _field)
+static const struct nlfield_parser nlf_p_if[] = {
+ { .off_in = _IN(ifi_type), .off_out = _OUT(ifi_type), .cb = nlf_get_u16 },
+ { .off_in = _IN(ifi_index), .off_out = _OUT(ifi_index), .cb = nlf_get_u32 },
+};
+
+static const struct nlattr_parser nla_p_linfo[] = {
+ { .type = IFLA_INFO_KIND, .off = _OUT(ifla_cloner), .cb = nlattr_get_stringn },
+ { .type = IFLA_INFO_DATA, .off = _OUT(ifla_idata), .cb = nlattr_get_nla },
+};
+NL_DECLARE_ATTR_PARSER(linfo_parser, nla_p_linfo);
+
+static const struct nlattr_parser nla_p_if[] = {
+ { .type = IFLA_IFNAME, .off = _OUT(ifla_ifname), .cb = nlattr_get_string },
+ { .type = IFLA_MTU, .off = _OUT(ifla_mtu), .cb = nlattr_get_uint32 },
+ { .type = IFLA_LINK, .off = _OUT(ifi_index), .cb = nlattr_get_uint32 },
+ { .type = IFLA_LINKINFO, .arg = &linfo_parser, .cb = nlattr_get_nested },
+ { .type = IFLA_GROUP, .off = _OUT(ifla_group), .cb = nlattr_get_string },
+ { .type = IFLA_ALT_IFNAME, .off = _OUT(ifla_ifname), .cb = nlattr_get_string },
+};
+#undef _IN
+#undef _OUT
+NL_DECLARE_STRICT_PARSER(ifmsg_parser, struct ifinfomsg, check_ifmsg, nlf_p_if, nla_p_if);
+
+static bool
+match_iface(struct nl_parsed_link *attrs, struct ifnet *ifp)
+{
+ if (attrs->ifi_index != 0 && attrs->ifi_index != ifp->if_index)
+ return (false);
+ if (attrs->ifi_type != 0 && attrs->ifi_index != ifp->if_type)
+ return (false);
+ if (attrs->ifla_ifname != NULL && strcmp(attrs->ifla_ifname, if_name(ifp)))
+ return (false);
+ /* TODO: add group match */
+
+ return (true);
+}
+
+/*
+ * {nlmsg_len=52, nlmsg_type=RTM_GETLINK, nlmsg_flags=NLM_F_REQUEST, nlmsg_seq=1662842818, nlmsg_pid=0},
+ * {ifi_family=AF_PACKET, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0},
+ * [
+ * [{nla_len=10, nla_type=IFLA_IFNAME}, "vnet9"],
+ * [{nla_len=8, nla_type=IFLA_EXT_MASK}, RTEXT_FILTER_VF]
+ * ]
+ */
+static int
+rtnl_handle_getlink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+ struct epoch_tracker et;
+ struct ifnet *ifp;
+ int error = 0;
+
+ struct nl_parsed_link attrs = {};
+ error = nl_parse_nlmsg(hdr, &ifmsg_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ struct netlink_walkargs wa = {
+ .so = nlp,
+ .nw = npt->nw,
+ .hdr.nlmsg_pid = hdr->nlmsg_pid,
+ .hdr.nlmsg_seq = hdr->nlmsg_seq,
+ .hdr.nlmsg_flags = hdr->nlmsg_flags | NLM_F_MULTI,
+ .hdr.nlmsg_type = NL_RTM_NEWLINK,
+ };
+
+ /* Fast track for an interface w/ explicit index match */
+ if (attrs.ifi_index != 0) {
+ NET_EPOCH_ENTER(et);
+ ifp = ifnet_byindex_ref(attrs.ifi_index);
+ NET_EPOCH_EXIT(et);
+ NLP_LOG(LOG_DEBUG3, nlp, "fast track -> searching index %u", attrs.ifi_index);
+ if (ifp != NULL) {
+ if (match_iface(&attrs, ifp)) {
+ if (!dump_iface(wa.nw, ifp, &wa.hdr))
+ error = ENOMEM;
+ } else
+ error = ESRCH;
+ if_rele(ifp);
+ } else
+ error = ESRCH;
+ return (error);
+ }
+
+ /*
+ * Fetching some link properties require performing ioctl's that may be blocking.
+ * Address it by saving referenced pointers of the matching links,
+ * exiting from epoch and going through the list one-by-one.
+ */
+
+ NL_LOG(LOG_DEBUG2, "Start dump");
+
+ struct ifnet **match_array;
+ int offset = 0, base_count = 16; /* start with 128 bytes */
+ match_array = malloc(base_count * sizeof(void *), M_TEMP, M_NOWAIT);
+
+ NLP_LOG(LOG_DEBUG3, nlp, "MATCHING: index=%u type=%d name=%s",
+ attrs.ifi_index, attrs.ifi_type, attrs.ifla_ifname);
+ NET_EPOCH_ENTER(et);
+ CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
+ wa.count++;
+ if (match_iface(&attrs, ifp)) {
+ if (offset < base_count) {
+ if (!if_try_ref(ifp))
+ continue;
+ match_array[offset++] = ifp;
+ continue;
+ }
+ /* Too many matches, need to reallocate */
+ struct ifnet **new_array;
+ int sz = base_count * sizeof(void *);
+ base_count *= 2;
+ new_array = malloc(sz * 2, M_TEMP, M_NOWAIT);
+ if (new_array == NULL) {
+ error = ENOMEM;
+ break;
+ }
+ memcpy(new_array, match_array, sz);
+ free(match_array, M_TEMP);
+ match_array = new_array;
+ }
+ }
+ NET_EPOCH_EXIT(et);
+
+ NL_LOG(LOG_DEBUG2, "Matched %d interface(s), dumping", offset);
+ for (int i = 0; error == 0 && i < offset; i++) {
+ if (!dump_iface(wa.nw, match_array[i], &wa.hdr))
+ error = ENOMEM;
+ }
+ for (int i = 0; i < offset; i++)
+ if_rele(match_array[i]);
+ free(match_array, M_TEMP);
+
+ NL_LOG(LOG_DEBUG2, "End dump, iterated %d dumped %d", wa.count, wa.dumped);
+
+ if (!nlmsg_end_dump(wa.nw, error, &wa.hdr)) {
+ NL_LOG(LOG_DEBUG, "Unable to finalize the dump");
+ return (ENOMEM);
+ }
+
+ return (error);
+}
+
+/*
+ * sendmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base=[
+ * {nlmsg_len=60, nlmsg_type=RTM_NEWLINK, nlmsg_flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, nlmsg_seq=1662715618, nlmsg_pid=0},
+ * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0},
+ * {nla_len=11, nla_type=IFLA_IFNAME}, "dummy0"],
+ * [
+ * {nla_len=16, nla_type=IFLA_LINKINFO},
+ * [
+ * {nla_len=9, nla_type=IFLA_INFO_KIND}, "dummy"...
+ * ]
+ * ]
+ */
+
+static int
+rtnl_handle_dellink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+ struct epoch_tracker et;
+ struct ifnet *ifp;
+ int error;
+
+ struct nl_parsed_link attrs = {};
+ error = nl_parse_nlmsg(hdr, &ifmsg_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ NET_EPOCH_ENTER(et);
+ ifp = ifnet_byindex_ref(attrs.ifi_index);
+ NET_EPOCH_EXIT(et);
+ if (ifp == NULL) {
+ NLP_LOG(LOG_DEBUG, nlp, "unable to find interface %u", attrs.ifi_index);
+ return (ENOENT);
+ }
+ NLP_LOG(LOG_DEBUG3, nlp, "mapped ifindex %u to %s", attrs.ifi_index, if_name(ifp));
+
+ sx_xlock(&ifnet_detach_sxlock);
+ error = if_clone_destroy(if_name(ifp));
+ sx_xunlock(&ifnet_detach_sxlock);
+
+ NLP_LOG(LOG_DEBUG2, nlp, "deleting interface %s returned %d", if_name(ifp), error);
+
+ if_rele(ifp);
+ return (error);
+}
+
+static int
+rtnl_handle_newlink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+ struct nl_cloner *cloner;
+ int error;
+
+ struct nl_parsed_link attrs = {};
+ error = nl_parse_nlmsg(hdr, &ifmsg_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ if (attrs.ifla_ifname == NULL || strlen(attrs.ifla_ifname) == 0) {
+ /* Applications like ip(8) verify RTM_NEWLINK existance
+ * by calling it with empty arguments. Always return "innocent"
+ * error.
+ */
+ NLMSG_REPORT_ERR_MSG(npt, "empty IFLA_IFNAME attribute");
+ return (EPERM);
+ }
+
+ if (attrs.ifla_cloner == NULL || strlen(attrs.ifla_cloner) == 0) {
+ NLMSG_REPORT_ERR_MSG(npt, "empty IFLA_INFO_KIND attribute");
+ return (EINVAL);
+ }
+
+ sx_slock(&rtnl_cloner_lock);
+ SLIST_FOREACH(cloner, &nl_cloners, next) {
+ if (!strcmp(attrs.ifla_cloner, cloner->name)) {
+ error = cloner->create_f(&attrs, nlp, npt);
+ sx_sunlock(&rtnl_cloner_lock);
+ return (error);
+ }
+ }
+ sx_sunlock(&rtnl_cloner_lock);
+
+ /* TODO: load cloner module if not exists & privilege permits */
+ NLMSG_REPORT_ERR_MSG(npt, "interface type %s not supported", attrs.ifla_cloner);
+ return (ENOTSUP);
+
+ return (error);
+}
+
+/*
+
+{ifa_family=AF_INET, ifa_prefixlen=8, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_HOST, ifa_index=if_nametoindex("lo")},
+ [
+ {{nla_len=8, nla_type=IFA_ADDRESS}, inet_addr("127.0.0.1")},
+ {{nla_len=8, nla_type=IFA_LOCAL}, inet_addr("127.0.0.1")},
+ {{nla_len=7, nla_type=IFA_LABEL}, "lo"},
+ {{nla_len=8, nla_type=IFA_FLAGS}, IFA_F_PERMANENT},
+ {{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=4294967295, ifa_valid=4294967295, cstamp=3619, tstamp=3619}}]},
+---
+
+{{len=72, type=RTM_NEWADDR, flags=NLM_F_MULTI, seq=1642191126, pid=566735},
+ {ifa_family=AF_INET6, ifa_prefixlen=96, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_UNIVERSE, ifa_index=if_nametoindex("virbr0")},
+ [
+ {{nla_len=20, nla_type=IFA_ADDRESS}, inet_pton(AF_INET6, "2a01:4f8:13a:70c:ffff::1")},
+ {{nla_len=20, nla_type=IFA_CACHEINFO}, {ifa_prefered=4294967295, ifa_valid=4294967295, cstamp=4283, tstamp=4283}},
+ {{nla_len=8, nla_type=IFA_FLAGS}, IFA_F_PERMANENT}]},
+*/
+
+static uint8_t
+ifa_get_scope(const struct ifaddr *ifa)
+{
+ const struct sockaddr *sa;
+ uint8_t addr_scope = RT_SCOPE_UNIVERSE;
+
+ sa = ifa->ifa_addr;
+ switch (sa->sa_family) {
+ case AF_INET:
+ {
+ struct in_addr addr;
+ addr = ((const struct sockaddr_in *)sa)->sin_addr;
+ if (IN_LOOPBACK(addr.s_addr))
+ addr_scope = RT_SCOPE_HOST;
+ else if (IN_LINKLOCAL(addr.s_addr))
+ addr_scope = RT_SCOPE_LINK;
+ break;
+ }
+ case AF_INET6:
+ {
+ const struct in6_addr *addr;
+ addr = &((const struct sockaddr_in6 *)sa)->sin6_addr;
+ if (IN6_IS_ADDR_LOOPBACK(addr))
+ addr_scope = RT_SCOPE_HOST;
+ else if (IN6_IS_ADDR_LINKLOCAL(addr))
+ addr_scope = RT_SCOPE_LINK;
+ break;
+ }
+ }
+
+ return (addr_scope);
+}
+
+static uint8_t
+inet6_get_plen(const struct in6_addr *addr)
+{
+
+ return (bitcount32(addr->s6_addr32[0]) + bitcount32(addr->s6_addr32[1]) +
+ bitcount32(addr->s6_addr32[2]) + bitcount32(addr->s6_addr32[3]));
+}
+
+static uint8_t
+get_sa_plen(const struct sockaddr *sa)
+{
+ const struct in6_addr *paddr6;
+ const struct in_addr *paddr;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ if (sa == NULL)
+ return (32);
+ paddr = &(((const struct sockaddr_in *)sa)->sin_addr);
+ return bitcount32(paddr->s_addr);;
+ case AF_INET6:
+ if (sa == NULL)
+ return (128);
+ paddr6 = &(((const struct sockaddr_in6 *)sa)->sin6_addr);
+ return inet6_get_plen(paddr6);
+ }
+
+ return (0);
+}
+
+
+/*
+ * {'attrs': [('IFA_ADDRESS', '12.0.0.1'),
+ ('IFA_LOCAL', '12.0.0.1'),
+ ('IFA_LABEL', 'eth10'),
+ ('IFA_FLAGS', 128),
+ ('IFA_CACHEINFO', {'ifa_preferred': 4294967295, 'ifa_valid': 4294967295, 'cstamp': 63745746, 'tstamp': 63745746})],
+ */
+static bool
+dump_iface_addr(struct nl_writer *nw, struct ifnet *ifp, struct ifaddr *ifa,
+ const struct nlmsghdr *hdr)
+{
+ struct ifaddrmsg *ifamsg;
+ struct sockaddr *sa = ifa->ifa_addr;
+
+ NL_LOG(LOG_DEBUG3, "dumping ifa %p type %s(%d) for interface %s",
+ ifa, rib_print_family(sa->sa_family), sa->sa_family, if_name(ifp));
+
+ if (!nlmsg_reply(nw, hdr, sizeof(struct ifaddrmsg)))
+ goto enomem;
+
+ ifamsg = nlmsg_reserve_object(nw, struct ifaddrmsg);
+ ifamsg->ifa_family = sa->sa_family;
+ ifamsg->ifa_prefixlen = get_sa_plen(ifa->ifa_netmask);
+ ifamsg->ifa_flags = 0; // ifa_flags is useless
+ ifamsg->ifa_scope = ifa_get_scope(ifa);
+ ifamsg->ifa_index = ifp->if_index;
+
+ struct sockaddr *dst_sa = ifa->ifa_dstaddr;
+ if ((dst_sa == NULL) || (dst_sa->sa_family != sa->sa_family))
+ dst_sa = sa;
+ dump_sa(nw, IFA_ADDRESS, dst_sa);
+ dump_sa(nw, IFA_LOCAL, sa);
+ nlattr_add_string(nw, IFA_LABEL, if_name(ifp));
+
+ uint32_t val = 0; // ifa->ifa_flags;
+ nlattr_add_u32(nw, IFA_FLAGS, val);
+
+ if (nlmsg_end(nw))
+ return (true);
+enomem:
+ NL_LOG(LOG_DEBUG, "Failed to dump ifa type %s(%d) for interface %s",
+ rib_print_family(sa->sa_family), sa->sa_family, if_name(ifp));
+ nlmsg_abort(nw);
+ return (false);
+}
+
+static int
+rtnl_handle_getaddr(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+ struct ifaddr *ifa;
+ struct ifnet *ifp;
+ int error = 0;
+
+ struct netlink_walkargs wa = {
+ .so = nlp,
+ .nw = npt->nw,
+ .hdr.nlmsg_pid = hdr->nlmsg_pid,
+ .hdr.nlmsg_seq = hdr->nlmsg_seq,
+ .hdr.nlmsg_flags = hdr->nlmsg_flags | NLM_F_MULTI,
+ .hdr.nlmsg_type = NL_RTM_NEWADDR,
+ };
+
+ NL_LOG(LOG_DEBUG2, "Start dump");
+
+ CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
+ CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
+ if (wa.family != 0 && wa.family != ifa->ifa_addr->sa_family)
+ continue;
+ if (ifa->ifa_addr->sa_family == AF_LINK)
+ continue;
+ wa.count++;
+ if (!dump_iface_addr(wa.nw, ifp, ifa, &wa.hdr)) {
+ error = ENOMEM;
+ break;
+ }
+ wa.dumped++;
+ }
+ if (error != 0)
+ break;
+ }
+
+ NL_LOG(LOG_DEBUG2, "End dump, iterated %d dumped %d", wa.count, wa.dumped);
+
+ if (!nlmsg_end_dump(wa.nw, error, &wa.hdr)) {
+ NL_LOG(LOG_DEBUG, "Unable to finalize the dump");
+ return (ENOMEM);
+ }
+
+ return (error);
+}
+
+static void
+rtnl_handle_ifaddr(void *arg __unused, struct ifaddr *ifa, int cmd)
+{
+ struct nlmsghdr hdr = {};
+ struct nl_writer nw = {};
+ uint32_t group = 0;
+
+ switch (ifa->ifa_addr->sa_family) {
+ case AF_INET:
+ group = RTNLGRP_IPV4_IFADDR;
+ break;
+ case AF_INET6:
+ group = RTNLGRP_IPV6_IFADDR;
+ break;
+ default:
+ NL_LOG(LOG_DEBUG2, "ifa notification for unknown AF: %d",
+ ifa->ifa_addr->sa_family);
+ return;
+ }
+
+ if (!nl_has_listeners(NETLINK_ROUTE, group))
+ return;
+
+ if (!nlmsg_get_group_writer(&nw, NLMSG_LARGE, NETLINK_ROUTE, group)) {
+ NL_LOG(LOG_DEBUG, "error allocating group writer");
+ return;
+ }
+
+ hdr.nlmsg_type = (cmd == RTM_DELETE) ? NL_RTM_DELADDR : NL_RTM_NEWADDR;
+
+ dump_iface_addr(&nw, ifa->ifa_ifp, ifa, &hdr);
+ nlmsg_flush(&nw);
+}
+
+static void
+rtnl_handle_ifattach(void *arg, struct ifnet *ifp)
+{
+ struct nlmsghdr hdr = { .nlmsg_type = NL_RTM_NEWLINK };
+ struct nl_writer nw = {};
+
+ if (!nl_has_listeners(NETLINK_ROUTE, RTNLGRP_LINK))
+ return;
+
+ if (!nlmsg_get_group_writer(&nw, NLMSG_LARGE, NETLINK_ROUTE, RTNLGRP_LINK)) {
+ NL_LOG(LOG_DEBUG, "error allocating mbuf");
+ return;
+ }
+ dump_iface(&nw, ifp, &hdr);
+ nlmsg_flush(&nw);
+}
+
+static void
+rtnl_handle_ifdetach(void *arg, struct ifnet *ifp)
+{
+ struct nlmsghdr hdr = { .nlmsg_type = NL_RTM_DELLINK };
+ struct nl_writer nw = {};
+
+ if (!nl_has_listeners(NETLINK_ROUTE, RTNLGRP_LINK))
+ return;
+
+ if (!nlmsg_get_group_writer(&nw, NLMSG_LARGE, NETLINK_ROUTE, RTNLGRP_LINK)) {
+ NL_LOG(LOG_DEBUG, "error allocating mbuf");
+ return;
+ }
+ dump_iface(&nw, ifp, &hdr);
+ nlmsg_flush(&nw);
+}
+
+static const struct rtnl_cmd_handler cmd_handlers[] = {
+ {
+ .cmd = NL_RTM_GETLINK,
+ .name = "RTM_GETLINK",
+ .cb = &rtnl_handle_getlink,
+ .flags = RTNL_F_NOEPOCH,
+ },
+ {
+ .cmd = NL_RTM_DELLINK,
+ .name = "RTM_DELLINK",
+ .cb = &rtnl_handle_dellink,
+ .priv = PRIV_NET_IFDESTROY,
+ .flags = RTNL_F_NOEPOCH,
+ },
+ {
+ .cmd = NL_RTM_NEWLINK,
+ .name = "RTM_NEWLINK",
+ .cb = &rtnl_handle_newlink,
+ .priv = PRIV_NET_IFCREATE,
+ .flags = RTNL_F_NOEPOCH,
+ },
+ {
+ .cmd = NL_RTM_GETADDR,
+ .name = "RTM_GETADDR",
+ .cb = &rtnl_handle_getaddr,
+ },
+ {
+ .cmd = NL_RTM_NEWADDR,
+ .name = "RTM_NEWADDR",
+ .cb = &rtnl_handle_getaddr,
+ },
+ {
+ .cmd = NL_RTM_DELADDR,
+ .name = "RTM_DELADDR",
+ .cb = &rtnl_handle_getaddr,
+ },
+};
+
+static const struct nlhdr_parser *all_parsers[] = { &ifmsg_parser };
+
+void
+rtnl_iface_add_cloner(struct nl_cloner *cloner)
+{
+ sx_xlock(&rtnl_cloner_lock);
+ SLIST_INSERT_HEAD(&nl_cloners, cloner, next);
+ sx_xunlock(&rtnl_cloner_lock);
+}
+
+void rtnl_iface_del_cloner(struct nl_cloner *cloner)
+{
+ sx_xlock(&rtnl_cloner_lock);
+ SLIST_REMOVE(&nl_cloners, cloner, nl_cloner, next);
+ sx_xunlock(&rtnl_cloner_lock);
+}
+
+void
+rtnl_ifaces_init(void)
+{
+ ifattach_event = EVENTHANDLER_REGISTER(
+ ifnet_arrival_event, rtnl_handle_ifattach, NULL,
+ EVENTHANDLER_PRI_ANY);
+ ifdetach_event = EVENTHANDLER_REGISTER(
+ ifnet_departure_event, rtnl_handle_ifdetach, NULL,
+ EVENTHANDLER_PRI_ANY);
+ ifaddr_event = EVENTHANDLER_REGISTER(
+ rt_addrmsg, rtnl_handle_ifaddr, NULL,
+ EVENTHANDLER_PRI_ANY);
+ NL_VERIFY_PARSERS(all_parsers);
+ rtnl_iface_drivers_register();
+ rtnl_register_messages(cmd_handlers, NL_ARRAY_LEN(cmd_handlers));
+}
+
+void
+rtnl_ifaces_destroy(void)
+{
+ EVENTHANDLER_DEREGISTER(ifnet_arrival_event, ifattach_event);
+ EVENTHANDLER_DEREGISTER(ifnet_departure_event, ifdetach_event);
+ EVENTHANDLER_DEREGISTER(rt_addrmsg, ifaddr_event);
+}
diff --git a/sys/netlink/route/iface_drivers.c b/sys/netlink/route/iface_drivers.c
new file mode 100644
index 000000000000..ccc8f2184fa3
--- /dev/null
+++ b/sys/netlink/route/iface_drivers.c
@@ -0,0 +1,165 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include "opt_inet.h"
+#include "opt_inet6.h"
+#include <sys/types.h>
+#include <sys/malloc.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/syslog.h>
+#include <sys/socketvar.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_var.h>
+#include <net/if_clone.h>
+#include <net/if_vlan_var.h>
+#include <net/route.h>
+#include <net/route/nhop.h>
+#include <net/route/route_ctl.h>
+#include <netlink/netlink.h>
+#include <netlink/netlink_ctl.h>
+#include <netlink/netlink_route.h>
+#include <netlink/route/route_var.h>
+
+#include <netinet6/scope6_var.h> /* scope deembedding */
+
+#define DEBUG_MOD_NAME nl_iface_drivers
+#define DEBUG_MAX_LEVEL LOG_DEBUG3
+#include <netlink/netlink_debug.h>
+_DECLARE_DEBUG(LOG_DEBUG);
+
+/*
+ *
+ * {len=76, type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1662892737, pid=0},
+ * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0},
+ * [
+ * {{nla_len=8, nla_type=IFLA_LINK}, 2},
+ * {{nla_len=12, nla_type=IFLA_IFNAME}, "xvlan22"},
+ * {{nla_len=24, nla_type=IFLA_LINKINFO},
+ * [
+ * {{nla_len=8, nla_type=IFLA_INFO_KIND}, "vlan"...},
+ * {{nla_len=12, nla_type=IFLA_INFO_DATA}, "\x06\x00\x01\x00\x16\x00\x00\x00"}]}]}, iov_len=76}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 76
+ */
+
+struct nl_parsed_vlan {
+ uint16_t vlan_id;
+ uint16_t vlan_proto;
+ struct ifla_vlan_flags vlan_flags;
+};
+
+#define _OUT(_field) offsetof(struct nl_parsed_vlan, _field)
+static const struct nlattr_parser nla_p_vlan[] = {
+ { .type = IFLA_VLAN_ID, .off = _OUT(vlan_id), .cb = nlattr_get_uint16 },
+ { .type = IFLA_VLAN_FLAGS, .off = _OUT(vlan_flags), .cb = nlattr_get_nla },
+ { .type = IFLA_VLAN_PROTOCOL, .off = _OUT(vlan_proto), .cb = nlattr_get_uint16 },
+};
+#undef _OUT
+NL_DECLARE_ATTR_PARSER(vlan_parser, nla_p_vlan);
+
+static int
+create_vlan(struct nl_parsed_link *lattrs, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+ struct epoch_tracker et;
+ struct ifnet *ifp;
+ int error;
+
+ /*
+ * lattrs.ifla_ifname is the new interface name
+ * lattrs.ifi_index contains parent interface index
+ * lattrs.ifla_idata contains un-parsed vlan data
+ */
+
+ struct nl_parsed_vlan attrs = {
+ .vlan_id = 0xFEFE,
+ .vlan_proto = ETHERTYPE_VLAN
+ };
+ NLP_LOG(LOG_DEBUG3, nlp, "nested: %p len %d", lattrs->ifla_idata, lattrs->ifla_idata->nla_len);
+
+ if (lattrs->ifla_idata == NULL) {
+ NLMSG_REPORT_ERR_MSG(npt, "vlan id is required, guessing not supported");
+ return (ENOTSUP);
+ }
+
+ error = nl_parse_nested(lattrs->ifla_idata, &vlan_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+ if (attrs.vlan_id > 4095) {
+ NLMSG_REPORT_ERR_MSG(npt, "Invalid VID: %d", attrs.vlan_id);
+ return (EINVAL);
+ }
+ if (attrs.vlan_proto != ETHERTYPE_VLAN && attrs.vlan_proto != ETHERTYPE_QINQ) {
+ NLMSG_REPORT_ERR_MSG(npt, "Unsupported ethertype: 0x%04X", attrs.vlan_proto);
+ return (ENOTSUP);
+ }
+
+ NET_EPOCH_ENTER(et);
+ ifp = ifnet_byindex_ref(lattrs->ifi_index);
+ NET_EPOCH_EXIT(et);
+ if (ifp == NULL) {
+ NLP_LOG(LOG_DEBUG, nlp, "unable to find parent interface %u",
+ lattrs->ifi_index);
+ return (ENOENT);
+ }
+
+ /* Waiting till if_clone changes lands */
+/*
+ struct vlanreq params = {
+ .vlr_tag = attrs.vlan_id,
+ .vlr_proto = attrs.vlan_proto,
+ };
+*/
+ int ifname_len = strlen(lattrs->ifla_ifname) + 1;
+ error = if_clone_create(lattrs->ifla_ifname, ifname_len, (char *)NULL);
+
+ NLP_LOG(LOG_DEBUG2, nlp, "clone for %s returned %d", lattrs->ifla_ifname, error);
+
+ if_rele(ifp);
+ return (error);
+}
+
+static struct nl_cloner vlan_cloner = {
+ .name = "vlan",
+ .create_f = create_vlan,
+
+};
+
+static const struct nlhdr_parser *all_parsers[] = { &vlan_parser };
+
+void
+rtnl_iface_drivers_register(void)
+{
+ rtnl_iface_add_cloner(&vlan_cloner);
+ NL_VERIFY_PARSERS(all_parsers);
+}
+
+
diff --git a/sys/netlink/route/ifaddrs.h b/sys/netlink/route/ifaddrs.h
new file mode 100644
index 000000000000..e2013cb266d7
--- /dev/null
+++ b/sys/netlink/route/ifaddrs.h
@@ -0,0 +1,90 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Interface address-related (RTM_<NEW|DEL|GET>ADDR) message header and attributes.
+ */
+
+#ifndef _NETLINK_ROUTE_IFADDRS_H_
+#define _NETLINK_ROUTE_IFADDRS_H_
+
+/* Base header for all of the relevant messages */
+struct ifaddrmsg {
+ uint8_t ifa_family; /* Address family */
+ uint8_t ifa_prefixlen; /* Prefix length */
+ uint8_t ifa_flags; /* Address-specific flags */
+ uint8_t ifa_scope; /* Address scope */
+ uint32_t ifa_index; /* Link ifindex */
+};
+
+#ifndef _KERNEL
+#define _NL_IFA_HDRLEN ((int)sizeof(struct ifaddrmsg))
+#define IFA_RTA(_ifa) ((struct rtattr *)(NL_ITEM_DATA(_ifa, _NL_IFA_HDRLEN)))
+#define IFA_PAYLOAD(_hdr) NLMSG_PAYLOAD(_hdr, _NL_IFA_HDRLEN)
+#endif
+
+/* Defined attributes */
+enum {
+ IFA_UNSPEC,
+ IFA_ADDRESS = 1, /* binary, prefix address (destination for p2p) */
+ IFA_LOCAL = 2, /* binary, interface address */
+ IFA_LABEL = 3, /* not supported */
+ IFA_BROADCAST = 4, /* binary, broadcast ifa */
+ IFA_ANYCAST = 5, /* not supported */
+ IFA_CACHEINFO = 6, /* not supported */
+ IFA_MULTICAST = 7, /* not supported */
+ IFA_FLAGS = 8, /* not supported */
+ IFA_RT_PRIORITY = 9, /* not supported */
+ IFA_TARGET_NETNSID = 10, /* not supported */
+ __IFA_MAX,
+};
+#define IFA_MAX (__IFA_MAX - 1)
+
+/* IFA_FLAGS attribute flags */
+#define IFA_F_SECONDARY 0x0001
+#define IFA_F_TEMPORARY IFA_F_SECONDARY
+#define IFA_F_NODAD 0x0002
+#define IFA_F_OPTIMISTIC 0x0004
+#define IFA_F_DADFAILED 0x0008
+#define IFA_F_HOMEADDRESS 0x0010
+#define IFA_F_DEPRECATED 0x0020
+#define IFA_F_TENTATIVE 0x0040
+#define IFA_F_PERMANENT 0x0080
+#define IFA_F_MANAGETEMPADDR 0x0100
+#define IFA_F_NOPREFIXROUTE 0x0200
+#define IFA_F_MCAUTOJOIN 0x0400
+#define IFA_F_STABLE_PRIVACY 0x0800
+
+/* IFA_CACHEINFO value */
+struct ifa_cacheinfo {
+ uint32_t ifa_prefered;
+ uint32_t ifa_valid;
+ uint32_t cstamp;
+ uint32_t tstamp;
+};
+
+#endif
diff --git a/sys/netlink/route/interface.h b/sys/netlink/route/interface.h
new file mode 100644
index 000000000000..cae763cc4a58
--- /dev/null
+++ b/sys/netlink/route/interface.h
@@ -0,0 +1,245 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Interface-related (RTM_<NEW|DEL|GET|SET>LINK) message header and attributes.
+ */
+
+#ifndef _NETLINK_ROUTE_INTERFACE_H_
+#define _NETLINK_ROUTE_INTERFACE_H_
+
+/* Base header for all of the relevant messages */
+struct ifinfomsg {
+ unsigned char ifi_family; /* not used */
+ unsigned char __ifi_pad;
+ unsigned short ifi_type; /* ARPHRD_* */
+ int ifi_index; /* Inteface index */
+ unsigned ifi_flags; /* IFF_* flags */
+ unsigned ifi_change; /* IFF_* change mask */
+};
+
+#ifndef _KERNEL
+/* Compatilbility helpers */
+#define _IFINFO_HDRLEN ((int)sizeof(struct ifinfomsg))
+#define IFLA_RTA(_ifi) ((struct rtattr *)NL_ITEM_DATA(_ifi, _IFINFO_HDRLEN))
+#define IFLA_PAYLOAD(_ifi) NLMSG_PAYLOAD(_ifi, _IFINFO_HDRLEN)
+#endif
+
+enum {
+ IFLA_UNSPEC = 0,
+ IFLA_ADDRESS = 1, /* binary: Link-level address (MAC) */
+#define IFLA_ADDRESS IFLA_ADDRESS
+ IFLA_BROADCAST = 2, /* binary: link-level broadcast address */
+#define IFLA_BROADCAST IFLA_BROADCAST
+ IFLA_IFNAME = 3, /* string: Interface name */
+#define IFLA_IFNAME IFLA_IFNAME
+ IFLA_MTU = 4, /* u32: Current interface L3 mtu */
+#define IFLA_MTU IFLA_MTU
+ IFLA_LINK = 5, /* u32: interface index */
+#define IFLA_LINK IFLA_LINK
+ IFLA_QDISC = 6, /* string: Queing policy (not supported) */
+#define IFLA_QDISC IFLA_QDISC
+ IFLA_STATS = 7, /* Interface counters */
+#define IFLA_STATS IFLA_STATS
+ IFLA_COST = 8, /* not supported */
+#define IFLA_COST IFLA_COST
+ IFLA_PRIORITY = 9, /* not supported */
+#define IFLA_PRIORITY IFLA_PRIORITY
+ IFLA_MASTER = 10, /* u32: parent interface ifindex */
+#define IFLA_MASTER IFLA_MASTER
+ IFLA_WIRELESS = 11, /* not supported */
+#define IFLA_WIRELESS IFLA_WIRELESS
+ IFLA_PROTINFO = 12, /* protocol-specific data */
+#define IFLA_PROTINFO IFLA_PROTINFO
+ IFLA_TXQLEN = 13, /* u32: transmit queue length */
+#define IFLA_TXQLEN IFLA_TXQLEN
+ IFLA_MAP = 14, /* not supported */
+#define IFLA_MAP IFLA_MAP
+ IFLA_WEIGHT = 15, /* not supported */
+#define IFLA_WEIGHT IFLA_WEIGHT
+ IFLA_OPERSTATE = 16, /* u8: ifOperStatus per RFC 2863 */
+#define IFLA_OPERSTATE IFLA_OPERSTATE
+ IFLA_LINKMODE = 17, /* u8: ifmedia (not supported) */
+#define IFLA_LINKMODE IFLA_LINKMODE
+ IFLA_LINKINFO = 18, /* nested: IFLA_INFO_ */
+#define IFLA_LINKINFO IFLA_LINKINFO
+ IFLA_NET_NS_PID = 19, /* u32: vnet id (not supported) */
+#define IFLA_NET_NS_PID IFLA_NET_NS_PID
+ IFLA_IFALIAS = 20, /* not supported */
+#define IFLA_IFALIAS IFLA_IFALIAS
+ IFLA_NUM_VF = 21, /* not supported */
+#define IFLA_NUM_VF IFLA_NUM_VF
+ IFLA_VFINFO_LIST= 22, /* not supported */
+#define IFLA_VFINFO_LIST IFLA_VFINFO_LIST
+ IFLA_STATS64 = 23, /* rtnl_link_stats64: iface stats */
+#define IFLA_STATS64 IFLA_STATS64
+ IFLA_VF_PORTS,
+ IFLA_PORT_SELF,
+ IFLA_AF_SPEC,
+ IFLA_GROUP, /* Group the device belongs to */
+ IFLA_NET_NS_FD,
+ IFLA_EXT_MASK, /* Extended info mask, VFs, etc */
+ IFLA_PROMISCUITY, /* Promiscuity count: > 0 means acts PROMISC */
+#define IFLA_PROMISCUITY IFLA_PROMISCUITY
+ IFLA_NUM_TX_QUEUES,
+ IFLA_NUM_RX_QUEUES,
+ IFLA_CARRIER,
+ IFLA_PHYS_PORT_ID,
+ IFLA_CARRIER_CHANGES,
+ IFLA_PHYS_SWITCH_ID,
+ IFLA_LINK_NETNSID,
+ IFLA_PHYS_PORT_NAME,
+ IFLA_PROTO_DOWN,
+ IFLA_GSO_MAX_SEGS,
+ IFLA_GSO_MAX_SIZE,
+ IFLA_PAD,
+ IFLA_XDP,
+ IFLA_EVENT,
+ IFLA_NEW_NETNSID,
+ IFLA_IF_NETNSID,
+ IFLA_TARGET_NETNSID = IFLA_IF_NETNSID, /* new alias */
+ IFLA_CARRIER_UP_COUNT,
+ IFLA_CARRIER_DOWN_COUNT,
+ IFLA_NEW_IFINDEX,
+ IFLA_MIN_MTU,
+ IFLA_MAX_MTU,
+ IFLA_PROP_LIST,
+ IFLA_ALT_IFNAME, /* Alternative ifname */
+ IFLA_PERM_ADDRESS,
+ IFLA_PROTO_DOWN_REASON,
+ __IFLA_MAX
+};
+#define IFLA_MAX (__IFLA_MAX - 1)
+
+/*
+ * Attributes that can be used as filters:
+ * IFLA_IFNAME, IFLA_GROUP, IFLA_ALT_IFNAME
+ * Headers that can be used as filters:
+ * ifi_index, ifi_type
+ */
+
+/*
+ * IFLA_OPERSTATE.
+ * The values below represent the possible
+ * states of ifOperStatus defined by RFC 2863
+ */
+enum {
+ IF_OPER_UNKNOWN = 0, /* status can not be determined */
+ IF_OPER_NOTPRESENT = 1, /* some (hardware) component not present */
+ IF_OPER_DOWN = 2, /* down */
+ IF_OPER_LOWERLAYERDOWN = 3, /* some lower-level interface is down */
+ IF_OPER_TESTING = 4, /* in some test mode */
+ IF_OPER_DORMANT = 5, /* "up" but waiting for some condition (802.1X) */
+ IF_OPER_UP = 6, /* ready to pass packets */
+};
+
+/* IFLA_STATS */
+struct rtnl_link_stats {
+ uint32_t rx_packets; /* total RX packets (IFCOUNTER_IPACKETS) */
+ uint32_t tx_packets; /* total TX packets (IFCOUNTER_OPACKETS) */
+ uint32_t rx_bytes; /* total RX bytes (IFCOUNTER_IBYTES) */
+ uint32_t tx_bytes; /* total TX bytes (IFCOUNTER_OBYTES) */
+ uint32_t rx_errors; /* RX errors (IFCOUNTER_IERRORS) */
+ uint32_t tx_errors; /* RX errors (IFCOUNTER_OERRORS) */
+ uint32_t rx_dropped; /* RX drop (no space in ring/no bufs) (IFCOUNTER_IQDROPS) */
+ uint32_t tx_dropped; /* TX drop (IFCOUNTER_OQDROPS) */
+ uint32_t multicast; /* RX multicast packets (IFCOUNTER_IMCASTS) */
+ uint32_t collisions; /* not supported */
+ uint32_t rx_length_errors; /* not supported */
+ uint32_t rx_over_errors; /* not supported */
+ uint32_t rx_crc_errors; /* not supported */
+ uint32_t rx_frame_errors; /* not supported */
+ uint32_t rx_fifo_errors; /* not supported */
+ uint32_t rx_missed_errors; /* not supported */
+ uint32_t tx_aborted_errors; /* not supported */
+ uint32_t tx_carrier_errors; /* not supported */
+ uint32_t tx_fifo_errors; /* not supported */
+ uint32_t tx_heartbeat_errors; /* not supported */
+ uint32_t tx_window_errors; /* not supported */
+ uint32_t rx_compressed; /* not supported */
+ uint32_t tx_compressed; /* not supported */
+ uint32_t rx_nohandler; /* dropped due to no proto handler (IFCOUNTER_NOPROTO) */
+};
+
+/* IFLA_STATS64 */
+struct rtnl_link_stats64 {
+ uint64_t rx_packets; /* total RX packets (IFCOUNTER_IPACKETS) */
+ uint64_t tx_packets; /* total TX packets (IFCOUNTER_OPACKETS) */
+ uint64_t rx_bytes; /* total RX bytes (IFCOUNTER_IBYTES) */
+ uint64_t tx_bytes; /* total TX bytes (IFCOUNTER_OBYTES) */
+ uint64_t rx_errors; /* RX errors (IFCOUNTER_IERRORS) */
+ uint64_t tx_errors; /* RX errors (IFCOUNTER_OERRORS) */
+ uint64_t rx_dropped; /* RX drop (no space in ring/no bufs) (IFCOUNTER_IQDROPS) */
+ uint64_t tx_dropped; /* TX drop (IFCOUNTER_OQDROPS) */
+ uint64_t multicast; /* RX multicast packets (IFCOUNTER_IMCASTS) */
+ uint64_t collisions; /* not supported */
+ uint64_t rx_length_errors; /* not supported */
+ uint64_t rx_over_errors; /* not supported */
+ uint64_t rx_crc_errors; /* not supported */
+ uint64_t rx_frame_errors; /* not supported */
+ uint64_t rx_fifo_errors; /* not supported */
+ uint64_t rx_missed_errors; /* not supported */
+ uint64_t tx_aborted_errors; /* not supported */
+ uint64_t tx_carrier_errors; /* not supported */
+ uint64_t tx_fifo_errors; /* not supported */
+ uint64_t tx_heartbeat_errors; /* not supported */
+ uint64_t tx_window_errors; /* not supported */
+ uint64_t rx_compressed; /* not supported */
+ uint64_t tx_compressed; /* not supported */
+ uint64_t rx_nohandler; /* dropped due to no proto handler (IFCOUNTER_NOPROTO) */
+};
+
+/* IFLA_LINKINFO child nlattr types */
+enum {
+ IFLA_INFO_UNSPEC,
+ IFLA_INFO_KIND = 1, /* string, link type ("vlan") */
+ IFLA_INFO_DATA = 2, /* Per-link-type custom data */
+ IFLA_INFO_XSTATS = 3,
+ IFLA_INFO_SLAVE_KIND = 4,
+ IFLA_INFO_SLAVE_DATA = 5,
+ __IFLA_INFO_MAX,
+};
+#define IFLA_INFO_MAX (__IFLA_INFO_MAX - 1)
+
+/* IFLA_INFO_DATA vlan attributes */
+enum {
+ IFLA_VLAN_UNSPEC,
+ IFLA_VLAN_ID,
+ IFLA_VLAN_FLAGS,
+ IFLA_VLAN_EGRESS_QOS,
+ IFLA_VLAN_INGRESS_QOS,
+ IFLA_VLAN_PROTOCOL,
+ __IFLA_VLAN_MAX,
+};
+
+#define IFLA_VLAN_MAX (__IFLA_VLAN_MAX - 1)
+struct ifla_vlan_flags {
+ uint32_t flags;
+ uint32_t mask;
+};
+
+#endif
diff --git a/sys/netlink/route/neigh.c b/sys/netlink/route/neigh.c
new file mode 100644
index 000000000000..02ad138240a2
--- /dev/null
+++ b/sys/netlink/route/neigh.c
@@ -0,0 +1,571 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include "opt_inet.h"
+#include "opt_inet6.h"
+#include <sys/types.h>
+#include <sys/malloc.h>
+#include <sys/socket.h>
+#include <sys/syslog.h>
+
+#include <net/if.h>
+#include <net/if_llatbl.h>
+#include <netlink/netlink.h>
+#include <netlink/netlink_ctl.h>
+#include <netlink/netlink_route.h>
+#include <netlink/route/route_var.h>
+
+#include <netinet6/in6_var.h> /* nd6.h requires this */
+#include <netinet6/nd6.h> /* nd6 state machine */
+#include <netinet6/scope6_var.h> /* scope deembedding */
+
+#define DEBUG_MOD_NAME nl_neigh
+#define DEBUG_MAX_LEVEL LOG_DEBUG3
+#include <netlink/netlink_debug.h>
+_DECLARE_DEBUG(LOG_DEBUG);
+
+static int lle_families[] = { AF_INET, AF_INET6 };
+
+static eventhandler_tag lle_event_p;
+
+struct netlink_walkargs {
+ struct nl_writer *nw;
+ struct nlmsghdr hdr;
+ struct nlpcb *so;
+ struct ifnet *ifp;
+ int family;
+ int error;
+ int count;
+ int dumped;
+};
+
+static int
+lle_state_to_nl_state(int family, struct llentry *lle)
+{
+ int state = lle->ln_state;
+
+ switch (family) {
+ case AF_INET:
+ if (lle->la_flags & (LLE_STATIC | LLE_IFADDR))
+ state = 1;
+ switch (state) {
+ case 0: /* ARP_LLINFO_INCOMPLETE */
+ return (NUD_INCOMPLETE);
+ case 1: /* ARP_LLINFO_REACHABLE */
+ return (NUD_REACHABLE);
+ case 2: /* ARP_LLINFO_VERIFY */
+ return (NUD_PROBE);
+ }
+ break;
+ case AF_INET6:
+ switch (state) {
+ case ND6_LLINFO_INCOMPLETE:
+ return (NUD_INCOMPLETE);
+ case ND6_LLINFO_REACHABLE:
+ return (NUD_REACHABLE);
+ case ND6_LLINFO_STALE:
+ return (NUD_STALE);
+ case ND6_LLINFO_DELAY:
+ return (NUD_DELAY);
+ case ND6_LLINFO_PROBE:
+ return (NUD_PROBE);
+ }
+ break;
+ }
+
+ return (NUD_NONE);
+}
+
+static uint32_t
+lle_flags_to_nl_flags(const struct llentry *lle)
+{
+ uint32_t nl_flags = 0;
+
+ if (lle->la_flags & LLE_IFADDR)
+ nl_flags |= NTF_SELF;
+ if (lle->la_flags & LLE_PUB)
+ nl_flags |= NTF_PROXY;
+ if (lle->la_flags & LLE_STATIC)
+ nl_flags |= NTF_STICKY;
+ if (lle->ln_router != 0)
+ nl_flags |= NTF_ROUTER;
+
+ return (nl_flags);
+}
+
+static int
+dump_lle_locked(struct llentry *lle, void *arg)
+{
+ struct netlink_walkargs *wa = (struct netlink_walkargs *)arg;
+ struct nlmsghdr *hdr = &wa->hdr;
+ struct nl_writer *nw = wa->nw;
+ struct ndmsg *ndm;
+ union {
+ struct in_addr in;
+ struct in6_addr in6;
+ } addr;
+
+ IF_DEBUG_LEVEL(LOG_DEBUG2) {
+ char llebuf[NHOP_PRINT_BUFSIZE];
+ llentry_print_buf_lltable(lle, llebuf, sizeof(llebuf));
+ NL_LOG(LOG_DEBUG2, "dumping %s", llebuf);
+ }
+
+ if (!nlmsg_reply(nw, hdr, sizeof(struct ndmsg)))
+ goto enomem;
+
+ ndm = nlmsg_reserve_object(nw, struct ndmsg);
+ ndm->ndm_family = wa->family;
+ ndm->ndm_ifindex = wa->ifp->if_index;
+ ndm->ndm_state = lle_state_to_nl_state(wa->family, lle);
+ ndm->ndm_flags = lle_flags_to_nl_flags(lle);
+
+ switch (wa->family) {
+#ifdef INET
+ case AF_INET:
+ addr.in = lle->r_l3addr.addr4;
+ nlattr_add(nw, NDA_DST, 4, &addr);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ addr.in6 = lle->r_l3addr.addr6;
+ in6_clearscope(&addr.in6);
+ nlattr_add(nw, NDA_DST, 16, &addr);
+ break;
+#endif
+ }
+
+ if (lle->r_flags & RLLE_VALID) {
+ /* Has L2 */
+ int addrlen = wa->ifp->if_addrlen;
+ nlattr_add(nw, NDA_LLADDR, addrlen, lle->ll_addr);
+ }
+
+ nlattr_add_u32(nw, NDA_PROBES, lle->la_asked);
+
+ struct nda_cacheinfo *cache;
+ cache = nlmsg_reserve_attr(nw, NDA_CACHEINFO, struct nda_cacheinfo);
+ if (cache == NULL)
+ goto enomem;
+ /* TODO: provide confirmed/updated */
+ cache->ndm_refcnt = lle->lle_refcnt;
+
+ if (nlmsg_end(nw))
+ return (0);
+enomem:
+ NL_LOG(LOG_DEBUG, "unable to dump lle state (ENOMEM)");
+ nlmsg_abort(nw);
+ return (ENOMEM);
+}
+
+static int
+dump_lle(struct lltable *llt, struct llentry *lle, void *arg)
+{
+ int error;
+
+ LLE_RLOCK(lle);
+ error = dump_lle_locked(lle, arg);
+ LLE_RUNLOCK(lle);
+ return (error);
+}
+
+static bool
+dump_llt(struct lltable *llt, struct netlink_walkargs *wa)
+{
+ lltable_foreach_lle(llt, dump_lle, wa);
+
+ return (true);
+}
+
+static int
+dump_llts_iface(struct netlink_walkargs *wa, struct ifnet *ifp, int family)
+{
+ int error = 0;
+
+ wa->ifp = ifp;
+ for (int i = 0; i < sizeof(lle_families) / sizeof(int); i++) {
+ int fam = lle_families[i];
+ struct lltable *llt = lltable_get(ifp, fam);
+ if (llt != NULL && (family == 0 || family == fam)) {
+ wa->count++;
+ wa->family = fam;
+ if (!dump_llt(llt, wa)) {
+ error = ENOMEM;
+ break;
+ }
+ wa->dumped++;
+ }
+ }
+ return (error);
+}
+
+static int
+dump_llts(struct netlink_walkargs *wa, struct ifnet *ifp, int family)
+{
+ NL_LOG(LOG_DEBUG, "Start dump ifp=%s family=%d", ifp ? if_name(ifp) : "NULL", family);
+
+ wa->hdr.nlmsg_flags |= NLM_F_MULTI;
+
+ if (ifp != NULL) {
+ dump_llts_iface(wa, ifp, family);
+ } else {
+ CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
+ dump_llts_iface(wa, ifp, family);
+ }
+ }
+
+ NL_LOG(LOG_DEBUG, "End dump, iterated %d dumped %d", wa->count, wa->dumped);
+
+ if (!nlmsg_end_dump(wa->nw, wa->error, &wa->hdr)) {
+ NL_LOG(LOG_DEBUG, "Unable to add new message");
+ return (ENOMEM);
+ }
+
+ return (0);
+}
+
+static int
+get_lle(struct netlink_walkargs *wa, struct ifnet *ifp, int family, struct sockaddr *dst)
+{
+ struct lltable *llt = lltable_get(ifp, family);
+ if (llt == NULL)
+ return (ESRCH);
+
+#ifdef INET6
+ if (dst->sa_family == AF_INET6) {
+ struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst;
+
+ if (IN6_IS_SCOPE_LINKLOCAL(&dst6->sin6_addr))
+ in6_set_unicast_scopeid(&dst6->sin6_addr, ifp->if_index);
+ }
+#endif
+ struct llentry *lle = lla_lookup(llt, LLE_UNLOCKED, dst);
+ if (lle == NULL)
+ return (ESRCH);
+
+ wa->ifp = ifp;
+ wa->family = family;
+
+ return (dump_lle(llt, lle, wa));
+}
+
+struct nl_parsed_neigh {
+ struct sockaddr *nda_dst;
+ struct ifnet *nda_ifp;
+ struct nlattr *nda_lladdr;
+ uint32_t ndm_flags;
+ uint16_t ndm_state;
+ uint8_t ndm_family;
+};
+
+#define _IN(_field) offsetof(struct ndmsg, _field)
+#define _OUT(_field) offsetof(struct nl_parsed_neigh, _field)
+static struct nlfield_parser nlf_p_neigh[] = {
+ { .off_in = _IN(ndm_family), .off_out = _OUT(ndm_family), .cb = nlf_get_u8 },
+ { .off_in = _IN(ndm_flags), .off_out = _OUT(ndm_flags), .cb = nlf_get_u8_u32 },
+ { .off_in = _IN(ndm_state), .off_out = _OUT(ndm_state), .cb = nlf_get_u16 },
+ { .off_in = _IN(ndm_ifindex), .off_out = _OUT(nda_ifp), .cb = nlf_get_ifpz },
+};
+
+static struct nlattr_parser nla_p_neigh[] = {
+ { .type = NDA_DST, .off = _OUT(nda_dst), .cb = nlattr_get_ip },
+ { .type = NDA_LLADDR, .off = _OUT(nda_lladdr), .cb = nlattr_get_nla },
+ { .type = NDA_IFINDEX, .off = _OUT(nda_ifp), .cb = nlattr_get_ifp },
+ { .type = NDA_FLAGS_EXT, .off = _OUT(ndm_flags), .cb = nlattr_get_uint32 },
+};
+#undef _IN
+#undef _OUT
+NL_DECLARE_PARSER(ndmsg_parser, struct ndmsg, nlf_p_neigh, nla_p_neigh);
+
+
+/*
+ * type=RTM_NEWNEIGH, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1661941473, pid=0},
+ * {ndm_family=AF_INET6, ndm_ifindex=if_nametoindex("enp0s31f6"), ndm_state=NUD_PERMANENT, ndm_flags=0, ndm_type=RTN_UNSPEC},
+ * [
+ * {{nla_len=20, nla_type=NDA_DST}, inet_pton(AF_INET6, "2a01:4f8:13a:70c::3")},
+ * {{nla_len=10, nla_type=NDA_LLADDR}, 20:4e:71:62:ae:f2}]}, iov_len=60}
+ */
+
+static int
+rtnl_handle_newneigh(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+ int error;
+
+ struct nl_parsed_neigh attrs = {};
+ error = nl_parse_nlmsg(hdr, &ndmsg_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ if (attrs.nda_ifp == NULL || attrs.nda_dst == NULL || attrs.nda_lladdr == NULL) {
+ if (attrs.nda_ifp == NULL)
+ NLMSG_REPORT_ERR_MSG(npt, "NDA_IFINDEX / ndm_ifindex not set");
+ if (attrs.nda_dst == NULL)
+ NLMSG_REPORT_ERR_MSG(npt, "NDA_DST not set");
+ if (attrs.nda_lladdr == NULL)
+ NLMSG_REPORT_ERR_MSG(npt, "NDA_LLADDR not set");
+ return (EINVAL);
+ }
+
+ if (attrs.nda_dst->sa_family != attrs.ndm_family) {
+ NLMSG_REPORT_ERR_MSG(npt,
+ "NDA_DST family (%d) is different from ndm_family (%d)",
+ attrs.nda_dst->sa_family, attrs.ndm_family);
+ return (EINVAL);
+ }
+
+ int addrlen = attrs.nda_ifp->if_addrlen;
+ if (attrs.nda_lladdr->nla_len != sizeof(struct nlattr) + addrlen) {
+ NLMSG_REPORT_ERR_MSG(npt,
+ "NDA_LLADDR address length (%ld) is different from expected (%d)",
+ attrs.nda_lladdr->nla_len - sizeof(struct nlattr), addrlen);
+ return (EINVAL);
+ }
+
+ if (attrs.ndm_state != NUD_PERMANENT) {
+ NLMSG_REPORT_ERR_MSG(npt, "ndm_state %d not supported", attrs.ndm_state);
+ return (ENOTSUP);
+ }
+
+ const uint16_t supported_flags = NTF_PROXY | NTF_STICKY;
+ if ((attrs.ndm_flags & supported_flags) != attrs.ndm_flags) {
+ NLMSG_REPORT_ERR_MSG(npt, "ndm_flags %X not supported",
+ attrs.ndm_flags &~ supported_flags);
+ return (ENOTSUP);
+ }
+
+ /* Replacement requires new entry creation anyway */
+ if ((hdr->nlmsg_flags & (NLM_F_CREATE | NLM_F_REPLACE)) == 0)
+ return (ENOTSUP);
+
+ struct lltable *llt = lltable_get(attrs.nda_ifp, attrs.ndm_family);
+ if (llt == NULL)
+ return (EAFNOSUPPORT);
+
+
+ uint8_t linkhdr[LLE_MAX_LINKHDR];
+ size_t linkhdrsize = sizeof(linkhdr);
+ int lladdr_off = 0;
+ if (lltable_calc_llheader(attrs.nda_ifp, attrs.ndm_family,
+ (char *)(attrs.nda_lladdr + 1), linkhdr, &linkhdrsize, &lladdr_off) != 0) {
+ NLMSG_REPORT_ERR_MSG(npt, "unable to calculate lle prepend data");
+ return (EINVAL);
+ }
+
+ int lle_flags = LLE_STATIC | ((attrs.ndm_flags & NTF_PROXY) ? LLE_PUB : 0);
+ struct llentry *lle = lltable_alloc_entry(llt, lle_flags, attrs.nda_dst);
+ if (lle == NULL)
+ return (ENOMEM);
+ lltable_set_entry_addr(attrs.nda_ifp, lle, linkhdr, linkhdrsize, lladdr_off);
+
+ /* llentry created, try to insert or update :*/
+ IF_AFDATA_WLOCK(attrs.nda_ifp);
+ LLE_WLOCK(lle);
+ struct llentry *lle_tmp = lla_lookup(llt, LLE_EXCLUSIVE, attrs.nda_dst);
+ if (lle_tmp != NULL) {
+ if (hdr->nlmsg_flags & NLM_F_EXCL) {
+ LLE_WUNLOCK(lle_tmp);
+ lle_tmp = NULL;
+ error = EEXIST;
+ } else if (hdr->nlmsg_flags & NLM_F_REPLACE) {
+ lltable_unlink_entry(llt, lle_tmp);
+ lltable_link_entry(llt, lle);
+ } else
+ error = EEXIST;
+ } else {
+ if (hdr->nlmsg_flags & NLM_F_CREATE)
+ lltable_link_entry(llt, lle);
+ else
+ error = ENOENT;
+ }
+ IF_AFDATA_WUNLOCK(attrs.nda_ifp);
+
+ if (error != 0) {
+ if (lle != NULL)
+ llentry_free(lle);
+ return (error);
+ }
+
+ if (lle_tmp != NULL)
+ llentry_free(lle_tmp);
+
+ /* XXX: We're inside epoch */
+ EVENTHANDLER_INVOKE(lle_event, lle, LLENTRY_RESOLVED);
+ LLE_WUNLOCK(lle);
+
+ return (0);
+}
+
+static int
+rtnl_handle_delneigh(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+ int error;
+
+ struct nl_parsed_neigh attrs = {};
+ error = nl_parse_nlmsg(hdr, &ndmsg_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ if (attrs.nda_dst == NULL) {
+ NLMSG_REPORT_ERR_MSG(npt, "NDA_DST not set");
+ return (EINVAL);
+ }
+
+ if (attrs.nda_ifp == NULL) {
+ NLMSG_REPORT_ERR_MSG(npt, "no ifindex provided");
+ return (EINVAL);
+ }
+
+ struct lltable *llt = lltable_get(attrs.nda_ifp, attrs.ndm_family);
+ if (llt == NULL)
+ return (EAFNOSUPPORT);
+
+ IF_AFDATA_WLOCK(attrs.nda_ifp);
+ struct llentry *lle = lla_lookup(llt, LLE_EXCLUSIVE, attrs.nda_dst);
+ if (lle != NULL) {
+ if ((lle->la_flags & LLE_IFADDR) != 0) {
+ LLE_WUNLOCK(lle);
+ lle = NULL;
+ error = EPERM;
+ } else
+ lltable_unlink_entry(llt, lle);
+ } else
+ error = ENOENT;
+ IF_AFDATA_WUNLOCK(attrs.nda_ifp);
+
+ if (error == 0 && lle != NULL)
+ EVENTHANDLER_INVOKE(lle_event, lle, LLENTRY_DELETED);
+
+ if (lle != NULL)
+ llentry_free(lle);
+
+ return (error);
+}
+
+static int
+rtnl_handle_getneigh(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+ int error;
+
+ struct nl_parsed_neigh attrs = {};
+ error = nl_parse_nlmsg(hdr, &ndmsg_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ if (attrs.nda_dst != NULL && attrs.nda_ifp == NULL) {
+ NLMSG_REPORT_ERR_MSG(npt, "has NDA_DST but no ifindex provided");
+ return (EINVAL);
+ }
+
+ struct netlink_walkargs wa = {
+ .so = nlp,
+ .nw = npt->nw,
+ .hdr.nlmsg_pid = hdr->nlmsg_pid,
+ .hdr.nlmsg_seq = hdr->nlmsg_seq,
+ .hdr.nlmsg_flags = hdr->nlmsg_flags,
+ .hdr.nlmsg_type = NL_RTM_NEWNEIGH,
+ };
+
+ if (attrs.nda_dst == NULL)
+ error = dump_llts(&wa, attrs.nda_ifp, attrs.ndm_family);
+ else
+ error = get_lle(&wa, attrs.nda_ifp, attrs.ndm_family, attrs.nda_dst);
+
+ return (error);
+}
+
+static const struct rtnl_cmd_handler cmd_handlers[] = {
+ {
+ .cmd = NL_RTM_NEWNEIGH,
+ .name = "RTM_NEWNEIGH",
+ .cb = &rtnl_handle_newneigh,
+ },
+ {
+ .cmd = NL_RTM_DELNEIGH,
+ .name = "RTM_DELNEIGH",
+ .cb = &rtnl_handle_delneigh,
+ .priv = PRIV_NET_ROUTE,
+ },
+ {
+ .cmd = NL_RTM_GETNEIGH,
+ .name = "RTM_GETNEIGH",
+ .cb = &rtnl_handle_getneigh,
+ .priv = PRIV_NET_ROUTE,
+ }
+};
+
+static void
+rtnl_lle_event(void *arg __unused, struct llentry *lle, int evt)
+{
+ struct ifnet *ifp;
+ int family;
+
+ LLE_WLOCK_ASSERT(lle);
+
+ ifp = lltable_get_ifp(lle->lle_tbl);
+ family = lltable_get_af(lle->lle_tbl);
+
+ if (family != AF_INET && family != AF_INET6)
+ return;
+
+ int nlmsgs_type = evt == LLENTRY_RESOLVED ? NL_RTM_NEWNEIGH : NL_RTM_DELNEIGH;
+
+ struct nl_writer nw = {};
+ if (!nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_ROUTE, RTNLGRP_NEIGH)) {
+ NL_LOG(LOG_DEBUG, "error allocating group writer");
+ return;
+ }
+
+ struct netlink_walkargs wa = {
+ .hdr.nlmsg_type = nlmsgs_type,
+ .nw = &nw,
+ .ifp = ifp,
+ .family = family,
+ };
+
+ dump_lle_locked(lle, &wa);
+ nlmsg_flush(&nw);
+}
+
+static const struct nlhdr_parser *all_parsers[] = { &ndmsg_parser };
+
+void
+rtnl_neighs_init()
+{
+ NL_VERIFY_PARSERS(all_parsers);
+ rtnl_register_messages(cmd_handlers, NL_ARRAY_LEN(cmd_handlers));
+ lle_event_p = EVENTHANDLER_REGISTER(lle_event, rtnl_lle_event, NULL,
+ EVENTHANDLER_PRI_ANY);
+}
+
+void
+rtnl_neighs_destroy()
+{
+ EVENTHANDLER_DEREGISTER(lle_event, lle_event_p);
+}
diff --git a/sys/netlink/route/neigh.h b/sys/netlink/route/neigh.h
new file mode 100644
index 000000000000..1ec1b95fdcde
--- /dev/null
+++ b/sys/netlink/route/neigh.h
@@ -0,0 +1,105 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Neighbors-related (RTM_<NEW|DEL|GET>NEIGH) message header and attributes.
+ */
+
+#ifndef _NETLINK_ROUTE_NEIGH_H_
+#define _NETLINK_ROUTE_NEIGH_H_
+
+/* Base header for all of the relevant messages */
+struct ndmsg {
+ uint8_t ndm_family;
+ uint8_t ndm_pad1;
+ uint16_t ndm_pad2;
+ int32_t ndm_ifindex;
+ uint16_t ndm_state;
+ uint8_t ndm_flags;
+ uint8_t ndm_type;
+};
+
+/* Attributes */
+enum {
+ NDA_UNSPEC,
+ NDA_DST, /* binary: neigh l3 address */
+ NDA_LLADDR, /* binary: neigh link-level address */
+ NDA_CACHEINFO, /* binary, struct nda_cacheinfo */
+ NDA_PROBES, /* XXX */
+ NDA_VLAN, /* upper 802.1Q tag */
+ NDA_PORT, /* not supported */
+ NDA_VNI, /* not supported */
+ NDA_IFINDEX, /* interface index */
+ NDA_MASTER, /* not supported */
+ NDA_LINK_NETNSID, /* not supported */
+ NDA_SRC_VNI, /* not supported */
+ NDA_PROTOCOL, /* XXX */
+ NDA_NH_ID, /* not supported */
+ NDA_FDB_EXT_ATTRS, /* not supported */
+ NDA_FLAGS_EXT, /* u32: ndm_flags */
+ NDA_NDM_STATE_MASK, /* XXX */
+ NDA_NDM_FLAGS_MASK, /* XXX */
+ __NDA_MAX
+};
+
+#define NDA_MAX (__NDA_MAX - 1)
+
+
+/* ndm_flags / NDA_FLAGS_EXT */
+#define NTF_USE 0x0001 /* XXX */
+#define NTF_SELF 0x0002 /* local station */
+#define NTF_MASTER 0x0004 /* XXX */
+#define NTF_PROXY 0x0008 /* proxy entry */
+#define NTF_EXT_LEARNED 0x0010 /* not used */
+#define NTF_OFFLOADED 0x0020 /* not used */
+#define NTF_STICKY 0x0040 /* permament entry */
+#define NTF_ROUTER 0x0080 /* dst indicated itself as a router */
+/* start of NDA_FLAGS_EXT */
+#define NTF_EXT_MANAGED 0x0100 /* not used */
+
+/* ndm_state */
+#define NUD_INCOMPLETE 0x01 /* No lladdr, address resolution in progress */
+#define NUD_REACHABLE 0x02 /* reachable & recently resolved */
+#define NUD_STALE 0x04 /* has lladdr but it's stale */
+#define NUD_DELAY 0x08 /* has lladdr, is stale, probes delayed */
+#define NUD_PROBE 0x10 /* has lladdr, is stale, probes sent */
+#define NUD_FAILED 0x20 /* unused */
+
+/* Dummy states */
+#define NUD_NOARP 0x40 /* not used */
+#define NUD_PERMANENT 0x80 /* not flushed */
+#define NUD_NONE 0x00
+
+/* NDA_CACHEINFO */
+struct nda_cacheinfo {
+ uint32_t ndm_confirmed; /* seconds since ARP/ND was received from neigh */
+ uint32_t ndm_used; /* seconds since last used (not provided) */
+ uint32_t ndm_updated; /* seconds since state was updated last */
+ uint32_t ndm_refcnt; /* number of references held */
+};
+
+#endif
diff --git a/sys/netlink/route/nexthop.c b/sys/netlink/route/nexthop.c
new file mode 100644
index 000000000000..92555aa8b123
--- /dev/null
+++ b/sys/netlink/route/nexthop.c
@@ -0,0 +1,1000 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include "opt_inet.h"
+#include "opt_inet6.h"
+#include <sys/types.h>
+#include <sys/malloc.h>
+#include <sys/rmlock.h>
+#include <sys/socket.h>
+#include <sys/ck.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/route/nhop.h>
+#include <net/route/nhop_utils.h>
+
+#include <net/route/route_ctl.h>
+#include <net/route/route_var.h>
+#include <netinet6/scope6_var.h>
+#include <netlink/netlink.h>
+#include <netlink/netlink_ctl.h>
+#include <netlink/netlink_var.h>
+#include <netlink/netlink_route.h>
+#include <netlink/route/route_var.h>
+
+#define DEBUG_MOD_NAME nl_nhop
+#define DEBUG_MAX_LEVEL LOG_DEBUG3
+#include <netlink/netlink_debug.h>
+_DECLARE_DEBUG(LOG_DEBUG3);
+
+/*
+ * This file contains the logic to maintain kernel nexthops and
+ * nexhop groups based om the data provided by the user.
+ *
+ * Kernel stores (nearly) all of the routing data in the nexthops,
+ * including the prefix-specific flags (NHF_HOST and NHF_DEFAULT).
+ *
+ * Netlink API provides higher-level abstraction for the user. Each
+ * user-created nexthop may map to multiple kernel nexthops.
+ *
+ * The following variations require separate kernel nexthop to be
+ * created:
+ * * prefix flags (NHF_HOST, NHF_DEFAULT)
+ * * using IPv6 gateway for IPv4 routes
+ * * different fibnum
+ *
+ * These kernel nexthops have the lifetime bound to the lifetime of
+ * the user_nhop object. They are not collected until user requests
+ * to delete the created user_nhop.
+ *
+ */
+struct user_nhop {
+ uint32_t un_idx; /* Userland-provided index */
+ uint32_t un_fibfam; /* fibnum+af(as highest byte) */
+ uint8_t un_protocol; /* protocol that install the record */
+ struct nhop_object *un_nhop; /* "production" nexthop */
+ struct nhop_object *un_nhop_src; /* nexthop to copy from */
+ struct weightened_nhop *un_nhgrp_src; /* nexthops for nhg */
+ uint32_t un_nhgrp_count; /* number of nexthops */
+ struct user_nhop *un_next; /* next item in hash chain */
+ struct user_nhop *un_nextchild; /* master -> children */
+ struct epoch_context un_epoch_ctx; /* epoch ctl helper */
+};
+
+/* produce hash value for an object */
+#define unhop_hash_obj(_obj) (hash_unhop(_obj))
+/* compare two objects */
+#define unhop_cmp(_one, _two) (cmp_unhop(_one, _two))
+/* next object accessor */
+#define unhop_next(_obj) (_obj)->un_next
+
+CHT_SLIST_DEFINE(unhop, struct user_nhop);
+
+struct unhop_ctl {
+ struct unhop_head un_head;
+ struct rmlock un_lock;
+};
+#define UN_LOCK_INIT(_ctl) rm_init(&(_ctl)->un_lock, "unhop_ctl")
+#define UN_TRACKER struct rm_priotracker un_tracker
+#define UN_RLOCK(_ctl) rm_rlock(&((_ctl)->un_lock), &un_tracker)
+#define UN_RUNLOCK(_ctl) rm_runlock(&((_ctl)->un_lock), &un_tracker)
+
+#define UN_WLOCK(_ctl) rm_wlock(&(_ctl)->un_lock);
+#define UN_WUNLOCK(_ctl) rm_wunlock(&(_ctl)->un_lock);
+
+VNET_DEFINE_STATIC(struct unhop_ctl *, un_ctl) = NULL;
+#define V_un_ctl VNET(un_ctl)
+
+static void consider_resize(struct unhop_ctl *ctl, uint32_t new_size);
+static int cmp_unhop(const struct user_nhop *a, const struct user_nhop *b);
+static unsigned int hash_unhop(const struct user_nhop *obj);
+
+static void destroy_unhop(struct user_nhop *unhop);
+static struct nhop_object *clone_unhop(const struct user_nhop *unhop,
+ uint32_t fibnum, int family, int nh_flags);
+
+static int
+cmp_unhop(const struct user_nhop *a, const struct user_nhop *b)
+{
+ return (a->un_idx == b->un_idx && a->un_fibfam == b->un_fibfam);
+}
+
+/*
+ * Hash callback: calculate hash of an object
+ */
+static unsigned int
+hash_unhop(const struct user_nhop *obj)
+{
+ return (obj->un_idx ^ obj->un_fibfam);
+}
+
+#define UNHOP_IS_MASTER(_unhop) ((_unhop)->un_fibfam == 0)
+
+/*
+ * Factory interface for creating matching kernel nexthops/nexthop groups
+ *
+ * @uidx: userland nexhop index used to create the nexthop
+ * @fibnum: fibnum nexthop will be used in
+ * @family: upper family nexthop will be used in
+ * @nh_flags: desired nexthop prefix flags
+ * @perror: pointer to store error to
+ *
+ * Returns referenced nexthop linked to @fibnum/@family rib on success.
+ */
+struct nhop_object *
+nl_find_nhop(uint32_t fibnum, int family, uint32_t uidx,
+ int nh_flags, int *perror)
+{
+ struct unhop_ctl *ctl = atomic_load_ptr(&V_un_ctl);
+ UN_TRACKER;
+
+ if (__predict_false(ctl == NULL))
+ return (NULL);
+
+ struct user_nhop key= {
+ .un_idx = uidx,
+ .un_fibfam = fibnum | ((uint32_t)family) << 24,
+ };
+ struct user_nhop *unhop;
+
+ nh_flags = nh_flags & (NHF_HOST | NHF_DEFAULT);
+
+ if (__predict_false(family == 0))
+ return (NULL);
+
+ UN_RLOCK(ctl);
+ CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
+ if (unhop != NULL) {
+ struct nhop_object *nh = unhop->un_nhop;
+ UN_RLOCK(ctl);
+ *perror = 0;
+ nhop_ref_any(nh);
+ return (nh);
+ }
+
+ /*
+ * Exact nexthop not found. Search for template nexthop to clone from.
+ */
+ key.un_fibfam = 0;
+ CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
+ if (unhop == NULL) {
+ UN_RUNLOCK(ctl);
+ *perror = ESRCH;
+ return (NULL);
+ }
+
+ UN_RUNLOCK(ctl);
+
+ /* Create entry to insert first */
+ struct user_nhop *un_new, *un_tmp;
+ un_new = malloc(sizeof(struct user_nhop), M_NETLINK, M_NOWAIT | M_ZERO);
+ if (un_new == NULL) {
+ *perror = ENOMEM;
+ return (NULL);
+ }
+ un_new->un_idx = uidx;
+ un_new->un_fibfam = fibnum | ((uint32_t)family) << 24;
+
+ /* Relying on epoch to protect unhop here */
+ un_new->un_nhop = clone_unhop(unhop, fibnum, family, nh_flags);
+ if (un_new->un_nhop == NULL) {
+ free(un_new, M_NETLINK);
+ *perror = ENOMEM;
+ return (NULL);
+ }
+
+ /* Insert back and report */
+ UN_WLOCK(ctl);
+
+ /* First, find template record once again */
+ CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
+ if (unhop == NULL) {
+ /* Someone deleted the nexthop during the call */
+ UN_WUNLOCK(ctl);
+ *perror = ESRCH;
+ destroy_unhop(un_new);
+ return (NULL);
+ }
+
+ /* Second, check the direct match */
+ CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, un_new, un_tmp);
+ struct nhop_object *nh;
+ if (un_tmp != NULL) {
+ /* Another thread already created the desired nextop, use it */
+ nh = un_tmp->un_nhop;
+ } else {
+ /* Finally, insert the new nexthop and link it to the primary */
+ nh = un_new->un_nhop;
+ CHT_SLIST_INSERT_HEAD(&ctl->un_head, unhop, un_new);
+ un_new->un_nextchild = unhop->un_nextchild;
+ unhop->un_nextchild = un_new;
+ un_new = NULL;
+ NL_LOG(LOG_DEBUG2, "linked cloned nexthop %p", nh);
+ }
+
+ UN_WUNLOCK(ctl);
+
+ if (un_new != NULL)
+ destroy_unhop(un_new);
+
+ *perror = 0;
+ nhop_ref_any(nh);
+ return (nh);
+}
+
+static struct user_nhop *
+nl_find_base_unhop(struct unhop_ctl *ctl, uint32_t uidx)
+{
+ struct user_nhop key= { .un_idx = uidx };
+ struct user_nhop *unhop = NULL;
+ UN_TRACKER;
+
+ UN_RLOCK(ctl);
+ CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
+ UN_RUNLOCK(ctl);
+
+ return (unhop);
+}
+
+#define MAX_STACK_NHOPS 4
+static struct nhop_object *
+clone_unhop(const struct user_nhop *unhop, uint32_t fibnum, int family, int nh_flags)
+{
+ const struct weightened_nhop *wn;
+ struct weightened_nhop *wn_new, wn_base[MAX_STACK_NHOPS];
+ struct nhop_object *nh = NULL;
+ uint32_t num_nhops;
+ int error;
+
+ if (unhop->un_nhop_src != NULL) {
+ IF_DEBUG_LEVEL(LOG_DEBUG2) {
+ char nhbuf[NHOP_PRINT_BUFSIZE];
+ nhop_print_buf_any(unhop->un_nhop_src, nhbuf, sizeof(nhbuf));
+ FIB_NH_LOG(LOG_DEBUG2, unhop->un_nhop_src,
+ "cloning nhop %s -> %u.%u flags 0x%X", nhbuf, fibnum,
+ family, nh_flags);
+ }
+ struct nhop_object *nh;
+ nh = nhop_alloc(fibnum, AF_UNSPEC);
+ if (nh == NULL)
+ return (NULL);
+ nhop_copy(nh, unhop->un_nhop_src);
+ /* Check that nexthop gateway is compatible with the new family */
+ if (!nhop_set_upper_family(nh, family)) {
+ nhop_free(nh);
+ return (NULL);
+ }
+ nhop_set_uidx(nh, unhop->un_idx);
+ nhop_set_pxtype_flag(nh, nh_flags);
+ return (nhop_get_nhop(nh, &error));
+ }
+
+ wn = unhop->un_nhgrp_src;
+ num_nhops = unhop->un_nhgrp_count;
+
+ if (num_nhops > MAX_STACK_NHOPS) {
+ wn_new = malloc(num_nhops * sizeof(struct weightened_nhop), M_TEMP, M_NOWAIT);
+ if (wn_new == NULL)
+ return (NULL);
+ } else
+ wn_new = wn_base;
+
+ for (int i = 0; i < num_nhops; i++) {
+ uint32_t uidx = nhop_get_uidx(wn[i].nh);
+ MPASS(uidx != 0);
+ wn_new[i].nh = nl_find_nhop(fibnum, family, uidx, nh_flags, &error);
+ if (error != 0)
+ break;
+ wn_new[i].weight = wn[i].weight;
+ }
+
+ if (error == 0) {
+ struct rib_head *rh = nhop_get_rh(wn_new[0].nh);
+ struct nhgrp_object *nhg;
+
+ error = nhgrp_get_group(rh, wn_new, num_nhops, unhop->un_idx, &nhg);
+ nh = (struct nhop_object *)nhg;
+ }
+
+ if (wn_new != wn_base)
+ free(wn_new, M_TEMP);
+ return (nh);
+}
+
+static void
+destroy_unhop(struct user_nhop *unhop)
+{
+ if (unhop->un_nhop != NULL)
+ nhop_free_any(unhop->un_nhop);
+ if (unhop->un_nhop_src != NULL)
+ nhop_free_any(unhop->un_nhop_src);
+ free(unhop, M_NETLINK);
+}
+
+static void
+destroy_unhop_epoch(epoch_context_t ctx)
+{
+ struct user_nhop *unhop;
+
+ unhop = __containerof(ctx, struct user_nhop, un_epoch_ctx);
+
+ destroy_unhop(unhop);
+}
+
+static uint32_t
+find_spare_uidx(struct unhop_ctl *ctl)
+{
+ struct user_nhop *unhop, key = {};
+ uint32_t uidx = 0;
+ UN_TRACKER;
+
+ UN_RLOCK(ctl);
+ /* This should return spare uid with 75% of 65k used in ~99/100 cases */
+ for (int i = 0; i < 16; i++) {
+ key.un_idx = (arc4random() % 65536) + 65536 * 4;
+ CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
+ if (unhop == NULL) {
+ uidx = key.un_idx;
+ break;
+ }
+ }
+ UN_RUNLOCK(ctl);
+
+ return (uidx);
+}
+
+
+/*
+ * Actual netlink code
+ */
+struct netlink_walkargs {
+ struct nl_writer *nw;
+ struct nlmsghdr hdr;
+ struct nlpcb *so;
+ int family;
+ int error;
+ int count;
+ int dumped;
+};
+#define ENOMEM_IF_NULL(_v) if ((_v) == NULL) goto enomem
+
+static bool
+dump_nhgrp(const struct user_nhop *unhop, struct nlmsghdr *hdr,
+ struct nl_writer *nw)
+{
+
+ if (!nlmsg_reply(nw, hdr, sizeof(struct nhmsg)))
+ goto enomem;
+
+ struct nhmsg *nhm = nlmsg_reserve_object(nw, struct nhmsg);
+ nhm->nh_family = AF_UNSPEC;
+ nhm->nh_scope = 0;
+ nhm->nh_protocol = unhop->un_protocol;
+ nhm->nh_flags = 0;
+
+ nlattr_add_u32(nw, NHA_ID, unhop->un_idx);
+ nlattr_add_u16(nw, NHA_GROUP_TYPE, NEXTHOP_GRP_TYPE_MPATH);
+
+ struct weightened_nhop *wn = unhop->un_nhgrp_src;
+ uint32_t num_nhops = unhop->un_nhgrp_count;
+ /* TODO: a better API? */
+ int nla_len = sizeof(struct nlattr);
+ nla_len += NETLINK_ALIGN(num_nhops * sizeof(struct nexthop_grp));
+ struct nlattr *nla = nlmsg_reserve_data(nw, nla_len, struct nlattr);
+ if (nla == NULL)
+ goto enomem;
+ nla->nla_type = NHA_GROUP;
+ nla->nla_len = nla_len;
+ for (int i = 0; i < num_nhops; i++) {
+ struct nexthop_grp *grp = &((struct nexthop_grp *)(nla + 1))[i];
+ grp->id = nhop_get_uidx(wn[i].nh);
+ grp->weight = wn[i].weight;
+ grp->resvd1 = 0;
+ grp->resvd2 = 0;
+ }
+
+ if (nlmsg_end(nw))
+ return (true);
+enomem:
+ NL_LOG(LOG_DEBUG, "error: unable to allocate attribute memory");
+ nlmsg_abort(nw);
+ return (false);
+}
+
+static bool
+dump_nhop(const struct user_nhop *unhop, struct nlmsghdr *hdr,
+ struct nl_writer *nw)
+{
+ struct nhop_object *nh = unhop->un_nhop_src;
+
+ if (!nlmsg_reply(nw, hdr, sizeof(struct nhmsg)))
+ goto enomem;
+
+ struct nhmsg *nhm = nlmsg_reserve_object(nw, struct nhmsg);
+ ENOMEM_IF_NULL(nhm);
+ nhm->nh_family = nhop_get_neigh_family(nh);
+ nhm->nh_scope = 0; // XXX: what's that?
+ nhm->nh_protocol = unhop->un_protocol;
+ nhm->nh_flags = 0;
+
+ nlattr_add_u32(nw, NHA_ID, unhop->un_idx);
+ if (nh->nh_flags & NHF_BLACKHOLE) {
+ nlattr_add_flag(nw, NHA_BLACKHOLE);
+ goto done;
+ }
+ nlattr_add_u32(nw, NHA_OIF, nh->nh_ifp->if_index);
+
+ switch (nh->gw_sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ nlattr_add(nw, NHA_GATEWAY, 4, &nh->gw4_sa.sin_addr);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct in6_addr addr = nh->gw6_sa.sin6_addr;
+ in6_clearscope(&addr);
+ nlattr_add(nw, NHA_GATEWAY, 16, &addr);
+ break;
+ }
+#endif
+ }
+
+done:
+ if (nlmsg_end(nw))
+ return (true);
+enomem:
+ nlmsg_abort(nw);
+ return (false);
+}
+
+static void
+dump_unhop(const struct user_nhop *unhop, struct nlmsghdr *hdr,
+ struct nl_writer *nw)
+{
+ if (unhop->un_nhop_src != NULL)
+ dump_nhop(unhop, hdr, nw);
+ else
+ dump_nhgrp(unhop, hdr, nw);
+}
+
+static int
+delete_unhop(struct unhop_ctl *ctl, struct nlmsghdr *hdr, uint32_t uidx)
+{
+ struct user_nhop *unhop_ret, *unhop_base, *unhop_chain;
+
+ struct user_nhop key = { .un_idx = uidx };
+
+ UN_WLOCK(ctl);
+
+ CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop_base);
+
+ if (unhop_base != NULL) {
+ CHT_SLIST_REMOVE(&ctl->un_head, unhop, unhop_base, unhop_ret);
+ IF_DEBUG_LEVEL(LOG_DEBUG2) {
+ char nhbuf[NHOP_PRINT_BUFSIZE];
+ nhop_print_buf_any(unhop_base->un_nhop, nhbuf, sizeof(nhbuf));
+ FIB_NH_LOG(LOG_DEBUG3, unhop_base->un_nhop,
+ "removed base nhop %u: %s", uidx, nhbuf);
+ }
+ /* Unlink all child nexhops as well, keeping the chain intact */
+ unhop_chain = unhop_base->un_nextchild;
+ while (unhop_chain != NULL) {
+ CHT_SLIST_REMOVE(&ctl->un_head, unhop, unhop_chain,
+ unhop_ret);
+ MPASS(unhop_chain == unhop_ret);
+ IF_DEBUG_LEVEL(LOG_DEBUG3) {
+ char nhbuf[NHOP_PRINT_BUFSIZE];
+ nhop_print_buf_any(unhop_chain->un_nhop,
+ nhbuf, sizeof(nhbuf));
+ FIB_NH_LOG(LOG_DEBUG3, unhop_chain->un_nhop,
+ "removed child nhop %u: %s", uidx, nhbuf);
+ }
+ unhop_chain = unhop_chain->un_nextchild;
+ }
+ }
+
+ UN_WUNLOCK(ctl);
+
+ if (unhop_base == NULL) {
+ NL_LOG(LOG_DEBUG, "unable to find unhop %u", uidx);
+ return (ENOENT);
+ }
+
+ /* Report nexthop deletion */
+ struct netlink_walkargs wa = {
+ .hdr.nlmsg_pid = hdr->nlmsg_pid,
+ .hdr.nlmsg_seq = hdr->nlmsg_seq,
+ .hdr.nlmsg_flags = hdr->nlmsg_flags,
+ .hdr.nlmsg_type = NL_RTM_DELNEXTHOP,
+ };
+
+ struct nl_writer nw = {};
+ if (!nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_ROUTE, RTNLGRP_NEXTHOP)) {
+ NL_LOG(LOG_DEBUG, "error allocating message writer");
+ return (ENOMEM);
+ }
+
+ dump_unhop(unhop_base, &wa.hdr, &nw);
+ nlmsg_flush(&nw);
+
+ while (unhop_base != NULL) {
+ unhop_chain = unhop_base->un_nextchild;
+ epoch_call(net_epoch_preempt, destroy_unhop_epoch,
+ &unhop_base->un_epoch_ctx);
+ unhop_base = unhop_chain;
+ }
+
+ return (0);
+}
+
+static void
+consider_resize(struct unhop_ctl *ctl, uint32_t new_size)
+{
+ void *new_ptr = NULL;
+ size_t alloc_size;
+
+ if (new_size == 0)
+ return;
+
+ if (new_size != 0) {
+ alloc_size = CHT_SLIST_GET_RESIZE_SIZE(new_size);
+ new_ptr = malloc(alloc_size, M_NETLINK, M_NOWAIT | M_ZERO);
+ if (new_ptr == NULL)
+ return;
+ }
+
+ NL_LOG(LOG_DEBUG, "resizing hash: %u -> %u", ctl->un_head.hash_size, new_size);
+ UN_WLOCK(ctl);
+ if (new_ptr != NULL) {
+ CHT_SLIST_RESIZE(&ctl->un_head, unhop, new_ptr, new_size);
+ }
+ UN_WUNLOCK(ctl);
+
+
+ if (new_ptr != NULL)
+ free(new_ptr, M_NETLINK);
+}
+
+static bool __noinline
+vnet_init_unhops()
+{
+ uint32_t num_buckets = 16;
+ size_t alloc_size = CHT_SLIST_GET_RESIZE_SIZE(num_buckets);
+
+ struct unhop_ctl *ctl = malloc(sizeof(struct unhop_ctl), M_NETLINK,
+ M_NOWAIT | M_ZERO);
+ if (ctl == NULL)
+ return (false);
+
+ void *ptr = malloc(alloc_size, M_NETLINK, M_NOWAIT | M_ZERO);
+ if (ptr == NULL) {
+ free(ctl, M_NETLINK);
+ return (false);
+ }
+ CHT_SLIST_INIT(&ctl->un_head, ptr, num_buckets);
+ UN_LOCK_INIT(ctl);
+
+ if (!atomic_cmpset_ptr((uintptr_t *)&V_un_ctl, (uintptr_t)NULL, (uintptr_t)ctl)) {
+ free(ptr, M_NETLINK);
+ free(ctl, M_NETLINK);
+ }
+
+ if (atomic_load_ptr(&V_un_ctl) == NULL)
+ return (false);
+
+ NL_LOG(LOG_NOTICE, "UNHOPS init done");
+
+ return (true);
+}
+
+static void
+vnet_destroy_unhops(const void *unused __unused)
+{
+ struct unhop_ctl *ctl = atomic_load_ptr(&V_un_ctl);
+ struct user_nhop *unhop, *tmp;
+
+ if (ctl == NULL)
+ return;
+ V_un_ctl = NULL;
+
+ /* Wait till all unhop users finish their reads */
+ epoch_wait_preempt(net_epoch_preempt);
+
+ UN_WLOCK(ctl);
+ CHT_SLIST_FOREACH_SAFE(&ctl->un_head, unhop, unhop, tmp) {
+ destroy_unhop(unhop);
+ } CHT_SLIST_FOREACH_SAFE_END;
+ UN_WUNLOCK(ctl);
+
+ free(ctl->un_head.ptr, M_NETLINK);
+ free(ctl, M_NETLINK);
+}
+VNET_SYSUNINIT(vnet_destroy_unhops, SI_SUB_PROTO_IF, SI_ORDER_ANY,
+ vnet_destroy_unhops, NULL);
+
+static int
+nlattr_get_nhg(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target)
+{
+ int error = 0;
+
+ /* Verify attribute correctness */
+ struct nexthop_grp *grp = NLA_DATA(nla);
+ int data_len = NLA_DATA_LEN(nla);
+
+ int count = data_len / sizeof(*grp);
+ if (count == 0 || (count * sizeof(*grp) != data_len)) {
+ NL_LOG(LOG_DEBUG, "Invalid length for RTA_GROUP: %d", data_len);
+ return (EINVAL);
+ }
+
+ *((struct nlattr **)target) = nla;
+ return (error);
+}
+
+struct nl_parsed_nhop {
+ uint32_t nha_id;
+ uint8_t nha_blackhole;
+ uint8_t nha_groups;
+ struct ifnet *nha_oif;
+ struct sockaddr *nha_gw;
+ struct nlattr *nha_group;
+ uint8_t nh_family;
+ uint8_t nh_protocol;
+};
+
+#define _IN(_field) offsetof(struct nhmsg, _field)
+#define _OUT(_field) offsetof(struct nl_parsed_nhop, _field)
+static const struct nlfield_parser nlf_p_nh[] = {
+ { .off_in = _IN(nh_family), .off_out = _OUT(nh_family), .cb = nlf_get_u8 },
+ { .off_in = _IN(nh_protocol), .off_out = _OUT(nh_protocol), .cb = nlf_get_u8 },
+};
+
+static const struct nlattr_parser nla_p_nh[] = {
+ { .type = NHA_ID, .off = _OUT(nha_id), .cb = nlattr_get_uint32 },
+ { .type = NHA_GROUP, .off = _OUT(nha_group), .cb = nlattr_get_nhg },
+ { .type = NHA_BLACKHOLE, .off = _OUT(nha_blackhole), .cb = nlattr_get_flag },
+ { .type = NHA_OIF, .off = _OUT(nha_oif), .cb = nlattr_get_ifp },
+ { .type = NHA_GATEWAY, .off = _OUT(nha_gw), .cb = nlattr_get_ip },
+ { .type = NHA_GROUPS, .off = _OUT(nha_groups), .cb = nlattr_get_flag },
+};
+#undef _IN
+#undef _OUT
+NL_DECLARE_PARSER(nhmsg_parser, struct nhmsg, nlf_p_nh, nla_p_nh);
+
+static bool
+eligible_nhg(const struct nhop_object *nh)
+{
+ return (nh->nh_flags & NHF_GATEWAY);
+}
+
+static int
+newnhg(struct unhop_ctl *ctl, struct nl_parsed_nhop *attrs, struct user_nhop *unhop)
+{
+ struct nexthop_grp *grp = NLA_DATA(attrs->nha_group);
+ int count = NLA_DATA_LEN(attrs->nha_group) / sizeof(*grp);
+ struct weightened_nhop *wn;
+
+ wn = malloc(sizeof(*wn) * count, M_NETLINK, M_NOWAIT | M_ZERO);
+ if (wn == NULL)
+ return (ENOMEM);
+
+ for (int i = 0; i < count; i++) {
+ struct user_nhop *unhop;
+ unhop = nl_find_base_unhop(ctl, grp[i].id);
+ if (unhop == NULL) {
+ NL_LOG(LOG_DEBUG, "unable to find uidx %u", grp[i].id);
+ free(wn, M_NETLINK);
+ return (ESRCH);
+ } else if (unhop->un_nhop_src == NULL) {
+ NL_LOG(LOG_DEBUG, "uidx %u is a group, nested group unsupported",
+ grp[i].id);
+ free(wn, M_NETLINK);
+ return (ENOTSUP);
+ } else if (!eligible_nhg(unhop->un_nhop_src)) {
+ NL_LOG(LOG_DEBUG, "uidx %u nhop is not mpath-eligible",
+ grp[i].id);
+ free(wn, M_NETLINK);
+ return (ENOTSUP);
+ }
+ /*
+ * TODO: consider more rigid eligibility checks:
+ * restrict nexthops with the same gateway
+ */
+ wn[i].nh = unhop->un_nhop_src;
+ wn[i].weight = grp[i].weight;
+ }
+ unhop->un_nhgrp_src = wn;
+ unhop->un_nhgrp_count = count;
+ return (0);
+}
+
+static int
+newnhop(struct nl_parsed_nhop *attrs, struct user_nhop *unhop)
+{
+ struct ifaddr *ifa = NULL;
+ struct nhop_object *nh;
+ int error;
+
+ if (!attrs->nha_blackhole) {
+ if (attrs->nha_gw == NULL) {
+ NL_LOG(LOG_DEBUG, "missing NHA_GATEWAY");
+ return (EINVAL);
+ }
+ if (attrs->nha_oif == NULL) {
+ NL_LOG(LOG_DEBUG, "missing NHA_OIF");
+ return (EINVAL);
+ }
+ if (ifa == NULL)
+ ifa = ifaof_ifpforaddr(attrs->nha_gw, attrs->nha_oif);
+ if (ifa == NULL) {
+ NL_LOG(LOG_DEBUG, "Unable to determine default source IP");
+ return (EINVAL);
+ }
+ }
+
+ int family = attrs->nha_gw != NULL ? attrs->nha_gw->sa_family : attrs->nh_family;
+
+ nh = nhop_alloc(RT_DEFAULT_FIB, family);
+ if (nh == NULL) {
+ NL_LOG(LOG_DEBUG, "Unable to allocate nexthop");
+ return (ENOMEM);
+ }
+ nhop_set_uidx(nh, attrs->nha_id);
+
+ if (attrs->nha_blackhole)
+ nhop_set_blackhole(nh, NHF_BLACKHOLE);
+ else {
+ nhop_set_gw(nh, attrs->nha_gw, true);
+ nhop_set_transmit_ifp(nh, attrs->nha_oif);
+ nhop_set_src(nh, ifa);
+ }
+
+ error = nhop_get_unlinked(nh);
+ if (error != 0) {
+ NL_LOG(LOG_DEBUG, "unable to finalize nexthop");
+ return (error);
+ }
+
+ IF_DEBUG_LEVEL(LOG_DEBUG2) {
+ char nhbuf[NHOP_PRINT_BUFSIZE];
+ nhop_print_buf(nh, nhbuf, sizeof(nhbuf));
+ NL_LOG(LOG_DEBUG2, "Adding unhop %u: %s", attrs->nha_id, nhbuf);
+ }
+
+ unhop->un_nhop_src = nh;
+ return (0);
+}
+
+static int
+rtnl_handle_newnhop(struct nlmsghdr *hdr, struct nlpcb *nlp,
+ struct nl_pstate *npt)
+{
+ struct user_nhop *unhop;
+ int error;
+
+ if ((__predict_false(V_un_ctl == NULL)) && (!vnet_init_unhops()))
+ return (ENOMEM);
+ struct unhop_ctl *ctl = V_un_ctl;
+
+ struct nl_parsed_nhop attrs = {};
+ error = nl_parse_nlmsg(hdr, &nhmsg_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ /*
+ * Get valid nha_id. Treat nha_id == 0 (auto-assignment) as a second-class
+ * citizen.
+ */
+ if (attrs.nha_id == 0) {
+ attrs.nha_id = find_spare_uidx(ctl);
+ if (attrs.nha_id == 0) {
+ NL_LOG(LOG_DEBUG, "Unable to get spare uidx");
+ return (ENOSPC);
+ }
+ }
+
+ NL_LOG(LOG_DEBUG, "IFINDEX %d", attrs.nha_oif ? attrs.nha_oif->if_index : 0);
+
+ unhop = malloc(sizeof(struct user_nhop), M_NETLINK, M_NOWAIT | M_ZERO);
+ if (unhop == NULL) {
+ NL_LOG(LOG_DEBUG, "Unable to allocate user_nhop");
+ return (ENOMEM);
+ }
+ unhop->un_idx = attrs.nha_id;
+ unhop->un_protocol = attrs.nh_protocol;
+
+ if (attrs.nha_group)
+ error = newnhg(ctl, &attrs, unhop);
+ else
+ error = newnhop(&attrs, unhop);
+
+ if (error != 0) {
+ free(unhop, M_NETLINK);
+ return (error);
+ }
+
+ UN_WLOCK(ctl);
+ /* Check if uidx already exists */
+ struct user_nhop *tmp = NULL;
+ CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, unhop, tmp);
+ if (tmp != NULL) {
+ UN_WUNLOCK(ctl);
+ NL_LOG(LOG_DEBUG, "nhop idx %u already exists", attrs.nha_id);
+ destroy_unhop(unhop);
+ return (EEXIST);
+ }
+ CHT_SLIST_INSERT_HEAD(&ctl->un_head, unhop, unhop);
+ uint32_t num_buckets_new = CHT_SLIST_GET_RESIZE_BUCKETS(&ctl->un_head);
+ UN_WUNLOCK(ctl);
+
+ /* Report addition of the next nexhop */
+ struct netlink_walkargs wa = {
+ .hdr.nlmsg_pid = hdr->nlmsg_pid,
+ .hdr.nlmsg_seq = hdr->nlmsg_seq,
+ .hdr.nlmsg_flags = hdr->nlmsg_flags,
+ .hdr.nlmsg_type = NL_RTM_NEWNEXTHOP,
+ };
+
+ struct nl_writer nw = {};
+ if (!nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_ROUTE, RTNLGRP_NEXTHOP)) {
+ NL_LOG(LOG_DEBUG, "error allocating message writer");
+ return (ENOMEM);
+ }
+
+ dump_unhop(unhop, &wa.hdr, &nw);
+ nlmsg_flush(&nw);
+
+ consider_resize(ctl, num_buckets_new);
+
+ return (0);
+}
+
+static int
+rtnl_handle_delnhop(struct nlmsghdr *hdr, struct nlpcb *nlp,
+ struct nl_pstate *npt)
+{
+ struct unhop_ctl *ctl = atomic_load_ptr(&V_un_ctl);
+ int error;
+
+ if (__predict_false(ctl == NULL))
+ return (ESRCH);
+
+ struct nl_parsed_nhop attrs = {};
+ error = nl_parse_nlmsg(hdr, &nhmsg_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ if (attrs.nha_id == 0) {
+ NL_LOG(LOG_DEBUG, "NHA_ID not set");
+ return (EINVAL);
+ }
+
+ error = delete_unhop(ctl, hdr, attrs.nha_id);
+
+ return (error);
+}
+
+static bool
+match_unhop(const struct nl_parsed_nhop *attrs, struct user_nhop *unhop)
+{
+ if (attrs->nha_id != 0 && unhop->un_idx != attrs->nha_id)
+ return (false);
+ if (attrs->nha_groups != 0 && unhop->un_nhgrp_src == NULL)
+ return (false);
+ if (attrs->nha_oif != NULL &&
+ (unhop->un_nhop_src == NULL || unhop->un_nhop_src->nh_ifp != attrs->nha_oif))
+ return (false);
+
+ return (true);
+}
+
+static int
+rtnl_handle_getnhop(struct nlmsghdr *hdr, struct nlpcb *nlp,
+ struct nl_pstate *npt)
+{
+ struct unhop_ctl *ctl = atomic_load_ptr(&V_un_ctl);
+ struct user_nhop *unhop;
+ UN_TRACKER;
+ int error;
+
+ if (__predict_false(ctl == NULL))
+ return (ESRCH);
+
+ struct nl_parsed_nhop attrs = {};
+ error = nl_parse_nlmsg(hdr, &nhmsg_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ struct netlink_walkargs wa = {
+ .nw = npt->nw,
+ .hdr.nlmsg_pid = hdr->nlmsg_pid,
+ .hdr.nlmsg_seq = hdr->nlmsg_seq,
+ .hdr.nlmsg_flags = hdr->nlmsg_flags,
+ .hdr.nlmsg_type = NL_RTM_NEWNEXTHOP,
+ };
+
+ if (attrs.nha_id != 0) {
+ NL_LOG(LOG_DEBUG2, "searching for uidx %u", attrs.nha_id);
+ struct user_nhop key= { .un_idx = attrs.nha_id };
+ UN_RLOCK(ctl);
+ CHT_SLIST_FIND_BYOBJ(&ctl->un_head, unhop, &key, unhop);
+ UN_RUNLOCK(ctl);
+
+ if (unhop == NULL)
+ return (ESRCH);
+ dump_unhop(unhop, &wa.hdr, wa.nw);
+ return (0);
+ }
+
+ UN_RLOCK(ctl);
+ wa.hdr.nlmsg_flags |= NLM_F_MULTI;
+ CHT_SLIST_FOREACH(&ctl->un_head, unhop, unhop) {
+ if (UNHOP_IS_MASTER(unhop) && match_unhop(&attrs, unhop))
+ dump_unhop(unhop, &wa.hdr, wa.nw);
+ } CHT_SLIST_FOREACH_END;
+ UN_RUNLOCK(ctl);
+
+ if (wa.error == 0) {
+ if (!nlmsg_end_dump(wa.nw, wa.error, &wa.hdr))
+ return (ENOMEM);
+ }
+ return (0);
+}
+
+static const struct rtnl_cmd_handler cmd_handlers[] = {
+ {
+ .cmd = NL_RTM_NEWNEXTHOP,
+ .name = "RTM_NEWNEXTHOP",
+ .cb = &rtnl_handle_newnhop,
+ .priv = PRIV_NET_ROUTE,
+ },
+ {
+ .cmd = NL_RTM_DELNEXTHOP,
+ .name = "RTM_DELNEXTHOP",
+ .cb = &rtnl_handle_delnhop,
+ .priv = PRIV_NET_ROUTE,
+ },
+ {
+ .cmd = NL_RTM_GETNEXTHOP,
+ .name = "RTM_GETNEXTHOP",
+ .cb = &rtnl_handle_getnhop,
+ }
+};
+
+static const struct nlhdr_parser *all_parsers[] = { &nhmsg_parser };
+
+void
+rtnl_nexthops_init()
+{
+ NL_VERIFY_PARSERS(all_parsers);
+ rtnl_register_messages(cmd_handlers, NL_ARRAY_LEN(cmd_handlers));
+}
diff --git a/sys/netlink/route/nexthop.h b/sys/netlink/route/nexthop.h
new file mode 100644
index 000000000000..310c3e08fc4b
--- /dev/null
+++ b/sys/netlink/route/nexthop.h
@@ -0,0 +1,102 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * NEXTHOP-related (RTM_<NEW|DEL|GET>NEXTHOP) message header and attributes.
+ */
+
+#ifndef _NETLINK_ROUTE_NEXTHOP_H_
+#define _NETLINK_ROUTE_NEXTHOP_H_
+
+/* Base header for all of the relevant messages */
+struct nhmsg {
+ unsigned char nh_family; /* transport family */
+ unsigned char nh_scope; /* ignored on RX, filled by kernel */
+ unsigned char nh_protocol; /* Routing protocol that installed nh */
+ unsigned char resvd;
+ unsigned int nh_flags; /* RTNH_F_* flags from route.h */
+};
+
+enum {
+ NHA_UNSPEC,
+ NHA_ID, /* u32: nexthop userland index, auto-assigned if 0 */
+ NHA_GROUP, /* binary: array of struct nexthop_grp */
+ NHA_GROUP_TYPE, /* u16: set to NEXTHOP_GRP_TYPE */
+ NHA_BLACKHOLE, /* flag: nexthop used to blackhole packets */
+ NHA_OIF, /* u32: transmit ifindex */
+ NHA_GATEWAY, /* network: IPv4/IPv6 gateway addr */
+ NHA_ENCAP_TYPE, /* not supported */
+ NHA_ENCAP, /* not supported */
+ NHA_GROUPS, /* flag: match nexthop groups */
+ NHA_MASTER, /* not supported */
+ NHA_FDB, /* not supported */
+ NHA_RES_GROUP, /* not supported */
+ NHA_RES_BUCKET, /* not supported */
+ __NHA_MAX,
+};
+#define NHA_MAX (__NHA_MAX - 1)
+
+/*
+ * Attributes that can be used as filters:
+ * NHA_ID (nexhop or group), NHA_OIF, NHA_GROUPS,
+ */
+
+/*
+ * NHA_GROUP: array of the following structures.
+ * If attribute is set, the only other valid attributes are
+ * NHA_ID and NHA_GROUP_TYPE.
+ * NHA_RES_GROUP and NHA_RES_BUCKET are not supported yet
+ */
+struct nexthop_grp {
+ uint32_t id; /* nexhop userland index */
+ uint8_t weight; /* weight of this nexthop */
+ uint8_t resvd1;
+ uint16_t resvd2;
+};
+
+/* NHA_GROUP_TYPE: u16 */
+enum {
+ NEXTHOP_GRP_TYPE_MPATH, /* default nexthop group */
+ NEXTHOP_GRP_TYPE_RES, /* resilient nexthop group */
+ __NEXTHOP_GRP_TYPE_MAX,
+};
+#define NEXTHOP_GRP_TYPE_MAX (__NEXTHOP_GRP_TYPE_MAX - 1)
+
+
+/* NHA_RES_GROUP */
+enum {
+ NHA_RES_GROUP_UNSPEC,
+ NHA_RES_GROUP_PAD = NHA_RES_GROUP_UNSPEC,
+ NHA_RES_GROUP_BUCKETS,
+ NHA_RES_GROUP_IDLE_TIMER,
+ NHA_RES_GROUP_UNBALANCED_TIMER,
+ NHA_RES_GROUP_UNBALANCED_TIME,
+ __NHA_RES_GROUP_MAX,
+};
+#define NHA_RES_GROUP_MAX (__NHA_RES_GROUP_MAX - 1)
+
+#endif
diff --git a/sys/netlink/route/route.c b/sys/netlink/route/route.c
new file mode 100644
index 000000000000..7573b371155e
--- /dev/null
+++ b/sys/netlink/route/route.c
@@ -0,0 +1,972 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Ng Peng Nam Sean
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#include "opt_inet.h"
+#include "opt_inet6.h"
+#include "opt_route.h"
+#include <sys/types.h>
+#include <sys/malloc.h>
+#include <sys/rmlock.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/route.h>
+#include <net/route/nhop.h>
+#include <net/route/route_ctl.h>
+#include <net/route/route_var.h>
+#include <netlink/netlink.h>
+#include <netlink/netlink_ctl.h>
+#include <netlink/netlink_route.h>
+#include <netlink/route/route_var.h>
+
+#define DEBUG_MOD_NAME nl_route
+#define DEBUG_MAX_LEVEL LOG_DEBUG3
+#include <netlink/netlink_debug.h>
+_DECLARE_DEBUG(LOG_DEBUG);
+
+static unsigned char
+get_rtm_type(const struct nhop_object *nh)
+{
+ int nh_flags = nh->nh_flags;
+
+ /* Use the fact that nhg runtime flags are only NHF_MULTIPATH */
+ if (nh_flags & NHF_BLACKHOLE)
+ return (RTN_BLACKHOLE);
+ else if (nh_flags & NHF_REJECT)
+ return (RTN_PROHIBIT);
+ return (RTN_UNICAST);
+}
+
+static uint8_t
+nl_get_rtm_protocol(const struct nhop_object *nh)
+{
+ if (NH_IS_NHGRP(nh)) {
+ const struct nhgrp_object *nhg = (const struct nhgrp_object *)nh;
+ uint8_t origin = nhgrp_get_origin(nhg);
+ if (origin != RTPROT_UNSPEC)
+ return (origin);
+ nh = nhg->nhops[0];
+ }
+ uint8_t origin = nhop_get_origin(nh);
+ if (origin != RTPROT_UNSPEC)
+ return (origin);
+ /* TODO: remove guesswork once all kernel users fill in origin */
+ int rt_flags = nhop_get_rtflags(nh);
+ if (rt_flags & RTF_PROTO1)
+ return (RTPROT_ZEBRA);
+ if (rt_flags & RTF_STATIC)
+ return (RTPROT_STATIC);
+ return (RTPROT_KERNEL);
+}
+
+static int
+get_rtmsg_type_from_rtsock(int cmd)
+{
+ switch (cmd) {
+ case RTM_ADD:
+ case RTM_CHANGE:
+ case RTM_GET:
+ return NL_RTM_NEWROUTE;
+ case RTM_DELETE:
+ return NL_RTM_DELROUTE;
+ }
+
+ return (0);
+}
+
+/*
+ * fibnum heuristics
+ *
+ * if (dump && rtm_table == 0 && !rta_table) RT_ALL_FIBS
+ * msg rtm_table RTA_TABLE result
+ * RTM_GETROUTE/dump 0 - RT_ALL_FIBS
+ * RTM_GETROUTE/dump 1 - 1
+ * RTM_GETROUTE/get 0 - 0
+ *
+ */
+
+static struct nhop_object *
+rc_get_nhop(const struct rib_cmd_info *rc)
+{
+ return ((rc->rc_cmd == RTM_DELETE) ? rc->rc_nh_old : rc->rc_nh_new);
+}
+
+static void
+dump_rc_nhop_gw(struct nl_writer *nw, const struct nhop_object *nh)
+{
+ int upper_family;
+
+ switch (nhop_get_neigh_family(nh)) {
+ case AF_LINK:
+ /* onlink prefix, skip */
+ break;
+ case AF_INET:
+ nlattr_add(nw, NL_RTA_GATEWAY, 4, &nh->gw4_sa.sin_addr);
+ break;
+ case AF_INET6:
+ upper_family = nhop_get_upper_family(nh);
+ if (upper_family == AF_INET6) {
+ nlattr_add(nw, NL_RTA_GATEWAY, 16, &nh->gw6_sa.sin6_addr);
+ } else if (upper_family == AF_INET) {
+ /* IPv4 over IPv6 */
+ char buf[20];
+ struct rtvia *via = (struct rtvia *)&buf[0];
+ via->rtvia_family = AF_INET6;
+ memcpy(via->rtvia_addr, &nh->gw6_sa.sin6_addr, 16);
+ nlattr_add(nw, NL_RTA_VIA, 17, via);
+ }
+ break;
+ }
+}
+
+static void
+dump_rc_nhop_mtu(struct nl_writer *nw, const struct nhop_object *nh)
+{
+ int nla_len = sizeof(struct nlattr) * 2 + sizeof(uint32_t);
+ struct nlattr *nla = nlmsg_reserve_data(nw, nla_len, struct nlattr);
+
+ if (nla == NULL)
+ return;
+ nla->nla_type = NL_RTA_METRICS;
+ nla->nla_len = nla_len;
+ nla++;
+ nla->nla_type = NL_RTAX_MTU;
+ nla->nla_len = sizeof(struct nlattr) + sizeof(uint32_t);
+ *((uint32_t *)(nla + 1)) = nh->nh_mtu;
+}
+
+static void
+dump_rc_nhg(struct nl_writer *nw, const struct nhgrp_object *nhg, struct rtmsg *rtm)
+{
+ uint32_t uidx = nhgrp_get_uidx(nhg);
+ uint32_t num_nhops;
+ const struct weightened_nhop *wn = nhgrp_get_nhops(nhg, &num_nhops);
+ uint32_t base_rtflags = nhop_get_rtflags(wn[0].nh);
+
+ if (uidx != 0)
+ nlattr_add_u32(nw, NL_RTA_NH_ID, uidx);
+
+ nlattr_add_u32(nw, NL_RTA_RTFLAGS, base_rtflags);
+ int off = nlattr_add_nested(nw, NL_RTA_MULTIPATH);
+ if (off == 0)
+ return;
+
+ for (int i = 0; i < num_nhops; i++) {
+ int nh_off = nlattr_save_offset(nw);
+ struct rtnexthop *rtnh = nlmsg_reserve_object(nw, struct rtnexthop);
+ if (rtnh == NULL)
+ return;
+ rtnh->rtnh_flags = 0;
+ rtnh->rtnh_ifindex = wn[i].nh->nh_ifp->if_index;
+ rtnh->rtnh_hops = wn[i].weight;
+ dump_rc_nhop_gw(nw, wn[i].nh);
+ uint32_t rtflags = nhop_get_rtflags(wn[i].nh);
+ if (rtflags != base_rtflags)
+ nlattr_add_u32(nw, NL_RTA_RTFLAGS, rtflags);
+ if (rtflags & RTF_FIXEDMTU)
+ dump_rc_nhop_mtu(nw, wn[i].nh);
+ rtnh = nlattr_restore_offset(nw, nh_off, struct rtnexthop);
+ /*
+ * nlattr_add() allocates 4-byte aligned storage, no need to aligh
+ * length here
+ * */
+ rtnh->rtnh_len = nlattr_save_offset(nw) - nh_off;
+ }
+ nlattr_set_len(nw, off);
+}
+
+static void
+dump_rc_nhop(struct nl_writer *nw, const struct nhop_object *nh, struct rtmsg *rtm)
+{
+ if (NH_IS_NHGRP(nh)) {
+ dump_rc_nhg(nw, (const struct nhgrp_object *)nh, rtm);
+ return;
+ }
+
+ uint32_t rtflags = nhop_get_rtflags(nh);
+
+ /*
+ * IPv4 over IPv6
+ * ('RTA_VIA', {'family': 10, 'addr': 'fe80::20c:29ff:fe67:2dd'}), ('RTA_OIF', 2),
+ * IPv4 w/ gw
+ * ('RTA_GATEWAY', '172.16.107.131'), ('RTA_OIF', 2)],
+ * Direct route:
+ * ('RTA_OIF', 2)
+ */
+ if (nh->nh_flags & NHF_GATEWAY)
+ dump_rc_nhop_gw(nw, nh);
+
+ uint32_t uidx = nhop_get_uidx(nh);
+ if (uidx != 0)
+ nlattr_add_u32(nw, NL_RTA_NH_ID, uidx);
+ nlattr_add_u32(nw, NL_RTA_KNH_ID, nhop_get_idx(nh));
+ nlattr_add_u32(nw, NL_RTA_RTFLAGS, rtflags);
+
+ if (rtflags & RTF_FIXEDMTU)
+ dump_rc_nhop_mtu(nw, nh);
+ uint32_t nh_expire = nhop_get_expire(nh);
+ if (nh_expire > 0)
+ nlattr_add_u32(nw, NL_RTA_EXPIRES, nh_expire - time_uptime);
+
+ /* In any case, fill outgoing interface */
+ nlattr_add_u32(nw, NL_RTA_OIF, nh->nh_ifp->if_index);
+}
+
+/*
+ * Dumps output from a rib command into an rtmsg
+ */
+
+static int
+dump_px(uint32_t fibnum, const struct nlmsghdr *hdr,
+ const struct rtentry *rt, struct route_nhop_data *rnd,
+ struct nl_writer *nw)
+{
+ struct rtmsg *rtm;
+ int error = 0;
+
+ NET_EPOCH_ASSERT();
+
+ if (!nlmsg_reply(nw, hdr, sizeof(struct rtmsg)))
+ goto enomem;
+
+ int family = rt_get_family(rt);
+ int rtm_off = nlattr_save_offset(nw);
+ rtm = nlmsg_reserve_object(nw, struct rtmsg);
+ rtm->rtm_family = family;
+ rtm->rtm_dst_len = 0;
+ rtm->rtm_src_len = 0;
+ rtm->rtm_tos = 0;
+ if (fibnum < 255)
+ rtm->rtm_table = (unsigned char)fibnum;
+ rtm->rtm_scope = RT_SCOPE_UNIVERSE;
+ if (!NH_IS_NHGRP(rnd->rnd_nhop)) {
+ rtm->rtm_protocol = nl_get_rtm_protocol(rnd->rnd_nhop);
+ rtm->rtm_type = get_rtm_type(rnd->rnd_nhop);
+ } else {
+ rtm->rtm_protocol = RTPROT_UNSPEC; /* TODO: protocol from nhg? */
+ rtm->rtm_type = RTN_UNICAST;
+ }
+
+ nlattr_add_u32(nw, NL_RTA_TABLE, fibnum);
+
+ int plen = 0;
+ uint32_t scopeid = 0;
+ switch (family) {
+ case AF_INET:
+ {
+ struct in_addr addr;
+ rt_get_inet_prefix_plen(rt, &addr, &plen, &scopeid);
+ nlattr_add(nw, NL_RTA_DST, 4, &addr);
+ break;
+ }
+ case AF_INET6:
+ {
+ struct in6_addr addr;
+ rt_get_inet6_prefix_plen(rt, &addr, &plen, &scopeid);
+ nlattr_add(nw, NL_RTA_DST, 16, &addr);
+ break;
+ }
+ default:
+ FIB_LOG(LOG_NOTICE, fibnum, family, "unsupported rt family: %d", family);
+ error = EAFNOSUPPORT;
+ goto flush;
+ }
+
+ rtm = nlattr_restore_offset(nw, rtm_off, struct rtmsg);
+ if (plen > 0)
+ rtm->rtm_dst_len = plen;
+ dump_rc_nhop(nw, rnd->rnd_nhop, rtm);
+
+ if (nlmsg_end(nw))
+ return (0);
+enomem:
+ error = ENOMEM;
+flush:
+ nlmsg_abort(nw);
+ return (error);
+}
+
+static int
+family_to_group(int family)
+{
+ switch (family) {
+ case AF_INET:
+ return (RTNLGRP_IPV4_ROUTE);
+ case AF_INET6:
+ return (RTNLGRP_IPV6_ROUTE);
+ }
+ return (0);
+}
+
+
+static void
+report_operation(uint32_t fibnum, struct rib_cmd_info *rc,
+ struct nlpcb *nlp, struct nlmsghdr *hdr)
+{
+ struct nl_writer nw;
+
+ uint32_t group_id = family_to_group(rt_get_family(rc->rc_rt));
+ if (nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_ROUTE, group_id)) {
+ struct route_nhop_data rnd = {
+ .rnd_nhop = rc_get_nhop(rc),
+ .rnd_weight = rc->rc_nh_weight,
+ };
+ hdr->nlmsg_flags &= ~(NLM_F_REPLACE | NLM_F_CREATE);
+ hdr->nlmsg_flags &= ~(NLM_F_EXCL | NLM_F_APPEND);
+ switch (rc->rc_cmd) {
+ case RTM_ADD:
+ hdr->nlmsg_type = NL_RTM_NEWROUTE;
+ hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL;
+ break;
+ case RTM_CHANGE:
+ hdr->nlmsg_type = NL_RTM_NEWROUTE;
+ hdr->nlmsg_flags |= NLM_F_REPLACE;
+ break;
+ case RTM_DELETE:
+ hdr->nlmsg_type = NL_RTM_DELROUTE;
+ break;
+ }
+ dump_px(fibnum, hdr, rc->rc_rt, &rnd, &nw);
+ nlmsg_flush(&nw);
+ }
+
+ rtsock_callback_p->route_f(fibnum, rc);
+}
+
+struct rta_mpath_nh {
+ struct sockaddr *gw;
+ struct ifnet *ifp;
+ uint8_t rtnh_flags;
+ uint8_t rtnh_weight;
+};
+
+#define _IN(_field) offsetof(struct rtnexthop, _field)
+#define _OUT(_field) offsetof(struct rta_mpath_nh, _field)
+const static struct nlattr_parser nla_p_rtnh[] = {
+ { .type = NL_RTA_GATEWAY, .off = _OUT(gw), .cb = nlattr_get_ip },
+ { .type = NL_RTA_VIA, .off = _OUT(gw), .cb = nlattr_get_ipvia },
+};
+const static struct nlfield_parser nlf_p_rtnh[] = {
+ { .off_in = _IN(rtnh_flags), .off_out = _OUT(rtnh_flags), .cb = nlf_get_u8 },
+ { .off_in = _IN(rtnh_hops), .off_out = _OUT(rtnh_weight), .cb = nlf_get_u8 },
+ { .off_in = _IN(rtnh_ifindex), .off_out = _OUT(ifp), .cb = nlf_get_ifpz },
+};
+#undef _IN
+#undef _OUT
+NL_DECLARE_PARSER(mpath_parser, struct rtnexthop, nlf_p_rtnh, nla_p_rtnh);
+
+struct rta_mpath {
+ int num_nhops;
+ struct rta_mpath_nh nhops[0];
+};
+
+static int
+nlattr_get_multipath(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target)
+{
+ int data_len = nla->nla_len - sizeof(struct nlattr);
+ struct rtnexthop *rtnh;
+
+ int max_nhops = data_len / sizeof(struct rtnexthop);
+
+ struct rta_mpath *mp = npt_alloc(npt, (max_nhops + 2) * sizeof(struct rta_mpath_nh));
+ mp->num_nhops = 0;
+
+ for (rtnh = (struct rtnexthop *)(nla + 1); data_len > 0; ) {
+ struct rta_mpath_nh *mpnh = &mp->nhops[mp->num_nhops++];
+
+ int error = nl_parse_header(rtnh, rtnh->rtnh_len, &mpath_parser,
+ npt, mpnh);
+ if (error != 0) {
+ NLMSG_REPORT_ERR_MSG(npt, "RTA_MULTIPATH: nexhop %d: parse failed",
+ mp->num_nhops - 1);
+ return (error);
+ }
+
+ int len = NL_ITEM_ALIGN(rtnh->rtnh_len);
+ data_len -= len;
+ rtnh = (struct rtnexthop *)((char *)rtnh + len);
+ }
+ if (data_len != 0 || mp->num_nhops == 0) {
+ NLMSG_REPORT_ERR_MSG(npt, "invalid RTA_MULTIPATH attr");
+ return (EINVAL);
+ }
+
+ *((struct rta_mpath **)target) = mp;
+ return (0);
+}
+
+
+struct nl_parsed_route {
+ struct sockaddr *rta_dst;
+ struct sockaddr *rta_gw;
+ struct ifnet *rta_oif;
+ struct rta_mpath *rta_multipath;
+ uint32_t rta_table;
+ uint32_t rta_rtflags;
+ uint32_t rta_nh_id;
+ uint32_t rtax_mtu;
+ uint8_t rtm_family;
+ uint8_t rtm_dst_len;
+};
+
+#define _IN(_field) offsetof(struct rtmsg, _field)
+#define _OUT(_field) offsetof(struct nl_parsed_route, _field)
+static struct nlattr_parser nla_p_rtmetrics[] = {
+ { .type = NL_RTAX_MTU, .off = _OUT(rtax_mtu), .cb = nlattr_get_uint32 },
+};
+NL_DECLARE_ATTR_PARSER(metrics_parser, nla_p_rtmetrics);
+
+static const struct nlattr_parser nla_p_rtmsg[] = {
+ { .type = NL_RTA_DST, .off = _OUT(rta_dst), .cb = nlattr_get_ip },
+ { .type = NL_RTA_OIF, .off = _OUT(rta_oif), .cb = nlattr_get_ifp },
+ { .type = NL_RTA_GATEWAY, .off = _OUT(rta_gw), .cb = nlattr_get_ip },
+ { .type = NL_RTA_METRICS, .arg = &metrics_parser, .cb = nlattr_get_nested },
+ { .type = NL_RTA_MULTIPATH, .off = _OUT(rta_multipath), .cb = nlattr_get_multipath },
+ { .type = NL_RTA_RTFLAGS, .off = _OUT(rta_rtflags), .cb = nlattr_get_uint32 },
+ { .type = NL_RTA_TABLE, .off = _OUT(rta_table), .cb = nlattr_get_uint32 },
+ { .type = NL_RTA_VIA, .off = _OUT(rta_gw), .cb = nlattr_get_ipvia },
+ { .type = NL_RTA_NH_ID, .off = _OUT(rta_nh_id), .cb = nlattr_get_uint32 },
+};
+
+static const struct nlfield_parser nlf_p_rtmsg[] = {
+ {.off_in = _IN(rtm_family), .off_out = _OUT(rtm_family), .cb = nlf_get_u8 },
+ {.off_in = _IN(rtm_dst_len), .off_out = _OUT(rtm_dst_len), .cb = nlf_get_u8 },
+};
+#undef _IN
+#undef _OUT
+NL_DECLARE_PARSER(rtm_parser, struct rtmsg, nlf_p_rtmsg, nla_p_rtmsg);
+
+struct netlink_walkargs {
+ struct nl_writer *nw;
+ struct route_nhop_data rnd;
+ struct nlmsghdr hdr;
+ struct nlpcb *nlp;
+ uint32_t fibnum;
+ int family;
+ int error;
+ int count;
+ int dumped;
+ int dumped_tables;
+};
+
+static int
+dump_rtentry(struct rtentry *rt, void *_arg)
+{
+ struct netlink_walkargs *wa = (struct netlink_walkargs *)_arg;
+ int error;
+
+ wa->count++;
+ if (wa->error != 0)
+ return (0);
+ wa->dumped++;
+
+ rt_get_rnd(rt, &wa->rnd);
+
+ error = dump_px(wa->fibnum, &wa->hdr, rt, &wa->rnd, wa->nw);
+
+ IF_DEBUG_LEVEL(LOG_DEBUG3) {
+ char rtbuf[INET6_ADDRSTRLEN + 5];
+ FIB_LOG(LOG_DEBUG3, wa->fibnum, wa->family,
+ "Dump %s, offset %u, error %d",
+ rt_print_buf(rt, rtbuf, sizeof(rtbuf)),
+ wa->nw->offset, error);
+ }
+ wa->error = error;
+
+ return (0);
+}
+
+static void
+dump_rtable_one(struct netlink_walkargs *wa, uint32_t fibnum, int family)
+{
+ FIB_LOG(LOG_DEBUG2, fibnum, family, "Start dump");
+ wa->count = 0;
+ wa->dumped = 0;
+
+ rib_walk(fibnum, family, false, dump_rtentry, wa);
+
+ wa->dumped_tables++;
+
+ FIB_LOG(LOG_DEBUG2, fibnum, family, "End dump, iterated %d dumped %d",
+ wa->count, wa->dumped);
+ NL_LOG(LOG_DEBUG2, "Current offset: %d", wa->nw->offset);
+}
+
+static int
+dump_rtable_fib(struct netlink_walkargs *wa, uint32_t fibnum, int family)
+{
+ wa->fibnum = fibnum;
+
+ if (family == AF_UNSPEC) {
+ for (int i = 0; i < AF_MAX; i++) {
+ if (rt_tables_get_rnh(fibnum, i) != 0) {
+ wa->family = i;
+ dump_rtable_one(wa, fibnum, i);
+ if (wa->error != 0)
+ break;
+ }
+ }
+ } else {
+ if (rt_tables_get_rnh(fibnum, family) != 0) {
+ wa->family = family;
+ dump_rtable_one(wa, fibnum, family);
+ }
+ }
+
+ return (wa->error);
+}
+
+static int
+handle_rtm_getroute(struct nlpcb *nlp, struct nl_parsed_route *attrs,
+ struct nlmsghdr *hdr, struct nl_pstate *npt)
+{
+ RIB_RLOCK_TRACKER;
+ struct rib_head *rnh;
+ struct rtentry *rt;
+ uint32_t fibnum = attrs->rta_table;
+ sa_family_t family = attrs->rtm_family;
+
+ if (attrs->rta_dst == NULL) {
+ NLMSG_REPORT_ERR_MSG(npt, "No RTA_DST supplied");
+ return (EINVAL);
+ }
+
+ FIB_LOG(LOG_DEBUG, fibnum, family, "getroute called");
+
+ rnh = rt_tables_get_rnh(fibnum, family);
+ if (rnh == NULL)
+ return (EAFNOSUPPORT);
+
+ RIB_RLOCK(rnh);
+
+ rt = (struct rtentry *)rnh->rnh_matchaddr(attrs->rta_dst, &rnh->head);
+ if (rt == NULL) {
+ RIB_RUNLOCK(rnh);
+ return (ESRCH);
+ }
+
+ struct route_nhop_data rnd;
+ rt_get_rnd(rt, &rnd);
+ rnd.rnd_nhop = nhop_select_func(rnd.rnd_nhop, 0);
+
+ RIB_RUNLOCK(rnh);
+
+ IF_DEBUG_LEVEL(LOG_DEBUG2) {
+ char rtbuf[NHOP_PRINT_BUFSIZE] __unused, nhbuf[NHOP_PRINT_BUFSIZE] __unused;
+ FIB_LOG(LOG_DEBUG2, fibnum, family, "getroute completed: got %s for %s",
+ nhop_print_buf_any(rnd.rnd_nhop, nhbuf, sizeof(nhbuf)),
+ rt_print_buf(rt, rtbuf, sizeof(rtbuf)));
+ }
+
+ hdr->nlmsg_type = NL_RTM_NEWROUTE;
+ dump_px(fibnum, hdr, rt, &rnd, npt->nw);
+
+ return (0);
+}
+
+static int
+handle_rtm_dump(struct nlpcb *nlp, uint32_t fibnum, int family,
+ struct nlmsghdr *hdr, struct nl_writer *nw)
+{
+ struct netlink_walkargs wa = {
+ .nlp = nlp,
+ .nw = nw,
+ .hdr.nlmsg_pid = hdr->nlmsg_pid,
+ .hdr.nlmsg_seq = hdr->nlmsg_seq,
+ .hdr.nlmsg_type = NL_RTM_NEWROUTE,
+ .hdr.nlmsg_flags = hdr->nlmsg_flags | NLM_F_MULTI,
+ };
+
+ if (fibnum == RT_TABLE_UNSPEC) {
+ for (int i = 0; i < V_rt_numfibs; i++) {
+ dump_rtable_fib(&wa, fibnum, family);
+ if (wa.error != 0)
+ break;
+ }
+ } else
+ dump_rtable_fib(&wa, fibnum, family);
+
+ if (wa.error == 0 && wa.dumped_tables == 0) {
+ FIB_LOG(LOG_DEBUG, fibnum, family, "incorrect fibnum/family");
+ wa.error = ESRCH;
+ // How do we propagate it?
+ }
+
+ if (!nlmsg_end_dump(wa.nw, wa.error, &wa.hdr)) {
+ NL_LOG(LOG_DEBUG, "Unable to finalize the dump");
+ return (ENOMEM);
+ }
+
+ return (wa.error);
+}
+
+static struct nhop_object *
+finalize_nhop(struct nhop_object *nh, int *perror)
+{
+ /*
+ * The following MUST be filled:
+ * nh_ifp, nh_ifa, nh_gw
+ */
+ if (nh->gw_sa.sa_family == 0) {
+ /*
+ * Empty gateway. Can be direct route with RTA_OIF set.
+ */
+ if (nh->nh_ifp != NULL)
+ nhop_set_direct_gw(nh, nh->nh_ifp);
+ else {
+ NL_LOG(LOG_DEBUG, "empty gateway and interface, skipping");
+ *perror = EINVAL;
+ return (NULL);
+ }
+ /* Both nh_ifp and gateway are set */
+ } else {
+ /* Gateway is set up, we can derive ifp if not set */
+ if (nh->nh_ifp == NULL) {
+ struct ifaddr *ifa = ifa_ifwithnet(&nh->gw_sa, 1, nhop_get_fibnum(nh));
+ if (ifa == NULL) {
+ NL_LOG(LOG_DEBUG, "Unable to determine ifp, skipping");
+ *perror = EINVAL;
+ return (NULL);
+ }
+ nhop_set_transmit_ifp(nh, ifa->ifa_ifp);
+ }
+ }
+ /* Both nh_ifp and gateway are set */
+ if (nh->nh_ifa == NULL) {
+ struct ifaddr *ifa = ifaof_ifpforaddr(&nh->gw_sa, nh->nh_ifp);
+ if (ifa == NULL) {
+ NL_LOG(LOG_DEBUG, "Unable to determine ifa, skipping");
+ *perror = EINVAL;
+ return (NULL);
+ }
+ nhop_set_src(nh, ifa);
+ }
+
+ return (nhop_get_nhop(nh, perror));
+}
+
+static int
+get_pxflag(const struct nl_parsed_route *attrs)
+{
+ int pxflag = 0;
+ switch (attrs->rtm_family) {
+ case AF_INET:
+ if (attrs->rtm_dst_len == 32)
+ pxflag = NHF_HOST;
+ else if (attrs->rtm_dst_len == 0)
+ pxflag = NHF_DEFAULT;
+ break;
+ case AF_INET6:
+ if (attrs->rtm_dst_len == 32)
+ pxflag = NHF_HOST;
+ else if (attrs->rtm_dst_len == 0)
+ pxflag = NHF_DEFAULT;
+ break;
+ }
+
+ return (pxflag);
+}
+
+static int
+get_op_flags(int nlm_flags)
+{
+ int op_flags = 0;
+
+ op_flags |= (nlm_flags & NLM_F_REPLACE) ? RTM_F_REPLACE : 0;
+ op_flags |= (nlm_flags & NLM_F_EXCL) ? RTM_F_EXCL : 0;
+ op_flags |= (nlm_flags & NLM_F_CREATE) ? RTM_F_CREATE : 0;
+ op_flags |= (nlm_flags & NLM_F_APPEND) ? RTM_F_APPEND : 0;
+
+ return (op_flags);
+}
+
+static int
+create_nexthop_one(struct nl_parsed_route *attrs, struct rta_mpath_nh *mpnh,
+ struct nl_pstate *npt, struct nhop_object **pnh)
+{
+ int error;
+
+ if (mpnh->gw == NULL)
+ return (EINVAL);
+
+ struct nhop_object *nh = nhop_alloc(attrs->rta_table, attrs->rtm_family);
+ if (nh == NULL)
+ return (ENOMEM);
+
+ nhop_set_gw(nh, mpnh->gw, true);
+ if (mpnh->ifp != NULL)
+ nhop_set_transmit_ifp(nh, mpnh->ifp);
+ nhop_set_rtflags(nh, attrs->rta_rtflags);
+
+ *pnh = finalize_nhop(nh, &error);
+
+ return (error);
+}
+
+static struct nhop_object *
+create_nexthop_from_attrs(struct nl_parsed_route *attrs,
+ struct nl_pstate *npt, int *perror)
+{
+ struct nhop_object *nh;
+ int error = 0;
+
+ if (attrs->rta_multipath != NULL) {
+ /* Multipath w/o explicit nexthops */
+ int num_nhops = attrs->rta_multipath->num_nhops;
+ struct weightened_nhop *wn = npt_alloc(npt, sizeof(*wn) * num_nhops);
+ nh = NULL;
+
+ for (int i = 0; i < num_nhops; i++) {
+ struct rta_mpath_nh *mpnh = &attrs->rta_multipath->nhops[i];
+
+ error = create_nexthop_one(attrs, mpnh, npt, &wn[i].nh);
+ if (error != 0) {
+ for (int j = 0; j < i; j++)
+ nhop_free(wn[j].nh);
+ break;
+ }
+ wn[i].weight = mpnh->rtnh_weight > 0 ? mpnh->rtnh_weight : 1;
+ }
+ if (error == 0) {
+ struct rib_head *rh = nhop_get_rh(wn[0].nh);
+
+ error = nhgrp_get_group(rh, wn, num_nhops, 0,
+ (struct nhgrp_object **)&nh);
+
+ for (int i = 0; i < num_nhops; i++)
+ nhop_free(wn[i].nh);
+ }
+ *perror = error;
+ } else {
+ nh = nhop_alloc(attrs->rta_table, attrs->rtm_family);
+ if (nh == NULL) {
+ *perror = ENOMEM;
+ return (NULL);
+ }
+ if (attrs->rta_gw != NULL)
+ nhop_set_gw(nh, attrs->rta_gw, true);
+ if (attrs->rta_oif != NULL)
+ nhop_set_transmit_ifp(nh, attrs->rta_oif);
+ if (attrs->rtax_mtu != 0)
+ nhop_set_mtu(nh, attrs->rtax_mtu, true);
+ if (attrs->rta_rtflags & RTF_BROADCAST)
+ nhop_set_broadcast(nh, true);
+ if (attrs->rta_rtflags & RTF_BLACKHOLE)
+ nhop_set_blackhole(nh, NHF_BLACKHOLE);
+ if (attrs->rta_rtflags & RTF_REJECT)
+ nhop_set_blackhole(nh, NHF_REJECT);
+ nhop_set_rtflags(nh, attrs->rta_rtflags);
+ nh = finalize_nhop(nh, perror);
+ }
+
+ return (nh);
+}
+
+static int
+rtnl_handle_newroute(struct nlmsghdr *hdr, struct nlpcb *nlp,
+ struct nl_pstate *npt)
+{
+ struct rib_cmd_info rc = {};
+ struct nhop_object *nh = NULL;
+ int error;
+
+ struct nl_parsed_route attrs = {};
+ error = nl_parse_nlmsg(hdr, &rtm_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ /* Check if we have enough data */
+ if (attrs.rta_dst == NULL) {
+ NL_LOG(LOG_DEBUG, "missing RTA_DST");
+ return (EINVAL);
+ }
+
+ if (attrs.rta_nh_id != 0) {
+ /* Referenced uindex */
+ int pxflag = get_pxflag(&attrs);
+ nh = nl_find_nhop(attrs.rta_table, attrs.rtm_family, attrs.rta_nh_id,
+ pxflag, &error);
+ if (error != 0)
+ return (error);
+ } else {
+ nh = create_nexthop_from_attrs(&attrs, npt, &error);
+ if (error != 0) {
+ NL_LOG(LOG_DEBUG, "Error creating nexthop");
+ return (error);
+ }
+ }
+
+ int weight = NH_IS_NHGRP(nh) ? 0 : RT_DEFAULT_WEIGHT;
+ struct route_nhop_data rnd = { .rnd_nhop = nh, .rnd_weight = weight };
+ int op_flags = get_op_flags(hdr->nlmsg_flags);
+
+ error = rib_add_route_px(attrs.rta_table, attrs.rta_dst, attrs.rtm_dst_len,
+ &rnd, op_flags, &rc);
+ if (error == 0)
+ report_operation(attrs.rta_table, &rc, nlp, hdr);
+ return (error);
+}
+
+static int
+path_match_func(const struct rtentry *rt, const struct nhop_object *nh, void *_data)
+{
+ struct nl_parsed_route *attrs = (struct nl_parsed_route *)_data;
+
+ if ((attrs->rta_gw != NULL) && !rib_match_gw(rt, nh, attrs->rta_gw))
+ return (0);
+
+ if ((attrs->rta_oif != NULL) && (attrs->rta_oif != nh->nh_ifp))
+ return (0);
+
+ return (1);
+}
+
+static int
+rtnl_handle_delroute(struct nlmsghdr *hdr, struct nlpcb *nlp,
+ struct nl_pstate *npt)
+{
+ struct rib_cmd_info rc;
+ int error;
+
+ struct nl_parsed_route attrs = {};
+ error = nl_parse_nlmsg(hdr, &rtm_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ if (attrs.rta_dst == NULL) {
+ NLMSG_REPORT_ERR_MSG(npt, "RTA_DST is not set");
+ return (ESRCH);
+ }
+
+ error = rib_del_route_px(attrs.rta_table, attrs.rta_dst,
+ attrs.rtm_dst_len, path_match_func, &attrs, 0, &rc);
+ if (error == 0)
+ report_operation(attrs.rta_table, &rc, nlp, hdr);
+ return (error);
+}
+
+static int
+rtnl_handle_getroute(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
+{
+ int error;
+
+ struct nl_parsed_route attrs = {};
+ error = nl_parse_nlmsg(hdr, &rtm_parser, npt, &attrs);
+ if (error != 0)
+ return (error);
+
+ if (hdr->nlmsg_flags & NLM_F_DUMP)
+ error = handle_rtm_dump(nlp, attrs.rta_table, attrs.rtm_family, hdr, npt->nw);
+ else
+ error = handle_rtm_getroute(nlp, &attrs, hdr, npt);
+
+ return (error);
+}
+
+void
+rtnl_handle_route_event(uint32_t fibnum, const struct rib_cmd_info *rc)
+{
+ int family, nlm_flags = 0;
+
+ struct nl_writer nw;
+
+ family = rt_get_family(rc->rc_rt);
+
+ /* XXX: check if there are active listeners first */
+
+ /* TODO: consider passing PID/type/seq */
+ switch (rc->rc_cmd) {
+ case RTM_ADD:
+ nlm_flags = NLM_F_EXCL | NLM_F_CREATE;
+ break;
+ case RTM_CHANGE:
+ nlm_flags = NLM_F_REPLACE;
+ break;
+ case RTM_DELETE:
+ nlm_flags = 0;
+ break;
+ }
+ IF_DEBUG_LEVEL(LOG_DEBUG2) {
+ char rtbuf[NHOP_PRINT_BUFSIZE] __unused;
+ FIB_LOG(LOG_DEBUG2, fibnum, family,
+ "received event %s for %s / nlm_flags=%X",
+ rib_print_cmd(rc->rc_cmd),
+ rt_print_buf(rc->rc_rt, rtbuf, sizeof(rtbuf)),
+ nlm_flags);
+ }
+
+ struct nlmsghdr hdr = {
+ .nlmsg_flags = nlm_flags,
+ .nlmsg_type = get_rtmsg_type_from_rtsock(rc->rc_cmd),
+ };
+
+ struct route_nhop_data rnd = {
+ .rnd_nhop = rc_get_nhop(rc),
+ .rnd_weight = rc->rc_nh_weight,
+ };
+
+ uint32_t group_id = family_to_group(family);
+ if (!nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_ROUTE, group_id)) {
+ NL_LOG(LOG_DEBUG, "error allocating event buffer");
+ return;
+ }
+
+ dump_px(fibnum, &hdr, rc->rc_rt, &rnd, &nw);
+ nlmsg_flush(&nw);
+}
+
+static const struct rtnl_cmd_handler cmd_handlers[] = {
+ {
+ .cmd = NL_RTM_GETROUTE,
+ .name = "RTM_GETROUTE",
+ .cb = &rtnl_handle_getroute,
+ },
+ {
+ .cmd = NL_RTM_DELROUTE,
+ .name = "RTM_DELROUTE",
+ .cb = &rtnl_handle_delroute,
+ .priv = PRIV_NET_ROUTE,
+ },
+ {
+ .cmd = NL_RTM_NEWROUTE,
+ .name = "RTM_NEWROUTE",
+ .cb = &rtnl_handle_newroute,
+ .priv = PRIV_NET_ROUTE,
+ }
+};
+
+static const struct nlhdr_parser *all_parsers[] = {&mpath_parser, &metrics_parser, &rtm_parser};
+
+void
+rtnl_routes_init()
+{
+ NL_VERIFY_PARSERS(all_parsers);
+ rtnl_register_messages(cmd_handlers, NL_ARRAY_LEN(cmd_handlers));
+}
diff --git a/sys/netlink/route/route.h b/sys/netlink/route/route.h
new file mode 100644
index 000000000000..6e1ef6cbf0c6
--- /dev/null
+++ b/sys/netlink/route/route.h
@@ -0,0 +1,366 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Route-related (RTM_<NEW|DEL|GET>ROUTE) message header and attributes.
+ */
+
+#ifndef _NETLINK_ROUTE_ROUTE_H_
+#define _NETLINK_ROUTE_ROUTE_H_
+
+/* Base header for all of the relevant messages */
+struct rtmsg {
+ unsigned char rtm_family; /* address family */
+ unsigned char rtm_dst_len; /* Prefix length */
+ unsigned char rtm_src_len; /* Source prefix length (not used) */
+ unsigned char rtm_tos; /* Type of service (not used) */
+ unsigned char rtm_table; /* rtable id */
+ unsigned char rtm_protocol; /* Routing protocol id (RTPROT_) */
+ unsigned char rtm_scope; /* Route distance (RT_SCOPE_) */
+ unsigned char rtm_type; /* Route type (RTN_) */
+ unsigned rtm_flags; /* Route flags (RTM_F_) */
+};
+
+/*
+ * RFC 3549, 3.1.1, route type (rtm_type field).
+ */
+enum {
+ RTN_UNSPEC,
+ RTN_UNICAST, /* Unicast route */
+ RTN_LOCAL, /* Accept locally (not supported) */
+ RTN_BROADCAST, /* Accept locally as broadcast, send as broadcast */
+ RTN_ANYCAST, /* Accept locally as broadcast, but send as unicast */
+ RTN_MULTICAST, /* Multicast route */
+ RTN_BLACKHOLE, /* Drop traffic towards destination */
+ RTN_UNREACHABLE, /* Destination is unreachable */
+ RTN_PROHIBIT, /* Administratively prohibited */
+ RTN_THROW, /* Not in this table (not supported) */
+ RTN_NAT, /* Translate this address (not supported) */
+ RTN_XRESOLVE, /* Use external resolver (not supported) */
+ __RTN_MAX,
+};
+#define RTN_MAX (__RTN_MAX - 1)
+
+/*
+ * RFC 3549, 3.1.1, protocol (Identifies what/who added the route).
+ * Values larger than RTPROT_STATIC(4) are not interpreted by the
+ * kernel, they are just for user information.
+ */
+#define RTPROT_UNSPEC 0
+#define RTPROT_REDIRECT 1 /* Route installed by ICMP redirect */
+#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_GATED 8
+#define RTPROT_RA 9
+#define RTPROT_MRT 1
+#define RTPROT_ZEBRA 11
+#define RTPROT_BIRD 12
+#define RTPROT_DNROUTED 13
+#define RTPROT_XORP 14
+#define RTPROT_NTK 15
+#define RTPROT_DHCP 16
+#define RTPROT_MROUTED 17
+#define RTPROT_KEEPALIVED 18
+#define RTPROT_BABEL 42
+#define RTPROT_OPENR 99
+#define RTPROT_BGP 186
+#define RTPROT_ISIS 187
+#define RTPROT_OSPF 188
+#define RTPROT_RIP 189
+#define RTPROT_EIGRP 192
+
+/*
+ * RFC 3549 3.1.1 Route scope (valid distance to destination).
+ *
+ * The values between RT_SCOPE_UNIVERSE(0) and RT_SCOPE_SITE(200)
+ * are available to the user.
+ */
+enum rt_scope_t {
+ RT_SCOPE_UNIVERSE = 0,
+ /* User defined values */
+ RT_SCOPE_SITE = 200,
+ RT_SCOPE_LINK = 253,
+ RT_SCOPE_HOST = 254,
+ RT_SCOPE_NOWHERE = 255
+};
+
+/*
+ * RFC 3549 3.1.1 Route flags (rtm_flags).
+ * Is a composition of RTNH_F flags (0x1..0x40 range), RTM_F flags (below)
+ * and per-protocol (IPv4/IPv6) flags.
+ */
+#define RTM_F_NOTIFY 0x00000100 /* not supported */
+#define RTM_F_CLONED 0x00000200 /* not supported */
+#define RTM_F_EQUALIZE 0x00000400 /* not supported */
+#define RTM_F_PREFIX 0x00000800 /* not supported */
+#define RTM_F_LOOKUP_TABLE 0x00001000 /* not supported */
+#define RTM_F_FIB_MATCH 0x00002000 /* not supported */
+#define RTM_F_OFFLOAD 0x00004000 /* not supported */
+#define RTM_F_TRAP 0x00008000 /* not supported */
+#define RTM_F_OFFLOAD_FAILED 0x20000000 /* not supported */
+
+/* Compatibility handling helpers */
+#ifndef _KERNEL
+#define NL_RTM_HDRLEN ((int)sizeof(struct rtmsg))
+#define RTM_RTA(_rtm) ((struct rtattr *)((char *)(_rtm) + NL_RTM_HDRLEN))
+#define RTM_PAYLOAD(_hdr) NLMSG_PAYLOAD((_hdr), NL_RTM_HDRLEN)
+#endif
+
+/*
+ * Routing table identifiers.
+ * FreeBSD route table numbering starts from 0, where 0 is a valid default routing table.
+ * Indicating "all tables" via netlink can be done by not including RTA_TABLE attribute
+ * and keeping rtm_table=0 (compatibility) or setting RTA_TABLE value to RT_TABLE_UNSPEC.
+ */
+#define RT_TABLE_MAIN 0 /* RT_DEFAULT_FIB */
+#define RT_TABLE_UNSPEC 0xFFFFFFFF /* RT_ALL_FIBS */
+
+enum rtattr_type_t {
+ NL_RTA_UNSPEC,
+ NL_RTA_DST = 1, /* binary, IPv4/IPv6 destination */
+ NL_RTA_SRC = 2, /* binary, preferred source address */
+ NL_RTA_IIF = 3, /* not supported */
+ NL_RTA_OIF = 4, /* u32, transmit ifindex */
+ NL_RTA_GATEWAY = 5, /* binary: IPv4/IPv6 gateway */
+ NL_RTA_PRIORITY = 6, /* not supported */
+ NL_RTA_PREFSRC = 7, /* not supported */
+ NL_RTA_METRICS = 8, /* nested, list of NL_RTAX* attrs */
+ NL_RTA_MULTIPATH = 9, /* binary, array of struct rtnexthop */
+ NL_RTA_PROTOINFO = 10, /* not supported / deprecated */
+ NL_RTA_KNH_ID = 10, /* u32, FreeBSD specific, kernel nexthop index */
+ NL_RTA_FLOW = 11, /* not supported */
+ NL_RTA_CACHEINFO = 12, /* not supported */
+ NL_RTA_SESSION = 13, /* not supported / deprecated */
+ NL_RTA_MP_ALGO = 14, /* not supported / deprecated */
+ NL_RTA_RTFLAGS = 14, /* u32, FreeBSD specific, */
+ NL_RTA_TABLE = 15, /* u32, fibnum */
+ NL_RTA_MARK = 16, /* not supported */
+ NL_RTA_MFC_STATS = 17, /* not supported */
+ NL_RTA_VIA = 18, /* binary, struct rtvia */
+ NL_RTA_NEWDST = 19, /* not supported */
+ NL_RTA_PREF = 20, /* not supported */
+ NL_RTA_ENCAP_TYPE = 21, /* not supported */
+ NL_RTA_ENCAP = 22, /* not supported */
+ NL_RTA_EXPIRES = 23, /* u32, seconds till expiration */
+ NL_RTA_PAD = 24, /* not supported */
+ NL_RTA_UID = 25, /* not supported */
+ NL_RTA_TTL_PROPAGATE = 26, /* not supported */
+ NL_RTA_IP_PROTO = 27, /* not supported */
+ NL_RTA_SPORT = 28, /* not supported */
+ NL_RTA_DPORT = 29, /* not supported */
+ NL_RTA_NH_ID = 30, /* u32, nexthop/nexthop group index */
+ __RTA_MAX
+};
+#define NL_RTA_MAX (__RTA_MAX - 1)
+
+/*
+ * Attributes that can be used as filters:
+ *
+ */
+
+#ifndef _KERNEL
+/*
+ * RTA_* space has clashes with rtsock namespace.
+ * Use NL_RTA_ prefix in the kernel and map to
+ * RTA_ for userland.
+ */
+#define RTA_UNSPEC NL_RTA_UNSPEC
+#define RTA_DST NL_RTA_DST
+#define RTA_SRC NL_RTA_SRC
+#define RTA_IIF NL_RTA_IIF
+#define RTA_OIF NL_RTA_OIF
+#define RTA_GATEWAY NL_RTA_GATEWAY
+#define RTA_PRIORITY NL_RTA_PRIORITY
+#define RTA_PREFSRC NL_RTA_PREFSRC
+#define RTA_METRICS NL_RTA_METRICS
+#define RTA_MULTIPATH NL_RTA_MULTIPATH
+#define RTA_PROTOINFO NL_RTA_PROTOINFO
+#define RTA_KNH_ID NL_RTA_KNH_ID
+#define RTA_FLOW NL_RTA_FLOW
+#define RTA_CACHEINFO NL_RTA_CACHEINFO
+#define RTA_SESSION NL_RTA_SESSION
+#define RTA_MP_ALGO NL_RTA_MP_ALGO
+#define RTA_TABLE NL_RTA_TABLE
+#define RTA_MARK NL_RTA_MARK
+#define RTA_MFC_STATS NL_RTA_MFC_STATS
+#define RTA_VIA NL_RTA_VIA
+#define RTA_NEWDST NL_RTA_NEWDST
+#define RTA_PREF NL_RTA_PREF
+#define RTA_ENCAP_TYPE NL_RTA_ENCAP_TYPE
+#define RTA_ENCAP NL_RTA_ENCAP
+#define RTA_EXPIRES NL_RTA_EXPIRES
+#define RTA_PAD NL_RTA_PAD
+#define RTA_UID NL_RTA_UID
+#define RTA_TTL_PROPAGATE NL_RTA_TTL_PROPAGATE
+#define RTA_IP_PROTO NL_RTA_IP_PROTO
+#define RTA_SPORT NL_RTA_SPORT
+#define RTA_DPORT NL_RTA_DPORT
+#define RTA_NH_ID NL_RTA_NH_ID
+#define RTA_MAX NL_RTA_MAX
+#endif
+
+/* route attribute header */
+struct rtattr {
+ unsigned short rta_len;
+ unsigned short rta_type;
+};
+
+#define NL_RTA_ALIGN_SIZE NL_ITEM_ALIGN_SIZE
+#define NL_RTA_ALIGN NL_ITEM_ALIGN
+#define NL_RTA_HDRLEN ((int)sizeof(struct rtattr))
+#define NL_RTA_DATA_LEN(_rta) ((int)((_rta)->rta_len - NL_RTA_HDRLEN))
+#define NL_RTA_DATA(_rta) NL_ITEM_DATA(_rta, NL_RTA_HDRLEN)
+#define NL_RTA_DATA_CONST(_rta) NL_ITEM_DATA_CONST(_rta, NL_RTA_HDRLEN)
+
+/* Compatibility attribute handling helpers */
+#ifndef _KERNEL
+#define RTA_ALIGNTO NL_RTA_ALIGN_SIZE
+#define RTA_ALIGN(_len) NL_RTA_ALIGN(_len)
+#define _RTA_LEN(_rta) ((int)(_rta)->rta_len)
+#define _RTA_ALIGNED_LEN(_rta) RTA_ALIGN(_RTA_LEN(_rta))
+#define RTA_OK(_rta, _len) NL_ITEM_OK(_rta, _len, NL_RTA_HDRLEN, _RTA_LEN)
+#define RTA_NEXT(_rta, _len) NL_ITEM_ITER(_rta, _len, _RTA_ALIGNED_LEN)
+#define RTA_LENGTH(_len) (NL_RTA_HDRLEN + (_len))
+#define RTA_SPACE(_len) RTA_ALIGN(RTA_LENGTH(_len))
+#define RTA_DATA(_rta) NL_RTA_DATA(_rta)
+#define RTA_PAYLOAD(_rta) ((int)(_RTA_LEN(_rta) - NL_RTA_HDRLEN))
+#endif
+
+/* RTA attribute headers */
+
+/* RTA_VIA */
+struct rtvia {
+ sa_family_t rtvia_family;
+ uint8_t rtvia_addr[0];
+};
+
+/*
+ * RTA_METRICS is a nested attribute, consisting of a list of
+ * TLVs with types defined below.
+ */
+ enum {
+ NL_RTAX_UNSPEC,
+ NL_RTAX_LOCK = 1, /* not supported */
+ NL_RTAX_MTU = 2, /* desired path MTU */
+ NL_RTAX_WINDOW = 3, /* not supported */
+ NL_RTAX_RTT = 4, /* not supported */
+ NL_RTAX_RTTVAR = 5, /* not supported */
+ NL_RTAX_SSTHRESH = 6, /* not supported */
+ NL_RTAX_CWND = 7, /* not supported */
+ NL_RTAX_ADVMSS = 8, /* not supported */
+ NL_RTAX_REORDERING = 9, /* not supported */
+ NL_RTAX_HOPLIMIT = 10, /* not supported */
+ NL_RTAX_INITCWND = 11, /* not supporrted */
+ NL_RTAX_FEATURES = 12, /* not supported */
+ NL_RTAX_RTO_MIN = 13, /* not supported */
+ NL_RTAX_INITRWND = 14, /* not supported */
+ NL_RTAX_QUICKACK = 15, /* not supported */
+ NL_RTAX_CC_ALGO = 15, /* not supported */
+ NL_RTAX_FASTOPEN_NO_COOKIE = 16, /* not supported */
+ __NL_RTAX_MAX
+};
+#define NL_RTAX_MAX (__NL_RTAX_MAX - 1)
+
+#define RTAX_FEATURE_ECN (1 << 0)
+#define RTAX_FEATURE_SACK (1 << 1)
+#define RTAX_FEATURE_TIMESTAMP (1 << 2)
+#define RTAX_FEATURE_ALLFRAG (1 << 3)
+
+#define RTAX_FEATURE_MASK \
+ (RTAX_FEATURE_ECN | RTAX_FEATURE_SACK | RTAX_FEATURE_TIMESTAMP | \
+ RTAX_FEATURE_ALLFRAG)
+
+#ifndef _KERNEL
+
+/*
+ * RTAX_* space clashes with rtsock namespace.
+ * Use NL_RTAX_ prefix in the kernel and map to
+ * RTAX_ for userland.
+ */
+#define RTAX_UNSPEC NL_RTAX_UNSPEC
+#define RTAX_LOCK NL_RTAX_LOCK
+#define RTAX_MTU NL_RTAX_MTU
+#define RTAX_WINDOW NL_RTAX_WINDOW
+#define RTAX_RTT NL_RTAX_RTT
+#define RTAX_RTTVAR NL_RTAX_RTTVAR
+#define RTAX_SSTHRESH NL_RTAX_SSTHRESH
+#define RTAX_CWND NL_RTAX_CWND
+#define RTAX_ADVMSS NL_RTAX_ADVMSS
+#define RTAX_REORDERING NL_RTAX_REORDERING
+#define RTAX_HOPLIMIT NL_RTAX_HOPLIMIT
+#define RTAX_INITCWND NL_RTAX_INITCWND
+#define RTAX_FEATURES NL_RTAX_FEATURES
+#define RTAX_RTO_MIN NL_RTAX_RTO_MIN
+#define RTAX_INITRWND NL_RTAX_INITRWND
+#define RTAX_QUICKACK NL_RTAX_QUICKACK
+#define RTAX_CC_ALGO NL_RTAX_CC_ALGO
+#define RTAX_FASTOPEN_NO_COOKIE NL_RTAX_FASTOPEN_NO_COOKIE
+#endif
+
+/*
+ * RTA_MULTIPATH consists of an array of rtnexthop structures.
+ * Each rtnexthop structure contains RTA_GATEWAY or RTA_VIA
+ * attribute following the header.
+ */
+struct rtnexthop {
+ unsigned short rtnh_len;
+ unsigned char rtnh_flags;
+ unsigned char rtnh_hops; /* nexthop weight */
+ int rtnh_ifindex;
+};
+
+/* rtnh_flags */
+#define RTNH_F_DEAD 0x01 /* not supported */
+#define RTNH_F_PERVASIVE 0x02 /* not supported */
+#define RTNH_F_ONLINK 0x04 /* not supported */
+#define RTNH_F_OFFLOAD 0x08 /* not supported */
+#define RTNH_F_LINKDOWN 0x10 /* not supported */
+#define RTNH_F_UNRESOLVED 0x20 /* not supported */
+#define RTNH_F_TRAP 0x40 /* not supported */
+
+#define RTNH_COMPARE_MASK (RTNH_F_DEAD | RTNH_F_LINKDOWN | \
+ RTNH_F_OFFLOAD | RTNH_F_TRAP)
+
+/* Macros to handle hexthops */
+#define RTNH_ALIGNTO NL_ITEM_ALIGN_SIZE
+#define RTNH_ALIGN(_len) NL_ITEM_ALIGN(_len)
+#define RTNH_HDRLEN ((int)sizeof(struct rtnexthop))
+#define _RTNH_LEN(_nh) ((int)(_nh)->rtnh_len)
+#define _RTNH_ALIGNED_LEN(_nh) RTNH_ALIGN(_RTNH_LEN(_nh))
+#define RTNH_OK(_nh, _len) NL_ITEM_OK(_nh, _len, RTNH_HDRLEN, _RTNH_LEN)
+#define RTNH_NEXT(_nh) ((struct rtnexthop *)((char *)(_nh) + _RTNH_ALIGNED_LEN(_nh)))
+#define RTNH_LENGTH(_len) (RTNH_HDRLEN + (_len))
+#define RTNH_SPACE(_len) RTNH_ALIGN(RTNH_LENGTH(_len))
+#define RTNH_DATA(_nh) ((struct rtattr *)NL_ITEM_DATA(_nh, RTNH_HDRLEN))
+
+struct rtgenmsg {
+ unsigned char rtgen_family;
+};
+
+#endif
diff --git a/sys/netlink/route/route_var.h b/sys/netlink/route/route_var.h
new file mode 100644
index 000000000000..7a31a8c896a5
--- /dev/null
+++ b/sys/netlink/route/route_var.h
@@ -0,0 +1,101 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains definitions shared among NETLINK_ROUTE family
+ */
+#ifndef _NETLINK_ROUTE_ROUTE_VAR_H_
+#define _NETLINK_ROUTE_ROUTE_VAR_H_
+
+#include <sys/priv.h> /* values for priv_check */
+
+struct nlmsghdr;
+struct nlpcb;
+struct nl_pstate;
+
+typedef int rtnl_msg_cb_f(struct nlmsghdr *hdr, struct nlpcb *nlp,
+ struct nl_pstate *npt);
+
+struct rtnl_cmd_handler {
+ int cmd;
+ const char *name;
+ rtnl_msg_cb_f *cb;
+ int priv;
+ int flags;
+};
+
+#define RTNL_F_NOEPOCH 0x01
+
+bool rtnl_register_messages(const struct rtnl_cmd_handler *handlers, int count);
+
+/* route.c */
+struct rib_cmd_info;
+void rtnl_handle_route_event(uint32_t fibnum, const struct rib_cmd_info *rc);
+void rtnl_routes_init(void);
+
+/* neigh.c */
+void rtnl_neighs_init(void);
+void rtnl_neighs_destroy(void);
+
+/* iface.c */
+struct nl_parsed_link {
+ char *ifla_group;
+ char *ifla_ifname;
+ char *ifla_cloner;
+ struct nlattr *ifla_idata;
+ unsigned short ifi_type;
+ int ifi_index;
+ uint32_t ifla_mtu;
+};
+
+typedef int rtnl_iface_create_f(struct nl_parsed_link *lattrs, struct nlpcb *nlp,
+ struct nl_pstate *npt);
+typedef int rtnl_iface_modify_f(struct nl_parsed_link *lattrs, struct nlpcb *nlp,
+ struct nl_pstate *npt);
+
+struct nl_cloner {
+ const char *name;
+ rtnl_iface_create_f *create_f;
+ rtnl_iface_modify_f *modify_f;
+ SLIST_ENTRY(nl_cloner) next;
+};
+
+void rtnl_ifaces_init(void);
+void rtnl_ifaces_destroy(void);
+void rtnl_iface_add_cloner(struct nl_cloner *cloner);
+void rtnl_iface_del_cloner(struct nl_cloner *cloner);
+
+/* iface_drivers.c */
+void rtnl_iface_drivers_register(void);
+
+/* nexthop.c */
+void rtnl_nexthops_init(void);
+struct nhop_object *nl_find_nhop(uint32_t fibnum, int family,
+ uint32_t uidx, int nh_flags, int *perror);
+
+
+#endif