VirtualBox

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

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

scm --update-copyright-year

  • 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 93115 2022-01-01 11:31:46Z 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 */
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
754bool NetworkAddress::isValidString(const char *pcszNetwork)
755{
756 RTNETADDRIPV4 addrv4;
757 RTNETADDRIPV6 addrv6;
758 int prefix;
759 int rc = RTNetStrToIPv4Cidr(pcszNetwork, &addrv4, &prefix);
760 if (RT_SUCCESS(rc))
761 return true;
762 rc = RTNetStrToIPv6Cidr(pcszNetwork, &addrv6, &prefix);
763 return RT_SUCCESS(rc);
764}
765
766class NetworkAddressIPv4 : public NetworkAddress
767{
768 public:
769 NetworkAddressIPv4(const char *pcszIpAddress, const char *pcszNetMask = VBOXNET_DEFAULT_IPV4MASK);
770 virtual bool matches(const char *pcszNetwork);
771 virtual const char *defaultNetwork() { return "192.168.56.1/21"; }; /* Matches defaults in VBox/Main/include/netif.h, see @bugref{10077}. */
772
773 private:
774 RTNETADDRIPV4 m_address;
775 int m_prefix;
776};
777
778NetworkAddressIPv4::NetworkAddressIPv4(const char *pcszIpAddress, const char *pcszNetMask)
779{
780 int rc = RTNetStrToIPv4Addr(pcszIpAddress, &m_address);
781 if (RT_SUCCESS(rc))
782 {
783 RTNETADDRIPV4 mask;
784 rc = RTNetStrToIPv4Addr(pcszNetMask, &mask);
785 if (RT_FAILURE(rc))
786 m_fValid = false;
787 else
788 rc = RTNetMaskToPrefixIPv4(&mask, &m_prefix);
789 }
790#if 0 /* cmd.set() does not support CIDR syntax */
791 else
792 rc = RTNetStrToIPv4Cidr(pcszIpAddress, &m_address, &m_prefix);
793#endif
794 m_fValid = RT_SUCCESS(rc);
795}
796
797bool NetworkAddressIPv4::matches(const char *pcszNetwork)
798{
799 RTNETADDRIPV4 allowedNet, allowedMask;
800 int allowedPrefix;
801 int rc = RTNetStrToIPv4Cidr(pcszNetwork, &allowedNet, &allowedPrefix);
802 if (RT_SUCCESS(rc))
803 rc = RTNetPrefixToMaskIPv4(allowedPrefix, &allowedMask);
804 if (RT_FAILURE(rc))
805 return false;
806 return m_prefix >= allowedPrefix && (m_address.au32[0] & allowedMask.au32[0]) == (allowedNet.au32[0] & allowedMask.au32[0]);
807}
808
809class NetworkAddressIPv6 : public NetworkAddress
810{
811 public:
812 NetworkAddressIPv6(const char *pcszIpAddress);
813 virtual bool matches(const char *pcszNetwork);
814 virtual const char *defaultNetwork() { return "FE80::/10"; };
815 private:
816 RTNETADDRIPV6 m_address;
817 int m_prefix;
818};
819
820NetworkAddressIPv6::NetworkAddressIPv6(const char *pcszIpAddress)
821{
822 int rc = RTNetStrToIPv6Cidr(pcszIpAddress, &m_address, &m_prefix);
823 m_fValid = RT_SUCCESS(rc);
824}
825
826bool NetworkAddressIPv6::matches(const char *pcszNetwork)
827{
828 RTNETADDRIPV6 allowedNet, allowedMask;
829 int allowedPrefix;
830 int rc = RTNetStrToIPv6Cidr(pcszNetwork, &allowedNet, &allowedPrefix);
831 if (RT_SUCCESS(rc))
832 rc = RTNetPrefixToMaskIPv6(allowedPrefix, &allowedMask);
833 if (RT_FAILURE(rc))
834 return false;
835 RTUINT128U u128Provided, u128Allowed;
836 return m_prefix >= allowedPrefix
837 && RTUInt128Compare(RTUInt128And(&u128Provided, &m_address, &allowedMask), RTUInt128And(&u128Allowed, &allowedNet, &allowedMask)) == 0;
838}
839
840
841class GlobalNetworkPermissionsConfig
842{
843 public:
844 bool forbids(const char *pcszIpAddress); /* address or address with mask in cidr */
845 bool forbids(const char *pcszIpAddress, const char *pcszNetMask);
846
847 private:
848 bool forbids(NetworkAddress& address);
849};
850
851bool GlobalNetworkPermissionsConfig::forbids(const char *pcszIpAddress)
852{
853 NetworkAddressIPv6 addrv6(pcszIpAddress);
854
855 if (addrv6.isValid())
856 return forbids(addrv6);
857
858 NetworkAddressIPv4 addrv4(pcszIpAddress);
859
860 if (addrv4.isValid())
861 return forbids(addrv4);
862
863 fprintf(stderr, "Error: invalid address '%s'\n", pcszIpAddress);
864 return true;
865}
866
867bool GlobalNetworkPermissionsConfig::forbids(const char *pcszIpAddress, const char *pcszNetMask)
868{
869 NetworkAddressIPv4 addrv4(pcszIpAddress, pcszNetMask);
870
871 if (addrv4.isValid())
872 return forbids(addrv4);
873
874 fprintf(stderr, "Error: invalid address '%s' with mask '%s'\n", pcszIpAddress, pcszNetMask);
875 return true;
876}
877
878bool GlobalNetworkPermissionsConfig::forbids(NetworkAddress& address)
879{
880 FILE *fp = fopen(VBOX_GLOBAL_NETWORK_CONFIG_PATH, "r");
881 if (!fp)
882 {
883 if (verbose)
884 fprintf(stderr, "Info: matching against default '%s' => %s\n", address.defaultNetwork(),
885 address.matches(address.defaultNetwork()) ? "MATCH" : "no match");
886 return !address.matches(address.defaultNetwork());
887 }
888
889 char *pszToken, szLine[1024];
890 for (int line = 1; fgets(szLine, sizeof(szLine), fp); ++line)
891 {
892 pszToken = strtok(szLine, " \t\n");
893 /* Skip anything except '*' lines */
894 if (pszToken == NULL || strcmp("*", pszToken))
895 continue;
896 /* Match the specified address against each network */
897 while ((pszToken = strtok(NULL, " \t\n")) != NULL)
898 {
899 if (!address.isValidString(pszToken))
900 {
901 fprintf(stderr, "Warning: %s(%d) invalid network '%s'\n", VBOX_GLOBAL_NETWORK_CONFIG_PATH, line, pszToken);
902 continue;
903 }
904 if (verbose)
905 fprintf(stderr, "Info: %s(%d) matching against '%s' => %s\n", VBOX_GLOBAL_NETWORK_CONFIG_PATH, line, pszToken,
906 address.matches(pszToken) ? "MATCH" : "no match");
907 if (address.matches(pszToken))
908 return false;
909 }
910 }
911 fclose(fp);
912 return true;
913}
914
915
916/*********************************************************************************************************************************
917* Main logic, argument parsing, etc. *
918*********************************************************************************************************************************/
919
920#if defined(RT_OS_LINUX)
921static CmdIfconfigLinux g_ifconfig;
922static AdapterLinux g_adapter;
923#elif defined(RT_OS_SOLARIS)
924static CmdIfconfigSolaris g_ifconfig;
925static AdapterSolaris g_adapter;
926#else
927static CmdIfconfigDarwin g_ifconfig;
928static Adapter g_adapter;
929#endif
930
931static AddressCommand& chooseAddressCommand()
932{
933#if defined(RT_OS_LINUX)
934 static CmdIpLinux g_ip;
935 if (g_ip.isAvailable())
936 return g_ip;
937#endif
938 return g_ifconfig;
939}
940
941int main(int argc, char *argv[])
942{
943 char szAdapterName[VBOXNETADP_MAX_NAME_LEN];
944 int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/);
945 if (RT_FAILURE(rc))
946 return RTMsgInitFailure(rc);
947
948
949 AddressCommand& cmd = chooseAddressCommand();
950
951
952 static const struct option options[] = {
953 { "dry-run", no_argument, NULL, 'n' },
954 { "verbose", no_argument, NULL, 'v' },
955 { NULL, 0, NULL, 0 }
956 };
957
958 int ch;
959 while ((ch = getopt_long(argc, argv, "nv", options, NULL)) != -1)
960 {
961 switch (ch)
962 {
963 case 'n':
964 dry_run = true;
965 verbose = true;
966 break;
967
968 case 'v':
969 verbose = true;
970 break;
971
972 case '?':
973 default:
974 return usage();
975 }
976 }
977 argc -= optind;
978 argv += optind;
979
980 if (argc == 0)
981 return usage();
982
983
984 /*
985 * VBoxNetAdpCtl add
986 */
987 if (strcmp(argv[0], "add") == 0)
988 {
989 if (argc > 1) /* extraneous args */
990 return usage();
991
992 /* Create a new interface, print its name. */
993 *szAdapterName = '\0';
994 rc = g_adapter.add(szAdapterName);
995 if (rc == EXIT_SUCCESS)
996 puts(szAdapterName);
997
998 return rc;
999 }
1000
1001
1002 /*
1003 * All other variants are of the form:
1004 * VBoxNetAdpCtl if0 ...action...
1005 */
1006 const char * const ifname = argv[0];
1007 const char * const action = argv[1];
1008 if (argc < 2)
1009 return usage();
1010
1011
1012#ifdef RT_OS_LINUX
1013 /*
1014 * VBoxNetAdpCtl iface42 speed
1015 *
1016 * This ugly hack is needed for retrieving the link speed on
1017 * pre-2.6.33 kernels (see @bugref{6345}).
1018 *
1019 * This variant is used with any interface, not just host-only.
1020 */
1021 if (strcmp(action, "speed") == 0)
1022 {
1023 if (argc > 2) /* extraneous args */
1024 return usage();
1025
1026 if (strlen(ifname) >= IFNAMSIZ)
1027 {
1028 fprintf(stderr, "Interface name too long\n");
1029 return EXIT_FAILURE;
1030 }
1031
1032 unsigned uSpeed = 0;
1033 rc = g_adapter.getSpeed(ifname, &uSpeed);
1034 if (rc == EXIT_SUCCESS)
1035 printf("%u", uSpeed);
1036
1037 return rc;
1038 }
1039#endif /* RT_OS_LINUX */
1040
1041
1042 /*
1043 * The rest of the actions only operate on host-only interfaces.
1044 */
1045 /** @todo Why the code below uses both ifname and szAdapterName? */
1046 rc = g_adapter.checkName(ifname, szAdapterName, sizeof(szAdapterName));
1047 if (rc != EXIT_SUCCESS)
1048 return rc;
1049
1050
1051 /*
1052 * VBoxNetAdpCtl vboxnetN remove
1053 */
1054 if (strcmp(action, "remove") == 0)
1055 {
1056 if (argc > 2) /* extraneous args */
1057 return usage();
1058
1059 /* Remove an existing interface */
1060 return g_adapter.remove(ifname);
1061 }
1062
1063 /*
1064 * VBoxNetAdpCtl vboxnetN add
1065 */
1066 if (strcmp(action, "add") == 0)
1067 {
1068 if (argc > 2) /* extraneous args */
1069 return usage();
1070
1071 /* Create an interface with the given name, print its name. */
1072 rc = g_adapter.add(szAdapterName);
1073 if (rc == EXIT_SUCCESS)
1074 puts(szAdapterName);
1075
1076 return rc;
1077 }
1078
1079
1080 /*
1081 * The rest of the actions are of the form
1082 * VBoxNetAdpCtl vboxnetN $addr [...]
1083 *
1084 * Use the argument after the address to select the action.
1085 */
1086 /** @todo Do early verification of addr format here? */
1087 const char * const addr = argv[1];
1088 const char * const keyword = argv[2];
1089
1090 GlobalNetworkPermissionsConfig config;
1091
1092 /*
1093 * VBoxNetAdpCtl vboxnetN 1.2.3.4
1094 */
1095 if (keyword == NULL)
1096 {
1097 if (config.forbids(addr))
1098 {
1099 fprintf(stderr, "Error: permission denied\n");
1100 return -VERR_ACCESS_DENIED;
1101 }
1102
1103 return cmd.set(ifname, addr);
1104 }
1105
1106 /*
1107 * VBoxNetAdpCtl vboxnetN 1.2.3.4 netmask 255.255.255.0
1108 */
1109 if (strcmp(keyword, "netmask") == 0)
1110 {
1111 if (argc != 4) /* too few or too many args */
1112 return usage();
1113
1114 const char * const mask = argv[3];
1115 if (config.forbids(addr, mask))
1116 {
1117 fprintf(stderr, "Error: permission denied\n");
1118 return -VERR_ACCESS_DENIED;
1119 }
1120
1121 return cmd.set(ifname, addr, mask);
1122 }
1123
1124 /*
1125 * VBoxNetAdpCtl vboxnetN 1.2.3.4 remove
1126 */
1127 if (strcmp(keyword, "remove") == 0)
1128 {
1129 if (argc > 3) /* extraneous args */
1130 return usage();
1131
1132 return cmd.remove(ifname, addr);
1133 }
1134
1135 return usage();
1136}
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