VirtualBox

source: vbox/trunk/src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp@ 104027

Last change on this file since 104027 was 99775, checked in by vboxsync, 19 months ago

*: Mark functions as static if not used outside of a given compilation unit. Enables the compiler to optimize inlining, reduces the symbol tables, exposes unused functions and in some rare cases exposes mismtaches between function declarations and definitions, but most importantly reduces the number of parfait reports for the extern-function-no-forward-declaration category. This should not result in any functional changes, bugref:3409

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.6 KB
Line 
1/* $Id: VBoxNetAdpCtl.cpp 99775 2023-05-12 12:21:58Z vboxsync $ */
2/** @file
3 * Apps - VBoxAdpCtl, Configuration tool for vboxnetX adapters.
4 */
5
6/*
7 * Copyright (C) 2009-2023 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/*********************************************************************************************************************************
31* Header Files *
32*********************************************************************************************************************************/
33#include <list>
34#include <errno.h>
35#include <getopt.h>
36#include <stdio.h>
37#include <stdarg.h>
38#include <stdlib.h>
39#include <string.h>
40#include <unistd.h>
41#include <sys/wait.h>
42#include <sys/ioctl.h>
43#include <sys/stat.h>
44#include <fcntl.h>
45
46#include <iprt/errcore.h>
47#include <iprt/initterm.h>
48#include <iprt/message.h>
49#include <iprt/net.h>
50#include <iprt/string.h>
51#include <iprt/uint128.h>
52
53#ifdef RT_OS_LINUX
54# include <arpa/inet.h>
55# include <net/if.h>
56# include <linux/types.h>
57/* Older versions of ethtool.h rely on these: */
58typedef unsigned long long u64;
59typedef __uint32_t u32;
60typedef __uint16_t u16;
61typedef __uint8_t u8;
62# include <limits.h> /* for INT_MAX */
63# include <linux/ethtool.h>
64# include <linux/sockios.h>
65#endif
66#ifdef RT_OS_SOLARIS
67# include <sys/ioccom.h>
68#endif
69
70/** @todo Error codes must be moved to some header file */
71#define ADPCTLERR_BAD_NAME 2
72#define ADPCTLERR_NO_CTL_DEV 3
73#define ADPCTLERR_IOCTL_FAILED 4
74#define ADPCTLERR_SOCKET_FAILED 5
75
76/** @todo These are duplicates from src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdpInternal.h */
77#define VBOXNETADP_CTL_DEV_NAME "/dev/vboxnetctl"
78#define VBOXNETADP_MAX_INSTANCES 128
79#define VBOXNETADP_NAME "vboxnet"
80#define VBOXNETADP_MAX_NAME_LEN 32
81#define VBOXNETADP_CTL_ADD _IOWR('v', 1, VBOXNETADPREQ)
82#define VBOXNETADP_CTL_REMOVE _IOW('v', 2, VBOXNETADPREQ)
83typedef struct VBoxNetAdpReq
84{
85 char szName[VBOXNETADP_MAX_NAME_LEN];
86} VBOXNETADPREQ;
87typedef VBOXNETADPREQ *PVBOXNETADPREQ;
88
89#define VBOXADPCTL_IFCONFIG_PATH1 "/sbin/ifconfig"
90#define VBOXADPCTL_IFCONFIG_PATH2 "/bin/ifconfig"
91
92bool verbose;
93bool dry_run;
94
95
96static int usage(void)
97{
98 fprintf(stderr, "Usage: VBoxNetAdpCtl <adapter> <address> ([netmask <address>] | remove)\n");
99 fprintf(stderr, " | VBoxNetAdpCtl [<adapter>] add\n");
100 fprintf(stderr, " | VBoxNetAdpCtl <adapter> remove\n");
101 return EXIT_FAILURE;
102}
103
104
105/*
106 * A wrapper on standard list that provides '<<' operator for adding several list members in a single
107 * line dynamically. For example: "CmdList(arg1) << arg2 << arg3" produces a list with three members.
108 */
109class CmdList
110{
111public:
112 /** Creates an empty list. */
113 CmdList() {};
114 /** Creates a list with a single member. */
115 CmdList(const char *pcszCommand) { m_list.push_back(pcszCommand); };
116 /** Provides access to the underlying standard list. */
117 const std::list<const char *>& getList(void) const { return m_list; };
118 /** Adds a member to the list. */
119 CmdList& operator<<(const char *pcszArgument);
120private:
121 std::list<const char *>m_list;
122};
123
124CmdList& CmdList::operator<<(const char *pcszArgument)
125{
126 m_list.push_back(pcszArgument);
127 return *this;
128}
129
130/** Simple helper to distinguish IPv4 and IPv6 addresses. */
131DECLINLINE(bool) isAddrV6(const char *pcszAddress)
132{
133 return !!(strchr(pcszAddress, ':'));
134}
135
136
137/*********************************************************************************************************************************
138* Generic address commands. *
139*********************************************************************************************************************************/
140
141/**
142 * The base class for all address manipulation commands. While being an abstract class,
143 * it provides a generic implementation of 'set' and 'remove' methods, which rely on
144 * pure virtual methods like 'addV4' and 'removeV4' to perform actual command execution.
145 */
146class AddressCommand
147{
148public:
149 AddressCommand() : m_pszPath(0) {};
150 virtual ~AddressCommand() {};
151
152 /** returns true if underlying command (executable) is present in the system. */
153 bool isAvailable(void)
154 { struct stat s; return (!stat(m_pszPath, &s) && S_ISREG(s.st_mode)); };
155
156 /*
157 * Someday we may want to support several IP addresses per adapter, but for now we
158 * have 'set' method only, which replaces all addresses with the one specifed.
159 *
160 * virtual int add(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0;
161 */
162 /** replace existing address(es) */
163 virtual int set(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0);
164 /** remove address */
165 virtual int remove(const char *pcszAdapter, const char *pcszAddress);
166protected:
167 /** IPv4-specific handler used by generic implementation of 'set' method if 'setV4' is not supported. */
168 virtual int addV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0;
169 /** IPv6-specific handler used by generic implementation of 'set' method. */
170 virtual int addV6(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0;
171 /** IPv4-specific handler used by generic implementation of 'set' method. */
172 virtual int setV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0;
173 /** IPv4-specific handler used by generic implementation of 'remove' method. */
174 virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) = 0;
175 /** IPv6-specific handler used by generic implementation of 'remove' method. */
176 virtual int removeV6(const char *pcszAdapter, const char *pcszAddress) = 0;
177 /** Composes the argument list of command that obtains all addresses assigned to the adapter. */
178 virtual CmdList getShowCommand(const char *pcszAdapter) const = 0;
179
180 /** Prepares an array of C strings needed for 'exec' call. */
181 char * const * allocArgv(const CmdList& commandList);
182 /** Hides process creation details. To be used in derived classes. */
183 int execute(CmdList& commandList);
184
185 /** A path to executable command. */
186 const char *m_pszPath;
187private:
188 /** Removes all previously asssigned addresses of a particular protocol family. */
189 int removeAddresses(const char *pcszAdapter, const char *pcszFamily);
190};
191
192/*
193 * A generic implementation of 'ifconfig' command for all platforms.
194 */
195class CmdIfconfig : public AddressCommand
196{
197public:
198 CmdIfconfig()
199 {
200 struct stat s;
201 if ( !stat(VBOXADPCTL_IFCONFIG_PATH1, &s)
202 && S_ISREG(s.st_mode))
203 m_pszPath = (char*)VBOXADPCTL_IFCONFIG_PATH1;
204 else
205 m_pszPath = (char*)VBOXADPCTL_IFCONFIG_PATH2;
206 };
207
208protected:
209 /** Returns platform-specific subcommand to add an address. */
210 virtual const char *addCmdArg(void) const = 0;
211 /** Returns platform-specific subcommand to remove an address. */
212 virtual const char *delCmdArg(void) const = 0;
213 virtual CmdList getShowCommand(const char *pcszAdapter) const
214 { return CmdList(pcszAdapter); };
215 virtual int addV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0)
216 { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); NOREF(pcszNetmask); };
217 virtual int addV6(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0)
218 {
219 return execute(CmdList(pcszAdapter) << "inet6" << addCmdArg() << pcszAddress);
220 NOREF(pcszNetmask);
221 };
222 virtual int setV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0)
223 {
224 if (!pcszNetmask)
225 return execute(CmdList(pcszAdapter) << pcszAddress);
226 return execute(CmdList(pcszAdapter) << pcszAddress << "netmask" << pcszNetmask);
227 };
228 virtual int removeV4(const char *pcszAdapter, const char *pcszAddress)
229 { return execute(CmdList(pcszAdapter) << delCmdArg() << pcszAddress); };
230 virtual int removeV6(const char *pcszAdapter, const char *pcszAddress)
231 { return execute(CmdList(pcszAdapter) << "inet6" << delCmdArg() << pcszAddress); };
232};
233
234
235/*********************************************************************************************************************************
236* Platform-specific commands *
237*********************************************************************************************************************************/
238
239class CmdIfconfigLinux : public CmdIfconfig
240{
241protected:
242 virtual int removeV4(const char *pcszAdapter, const char *pcszAddress)
243 { return execute(CmdList(pcszAdapter) << "0.0.0.0"); NOREF(pcszAddress); };
244 virtual const char *addCmdArg(void) const { return "add"; };
245 virtual const char *delCmdArg(void) const { return "del"; };
246};
247
248class CmdIfconfigDarwin : public CmdIfconfig
249{
250protected:
251 virtual const char *addCmdArg(void) const { return "add"; };
252 virtual const char *delCmdArg(void) const { return "delete"; };
253};
254
255class CmdIfconfigSolaris : public CmdIfconfig
256{
257public:
258 virtual int set(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0)
259 {
260 const char *pcszFamily = isAddrV6(pcszAddress) ? "inet6" : "inet";
261 int status;
262
263 status = execute(CmdList(pcszAdapter) << pcszFamily);
264 if (status != EXIT_SUCCESS)
265 status = execute(CmdList(pcszAdapter) << pcszFamily << "plumb" << "up");
266 if (status != EXIT_SUCCESS)
267 return status;
268
269 return CmdIfconfig::set(pcszAdapter, pcszAddress, pcszNetmask);
270 };
271protected:
272 /* We can umplumb IPv4 interfaces only! */
273 virtual int removeV4(const char *pcszAdapter, const char *pcszAddress)
274 {
275 int rc = CmdIfconfig::removeV4(pcszAdapter, pcszAddress);
276
277 /** @todo Do we really need to unplumb inet here? */
278 execute(CmdList(pcszAdapter) << "inet" << "unplumb");
279 return rc;
280 };
281 virtual const char *addCmdArg(void) const { return "addif"; };
282 virtual const char *delCmdArg(void) const { return "removeif"; };
283};
284
285
286#ifdef RT_OS_LINUX
287/*
288 * Helper class to incapsulate IPv4 address conversion.
289 *
290 * Note that this class relies on NetworkAddress to have been used for
291 * checking validity of IP addresses prior calling any methods of this
292 * class.
293 */
294class AddressIPv4
295{
296public:
297 AddressIPv4(const char *pcszAddress, const char *pcszNetmask = 0)
298 {
299 m_Prefix = 0;
300 memset(&m_Address, 0, sizeof(m_Address));
301
302 if (pcszNetmask)
303 m_Prefix = maskToPrefix(pcszNetmask);
304 else
305 {
306 /*
307 * Since guessing network mask is probably futile we simply use 24,
308 * as it matches our defaults. When non-default values are used
309 * providing a proper netmask is up to the user.
310 */
311 m_Prefix = 24;
312 }
313 int rc = RTNetStrToIPv4Addr(pcszAddress, &m_Address);
314 AssertRCReturnVoid(rc);
315 snprintf(m_szAddressAndMask, sizeof(m_szAddressAndMask), "%s/%d", pcszAddress, m_Prefix);
316 deriveBroadcast(&m_Address, m_Prefix);
317 }
318 const char *getBroadcast() const { return m_szBroadcast; };
319 const char *getAddressAndMask() const { return m_szAddressAndMask; };
320private:
321 int maskToPrefix(const char *pcszNetmask);
322 void deriveBroadcast(PCRTNETADDRIPV4 pcAddress, int uPrefix);
323
324 int m_Prefix;
325 RTNETADDRIPV4 m_Address;
326 char m_szAddressAndMask[INET_ADDRSTRLEN + 3]; /* e.g. 192.168.56.101/24 */
327 char m_szBroadcast[INET_ADDRSTRLEN];
328};
329
330int AddressIPv4::maskToPrefix(const char *pcszNetmask)
331{
332 RTNETADDRIPV4 mask;
333 int prefix = 0;
334
335 int rc = RTNetStrToIPv4Addr(pcszNetmask, &mask);
336 AssertRCReturn(rc, 0);
337 rc = RTNetMaskToPrefixIPv4(&mask, &prefix);
338 AssertRCReturn(rc, 0);
339
340 return prefix;
341}
342
343void AddressIPv4::deriveBroadcast(PCRTNETADDRIPV4 pcAddress, int iPrefix)
344{
345 RTNETADDRIPV4 mask, broadcast;
346 int rc = RTNetPrefixToMaskIPv4(iPrefix, &mask);
347 AssertRCReturnVoid(rc);
348 broadcast.au32[0] = (pcAddress->au32[0] & mask.au32[0]) | ~mask.au32[0];
349 inet_ntop(AF_INET, broadcast.au32, m_szBroadcast, sizeof(m_szBroadcast));
350}
351
352
353/*
354 * Linux-specific implementation of 'ip' command, as other platforms do not support it.
355 */
356class CmdIpLinux : public AddressCommand
357{
358public:
359 CmdIpLinux() { m_pszPath = "/sbin/ip"; };
360 /**
361 * IPv4 and IPv6 syntax is the same, so we override `remove` instead of implementing
362 * family-specific commands. It would be easier to use the same body in both
363 * 'removeV4' and 'removeV6', so we override 'remove' to illustrate how to do common
364 * implementation.
365 */
366 virtual int remove(const char *pcszAdapter, const char *pcszAddress)
367 { return execute(CmdList("addr") << "del" << pcszAddress << "dev" << pcszAdapter); };
368protected:
369 virtual int addV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0)
370 {
371 AddressIPv4 addr(pcszAddress, pcszNetmask);
372 bringUp(pcszAdapter);
373 return execute(CmdList("addr") << "add" << addr.getAddressAndMask() <<
374 "broadcast" << addr.getBroadcast() << "dev" << pcszAdapter);
375 };
376 virtual int addV6(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0)
377 {
378 bringUp(pcszAdapter);
379 return execute(CmdList("addr") << "add" << pcszAddress << "dev" << pcszAdapter);
380 NOREF(pcszNetmask);
381 };
382 /**
383 * Our command does not support 'replacing' addresses. Reporting this fact to generic implementation
384 * of 'set' causes it to remove all assigned addresses, then 'add' the new one.
385 */
386 virtual int setV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0)
387 { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); NOREF(pcszNetmask); };
388 /** We use family-agnostic command syntax. See 'remove' above. */
389 virtual int removeV4(const char *pcszAdapter, const char *pcszAddress)
390 { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); };
391 /** We use family-agnostic command syntax. See 'remove' above. */
392 virtual int removeV6(const char *pcszAdapter, const char *pcszAddress)
393 { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); };
394 virtual CmdList getShowCommand(const char *pcszAdapter) const
395 { return CmdList("addr") << "show" << "dev" << pcszAdapter; };
396private:
397 /** Brings up the adapter */
398 void bringUp(const char *pcszAdapter)
399 { execute(CmdList("link") << "set" << "dev" << pcszAdapter << "up"); };
400};
401#endif /* RT_OS_LINUX */
402
403
404/*********************************************************************************************************************************
405* Generic address command implementations *
406*********************************************************************************************************************************/
407
408int AddressCommand::set(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask)
409{
410 if (isAddrV6(pcszAddress))
411 {
412 removeAddresses(pcszAdapter, "inet6");
413 return addV6(pcszAdapter, pcszAddress, pcszNetmask);
414 }
415 int rc = setV4(pcszAdapter, pcszAddress, pcszNetmask);
416 if (rc == ENOTSUP)
417 {
418 removeAddresses(pcszAdapter, "inet");
419 rc = addV4(pcszAdapter, pcszAddress, pcszNetmask);
420 }
421 return rc;
422}
423
424int AddressCommand::remove(const char *pcszAdapter, const char *pcszAddress)
425{
426 if (isAddrV6(pcszAddress))
427 return removeV6(pcszAdapter, pcszAddress);
428 return removeV4(pcszAdapter, pcszAddress);
429}
430
431/*
432 * Allocate an array of exec arguments. In addition to arguments provided
433 * we need to include the full path to the executable as well as "terminating"
434 * null pointer marking the end of the array.
435 */
436char * const * AddressCommand::allocArgv(const CmdList& list)
437{
438 int i = 0;
439 std::list<const char *>::const_iterator it;
440 const char **argv = (const char **)calloc(list.getList().size() + 2, sizeof(const char *));
441 if (argv)
442 {
443 argv[i++] = m_pszPath;
444 for (it = list.getList().begin(); it != list.getList().end(); ++it)
445 argv[i++] = *it;
446 argv[i++] = NULL;
447 }
448 return (char * const*)argv;
449}
450
451int AddressCommand::execute(CmdList& list)
452{
453 char * const pEnv[] = { (char*)"LC_ALL=C", NULL };
454 char * const* argv = allocArgv(list);
455 if (argv == NULL)
456 return EXIT_FAILURE;
457
458 if (verbose)
459 {
460 const char *sep = "";
461 for (const char * const *pArg = argv; *pArg != NULL; ++pArg)
462 {
463 printf("%s%s", sep, *pArg);
464 sep = " ";
465 }
466 printf("\n");
467 }
468 if (dry_run)
469 {
470 free((void *)argv);
471 return EXIT_SUCCESS;
472 }
473
474 int rc = EXIT_FAILURE; /* o/~ hope for the best, expect the worst */
475 pid_t childPid = fork();
476 switch (childPid)
477 {
478 case -1: /* Something went wrong. */
479 perror("fork");
480 break;
481
482 case 0: /* Child process. */
483 if (execve(argv[0], argv, pEnv) == -1)
484 {
485 perror("execve");
486 exit(EXIT_FAILURE);
487 /* NOTREACHED */
488 }
489 break;
490
491 default: /* Parent process. */
492 {
493 int status;
494 pid_t waited = waitpid(childPid, &status, 0);
495 if (waited == childPid) /* likely*/
496 {
497 if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS)
498 rc = EXIT_SUCCESS;
499 }
500 else if (waited == (pid_t)-1)
501 {
502 perror("waitpid");
503 }
504 else
505 {
506 /* should never happen */
507 fprintf(stderr, "waitpid: unexpected pid %lld\n",
508 (long long int)waited);
509 }
510 break;
511 }
512 }
513
514 free((void*)argv);
515 return rc;
516}
517
518#define MAX_ADDRESSES 128
519#define MAX_ADDRLEN 64
520
521int AddressCommand::removeAddresses(const char *pcszAdapter, const char *pcszFamily)
522{
523 char szBuf[1024];
524 char aszAddresses[MAX_ADDRESSES][MAX_ADDRLEN];
525 int rc = EXIT_SUCCESS;
526 int fds[2];
527
528 memset(aszAddresses, 0, sizeof(aszAddresses));
529
530 rc = pipe(fds);
531 if (rc < 0)
532 return errno;
533
534 pid_t pid = fork();
535 if (pid < 0)
536 return errno;
537
538 if (pid == 0)
539 {
540 /* child */
541 close(fds[0]);
542 close(STDOUT_FILENO);
543 rc = dup2(fds[1], STDOUT_FILENO);
544 if (rc >= 0)
545 {
546 char * const * argv = allocArgv(getShowCommand(pcszAdapter));
547 char * const envp[] = { (char*)"LC_ALL=C", NULL };
548
549 if (execve(argv[0], argv, envp) == -1)
550 {
551 free((void *)argv);
552 return errno;
553 }
554
555 free((void *)argv);
556 }
557 return rc;
558 }
559
560 /* parent */
561 close(fds[1]);
562 FILE *fp = fdopen(fds[0], "r");
563 if (!fp)
564 return false;
565
566 int cAddrs;
567 for (cAddrs = 0; cAddrs < MAX_ADDRESSES && fgets(szBuf, sizeof(szBuf), fp);)
568 {
569 int cbSkipWS = strspn(szBuf, " \t");
570 char *pszWord = strtok(szBuf + cbSkipWS, " ");
571 /* We are concerned with particular family address lines only. */
572 if (!pszWord || strcmp(pszWord, pcszFamily))
573 continue;
574
575 pszWord = strtok(NULL, " ");
576
577 /* Skip "addr:" word if present. */
578 if (pszWord && !strcmp(pszWord, "addr:"))
579 pszWord = strtok(NULL, " ");
580
581 /* Skip link-local address lines. */
582 if (!pszWord || !strncmp(pszWord, "fe80", 4))
583 continue;
584 strncpy(aszAddresses[cAddrs++], pszWord, MAX_ADDRLEN-1);
585 }
586 fclose(fp);
587
588 for (int i = 0; i < cAddrs && rc == EXIT_SUCCESS; i++)
589 rc = remove(pcszAdapter, aszAddresses[i]);
590
591 return rc;
592}
593
594
595/*********************************************************************************************************************************
596* Adapter creation/removal implementations *
597*********************************************************************************************************************************/
598
599/*
600 * A generic implementation of adapter creation/removal ioctl calls.
601 */
602class Adapter
603{
604public:
605 int add(char *pszNameInOut);
606 int remove(const char *pcszName);
607 int checkName(const char *pcszNameIn, char *pszNameOut, size_t cbNameOut);
608protected:
609 virtual int doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq);
610};
611
612/*
613 * Solaris does not support dynamic creation/removal of adapters.
614 */
615class AdapterSolaris : public Adapter
616{
617protected:
618 virtual int doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq)
619 { return 1 /*ENOTSUP*/; NOREF(iCmd); NOREF(pReq); };
620};
621
622#if defined(RT_OS_LINUX)
623/*
624 * Linux implementation provides a 'workaround' to obtain adapter speed.
625 */
626class AdapterLinux : public Adapter
627{
628public:
629 int getSpeed(const char *pszName, unsigned *puSpeed);
630};
631
632int AdapterLinux::getSpeed(const char *pszName, unsigned *puSpeed)
633{
634 struct ifreq IfReq;
635 struct ethtool_value EthToolVal;
636 struct ethtool_cmd EthToolReq;
637 int fd = socket(AF_INET, SOCK_DGRAM, 0);
638 if (fd < 0)
639 {
640 fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link "
641 "speed for %s: ", pszName);
642 perror("VBoxNetAdpCtl: failed to open control socket");
643 return ADPCTLERR_SOCKET_FAILED;
644 }
645 /* Get link status first. */
646 memset(&EthToolVal, 0, sizeof(EthToolVal));
647 memset(&IfReq, 0, sizeof(IfReq));
648 snprintf(IfReq.ifr_name, sizeof(IfReq.ifr_name), "%s", pszName);
649
650 EthToolVal.cmd = ETHTOOL_GLINK;
651 IfReq.ifr_data = (caddr_t)&EthToolVal;
652 int rc = ioctl(fd, SIOCETHTOOL, &IfReq);
653 if (rc == 0)
654 {
655 if (EthToolVal.data)
656 {
657 memset(&IfReq, 0, sizeof(IfReq));
658 snprintf(IfReq.ifr_name, sizeof(IfReq.ifr_name), "%s", pszName);
659 EthToolReq.cmd = ETHTOOL_GSET;
660 IfReq.ifr_data = (caddr_t)&EthToolReq;
661 rc = ioctl(fd, SIOCETHTOOL, &IfReq);
662 if (rc == 0)
663 {
664 *puSpeed = EthToolReq.speed;
665 }
666 else
667 {
668 fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link "
669 "speed for %s: ", pszName);
670 perror("VBoxNetAdpCtl: ioctl failed");
671 rc = ADPCTLERR_IOCTL_FAILED;
672 }
673 }
674 else
675 *puSpeed = 0;
676 }
677 else
678 {
679 fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link "
680 "status for %s: ", pszName);
681 perror("VBoxNetAdpCtl: ioctl failed");
682 rc = ADPCTLERR_IOCTL_FAILED;
683 }
684
685 close(fd);
686 return rc;
687}
688#endif /* defined(RT_OS_LINUX) */
689
690int Adapter::add(char *pszName /* in/out */)
691{
692 VBOXNETADPREQ Req;
693 memset(&Req, '\0', sizeof(Req));
694 snprintf(Req.szName, sizeof(Req.szName), "%s", pszName);
695 int rc = doIOCtl(VBOXNETADP_CTL_ADD, &Req);
696 if (rc == 0)
697 strncpy(pszName, Req.szName, VBOXNETADP_MAX_NAME_LEN);
698 return rc;
699}
700
701int Adapter::remove(const char *pcszName)
702{
703 VBOXNETADPREQ Req;
704 memset(&Req, '\0', sizeof(Req));
705 snprintf(Req.szName, sizeof(Req.szName), "%s", pcszName);
706 return doIOCtl(VBOXNETADP_CTL_REMOVE, &Req);
707}
708
709int Adapter::checkName(const char *pcszNameIn, char *pszNameOut, size_t cbNameOut)
710{
711 int iAdapterIndex = -1;
712
713 if ( strlen(pcszNameIn) >= VBOXNETADP_MAX_NAME_LEN
714 || sscanf(pcszNameIn, "vboxnet%d", &iAdapterIndex) != 1
715 || iAdapterIndex < 0 || iAdapterIndex >= VBOXNETADP_MAX_INSTANCES )
716 {
717 fprintf(stderr, "VBoxNetAdpCtl: Setting configuration for '%s' is not supported.\n", pcszNameIn);
718 return ADPCTLERR_BAD_NAME;
719 }
720 snprintf(pszNameOut, cbNameOut, "vboxnet%d", iAdapterIndex);
721 if (strcmp(pszNameOut, pcszNameIn))
722 {
723 fprintf(stderr, "VBoxNetAdpCtl: Invalid adapter name '%s'.\n", pcszNameIn);
724 return ADPCTLERR_BAD_NAME;
725 }
726
727 return 0;
728}
729
730int Adapter::doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq)
731{
732 int fd = open(VBOXNETADP_CTL_DEV_NAME, O_RDWR);
733 if (fd == -1)
734 {
735 fprintf(stderr, "VBoxNetAdpCtl: Error while %s %s: ",
736 iCmd == VBOXNETADP_CTL_REMOVE ? "removing" : "adding",
737 pReq->szName[0] ? pReq->szName : "new interface");
738 perror("failed to open " VBOXNETADP_CTL_DEV_NAME);
739 return ADPCTLERR_NO_CTL_DEV;
740 }
741
742 int rc = ioctl(fd, iCmd, pReq);
743 if (rc == -1)
744 {
745 fprintf(stderr, "VBoxNetAdpCtl: Error while %s %s: ",
746 iCmd == VBOXNETADP_CTL_REMOVE ? "removing" : "adding",
747 pReq->szName[0] ? pReq->szName : "new interface");
748 perror("VBoxNetAdpCtl: ioctl failed for " VBOXNETADP_CTL_DEV_NAME);
749 rc = ADPCTLERR_IOCTL_FAILED;
750 }
751
752 close(fd);
753
754 return rc;
755}
756
757
758/*********************************************************************************************************************************
759* Global config file implementation *
760*********************************************************************************************************************************/
761
762#define VBOX_GLOBAL_NETWORK_CONFIG_PATH "/etc/vbox/networks.conf"
763#define VBOXNET_DEFAULT_IPV4MASK "255.255.255.0"
764
765class NetworkAddress
766{
767 public:
768 bool isValidString(const char *pcszNetwork);
769 bool isValid() { return m_fValid; };
770 virtual bool matches(const char *pcszNetwork) = 0;
771 virtual const char *defaultNetwork() = 0;
772 protected:
773 bool m_fValid;
774};
775
776bool NetworkAddress::isValidString(const char *pcszNetwork)
777{
778 RTNETADDRIPV4 addrv4;
779 RTNETADDRIPV6 addrv6;
780 int prefix;
781 int rc = RTNetStrToIPv4Cidr(pcszNetwork, &addrv4, &prefix);
782 if (RT_SUCCESS(rc))
783 return true;
784 rc = RTNetStrToIPv6Cidr(pcszNetwork, &addrv6, &prefix);
785 return RT_SUCCESS(rc);
786}
787
788class NetworkAddressIPv4 : public NetworkAddress
789{
790 public:
791 NetworkAddressIPv4(const char *pcszIpAddress, const char *pcszNetMask = VBOXNET_DEFAULT_IPV4MASK);
792 virtual bool matches(const char *pcszNetwork);
793 virtual const char *defaultNetwork() { return "192.168.56.1/21"; }; /* Matches defaults in VBox/Main/include/netif.h, see @bugref{10077}. */
794
795 protected:
796 bool isValidUnicastAddress(PCRTNETADDRIPV4 address);
797
798 private:
799 RTNETADDRIPV4 m_address;
800 int m_prefix;
801};
802
803NetworkAddressIPv4::NetworkAddressIPv4(const char *pcszIpAddress, const char *pcszNetMask)
804{
805 int rc = RTNetStrToIPv4Addr(pcszIpAddress, &m_address);
806 if (RT_SUCCESS(rc))
807 {
808 RTNETADDRIPV4 mask;
809 rc = RTNetStrToIPv4Addr(pcszNetMask, &mask);
810 if (RT_FAILURE(rc))
811 m_fValid = false;
812 else
813 rc = RTNetMaskToPrefixIPv4(&mask, &m_prefix);
814 }
815#if 0 /* cmd.set() does not support CIDR syntax */
816 else
817 rc = RTNetStrToIPv4Cidr(pcszIpAddress, &m_address, &m_prefix);
818#endif
819 m_fValid = RT_SUCCESS(rc) && isValidUnicastAddress(&m_address);
820}
821
822bool NetworkAddressIPv4::isValidUnicastAddress(PCRTNETADDRIPV4 address)
823{
824 /* Multicast addresses are not allowed. */
825 if ((address->au8[0] & 0xF0) == 0xE0)
826 return false;
827
828 /* Broadcast address is not allowed. */
829 if (address->au32[0] == 0xFFFFFFFF) /* Endianess does not matter in this particual case. */
830 return false;
831
832 /* Loopback addresses are not allowed. */
833 if ((address->au8[0] & 0xFF) == 0x7F)
834 return false;
835
836 return true;
837}
838
839bool NetworkAddressIPv4::matches(const char *pcszNetwork)
840{
841 RTNETADDRIPV4 allowedNet, allowedMask;
842 int allowedPrefix;
843 int rc = RTNetStrToIPv4Cidr(pcszNetwork, &allowedNet, &allowedPrefix);
844 if (RT_SUCCESS(rc))
845 rc = RTNetPrefixToMaskIPv4(allowedPrefix, &allowedMask);
846 if (RT_FAILURE(rc))
847 return false;
848 return m_prefix >= allowedPrefix && (m_address.au32[0] & allowedMask.au32[0]) == (allowedNet.au32[0] & allowedMask.au32[0]);
849}
850
851class NetworkAddressIPv6 : public NetworkAddress
852{
853 public:
854 NetworkAddressIPv6(const char *pcszIpAddress);
855 virtual bool matches(const char *pcszNetwork);
856 virtual const char *defaultNetwork() { return "FE80::/10"; };
857 private:
858 RTNETADDRIPV6 m_address;
859 int m_prefix;
860};
861
862NetworkAddressIPv6::NetworkAddressIPv6(const char *pcszIpAddress)
863{
864 int rc = RTNetStrToIPv6Cidr(pcszIpAddress, &m_address, &m_prefix);
865 m_fValid = RT_SUCCESS(rc);
866}
867
868bool NetworkAddressIPv6::matches(const char *pcszNetwork)
869{
870 RTNETADDRIPV6 allowedNet, allowedMask;
871 int allowedPrefix;
872 int rc = RTNetStrToIPv6Cidr(pcszNetwork, &allowedNet, &allowedPrefix);
873 if (RT_SUCCESS(rc))
874 rc = RTNetPrefixToMaskIPv6(allowedPrefix, &allowedMask);
875 if (RT_FAILURE(rc))
876 return false;
877 RTUINT128U u128Provided, u128Allowed;
878 return m_prefix >= allowedPrefix
879 && RTUInt128Compare(RTUInt128And(&u128Provided, &m_address, &allowedMask), RTUInt128And(&u128Allowed, &allowedNet, &allowedMask)) == 0;
880}
881
882
883class GlobalNetworkPermissionsConfig
884{
885 public:
886 bool forbids(const char *pcszIpAddress); /* address or address with mask in cidr */
887 bool forbids(const char *pcszIpAddress, const char *pcszNetMask);
888
889 private:
890 bool forbids(NetworkAddress& address);
891};
892
893bool GlobalNetworkPermissionsConfig::forbids(const char *pcszIpAddress)
894{
895 NetworkAddressIPv6 addrv6(pcszIpAddress);
896
897 if (addrv6.isValid())
898 return forbids(addrv6);
899
900 NetworkAddressIPv4 addrv4(pcszIpAddress);
901
902 if (addrv4.isValid())
903 return forbids(addrv4);
904
905 fprintf(stderr, "Error: invalid address '%s'\n", pcszIpAddress);
906 return true;
907}
908
909bool GlobalNetworkPermissionsConfig::forbids(const char *pcszIpAddress, const char *pcszNetMask)
910{
911 NetworkAddressIPv4 addrv4(pcszIpAddress, pcszNetMask);
912
913 if (addrv4.isValid())
914 return forbids(addrv4);
915
916 fprintf(stderr, "Error: invalid address '%s' with mask '%s'\n", pcszIpAddress, pcszNetMask);
917 return true;
918}
919
920bool GlobalNetworkPermissionsConfig::forbids(NetworkAddress& address)
921{
922 FILE *fp = fopen(VBOX_GLOBAL_NETWORK_CONFIG_PATH, "r");
923 if (!fp)
924 {
925 if (verbose)
926 fprintf(stderr, "Info: matching against default '%s' => %s\n", address.defaultNetwork(),
927 address.matches(address.defaultNetwork()) ? "MATCH" : "no match");
928 return !address.matches(address.defaultNetwork());
929 }
930
931 char *pszToken, szLine[1024];
932 for (int line = 1; fgets(szLine, sizeof(szLine), fp); ++line)
933 {
934 pszToken = strtok(szLine, " \t\n");
935 /* Skip anything except '*' lines */
936 if (pszToken == NULL || strcmp("*", pszToken))
937 continue;
938 /* Match the specified address against each network */
939 while ((pszToken = strtok(NULL, " \t\n")) != NULL)
940 {
941 if (!address.isValidString(pszToken))
942 {
943 fprintf(stderr, "Warning: %s(%d) invalid network '%s'\n", VBOX_GLOBAL_NETWORK_CONFIG_PATH, line, pszToken);
944 continue;
945 }
946 if (verbose)
947 fprintf(stderr, "Info: %s(%d) matching against '%s' => %s\n", VBOX_GLOBAL_NETWORK_CONFIG_PATH, line, pszToken,
948 address.matches(pszToken) ? "MATCH" : "no match");
949 if (address.matches(pszToken))
950 {
951 fclose(fp);
952 return false;
953 }
954 }
955 }
956 fclose(fp);
957 return true;
958}
959
960
961/*********************************************************************************************************************************
962* Main logic, argument parsing, etc. *
963*********************************************************************************************************************************/
964
965#if defined(RT_OS_LINUX)
966static CmdIfconfigLinux g_ifconfig;
967static AdapterLinux g_adapter;
968#elif defined(RT_OS_SOLARIS)
969static CmdIfconfigSolaris g_ifconfig;
970static AdapterSolaris g_adapter;
971#else
972static CmdIfconfigDarwin g_ifconfig;
973static Adapter g_adapter;
974#endif
975
976static AddressCommand& chooseAddressCommand()
977{
978#if defined(RT_OS_LINUX)
979 static CmdIpLinux g_ip;
980 if (g_ip.isAvailable())
981 return g_ip;
982#endif
983 return g_ifconfig;
984}
985
986int main(int argc, char *argv[])
987{
988 char szAdapterName[VBOXNETADP_MAX_NAME_LEN];
989 int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/);
990 if (RT_FAILURE(rc))
991 return RTMsgInitFailure(rc);
992
993
994 AddressCommand& cmd = chooseAddressCommand();
995
996
997 static const struct option options[] = {
998 { "dry-run", no_argument, NULL, 'n' },
999 { "verbose", no_argument, NULL, 'v' },
1000 { NULL, 0, NULL, 0 }
1001 };
1002
1003 int ch;
1004 while ((ch = getopt_long(argc, argv, "nv", options, NULL)) != -1)
1005 {
1006 switch (ch)
1007 {
1008 case 'n':
1009 dry_run = true;
1010 verbose = true;
1011 break;
1012
1013 case 'v':
1014 verbose = true;
1015 break;
1016
1017 case '?':
1018 default:
1019 return usage();
1020 }
1021 }
1022 argc -= optind;
1023 argv += optind;
1024
1025 if (argc == 0)
1026 return usage();
1027
1028
1029 /*
1030 * VBoxNetAdpCtl add
1031 */
1032 if (strcmp(argv[0], "add") == 0)
1033 {
1034 if (argc > 1) /* extraneous args */
1035 return usage();
1036
1037 /* Create a new interface, print its name. */
1038 *szAdapterName = '\0';
1039 rc = g_adapter.add(szAdapterName);
1040 if (rc == EXIT_SUCCESS)
1041 puts(szAdapterName);
1042
1043 return rc;
1044 }
1045
1046
1047 /*
1048 * All other variants are of the form:
1049 * VBoxNetAdpCtl if0 ...action...
1050 */
1051 const char * const ifname = argv[0];
1052 const char * const action = argv[1];
1053 if (argc < 2)
1054 return usage();
1055
1056
1057#ifdef RT_OS_LINUX
1058 /*
1059 * VBoxNetAdpCtl iface42 speed
1060 *
1061 * This ugly hack is needed for retrieving the link speed on
1062 * pre-2.6.33 kernels (see @bugref{6345}).
1063 *
1064 * This variant is used with any interface, not just host-only.
1065 */
1066 if (strcmp(action, "speed") == 0)
1067 {
1068 if (argc > 2) /* extraneous args */
1069 return usage();
1070
1071 if (strlen(ifname) >= IFNAMSIZ)
1072 {
1073 fprintf(stderr, "Interface name too long\n");
1074 return EXIT_FAILURE;
1075 }
1076
1077 unsigned uSpeed = 0;
1078 rc = g_adapter.getSpeed(ifname, &uSpeed);
1079 if (rc == EXIT_SUCCESS)
1080 printf("%u", uSpeed);
1081
1082 return rc;
1083 }
1084#endif /* RT_OS_LINUX */
1085
1086
1087 /*
1088 * The rest of the actions only operate on host-only interfaces.
1089 */
1090 /** @todo Why the code below uses both ifname and szAdapterName? */
1091 rc = g_adapter.checkName(ifname, szAdapterName, sizeof(szAdapterName));
1092 if (rc != EXIT_SUCCESS)
1093 return rc;
1094
1095
1096 /*
1097 * VBoxNetAdpCtl vboxnetN remove
1098 */
1099 if (strcmp(action, "remove") == 0)
1100 {
1101 if (argc > 2) /* extraneous args */
1102 return usage();
1103
1104 /* Remove an existing interface */
1105 return g_adapter.remove(ifname);
1106 }
1107
1108 /*
1109 * VBoxNetAdpCtl vboxnetN add
1110 */
1111 if (strcmp(action, "add") == 0)
1112 {
1113 if (argc > 2) /* extraneous args */
1114 return usage();
1115
1116 /* Create an interface with the given name, print its name. */
1117 rc = g_adapter.add(szAdapterName);
1118 if (rc == EXIT_SUCCESS)
1119 puts(szAdapterName);
1120
1121 return rc;
1122 }
1123
1124
1125 /*
1126 * The rest of the actions are of the form
1127 * VBoxNetAdpCtl vboxnetN $addr [...]
1128 *
1129 * Use the argument after the address to select the action.
1130 */
1131 /** @todo Do early verification of addr format here? */
1132 const char * const addr = argv[1];
1133 const char * const keyword = argv[2];
1134
1135 GlobalNetworkPermissionsConfig config;
1136
1137 /*
1138 * VBoxNetAdpCtl vboxnetN 1.2.3.4
1139 */
1140 if (keyword == NULL)
1141 {
1142 if (config.forbids(addr))
1143 {
1144 fprintf(stderr, "Error: permission denied\n");
1145 return -VERR_ACCESS_DENIED;
1146 }
1147
1148 return cmd.set(ifname, addr);
1149 }
1150
1151 /*
1152 * VBoxNetAdpCtl vboxnetN 1.2.3.4 netmask 255.255.255.0
1153 */
1154 if (strcmp(keyword, "netmask") == 0)
1155 {
1156 if (argc != 4) /* too few or too many args */
1157 return usage();
1158
1159 const char * const mask = argv[3];
1160 if (config.forbids(addr, mask))
1161 {
1162 fprintf(stderr, "Error: permission denied\n");
1163 return -VERR_ACCESS_DENIED;
1164 }
1165
1166 return cmd.set(ifname, addr, mask);
1167 }
1168
1169 /*
1170 * VBoxNetAdpCtl vboxnetN 1.2.3.4 remove
1171 */
1172 if (strcmp(keyword, "remove") == 0)
1173 {
1174 if (argc > 3) /* extraneous args */
1175 return usage();
1176
1177 return cmd.remove(ifname, addr);
1178 }
1179
1180 return usage();
1181}
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