VBoxNetDHCP.cpp revision 0d49a2fbc9857ad8dd29542de7fb37202f1a283f
/* $Id$ */
/** @file
* VBoxNetDHCP - DHCP Service for connecting to IntNet.
*/
/*
* Copyright (C) 2009-2011 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* 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.
*/
/** @page pg_net_dhcp VBoxNetDHCP
*
* Write a few words...
*
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <VBox/com/com.h>
#include <VBox/com/listeners.h>
#include <VBox/com/string.h>
#include <VBox/com/Guid.h>
#include <VBox/com/array.h>
#include <VBox/com/ErrorInfo.h>
#include <VBox/com/errorprint.h>
#include <VBox/com/EventQueue.h>
#include <VBox/com/VirtualBox.h>
#include <iprt/alloca.h>
#include <iprt/buildconfig.h>
#include <iprt/err.h>
#include <iprt/net.h> /* must come before getopt */
#include <iprt/getopt.h>
#include <iprt/initterm.h>
#include <iprt/message.h>
#include <iprt/param.h>
#include <iprt/path.h>
#include <iprt/stream.h>
#include <iprt/time.h>
#include <iprt/string.h>
#include <VBox/sup.h>
#include <VBox/intnet.h>
#include <VBox/intnetinline.h>
#include <VBox/vmm/vmm.h>
#include <VBox/version.h>
#include "../NetLib/VBoxNetLib.h"
#include "../NetLib/shared_ptr.h"
#include <vector>
#include <list>
#include <string>
#include <map>
#include "../NetLib/VBoxNetBaseService.h"
#include "../NetLib/utils.h"
#ifdef RT_OS_WINDOWS /* WinMain */
# include <Windows.h>
# include <stdlib.h>
# ifdef INET_ADDRSTRLEN
/* On Windows INET_ADDRSTRLEN defined as 22 Ws2ipdef.h, because it include port number */
# undef INET_ADDRSTRLEN
# endif
# define INET_ADDRSTRLEN 16
#else
# include <netinet/in.h>
#endif
#include "Config.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* DHCP server instance.
*/
class VBoxNetDhcp: public VBoxNetBaseService, public NATNetworkEventAdapter
{
public:
VBoxNetDhcp();
virtual ~VBoxNetDhcp();
int init();
void usage(void) { /* XXX: document options */ };
int parseOpt(int rc, const RTGETOPTUNION& getOptVal);
int processFrame(void *, size_t) {return VERR_IGNORED; };
int processGSO(PCPDMNETWORKGSO, size_t) {return VERR_IGNORED; };
int processUDP(void *, size_t);
protected:
bool handleDhcpMsg(uint8_t uMsgType, PCRTNETBOOTP pDhcpMsg, size_t cb);
void debugPrintV(int32_t iMinLevel, bool fMsg, const char *pszFmt, va_list va) const;
static const char *debugDhcpName(uint8_t uMsgType);
private:
int initNoMain();
int initWithMain();
HRESULT HandleEvent(VBoxEventType_T aEventType, IEvent *pEvent);
int fetchAndUpdateDnsInfo();
protected:
/** @name The DHCP server specific configuration data members.
* @{ */
/*
* XXX: what was the plan? SQL3 or plain text file?
* How it will coexists with managment from VBoxManagement, who should manage db
* in that case (VBoxManage, VBoxSVC ???)
*/
std::string m_LeaseDBName;
/** @} */
/* corresponding dhcp server description in Main */
ComPtr<IDHCPServer> m_DhcpServer;
ComPtr<INATNetwork> m_NATNetwork;
/** Listener for Host DNS changes */
ComPtr<NATNetworkListenerImpl> m_vboxListener;
/*
* We will ignore cmd line parameters IFF there will be some DHCP specific arguments
* otherwise all paramters will come from Main.
*/
bool m_fIgnoreCmdLineParameters;
/*
* -b -n 10.0.1.2 -m 255.255.255.0 -> to the list processing in
*/
typedef struct
{
char Key;
std::string strValue;
} CMDLNPRM;
std::list<CMDLNPRM> CmdParameterll;
typedef std::list<CMDLNPRM>::iterator CmdParameterIterator;
/** @name Debug stuff
* @{ */
int32_t m_cVerbosity;
uint8_t m_uCurMsgType;
size_t m_cbCurMsg;
PCRTNETBOOTP m_pCurMsg;
VBOXNETUDPHDRS m_CurHdrs;
/** @} */
};
static inline int configGetBoundryAddress(const ComDhcpServerPtr& dhcp, bool fUpperBoundry, RTNETADDRIPV4& boundryAddress)
{
boundryAddress.u = INADDR_ANY;
HRESULT hrc;
com::Bstr strAddress;
if (fUpperBoundry)
hrc = dhcp->COMGETTER(UpperIP)(strAddress.asOutParam());
else
hrc = dhcp->COMGETTER(LowerIP)(strAddress.asOutParam());
AssertComRCReturn(hrc, VERR_INTERNAL_ERROR);
return RTNetStrToIPv4Addr(com::Utf8Str(strAddress).c_str(), &boundryAddress);
}
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** Pointer to the DHCP server. */
static VBoxNetDhcp *g_pDhcp;
/* DHCP server specific options */
static RTGETOPTDEF g_aOptionDefs[] =
{
{ "--lease-db", 'D', RTGETOPT_REQ_STRING },
{ "--begin-config", 'b', RTGETOPT_REQ_NOTHING },
{ "--gateway", 'g', RTGETOPT_REQ_IPV4ADDR },
{ "--lower-ip", 'l', RTGETOPT_REQ_IPV4ADDR },
{ "--upper-ip", 'u', RTGETOPT_REQ_IPV4ADDR },
};
/**
* Construct a DHCP server with a default configuration.
*/
VBoxNetDhcp::VBoxNetDhcp():VBoxNetBaseService("VBoxNetDhcp", "VBoxNetDhcp")
{
/* m_enmTrunkType = kIntNetTrunkType_WhateverNone; */
RTMAC mac;
mac.au8[0] = 0x08;
mac.au8[1] = 0x00;
mac.au8[2] = 0x27;
mac.au8[3] = 0x40;
mac.au8[4] = 0x41;
mac.au8[5] = 0x42;
setMacAddress(mac);
RTNETADDRIPV4 address;
address.u = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10, 0, 2, 5)));
setIpv4Address(address);
setSendBufSize(8 * _1K);
setRecvBufSize(50 * _1K);
m_uCurMsgType = UINT8_MAX;
m_cbCurMsg = 0;
m_pCurMsg = NULL;
memset(&m_CurHdrs, '\0', sizeof(m_CurHdrs));
m_fIgnoreCmdLineParameters = true;
for(unsigned int i = 0; i < RT_ELEMENTS(g_aOptionDefs); ++i)
addCommandLineOption(&g_aOptionDefs[i]);
}
/**
* Destruct a DHCP server.
*/
VBoxNetDhcp::~VBoxNetDhcp()
{
}
/**
* Parse the DHCP specific arguments.
*
* This callback caled for each paramenter so
* ....
* we nee post analisys of the parameters, at least
* for -b, -g, -l, -u, -m
*/
int VBoxNetDhcp::parseOpt(int rc, const RTGETOPTUNION& Val)
{
CMDLNPRM prm;
/* Ok, we've entered here, thus we can't ignore cmd line parameters anymore */
m_fIgnoreCmdLineParameters = false;
prm.Key = rc;
switch (rc)
{
case 'l':
case 'u':
case 'g':
{
char buf[17];
RTStrPrintf(buf, 17, "%RTnaipv4", Val.IPv4Addr.u);
prm.strValue = buf;
CmdParameterll.push_back(prm);
}
break;
case 'b': // ignore
case 'D': // ignore
break;
default:
rc = RTGetOptPrintError(rc, &Val);
RTPrintf("Use --help for more information.\n");
return rc;
}
return VINF_SUCCESS;
}
int VBoxNetDhcp::init()
{
int rc = this->VBoxNetBaseService::init();
AssertRCReturn(rc, rc);
NetworkManager *netManager = NetworkManager::getNetworkManager();
netManager->setOurAddress(getIpv4Address());
netManager->setOurNetmask(getIpv4Netmask());
netManager->setOurMac(getMacAddress());
netManager->setService(this);
if (isMainNeeded())
rc = initWithMain();
else
rc = initNoMain();
AssertRCReturn(rc, rc);
return VINF_SUCCESS;
}
int VBoxNetDhcp::processUDP(void *pv, size_t cbPv)
{
PCRTNETBOOTP pDhcpMsg = (PCRTNETBOOTP)pv;
m_pCurMsg = pDhcpMsg;
m_cbCurMsg = cbPv;
uint8_t uMsgType;
if (RTNetIPv4IsDHCPValid(NULL /* why is this here? */, pDhcpMsg, cbPv, &uMsgType))
{
m_uCurMsgType = uMsgType;
{
/* To avoid fight with event processing thread */
VBoxNetALock(this);
handleDhcpMsg(uMsgType, pDhcpMsg, cbPv);
}
m_uCurMsgType = UINT8_MAX;
}
else
debugPrint(1, true, "VBoxNetDHCP: Skipping invalid DHCP packet.\n"); /** @todo handle pure bootp clients too? */
m_pCurMsg = NULL;
m_cbCurMsg = 0;
return VINF_SUCCESS;
}
/**
* Handles a DHCP message.
*
* @returns true if handled, false if not.
* @param uMsgType The message type.
* @param pDhcpMsg The DHCP message.
* @param cb The size of the DHCP message.
*/
bool VBoxNetDhcp::handleDhcpMsg(uint8_t uMsgType, PCRTNETBOOTP pDhcpMsg, size_t cb)
{
if (pDhcpMsg->bp_op == RTNETBOOTP_OP_REQUEST)
{
NetworkManager *networkManager = NetworkManager::getNetworkManager();
switch (uMsgType)
{
case RTNET_DHCP_MT_DISCOVER:
return networkManager->handleDhcpReqDiscover(pDhcpMsg, cb);
case RTNET_DHCP_MT_REQUEST:
return networkManager->handleDhcpReqRequest(pDhcpMsg, cb);
case RTNET_DHCP_MT_DECLINE:
return networkManager->handleDhcpReqDecline(pDhcpMsg, cb);
case RTNET_DHCP_MT_RELEASE:
return networkManager->handleDhcpReqRelease(pDhcpMsg, cb);
case RTNET_DHCP_MT_INFORM:
debugPrint(0, true, "Should we handle this?");
break;
default:
debugPrint(0, true, "Unexpected.");
break;
}
}
return false;
}
/**
* Print debug message depending on the m_cVerbosity level.
*
* @param iMinLevel The minimum m_cVerbosity level for this message.
* @param fMsg Whether to dump parts for the current DHCP message.
* @param pszFmt The message format string.
* @param va Optional arguments.
*/
void VBoxNetDhcp::debugPrintV(int iMinLevel, bool fMsg, const char *pszFmt, va_list va) const
{
if (iMinLevel <= m_cVerbosity)
{
va_list vaCopy; /* This dude is *very* special, thus the copy. */
va_copy(vaCopy, va);
RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: %s: %N\n", iMinLevel >= 2 ? "debug" : "info", pszFmt, &vaCopy);
va_end(vaCopy);
if ( fMsg
&& m_cVerbosity >= 2
&& m_pCurMsg)
{
/* XXX: export this to debugPrinfDhcpMsg or variant and other method export
* to base class
*/
const char *pszMsg = m_uCurMsgType != UINT8_MAX ? debugDhcpName(m_uCurMsgType) : "";
RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: debug: %8s chaddr=%.6Rhxs ciaddr=%d.%d.%d.%d yiaddr=%d.%d.%d.%d siaddr=%d.%d.%d.%d xid=%#x\n",
pszMsg,
&m_pCurMsg->bp_chaddr,
m_pCurMsg->bp_ciaddr.au8[0], m_pCurMsg->bp_ciaddr.au8[1], m_pCurMsg->bp_ciaddr.au8[2], m_pCurMsg->bp_ciaddr.au8[3],
m_pCurMsg->bp_yiaddr.au8[0], m_pCurMsg->bp_yiaddr.au8[1], m_pCurMsg->bp_yiaddr.au8[2], m_pCurMsg->bp_yiaddr.au8[3],
m_pCurMsg->bp_siaddr.au8[0], m_pCurMsg->bp_siaddr.au8[1], m_pCurMsg->bp_siaddr.au8[2], m_pCurMsg->bp_siaddr.au8[3],
m_pCurMsg->bp_xid);
}
}
}
/**
* Gets the name of given DHCP message type.
*
* @returns Readonly name.
* @param uMsgType The message number.
*/
/* static */ const char *VBoxNetDhcp::debugDhcpName(uint8_t uMsgType)
{
switch (uMsgType)
{
case 0: return "MT_00";
case RTNET_DHCP_MT_DISCOVER: return "DISCOVER";
case RTNET_DHCP_MT_OFFER: return "OFFER";
case RTNET_DHCP_MT_REQUEST: return "REQUEST";
case RTNET_DHCP_MT_DECLINE: return "DECLINE";
case RTNET_DHCP_MT_ACK: return "ACK";
case RTNET_DHCP_MT_NAC: return "NAC";
case RTNET_DHCP_MT_RELEASE: return "RELEASE";
case RTNET_DHCP_MT_INFORM: return "INFORM";
case 9: return "MT_09";
case 10: return "MT_0a";
case 11: return "MT_0b";
case 12: return "MT_0c";
case 13: return "MT_0d";
case 14: return "MT_0e";
case 15: return "MT_0f";
case 16: return "MT_10";
case 17: return "MT_11";
case 18: return "MT_12";
case 19: return "MT_13";
case UINT8_MAX: return "MT_ff";
default: return "UNKNOWN";
}
}
int VBoxNetDhcp::initNoMain()
{
CmdParameterIterator it;
RTNETADDRIPV4 address = getIpv4Address();
RTNETADDRIPV4 netmask = getIpv4Netmask();
RTNETADDRIPV4 networkId;
networkId.u = address.u & netmask.u;
RTNETADDRIPV4 UpperAddress;
RTNETADDRIPV4 LowerAddress = networkId;
UpperAddress.u = RT_H2N_U32(RT_N2H_U32(LowerAddress.u) | RT_N2H_U32(netmask.u));
for (it = CmdParameterll.begin(); it != CmdParameterll.end(); ++it)
{
switch(it->Key)
{
case 'l':
RTNetStrToIPv4Addr(it->strValue.c_str(), &LowerAddress);
break;
case 'u':
RTNetStrToIPv4Addr(it->strValue.c_str(), &UpperAddress);
break;
case 'b':
break;
}
}
ConfigurationManager *confManager = ConfigurationManager::getConfigurationManager();
AssertPtrReturn(confManager, VERR_INTERNAL_ERROR);
confManager->addNetwork(unconst(g_RootConfig),
networkId,
netmask,
LowerAddress,
UpperAddress);
return VINF_SUCCESS;
}
int VBoxNetDhcp::initWithMain()
{
/* ok, here we should initiate instance of dhcp server
* and listener for Dhcp configuration events
*/
AssertRCReturn(virtualbox.isNull(), VERR_INTERNAL_ERROR);
std::string networkName = getNetwork();
int rc = findDhcpServer(virtualbox, networkName, m_DhcpServer);
AssertRCReturn(rc, rc);
rc = findNatNetwork(virtualbox, networkName, m_NATNetwork);
AssertRCReturn(rc, rc);
BOOL fNeedDhcpServer = isDhcpRequired(m_NATNetwork);
if (!fNeedDhcpServer)
return VERR_CANCELLED;
RTNETADDRIPV4 gateway;
com::Bstr strGateway;
HRESULT hrc = m_NATNetwork->COMGETTER(Gateway)(strGateway.asOutParam());
AssertComRCReturn(hrc, VERR_INTERNAL_ERROR);
RTNetStrToIPv4Addr(com::Utf8Str(strGateway).c_str(), &gateway);
ConfigurationManager *confManager = ConfigurationManager::getConfigurationManager();
AssertPtrReturn(confManager, VERR_INTERNAL_ERROR);
confManager->addToAddressList(RTNET_DHCP_OPT_ROUTERS, gateway);
rc = fetchAndUpdateDnsInfo();
AssertMsgRCReturn(rc, ("Wasn't able to fetch Dns info"), rc);
ComEventTypeArray aVBoxEvents;
aVBoxEvents.push_back(VBoxEventType_OnHostNameResolutionConfigurationChange);
rc = createNatListener(m_vboxListener, virtualbox, this, aVBoxEvents);
AssertRCReturn(rc, rc);
RTNETADDRIPV4 LowerAddress;
rc = configGetBoundryAddress(m_DhcpServer, false, LowerAddress);
AssertMsgRCReturn(rc, ("can't get lower boundrary adderss'"),rc);
RTNETADDRIPV4 UpperAddress;
rc = configGetBoundryAddress(m_DhcpServer, true, UpperAddress);
AssertMsgRCReturn(rc, ("can't get upper boundrary adderss'"),rc);
RTNETADDRIPV4 address = getIpv4Address();
RTNETADDRIPV4 netmask = getIpv4Netmask();
RTNETADDRIPV4 networkId = networkid(address, netmask);
std::string name = std::string("default");
confManager->addNetwork(unconst(g_RootConfig),
networkId,
netmask,
LowerAddress,
UpperAddress);
com::Bstr bstr;
hrc = virtualbox->COMGETTER(HomeFolder)(bstr.asOutParam());
std::string strXmlLeaseFile(com::Utf8StrFmt("%ls%c%s.leases",
bstr.raw(), RTPATH_DELIMITER, networkName.c_str()).c_str());
confManager->loadFromFile(strXmlLeaseFile);
return VINF_SUCCESS;
}
int VBoxNetDhcp::fetchAndUpdateDnsInfo()
{
ComHostPtr host;
if (SUCCEEDED(virtualbox->COMGETTER(Host)(host.asOutParam())))
{
AddressToOffsetMapping mapIp4Addr2Off;
int rc = localMappings(m_NATNetwork, mapIp4Addr2Off);
/* XXX: here could be several cases: 1. COM error, 2. not found (empty) 3. ? */
AssertMsgRCReturn(rc, ("Can't fetch local mappings"), rc);
RTNETADDRIPV4 address = getIpv4Address();
RTNETADDRIPV4 netmask = getIpv4Netmask();
AddressList nameservers;
rc = hostDnsServers(host, networkid(address, netmask), mapIp4Addr2Off, nameservers);
AssertMsgRCReturn(rc, ("Debug me!!!"), rc);
/* XXX: Search strings */
std::string domain;
rc = hostDnsDomain(host, domain);
AssertMsgRCReturn(rc, ("Debug me!!"), rc);
{
VBoxNetALock(this);
ConfigurationManager *confManager = ConfigurationManager::getConfigurationManager();
confManager->flushAddressList(RTNET_DHCP_OPT_DNS);
for (AddressList::iterator it = nameservers.begin(); it != nameservers.end(); ++it)
confManager->addToAddressList(RTNET_DHCP_OPT_DNS, *it);
confManager->setString(RTNET_DHCP_OPT_DOMAIN_NAME, domain);
}
}
return VINF_SUCCESS;
}
HRESULT VBoxNetDhcp::HandleEvent(VBoxEventType_T aEventType, IEvent *pEvent)
{
switch(aEventType)
{
case VBoxEventType_OnHostNameResolutionConfigurationChange:
fetchAndUpdateDnsInfo();
break;
}
return S_OK;
}
/**
* Entry point.
*/
extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv)
{
/*
* Instantiate the DHCP server and hand it the options.
*/
VBoxNetDhcp *pDhcp = new VBoxNetDhcp();
if (!pDhcp)
{
RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: new VBoxNetDhcp failed!\n");
return 1;
}
int rc = pDhcp->parseArgs(argc - 1, argv + 1);
if (rc)
return rc;
pDhcp->init();
/*
* Try connect the server to the network.
*/
rc = pDhcp->tryGoOnline();
if (RT_FAILURE(rc))
{
delete pDhcp;
return 1;
}
/*
* Process requests.
*/
g_pDhcp = pDhcp;
rc = pDhcp->run();
g_pDhcp = NULL;
delete pDhcp;
return 0;
}
#ifndef VBOX_WITH_HARDENING
int main(int argc, char **argv)
{
int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB);
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
return TrustedMain(argc, argv);
}
# ifdef RT_OS_WINDOWS
static LRESULT CALLBACK WindowProc(HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
if(uMsg == WM_DESTROY)
{
PostQuitMessage(0);
return 0;
}
return DefWindowProc (hwnd, uMsg, wParam, lParam);
}
static LPCWSTR g_WndClassName = L"VBoxNetDHCPClass";
static DWORD WINAPI MsgThreadProc(__in LPVOID lpParameter)
{
HWND hwnd = 0;
HINSTANCE hInstance = (HINSTANCE)GetModuleHandle (NULL);
bool bExit = false;
/* Register the Window Class. */
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof(void *);
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_WndClassName;
ATOM atomWindowClass = RegisterClass(&wc);
if (atomWindowClass != 0)
{
/* Create the window. */
hwnd = CreateWindowEx (WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
g_WndClassName, g_WndClassName,
WS_POPUPWINDOW,
-200, -200, 100, 100, NULL, NULL, hInstance, NULL);
if (hwnd)
{
SetWindowPos(hwnd, HWND_TOPMOST, -200, -200, 0, 0,
SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
DestroyWindow (hwnd);
bExit = true;
}
UnregisterClass (g_WndClassName, hInstance);
}
if(bExit)
{
/* no need any accuracy here, in anyway the DHCP server usually gets terminated with TerminateProcess */
exit(0);
}
return 0;
}
/** (We don't want a console usually.) */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
NOREF(hInstance); NOREF(hPrevInstance); NOREF(lpCmdLine); NOREF(nCmdShow);
HANDLE hThread = CreateThread(
NULL, /*__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, */
0, /*__in SIZE_T dwStackSize, */
MsgThreadProc, /*__in LPTHREAD_START_ROUTINE lpStartAddress,*/
NULL, /*__in_opt LPVOID lpParameter,*/
0, /*__in DWORD dwCreationFlags,*/
NULL /*__out_opt LPDWORD lpThreadId*/
);
if(hThread != NULL)
CloseHandle(hThread);
return main(__argc, __argv);
}
# endif /* RT_OS_WINDOWS */
#endif /* !VBOX_WITH_HARDENING */