diff options
Diffstat (limited to 'src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp')
-rw-r--r-- | src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp | 1178 |
1 files changed, 1178 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp b/src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp new file mode 100644 index 00000000..5433c969 --- /dev/null +++ b/src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp @@ -0,0 +1,1178 @@ +/* $Id: VBoxNetAdpCtl.cpp $ */ +/** @file + * Apps - VBoxAdpCtl, Configuration tool for vboxnetX adapters. + */ + +/* + * Copyright (C) 2009-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 * +*********************************************************************************************************************************/ +#include <list> +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/net.h> +#include <iprt/string.h> +#include <iprt/uint128.h> + +#ifdef RT_OS_LINUX +# include <arpa/inet.h> +# include <net/if.h> +# include <linux/types.h> +/* Older versions of ethtool.h rely on these: */ +typedef unsigned long long u64; +typedef __uint32_t u32; +typedef __uint16_t u16; +typedef __uint8_t u8; +# include <limits.h> /* for INT_MAX */ +# include <linux/ethtool.h> +# include <linux/sockios.h> +#endif +#ifdef RT_OS_SOLARIS +# include <sys/ioccom.h> +#endif + +/** @todo Error codes must be moved to some header file */ +#define ADPCTLERR_BAD_NAME 2 +#define ADPCTLERR_NO_CTL_DEV 3 +#define ADPCTLERR_IOCTL_FAILED 4 +#define ADPCTLERR_SOCKET_FAILED 5 + +/** @todo These are duplicates from src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdpInternal.h */ +#define VBOXNETADP_CTL_DEV_NAME "/dev/vboxnetctl" +#define VBOXNETADP_MAX_INSTANCES 128 +#define VBOXNETADP_NAME "vboxnet" +#define VBOXNETADP_MAX_NAME_LEN 32 +#define VBOXNETADP_CTL_ADD _IOWR('v', 1, VBOXNETADPREQ) +#define VBOXNETADP_CTL_REMOVE _IOW('v', 2, VBOXNETADPREQ) +typedef struct VBoxNetAdpReq +{ + char szName[VBOXNETADP_MAX_NAME_LEN]; +} VBOXNETADPREQ; +typedef VBOXNETADPREQ *PVBOXNETADPREQ; + +#define VBOXADPCTL_IFCONFIG_PATH1 "/sbin/ifconfig" +#define VBOXADPCTL_IFCONFIG_PATH2 "/bin/ifconfig" + +bool verbose; +bool dry_run; + + +static int usage(void) +{ + fprintf(stderr, "Usage: VBoxNetAdpCtl <adapter> <address> ([netmask <address>] | remove)\n"); + fprintf(stderr, " | VBoxNetAdpCtl [<adapter>] add\n"); + fprintf(stderr, " | VBoxNetAdpCtl <adapter> remove\n"); + return EXIT_FAILURE; +} + + +/* + * A wrapper on standard list that provides '<<' operator for adding several list members in a single + * line dynamically. For example: "CmdList(arg1) << arg2 << arg3" produces a list with three members. + */ +class CmdList +{ +public: + /** Creates an empty list. */ + CmdList() {}; + /** Creates a list with a single member. */ + CmdList(const char *pcszCommand) { m_list.push_back(pcszCommand); }; + /** Provides access to the underlying standard list. */ + const std::list<const char *>& getList(void) const { return m_list; }; + /** Adds a member to the list. */ + CmdList& operator<<(const char *pcszArgument); +private: + std::list<const char *>m_list; +}; + +CmdList& CmdList::operator<<(const char *pcszArgument) +{ + m_list.push_back(pcszArgument); + return *this; +} + +/** Simple helper to distinguish IPv4 and IPv6 addresses. */ +inline bool isAddrV6(const char *pcszAddress) +{ + return !!(strchr(pcszAddress, ':')); +} + + +/********************************************************************************************************************************* +* Generic address commands. * +*********************************************************************************************************************************/ + +/** + * The base class for all address manipulation commands. While being an abstract class, + * it provides a generic implementation of 'set' and 'remove' methods, which rely on + * pure virtual methods like 'addV4' and 'removeV4' to perform actual command execution. + */ +class AddressCommand +{ +public: + AddressCommand() : m_pszPath(0) {}; + virtual ~AddressCommand() {}; + + /** returns true if underlying command (executable) is present in the system. */ + bool isAvailable(void) + { struct stat s; return (!stat(m_pszPath, &s) && S_ISREG(s.st_mode)); }; + + /* + * Someday we may want to support several IP addresses per adapter, but for now we + * have 'set' method only, which replaces all addresses with the one specifed. + * + * virtual int add(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0; + */ + /** replace existing address(es) */ + virtual int set(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0); + /** remove address */ + virtual int remove(const char *pcszAdapter, const char *pcszAddress); +protected: + /** IPv4-specific handler used by generic implementation of 'set' method if 'setV4' is not supported. */ + virtual int addV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0; + /** IPv6-specific handler used by generic implementation of 'set' method. */ + virtual int addV6(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0; + /** IPv4-specific handler used by generic implementation of 'set' method. */ + virtual int setV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0; + /** IPv4-specific handler used by generic implementation of 'remove' method. */ + virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) = 0; + /** IPv6-specific handler used by generic implementation of 'remove' method. */ + virtual int removeV6(const char *pcszAdapter, const char *pcszAddress) = 0; + /** Composes the argument list of command that obtains all addresses assigned to the adapter. */ + virtual CmdList getShowCommand(const char *pcszAdapter) const = 0; + + /** Prepares an array of C strings needed for 'exec' call. */ + char * const * allocArgv(const CmdList& commandList); + /** Hides process creation details. To be used in derived classes. */ + int execute(CmdList& commandList); + + /** A path to executable command. */ + const char *m_pszPath; +private: + /** Removes all previously asssigned addresses of a particular protocol family. */ + int removeAddresses(const char *pcszAdapter, const char *pcszFamily); +}; + +/* + * A generic implementation of 'ifconfig' command for all platforms. + */ +class CmdIfconfig : public AddressCommand +{ +public: + CmdIfconfig() + { + struct stat s; + if ( !stat(VBOXADPCTL_IFCONFIG_PATH1, &s) + && S_ISREG(s.st_mode)) + m_pszPath = (char*)VBOXADPCTL_IFCONFIG_PATH1; + else + m_pszPath = (char*)VBOXADPCTL_IFCONFIG_PATH2; + }; + +protected: + /** Returns platform-specific subcommand to add an address. */ + virtual const char *addCmdArg(void) const = 0; + /** Returns platform-specific subcommand to remove an address. */ + virtual const char *delCmdArg(void) const = 0; + virtual CmdList getShowCommand(const char *pcszAdapter) const + { return CmdList(pcszAdapter); }; + virtual int addV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); NOREF(pcszNetmask); }; + virtual int addV6(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { + return execute(CmdList(pcszAdapter) << "inet6" << addCmdArg() << pcszAddress); + NOREF(pcszNetmask); + }; + virtual int setV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { + if (!pcszNetmask) + return execute(CmdList(pcszAdapter) << pcszAddress); + return execute(CmdList(pcszAdapter) << pcszAddress << "netmask" << pcszNetmask); + }; + virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) + { return execute(CmdList(pcszAdapter) << delCmdArg() << pcszAddress); }; + virtual int removeV6(const char *pcszAdapter, const char *pcszAddress) + { return execute(CmdList(pcszAdapter) << "inet6" << delCmdArg() << pcszAddress); }; +}; + + +/********************************************************************************************************************************* +* Platform-specific commands * +*********************************************************************************************************************************/ + +class CmdIfconfigLinux : public CmdIfconfig +{ +protected: + virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) + { return execute(CmdList(pcszAdapter) << "0.0.0.0"); NOREF(pcszAddress); }; + virtual const char *addCmdArg(void) const { return "add"; }; + virtual const char *delCmdArg(void) const { return "del"; }; +}; + +class CmdIfconfigDarwin : public CmdIfconfig +{ +protected: + virtual const char *addCmdArg(void) const { return "add"; }; + virtual const char *delCmdArg(void) const { return "delete"; }; +}; + +class CmdIfconfigSolaris : public CmdIfconfig +{ +public: + virtual int set(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { + const char *pcszFamily = isAddrV6(pcszAddress) ? "inet6" : "inet"; + int status; + + status = execute(CmdList(pcszAdapter) << pcszFamily); + if (status != EXIT_SUCCESS) + status = execute(CmdList(pcszAdapter) << pcszFamily << "plumb" << "up"); + if (status != EXIT_SUCCESS) + return status; + + return CmdIfconfig::set(pcszAdapter, pcszAddress, pcszNetmask); + }; +protected: + /* We can umplumb IPv4 interfaces only! */ + virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) + { + int rc = CmdIfconfig::removeV4(pcszAdapter, pcszAddress); + + /** @todo Do we really need to unplumb inet here? */ + execute(CmdList(pcszAdapter) << "inet" << "unplumb"); + return rc; + }; + virtual const char *addCmdArg(void) const { return "addif"; }; + virtual const char *delCmdArg(void) const { return "removeif"; }; +}; + + +#ifdef RT_OS_LINUX +/* + * Helper class to incapsulate IPv4 address conversion. + * + * Note that this class relies on NetworkAddress to have been used for + * checking validity of IP addresses prior calling any methods of this + * class. + */ +class AddressIPv4 +{ +public: + AddressIPv4(const char *pcszAddress, const char *pcszNetmask = 0) + { + m_Prefix = 0; + memset(&m_Address, 0, sizeof(m_Address)); + + if (pcszNetmask) + m_Prefix = maskToPrefix(pcszNetmask); + else + { + /* + * Since guessing network mask is probably futile we simply use 24, + * as it matches our defaults. When non-default values are used + * providing a proper netmask is up to the user. + */ + m_Prefix = 24; + } + int rc = RTNetStrToIPv4Addr(pcszAddress, &m_Address); + AssertRCReturnVoid(rc); + snprintf(m_szAddressAndMask, sizeof(m_szAddressAndMask), "%s/%d", pcszAddress, m_Prefix); + deriveBroadcast(&m_Address, m_Prefix); + } + const char *getBroadcast() const { return m_szBroadcast; }; + const char *getAddressAndMask() const { return m_szAddressAndMask; }; +private: + int maskToPrefix(const char *pcszNetmask); + void deriveBroadcast(PCRTNETADDRIPV4 pcAddress, int uPrefix); + + int m_Prefix; + RTNETADDRIPV4 m_Address; + char m_szAddressAndMask[INET_ADDRSTRLEN + 3]; /* e.g. 192.168.56.101/24 */ + char m_szBroadcast[INET_ADDRSTRLEN]; +}; + +int AddressIPv4::maskToPrefix(const char *pcszNetmask) +{ + RTNETADDRIPV4 mask; + int prefix = 0; + + int rc = RTNetStrToIPv4Addr(pcszNetmask, &mask); + AssertRCReturn(rc, 0); + rc = RTNetMaskToPrefixIPv4(&mask, &prefix); + AssertRCReturn(rc, 0); + + return prefix; +} + +void AddressIPv4::deriveBroadcast(PCRTNETADDRIPV4 pcAddress, int iPrefix) +{ + RTNETADDRIPV4 mask, broadcast; + int rc = RTNetPrefixToMaskIPv4(iPrefix, &mask); + AssertRCReturnVoid(rc); + broadcast.au32[0] = (pcAddress->au32[0] & mask.au32[0]) | ~mask.au32[0]; + inet_ntop(AF_INET, broadcast.au32, m_szBroadcast, sizeof(m_szBroadcast)); +} + + +/* + * Linux-specific implementation of 'ip' command, as other platforms do not support it. + */ +class CmdIpLinux : public AddressCommand +{ +public: + CmdIpLinux() { m_pszPath = "/sbin/ip"; }; + /** + * IPv4 and IPv6 syntax is the same, so we override `remove` instead of implementing + * family-specific commands. It would be easier to use the same body in both + * 'removeV4' and 'removeV6', so we override 'remove' to illustrate how to do common + * implementation. + */ + virtual int remove(const char *pcszAdapter, const char *pcszAddress) + { return execute(CmdList("addr") << "del" << pcszAddress << "dev" << pcszAdapter); }; +protected: + virtual int addV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { + AddressIPv4 addr(pcszAddress, pcszNetmask); + bringUp(pcszAdapter); + return execute(CmdList("addr") << "add" << addr.getAddressAndMask() << + "broadcast" << addr.getBroadcast() << "dev" << pcszAdapter); + }; + virtual int addV6(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { + bringUp(pcszAdapter); + return execute(CmdList("addr") << "add" << pcszAddress << "dev" << pcszAdapter); + NOREF(pcszNetmask); + }; + /** + * Our command does not support 'replacing' addresses. Reporting this fact to generic implementation + * of 'set' causes it to remove all assigned addresses, then 'add' the new one. + */ + virtual int setV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); NOREF(pcszNetmask); }; + /** We use family-agnostic command syntax. See 'remove' above. */ + virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) + { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); }; + /** We use family-agnostic command syntax. See 'remove' above. */ + virtual int removeV6(const char *pcszAdapter, const char *pcszAddress) + { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); }; + virtual CmdList getShowCommand(const char *pcszAdapter) const + { return CmdList("addr") << "show" << "dev" << pcszAdapter; }; +private: + /** Brings up the adapter */ + void bringUp(const char *pcszAdapter) + { execute(CmdList("link") << "set" << "dev" << pcszAdapter << "up"); }; +}; +#endif /* RT_OS_LINUX */ + + +/********************************************************************************************************************************* +* Generic address command implementations * +*********************************************************************************************************************************/ + +int AddressCommand::set(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask) +{ + if (isAddrV6(pcszAddress)) + { + removeAddresses(pcszAdapter, "inet6"); + return addV6(pcszAdapter, pcszAddress, pcszNetmask); + } + int rc = setV4(pcszAdapter, pcszAddress, pcszNetmask); + if (rc == ENOTSUP) + { + removeAddresses(pcszAdapter, "inet"); + rc = addV4(pcszAdapter, pcszAddress, pcszNetmask); + } + return rc; +} + +int AddressCommand::remove(const char *pcszAdapter, const char *pcszAddress) +{ + if (isAddrV6(pcszAddress)) + return removeV6(pcszAdapter, pcszAddress); + return removeV4(pcszAdapter, pcszAddress); +} + +/* + * Allocate an array of exec arguments. In addition to arguments provided + * we need to include the full path to the executable as well as "terminating" + * null pointer marking the end of the array. + */ +char * const * AddressCommand::allocArgv(const CmdList& list) +{ + int i = 0; + std::list<const char *>::const_iterator it; + const char **argv = (const char **)calloc(list.getList().size() + 2, sizeof(const char *)); + if (argv) + { + argv[i++] = m_pszPath; + for (it = list.getList().begin(); it != list.getList().end(); ++it) + argv[i++] = *it; + argv[i++] = NULL; + } + return (char * const*)argv; +} + +int AddressCommand::execute(CmdList& list) +{ + char * const pEnv[] = { (char*)"LC_ALL=C", NULL }; + char * const* argv = allocArgv(list); + if (argv == NULL) + return EXIT_FAILURE; + + if (verbose) + { + const char *sep = ""; + for (const char * const *pArg = argv; *pArg != NULL; ++pArg) + { + printf("%s%s", sep, *pArg); + sep = " "; + } + printf("\n"); + } + if (dry_run) + { + free((void *)argv); + return EXIT_SUCCESS; + } + + int rc = EXIT_FAILURE; /* o/~ hope for the best, expect the worst */ + pid_t childPid = fork(); + switch (childPid) + { + case -1: /* Something went wrong. */ + perror("fork"); + break; + + case 0: /* Child process. */ + if (execve(argv[0], argv, pEnv) == -1) + { + perror("execve"); + exit(EXIT_FAILURE); + /* NOTREACHED */ + } + break; + + default: /* Parent process. */ + { + int status; + pid_t waited = waitpid(childPid, &status, 0); + if (waited == childPid) /* likely*/ + { + if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) + rc = EXIT_SUCCESS; + } + else if (waited == (pid_t)-1) + { + perror("waitpid"); + } + else + { + /* should never happen */ + fprintf(stderr, "waitpid: unexpected pid %lld\n", + (long long int)waited); + } + break; + } + } + + free((void*)argv); + return rc; +} + +#define MAX_ADDRESSES 128 +#define MAX_ADDRLEN 64 + +int AddressCommand::removeAddresses(const char *pcszAdapter, const char *pcszFamily) +{ + char szBuf[1024]; + char aszAddresses[MAX_ADDRESSES][MAX_ADDRLEN]; + int rc = EXIT_SUCCESS; + int fds[2]; + + memset(aszAddresses, 0, sizeof(aszAddresses)); + + rc = pipe(fds); + if (rc < 0) + return errno; + + pid_t pid = fork(); + if (pid < 0) + return errno; + + if (pid == 0) + { + /* child */ + close(fds[0]); + close(STDOUT_FILENO); + rc = dup2(fds[1], STDOUT_FILENO); + if (rc >= 0) + { + char * const * argv = allocArgv(getShowCommand(pcszAdapter)); + char * const envp[] = { (char*)"LC_ALL=C", NULL }; + + if (execve(argv[0], argv, envp) == -1) + { + free((void *)argv); + return errno; + } + + free((void *)argv); + } + return rc; + } + + /* parent */ + close(fds[1]); + FILE *fp = fdopen(fds[0], "r"); + if (!fp) + return false; + + int cAddrs; + for (cAddrs = 0; cAddrs < MAX_ADDRESSES && fgets(szBuf, sizeof(szBuf), fp);) + { + int cbSkipWS = strspn(szBuf, " \t"); + char *pszWord = strtok(szBuf + cbSkipWS, " "); + /* We are concerned with particular family address lines only. */ + if (!pszWord || strcmp(pszWord, pcszFamily)) + continue; + + pszWord = strtok(NULL, " "); + + /* Skip "addr:" word if present. */ + if (pszWord && !strcmp(pszWord, "addr:")) + pszWord = strtok(NULL, " "); + + /* Skip link-local address lines. */ + if (!pszWord || !strncmp(pszWord, "fe80", 4)) + continue; + strncpy(aszAddresses[cAddrs++], pszWord, MAX_ADDRLEN-1); + } + fclose(fp); + + for (int i = 0; i < cAddrs && rc == EXIT_SUCCESS; i++) + rc = remove(pcszAdapter, aszAddresses[i]); + + return rc; +} + + +/********************************************************************************************************************************* +* Adapter creation/removal implementations * +*********************************************************************************************************************************/ + +/* + * A generic implementation of adapter creation/removal ioctl calls. + */ +class Adapter +{ +public: + int add(char *pszNameInOut); + int remove(const char *pcszName); + int checkName(const char *pcszNameIn, char *pszNameOut, size_t cbNameOut); +protected: + virtual int doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq); +}; + +/* + * Solaris does not support dynamic creation/removal of adapters. + */ +class AdapterSolaris : public Adapter +{ +protected: + virtual int doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq) + { return 1 /*ENOTSUP*/; NOREF(iCmd); NOREF(pReq); }; +}; + +#if defined(RT_OS_LINUX) +/* + * Linux implementation provides a 'workaround' to obtain adapter speed. + */ +class AdapterLinux : public Adapter +{ +public: + int getSpeed(const char *pszName, unsigned *puSpeed); +}; + +int AdapterLinux::getSpeed(const char *pszName, unsigned *puSpeed) +{ + struct ifreq IfReq; + struct ethtool_value EthToolVal; + struct ethtool_cmd EthToolReq; + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + { + fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link " + "speed for %s: ", pszName); + perror("VBoxNetAdpCtl: failed to open control socket"); + return ADPCTLERR_SOCKET_FAILED; + } + /* Get link status first. */ + memset(&EthToolVal, 0, sizeof(EthToolVal)); + memset(&IfReq, 0, sizeof(IfReq)); + snprintf(IfReq.ifr_name, sizeof(IfReq.ifr_name), "%s", pszName); + + EthToolVal.cmd = ETHTOOL_GLINK; + IfReq.ifr_data = (caddr_t)&EthToolVal; + int rc = ioctl(fd, SIOCETHTOOL, &IfReq); + if (rc == 0) + { + if (EthToolVal.data) + { + memset(&IfReq, 0, sizeof(IfReq)); + snprintf(IfReq.ifr_name, sizeof(IfReq.ifr_name), "%s", pszName); + EthToolReq.cmd = ETHTOOL_GSET; + IfReq.ifr_data = (caddr_t)&EthToolReq; + rc = ioctl(fd, SIOCETHTOOL, &IfReq); + if (rc == 0) + { + *puSpeed = EthToolReq.speed; + } + else + { + fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link " + "speed for %s: ", pszName); + perror("VBoxNetAdpCtl: ioctl failed"); + rc = ADPCTLERR_IOCTL_FAILED; + } + } + else + *puSpeed = 0; + } + else + { + fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link " + "status for %s: ", pszName); + perror("VBoxNetAdpCtl: ioctl failed"); + rc = ADPCTLERR_IOCTL_FAILED; + } + + close(fd); + return rc; +} +#endif /* defined(RT_OS_LINUX) */ + +int Adapter::add(char *pszName /* in/out */) +{ + VBOXNETADPREQ Req; + memset(&Req, '\0', sizeof(Req)); + snprintf(Req.szName, sizeof(Req.szName), "%s", pszName); + int rc = doIOCtl(VBOXNETADP_CTL_ADD, &Req); + if (rc == 0) + strncpy(pszName, Req.szName, VBOXNETADP_MAX_NAME_LEN); + return rc; +} + +int Adapter::remove(const char *pcszName) +{ + VBOXNETADPREQ Req; + memset(&Req, '\0', sizeof(Req)); + snprintf(Req.szName, sizeof(Req.szName), "%s", pcszName); + return doIOCtl(VBOXNETADP_CTL_REMOVE, &Req); +} + +int Adapter::checkName(const char *pcszNameIn, char *pszNameOut, size_t cbNameOut) +{ + int iAdapterIndex = -1; + + if ( strlen(pcszNameIn) >= VBOXNETADP_MAX_NAME_LEN + || sscanf(pcszNameIn, "vboxnet%d", &iAdapterIndex) != 1 + || iAdapterIndex < 0 || iAdapterIndex >= VBOXNETADP_MAX_INSTANCES ) + { + fprintf(stderr, "VBoxNetAdpCtl: Setting configuration for '%s' is not supported.\n", pcszNameIn); + return ADPCTLERR_BAD_NAME; + } + snprintf(pszNameOut, cbNameOut, "vboxnet%d", iAdapterIndex); + if (strcmp(pszNameOut, pcszNameIn)) + { + fprintf(stderr, "VBoxNetAdpCtl: Invalid adapter name '%s'.\n", pcszNameIn); + return ADPCTLERR_BAD_NAME; + } + + return 0; +} + +int Adapter::doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq) +{ + int fd = open(VBOXNETADP_CTL_DEV_NAME, O_RDWR); + if (fd == -1) + { + fprintf(stderr, "VBoxNetAdpCtl: Error while %s %s: ", + iCmd == VBOXNETADP_CTL_REMOVE ? "removing" : "adding", + pReq->szName[0] ? pReq->szName : "new interface"); + perror("failed to open " VBOXNETADP_CTL_DEV_NAME); + return ADPCTLERR_NO_CTL_DEV; + } + + int rc = ioctl(fd, iCmd, pReq); + if (rc == -1) + { + fprintf(stderr, "VBoxNetAdpCtl: Error while %s %s: ", + iCmd == VBOXNETADP_CTL_REMOVE ? "removing" : "adding", + pReq->szName[0] ? pReq->szName : "new interface"); + perror("VBoxNetAdpCtl: ioctl failed for " VBOXNETADP_CTL_DEV_NAME); + rc = ADPCTLERR_IOCTL_FAILED; + } + + close(fd); + + return rc; +} + + +/********************************************************************************************************************************* +* Global config file implementation * +*********************************************************************************************************************************/ + +#define VBOX_GLOBAL_NETWORK_CONFIG_PATH "/etc/vbox/networks.conf" +#define VBOXNET_DEFAULT_IPV4MASK "255.255.255.0" + +class NetworkAddress +{ + public: + bool isValidString(const char *pcszNetwork); + bool isValid() { return m_fValid; }; + virtual bool matches(const char *pcszNetwork) = 0; + virtual const char *defaultNetwork() = 0; + protected: + bool m_fValid; +}; + +bool NetworkAddress::isValidString(const char *pcszNetwork) +{ + RTNETADDRIPV4 addrv4; + RTNETADDRIPV6 addrv6; + int prefix; + int rc = RTNetStrToIPv4Cidr(pcszNetwork, &addrv4, &prefix); + if (RT_SUCCESS(rc)) + return true; + rc = RTNetStrToIPv6Cidr(pcszNetwork, &addrv6, &prefix); + return RT_SUCCESS(rc); +} + +class NetworkAddressIPv4 : public NetworkAddress +{ + public: + NetworkAddressIPv4(const char *pcszIpAddress, const char *pcszNetMask = VBOXNET_DEFAULT_IPV4MASK); + virtual bool matches(const char *pcszNetwork); + virtual const char *defaultNetwork() { return "192.168.56.1/21"; }; /* Matches defaults in VBox/Main/include/netif.h, see @bugref{10077}. */ + + protected: + bool isValidUnicastAddress(PCRTNETADDRIPV4 address); + + private: + RTNETADDRIPV4 m_address; + int m_prefix; +}; + +NetworkAddressIPv4::NetworkAddressIPv4(const char *pcszIpAddress, const char *pcszNetMask) +{ + int rc = RTNetStrToIPv4Addr(pcszIpAddress, &m_address); + if (RT_SUCCESS(rc)) + { + RTNETADDRIPV4 mask; + rc = RTNetStrToIPv4Addr(pcszNetMask, &mask); + if (RT_FAILURE(rc)) + m_fValid = false; + else + rc = RTNetMaskToPrefixIPv4(&mask, &m_prefix); + } +#if 0 /* cmd.set() does not support CIDR syntax */ + else + rc = RTNetStrToIPv4Cidr(pcszIpAddress, &m_address, &m_prefix); +#endif + m_fValid = RT_SUCCESS(rc) && isValidUnicastAddress(&m_address); +} + +bool NetworkAddressIPv4::isValidUnicastAddress(PCRTNETADDRIPV4 address) +{ + /* Multicast addresses are not allowed. */ + if ((address->au8[0] & 0xF0) == 0xE0) + return false; + + /* Broadcast address is not allowed. */ + if (address->au32[0] == 0xFFFFFFFF) /* Endianess does not matter in this particual case. */ + return false; + + /* Loopback addresses are not allowed. */ + if ((address->au8[0] & 0xFF) == 0x7F) + return false; + + return true; +} + +bool NetworkAddressIPv4::matches(const char *pcszNetwork) +{ + RTNETADDRIPV4 allowedNet, allowedMask; + int allowedPrefix; + int rc = RTNetStrToIPv4Cidr(pcszNetwork, &allowedNet, &allowedPrefix); + if (RT_SUCCESS(rc)) + rc = RTNetPrefixToMaskIPv4(allowedPrefix, &allowedMask); + if (RT_FAILURE(rc)) + return false; + return m_prefix >= allowedPrefix && (m_address.au32[0] & allowedMask.au32[0]) == (allowedNet.au32[0] & allowedMask.au32[0]); +} + +class NetworkAddressIPv6 : public NetworkAddress +{ + public: + NetworkAddressIPv6(const char *pcszIpAddress); + virtual bool matches(const char *pcszNetwork); + virtual const char *defaultNetwork() { return "FE80::/10"; }; + private: + RTNETADDRIPV6 m_address; + int m_prefix; +}; + +NetworkAddressIPv6::NetworkAddressIPv6(const char *pcszIpAddress) +{ + int rc = RTNetStrToIPv6Cidr(pcszIpAddress, &m_address, &m_prefix); + m_fValid = RT_SUCCESS(rc); +} + +bool NetworkAddressIPv6::matches(const char *pcszNetwork) +{ + RTNETADDRIPV6 allowedNet, allowedMask; + int allowedPrefix; + int rc = RTNetStrToIPv6Cidr(pcszNetwork, &allowedNet, &allowedPrefix); + if (RT_SUCCESS(rc)) + rc = RTNetPrefixToMaskIPv6(allowedPrefix, &allowedMask); + if (RT_FAILURE(rc)) + return false; + RTUINT128U u128Provided, u128Allowed; + return m_prefix >= allowedPrefix + && RTUInt128Compare(RTUInt128And(&u128Provided, &m_address, &allowedMask), RTUInt128And(&u128Allowed, &allowedNet, &allowedMask)) == 0; +} + + +class GlobalNetworkPermissionsConfig +{ + public: + bool forbids(const char *pcszIpAddress); /* address or address with mask in cidr */ + bool forbids(const char *pcszIpAddress, const char *pcszNetMask); + + private: + bool forbids(NetworkAddress& address); +}; + +bool GlobalNetworkPermissionsConfig::forbids(const char *pcszIpAddress) +{ + NetworkAddressIPv6 addrv6(pcszIpAddress); + + if (addrv6.isValid()) + return forbids(addrv6); + + NetworkAddressIPv4 addrv4(pcszIpAddress); + + if (addrv4.isValid()) + return forbids(addrv4); + + fprintf(stderr, "Error: invalid address '%s'\n", pcszIpAddress); + return true; +} + +bool GlobalNetworkPermissionsConfig::forbids(const char *pcszIpAddress, const char *pcszNetMask) +{ + NetworkAddressIPv4 addrv4(pcszIpAddress, pcszNetMask); + + if (addrv4.isValid()) + return forbids(addrv4); + + fprintf(stderr, "Error: invalid address '%s' with mask '%s'\n", pcszIpAddress, pcszNetMask); + return true; +} + +bool GlobalNetworkPermissionsConfig::forbids(NetworkAddress& address) +{ + FILE *fp = fopen(VBOX_GLOBAL_NETWORK_CONFIG_PATH, "r"); + if (!fp) + { + if (verbose) + fprintf(stderr, "Info: matching against default '%s' => %s\n", address.defaultNetwork(), + address.matches(address.defaultNetwork()) ? "MATCH" : "no match"); + return !address.matches(address.defaultNetwork()); + } + + char *pszToken, szLine[1024]; + for (int line = 1; fgets(szLine, sizeof(szLine), fp); ++line) + { + pszToken = strtok(szLine, " \t\n"); + /* Skip anything except '*' lines */ + if (pszToken == NULL || strcmp("*", pszToken)) + continue; + /* Match the specified address against each network */ + while ((pszToken = strtok(NULL, " \t\n")) != NULL) + { + if (!address.isValidString(pszToken)) + { + fprintf(stderr, "Warning: %s(%d) invalid network '%s'\n", VBOX_GLOBAL_NETWORK_CONFIG_PATH, line, pszToken); + continue; + } + if (verbose) + fprintf(stderr, "Info: %s(%d) matching against '%s' => %s\n", VBOX_GLOBAL_NETWORK_CONFIG_PATH, line, pszToken, + address.matches(pszToken) ? "MATCH" : "no match"); + if (address.matches(pszToken)) + return false; + } + } + fclose(fp); + return true; +} + + +/********************************************************************************************************************************* +* Main logic, argument parsing, etc. * +*********************************************************************************************************************************/ + +#if defined(RT_OS_LINUX) +static CmdIfconfigLinux g_ifconfig; +static AdapterLinux g_adapter; +#elif defined(RT_OS_SOLARIS) +static CmdIfconfigSolaris g_ifconfig; +static AdapterSolaris g_adapter; +#else +static CmdIfconfigDarwin g_ifconfig; +static Adapter g_adapter; +#endif + +static AddressCommand& chooseAddressCommand() +{ +#if defined(RT_OS_LINUX) + static CmdIpLinux g_ip; + if (g_ip.isAvailable()) + return g_ip; +#endif + return g_ifconfig; +} + +int main(int argc, char *argv[]) +{ + char szAdapterName[VBOXNETADP_MAX_NAME_LEN]; + int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + + AddressCommand& cmd = chooseAddressCommand(); + + + static const struct option options[] = { + { "dry-run", no_argument, NULL, 'n' }, + { "verbose", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } + }; + + int ch; + while ((ch = getopt_long(argc, argv, "nv", options, NULL)) != -1) + { + switch (ch) + { + case 'n': + dry_run = true; + verbose = true; + break; + + case 'v': + verbose = true; + break; + + case '?': + default: + return usage(); + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + return usage(); + + + /* + * VBoxNetAdpCtl add + */ + if (strcmp(argv[0], "add") == 0) + { + if (argc > 1) /* extraneous args */ + return usage(); + + /* Create a new interface, print its name. */ + *szAdapterName = '\0'; + rc = g_adapter.add(szAdapterName); + if (rc == EXIT_SUCCESS) + puts(szAdapterName); + + return rc; + } + + + /* + * All other variants are of the form: + * VBoxNetAdpCtl if0 ...action... + */ + const char * const ifname = argv[0]; + const char * const action = argv[1]; + if (argc < 2) + return usage(); + + +#ifdef RT_OS_LINUX + /* + * VBoxNetAdpCtl iface42 speed + * + * This ugly hack is needed for retrieving the link speed on + * pre-2.6.33 kernels (see @bugref{6345}). + * + * This variant is used with any interface, not just host-only. + */ + if (strcmp(action, "speed") == 0) + { + if (argc > 2) /* extraneous args */ + return usage(); + + if (strlen(ifname) >= IFNAMSIZ) + { + fprintf(stderr, "Interface name too long\n"); + return EXIT_FAILURE; + } + + unsigned uSpeed = 0; + rc = g_adapter.getSpeed(ifname, &uSpeed); + if (rc == EXIT_SUCCESS) + printf("%u", uSpeed); + + return rc; + } +#endif /* RT_OS_LINUX */ + + + /* + * The rest of the actions only operate on host-only interfaces. + */ + /** @todo Why the code below uses both ifname and szAdapterName? */ + rc = g_adapter.checkName(ifname, szAdapterName, sizeof(szAdapterName)); + if (rc != EXIT_SUCCESS) + return rc; + + + /* + * VBoxNetAdpCtl vboxnetN remove + */ + if (strcmp(action, "remove") == 0) + { + if (argc > 2) /* extraneous args */ + return usage(); + + /* Remove an existing interface */ + return g_adapter.remove(ifname); + } + + /* + * VBoxNetAdpCtl vboxnetN add + */ + if (strcmp(action, "add") == 0) + { + if (argc > 2) /* extraneous args */ + return usage(); + + /* Create an interface with the given name, print its name. */ + rc = g_adapter.add(szAdapterName); + if (rc == EXIT_SUCCESS) + puts(szAdapterName); + + return rc; + } + + + /* + * The rest of the actions are of the form + * VBoxNetAdpCtl vboxnetN $addr [...] + * + * Use the argument after the address to select the action. + */ + /** @todo Do early verification of addr format here? */ + const char * const addr = argv[1]; + const char * const keyword = argv[2]; + + GlobalNetworkPermissionsConfig config; + + /* + * VBoxNetAdpCtl vboxnetN 1.2.3.4 + */ + if (keyword == NULL) + { + if (config.forbids(addr)) + { + fprintf(stderr, "Error: permission denied\n"); + return -VERR_ACCESS_DENIED; + } + + return cmd.set(ifname, addr); + } + + /* + * VBoxNetAdpCtl vboxnetN 1.2.3.4 netmask 255.255.255.0 + */ + if (strcmp(keyword, "netmask") == 0) + { + if (argc != 4) /* too few or too many args */ + return usage(); + + const char * const mask = argv[3]; + if (config.forbids(addr, mask)) + { + fprintf(stderr, "Error: permission denied\n"); + return -VERR_ACCESS_DENIED; + } + + return cmd.set(ifname, addr, mask); + } + + /* + * VBoxNetAdpCtl vboxnetN 1.2.3.4 remove + */ + if (strcmp(keyword, "remove") == 0) + { + if (argc > 3) /* extraneous args */ + return usage(); + + return cmd.remove(ifname, addr); + } + + return usage(); +} |