VirtualBox

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

Last change on this file since 93472 was 93472, checked in by vboxsync, 3 years ago

VBoxNetAdpCtl: (bugref:8093) Replaced adhoc IP address conversions with calls to IPRT functions

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