VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/NATNetworkImpl.cpp@ 98351

Last change on this file since 98351 was 98292, checked in by vboxsync, 23 months ago

Main/src-server: rc -> hrc/vrc. Enabled scm rc checks. bugref:10223

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.8 KB
Line 
1/* $Id: NATNetworkImpl.cpp 98292 2023-01-25 01:14:53Z vboxsync $ */
2/** @file
3 * INATNetwork implementation.
4 */
5
6/*
7 * Copyright (C) 2013-2023 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#define LOG_GROUP LOG_GROUP_MAIN_NATNETWORK
29#include "NetworkServiceRunner.h"
30#include "DHCPServerImpl.h"
31#include "NATNetworkImpl.h"
32#include "AutoCaller.h"
33
34#include <iprt/asm.h>
35#include <iprt/cpp/utils.h>
36#include <iprt/net.h>
37#include <iprt/cidr.h>
38#include <iprt/net.h>
39#include <VBox/com/array.h>
40#include <VBox/com/ptr.h>
41#include <VBox/settings.h>
42
43#include "EventImpl.h"
44#include "LoggingNew.h"
45
46#include "VirtualBoxImpl.h"
47#include <algorithm>
48#include <list>
49
50#ifndef RT_OS_WINDOWS
51# include <netinet/in.h>
52#else
53# define IN_LOOPBACKNET 127
54#endif
55
56
57// constructor / destructor
58/////////////////////////////////////////////////////////////////////////////
59struct NATNetwork::Data
60{
61 Data()
62 : pVirtualBox(NULL)
63 , offGateway(0)
64 , offDhcp(0)
65 {
66 }
67 virtual ~Data(){}
68 const ComObjPtr<EventSource> pEventSource;
69#ifdef VBOX_WITH_NAT_SERVICE
70 NATNetworkServiceRunner NATRunner;
71 ComObjPtr<IDHCPServer> dhcpServer;
72#endif
73 /** weak VirtualBox parent */
74 VirtualBox * const pVirtualBox;
75
76 /** NATNetwork settings */
77 settings::NATNetwork s;
78
79 com::Utf8Str IPv4Gateway;
80 com::Utf8Str IPv4NetworkMask;
81 com::Utf8Str IPv4DhcpServer;
82 com::Utf8Str IPv4DhcpServerLowerIp;
83 com::Utf8Str IPv4DhcpServerUpperIp;
84
85 uint32_t offGateway;
86 uint32_t offDhcp;
87
88 void recalculatePortForwarding(const RTNETADDRIPV4 &AddrNew, const RTNETADDRIPV4 &MaskNew);
89};
90
91
92NATNetwork::NATNetwork()
93 : m(NULL)
94{
95}
96
97
98NATNetwork::~NATNetwork()
99{
100}
101
102
103HRESULT NATNetwork::FinalConstruct()
104{
105 return BaseFinalConstruct();
106}
107
108
109void NATNetwork::FinalRelease()
110{
111 uninit();
112
113 BaseFinalRelease();
114}
115
116
117void NATNetwork::uninit()
118{
119 /* Enclose the state transition Ready->InUninit->NotReady */
120 AutoUninitSpan autoUninitSpan(this);
121 if (autoUninitSpan.uninitDone())
122 return;
123 unconst(m->pVirtualBox) = NULL;
124 delete m;
125 m = NULL;
126}
127
128HRESULT NATNetwork::init(VirtualBox *aVirtualBox, com::Utf8Str aName)
129{
130 AutoInitSpan autoInitSpan(this);
131 AssertReturn(autoInitSpan.isOk(), E_FAIL);
132
133 m = new Data();
134 /* share VirtualBox weakly */
135 unconst(m->pVirtualBox) = aVirtualBox;
136 m->s.strNetworkName = aName;
137 m->s.strIPv4NetworkCidr = "10.0.2.0/24";
138 m->offGateway = 1;
139 i_recalculateIPv6Prefix(); /* set m->strIPv6Prefix based on IPv4 */
140
141 settings::NATHostLoopbackOffset off;
142 off.strLoopbackHostAddress = "127.0.0.1";
143 off.u32Offset = (uint32_t)2;
144 m->s.llHostLoopbackOffsetList.push_back(off);
145
146 i_recalculateIpv4AddressAssignments();
147
148 HRESULT hrc = unconst(m->pEventSource).createObject();
149 if (FAILED(hrc)) throw hrc;
150
151 hrc = m->pEventSource->init();
152 if (FAILED(hrc)) throw hrc;
153
154 /* Confirm a successful initialization */
155 autoInitSpan.setSucceeded();
156
157 return S_OK;
158}
159
160
161HRESULT NATNetwork::setErrorBusy()
162{
163 return setError(E_FAIL,
164 tr("Unable to change settings"
165 " while NATNetwork instance is running"));
166}
167
168
169HRESULT NATNetwork::i_loadSettings(const settings::NATNetwork &data)
170{
171 AutoCaller autoCaller(this);
172 AssertComRCReturnRC(autoCaller.hrc());
173
174 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
175 m->s = data;
176 if ( m->s.strIPv6Prefix.isEmpty()
177 /* also clean up bogus old default */
178 || m->s.strIPv6Prefix == "fe80::/64")
179 i_recalculateIPv6Prefix(); /* set m->strIPv6Prefix based on IPv4 */
180 i_recalculateIpv4AddressAssignments();
181
182 return S_OK;
183}
184
185HRESULT NATNetwork::i_saveSettings(settings::NATNetwork &data)
186{
187 AutoCaller autoCaller(this);
188 if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
189
190 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
191 AssertReturn(!m->s.strNetworkName.isEmpty(), E_FAIL);
192 data = m->s;
193
194 m->pVirtualBox->i_onNATNetworkSetting(m->s.strNetworkName,
195 m->s.fEnabled,
196 m->s.strIPv4NetworkCidr,
197 m->IPv4Gateway,
198 m->s.fAdvertiseDefaultIPv6Route,
199 m->s.fNeedDhcpServer);
200
201 /* Notify listeners listening on this network only */
202 ::FireNATNetworkSettingEvent(m->pEventSource,
203 m->s.strNetworkName,
204 m->s.fEnabled,
205 m->s.strIPv4NetworkCidr,
206 m->IPv4Gateway,
207 m->s.fAdvertiseDefaultIPv6Route,
208 m->s.fNeedDhcpServer);
209
210 return S_OK;
211}
212
213HRESULT NATNetwork::getEventSource(ComPtr<IEventSource> &aEventSource)
214{
215 /* event source is const, no need to lock */
216 m->pEventSource.queryInterfaceTo(aEventSource.asOutParam());
217 return S_OK;
218}
219
220HRESULT NATNetwork::getNetworkName(com::Utf8Str &aNetworkName)
221{
222 AssertReturn(!m->s.strNetworkName.isEmpty(), E_FAIL);
223 aNetworkName = m->s.strNetworkName;
224 return S_OK;
225}
226
227HRESULT NATNetwork::setNetworkName(const com::Utf8Str &aNetworkName)
228{
229 if (aNetworkName.isEmpty())
230 return setError(E_INVALIDARG,
231 tr("Network name cannot be empty"));
232
233 {
234 AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
235 if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
236 return setErrorBusy();
237
238 /** @todo r=uwe who ensures there's no other network with that name? */
239
240 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
241 if (aNetworkName == m->s.strNetworkName)
242 return S_OK;
243
244 m->s.strNetworkName = aNetworkName;
245 }
246
247
248 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
249 HRESULT hrc = m->pVirtualBox->i_saveSettings();
250 ComAssertComRCRetRC(hrc);
251
252 return S_OK;
253}
254
255HRESULT NATNetwork::getEnabled(BOOL *aEnabled)
256{
257 *aEnabled = m->s.fEnabled;
258
259 i_recalculateIpv4AddressAssignments();
260 return S_OK;
261}
262
263HRESULT NATNetwork::setEnabled(const BOOL aEnabled)
264{
265 {
266 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
267 if (RT_BOOL(aEnabled) == m->s.fEnabled)
268 return S_OK;
269 m->s.fEnabled = RT_BOOL(aEnabled);
270 }
271
272 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
273 HRESULT hrc = m->pVirtualBox->i_saveSettings();
274 ComAssertComRCRetRC(hrc);
275 return S_OK;
276}
277
278HRESULT NATNetwork::getGateway(com::Utf8Str &aIPv4Gateway)
279{
280 aIPv4Gateway = m->IPv4Gateway;
281 return S_OK;
282}
283
284HRESULT NATNetwork::getNetwork(com::Utf8Str &aNetwork)
285{
286 aNetwork = m->s.strIPv4NetworkCidr;
287 return S_OK;
288}
289
290
291HRESULT NATNetwork::setNetwork(const com::Utf8Str &aIPv4NetworkCidr)
292{
293 RTNETADDRIPV4 Net;
294 int iPrefix;
295 int vrc = RTNetStrToIPv4Cidr(aIPv4NetworkCidr.c_str(), &Net, &iPrefix);
296 if (RT_FAILURE(vrc))
297 return setErrorBoth(E_FAIL, vrc, tr("%s is not a valid IPv4 CIDR notation"), aIPv4NetworkCidr.c_str());
298
299 /*
300 * /32 is a single address, not a network, /31 is the degenerate
301 * point-to-point case, so reject these. Larger values and
302 * negative values are already treated as errors by the
303 * conversion.
304 */
305 if (iPrefix > 30)
306 return setError(E_FAIL, tr("%s network is too small"), aIPv4NetworkCidr.c_str());
307
308 if (iPrefix == 0)
309 return setError(E_FAIL, tr("%s specifies zero prefix"), aIPv4NetworkCidr.c_str());
310
311 RTNETADDRIPV4 Mask;
312 vrc = RTNetPrefixToMaskIPv4(iPrefix, &Mask);
313 AssertRCReturn(vrc, setErrorBoth(E_FAIL, vrc, tr("%s: internal error: failed to convert prefix %d to netmask: %Rrc"),
314 aIPv4NetworkCidr.c_str(), iPrefix, vrc));
315
316 if ((Net.u & ~Mask.u) != 0)
317 return setError(E_FAIL, tr("%s: the specified address is longer than the specified prefix"),
318 aIPv4NetworkCidr.c_str());
319
320 /** @todo r=uwe Check the address is unicast, not a loopback, etc. */
321
322 /* normalized CIDR notation */
323 com::Utf8StrFmt strCidr("%RTnaipv4/%d", Net.u, iPrefix);
324
325 {
326 AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
327 if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
328 return setErrorBusy();
329
330 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
331
332 if (m->s.strIPv4NetworkCidr == strCidr)
333 return S_OK;
334
335 m->recalculatePortForwarding(Net, Mask);
336
337 m->s.strIPv4NetworkCidr = strCidr;
338 i_recalculateIpv4AddressAssignments();
339 }
340
341 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
342 HRESULT hrc = m->pVirtualBox->i_saveSettings();
343 ComAssertComRCRetRC(hrc);
344 return S_OK;
345}
346
347
348/**
349 * Do best effort attempt at converting existing port forwarding rules
350 * from the old prefix to the new one. This might not be possible if
351 * the new prefix is longer (i.e. the network is smaller) or if a rule
352 * lists destination not from the network (though that rule wouldn't
353 * be terribly useful, at least currently).
354 */
355void NATNetwork::Data::recalculatePortForwarding(const RTNETADDRIPV4 &NetNew,
356 const RTNETADDRIPV4 &MaskNew)
357{
358 if (s.mapPortForwardRules4.empty())
359 return; /* nothing to do */
360
361 RTNETADDRIPV4 NetOld;
362 int iPrefixOld;
363 int vrc = RTNetStrToIPv4Cidr(s.strIPv4NetworkCidr.c_str(), &NetOld, &iPrefixOld);
364 if (RT_FAILURE(vrc))
365 return;
366
367 RTNETADDRIPV4 MaskOld;
368 vrc = RTNetPrefixToMaskIPv4(iPrefixOld, &MaskOld);
369 if (RT_FAILURE(vrc))
370 return;
371
372 for (settings::NATRulesMap::iterator it = s.mapPortForwardRules4.begin();
373 it != s.mapPortForwardRules4.end();
374 ++it)
375 {
376 settings::NATRule &rule = it->second;
377
378 /* parse the old destination address */
379 RTNETADDRIPV4 AddrOld;
380 vrc = RTNetStrToIPv4Addr(rule.strGuestIP.c_str(), &AddrOld);
381 if (RT_FAILURE(vrc))
382 continue;
383
384 /* is it in the old network? (likely) */
385 if ((AddrOld.u & MaskOld.u) != NetOld.u)
386 continue;
387
388 uint32_t u32Host = (AddrOld.u & ~MaskOld.u);
389
390 /* does it fit into the new network? */
391 if ((u32Host & MaskNew.u) != 0)
392 continue;
393
394 rule.strGuestIP.printf("%RTnaipv4", NetNew.u | u32Host);
395 }
396}
397
398
399HRESULT NATNetwork::getIPv6Enabled(BOOL *aIPv6Enabled)
400{
401 *aIPv6Enabled = m->s.fIPv6Enabled;
402
403 return S_OK;
404}
405
406
407HRESULT NATNetwork::setIPv6Enabled(const BOOL aIPv6Enabled)
408{
409 {
410 AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
411 if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
412 return setErrorBusy();
413
414 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
415
416 if (RT_BOOL(aIPv6Enabled) == m->s.fIPv6Enabled)
417 return S_OK;
418
419 /*
420 * If we are enabling ipv6 and the prefix is not set, provide
421 * the default based on ipv4.
422 */
423 if (aIPv6Enabled && m->s.strIPv6Prefix.isEmpty())
424 i_recalculateIPv6Prefix();
425
426 m->s.fIPv6Enabled = RT_BOOL(aIPv6Enabled);
427 }
428
429 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
430 HRESULT hrc = m->pVirtualBox->i_saveSettings();
431 ComAssertComRCRetRC(hrc);
432
433 return S_OK;
434}
435
436
437HRESULT NATNetwork::getIPv6Prefix(com::Utf8Str &aIPv6Prefix)
438{
439 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
440
441 aIPv6Prefix = m->s.strIPv6Prefix;
442 return S_OK;
443}
444
445HRESULT NATNetwork::setIPv6Prefix(const com::Utf8Str &aIPv6Prefix)
446{
447 HRESULT hrc;
448 int vrc;
449
450 /* Since we store it in text form, use canonical representation */
451 com::Utf8Str strNormalizedIPv6Prefix;
452
453 const char *pcsz = RTStrStripL(aIPv6Prefix.c_str());
454 if (*pcsz != '\0') /* verify it first if not empty/blank */
455 {
456 RTNETADDRIPV6 Net6;
457 int iPrefixLength;
458 vrc = RTNetStrToIPv6Cidr(aIPv6Prefix.c_str(), &Net6, &iPrefixLength);
459 if (RT_FAILURE(vrc))
460 return setError(E_INVALIDARG, tr("%s is not a valid IPv6 prefix"), aIPv6Prefix.c_str());
461
462 /* Accept both addr:: and addr::/64 */
463 if (iPrefixLength == 128) /* no length was specified after the address? */
464 iPrefixLength = 64; /* take it to mean /64 which we require anyway */
465 else if (iPrefixLength != 64)
466 return setError(E_INVALIDARG, tr("Invalid IPv6 prefix length %d, must be 64"), iPrefixLength);
467
468 /* Verify the address is unicast. */
469 if ( (Net6.au8[0] & 0xe0) != 0x20 /* global 2000::/3 */
470 && (Net6.au8[0] & 0xfe) != 0xfc) /* local fc00::/7 */
471 return setError(E_INVALIDARG, tr("IPv6 prefix %RTnaipv6 is not unicast"), &Net6);
472
473 /* Verify the interfaces ID part is zero */
474 if (Net6.au64[1] != 0)
475 return setError(E_INVALIDARG, tr("Non-zero bits in the interface ID part of the IPv6 prefix %RTnaipv6/64"), &Net6);
476
477 vrc = strNormalizedIPv6Prefix.printfNoThrow("%RTnaipv6/64", &Net6);
478 if (RT_FAILURE(vrc))
479 {
480 if (vrc == VERR_NO_MEMORY)
481 return setError(E_OUTOFMEMORY);
482 return setError(E_FAIL, tr("Internal error"));
483 }
484 }
485
486 {
487 AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
488 if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
489 return setErrorBusy();
490
491 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
492
493 if (strNormalizedIPv6Prefix == m->s.strIPv6Prefix)
494 return S_OK;
495
496 /* only allow prefix to be empty if IPv6 is disabled */
497 if (strNormalizedIPv6Prefix.isEmpty() && m->s.fIPv6Enabled)
498 return setError(E_FAIL, tr("Setting an empty IPv6 prefix when IPv6 is enabled"));
499
500 /**
501 * @todo
502 * silently ignore network IPv6 prefix update.
503 * todo: see similar todo in NATNetwork::COMSETTER(Network)(IN_BSTR)
504 */
505 if (!m->s.mapPortForwardRules6.empty())
506 return S_OK;
507
508 m->s.strIPv6Prefix = strNormalizedIPv6Prefix;
509 }
510
511 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
512 hrc = m->pVirtualBox->i_saveSettings();
513 ComAssertComRCRetRC(hrc);
514
515 return S_OK;
516}
517
518
519HRESULT NATNetwork::getAdvertiseDefaultIPv6RouteEnabled(BOOL *aAdvertiseDefaultIPv6Route)
520{
521 *aAdvertiseDefaultIPv6Route = m->s.fAdvertiseDefaultIPv6Route;
522
523 return S_OK;
524}
525
526
527HRESULT NATNetwork::setAdvertiseDefaultIPv6RouteEnabled(const BOOL aAdvertiseDefaultIPv6Route)
528{
529 {
530 AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
531 if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
532 return setErrorBusy();
533
534 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
535
536 if (RT_BOOL(aAdvertiseDefaultIPv6Route) == m->s.fAdvertiseDefaultIPv6Route)
537 return S_OK;
538
539 m->s.fAdvertiseDefaultIPv6Route = RT_BOOL(aAdvertiseDefaultIPv6Route);
540
541 }
542
543 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
544 HRESULT hrc = m->pVirtualBox->i_saveSettings();
545 ComAssertComRCRetRC(hrc);
546
547 return S_OK;
548}
549
550
551HRESULT NATNetwork::getNeedDhcpServer(BOOL *aNeedDhcpServer)
552{
553 *aNeedDhcpServer = m->s.fNeedDhcpServer;
554
555 return S_OK;
556}
557
558HRESULT NATNetwork::setNeedDhcpServer(const BOOL aNeedDhcpServer)
559{
560 {
561 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
562
563 if (RT_BOOL(aNeedDhcpServer) == m->s.fNeedDhcpServer)
564 return S_OK;
565
566 m->s.fNeedDhcpServer = RT_BOOL(aNeedDhcpServer);
567
568 i_recalculateIpv4AddressAssignments();
569
570 }
571
572 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
573 HRESULT hrc = m->pVirtualBox->i_saveSettings();
574 ComAssertComRCRetRC(hrc);
575
576 return S_OK;
577}
578
579HRESULT NATNetwork::getLocalMappings(std::vector<com::Utf8Str> &aLocalMappings)
580{
581 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
582
583 aLocalMappings.resize(m->s.llHostLoopbackOffsetList.size());
584 size_t i = 0;
585 for (settings::NATLoopbackOffsetList::const_iterator it = m->s.llHostLoopbackOffsetList.begin();
586 it != m->s.llHostLoopbackOffsetList.end(); ++it, ++i)
587 {
588 aLocalMappings[i] = Utf8StrFmt("%s=%d",
589 (*it).strLoopbackHostAddress.c_str(),
590 (*it).u32Offset);
591 }
592
593 return S_OK;
594}
595
596HRESULT NATNetwork::addLocalMapping(const com::Utf8Str &aHostId, LONG aOffset)
597{
598 RTNETADDRIPV4 addr;
599 int vrc = RTNetStrToIPv4Addr(Utf8Str(aHostId).c_str(), &addr);
600 if (RT_FAILURE(vrc))
601 return E_INVALIDARG;
602
603 /* check against 127/8 */
604 if ((RT_N2H_U32(addr.u) >> IN_CLASSA_NSHIFT) != IN_LOOPBACKNET)
605 return E_INVALIDARG;
606
607 /* check against networkid vs network mask */
608 RTNETADDRIPV4 net, mask;
609 vrc = RTCidrStrToIPv4(Utf8Str(m->s.strIPv4NetworkCidr).c_str(), &net, &mask);
610 if (RT_FAILURE(vrc))
611 return E_INVALIDARG;
612
613 if (((net.u + (uint32_t)aOffset) & mask.u) != net.u)
614 return E_INVALIDARG;
615
616 settings::NATLoopbackOffsetList::iterator it;
617
618 it = std::find(m->s.llHostLoopbackOffsetList.begin(),
619 m->s.llHostLoopbackOffsetList.end(),
620 aHostId);
621 if (it != m->s.llHostLoopbackOffsetList.end())
622 {
623 if (aOffset == 0) /* erase */
624 m->s.llHostLoopbackOffsetList.erase(it, it);
625 else /* modify */
626 {
627 settings::NATLoopbackOffsetList::iterator it1;
628 it1 = std::find(m->s.llHostLoopbackOffsetList.begin(),
629 m->s.llHostLoopbackOffsetList.end(),
630 (uint32_t)aOffset);
631 if (it1 != m->s.llHostLoopbackOffsetList.end())
632 return E_INVALIDARG; /* this offset is already registered. */
633
634 (*it).u32Offset = (uint32_t)aOffset;
635 }
636
637 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
638 return m->pVirtualBox->i_saveSettings();
639 }
640
641 /* injection */
642 it = std::find(m->s.llHostLoopbackOffsetList.begin(),
643 m->s.llHostLoopbackOffsetList.end(),
644 (uint32_t)aOffset);
645
646 if (it != m->s.llHostLoopbackOffsetList.end())
647 return E_INVALIDARG; /* offset is already registered. */
648
649 settings::NATHostLoopbackOffset off;
650 off.strLoopbackHostAddress = aHostId;
651 off.u32Offset = (uint32_t)aOffset;
652 m->s.llHostLoopbackOffsetList.push_back(off);
653
654 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
655 return m->pVirtualBox->i_saveSettings();
656}
657
658
659HRESULT NATNetwork::getLoopbackIp6(LONG *aLoopbackIp6)
660{
661 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
662
663 *aLoopbackIp6 = (LONG)m->s.u32HostLoopback6Offset;
664 return S_OK;
665}
666
667
668HRESULT NATNetwork::setLoopbackIp6(LONG aLoopbackIp6)
669{
670 {
671 AutoReadLock alockNatNetList(m->pVirtualBox->i_getNatNetLock() COMMA_LOCKVAL_SRC_POS);
672 if (m->pVirtualBox->i_isNatNetStarted(m->s.strNetworkName))
673 return setErrorBusy();
674
675 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
676
677 if (aLoopbackIp6 < 0)
678 return E_INVALIDARG;
679
680 if (static_cast<uint32_t>(aLoopbackIp6) == m->s.u32HostLoopback6Offset)
681 return S_OK;
682
683 m->s.u32HostLoopback6Offset = (uint32_t)aLoopbackIp6;
684 }
685
686 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
687 return m->pVirtualBox->i_saveSettings();
688}
689
690
691HRESULT NATNetwork::getPortForwardRules4(std::vector<com::Utf8Str> &aPortForwardRules4)
692{
693 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
694 i_getPortForwardRulesFromMap(aPortForwardRules4,
695 m->s.mapPortForwardRules4);
696 return S_OK;
697}
698
699HRESULT NATNetwork::getPortForwardRules6(std::vector<com::Utf8Str> &aPortForwardRules6)
700{
701 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
702 i_getPortForwardRulesFromMap(aPortForwardRules6,
703 m->s.mapPortForwardRules6);
704 return S_OK;
705}
706
707HRESULT NATNetwork::addPortForwardRule(BOOL aIsIpv6,
708 const com::Utf8Str &aPortForwardRuleName,
709 NATProtocol_T aProto,
710 const com::Utf8Str &aHostIp,
711 USHORT aHostPort,
712 const com::Utf8Str &aGuestIp,
713 USHORT aGuestPort)
714{
715 {
716 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
717 Utf8Str name = aPortForwardRuleName;
718 Utf8Str proto;
719 settings::NATRule r;
720 settings::NATRulesMap &mapRules = aIsIpv6 ? m->s.mapPortForwardRules6 : m->s.mapPortForwardRules4;
721 switch (aProto)
722 {
723 case NATProtocol_TCP:
724 proto = "tcp";
725 break;
726 case NATProtocol_UDP:
727 proto = "udp";
728 break;
729 default:
730 return E_INVALIDARG;
731 }
732 if (name.isEmpty())
733 name = Utf8StrFmt("%s_[%s]%%%d_[%s]%%%d", proto.c_str(),
734 aHostIp.c_str(), aHostPort,
735 aGuestIp.c_str(), aGuestPort);
736
737 for (settings::NATRulesMap::iterator it = mapRules.begin(); it != mapRules.end(); ++it)
738 {
739 r = it->second;
740 if (it->first == name)
741 return setError(E_INVALIDARG,
742 tr("A NAT rule of this name already exists"));
743 if ( r.strHostIP == aHostIp
744 && r.u16HostPort == aHostPort
745 && r.proto == aProto)
746 return setError(E_INVALIDARG,
747 tr("A NAT rule for this host port and this host IP already exists"));
748 }
749
750 r.strName = name.c_str();
751 r.proto = aProto;
752 r.strHostIP = aHostIp;
753 r.u16HostPort = aHostPort;
754 r.strGuestIP = aGuestIp;
755 r.u16GuestPort = aGuestPort;
756 mapRules.insert(std::make_pair(name, r));
757 }
758 {
759 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
760 HRESULT hrc = m->pVirtualBox->i_saveSettings();
761 ComAssertComRCRetRC(hrc);
762 }
763
764 m->pVirtualBox->i_onNATNetworkPortForward(m->s.strNetworkName, TRUE, aIsIpv6,
765 aPortForwardRuleName, aProto,
766 aHostIp, aHostPort,
767 aGuestIp, aGuestPort);
768
769 /* Notify listeners listening on this network only */
770 ::FireNATNetworkPortForwardEvent(m->pEventSource, m->s.strNetworkName, TRUE,
771 aIsIpv6, aPortForwardRuleName, aProto,
772 aHostIp, aHostPort,
773 aGuestIp, aGuestPort);
774
775 return S_OK;
776}
777
778HRESULT NATNetwork::removePortForwardRule(BOOL aIsIpv6, const com::Utf8Str &aPortForwardRuleName)
779{
780 Utf8Str strHostIP;
781 Utf8Str strGuestIP;
782 uint16_t u16HostPort;
783 uint16_t u16GuestPort;
784 NATProtocol_T proto;
785
786 {
787 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
788 settings::NATRulesMap &mapRules = aIsIpv6 ? m->s.mapPortForwardRules6 : m->s.mapPortForwardRules4;
789 settings::NATRulesMap::iterator it = mapRules.find(aPortForwardRuleName);
790
791 if (it == mapRules.end())
792 return E_INVALIDARG;
793
794 strHostIP = it->second.strHostIP;
795 strGuestIP = it->second.strGuestIP;
796 u16HostPort = it->second.u16HostPort;
797 u16GuestPort = it->second.u16GuestPort;
798 proto = it->second.proto;
799
800 mapRules.erase(it);
801 }
802
803 {
804 AutoWriteLock vboxLock(m->pVirtualBox COMMA_LOCKVAL_SRC_POS);
805 HRESULT hrc = m->pVirtualBox->i_saveSettings();
806 ComAssertComRCRetRC(hrc);
807 }
808
809 m->pVirtualBox->i_onNATNetworkPortForward(m->s.strNetworkName, FALSE, aIsIpv6, aPortForwardRuleName, proto,
810 strHostIP, u16HostPort, strGuestIP, u16GuestPort);
811
812 /* Notify listeners listening on this network only */
813 ::FireNATNetworkPortForwardEvent(m->pEventSource, m->s.strNetworkName, FALSE, aIsIpv6, aPortForwardRuleName, proto,
814 strHostIP, u16HostPort, strGuestIP, u16GuestPort);
815 return S_OK;
816}
817
818
819void NATNetwork::i_updateDomainNameOption(ComPtr<IHost> &host)
820{
821 com::Bstr domain;
822 if (FAILED(host->COMGETTER(DomainName)(domain.asOutParam())))
823 LogRel(("NATNetwork: Failed to get host's domain name\n"));
824 ComPtr<IDHCPGlobalConfig> pDHCPConfig;
825 HRESULT hrc = m->dhcpServer->COMGETTER(GlobalConfig)(pDHCPConfig.asOutParam());
826 if (FAILED(hrc))
827 {
828 LogRel(("NATNetwork: Failed to get global DHCP config when updating domain name option with %Rhrc\n", hrc));
829 return;
830 }
831 if (domain.isNotEmpty())
832 {
833 hrc = pDHCPConfig->SetOption(DHCPOption_DomainName, DHCPOptionEncoding_Normal, domain.raw());
834 if (FAILED(hrc))
835 LogRel(("NATNetwork: Failed to add domain name option with %Rhrc\n", hrc));
836 }
837 else
838 pDHCPConfig->RemoveOption(DHCPOption_DomainName);
839}
840
841void NATNetwork::i_updateDomainNameServerOption(ComPtr<IHost> &host)
842{
843 RTNETADDRIPV4 networkid, netmask;
844 int vrc = RTCidrStrToIPv4(m->s.strIPv4NetworkCidr.c_str(), &networkid, &netmask);
845 if (RT_FAILURE(vrc))
846 {
847 LogRel(("NATNetwork: Failed to parse cidr %s with %Rrc\n", m->s.strIPv4NetworkCidr.c_str(), vrc));
848 return;
849 }
850
851 /* XXX: these are returned, surprisingly, in host order */
852 networkid.u = RT_H2N_U32(networkid.u);
853 netmask.u = RT_H2N_U32(netmask.u);
854
855 com::SafeArray<BSTR> nameServers;
856 HRESULT hrc = host->COMGETTER(NameServers)(ComSafeArrayAsOutParam(nameServers));
857 if (FAILED(hrc))
858 {
859 LogRel(("NATNetwork: Failed to get name servers from host with %Rhrc\n", hrc));
860 return;
861 }
862 ComPtr<IDHCPGlobalConfig> pDHCPConfig;
863 hrc = m->dhcpServer->COMGETTER(GlobalConfig)(pDHCPConfig.asOutParam());
864 if (FAILED(hrc))
865 {
866 LogRel(("NATNetwork: Failed to get global DHCP config when updating domain name server option with %Rhrc\n", hrc));
867 return;
868 }
869
870 size_t cAddresses = nameServers.size();
871 if (cAddresses)
872 {
873 RTCList<RTCString> lstServers;
874 /* The following code was copied (and adapted a bit) from VBoxNetDhcp::hostDnsServers */
875 /*
876 * Recent fashion is to run dnsmasq on 127.0.1.1 which we
877 * currently can't map. If that's the only nameserver we've got,
878 * we need to use DNS proxy for VMs to reach it.
879 */
880 bool fUnmappedLoopback = false;
881
882 for (size_t i = 0; i < cAddresses; ++i)
883 {
884 com::Utf8Str strNameServerAddress(nameServers[i]);
885 RTNETADDRIPV4 addr;
886 vrc = RTNetStrToIPv4Addr(strNameServerAddress.c_str(), &addr);
887 if (RT_FAILURE(vrc))
888 {
889 LogRel(("NATNetwork: Failed to parse IP address %s with %Rrc\n", strNameServerAddress.c_str(), vrc));
890 continue;
891 }
892
893 if (addr.u == INADDR_ANY)
894 {
895 /*
896 * This doesn't seem to be very well documented except for
897 * RTFS of res_init.c, but INADDR_ANY is a valid value for
898 * for "nameserver".
899 */
900 addr.u = RT_H2N_U32_C(INADDR_LOOPBACK);
901 }
902
903 if (addr.au8[0] == 127)
904 {
905 settings::NATLoopbackOffsetList::const_iterator it;
906
907 it = std::find(m->s.llHostLoopbackOffsetList.begin(),
908 m->s.llHostLoopbackOffsetList.end(),
909 strNameServerAddress);
910 if (it == m->s.llHostLoopbackOffsetList.end())
911 {
912 fUnmappedLoopback = true;
913 continue;
914 }
915 addr.u = RT_H2N_U32(RT_N2H_U32(networkid.u) + it->u32Offset);
916 }
917 lstServers.append(RTCStringFmt("%RTnaipv4", addr));
918 }
919
920 if (lstServers.isEmpty() && fUnmappedLoopback)
921 lstServers.append(RTCStringFmt("%RTnaipv4", networkid.u | RT_H2N_U32_C(1U))); /* proxy */
922
923 hrc = pDHCPConfig->SetOption(DHCPOption_DomainNameServers, DHCPOptionEncoding_Normal, Bstr(RTCString::join(lstServers, " ")).raw());
924 if (FAILED(hrc))
925 LogRel(("NATNetwork: Failed to add domain name server option '%s' with %Rhrc\n", RTCString::join(lstServers, " ").c_str(), hrc));
926 }
927 else
928 pDHCPConfig->RemoveOption(DHCPOption_DomainNameServers);
929}
930
931void NATNetwork::i_updateDnsOptions()
932{
933 ComPtr<IHost> host;
934 if (SUCCEEDED(m->pVirtualBox->COMGETTER(Host)(host.asOutParam())))
935 {
936 i_updateDomainNameOption(host);
937 i_updateDomainNameServerOption(host);
938 }
939}
940
941
942HRESULT NATNetwork::start()
943{
944#ifdef VBOX_WITH_NAT_SERVICE
945 if (!m->s.fEnabled) return S_OK;
946 AssertReturn(!m->s.strNetworkName.isEmpty(), E_FAIL);
947
948 m->NATRunner.resetArguments();
949 m->NATRunner.addArgPair(NetworkServiceRunner::kpszKeyNetwork, Utf8Str(m->s.strNetworkName).c_str());
950
951 /* No portforwarding rules from command-line, all will be fetched via API */
952
953 if (m->s.fNeedDhcpServer)
954 {
955 /*
956 * Just to as idea... via API (on creation user pass the cidr of network and)
957 * and we calculate it's addreses (mutable?).
958 */
959
960 /*
961 * Configuration and running DHCP server:
962 * 1. find server first createDHCPServer
963 * 2. if return status is E_INVALARG => server already exists just find and start.
964 * 3. if return status neither E_INVALRG nor S_OK => return E_FAIL
965 * 4. if return status S_OK proceed to DHCP server configuration
966 * 5. call setConfiguration() and pass all required parameters
967 * 6. start dhcp server.
968 */
969 HRESULT hrc = m->pVirtualBox->FindDHCPServerByNetworkName(Bstr(m->s.strNetworkName).raw(),
970 m->dhcpServer.asOutParam());
971 switch (hrc)
972 {
973 case E_INVALIDARG:
974 /* server haven't beeen found let create it then */
975 hrc = m->pVirtualBox->CreateDHCPServer(Bstr(m->s.strNetworkName).raw(),
976 m->dhcpServer.asOutParam());
977 if (FAILED(hrc))
978 return E_FAIL;
979 /* breakthrough */
980
981 {
982 LogFunc(("gateway: %s, dhcpserver:%s, dhcplowerip:%s, dhcpupperip:%s\n",
983 m->IPv4Gateway.c_str(),
984 m->IPv4DhcpServer.c_str(),
985 m->IPv4DhcpServerLowerIp.c_str(),
986 m->IPv4DhcpServerUpperIp.c_str()));
987
988 hrc = m->dhcpServer->COMSETTER(Enabled)(true);
989
990 hrc = m->dhcpServer->SetConfiguration(Bstr(m->IPv4DhcpServer).raw(),
991 Bstr(m->IPv4NetworkMask).raw(),
992 Bstr(m->IPv4DhcpServerLowerIp).raw(),
993 Bstr(m->IPv4DhcpServerUpperIp).raw());
994 }
995 case S_OK:
996 break;
997
998 default:
999 return E_FAIL;
1000 }
1001
1002#ifdef VBOX_WITH_DHCPD
1003 i_updateDnsOptions();
1004#endif /* VBOX_WITH_DHCPD */
1005 /* XXX: AddGlobalOption(DhcpOpt_Router,) - enables attachement of DhcpServer to Main (no longer true with VBoxNetDhcpd). */
1006 ComPtr<IDHCPGlobalConfig> pDHCPConfig;
1007 hrc = m->dhcpServer->COMGETTER(GlobalConfig)(pDHCPConfig.asOutParam());
1008 if (FAILED(hrc))
1009 {
1010 LogRel(("NATNetwork: Failed to get global DHCP config when updating IPv4 gateway option with %Rhrc\n", hrc));
1011 m->dhcpServer.setNull();
1012 return E_FAIL;
1013 }
1014 pDHCPConfig->SetOption(DHCPOption_Routers, DHCPOptionEncoding_Normal, Bstr(m->IPv4Gateway).raw());
1015
1016 hrc = m->dhcpServer->Start(Bstr::Empty.raw(), Bstr(TRUNKTYPE_WHATEVER).raw());
1017 if (FAILED(hrc))
1018 {
1019 m->dhcpServer.setNull();
1020 return E_FAIL;
1021 }
1022 }
1023
1024 if (RT_SUCCESS(m->NATRunner.start(false /* KillProcOnStop */)))
1025 {
1026 m->pVirtualBox->i_onNATNetworkStartStop(m->s.strNetworkName, TRUE);
1027 return S_OK;
1028 }
1029 /** @todo missing setError()! */
1030 return E_FAIL;
1031#else
1032 ReturnComNotImplemented();
1033#endif
1034}
1035
1036HRESULT NATNetwork::stop()
1037{
1038#ifdef VBOX_WITH_NAT_SERVICE
1039 m->pVirtualBox->i_onNATNetworkStartStop(m->s.strNetworkName, FALSE);
1040
1041 if (!m->dhcpServer.isNull())
1042 m->dhcpServer->Stop();
1043
1044 if (RT_SUCCESS(m->NATRunner.stop()))
1045 return S_OK;
1046
1047 /** @todo missing setError()! */
1048 return E_FAIL;
1049#else
1050 ReturnComNotImplemented();
1051#endif
1052}
1053
1054
1055void NATNetwork::i_getPortForwardRulesFromMap(std::vector<com::Utf8Str> &aPortForwardRules, settings::NATRulesMap &aRules)
1056{
1057 aPortForwardRules.resize(aRules.size());
1058 size_t i = 0;
1059 for (settings::NATRulesMap::const_iterator it = aRules.begin();
1060 it != aRules.end(); ++it, ++i)
1061 {
1062 settings::NATRule r = it->second;
1063 aPortForwardRules[i] = Utf8StrFmt("%s:%s:[%s]:%d:[%s]:%d",
1064 r.strName.c_str(),
1065 (r.proto == NATProtocol_TCP ? "tcp" : "udp"),
1066 r.strHostIP.c_str(),
1067 r.u16HostPort,
1068 r.strGuestIP.c_str(),
1069 r.u16GuestPort);
1070 }
1071}
1072
1073
1074int NATNetwork::i_findFirstAvailableOffset(ADDRESSLOOKUPTYPE addrType, uint32_t *poff)
1075{
1076 RTNETADDRIPV4 network, netmask;
1077 int vrc = RTCidrStrToIPv4(m->s.strIPv4NetworkCidr.c_str(), &network, &netmask);
1078 AssertRCReturn(vrc, vrc);
1079
1080 uint32_t off;
1081 for (off = 1; off < ~netmask.u; ++off)
1082 {
1083 bool skip = false;
1084 for (settings::NATLoopbackOffsetList::iterator it = m->s.llHostLoopbackOffsetList.begin();
1085 it != m->s.llHostLoopbackOffsetList.end();
1086 ++it)
1087 {
1088 if ((*it).u32Offset == off)
1089 {
1090 skip = true;
1091 break;
1092 }
1093
1094 }
1095
1096 if (skip)
1097 continue;
1098
1099 if (off == m->offGateway)
1100 {
1101 if (addrType == ADDR_GATEWAY)
1102 break;
1103 else
1104 continue;
1105 }
1106
1107 if (off == m->offDhcp)
1108 {
1109 if (addrType == ADDR_DHCP)
1110 break;
1111 else
1112 continue;
1113 }
1114
1115 if (!skip)
1116 break;
1117 }
1118
1119 if (poff)
1120 *poff = off;
1121
1122 return VINF_SUCCESS;
1123}
1124
1125int NATNetwork::i_recalculateIpv4AddressAssignments()
1126{
1127 RTNETADDRIPV4 network, netmask;
1128 int vrc = RTCidrStrToIPv4(m->s.strIPv4NetworkCidr.c_str(), &network, &netmask);
1129 AssertRCReturn(vrc, vrc);
1130
1131 i_findFirstAvailableOffset(ADDR_GATEWAY, &m->offGateway);
1132 if (m->s.fNeedDhcpServer)
1133 i_findFirstAvailableOffset(ADDR_DHCP, &m->offDhcp);
1134
1135 /* I don't remember the reason CIDR calculated on the host. */
1136 RTNETADDRIPV4 gateway = network;
1137 gateway.u += m->offGateway;
1138 gateway.u = RT_H2N_U32(gateway.u);
1139 char szTmpIp[16];
1140 RTStrPrintf(szTmpIp, sizeof(szTmpIp), "%RTnaipv4", gateway);
1141 m->IPv4Gateway = szTmpIp;
1142
1143 if (m->s.fNeedDhcpServer)
1144 {
1145 RTNETADDRIPV4 dhcpserver = network;
1146 dhcpserver.u += m->offDhcp;
1147
1148 /* XXX: adding more services should change the math here */
1149 RTNETADDRIPV4 dhcplowerip = network;
1150 uint32_t offDhcpLowerIp;
1151 i_findFirstAvailableOffset(ADDR_DHCPLOWERIP, &offDhcpLowerIp);
1152 dhcplowerip.u = RT_H2N_U32(dhcplowerip.u + offDhcpLowerIp);
1153
1154 RTNETADDRIPV4 dhcpupperip;
1155 dhcpupperip.u = RT_H2N_U32((network.u | ~netmask.u) - 1);
1156
1157 dhcpserver.u = RT_H2N_U32(dhcpserver.u);
1158 network.u = RT_H2N_U32(network.u);
1159
1160 RTStrPrintf(szTmpIp, sizeof(szTmpIp), "%RTnaipv4", dhcpserver);
1161 m->IPv4DhcpServer = szTmpIp;
1162 RTStrPrintf(szTmpIp, sizeof(szTmpIp), "%RTnaipv4", dhcplowerip);
1163 m->IPv4DhcpServerLowerIp = szTmpIp;
1164 RTStrPrintf(szTmpIp, sizeof(szTmpIp), "%RTnaipv4", dhcpupperip);
1165 m->IPv4DhcpServerUpperIp = szTmpIp;
1166
1167 LogFunc(("network:%RTnaipv4, dhcpserver:%RTnaipv4, dhcplowerip:%RTnaipv4, dhcpupperip:%RTnaipv4\n",
1168 network, dhcpserver, dhcplowerip, dhcpupperip));
1169 }
1170
1171 /* we need IPv4NetworkMask for NAT's gw service start */
1172 netmask.u = RT_H2N_U32(netmask.u);
1173 RTStrPrintf(szTmpIp, 16, "%RTnaipv4", netmask);
1174 m->IPv4NetworkMask = szTmpIp;
1175
1176 LogFlowFunc(("getaway:%RTnaipv4, netmask:%RTnaipv4\n", gateway, netmask));
1177 return VINF_SUCCESS;
1178}
1179
1180
1181int NATNetwork::i_recalculateIPv6Prefix()
1182{
1183 RTNETADDRIPV4 net, mask;
1184 int vrc = RTCidrStrToIPv4(Utf8Str(m->s.strIPv4NetworkCidr).c_str(), &net, &mask);
1185 if (RT_FAILURE(vrc))
1186 return vrc;
1187
1188 net.u = RT_H2N_U32(net.u); /* XXX: fix RTCidrStrToIPv4! */
1189
1190 /*
1191 * [fd17:625c:f037:XXXX::/64] - RFC 4193 (ULA) Locally Assigned
1192 * Global ID where XXXX, 16 bit Subnet ID, are two bytes from the
1193 * middle of the IPv4 address, e.g. :dead: for 10.222.173.1
1194 */
1195 RTNETADDRIPV6 prefix;
1196 RT_ZERO(prefix);
1197
1198 prefix.au8[0] = 0xFD;
1199 prefix.au8[1] = 0x17;
1200
1201 prefix.au8[2] = 0x62;
1202 prefix.au8[3] = 0x5C;
1203
1204 prefix.au8[4] = 0xF0;
1205 prefix.au8[5] = 0x37;
1206
1207 prefix.au8[6] = net.au8[1];
1208 prefix.au8[7] = net.au8[2];
1209
1210 char szBuf[32];
1211 RTStrPrintf(szBuf, sizeof(szBuf), "%RTnaipv6/64", &prefix);
1212
1213 m->s.strIPv6Prefix = szBuf;
1214 return VINF_SUCCESS;
1215}
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