VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp@ 98068

Last change on this file since 98068 was 98068, checked in by vboxsync, 2 years ago

Main/HostDnsServiceLinux.cpp: Worked out a bunch of bugs and issues with HostDnsServiceLinux::monitorThreadProc. Main problems: 1. dropping events because of only processing the first one read returned when read can return lots of them. 2. modifying directory watch config means races. 3. if symlinked we must also watch the directory where the actual resolv.conf file is. bugref:10255

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.6 KB
Line 
1/* $Id: HostDnsServiceLinux.cpp 98068 2023-01-13 04:14:20Z vboxsync $ */
2/** @file
3 * Linux specific DNS information fetching.
4 */
5
6/*
7 * Copyright (C) 2013-2022 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.215389.xyz.
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/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_MAIN_HOST
33#include <iprt/assert.h>
34#include <iprt/errcore.h>
35#include <iprt/initterm.h>
36#include <iprt/file.h>
37#include <VBox/log.h>
38#include <iprt/stream.h>
39#include <iprt/string.h>
40#include <iprt/semaphore.h>
41#include <iprt/thread.h>
42
43#include <errno.h>
44#include <poll.h>
45#include <string.h>
46#include <unistd.h>
47
48#include <fcntl.h>
49
50#include <linux/limits.h>
51
52/* Workaround for <sys/cdef.h> defining __flexarr to [] which beats us in
53 * struct inotify_event (char name __flexarr). */
54#include <sys/cdefs.h>
55#undef __flexarr
56#define __flexarr [0]
57#include <sys/inotify.h>
58#include <sys/types.h>
59#include <sys/socket.h>
60#include <sys/stat.h>
61
62#include "../HostDnsService.h"
63
64
65/*********************************************************************************************************************************
66* Global Variables *
67*********************************************************************************************************************************/
68static int g_DnsMonitorStop[2];
69
70static const char g_szEtcFolder[] = "/etc";
71static const char g_szResolvConfPath[] = "/etc/resolv.conf";
72static const char g_szResolvConfFilename[] = "resolv.conf";
73
74
75class FileDescriptor
76{
77public:
78 FileDescriptor(int d = -1)
79 : fd(d)
80 {}
81
82 virtual ~FileDescriptor() {
83 if (fd != -1)
84 close(fd);
85 }
86
87 int fileDescriptor() const {return fd;}
88
89protected:
90 int fd;
91};
92
93
94class AutoNotify : public FileDescriptor
95{
96public:
97 AutoNotify()
98 {
99 FileDescriptor::fd = inotify_init();
100 AssertReturnVoid(FileDescriptor::fd != -1);
101 }
102};
103
104HostDnsServiceLinux::~HostDnsServiceLinux()
105{
106}
107
108HRESULT HostDnsServiceLinux::init(HostDnsMonitorProxy *pProxy)
109{
110 return HostDnsServiceResolvConf::init(pProxy, "/etc/resolv.conf");
111}
112
113int HostDnsServiceLinux::monitorThreadShutdown(RTMSINTERVAL uTimeoutMs)
114{
115 RT_NOREF(uTimeoutMs);
116
117 send(g_DnsMonitorStop[0], "", 1, 0);
118
119 /** @todo r=andy Do we have to wait for something here? Can this fail? */
120 return VINF_SUCCESS;
121}
122
123#ifdef LOG_ENABLED
124/**
125 * Format the notifcation event mask into a buffer for logging purposes.
126 */
127static const char *InotifyMaskToStr(char *psz, size_t cb, uint32_t fMask)
128{
129 static struct { const char *pszName; uint32_t cchName, fFlag; } const s_aFlags[] =
130 {
131# define ENTRY(fFlag) { #fFlag, sizeof(#fFlag) - 1, fFlag }
132 ENTRY(IN_ACCESS),
133 ENTRY(IN_MODIFY),
134 ENTRY(IN_ATTRIB),
135 ENTRY(IN_CLOSE_WRITE),
136 ENTRY(IN_CLOSE_NOWRITE),
137 ENTRY(IN_OPEN),
138 ENTRY(IN_MOVED_FROM),
139 ENTRY(IN_MOVED_TO),
140 ENTRY(IN_CREATE),
141 ENTRY(IN_DELETE),
142 ENTRY(IN_DELETE_SELF),
143 ENTRY(IN_MOVE_SELF),
144 ENTRY(IN_Q_OVERFLOW),
145 ENTRY(IN_IGNORED),
146 ENTRY(IN_UNMOUNT),
147 ENTRY(IN_ISDIR),
148 };
149 size_t offDst = 0;
150 for (size_t i = 0; i < RT_ELEMENTS(s_aFlags); i++)
151 if (fMask & s_aFlags[i].fFlag)
152 {
153 if (offDst && offDst < cb)
154 psz[offDst++] = ' ';
155 if (offDst < cb)
156 {
157 size_t cbToCopy = RT_MIN(s_aFlags[i].cchName, cb - offDst);
158 memcpy(&psz[offDst], s_aFlags[i].pszName, cbToCopy);
159 offDst += cbToCopy;
160 }
161
162 fMask &= ~s_aFlags[i].fFlag;
163 if (!fMask)
164 break;
165 }
166 if (fMask && offDst < cb)
167 RTStrPrintf(&psz[offDst], cb - offDst, offDst ? " %#x" : "%#x", fMask);
168 else
169 psz[RT_MIN(offDst, cb - 1)] = '\0';
170 return psz;
171}
172#endif
173
174
175/**
176 * Helper for HostDnsServiceLinux::monitorThreadProc.
177 */
178static int monitorSymlinkedDir(int iInotifyFd, char szRealResolvConf[PATH_MAX], size_t *poffFilename)
179{
180 RT_BZERO(szRealResolvConf, PATH_MAX);
181
182 /* Check that it's a symlink first. */
183 struct stat st;
184 if ( lstat(g_szResolvConfPath, &st) >= 0
185 && S_ISLNK(st.st_mode))
186 {
187 /* If realpath fails, the file must've been deleted while we were busy: */
188 if ( realpath(g_szResolvConfPath, szRealResolvConf)
189 && strchr(szRealResolvConf, '/'))
190 {
191 /* Cut of the filename part. We only need that for deletion checks and such. */
192 size_t const offFilename = strrchr(szRealResolvConf, '/') - &szRealResolvConf[0];
193 *poffFilename = offFilename + 1;
194 szRealResolvConf[offFilename] = '\0';
195
196 /* Try set up directory monitoring. (File monitoring is done via the symlink.) */
197 return inotify_add_watch(iInotifyFd, szRealResolvConf, IN_MOVE | IN_CREATE | IN_DELETE);
198 }
199 }
200
201 *poffFilename = 0;
202 szRealResolvConf[0] = '\0';
203 return -1;
204}
205
206int HostDnsServiceLinux::monitorThreadProc(void)
207{
208 /*
209 * inotify initialization.
210 *
211 * The order here helps keep the descriptor values stable.
212 *
213 * Note! Ignoring failures here is safe, because poll will ignore entires
214 * with negative fd values.
215 */
216 AutoNotify Notify;
217
218 /* Monitor the /etc directory so we can detect moves, unliking and creations
219 involving /etc/resolv.conf: */
220 int const iWdDir = inotify_add_watch(Notify.fileDescriptor(), g_szEtcFolder, IN_MOVE | IN_CREATE | IN_DELETE);
221
222 /* In case g_szResolvConfPath is a symbolic link, monitor the target directory
223 too for changes to what it links to. */
224 char szRealResolvConf[PATH_MAX];
225 size_t offRealResolvConfName = 0;
226 int iWdSymDir = ::monitorSymlinkedDir(Notify.fileDescriptor(), szRealResolvConf, &offRealResolvConfName);
227
228 /* Monitor the resolv.conf itself if it exists, following all symlinks. */
229 int iWdFile = inotify_add_watch(Notify.fileDescriptor(), g_szResolvConfPath, IN_CLOSE_WRITE | IN_DELETE_SELF);
230
231 Log5Func(("iWdDir=%d iWdSymDir=%d iWdFile=%d\n", iWdDir, iWdSymDir, iWdFile));
232
233 /*
234 * Create a socket pair for signalling shutdown via (see monitorThreadShutdown).
235 */
236 int rc = socketpair(AF_LOCAL, SOCK_DGRAM, 0, g_DnsMonitorStop);
237 AssertMsgReturn(rc == 0, ("socketpair: failed (%d: %s)\n", errno, strerror(errno)), E_FAIL);
238
239 /* automatic cleanup tricks */
240 FileDescriptor stopper0(g_DnsMonitorStop[0]);
241 FileDescriptor stopper1(g_DnsMonitorStop[1]);
242
243 /*
244 * poll initialization:
245 */
246 pollfd aFdPolls[2];
247 RT_ZERO(aFdPolls);
248
249 aFdPolls[0].fd = Notify.fileDescriptor();
250 aFdPolls[0].events = POLLIN;
251
252 aFdPolls[1].fd = g_DnsMonitorStop[1];
253 aFdPolls[1].events = POLLIN;
254
255 onMonitorThreadInitDone();
256
257 /*
258 * The monitoring loop.
259 */
260 for (;;)
261 {
262 /*
263 * Wait for something to happen.
264 */
265 rc = poll(aFdPolls, RT_ELEMENTS(aFdPolls), -1 /*infinite timeout*/);
266 if (rc == -1)
267 {
268 LogRelMax(32, ("HostDnsServiceLinux::monitorThreadProc: poll failed %d: errno=%d\n", rc, errno));
269 RTThreadSleep(1);
270 continue;
271 }
272 Log5Func(("poll returns %d: [0]=%#x [1]=%#x\n", rc, aFdPolls[1].revents, aFdPolls[0].revents));
273
274 AssertMsgReturn( (aFdPolls[0].revents & (POLLERR | POLLNVAL)) == 0
275 && (aFdPolls[1].revents & (POLLERR | POLLNVAL)) == 0, ("Debug Me"), VERR_INTERNAL_ERROR);
276
277
278 /*
279 * Check for shutdown first.
280 */
281 if (aFdPolls[1].revents & POLLIN)
282 return VINF_SUCCESS;
283
284 if (aFdPolls[0].revents & POLLIN)
285 {
286 /*
287 * Read the notification event.
288 */
289#define INOTIFY_EVENT_SIZE (RT_UOFFSETOF(struct inotify_event, name))
290 union
291 {
292 uint8_t abBuf[(INOTIFY_EVENT_SIZE * 2 - 1 + NAME_MAX) / INOTIFY_EVENT_SIZE * INOTIFY_EVENT_SIZE * 4];
293 uint64_t uAlignTrick[2];
294 } uEvtBuf;
295
296 ssize_t cbEvents = read(Notify.fileDescriptor(), &uEvtBuf, sizeof(uEvtBuf));
297 Log5Func(("read(inotify) -> %zd\n", cbEvents));
298 if (cbEvents > 0)
299 Log5(("%.*Rhxd\n", cbEvents, &uEvtBuf));
300
301 /*
302 * Process the events.
303 *
304 * We'll keep the old watch descriptor number till after we're done
305 * parsing this block of events. Even so, the removal of watches
306 * isn't race free, as they'll get automatically removed when what
307 * is being watched is unliked.
308 */
309 int iWdFileNew = iWdFile;
310 int iWdSymDirNew = iWdSymDir;
311 bool fTryReRead = false;
312 struct inotify_event const *pCurEvt = (struct inotify_event const *)&uEvtBuf;
313 while (cbEvents >= (ssize_t)INOTIFY_EVENT_SIZE)
314 {
315#ifdef LOG_ENABLED
316 char szTmp[64];
317 if (pCurEvt->len == 0)
318 Log5Func(("event: wd=%#x mask=%#x (%s) cookie=%#x\n", pCurEvt->wd, pCurEvt->mask,
319 InotifyMaskToStr(szTmp, sizeof(szTmp), pCurEvt->mask), pCurEvt->cookie));
320 else
321 Log5Func(("event: wd=%#x mask=%#x (%s) cookie=%#x len=%#x '%s'\n",
322 pCurEvt->wd, pCurEvt->mask, InotifyMaskToStr(szTmp, sizeof(szTmp), pCurEvt->mask),
323 pCurEvt->cookie, pCurEvt->len, pCurEvt->name));
324#endif
325
326 /*
327 * The file itself (symlinks followed, remember):
328 */
329 if (pCurEvt->wd == iWdFile)
330 {
331 if (pCurEvt->mask & IN_CLOSE_WRITE)
332 {
333 Log5Func(("file: close-after-write => trigger re-read\n"));
334 fTryReRead = true;
335 }
336 else if (pCurEvt->mask & IN_DELETE_SELF)
337 {
338 Log5Func(("file: deleted self\n"));
339 if (iWdFileNew != -1)
340 {
341 rc = inotify_rm_watch(Notify.fileDescriptor(), iWdFileNew);
342 AssertMsg(rc >= 0, ("%d/%d\n", rc, errno));
343 iWdFileNew = -1;
344 }
345 }
346 else if (pCurEvt->mask & IN_IGNORED)
347 iWdFileNew = -1; /* file deleted */
348 else
349 AssertMsgFailed(("file: mask=%#x\n", pCurEvt->mask));
350 }
351 /*
352 * The /etc directory
353 *
354 * We only care about events relating to the creation, deletion and
355 * renaming of 'resolv.conf'. We'll restablish both the direct file
356 * watching and the watching of any symlinked directory on all of
357 * these events, although for the former we'll delay the re-starting
358 * of the watching till all events have been processed.
359 */
360 else if (pCurEvt->wd == iWdDir)
361 {
362 if ( pCurEvt->len > 0
363 && strcmp(g_szResolvConfFilename, pCurEvt->name) == 0)
364 {
365 if (pCurEvt->mask & (IN_MOVE | IN_CREATE | IN_DELETE))
366 {
367 if (iWdFileNew >= 0)
368 {
369 rc = inotify_rm_watch(Notify.fileDescriptor(), iWdFileNew);
370 Log5Func(("dir: moved / created / deleted: dropped file watch (%d - rc=%d/err=%d)\n",
371 iWdFileNew, rc, errno));
372 iWdFileNew = -1;
373 }
374 if (iWdSymDirNew >= 0)
375 {
376 rc = inotify_rm_watch(Notify.fileDescriptor(), iWdSymDirNew);
377 Log5Func(("dir: moved / created / deleted: dropped symlinked dir watch (%d - %s/%s - rc=%d/err=%d)\n",
378 iWdSymDirNew, szRealResolvConf, &szRealResolvConf[offRealResolvConfName], rc, errno));
379 iWdSymDirNew = -1;
380 offRealResolvConfName = 0;
381 }
382 if (pCurEvt->mask & (IN_MOVED_TO | IN_CREATE))
383 {
384 Log5Func(("dir: moved_to / created: trigger re-read\n"));
385 fTryReRead = true;
386
387 iWdSymDirNew = ::monitorSymlinkedDir(Notify.fileDescriptor(),
388 szRealResolvConf, &offRealResolvConfName);
389 if (iWdSymDirNew < 0)
390 Log5Func(("dir: moved_to / created: re-stablished symlinked-directory monitoring: iWdSymDir=%d (%s/%s)\n",
391 iWdSymDirNew, szRealResolvConf, &szRealResolvConf[offRealResolvConfName]));
392 }
393 }
394 else
395 AssertMsgFailed(("dir: %#x\n", pCurEvt->mask));
396 }
397 }
398 /*
399 * The directory of a symlinked resolv.conf.
400 *
401 * Where we only care when the symlink target is created, moved_to,
402 * deleted or moved_from - i.e. a minimal version of the /etc event
403 * processing above.
404 *
405 * Note! Since we re-statablish monitoring above, szRealResolvConf
406 * might not match the event we're processing. Fortunately,
407 * this shouldn't be important except for debug logging.
408 */
409 else if (pCurEvt->wd == iWdSymDir)
410 {
411 if ( pCurEvt->len > 0
412 && offRealResolvConfName > 0
413 && strcmp(&szRealResolvConf[offRealResolvConfName], pCurEvt->name) == 0)
414 {
415 if (iWdFileNew >= 0)
416 {
417 rc = inotify_rm_watch(Notify.fileDescriptor(), iWdFileNew);
418 Log5Func(("symdir: moved / created / deleted: drop file watch (%d - rc=%d/err=%d)\n",
419 iWdFileNew, rc, errno));
420 iWdFileNew = -1;
421 }
422 if (pCurEvt->mask & (IN_MOVED_TO | IN_CREATE))
423 {
424 Log5Func(("symdir: moved_to / created: trigger re-read\n"));
425 fTryReRead = true;
426 }
427 }
428 }
429 /* We can get here it seems if our inotify_rm_watch calls above takes
430 place after new events relating to the two descriptors happens. */
431 else
432 Log5Func(("Unknown (obsoleted) wd value: %d (mask=%#x cookie=%#x len=%#x)\n",
433 pCurEvt->wd, pCurEvt->mask, pCurEvt->cookie, pCurEvt->len));
434
435 /* advance to the next event */
436 Assert(pCurEvt->len / INOTIFY_EVENT_SIZE * INOTIFY_EVENT_SIZE == pCurEvt->len);
437 size_t const cbCurEvt = INOTIFY_EVENT_SIZE + pCurEvt->len;
438 pCurEvt = (struct inotify_event const *)((uintptr_t)pCurEvt + cbCurEvt);
439 cbEvents -= cbCurEvt;
440 }
441
442 /*
443 * Commit the new watch descriptor numbers now that we're
444 * done processing event using the old ones.
445 */
446 iWdFile = iWdFileNew;
447 iWdSymDir = iWdSymDirNew;
448
449 /*
450 * If the resolv.conf watch descriptor is -1, try restablish it here.
451 */
452 if (iWdFile == -1)
453 {
454 iWdFile = inotify_add_watch(Notify.fileDescriptor(), g_szResolvConfPath, IN_CLOSE_WRITE | IN_DELETE_SELF);
455 if (iWdFile >= 0)
456 {
457 Log5Func(("Re-established file watcher: iWdFile=%d\n", iWdFile));
458 fTryReRead = true;
459 }
460 }
461
462 /*
463 * If any of the events indicate that we should re-read the file, we
464 * do so now. Should reduce number of unnecessary re-reads.
465 */
466 if (fTryReRead)
467 {
468 Log5Func(("Calling readResolvConf()...\n"));
469 readResolvConf();
470 }
471 }
472 }
473}
474
Note: See TracBrowser for help on using the repository browser.

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