/* $Id: VBoxNetDHCP.cpp 57358 2015-08-14 15:16:38Z vboxsync $ */ /** @file * VBoxNetDHCP - DHCP Service for connecting to IntNet. */ /* * Copyright (C) 2009-2015 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 #include #include #include #include #include #include #include #include #include #include #include #include /* must come before getopt */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../NetLib/VBoxNetLib.h" #include "../NetLib/shared_ptr.h" #include #include #include #include #include "../NetLib/VBoxNetBaseService.h" #include "../NetLib/utils.h" #ifdef RT_OS_WINDOWS /* WinMain */ # include # include # 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 #endif #include "Config.h" /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * DHCP server instance. */ class VBoxNetDhcp: public VBoxNetBaseService, public NATNetworkEventAdapter { public: VBoxNetDhcp(); virtual ~VBoxNetDhcp(); int init(); void done(); 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 m_DhcpServer; ComPtr m_NATNetwork; /** Listener for Host DNS changes */ ComNatListenerPtr m_VBoxListener; ComNatListenerPtr m_VBoxClientListener; NetworkManager *m_NetworkManager; /* * 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 CmdParameterll; typedef std::list::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_NetworkManager(NULL) { /* 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; RT_ZERO(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); if (isMainNeeded()) rc = initWithMain(); else rc = initNoMain(); AssertRCReturn(rc, rc); m_NetworkManager = NetworkManager::getNetworkManager(m_DhcpServer); AssertPtrReturn(m_NetworkManager, VERR_INTERNAL_ERROR); m_NetworkManager->setOurAddress(getIpv4Address()); m_NetworkManager->setOurNetmask(getIpv4Netmask()); m_NetworkManager->setOurMac(getMacAddress()); m_NetworkManager->setService(this); return VINF_SUCCESS; } void VBoxNetDhcp::done() { destroyNatListener(m_VBoxListener, virtualbox); destroyClientListener(m_VBoxClientListener, virtualboxClient); } 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) { AssertPtrReturn(m_NetworkManager, false); switch (uMsgType) { case RTNET_DHCP_MT_DISCOVER: return m_NetworkManager->handleDhcpReqDiscover(pDhcpMsg, cb); case RTNET_DHCP_MT_REQUEST: return m_NetworkManager->handleDhcpReqRequest(pDhcpMsg, cb); case RTNET_DHCP_MT_DECLINE: return m_NetworkManager->handleDhcpReqDecline(pDhcpMsg, cb); case RTNET_DHCP_MT_RELEASE: return m_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 = getNetworkName(); 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 eventTypes; eventTypes.push_back(VBoxEventType_OnHostNameResolutionConfigurationChange); eventTypes.push_back(VBoxEventType_OnNATNetworkStartStop); rc = createNatListener(m_VBoxListener, virtualbox, this, eventTypes); AssertRCReturn(rc, rc); } { ComEventTypeArray eventTypes; eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged); rc = createClientListener(m_VBoxClientListener, virtualboxClient, this, eventTypes); 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()); com::Utf8StrFmt strXmlLeaseFile("%ls%c%s.leases", bstr.raw(), RTPATH_DELIMITER, networkName.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; case VBoxEventType_OnNATNetworkStartStop: { ComPtr pStartStopEvent = pEvent; com::Bstr networkName; HRESULT hrc = pStartStopEvent->COMGETTER(NetworkName)(networkName.asOutParam()); AssertComRCReturn(hrc, hrc); if (networkName.compare(getNetworkName().c_str())) break; /* change not for our network */ BOOL fStart = TRUE; hrc = pStartStopEvent->COMGETTER(StartEvent)(&fStart); AssertComRCReturn(hrc, hrc); if (!fStart) shutdown(); break; } case VBoxEventType_OnVBoxSVCAvailabilityChanged: { shutdown(); 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(); pDhcp->done(); 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 */