summaryrefslogtreecommitdiffstats
path: root/zebra/zebra_netns_id.c
diff options
context:
space:
mode:
Diffstat (limited to 'zebra/zebra_netns_id.c')
-rw-r--r--zebra/zebra_netns_id.c363
1 files changed, 363 insertions, 0 deletions
diff --git a/zebra/zebra_netns_id.c b/zebra/zebra_netns_id.c
new file mode 100644
index 0000000..73d585c
--- /dev/null
+++ b/zebra/zebra_netns_id.c
@@ -0,0 +1,363 @@
+/* zebra NETNS ID handling routines
+ * those routines are implemented locally to avoid having external dependencies.
+ * Copyright (C) 2018 6WIND
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "ns.h"
+#include "vrf.h"
+#include "log.h"
+#include "lib_errors.h"
+
+#include "zebra/rib.h"
+#include "zebra/zebra_dplane.h"
+#if defined(HAVE_NETLINK)
+
+#include <linux/net_namespace.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include "zebra_ns.h"
+#include "kernel_netlink.h"
+#endif /* defined(HAVE_NETLINK) */
+
+#include "zebra/zebra_netns_id.h"
+#include "zebra/zebra_errors.h"
+
+/* in case NEWNSID not available, the NSID will be locally obtained
+ */
+#define NS_BASE_NSID 0
+
+#if defined(HAVE_NETLINK)
+
+#define NETLINK_SOCKET_BUFFER_SIZE 512
+#define NETLINK_ALIGNTO 4
+#define NETLINK_ALIGN(len) \
+ (((len) + NETLINK_ALIGNTO - 1) & ~(NETLINK_ALIGNTO - 1))
+#define NETLINK_NLATTR_LEN(_a, _b) (unsigned int)((char *)_a - (char *)_b)
+
+#endif /* defined(HAVE_NETLINK) */
+
+static ns_id_t zebra_ns_id_get_fallback(const char *netnspath)
+{
+ static int zebra_ns_id_local;
+
+ return zebra_ns_id_local++;
+}
+
+#if defined(HAVE_NETLINK)
+
+static struct nlmsghdr *initiate_nlh(char *buf, unsigned int *seq, int type)
+{
+ struct nlmsghdr *nlh;
+
+ nlh = (struct nlmsghdr *)buf;
+ nlh->nlmsg_len = NETLINK_ALIGN(sizeof(struct nlmsghdr));
+
+ nlh->nlmsg_type = type;
+ nlh->nlmsg_flags = NLM_F_REQUEST;
+ if (type == RTM_NEWNSID)
+ nlh->nlmsg_flags |= NLM_F_ACK;
+ nlh->nlmsg_seq = *seq = time(NULL);
+ return nlh;
+}
+
+static int send_receive(int sock, struct nlmsghdr *nlh, unsigned int seq,
+ char *buf)
+{
+ int ret;
+ static const struct sockaddr_nl snl = {.nl_family = AF_NETLINK};
+
+ ret = sendto(sock, (const void *)nlh, (size_t)nlh->nlmsg_len, 0,
+ (struct sockaddr *)&snl, (socklen_t)sizeof(snl));
+ if (ret < 0) {
+ flog_err_sys(EC_LIB_SOCKET, "netlink( %u) sendmsg() error: %s",
+ sock, safe_strerror(errno));
+ return -1;
+ }
+
+ /* reception */
+ struct sockaddr_nl addr;
+ struct iovec iov = {
+ .iov_base = buf, .iov_len = NETLINK_SOCKET_BUFFER_SIZE,
+ };
+ struct msghdr msg = {
+ .msg_name = &addr,
+ .msg_namelen = sizeof(struct sockaddr_nl),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = NULL,
+ .msg_controllen = 0,
+ .msg_flags = 0,
+ };
+ ret = recvmsg(sock, &msg, 0);
+ if (ret < 0) {
+ flog_err_sys(EC_LIB_SOCKET,
+ "netlink recvmsg: error %d (errno %u)", ret,
+ errno);
+ return -1;
+ }
+ if (msg.msg_flags & MSG_TRUNC) {
+ flog_err(EC_ZEBRA_NETLINK_LENGTH_ERROR,
+ "netlink recvmsg : error message truncated");
+ return -1;
+ }
+ /* nlh already points to buf */
+ if (nlh->nlmsg_seq != seq) {
+ flog_err(
+ EC_ZEBRA_NETLINK_BAD_SEQUENCE,
+ "netlink recvmsg: bad sequence number %x (expected %x)",
+ seq, nlh->nlmsg_seq);
+ return -1;
+ }
+ return ret;
+}
+
+/* extract on a valid nlmsg the nsid
+ * valid nlmsghdr - not a nlmsgerr
+ */
+static ns_id_t extract_nsid(struct nlmsghdr *nlh, char *buf)
+{
+ ns_id_t ns_id = NS_UNKNOWN;
+ int offset = NETLINK_ALIGN(sizeof(struct nlmsghdr))
+ + NETLINK_ALIGN(sizeof(struct rtgenmsg));
+ void *tail = (void *)((char *)nlh + NETLINK_ALIGN(nlh->nlmsg_len));
+ struct nlattr *attr;
+
+ for (attr = (struct nlattr *)(buf + offset);
+ NETLINK_NLATTR_LEN(tail, attr) >= sizeof(struct nlattr)
+ && attr->nla_len >= sizeof(struct nlattr)
+ && attr->nla_len <= NETLINK_NLATTR_LEN(tail, attr);
+ attr += NETLINK_ALIGN(attr->nla_len)) {
+ if ((attr->nla_type & NLA_TYPE_MASK) == NETNSA_NSID) {
+ uint32_t *ptr = (uint32_t *)(attr);
+
+ ns_id = ptr[1];
+ break;
+ }
+ }
+ return ns_id;
+}
+
+/* fd_param = -1 is ignored.
+ * netnspath set to null is ignored.
+ * one of the 2 params is mandatory. netnspath is looked in priority
+ */
+ns_id_t zebra_ns_id_get(const char *netnspath, int fd_param)
+{
+ int ns_id = -1;
+ struct sockaddr_nl snl;
+ int fd = -1, sock, ret;
+ unsigned int seq;
+ ns_id_t return_nsid = NS_UNKNOWN;
+
+ /* netns path check */
+ if (!netnspath && fd_param == -1)
+ return NS_UNKNOWN;
+ if (netnspath) {
+ fd = open(netnspath, O_RDONLY);
+ if (fd == -1)
+ return NS_UNKNOWN;
+ } else if (fd_param != -1)
+ fd = fd_param;
+ /* netlink socket */
+ sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (sock < 0) {
+ flog_err_sys(EC_LIB_SOCKET, "netlink( %u) socket() error: %s",
+ sock, safe_strerror(errno));
+ if (netnspath)
+ close(fd);
+ return NS_UNKNOWN;
+ }
+ memset(&snl, 0, sizeof(snl));
+ snl.nl_family = AF_NETLINK;
+ snl.nl_groups = RTNLGRP_NSID;
+ snl.nl_pid = 0; /* AUTO PID */
+ ret = bind(sock, (struct sockaddr *)&snl, sizeof(snl));
+ if (ret < 0) {
+ flog_err_sys(EC_LIB_SOCKET,
+ "netlink( %u) socket() bind error: %s", sock,
+ safe_strerror(errno));
+ close(sock);
+ if (netnspath)
+ close(fd);
+ return NS_UNKNOWN;
+ }
+
+ /* message to send to netlink,and response : NEWNSID */
+ char buf[NETLINK_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ struct rtgenmsg *rt;
+ int len;
+
+ memset(buf, 0, NETLINK_SOCKET_BUFFER_SIZE);
+ nlh = initiate_nlh(buf, &seq, RTM_NEWNSID);
+ rt = (struct rtgenmsg *)(buf + nlh->nlmsg_len);
+ nlh->nlmsg_len += NETLINK_ALIGN(sizeof(struct rtgenmsg));
+ rt->rtgen_family = AF_UNSPEC;
+
+ nl_attr_put32(nlh, NETLINK_SOCKET_BUFFER_SIZE, NETNSA_FD, fd);
+ nl_attr_put32(nlh, NETLINK_SOCKET_BUFFER_SIZE, NETNSA_NSID, ns_id);
+
+ ret = send_receive(sock, nlh, seq, buf);
+ if (ret < 0) {
+ close(sock);
+ if (netnspath)
+ close(fd);
+ return NS_UNKNOWN;
+ }
+ nlh = (struct nlmsghdr *)buf;
+
+ /* message to analyse : NEWNSID response */
+ ret = 0;
+ if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) {
+ return_nsid = extract_nsid(nlh, buf);
+ } else {
+ if (nlh->nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr *err =
+ (struct nlmsgerr
+ *)((char *)nlh
+ + NETLINK_ALIGN(
+ sizeof(struct nlmsghdr)));
+
+ ret = -1;
+ if (err->error < 0)
+ errno = -err->error;
+ else
+ errno = err->error;
+ if (errno == 0) {
+ /* request NEWNSID was successfull
+ * return EEXIST error to get GETNSID
+ */
+ errno = EEXIST;
+ }
+ } else {
+ /* other errors ignored
+ * attempt to get nsid
+ */
+ ret = -1;
+ errno = EEXIST;
+ }
+ }
+
+ if (errno != EEXIST && ret != 0) {
+ flog_err(EC_LIB_SOCKET,
+ "netlink( %u) recvfrom() error 2 when reading: %s", fd,
+ safe_strerror(errno));
+ close(sock);
+ if (netnspath)
+ close(fd);
+ if (errno == ENOTSUP) {
+ zlog_debug("NEWNSID locally generated");
+ return zebra_ns_id_get_fallback(netnspath);
+ }
+ return NS_UNKNOWN;
+ }
+ /* message to send to netlink : GETNSID */
+ memset(buf, 0, NETLINK_SOCKET_BUFFER_SIZE);
+ nlh = initiate_nlh(buf, &seq, RTM_GETNSID);
+ rt = (struct rtgenmsg *)(buf + nlh->nlmsg_len);
+ nlh->nlmsg_len += NETLINK_ALIGN(sizeof(struct rtgenmsg));
+ rt->rtgen_family = AF_UNSPEC;
+
+ nl_attr_put32(nlh, NETLINK_SOCKET_BUFFER_SIZE, NETNSA_FD, fd);
+ nl_attr_put32(nlh, NETLINK_SOCKET_BUFFER_SIZE, NETNSA_NSID, ns_id);
+
+ ret = send_receive(sock, nlh, seq, buf);
+ if (ret < 0) {
+ close(sock);
+ if (netnspath)
+ close(fd);
+ return NS_UNKNOWN;
+ }
+ nlh = (struct nlmsghdr *)buf;
+ len = ret;
+ ret = 0;
+ do {
+ if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) {
+ return_nsid = extract_nsid(nlh, buf);
+ if (return_nsid != NS_UNKNOWN)
+ break;
+ } else if (nlh->nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr *err =
+ (struct nlmsgerr *)((char *)nlh +
+ NETLINK_ALIGN(sizeof(
+ struct nlmsghdr)));
+ if (err->error < 0)
+ errno = -err->error;
+ else
+ errno = err->error;
+ break;
+ }
+ len = len - NETLINK_ALIGN(nlh->nlmsg_len);
+ nlh = (struct nlmsghdr *)((char *)nlh +
+ NETLINK_ALIGN(nlh->nlmsg_len));
+ } while (len != 0 && ret == 0);
+
+ if (netnspath)
+ close(fd);
+ close(sock);
+ return return_nsid;
+}
+
+#else
+ns_id_t zebra_ns_id_get(const char *netnspath, int fd __attribute__ ((unused)))
+{
+ return zebra_ns_id_get_fallback(netnspath);
+}
+
+#endif /* ! defined(HAVE_NETLINK) */
+
+#ifdef HAVE_NETNS
+static void zebra_ns_create_netns_directory(void)
+{
+ /* check that /var/run/netns is created */
+ /* S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH */
+ if (mkdir(NS_RUN_DIR, 0755)) {
+ if (errno != EEXIST) {
+ flog_warn(EC_ZEBRA_NAMESPACE_DIR_INACCESSIBLE,
+ "NS check: failed to access %s", NS_RUN_DIR);
+ return;
+ }
+ }
+}
+#endif
+
+ns_id_t zebra_ns_id_get_default(void)
+{
+#ifdef HAVE_NETNS
+ int fd;
+#endif /* !HAVE_NETNS */
+
+#ifdef HAVE_NETNS
+ if (vrf_is_backend_netns())
+ zebra_ns_create_netns_directory();
+ fd = open(NS_DEFAULT_NAME, O_RDONLY);
+
+ if (fd == -1)
+ return NS_DEFAULT;
+ if (!vrf_is_backend_netns()) {
+ close(fd);
+ return NS_DEFAULT;
+ }
+ close(fd);
+ return zebra_ns_id_get((char *)NS_DEFAULT_NAME, -1);
+#else /* HAVE_NETNS */
+ return NS_DEFAULT;
+#endif /* !HAVE_NETNS */
+}