summaryrefslogtreecommitdiffstats
path: root/zebra/netconf_netlink.c
diff options
context:
space:
mode:
Diffstat (limited to 'zebra/netconf_netlink.c')
-rw-r--r--zebra/netconf_netlink.c227
1 files changed, 227 insertions, 0 deletions
diff --git a/zebra/netconf_netlink.c b/zebra/netconf_netlink.c
new file mode 100644
index 0000000..7352dfb
--- /dev/null
+++ b/zebra/netconf_netlink.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * netconf_netlink.c - netconf interaction with the kernel using
+ * netlink
+ * Copyright (C) 2021 Nvidia, Inc.
+ * Donald Sharp
+ */
+#include <zebra.h>
+
+#ifdef HAVE_NETLINK /* Netlink OSes only */
+
+#include <ns.h>
+
+#include "linux/netconf.h"
+
+#include "lib/lib_errors.h"
+#include "zebra/zebra_ns.h"
+#include "zebra/zebra_dplane.h"
+#include "zebra/kernel_netlink.h"
+#include "zebra/netconf_netlink.h"
+#include "zebra/debug.h"
+
+static struct rtattr *netconf_rta(struct netconfmsg *ncm)
+{
+ return (struct rtattr *)((char *)ncm +
+ NLMSG_ALIGN(sizeof(struct netconfmsg)));
+}
+
+/*
+ * Handle netconf update about a single interface: create dplane
+ * context, and enqueue for processing in the main zebra pthread.
+ */
+static int
+netlink_netconf_dplane_update(ns_id_t ns_id, afi_t afi, ifindex_t ifindex,
+ enum dplane_netconf_status_e mpls_on,
+ enum dplane_netconf_status_e mcast_on,
+ enum dplane_netconf_status_e linkdown_on)
+{
+ struct zebra_dplane_ctx *ctx;
+
+ ctx = dplane_ctx_alloc();
+ dplane_ctx_set_op(ctx, DPLANE_OP_INTF_NETCONFIG);
+ dplane_ctx_set_ns_id(ctx, ns_id);
+ dplane_ctx_set_afi(ctx, afi);
+ dplane_ctx_set_ifindex(ctx, ifindex);
+
+ dplane_ctx_set_netconf_mpls(ctx, mpls_on);
+ dplane_ctx_set_netconf_mcast(ctx, mcast_on);
+ dplane_ctx_set_netconf_linkdown(ctx, linkdown_on);
+
+ /* Enqueue ctx for main pthread to process */
+ dplane_provider_enqueue_to_zebra(ctx);
+
+ return 0;
+}
+
+/*
+ * Parse and process an incoming netlink netconf update.
+ */
+int netlink_netconf_change(struct nlmsghdr *h, ns_id_t ns_id, int startup)
+{
+ struct netconfmsg *ncm;
+ struct rtattr *tb[NETCONFA_MAX + 1] = {};
+ int len;
+ ifindex_t ifindex;
+ uint32_t ival;
+ afi_t afi;
+ enum dplane_netconf_status_e mpls_on = DPLANE_NETCONF_STATUS_UNKNOWN;
+ enum dplane_netconf_status_e mcast_on = DPLANE_NETCONF_STATUS_UNKNOWN;
+ enum dplane_netconf_status_e linkdown_on =
+ DPLANE_NETCONF_STATUS_UNKNOWN;
+
+ if (h->nlmsg_type != RTM_NEWNETCONF && h->nlmsg_type != RTM_DELNETCONF)
+ return 0;
+
+ len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct netconfmsg));
+ if (len < 0) {
+ zlog_err("%s: Message received from netlink is of a broken size: %d, min %zu",
+ __func__, h->nlmsg_len,
+ (size_t)NLMSG_LENGTH(sizeof(struct netconfmsg)));
+ return -1;
+ }
+
+ ncm = NLMSG_DATA(h);
+
+ /*
+ * FRR does not have an internal representation of afi_t for
+ * the MPLS Address Family that the kernel has. So let's
+ * just call it v4. This is ok because the kernel appears
+ * to do a good job of not sending data that is mixed/matched
+ * across families
+ */
+#ifdef AF_MPLS
+ if (ncm->ncm_family == AF_MPLS)
+ afi = AFI_IP;
+ else
+#endif /* AF_MPLS */
+ afi = family2afi(ncm->ncm_family);
+
+ netlink_parse_rtattr(tb, NETCONFA_MAX, netconf_rta(ncm), len);
+
+ if (!tb[NETCONFA_IFINDEX]) {
+ zlog_err("NETCONF message received from netlink without an ifindex");
+ return 0;
+ }
+
+ ifindex = *(ifindex_t *)RTA_DATA(tb[NETCONFA_IFINDEX]);
+
+ if (tb[NETCONFA_INPUT]) {
+ ival = *(uint32_t *)RTA_DATA(tb[NETCONFA_INPUT]);
+ if (ival != 0)
+ mpls_on = DPLANE_NETCONF_STATUS_ENABLED;
+ else
+ mpls_on = DPLANE_NETCONF_STATUS_DISABLED;
+ }
+
+ if (tb[NETCONFA_MC_FORWARDING]) {
+ ival = *(uint32_t *)RTA_DATA(tb[NETCONFA_MC_FORWARDING]);
+ if (ival != 0)
+ mcast_on = DPLANE_NETCONF_STATUS_ENABLED;
+ else
+ mcast_on = DPLANE_NETCONF_STATUS_DISABLED;
+ }
+
+ if (tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN]) {
+ ival = *(uint32_t *)RTA_DATA(
+ tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN]);
+ if (ival != 0)
+ linkdown_on = DPLANE_NETCONF_STATUS_ENABLED;
+ else
+ linkdown_on = DPLANE_NETCONF_STATUS_DISABLED;
+ }
+
+ if (IS_ZEBRA_DEBUG_KERNEL)
+ zlog_debug(
+ "%s: interface %u is mpls on: %d multicast on: %d linkdown: %d",
+ __func__, ifindex, mpls_on, mcast_on, linkdown_on);
+
+ /* Create a dplane context and pass it along for processing */
+ netlink_netconf_dplane_update(ns_id, afi, ifindex, mpls_on, mcast_on,
+ linkdown_on);
+
+ return 0;
+}
+
+/*
+ * Request info from the host OS. This only sends the request; any replies
+ * are processed asynchronously.
+ */
+int netlink_request_netconf(int sockfd)
+{
+ struct nlsock *nls;
+ struct {
+ struct nlmsghdr n;
+ struct netconfmsg ncm;
+ char buf[1024];
+ } req = {};
+
+ nls = kernel_netlink_nlsock_lookup(sockfd);
+
+ if (IS_ZEBRA_DEBUG_KERNEL)
+ zlog_debug("%s: nlsock %s", __func__, nls ? nls->name : "NULL");
+
+ if (nls == NULL)
+ return -1;
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct netconfmsg));
+ req.n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
+ req.n.nlmsg_type = RTM_GETNETCONF;
+ req.ncm.ncm_family = AF_UNSPEC;
+
+ return netlink_request(nls, &req);
+}
+
+extern struct zebra_privs_t zserv_privs;
+/*
+ * Currently netconf has no ability to set from netlink.
+ * So we've received a request to do this work in the data plane.
+ * as such we need to set the value via the /proc system
+ */
+enum netlink_msg_status netlink_put_intf_netconfig(struct nl_batch *bth,
+ struct zebra_dplane_ctx *ctx)
+{
+ const char *ifname = dplane_ctx_get_ifname(ctx);
+ enum dplane_netconf_status_e mpls_on = dplane_ctx_get_netconf_mpls(ctx);
+ char set[64];
+ char mpls_proc[PATH_MAX];
+ int fd, ret = FRR_NETLINK_ERROR;
+
+ snprintf(mpls_proc, sizeof(mpls_proc),
+ "/proc/sys/net/mpls/conf/%s/input", ifname);
+
+ if (mpls_on == DPLANE_NETCONF_STATUS_ENABLED)
+ snprintf(set, sizeof(set), "1\n");
+ else if (mpls_on == DPLANE_NETCONF_STATUS_DISABLED)
+ snprintf(set, sizeof(set), "0\n");
+ else {
+ flog_err_sys(
+ EC_LIB_DEVELOPMENT,
+ "%s: Expected interface %s to be set to ENABLED or DISABLED was %d",
+ __func__, ifname, mpls_on);
+ return ret;
+ }
+
+ frr_with_privs (&zserv_privs) {
+ fd = open(mpls_proc, O_WRONLY);
+ if (fd < 0) {
+ flog_err_sys(
+ EC_LIB_SOCKET,
+ "%s: Unable to open %s for writing: %s(%d)",
+ __func__, mpls_proc, safe_strerror(errno),
+ errno);
+ return ret;
+ }
+ if (write(fd, set, 2) == 2)
+ ret = FRR_NETLINK_SUCCESS;
+ else
+ flog_err_sys(EC_LIB_SOCKET,
+ "%s: Unsuccessful write to %s: %s(%d)",
+ __func__, mpls_proc, safe_strerror(errno),
+ errno);
+ close(fd);
+ }
+ return ret;
+}
+
+#endif /* HAVE_NETLINK */