VirtualBox

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

Last change on this file since 107044 was 106061, checked in by vboxsync, 2 months ago

Copyright year updates by scm.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette