summaryrefslogtreecommitdiffstats
path: root/src/VBox/NetworkServices/NAT/rtmon_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/NetworkServices/NAT/rtmon_linux.c')
-rw-r--r--src/VBox/NetworkServices/NAT/rtmon_linux.c259
1 files changed, 259 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/NAT/rtmon_linux.c b/src/VBox/NetworkServices/NAT/rtmon_linux.c
new file mode 100644
index 00000000..1ef37e34
--- /dev/null
+++ b/src/VBox/NetworkServices/NAT/rtmon_linux.c
@@ -0,0 +1,259 @@
+/* $Id: rtmon_linux.c $ */
+/** @file
+ * NAT Network - IPv6 default route monitor for Linux netlink.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#define LOG_GROUP LOG_GROUP_NAT_SERVICE
+
+#include "proxy.h"
+
+#include <sys/types.h> /* must come before linux/netlink */
+#include <sys/socket.h>
+
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+
+static int rtmon_check_defaults(const void *buf, size_t len);
+
+
+/**
+ * Read IPv6 routing table - Linux rtnetlink version.
+ *
+ * XXX: TODO: To avoid re-reading the table we should subscribe to
+ * updates by binding a monitoring NETLINK_ROUTE socket to
+ * sockaddr_nl::nl_groups = RTMGRP_IPV6_ROUTE.
+ *
+ * But that will provide updates only. Documentation is scarce, but
+ * from what I've seen it seems that to get accurate routing info the
+ * monitoring socket needs to be created first, then full routing
+ * table requested (easier to do via spearate socket), then monitoring
+ * socket polled for input. The first update(s) of the monitoring
+ * socket may happen before full table is returned, so we can't just
+ * count the defaults, we need to keep track of their { oif, gw } to
+ * correctly ignore updates that are reported via monitoring socket,
+ * but that are already reflected in the full routing table returned
+ * in response to our request.
+ */
+int
+rtmon_get_defaults(void)
+{
+ int rtsock;
+ ssize_t nsent, ssize;
+ int ndefrts;
+
+ char *buf = NULL;
+ size_t bufsize;
+
+ struct {
+ struct nlmsghdr nh;
+ struct rtmsg rtm;
+ char attrbuf[512];
+ } rtreq;
+
+ memset(&rtreq, 0, sizeof(rtreq));
+ rtreq.nh.nlmsg_type = RTM_GETROUTE;
+ rtreq.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ rtreq.rtm.rtm_family = AF_INET6;
+ rtreq.rtm.rtm_table = RT_TABLE_MAIN;
+ rtreq.rtm.rtm_protocol = RTPROT_UNSPEC;
+
+ rtreq.nh.nlmsg_len = NLMSG_SPACE(sizeof(rtreq.rtm));
+
+ bufsize = 1024;
+ ssize = bufsize;
+ for (;;) {
+ char *newbuf;
+ int recverr;
+
+ newbuf = (char *)realloc(buf, ssize);
+ if (newbuf == NULL) {
+ DPRINTF0(("rtmon: failed to %sallocate buffer\n",
+ buf == NULL ? "" : "re"));
+ free(buf);
+ return -1;
+ }
+
+ buf = newbuf;
+ bufsize = ssize;
+
+ /* it's easier to reopen than to flush */
+ rtsock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (rtsock < 0) {
+ DPRINTF0(("rtmon: failed to create netlink socket: %s", strerror(errno)));
+ free(buf);
+ return -1;
+ }
+
+ nsent = send(rtsock, &rtreq, rtreq.nh.nlmsg_len, 0);
+ if (nsent < 0) {
+ DPRINTF0(("rtmon: RTM_GETROUTE failed: %s", strerror(errno)));
+ close (rtsock);
+ free(buf);
+ return -1;
+ }
+
+ ssize = recv(rtsock, buf, bufsize, MSG_TRUNC);
+ recverr = errno;
+ close (rtsock);
+
+ if (ssize < 0) {
+ DPRINTF(("rtmon: failed to read RTM_GETROUTE response: %s",
+ strerror(recverr)));
+ free(buf);
+ return -1;
+ }
+
+ if ((size_t)ssize <= bufsize) {
+ DPRINTF2(("rtmon: RTM_GETROUTE: %lu bytes\n",
+ (unsigned long)ssize));
+ break;
+ }
+
+ DPRINTF2(("rtmon: RTM_GETROUTE: truncated %lu to %lu bytes, retrying\n",
+ (unsigned long)ssize, (unsigned long)bufsize));
+ /* try again with larger buffer */
+ }
+
+ ndefrts = rtmon_check_defaults(buf, (size_t)ssize);
+ free(buf);
+
+ if (ndefrts == 0) {
+ DPRINTF(("rtmon: no IPv6 default routes found\n"));
+ }
+ else {
+ DPRINTF(("rtmon: %d IPv6 default route%s found\n",
+ ndefrts,
+ ndefrts == 1 || ndefrts == -1 ? "" : "s"));
+ }
+
+ return ndefrts;
+}
+
+
+/**
+ * Scan netlink message in the buffer for IPv6 default route changes.
+ */
+static int
+rtmon_check_defaults(const void *buf, size_t len)
+{
+ struct nlmsghdr *nh;
+ int dfltdiff = 0;
+
+ for (nh = (struct nlmsghdr *)buf;
+ NLMSG_OK(nh, len);
+ nh = NLMSG_NEXT(nh, len))
+ {
+ struct rtmsg *rtm;
+ struct rtattr *rta;
+ int attrlen;
+ int delta = 0;
+ const void *gwbuf;
+ size_t gwlen;
+ int oif;
+
+ DPRINTF2(("nlmsg seq %d type %d flags 0x%x\n",
+ nh->nlmsg_seq, nh->nlmsg_type, nh->nlmsg_flags));
+
+ if (nh->nlmsg_type == NLMSG_DONE) {
+ break;
+ }
+
+ if (nh->nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr *ne = (struct nlmsgerr *)NLMSG_DATA(nh);
+ DPRINTF2(("> error %d\n", ne->error));
+ LWIP_UNUSED_ARG(ne);
+ break;
+ }
+
+ if (nh->nlmsg_type < RTM_BASE || RTM_MAX <= nh->nlmsg_type) {
+ /* shouldn't happen */
+ DPRINTF2(("> not an RTM message!\n"));
+ continue;
+ }
+
+
+ rtm = (struct rtmsg *)NLMSG_DATA(nh);
+ attrlen = RTM_PAYLOAD(nh);
+
+ if (nh->nlmsg_type == RTM_NEWROUTE) {
+ delta = +1;
+ }
+ else if (nh->nlmsg_type == RTM_DELROUTE) {
+ delta = -1;
+ }
+ else {
+ /* shouldn't happen */
+ continue;
+ }
+
+ /*
+ * Is this an IPv6 default route in the main table? (Local
+ * table always has ::/0 reject route, hence the last check).
+ */
+ if (rtm->rtm_family == AF_INET6 /* should always be true */
+ && rtm->rtm_dst_len == 0
+ && rtm->rtm_table == RT_TABLE_MAIN)
+ {
+ dfltdiff += delta;
+ }
+ else {
+ /* some other route change */
+ continue;
+ }
+
+
+ gwbuf = NULL;
+ gwlen = 0;
+ oif = -1;
+
+ for (rta = RTM_RTA(rtm);
+ RTA_OK(rta, attrlen);
+ rta = RTA_NEXT(rta, attrlen))
+ {
+ if (rta->rta_type == RTA_GATEWAY) {
+ gwbuf = RTA_DATA(rta);
+ gwlen = RTA_PAYLOAD(rta);
+ }
+ else if (rta->rta_type == RTA_OIF) {
+ /* assert RTA_PAYLOAD(rta) == 4 */
+ memcpy(&oif, RTA_DATA(rta), sizeof(oif));
+ }
+ }
+
+ /* XXX: TODO: note that { oif, gw } was added/removed */
+ LWIP_UNUSED_ARG(gwbuf);
+ LWIP_UNUSED_ARG(gwlen);
+ LWIP_UNUSED_ARG(oif);
+ }
+
+ return dfltdiff;
+}