diff options
Diffstat (limited to 'zebra/netconf_netlink.c')
-rw-r--r-- | zebra/netconf_netlink.c | 227 |
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 */ |