DrvNAT.cpp revision 55c6dd0c4e495858f1681a45e28e04c791ef066e
/* $Id$ */
/** @file
* DrvNAT - NAT network transport driver.
*/
/*
* Copyright (C) 2006-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DRV_NAT
#define __STDC_LIMIT_MACROS
#define __STDC_CONSTANT_MACROS
#include "slirp/libslirp.h"
#include <iprt/critsect.h>
#include "VBoxDD.h"
#ifndef RT_OS_WINDOWS
# include <unistd.h>
# include <fcntl.h>
# include <poll.h>
# include <errno.h>
#endif
#ifdef RT_OS_FREEBSD
#endif
#include <iprt/semaphore.h>
#ifdef RT_OS_DARWIN
# include <SystemConfiguration/SystemConfiguration.h>
# include <CoreFoundation/CoreFoundation.h>
#endif
#define COUNTERS_INIT
#include "counters.h"
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/**
* @todo: This is a bad hack to prevent freezing the guest during high network
* activity. Windows host only. This needs to be fixed properly.
*/
#define VBOX_NAT_DELAY_HACK
do { \
return PDMDrvHlpVMSetError((pthis)->pDrvIns, (rc), RT_SRC_POS, N_("NAT#%d: configuration query for \""name"\" " #type_name " failed"), \
} while (0)
do { \
if (RT_FAILURE((rc))) \
return PDMDrvHlpVMSetError((pthis)->pDrvIns, (rc), RT_SRC_POS, N_("NAT#%d: configuration query for \""name"\" " #type_name " failed"), \
} while (0)
do { \
return PDMDrvHlpVMSetError((pthis)->pDrvIns, (rc), RT_SRC_POS, N_("NAT#%d: configuration query for \""name"\" " #type_name " failed"), \
} while (0)
do { \
char sz##x[32]; \
if (rc != VERR_CFGM_VALUE_NOT_FOUND) \
} while (0)
do \
{ \
int status = 0; \
} while (0)
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* NAT network transport driver instance data.
*
* @implements PDMINETWORKUP
*/
typedef struct DRVNAT
{
/** The network interface. */
/** The network NAT Engine configureation. */
/** The port we're attached to. */
/** The network config of the port we're attached to. */
/** Pointer to the driver instance. */
/** Link state */
/** NAT state for this instance. */
/** TFTP directory prefix. */
char *pszTFTPPrefix;
/** Boot file name to provide in the DHCP server response. */
char *pszBootFile;
/** tftp server name to provide in the DHCP server response. */
char *pszNextServer;
/** Polling thread. */
/** Queue for NAT-thread-external events. */
/** The guest IP for port-forwarding. */
/** Link state set when the VM is suspended. */
#ifndef RT_OS_WINDOWS
/** The write end of the control pipe. */
/** The read end of the control pipe. */
#else
/** for external notification */
#endif
#include "counters.h"
/** thread delivering packets for receiving by the guest */
/** thread delivering urg packets for receiving by the guest */
/** event to wakeup the guest receive thread */
/** event to wakeup the guest urgent receive thread */
/** Receive Req queue (deliver packets to the guest) */
/** Receive Urgent Req queue (deliver packets to the guest). */
/** makes access to device func RecvAvail and Recv atomical. */
/** Number of in-flight urgent packets. */
/** Number of in-flight regular packets. */
/** Transmit lock taken by BeginXmit and released by EndXmit. */
#ifdef RT_OS_DARWIN
/* Handle of the DNS watcher runloop source. */
#endif
} DRVNAT;
/** Pointer to the NAT driver instance data. */
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
{
return VINF_SUCCESS;
{
}
return VINF_SUCCESS;
}
{
int rc;
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
{
{
}
}
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
static DECLCALLBACK(void) drvNATUrgRecvWorker(PDRVNAT pThis, uint8_t *pu8Buf, int cb, struct mbuf *m)
{
if (RT_SUCCESS(rc))
{
}
else if ( rc != VERR_TIMEOUT
&& rc != VERR_INTERRUPTED)
{
}
{
}
}
{
int rc;
{
if ( RT_FAILURE(rc)
&& ( rc == VERR_TIMEOUT
|| rc == VERR_INTERRUPTED))
goto done_unlocked;
}
if (RT_SUCCESS(rc))
{
}
else if ( rc != VERR_TIMEOUT
&& rc != VERR_INTERRUPTED)
{
}
}
/**
* Frees a S/G buffer allocated by drvNATNetworkUp_AllocBuf.
*
* @param pThis Pointer to the NAT instance.
* @param pSgBuf The S/G buffer to free.
*/
{
if (pSgBuf->pvAllocator)
{
}
{
}
}
/**
* Worker function for drvNATSend().
*
* @param pThis Pointer to the NAT instance.
* @thread NAT
*/
{
{
if (m)
{
/*
* A normal frame.
*/
}
else
{
/*
* GSO frame, need to segment it.
*/
/** @todo Make the NAT engine grok large frames? Could be more efficient... */
#if 0 /* this is for testing PDMNetGsoCarveSegmentQD. */
#endif
{
void *pvSeg;
if (!m)
break;
#if 1
#else
#endif
}
}
}
/** @todo Implement the VERR_TRY_AGAIN drvNATNetworkUp_AllocBuf semantics. */
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnBeginXmit}
*/
{
if (RT_FAILURE(rc))
{
/** @todo Kick the worker thread when we have one... */
rc = VERR_TRY_AGAIN;
}
return rc;
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnAllocBuf}
*/
{
/*
* Drop the incoming frame if the NAT thread isn't running.
*/
{
Log(("drvNATNetowrkUp_AllocBuf: returns VERR_NET_NO_NETWORK\n"));
return VERR_NET_NO_NETWORK;
}
/*
*/
if (!pSgBuf)
return VERR_NO_MEMORY;
if (!pGso)
{
/*
* Drop the frame if it is too big.
*/
if (cbMin >= DRVNAT_MAXFRAMESIZE)
{
Log(("drvNATNetowrkUp_AllocBuf: drops over-sized frame (%u bytes), returns VERR_INVALID_PARAMETER\n",
cbMin));
return VERR_INVALID_PARAMETER;
}
if (!pSgBuf->pvAllocator)
{
return VERR_TRY_AGAIN;
}
}
else
{
/*
* Drop the frame if its segment is too big.
*/
{
Log(("drvNATNetowrkUp_AllocBuf: drops over-sized frame (%u bytes), returns VERR_INVALID_PARAMETER\n",
return VERR_INVALID_PARAMETER;
}
{
return VERR_TRY_AGAIN;
}
}
/*
* Initialize the S/G buffer and return.
*/
#if 0 /* poison */
#endif
return VINF_SUCCESS;
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnFreeBuf}
*/
static DECLCALLBACK(int) drvNATNetworkUp_FreeBuf(PPDMINETWORKUP pInterface, PPDMSCATTERGATHER pSgBuf)
{
return VINF_SUCCESS;
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnSendBuf}
*/
static DECLCALLBACK(int) drvNATNetworkUp_SendBuf(PPDMINETWORKUP pInterface, PPDMSCATTERGATHER pSgBuf, bool fOnWorkerThread)
{
int rc;
{
/* Set an FTM checkpoint as this operation changes the state permanently. */
if (RT_SUCCESS(rc))
{
return VINF_SUCCESS;
}
}
else
rc = VERR_NET_DOWN;
return rc;
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnEndXmit}
*/
{
}
/**
* Get the NAT thread out of poll/WSAWaitForMultipleEvents
*/
{
int rc;
#ifndef RT_OS_WINDOWS
/* kick poll() */
#else
/* kick WSAWaitForMultipleEvents */
#endif
}
/**
* @interface_method_impl{PDMINETWORKUP,pfnSetPromiscuousMode}
*/
static DECLCALLBACK(void) drvNATNetworkUp_SetPromiscuousMode(PPDMINETWORKUP pInterface, bool fPromiscuous)
{
/* nothing to do */
}
/**
* Worker function for drvNATNetworkUp_NotifyLinkChanged().
* @thread "NAT" thread.
*/
{
switch (enmLinkState)
{
case PDMNETWORKLINKSTATE_UP:
LogRel(("NAT: link up\n"));
break;
case PDMNETWORKLINKSTATE_DOWN:
LogRel(("NAT: link down\n"));
break;
default:
}
}
/**
* Notification on link status changes.
*
* @param pInterface Pointer to the interface structure containing the called function pointer.
* @param enmLinkState The new link state.
* @thread EMT
*/
static DECLCALLBACK(void) drvNATNetworkUp_NotifyLinkChanged(PPDMINETWORKUP pInterface, PDMNETWORKLINKSTATE enmLinkState)
{
/* Don't queue new requests when the NAT thread is about to stop.
* But the VM could also be paused. So memorize the desired state. */
{
return;
}
{
}
else
}
{
if (pThis->pIAboveConfig)
if (fRemove)
else
}
DECLCALLBACK(int) drvNATNetworkNatConfig_RedirectRuleCommand(PPDMINETWORKNATCONFIG pInterface, bool fRemove,
{
LogFlowFunc(("fRemove=%d, fUdp=%d, pHostIp=%s, u16HostPort=%u, pGuestIp=%s, u16GuestPort=%u\n",
u16GuestPort));
{
}
else
return rc;
}
/**
* NAT thread handling the slirp stuff.
*
* The slirp implementation is single-threaded so we execute this enginre in a
* dedicated thread. We take care that this thread does not become the
* bottleneck: If the guest wants to send, a request is enqueued into the
* hSlirpReqQueue and handled asynchronously by this thread. If this thread
* wants to deliver packets to the guest, it enqueues a request into
* hRecvReqQueue which is later handled by the Recv thread.
*/
{
int nFDs = -1;
#ifdef RT_OS_WINDOWS
unsigned int cBreak = 0;
#else /* RT_OS_WINDOWS */
unsigned int cPollNegRet = 0;
#endif /* !RT_OS_WINDOWS */
return VINF_SUCCESS;
/*
* Polling loop.
*/
{
/*
*/
#ifndef RT_OS_WINDOWS
/* allocation for all sockets + Management pipe */
struct pollfd *polls = (struct pollfd *)RTMemAlloc((1 + nFDs) * sizeof(struct pollfd) + sizeof(uint32_t));
return VERR_NO_MEMORY;
/* don't pass the management pipe */
/* POLLRDBAND usually doesn't used on Linux but seems used on Solaris */
if (cChangedFDs < 0)
{
{
Log2(("NAT: signal was caught while sleep on poll\n"));
/* No error, just process all outstanding requests but don't wait */
cChangedFDs = 0;
}
else if (cPollNegRet++ > 128)
{
cPollNegRet = 0;
}
}
if (cChangedFDs >= 0)
{
{
/* drain the pipe
*
* Note! drvNATSend decoupled so we don't know how many times
* device's thread sends before we've entered multiplex,
* so to avoid false alarm drain pipe here to the very end
*
* @todo: Probably we should counter drvNATSend to count how
* deep pipe has been filed before drain.
*
*/
/** @todo XXX: Make it reading exactly we need to drain the
* pipe.*/
char ch;
}
}
/* process _all_ outstanding requests but don't wait */
#else /* RT_OS_WINDOWS */
nFDs = -1;
FALSE);
&& dwEvent != WSA_WAIT_TIMEOUT)
{
int error = WSAGetLastError();
}
if (dwEvent == WSA_WAIT_TIMEOUT)
{
continue;
}
/* poll the sockets in any case */
/* process _all_ outstanding requests but don't wait */
# ifdef VBOX_NAT_DELAY_HACK
if (cBreak++ > 128)
{
cBreak = 0;
RTThreadSleep(2);
}
# endif
#endif /* RT_OS_WINDOWS */
}
return VINF_SUCCESS;
}
/**
* Unblock the send thread so it can respond to a state change.
*
* @returns VBox status code.
* @param pDevIns The pcnet device instance.
* @param pThread The send thread.
*/
{
return VINF_SUCCESS;
}
/**
* Function called by slirp to check if it's possible to feed incoming data to the network port.
* @returns 1 if possible.
* @returns 0 if not possible.
*/
int slirp_can_output(void *pvUser)
{
return 1;
}
void slirp_push_recv_thread(void *pvUser)
{
}
{
/* don't queue new requests when the NAT thread is about to stop */
return;
int rc = RTReqQueueCallEx(pThis->hUrgRecvReqQueue, NULL /*ppReq*/, 0 /*cMillies*/, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
}
/**
* Function called by slirp to wake up device after VERR_TRY_AGAIN
*/
void slirp_output_pending(void *pvUser)
{
}
/**
* Function called by slirp to feed incoming data to the NIC.
*/
{
/* don't queue new requests when the NAT thread is about to stop */
return;
int rc = RTReqQueueCallEx(pThis->hRecvReqQueue, NULL /*ppReq*/, 0 /*cMillies*/, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
}
#ifdef RT_OS_DARWIN
/**
* Callback for the SystemConfiguration framework to notify us whenever the DNS
* server changes.
*
* @returns nothing.
* @param hDynStor The DynamicStore handle.
* @param hChangedKey Array of changed keys we watch for.
* @param pvUser Opaque user data (NAT driver instance).
*/
static DECLCALLBACK(void) drvNatDnsChanged(SCDynamicStoreRef hDynStor, CFArrayRef hChangedKeys, void *pvUser)
{
LogRel(("NAT: DNS servers changed, triggering reconnect\n"));
CFDictionaryRef hDnsDict = (CFDictionaryRef)SCDynamicStoreCopyValue(hDynStor, CFSTR("State:/Network/Global/DNS"));
if (hDnsDict)
{
CFArrayRef hArrAddresses = (CFArrayRef)CFDictionaryGetValue(hDnsDict, kSCPropNetDNSServerAddresses);
if (hArrAddresses)
}
}
#endif
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
{
return NULL;
}
/**
* Get the MAC address into the slirp stack.
*
* Called by drvNATLoadDone and drvNATPowerOn.
*/
{
if (pThis->pIAboveConfig)
{
/* Re-activate the port forwarding. If */
}
}
/**
* After loading we have to pass the MAC address of the ethernet device to the slirp stack.
* Otherwise the guest is not reachable until it performs a DHCP request or an ARP request
* (usually done during guest boot).
*/
{
return VINF_SUCCESS;
}
/**
* Some guests might not use DHCP to retrieve an IP but use a static IP.
*/
{
}
/**
* @interface_method_impl{PDMDEVREG,pfnResume}
*/
{
switch (enmReason)
{
/*
* Host resumed from a suspend and the network might have changed.
* Disconnect the guest from the network temporarily to let it pick up the changes.
*/
#ifndef RT_OS_DARWIN
#endif
return;
default: /* Ignore every other resume reason. */
/* do nothing */
return;
}
}
/**
* Info handler.
*/
{
}
{
int rc = VINF_SUCCESS;
{
N_("Unknown configuration in dns mapping"));
char szHostNameOrPattern[255];
bool fMatch = false; /* false used for equal matching, and true if wildcard pattern is used. */
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
{
GET_STRING(rc, pThis, pNode, "HostNamePattern", szHostNameOrPattern[0], sizeof(szHostNameOrPattern));
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
{
char szNodeName[225];
LogRel(("NAT: Neither 'HostName' nor 'HostNamePattern' is specified for mapping %s\n", szNodeName));
continue;
}
fMatch = true;
}
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
{
continue;
}
slirp_add_host_resolver_mapping(pThis->pNATState, fMatch ? NULL : szHostNameOrPattern, fMatch ? szHostNameOrPattern : NULL, HostIP.s_addr);
}
return rc;
}
#endif /* !VBOX_WITH_DNSMAPPING_IN_HOSTRESOLVER */
/**
* Sets up the redirectors.
*
* @returns VBox status code.
* @param pCfg The configuration handle.
*/
static int drvNATConstructRedir(unsigned iInstance, PDRVNAT pThis, PCFGMNODE pCfg, PRTNETADDRIPV4 pNetwork)
{
/*
* Enumerate redirections.
*/
{
char szNodeName[32];
continue;
#endif
/*
* Validate the port forwarding config.
*/
N_("Unknown configuration in port forwarding"));
/* protocol type */
bool fUDP;
char szProtocol[32];
int rc;
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
{
fUDP = false;
}
else if (RT_SUCCESS(rc))
{
fUDP = false;
fUDP = true;
else
N_("NAT#%d: Invalid configuration value for \"Protocol\": \"%s\""),
}
else
N_("NAT#%d: configuration query for \"Protocol\" failed"),
/* host port */
/* guest port */
/* guest address */
/* Store the guest IP for re-establishing the port-forwarding rules. Note that GuestIP
* is not documented. Without */
/*
* Call slirp about it.
*/
if (slirp_add_redirect(pThis->pNATState, fUDP, BindIP, iHostPort, GuestIP, iGuestPort, Mac.au8) < 0)
N_("NAT#%d: configuration error: failed to set up "
"redirection of %d to %d. Probably a conflict with "
} /* for each redir rule */
return VINF_SUCCESS;
}
/**
* Destruct a driver instance.
*
* Most VM resources are freed by the VM. This callback is provided so that any non-VM
* resources can be freed correctly.
*
* @param pDrvIns The driver instance data.
*/
{
LogFlow(("drvNATDestruct:\n"));
{
#ifdef VBOX_WITH_STATISTICS
# include "counters.h"
#endif
}
#ifdef RT_OS_DARWIN
/* Cleanup the DNS watcher. */
#endif
}
/**
* Construct a NAT network transport driver instance.
*
* @copydoc FNPDMDRVCONSTRUCT
*/
{
LogFlow(("drvNATConstruct:\n"));
/*
* Init the static parts.
*/
#ifdef RT_OS_DARWIN
#endif
/* IBase */
/* INetwork */
/* NAT engine configuration */
/*
* Validate the config.
*/
if (!CFGMR3AreValuesValid(pCfg,
"PassDomain\0TFTPPrefix\0BootFile\0Network"
"\0NextServer\0DNSProxy\0BindIP\0UseHostResolver\0"
"SlirpMTU\0AliasMode\0"
"SockRcv\0SockSnd\0TcpRcv\0TcpSnd\0"
"ICMPCacheLimit\0"
"SoMaxConnection\0"
"HostResolverMappings\0"
#endif
))
N_("Unknown NAT configuration option, only supports PassDomain,"
" TFTPPrefix, BootFile and Network"));
/*
* Get the configuration settings.
*/
int rc;
bool fPassDomain = true;
int fDNSProxy = 0;
int fUseHostResolver = 0;
int MTU = 1500;
int i32AliasMode = 0;
int i32MainAliasMode = 0;
int iIcmpCacheLimit = 100;
int i32SoMaxConn = 10;
/*
* Query the network port interface.
*/
if (!pThis->pIAboveNet)
"export the network port interface"));
if (!pThis->pIAboveConfig)
"export the network config interface"));
/* Generate a network address for this network card. */
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
"missing network"),
if (RT_FAILURE(rc))
"network '%s' describes not a valid IPv4 network"),
/*
* Initialize slirp.
*/
if (RT_SUCCESS(rc))
{
LogRel(("NAT: value of BindIP has been ignored\n"));
do \
{ \
int len = 0; \
if (RT_SUCCESS(rc)) \
} while(0)
#ifdef VBOX_WITH_STATISTICS
# define DRV_PROFILE_COUNTER(name, dsc) REGISTER_COUNTER(name, pThis, STAMTYPE_PROFILE, STAMUNIT_TICKS_PER_CALL, dsc)
# define DRV_COUNTING_COUNTER(name, dsc) REGISTER_COUNTER(name, pThis, STAMTYPE_COUNTER, STAMUNIT_COUNT, dsc)
# include "counters.h"
#endif
if (pMappingsCfg)
{
}
#endif
if (RT_SUCCESS(rc))
{
/*
* Register a load done notification to get the MAC address into the slirp
* engine after we loaded a guest state.
*/
char szTmp[128];
#ifndef RT_OS_WINDOWS
/*
* Create the control pipe.
*/
#else
#endif
#ifdef RT_OS_DARWIN
/* Set up a watcher which notifies us everytime the DNS server changes. */
int rc2 = VINF_SUCCESS;
SCDynStorCtx.version = 0;
SCDynamicStoreRef hDynStor = SCDynamicStoreCreate(NULL, CFSTR("org.virtualbox.drvnat"), drvNatDnsChanged, &SCDynStorCtx);
if (hDynStor)
{
if (hRunLoopSrc)
{
{
};
if (hArray)
{
{
}
else
}
else
}
}
else
if (RT_FAILURE(rc2))
LogRel(("NAT#%d: Failed to install DNS change notifier. The guest might loose DNS access when switching networks on the host\n",
#endif
/* might return VINF_NAT_DNS */
return rc;
}
/* failure path */
}
else
{
}
return rc;
}
/**
* NAT network transport driver registration record.
*/
{
/* u32Version */
/* szName */
"NAT",
/* szRCMod */
"",
/* szR0Mod */
"",
/* pszDescription */
"NAT Network Transport Driver",
/* fFlags */
/* fClass. */
/* cMaxInstances */
~0U,
/* cbInstance */
sizeof(DRVNAT),
/* pfnConstruct */
/* pfnDestruct */
/* pfnRelocate */
NULL,
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
/* pfnReset */
NULL,
/* pfnSuspend */
NULL,
/* pfnResume */
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnPowerOff */
NULL,
/* pfnSoftReset */
NULL,
/* u32EndVersion */
};