diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp')
-rw-r--r-- | src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp b/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp new file mode 100644 index 00000000..ed80412e --- /dev/null +++ b/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp @@ -0,0 +1,476 @@ +/* $Id: HostDnsServiceLinux.cpp $ */ +/** @file + * Linux specific DNS information fetching. + */ + +/* + * 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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_MAIN_HOST +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/file.h> +#include <VBox/log.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> + +#include <errno.h> +#include <poll.h> +#include <string.h> +#include <unistd.h> + +#include <fcntl.h> + +#include <linux/limits.h> + +/* Workaround for <sys/cdef.h> defining __flexarr to [] which beats us in + * struct inotify_event (char name __flexarr). */ +#include <sys/cdefs.h> +#undef __flexarr +#define __flexarr [0] +#include <sys/inotify.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include "../HostDnsService.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const char g_szEtcFolder[] = "/etc"; +static const char g_szResolvConfPath[] = "/etc/resolv.conf"; +static const char g_szResolvConfFilename[] = "resolv.conf"; + + +HostDnsServiceLinux::~HostDnsServiceLinux() +{ + if (m_fdShutdown >= 0) + { + close(m_fdShutdown); + m_fdShutdown = -1; + } +} + +HRESULT HostDnsServiceLinux::init(HostDnsMonitorProxy *pProxy) +{ + return HostDnsServiceResolvConf::init(pProxy, "/etc/resolv.conf"); +} + +int HostDnsServiceLinux::monitorThreadShutdown(RTMSINTERVAL uTimeoutMs) +{ + RT_NOREF(uTimeoutMs); + + if (m_fdShutdown >= 0) + send(m_fdShutdown, "", 1, MSG_NOSIGNAL); + + return VINF_SUCCESS; +} + +/** + * Format the notifcation event mask into a buffer for logging purposes. + */ +static const char *InotifyMaskToStr(char *psz, size_t cb, uint32_t fMask) +{ + static struct { const char *pszName; uint32_t cchName, fFlag; } const s_aFlags[] = + { +# define ENTRY(fFlag) { #fFlag, sizeof(#fFlag) - 1, fFlag } + ENTRY(IN_ACCESS), + ENTRY(IN_MODIFY), + ENTRY(IN_ATTRIB), + ENTRY(IN_CLOSE_WRITE), + ENTRY(IN_CLOSE_NOWRITE), + ENTRY(IN_OPEN), + ENTRY(IN_MOVED_FROM), + ENTRY(IN_MOVED_TO), + ENTRY(IN_CREATE), + ENTRY(IN_DELETE), + ENTRY(IN_DELETE_SELF), + ENTRY(IN_MOVE_SELF), + ENTRY(IN_Q_OVERFLOW), + ENTRY(IN_IGNORED), + ENTRY(IN_UNMOUNT), + ENTRY(IN_ISDIR), + }; + size_t offDst = 0; + for (size_t i = 0; i < RT_ELEMENTS(s_aFlags); i++) + if (fMask & s_aFlags[i].fFlag) + { + if (offDst && offDst < cb) + psz[offDst++] = ' '; + if (offDst < cb) + { + size_t cbToCopy = RT_MIN(s_aFlags[i].cchName, cb - offDst); + memcpy(&psz[offDst], s_aFlags[i].pszName, cbToCopy); + offDst += cbToCopy; + } + + fMask &= ~s_aFlags[i].fFlag; + if (!fMask) + break; + } + if (fMask && offDst < cb) + RTStrPrintf(&psz[offDst], cb - offDst, offDst ? " %#x" : "%#x", fMask); + else + psz[RT_MIN(offDst, cb - 1)] = '\0'; + return psz; +} + +/** + * Helper for HostDnsServiceLinux::monitorThreadProc. + */ +static int monitorSymlinkedDir(int iInotifyFd, char szRealResolvConf[PATH_MAX], size_t *poffFilename) +{ + RT_BZERO(szRealResolvConf, PATH_MAX); + + /* Check that it's a symlink first. */ + struct stat st; + if ( lstat(g_szResolvConfPath, &st) >= 0 + && S_ISLNK(st.st_mode)) + { + /* If realpath fails, the file must've been deleted while we were busy: */ + if ( realpath(g_szResolvConfPath, szRealResolvConf) + && strchr(szRealResolvConf, '/')) + { + /* Cut of the filename part. We only need that for deletion checks and such. */ + size_t const offFilename = strrchr(szRealResolvConf, '/') - &szRealResolvConf[0]; + *poffFilename = offFilename + 1; + szRealResolvConf[offFilename] = '\0'; + + /* Try set up directory monitoring. (File monitoring is done via the symlink.) */ + return inotify_add_watch(iInotifyFd, szRealResolvConf, IN_MOVE | IN_CREATE | IN_DELETE); + } + } + + *poffFilename = 0; + szRealResolvConf[0] = '\0'; + return -1; +} + +/** @todo If this code is needed elsewhere, we should abstract it into an IPRT + * thingy that monitors a file (path) for changes. This code is a little + * bit too complex to be duplicated. */ +int HostDnsServiceLinux::monitorThreadProc(void) +{ + /* + * Create a socket pair for signalling shutdown (see monitorThreadShutdown). + * ASSUME Linux 2.6.27 or later and that we can use SOCK_CLOEXEC. + */ + int aiStopPair[2]; + int iRc = socketpair(AF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0, aiStopPair); + int iErr = errno; + AssertLogRelMsgReturn(iRc == 0, ("socketpair: failed (%d: %s)\n", iErr, strerror(iErr)), RTErrConvertFromErrno(iErr)); + + m_fdShutdown = aiStopPair[0]; + + onMonitorThreadInitDone(); + + /* + * inotify initialization (using inotify_init1 w/ IN_CLOEXEC introduced + * in 2.6.27 shouldn't be a problem any more). + * + * Note! Ignoring failures here is safe, because poll will ignore entires + * with negative fd values. + */ + int const iNotifyFd = inotify_init1(IN_CLOEXEC); + if (iNotifyFd < 0) + LogRel(("HostDnsServiceLinux::monitorThreadProc: Warning! inotify_init failed (errno=%d)\n", errno)); + + /* Monitor the /etc directory so we can detect moves, creating and unlinking + involving /etc/resolv.conf: */ + int const iWdDir = inotify_add_watch(iNotifyFd, g_szEtcFolder, IN_MOVE | IN_CREATE | IN_DELETE); + + /* In case g_szResolvConfPath is a symbolic link, monitor the target directory + too for changes to what it links to (kept up to date via iWdDir). */ + char szRealResolvConf[PATH_MAX]; + size_t offRealResolvConfName = 0; + int iWdSymDir = ::monitorSymlinkedDir(iNotifyFd, szRealResolvConf, &offRealResolvConfName); + + /* Monitor the resolv.conf itself if it exists, following all symlinks. */ + int iWdFile = inotify_add_watch(iNotifyFd, g_szResolvConfPath, IN_CLOSE_WRITE | IN_DELETE_SELF); + + LogRel5(("HostDnsServiceLinux::monitorThreadProc: inotify: %d - iWdDir=%d iWdSymDir=%d iWdFile=%d\n", + iNotifyFd, iWdDir, iWdSymDir, iWdFile)); + + /* + * poll initialization: + */ + pollfd aFdPolls[2]; + RT_ZERO(aFdPolls); + + aFdPolls[0].fd = iNotifyFd; + aFdPolls[0].events = POLLIN; + + aFdPolls[1].fd = aiStopPair[1]; + aFdPolls[1].events = POLLIN; + + /* + * The monitoring loop. + */ + int vrcRet = VINF_SUCCESS; + for (;;) + { + /* + * Wait for something to happen. + */ + iRc = poll(aFdPolls, RT_ELEMENTS(aFdPolls), -1 /*infinite timeout*/); + if (iRc == -1) + { + if (errno != EINTR) + { + LogRelMax(32, ("HostDnsServiceLinux::monitorThreadProc: poll failed %d: errno=%d\n", iRc, errno)); + RTThreadSleep(1); + } + continue; + } + Log5Func(("poll returns %d: [0]=%#x [1]=%#x\n", iRc, aFdPolls[1].revents, aFdPolls[0].revents)); + + AssertMsgBreakStmt( (aFdPolls[0].revents & (POLLERR | POLLNVAL)) == 0 /* (ok for fd=-1 too, revents=0 then) */ + && (aFdPolls[1].revents & (POLLERR | POLLNVAL)) == 0, + ("Debug Me: [0]=%d,%#x [1]=%d, %#x\n", + aFdPolls[0].fd, aFdPolls[0].revents, aFdPolls[0].fd, aFdPolls[1].revents), + vrcRet = VERR_INTERNAL_ERROR); + + /* + * Check for shutdown first. + */ + if (aFdPolls[1].revents & POLLIN) + break; /** @todo should probably drain aiStopPair[1] here if we're really paranoid. + * we'll be closing our end of the socket/pipe, so any stuck write + * should return too (ECONNRESET, ENOTCONN or EPIPE). */ + + if (aFdPolls[0].revents & POLLIN) + { + /* + * Read the notification event. + */ +#define INOTIFY_EVENT_SIZE (RT_UOFFSETOF(struct inotify_event, name)) + union + { + uint8_t abBuf[(INOTIFY_EVENT_SIZE * 2 - 1 + NAME_MAX) / INOTIFY_EVENT_SIZE * INOTIFY_EVENT_SIZE * 4]; + uint64_t uAlignTrick[2]; + } uEvtBuf; + + ssize_t cbEvents = read(iNotifyFd, &uEvtBuf, sizeof(uEvtBuf)); + Log5Func(("read(inotify) -> %zd\n", cbEvents)); + if (cbEvents > 0) + Log5(("%.*Rhxd\n", cbEvents, &uEvtBuf)); + + /* + * Process the events. + * + * We'll keep the old watch descriptor number till after we're done + * parsing this block of events. Even so, the removal of watches + * isn't race free, as they'll get automatically removed when what + * is being watched is unliked. + */ + int iWdFileNew = iWdFile; + int iWdSymDirNew = iWdSymDir; + bool fTryReRead = false; + struct inotify_event const *pCurEvt = (struct inotify_event const *)&uEvtBuf; + while (cbEvents >= (ssize_t)INOTIFY_EVENT_SIZE) + { + char szTmp[64]; + if (pCurEvt->len == 0) + LogRel5(("HostDnsServiceLinux::monitorThreadProc: event: wd=%#x mask=%#x (%s) cookie=%#x\n", + pCurEvt->wd, pCurEvt->mask, InotifyMaskToStr(szTmp, sizeof(szTmp), pCurEvt->mask), pCurEvt->cookie)); + else + LogRel5(("HostDnsServiceLinux::monitorThreadProc: event: wd=%#x mask=%#x (%s) cookie=%#x len=%#x '%s'\n", + pCurEvt->wd, pCurEvt->mask, InotifyMaskToStr(szTmp, sizeof(szTmp), pCurEvt->mask), + pCurEvt->cookie, pCurEvt->len, pCurEvt->name)); + + /* + * The file itself (symlinks followed, remember): + */ + if (pCurEvt->wd == iWdFile) + { + if (pCurEvt->mask & IN_CLOSE_WRITE) + { + Log5Func(("file: close-after-write => trigger re-read\n")); + fTryReRead = true; + } + else if (pCurEvt->mask & IN_DELETE_SELF) + { + Log5Func(("file: deleted self\n")); + if (iWdFileNew != -1) + { + iRc = inotify_rm_watch(iNotifyFd, iWdFileNew); + AssertMsg(iRc >= 0, ("%d/%d\n", iRc, errno)); + iWdFileNew = -1; + } + } + else if (pCurEvt->mask & IN_IGNORED) + iWdFileNew = -1; /* file deleted */ + else + AssertMsgFailed(("file: mask=%#x\n", pCurEvt->mask)); + } + /* + * The /etc directory + * + * We only care about events relating to the creation, deletion and + * renaming of 'resolv.conf'. We'll restablish both the direct file + * watching and the watching of any symlinked directory on all of + * these events, although for the former we'll delay the re-starting + * of the watching till all events have been processed. + */ + else if (pCurEvt->wd == iWdDir) + { + if ( pCurEvt->len > 0 + && strcmp(g_szResolvConfFilename, pCurEvt->name) == 0) + { + if (pCurEvt->mask & (IN_MOVE | IN_CREATE | IN_DELETE)) + { + if (iWdFileNew >= 0) + { + iRc = inotify_rm_watch(iNotifyFd, iWdFileNew); + Log5Func(("dir: moved / created / deleted: dropped file watch (%d - iRc=%d/err=%d)\n", + iWdFileNew, iRc, errno)); + iWdFileNew = -1; + } + if (iWdSymDirNew >= 0) + { + iRc = inotify_rm_watch(iNotifyFd, iWdSymDirNew); + Log5Func(("dir: moved / created / deleted: dropped symlinked dir watch (%d - %s/%s - iRc=%d/err=%d)\n", + iWdSymDirNew, szRealResolvConf, &szRealResolvConf[offRealResolvConfName], iRc, errno)); + iWdSymDirNew = -1; + offRealResolvConfName = 0; + } + if (pCurEvt->mask & (IN_MOVED_TO | IN_CREATE)) + { + Log5Func(("dir: moved_to / created: trigger re-read\n")); + fTryReRead = true; + + iWdSymDirNew = ::monitorSymlinkedDir(iNotifyFd, szRealResolvConf, &offRealResolvConfName); + if (iWdSymDirNew < 0) + Log5Func(("dir: moved_to / created: re-stablished symlinked-directory monitoring: iWdSymDir=%d (%s/%s)\n", + iWdSymDirNew, szRealResolvConf, &szRealResolvConf[offRealResolvConfName])); + } + } + else + AssertMsgFailed(("dir: %#x\n", pCurEvt->mask)); + } + } + /* + * The directory of a symlinked resolv.conf. + * + * Where we only care when the symlink target is created, moved_to, + * deleted or moved_from - i.e. a minimal version of the /etc event + * processing above. + * + * Note! Since we re-statablish monitoring above, szRealResolvConf + * might not match the event we're processing. Fortunately, + * this shouldn't be important except for debug logging. + */ + else if (pCurEvt->wd == iWdSymDir) + { + if ( pCurEvt->len > 0 + && offRealResolvConfName > 0 + && strcmp(&szRealResolvConf[offRealResolvConfName], pCurEvt->name) == 0) + { + if (iWdFileNew >= 0) + { + iRc = inotify_rm_watch(iNotifyFd, iWdFileNew); + Log5Func(("symdir: moved / created / deleted: drop file watch (%d - iRc=%d/err=%d)\n", + iWdFileNew, iRc, errno)); + iWdFileNew = -1; + } + if (pCurEvt->mask & (IN_MOVED_TO | IN_CREATE)) + { + Log5Func(("symdir: moved_to / created: trigger re-read\n")); + fTryReRead = true; + } + } + } + /* We can get here it seems if our inotify_rm_watch calls above takes + place after new events relating to the two descriptors happens. */ + else + Log5Func(("Unknown (obsoleted) wd value: %d (mask=%#x cookie=%#x len=%#x)\n", + pCurEvt->wd, pCurEvt->mask, pCurEvt->cookie, pCurEvt->len)); + + /* advance to the next event */ + Assert(pCurEvt->len / INOTIFY_EVENT_SIZE * INOTIFY_EVENT_SIZE == pCurEvt->len); + size_t const cbCurEvt = INOTIFY_EVENT_SIZE + pCurEvt->len; + pCurEvt = (struct inotify_event const *)((uintptr_t)pCurEvt + cbCurEvt); + cbEvents -= cbCurEvt; + } + + /* + * Commit the new watch descriptor numbers now that we're + * done processing event using the old ones. + */ + iWdFile = iWdFileNew; + iWdSymDir = iWdSymDirNew; + + /* + * If the resolv.conf watch descriptor is -1, try restablish it here. + */ + if (iWdFile == -1) + { + iWdFile = inotify_add_watch(iNotifyFd, g_szResolvConfPath, IN_CLOSE_WRITE | IN_DELETE_SELF); + if (iWdFile >= 0) + { + Log5Func(("Re-established file watcher: iWdFile=%d\n", iWdFile)); + fTryReRead = true; + } + } + + /* + * If any of the events indicate that we should re-read the file, we + * do so now. Should reduce number of unnecessary re-reads. + */ + if (fTryReRead) + { + Log5Func(("Calling readResolvConf()...\n")); + try + { + readResolvConf(); + } + catch (...) + { + LogRel(("HostDnsServiceLinux::monitorThreadProc: readResolvConf threw exception!\n")); + } + } + } + } + + /* + * Close file descriptors. + */ + if (aiStopPair[0] == m_fdShutdown) /* paranoia */ + { + m_fdShutdown = -1; + close(aiStopPair[0]); + } + close(aiStopPair[1]); + close(iNotifyFd); + LogRel5(("HostDnsServiceLinux::monitorThreadProc: returns %Rrc\n", vrcRet)); + return vrcRet; +} + |