VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/NAT/rtmon_linux.c

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 7.1 KB
Line 
1/* $Id: rtmon_linux.c 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * NAT Network - IPv6 default route monitor for Linux netlink.
4 */
5
6/*
7 * Copyright (C) 2013-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29#define LOG_GROUP LOG_GROUP_NAT_SERVICE
30
31#include "proxy.h"
32
33#include <sys/types.h> /* must come before linux/netlink */
34#include <sys/socket.h>
35
36#include <asm/types.h>
37#include <linux/netlink.h>
38#include <linux/rtnetlink.h>
39
40#include <errno.h>
41#include <string.h>
42#include <unistd.h>
43
44
45static int rtmon_check_defaults(const void *buf, size_t len);
46
47
48/**
49 * Read IPv6 routing table - Linux rtnetlink version.
50 *
51 * XXX: TODO: To avoid re-reading the table we should subscribe to
52 * updates by binding a monitoring NETLINK_ROUTE socket to
53 * sockaddr_nl::nl_groups = RTMGRP_IPV6_ROUTE.
54 *
55 * But that will provide updates only. Documentation is scarce, but
56 * from what I've seen it seems that to get accurate routing info the
57 * monitoring socket needs to be created first, then full routing
58 * table requested (easier to do via spearate socket), then monitoring
59 * socket polled for input. The first update(s) of the monitoring
60 * socket may happen before full table is returned, so we can't just
61 * count the defaults, we need to keep track of their { oif, gw } to
62 * correctly ignore updates that are reported via monitoring socket,
63 * but that are already reflected in the full routing table returned
64 * in response to our request.
65 */
66int
67rtmon_get_defaults(void)
68{
69 int rtsock;
70 ssize_t nsent, ssize;
71 int ndefrts;
72
73 char *buf = NULL;
74 size_t bufsize;
75
76 struct {
77 struct nlmsghdr nh;
78 struct rtmsg rtm;
79 char attrbuf[512];
80 } rtreq;
81
82 memset(&rtreq, 0, sizeof(rtreq));
83 rtreq.nh.nlmsg_type = RTM_GETROUTE;
84 rtreq.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
85 rtreq.rtm.rtm_family = AF_INET6;
86 rtreq.rtm.rtm_table = RT_TABLE_MAIN;
87 rtreq.rtm.rtm_protocol = RTPROT_UNSPEC;
88
89 rtreq.nh.nlmsg_len = NLMSG_SPACE(sizeof(rtreq.rtm));
90
91 bufsize = 1024;
92 ssize = bufsize;
93 for (;;) {
94 char *newbuf;
95 int recverr;
96
97 newbuf = (char *)realloc(buf, ssize);
98 if (newbuf == NULL) {
99 DPRINTF0(("rtmon: failed to %sallocate buffer\n",
100 buf == NULL ? "" : "re"));
101 free(buf);
102 return -1;
103 }
104
105 buf = newbuf;
106 bufsize = ssize;
107
108 /* it's easier to reopen than to flush */
109 rtsock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
110 if (rtsock < 0) {
111 DPRINTF0(("rtmon: failed to create netlink socket: %s", strerror(errno)));
112 free(buf);
113 return -1;
114 }
115
116 nsent = send(rtsock, &rtreq, rtreq.nh.nlmsg_len, 0);
117 if (nsent < 0) {
118 DPRINTF0(("rtmon: RTM_GETROUTE failed: %s", strerror(errno)));
119 close (rtsock);
120 free(buf);
121 return -1;
122 }
123
124 ssize = recv(rtsock, buf, bufsize, MSG_TRUNC);
125 recverr = errno;
126 close (rtsock);
127
128 if (ssize < 0) {
129 DPRINTF(("rtmon: failed to read RTM_GETROUTE response: %s",
130 strerror(recverr)));
131 free(buf);
132 return -1;
133 }
134
135 if ((size_t)ssize <= bufsize) {
136 DPRINTF2(("rtmon: RTM_GETROUTE: %lu bytes\n",
137 (unsigned long)ssize));
138 break;
139 }
140
141 DPRINTF2(("rtmon: RTM_GETROUTE: truncated %lu to %lu bytes, retrying\n",
142 (unsigned long)ssize, (unsigned long)bufsize));
143 /* try again with larger buffer */
144 }
145
146 ndefrts = rtmon_check_defaults(buf, (size_t)ssize);
147 free(buf);
148
149 if (ndefrts == 0) {
150 DPRINTF(("rtmon: no IPv6 default routes found\n"));
151 }
152 else {
153 DPRINTF(("rtmon: %d IPv6 default route%s found\n",
154 ndefrts,
155 ndefrts == 1 || ndefrts == -1 ? "" : "s"));
156 }
157
158 return ndefrts;
159}
160
161
162/**
163 * Scan netlink message in the buffer for IPv6 default route changes.
164 */
165static int
166rtmon_check_defaults(const void *buf, size_t len)
167{
168 struct nlmsghdr *nh;
169 int dfltdiff = 0;
170
171 for (nh = (struct nlmsghdr *)buf;
172 NLMSG_OK(nh, len);
173 nh = NLMSG_NEXT(nh, len))
174 {
175 struct rtmsg *rtm;
176 struct rtattr *rta;
177 int attrlen;
178 int delta = 0;
179 const void *gwbuf;
180 size_t gwlen;
181 int oif;
182
183 DPRINTF2(("nlmsg seq %d type %d flags 0x%x\n",
184 nh->nlmsg_seq, nh->nlmsg_type, nh->nlmsg_flags));
185
186 if (nh->nlmsg_type == NLMSG_DONE) {
187 break;
188 }
189
190 if (nh->nlmsg_type == NLMSG_ERROR) {
191 struct nlmsgerr *ne = (struct nlmsgerr *)NLMSG_DATA(nh);
192 DPRINTF2(("> error %d\n", ne->error));
193 LWIP_UNUSED_ARG(ne);
194 break;
195 }
196
197 if (nh->nlmsg_type < RTM_BASE || RTM_MAX <= nh->nlmsg_type) {
198 /* shouldn't happen */
199 DPRINTF2(("> not an RTM message!\n"));
200 continue;
201 }
202
203
204 rtm = (struct rtmsg *)NLMSG_DATA(nh);
205 attrlen = RTM_PAYLOAD(nh);
206
207 if (nh->nlmsg_type == RTM_NEWROUTE) {
208 delta = +1;
209 }
210 else if (nh->nlmsg_type == RTM_DELROUTE) {
211 delta = -1;
212 }
213 else {
214 /* shouldn't happen */
215 continue;
216 }
217
218 /*
219 * Is this an IPv6 default route in the main table? (Local
220 * table always has ::/0 reject route, hence the last check).
221 */
222 if (rtm->rtm_family == AF_INET6 /* should always be true */
223 && rtm->rtm_dst_len == 0
224 && rtm->rtm_table == RT_TABLE_MAIN)
225 {
226 dfltdiff += delta;
227 }
228 else {
229 /* some other route change */
230 continue;
231 }
232
233
234 gwbuf = NULL;
235 gwlen = 0;
236 oif = -1;
237
238 for (rta = RTM_RTA(rtm);
239 RTA_OK(rta, attrlen);
240 rta = RTA_NEXT(rta, attrlen))
241 {
242 if (rta->rta_type == RTA_GATEWAY) {
243 gwbuf = RTA_DATA(rta);
244 gwlen = RTA_PAYLOAD(rta);
245 }
246 else if (rta->rta_type == RTA_OIF) {
247 /* assert RTA_PAYLOAD(rta) == 4 */
248 memcpy(&oif, RTA_DATA(rta), sizeof(oif));
249 }
250 }
251
252 /* XXX: TODO: note that { oif, gw } was added/removed */
253 LWIP_UNUSED_ARG(gwbuf);
254 LWIP_UNUSED_ARG(gwlen);
255 LWIP_UNUSED_ARG(oif);
256 }
257
258 return dfltdiff;
259}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette