VirtualBox

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

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

VBoxNetAdpCtl: (bugref:8093) Added unicast and loopback checks

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