VirtualBox

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

Last change on this file since 106061 was 106061, checked in by vboxsync, 8 weeks ago

Copyright year updates by scm.

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