VirtualBox

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

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

VBoxNetAdp: (bugref:10077) Build and other fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.9 KB
Line 
1/* $Id: VBoxNetAdpCtl.cpp 91770 2021-10-15 19:38:17Z vboxsync $ */
2/** @file
3 * Apps - VBoxAdpCtl, Configuration tool for vboxnetX adapters.
4 */
5
6/*
7 * Copyright (C) 2009-2020 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/err.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 */
280class AddressIPv4
281{
282public:
283 AddressIPv4(const char *pcszAddress, const char *pcszNetmask = 0)
284 {
285 if (pcszNetmask)
286 m_Prefix = maskToPrefix(pcszNetmask);
287 else
288 {
289 /*
290 * Since guessing network mask is probably futile we simply use 24,
291 * as it matches our defaults. When non-default values are used
292 * providing a proper netmask is up to the user.
293 */
294 m_Prefix = 24;
295 }
296 inet_pton(AF_INET, pcszAddress, &(m_Address.sin_addr));
297 snprintf(m_szAddressAndMask, sizeof(m_szAddressAndMask), "%s/%d", pcszAddress, m_Prefix);
298 m_Broadcast.sin_addr.s_addr = computeBroadcast(m_Address.sin_addr.s_addr, m_Prefix);
299 inet_ntop(AF_INET, &(m_Broadcast.sin_addr), m_szBroadcast, sizeof(m_szBroadcast));
300 }
301 const char *getBroadcast() const { return m_szBroadcast; };
302 const char *getAddressAndMask() const { return m_szAddressAndMask; };
303private:
304 unsigned int maskToPrefix(const char *pcszNetmask);
305 unsigned long computeBroadcast(unsigned long ulAddress, unsigned int uPrefix);
306
307 unsigned int m_Prefix;
308 struct sockaddr_in m_Address;
309 struct sockaddr_in m_Broadcast;
310 char m_szAddressAndMask[INET_ADDRSTRLEN + 3]; /* e.g. 192.168.56.101/24 */
311 char m_szBroadcast[INET_ADDRSTRLEN];
312};
313
314unsigned int AddressIPv4::maskToPrefix(const char *pcszNetmask)
315{
316 unsigned cBits = 0;
317 unsigned m[4];
318
319 if (sscanf(pcszNetmask, "%u.%u.%u.%u", &m[0], &m[1], &m[2], &m[3]) == 4)
320 {
321 for (int i = 0; i < 4 && m[i]; ++i)
322 {
323 int mask = m[i];
324 while (mask & 0x80)
325 {
326 cBits++;
327 mask <<= 1;
328 }
329 }
330 }
331 return cBits;
332}
333
334unsigned long AddressIPv4::computeBroadcast(unsigned long ulAddress, unsigned int uPrefix)
335{
336 /* Note: the address is big-endian. */
337 unsigned long ulNetworkMask = (1l << uPrefix) - 1;
338 return (ulAddress & ulNetworkMask) | ~ulNetworkMask;
339}
340
341
342/*
343 * Linux-specific implementation of 'ip' command, as other platforms do not support it.
344 */
345class CmdIpLinux : public AddressCommand
346{
347public:
348 CmdIpLinux() { m_pszPath = "/sbin/ip"; };
349 /**
350 * IPv4 and IPv6 syntax is the same, so we override `remove` instead of implementing
351 * family-specific commands. It would be easier to use the same body in both
352 * 'removeV4' and 'removeV6', so we override 'remove' to illustrate how to do common
353 * implementation.
354 */
355 virtual int remove(const char *pcszAdapter, const char *pcszAddress)
356 { return execute(CmdList("addr") << "del" << pcszAddress << "dev" << pcszAdapter); };
357protected:
358 virtual int addV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0)
359 {
360 AddressIPv4 addr(pcszAddress, pcszNetmask);
361 bringUp(pcszAdapter);
362 return execute(CmdList("addr") << "add" << addr.getAddressAndMask() <<
363 "broadcast" << addr.getBroadcast() << "dev" << pcszAdapter);
364 };
365 virtual int addV6(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0)
366 {
367 bringUp(pcszAdapter);
368 return execute(CmdList("addr") << "add" << pcszAddress << "dev" << pcszAdapter);
369 NOREF(pcszNetmask);
370 };
371 /**
372 * Our command does not support 'replacing' addresses. Reporting this fact to generic implementation
373 * of 'set' causes it to remove all assigned addresses, then 'add' the new one.
374 */
375 virtual int setV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0)
376 { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); NOREF(pcszNetmask); };
377 /** We use family-agnostic command syntax. See 'remove' above. */
378 virtual int removeV4(const char *pcszAdapter, const char *pcszAddress)
379 { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); };
380 /** We use family-agnostic command syntax. See 'remove' above. */
381 virtual int removeV6(const char *pcszAdapter, const char *pcszAddress)
382 { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); };
383 virtual CmdList getShowCommand(const char *pcszAdapter) const
384 { return CmdList("addr") << "show" << "dev" << pcszAdapter; };
385private:
386 /** Brings up the adapter */
387 void bringUp(const char *pcszAdapter)
388 { execute(CmdList("link") << "set" << "dev" << pcszAdapter << "up"); };
389};
390#endif /* RT_OS_LINUX */
391
392
393/*********************************************************************************************************************************
394* Generic address command implementations *
395*********************************************************************************************************************************/
396
397int AddressCommand::set(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask)
398{
399 if (isAddrV6(pcszAddress))
400 {
401 removeAddresses(pcszAdapter, "inet6");
402 return addV6(pcszAdapter, pcszAddress, pcszNetmask);
403 }
404 int rc = setV4(pcszAdapter, pcszAddress, pcszNetmask);
405 if (rc == ENOTSUP)
406 {
407 removeAddresses(pcszAdapter, "inet");
408 rc = addV4(pcszAdapter, pcszAddress, pcszNetmask);
409 }
410 return rc;
411}
412
413int AddressCommand::remove(const char *pcszAdapter, const char *pcszAddress)
414{
415 if (isAddrV6(pcszAddress))
416 return removeV6(pcszAdapter, pcszAddress);
417 return removeV4(pcszAdapter, pcszAddress);
418}
419
420/*
421 * Allocate an array of exec arguments. In addition to arguments provided
422 * we need to include the full path to the executable as well as "terminating"
423 * null pointer marking the end of the array.
424 */
425char * const * AddressCommand::allocArgv(const CmdList& list)
426{
427 int i = 0;
428 std::list<const char *>::const_iterator it;
429 const char **argv = (const char **)calloc(list.getList().size() + 2, sizeof(const char *));
430 if (argv)
431 {
432 argv[i++] = m_pszPath;
433 for (it = list.getList().begin(); it != list.getList().end(); ++it)
434 argv[i++] = *it;
435 argv[i++] = NULL;
436 }
437 return (char * const*)argv;
438}
439
440int AddressCommand::execute(CmdList& list)
441{
442 char * const pEnv[] = { (char*)"LC_ALL=C", NULL };
443 char * const* argv = allocArgv(list);
444 if (argv == NULL)
445 return EXIT_FAILURE;
446
447 if (verbose)
448 {
449 const char *sep = "";
450 for (const char * const *pArg = argv; *pArg != NULL; ++pArg)
451 {
452 printf("%s%s", sep, *pArg);
453 sep = " ";
454 }
455 printf("\n");
456 }
457 if (dry_run)
458 return EXIT_SUCCESS;
459
460 int rc = EXIT_FAILURE; /* o/~ hope for the best, expect the worst */
461 pid_t childPid = fork();
462 switch (childPid)
463 {
464 case -1: /* Something went wrong. */
465 perror("fork");
466 break;
467
468 case 0: /* Child process. */
469 if (execve(argv[0], argv, pEnv) == -1)
470 {
471 perror("execve");
472 exit(EXIT_FAILURE);
473 /* NOTREACHED */
474 }
475 break;
476
477 default: /* Parent process. */
478 {
479 int status;
480 pid_t waited = waitpid(childPid, &status, 0);
481 if (waited == childPid) /* likely*/
482 {
483 if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS)
484 rc = EXIT_SUCCESS;
485 }
486 else if (waited == (pid_t)-1)
487 {
488 perror("waitpid");
489 }
490 else
491 {
492 /* should never happen */
493 fprintf(stderr, "waitpid: unexpected pid %lld\n",
494 (long long int)waited);
495 }
496 break;
497 }
498 }
499
500 free((void*)argv);
501 return rc;
502}
503
504#define MAX_ADDRESSES 128
505#define MAX_ADDRLEN 64
506
507int AddressCommand::removeAddresses(const char *pcszAdapter, const char *pcszFamily)
508{
509 char szBuf[1024];
510 char aszAddresses[MAX_ADDRESSES][MAX_ADDRLEN];
511 int rc = EXIT_SUCCESS;
512 int fds[2];
513 char * const * argv = allocArgv(getShowCommand(pcszAdapter));
514 char * const envp[] = { (char*)"LC_ALL=C", NULL };
515
516 memset(aszAddresses, 0, sizeof(aszAddresses));
517
518 rc = pipe(fds);
519 if (rc < 0)
520 return errno;
521
522 pid_t pid = fork();
523 if (pid < 0)
524 return errno;
525
526 if (pid == 0)
527 {
528 /* child */
529 close(fds[0]);
530 close(STDOUT_FILENO);
531 rc = dup2(fds[1], STDOUT_FILENO);
532 if (rc >= 0)
533 if (execve(argv[0], argv, envp) == -1)
534 return errno;
535 return rc;
536 }
537
538 /* parent */
539 close(fds[1]);
540 FILE *fp = fdopen(fds[0], "r");
541 if (!fp)
542 return false;
543
544 int cAddrs;
545 for (cAddrs = 0; cAddrs < MAX_ADDRESSES && fgets(szBuf, sizeof(szBuf), fp);)
546 {
547 int cbSkipWS = strspn(szBuf, " \t");
548 char *pszWord = strtok(szBuf + cbSkipWS, " ");
549 /* We are concerned with particular family address lines only. */
550 if (!pszWord || strcmp(pszWord, pcszFamily))
551 continue;
552
553 pszWord = strtok(NULL, " ");
554
555 /* Skip "addr:" word if present. */
556 if (pszWord && !strcmp(pszWord, "addr:"))
557 pszWord = strtok(NULL, " ");
558
559 /* Skip link-local address lines. */
560 if (!pszWord || !strncmp(pszWord, "fe80", 4))
561 continue;
562 strncpy(aszAddresses[cAddrs++], pszWord, MAX_ADDRLEN-1);
563 }
564 fclose(fp);
565
566 for (int i = 0; i < cAddrs && rc == EXIT_SUCCESS; i++)
567 rc = remove(pcszAdapter, aszAddresses[i]);
568
569 return rc;
570}
571
572
573/*********************************************************************************************************************************
574* Adapter creation/removal implementations *
575*********************************************************************************************************************************/
576
577/*
578 * A generic implementation of adapter creation/removal ioctl calls.
579 */
580class Adapter
581{
582public:
583 int add(char *pszNameInOut);
584 int remove(const char *pcszName);
585 int checkName(const char *pcszNameIn, char *pszNameOut, size_t cbNameOut);
586protected:
587 virtual int doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq);
588};
589
590/*
591 * Solaris does not support dynamic creation/removal of adapters.
592 */
593class AdapterSolaris : public Adapter
594{
595protected:
596 virtual int doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq)
597 { return 1 /*ENOTSUP*/; NOREF(iCmd); NOREF(pReq); };
598};
599
600#if defined(RT_OS_LINUX)
601/*
602 * Linux implementation provides a 'workaround' to obtain adapter speed.
603 */
604class AdapterLinux : public Adapter
605{
606public:
607 int getSpeed(const char *pszName, unsigned *puSpeed);
608};
609
610int AdapterLinux::getSpeed(const char *pszName, unsigned *puSpeed)
611{
612 struct ifreq IfReq;
613 struct ethtool_value EthToolVal;
614 struct ethtool_cmd EthToolReq;
615 int fd = socket(AF_INET, SOCK_DGRAM, 0);
616 if (fd < 0)
617 {
618 fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link "
619 "speed for %s: ", pszName);
620 perror("VBoxNetAdpCtl: failed to open control socket");
621 return ADPCTLERR_SOCKET_FAILED;
622 }
623 /* Get link status first. */
624 memset(&EthToolVal, 0, sizeof(EthToolVal));
625 memset(&IfReq, 0, sizeof(IfReq));
626 snprintf(IfReq.ifr_name, sizeof(IfReq.ifr_name), "%s", pszName);
627
628 EthToolVal.cmd = ETHTOOL_GLINK;
629 IfReq.ifr_data = (caddr_t)&EthToolVal;
630 int rc = ioctl(fd, SIOCETHTOOL, &IfReq);
631 if (rc == 0)
632 {
633 if (EthToolVal.data)
634 {
635 memset(&IfReq, 0, sizeof(IfReq));
636 snprintf(IfReq.ifr_name, sizeof(IfReq.ifr_name), "%s", pszName);
637 EthToolReq.cmd = ETHTOOL_GSET;
638 IfReq.ifr_data = (caddr_t)&EthToolReq;
639 rc = ioctl(fd, SIOCETHTOOL, &IfReq);
640 if (rc == 0)
641 {
642 *puSpeed = EthToolReq.speed;
643 }
644 else
645 {
646 fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link "
647 "speed for %s: ", pszName);
648 perror("VBoxNetAdpCtl: ioctl failed");
649 rc = ADPCTLERR_IOCTL_FAILED;
650 }
651 }
652 else
653 *puSpeed = 0;
654 }
655 else
656 {
657 fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link "
658 "status for %s: ", pszName);
659 perror("VBoxNetAdpCtl: ioctl failed");
660 rc = ADPCTLERR_IOCTL_FAILED;
661 }
662
663 close(fd);
664 return rc;
665}
666#endif /* defined(RT_OS_LINUX) */
667
668int Adapter::add(char *pszName /* in/out */)
669{
670 VBOXNETADPREQ Req;
671 memset(&Req, '\0', sizeof(Req));
672 snprintf(Req.szName, sizeof(Req.szName), "%s", pszName);
673 int rc = doIOCtl(VBOXNETADP_CTL_ADD, &Req);
674 if (rc == 0)
675 strncpy(pszName, Req.szName, VBOXNETADP_MAX_NAME_LEN);
676 return rc;
677}
678
679int Adapter::remove(const char *pcszName)
680{
681 VBOXNETADPREQ Req;
682 memset(&Req, '\0', sizeof(Req));
683 snprintf(Req.szName, sizeof(Req.szName), "%s", pcszName);
684 return doIOCtl(VBOXNETADP_CTL_REMOVE, &Req);
685}
686
687int Adapter::checkName(const char *pcszNameIn, char *pszNameOut, size_t cbNameOut)
688{
689 int iAdapterIndex = -1;
690
691 if ( strlen(pcszNameIn) >= VBOXNETADP_MAX_NAME_LEN
692 || sscanf(pcszNameIn, "vboxnet%d", &iAdapterIndex) != 1
693 || iAdapterIndex < 0 || iAdapterIndex >= VBOXNETADP_MAX_INSTANCES )
694 {
695 fprintf(stderr, "VBoxNetAdpCtl: Setting configuration for '%s' is not supported.\n", pcszNameIn);
696 return ADPCTLERR_BAD_NAME;
697 }
698 snprintf(pszNameOut, cbNameOut, "vboxnet%d", iAdapterIndex);
699 if (strcmp(pszNameOut, pcszNameIn))
700 {
701 fprintf(stderr, "VBoxNetAdpCtl: Invalid adapter name '%s'.\n", pcszNameIn);
702 return ADPCTLERR_BAD_NAME;
703 }
704
705 return 0;
706}
707
708int Adapter::doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq)
709{
710 int fd = open(VBOXNETADP_CTL_DEV_NAME, O_RDWR);
711 if (fd == -1)
712 {
713 fprintf(stderr, "VBoxNetAdpCtl: Error while %s %s: ",
714 iCmd == VBOXNETADP_CTL_REMOVE ? "removing" : "adding",
715 pReq->szName[0] ? pReq->szName : "new interface");
716 perror("failed to open " VBOXNETADP_CTL_DEV_NAME);
717 return ADPCTLERR_NO_CTL_DEV;
718 }
719
720 int rc = ioctl(fd, iCmd, pReq);
721 if (rc == -1)
722 {
723 fprintf(stderr, "VBoxNetAdpCtl: Error while %s %s: ",
724 iCmd == VBOXNETADP_CTL_REMOVE ? "removing" : "adding",
725 pReq->szName[0] ? pReq->szName : "new interface");
726 perror("VBoxNetAdpCtl: ioctl failed for " VBOXNETADP_CTL_DEV_NAME);
727 rc = ADPCTLERR_IOCTL_FAILED;
728 }
729
730 close(fd);
731
732 return rc;
733}
734
735
736/*********************************************************************************************************************************
737* Global config file implementation *
738*********************************************************************************************************************************/
739
740#define VBOX_GLOBAL_NETWORK_CONFIG_PATH "/etc/vbox/networks.conf"
741#define VBOXNET_DEFAULT_IPV4MASK "255.255.255.0"
742
743class NetworkAddress
744{
745 public:
746 bool isValidString(const char *pcszNetwork);
747 bool isValid() { return m_fValid; };
748 virtual bool matches(const char *pcszNetwork) = 0;
749 virtual const char *defaultNetwork() = 0;
750 protected:
751 bool m_fValid;
752
753 int RTNetStrToIPv4CidrWithZeroPrefixAllowed(const char *pcszAddr, PRTNETADDRIPV4 pAddr, int *piPrefix);
754 int RTNetStrToIPv6CidrWithZeroPrefixAllowed(const char *pcszAddr, PRTNETADDRIPV6 pAddr, int *piPrefix);
755};
756
757int NetworkAddress::RTNetStrToIPv4CidrWithZeroPrefixAllowed(const char *pcszAddr, PRTNETADDRIPV4 pAddr, int *piPrefix)
758{
759 RTNETADDRIPV4 addr;
760 char *pszNext;
761
762 AssertPtrReturn(pcszAddr, VERR_INVALID_PARAMETER);
763 AssertPtrReturn(pAddr, VERR_INVALID_PARAMETER);
764 AssertPtrReturn(piPrefix, VERR_INVALID_PARAMETER);
765
766 pcszAddr = RTStrStripL(pcszAddr);
767 int rc = RTNetStrToIPv4AddrEx(pcszAddr, &addr, &pszNext);
768 if (RT_FAILURE(rc))
769 return rc;
770
771 /*
772 * If the prefix is missing, treat is as exact (/32) address
773 * specification.
774 */
775 if (*pszNext == '\0' || rc == VWRN_TRAILING_SPACES)
776 {
777 *pAddr = addr;
778 *piPrefix = 32;
779 return VINF_SUCCESS;
780 }
781
782 if (*pszNext == '/')
783 ++pszNext;
784 else
785 return VERR_INVALID_PARAMETER;
786
787 uint32_t prefix;
788 rc = RTStrToUInt32Ex(pszNext, &pszNext, 0, &prefix);
789 if ((rc == VINF_SUCCESS || rc == VWRN_TRAILING_SPACES) && prefix == 0)
790 {
791 *pAddr = addr;
792 *piPrefix = 0;
793 return VINF_SUCCESS;
794 }
795 return RTNetStrToIPv4Cidr(pcszAddr, pAddr, piPrefix);
796}
797
798int NetworkAddress::RTNetStrToIPv6CidrWithZeroPrefixAllowed(const char *pcszAddr, PRTNETADDRIPV6 pAddr, int *piPrefix)
799{
800 RTNETADDRIPV6 Addr;
801 uint8_t u8Prefix;
802 char *pszNext;
803 int rc;
804
805 AssertPtrReturn(pcszAddr, VERR_INVALID_PARAMETER);
806 AssertPtrReturn(pAddr, VERR_INVALID_PARAMETER);
807 AssertPtrReturn(piPrefix, VERR_INVALID_PARAMETER);
808
809 pcszAddr = RTStrStripL(pcszAddr);
810 rc = RTNetStrToIPv6AddrEx(pcszAddr, &Addr, &pszNext);
811 if (RT_FAILURE(rc))
812 return rc;
813
814 /*
815 * If the prefix is missing, treat is as exact (/128) address
816 * specification.
817 */
818 if (*pszNext == '\0' || rc == VWRN_TRAILING_SPACES)
819 {
820 *pAddr = Addr;
821 *piPrefix = 128;
822 return VINF_SUCCESS;
823 }
824
825 if (*pszNext != '/')
826 return VERR_INVALID_PARAMETER;
827
828 ++pszNext;
829 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &u8Prefix);
830 if (RT_FAILURE(rc) || rc == VWRN_TRAILING_CHARS)
831 return VERR_INVALID_PARAMETER;
832
833 if (u8Prefix > 128)
834 return VERR_INVALID_PARAMETER;
835
836 *pAddr = Addr;
837 *piPrefix = u8Prefix;
838 return VINF_SUCCESS;
839}
840
841bool NetworkAddress::isValidString(const char *pcszNetwork)
842{
843 RTNETADDRIPV4 addrv4;
844 RTNETADDRIPV6 addrv6;
845 int prefix;
846 int rc = RTNetStrToIPv4CidrWithZeroPrefixAllowed(pcszNetwork, &addrv4, &prefix);
847 if (RT_SUCCESS(rc))
848 return true;
849 rc = RTNetStrToIPv6CidrWithZeroPrefixAllowed(pcszNetwork, &addrv6, &prefix);
850 return RT_SUCCESS(rc);
851}
852
853class NetworkAddressIPv4 : public NetworkAddress
854{
855 public:
856 NetworkAddressIPv4(const char *pcszIpAddress, const char *pcszNetMask = VBOXNET_DEFAULT_IPV4MASK);
857 virtual bool matches(const char *pcszNetwork);
858 virtual const char *defaultNetwork() { return "192.168.56.1/21"; }; /* Matches defaults in VBox/Main/include/netif.h, see @bugref{10077}. */
859
860 private:
861 RTNETADDRIPV4 m_address;
862 int m_prefix;
863};
864
865NetworkAddressIPv4::NetworkAddressIPv4(const char *pcszIpAddress, const char *pcszNetMask)
866{
867 int rc = RTNetStrToIPv4Addr(pcszIpAddress, &m_address);
868 if (RT_SUCCESS(rc))
869 {
870 RTNETADDRIPV4 mask;
871 rc = RTNetStrToIPv4Addr(pcszNetMask, &mask);
872 if (RT_FAILURE(rc))
873 m_fValid = false;
874 else
875 rc = RTNetMaskToPrefixIPv4(&mask, &m_prefix);
876 }
877#if 0 /* cmd.set() does not support CIDR syntax */
878 else
879 rc = RTNetStrToIPv4Cidr(pcszIpAddress, &m_address, &m_prefix);
880#endif
881 m_fValid = RT_SUCCESS(rc);
882}
883
884bool NetworkAddressIPv4::matches(const char *pcszNetwork)
885{
886 RTNETADDRIPV4 allowedNet, allowedMask;
887 int allowedPrefix;
888 int rc = RTNetStrToIPv4CidrWithZeroPrefixAllowed(pcszNetwork, &allowedNet, &allowedPrefix);
889 if (RT_SUCCESS(rc))
890 rc = RTNetPrefixToMaskIPv4(allowedPrefix, &allowedMask);
891 if (RT_FAILURE(rc))
892 return false;
893 return m_prefix >= allowedPrefix && (m_address.au32[0] & allowedMask.au32[0]) == (allowedNet.au32[0] & allowedMask.au32[0]);
894}
895
896class NetworkAddressIPv6 : public NetworkAddress
897{
898 public:
899 NetworkAddressIPv6(const char *pcszIpAddress);
900 virtual bool matches(const char *pcszNetwork);
901 virtual const char *defaultNetwork() { return "FE80::/10"; };
902 private:
903 RTNETADDRIPV6 m_address;
904 int m_prefix;
905};
906
907NetworkAddressIPv6::NetworkAddressIPv6(const char *pcszIpAddress)
908{
909 int rc = RTNetStrToIPv6Cidr(pcszIpAddress, &m_address, &m_prefix);
910 m_fValid = RT_SUCCESS(rc);
911}
912
913bool NetworkAddressIPv6::matches(const char *pcszNetwork)
914{
915 RTNETADDRIPV6 allowedNet, allowedMask;
916 int allowedPrefix;
917 int rc = RTNetStrToIPv6CidrWithZeroPrefixAllowed(pcszNetwork, &allowedNet, &allowedPrefix);
918 if (RT_SUCCESS(rc))
919 rc = RTNetPrefixToMaskIPv6(allowedPrefix, &allowedMask);
920 if (RT_FAILURE(rc))
921 return false;
922 RTUINT128U u128Provided, u128Allowed;
923 return m_prefix >= allowedPrefix
924 && RTUInt128Compare(RTUInt128And(&u128Provided, &m_address, &allowedMask), RTUInt128And(&u128Allowed, &allowedNet, &allowedMask)) == 0;
925}
926
927
928class GlobalNetworkPermissionsConfig
929{
930 public:
931 bool forbids(const char *pcszIpAddress); /* address or address with mask in cidr */
932 bool forbids(const char *pcszIpAddress, const char *pcszNetMask);
933
934 private:
935 bool forbids(NetworkAddress& address);
936};
937
938bool GlobalNetworkPermissionsConfig::forbids(const char *pcszIpAddress)
939{
940 NetworkAddressIPv6 addrv6(pcszIpAddress);
941
942 if (addrv6.isValid())
943 return forbids(addrv6);
944
945 NetworkAddressIPv4 addrv4(pcszIpAddress);
946
947 if (addrv4.isValid())
948 return forbids(addrv4);
949
950 fprintf(stderr, "Error: invalid address '%s'\n", pcszIpAddress);
951 return true;
952}
953
954bool GlobalNetworkPermissionsConfig::forbids(const char *pcszIpAddress, const char *pcszNetMask)
955{
956 NetworkAddressIPv4 addrv4(pcszIpAddress, pcszNetMask);
957
958 if (addrv4.isValid())
959 return forbids(addrv4);
960
961 fprintf(stderr, "Error: invalid address '%s' with mask '%s'\n", pcszIpAddress, pcszNetMask);
962 return true;
963}
964
965bool GlobalNetworkPermissionsConfig::forbids(NetworkAddress& address)
966{
967 FILE *fp = fopen(VBOX_GLOBAL_NETWORK_CONFIG_PATH, "r");
968 if (!fp)
969 {
970 if (verbose)
971 fprintf(stderr, "Info: matching against default '%s' => %s\n", address.defaultNetwork(),
972 address.matches(address.defaultNetwork()) ? "MATCH" : "no match");
973 return !address.matches(address.defaultNetwork());
974 }
975
976 char *pszToken, szLine[1024];
977 for (int line = 1; fgets(szLine, sizeof(szLine), fp); ++line)
978 {
979 /* Skip anything except '*' lines */
980 if (strcmp("*", strtok(szLine, " \t\n")))
981 continue;
982 /* Match the specified address against each network */
983 while ((pszToken = strtok(NULL, " \t\n")) != NULL)
984 {
985 if (!address.isValidString(pszToken))
986 {
987 fprintf(stderr, "Warning: %s(%d) invalid network '%s'\n", VBOX_GLOBAL_NETWORK_CONFIG_PATH, line, pszToken);
988 continue;
989 }
990 if (verbose)
991 fprintf(stderr, "Info: %s(%d) matching against '%s' => %s\n", VBOX_GLOBAL_NETWORK_CONFIG_PATH, line, pszToken,
992 address.matches(pszToken) ? "MATCH" : "no match");
993 if (address.matches(pszToken))
994 return false;
995 }
996 }
997 fclose(fp);
998 return true;
999}
1000
1001
1002/*********************************************************************************************************************************
1003* Main logic, argument parsing, etc. *
1004*********************************************************************************************************************************/
1005
1006#if defined(RT_OS_LINUX)
1007static CmdIfconfigLinux g_ifconfig;
1008static AdapterLinux g_adapter;
1009#elif defined(RT_OS_SOLARIS)
1010static CmdIfconfigSolaris g_ifconfig;
1011static AdapterSolaris g_adapter;
1012#else
1013static CmdIfconfigDarwin g_ifconfig;
1014static Adapter g_adapter;
1015#endif
1016
1017static AddressCommand& chooseAddressCommand()
1018{
1019#if defined(RT_OS_LINUX)
1020 static CmdIpLinux g_ip;
1021 if (g_ip.isAvailable())
1022 return g_ip;
1023#endif
1024 return g_ifconfig;
1025}
1026
1027int main(int argc, char *argv[])
1028{
1029 char szAdapterName[VBOXNETADP_MAX_NAME_LEN];
1030 int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/);
1031 if (RT_FAILURE(rc))
1032 return RTMsgInitFailure(rc);
1033
1034
1035 AddressCommand& cmd = chooseAddressCommand();
1036
1037
1038 static const struct option options[] = {
1039 { "dry-run", no_argument, NULL, 'n' },
1040 { "verbose", no_argument, NULL, 'v' },
1041 { NULL, 0, NULL, 0 }
1042 };
1043
1044 int ch;
1045 while ((ch = getopt_long(argc, argv, "nv", options, NULL)) != -1)
1046 {
1047 switch (ch)
1048 {
1049 case 'n':
1050 dry_run = true;
1051 verbose = true;
1052 break;
1053
1054 case 'v':
1055 verbose = true;
1056 break;
1057
1058 case '?':
1059 default:
1060 return usage();
1061 }
1062 }
1063 argc -= optind;
1064 argv += optind;
1065
1066 if (argc == 0)
1067 return usage();
1068
1069
1070 /*
1071 * VBoxNetAdpCtl add
1072 */
1073 if (strcmp(argv[0], "add") == 0)
1074 {
1075 if (argc > 1) /* extraneous args */
1076 return usage();
1077
1078 /* Create a new interface, print its name. */
1079 *szAdapterName = '\0';
1080 rc = g_adapter.add(szAdapterName);
1081 if (rc == EXIT_SUCCESS)
1082 puts(szAdapterName);
1083
1084 return rc;
1085 }
1086
1087
1088 /*
1089 * All other variants are of the form:
1090 * VBoxNetAdpCtl if0 ...action...
1091 */
1092 const char * const ifname = argv[0];
1093 const char * const action = argv[1];
1094 if (argc < 2)
1095 return usage();
1096
1097
1098#ifdef RT_OS_LINUX
1099 /*
1100 * VBoxNetAdpCtl iface42 speed
1101 *
1102 * This ugly hack is needed for retrieving the link speed on
1103 * pre-2.6.33 kernels (see @bugref{6345}).
1104 *
1105 * This variant is used with any interface, not just host-only.
1106 */
1107 if (strcmp(action, "speed") == 0)
1108 {
1109 if (argc > 2) /* extraneous args */
1110 return usage();
1111
1112 if (strlen(ifname) >= IFNAMSIZ)
1113 {
1114 fprintf(stderr, "Interface name too long\n");
1115 return EXIT_FAILURE;
1116 }
1117
1118 unsigned uSpeed = 0;
1119 rc = g_adapter.getSpeed(ifname, &uSpeed);
1120 if (rc == EXIT_SUCCESS)
1121 printf("%u", uSpeed);
1122
1123 return rc;
1124 }
1125#endif /* RT_OS_LINUX */
1126
1127
1128 /*
1129 * The rest of the actions only operate on host-only interfaces.
1130 */
1131 /** @todo Why the code below uses both ifname and szAdapterName? */
1132 rc = g_adapter.checkName(ifname, szAdapterName, sizeof(szAdapterName));
1133 if (rc != EXIT_SUCCESS)
1134 return rc;
1135
1136
1137 /*
1138 * VBoxNetAdpCtl vboxnetN remove
1139 */
1140 if (strcmp(action, "remove") == 0)
1141 {
1142 if (argc > 2) /* extraneous args */
1143 return usage();
1144
1145 /* Remove an existing interface */
1146 return g_adapter.remove(ifname);
1147 }
1148
1149 /*
1150 * VBoxNetAdpCtl vboxnetN add
1151 */
1152 if (strcmp(action, "add") == 0)
1153 {
1154 if (argc > 2) /* extraneous args */
1155 return usage();
1156
1157 /* Create an interface with the given name, print its name. */
1158 rc = g_adapter.add(szAdapterName);
1159 if (rc == EXIT_SUCCESS)
1160 puts(szAdapterName);
1161
1162 return rc;
1163 }
1164
1165
1166 /*
1167 * The rest of the actions are of the form
1168 * VBoxNetAdpCtl vboxnetN $addr [...]
1169 *
1170 * Use the argument after the address to select the action.
1171 */
1172 /** @todo Do early verification of addr format here? */
1173 const char * const addr = argv[1];
1174 const char * const keyword = argv[2];
1175
1176 GlobalNetworkPermissionsConfig config;
1177
1178 /*
1179 * VBoxNetAdpCtl vboxnetN 1.2.3.4
1180 */
1181 if (keyword == NULL)
1182 {
1183 if (config.forbids(addr))
1184 {
1185 fprintf(stderr, "Error: permission denied\n");
1186 return -VERR_ACCESS_DENIED;
1187 }
1188
1189 return cmd.set(ifname, addr);
1190 }
1191
1192 /*
1193 * VBoxNetAdpCtl vboxnetN 1.2.3.4 netmask 255.255.255.0
1194 */
1195 if (strcmp(keyword, "netmask") == 0)
1196 {
1197 if (argc != 4) /* too few or too many args */
1198 return usage();
1199
1200 const char * const mask = argv[3];
1201 if (config.forbids(addr, mask))
1202 {
1203 fprintf(stderr, "Error: permission denied\n");
1204 return -VERR_ACCESS_DENIED;
1205 }
1206
1207 return cmd.set(ifname, addr, mask);
1208 }
1209
1210 /*
1211 * VBoxNetAdpCtl vboxnetN 1.2.3.4 remove
1212 */
1213 if (strcmp(keyword, "remove") == 0)
1214 {
1215 if (argc > 3) /* extraneous args */
1216 return usage();
1217
1218 return cmd.remove(ifname, addr);
1219 }
1220
1221 return usage();
1222}
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