/* $Id: VBoxNetDHCP.cpp 38636 2011-09-05 13:49:45Z vboxsync $ */ /** @file * VBoxNetDHCP - DHCP Service for connecting to IntNet. */ /* * Copyright (C) 2009 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 /* must come before getopt */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../NetLib/VBoxNetLib.h" #include #include #ifdef RT_OS_WINDOWS /* WinMain */ # include # include #endif /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ /** * DHCP configuration item. * * This is all public data because I'm too lazy to do it properly right now. */ class VBoxNetDhcpCfg { public: /** The etheret addresses this matches config applies to. * An empty vector means 'ANY'. */ std::vector m_MacAddresses; /** The upper address in the range. */ RTNETADDRIPV4 m_UpperAddr; /** The lower address in the range. */ RTNETADDRIPV4 m_LowerAddr; /** Option 1: The net mask. */ RTNETADDRIPV4 m_SubnetMask; /* * Option 2: The time offset. */ /** Option 3: Routers for the subnet. */ std::vector m_Routers; /* * Option 4: Time server. */ /* * Option 5: Name server. */ /** Option 6: Domain Name Server (DNS) */ std::vector m_DNSes; /* * Option 7: Log server. */ /* * Option 8: Cookie server. */ /* * Option 9: LPR server. */ /* * Option 10: Impress server. */ /* * Option 11: Resource location server. */ /* * Option 12: Host name. */ std::string m_HostName; /* * Option 13: Boot file size option. */ /* * Option 14: Merit dump file. */ /** Option 15: Domain name. */ std::string m_DomainName; /* * Option 16: Swap server. */ /* * Option 17: Root path. */ /* * Option 18: Extension path. */ /* * Option 19: IP forwarding enable/disable. */ /* * Option 20: Non-local routing enable/disable. */ /* * Option 21: Policy filter. */ /* * Option 22: Maximum datagram reassembly size (MRS). */ /* * Option 23: Default IP time-to-live. */ /* * Option 24: Path MTU aging timeout. */ /* * Option 25: Path MTU plateau table. */ /* * Option 26: Interface MTU. */ /* * Option 27: All subnets are local. */ /* * Option 28: Broadcast address. */ /* * Option 29: Perform maximum discovery. */ /* * Option 30: Mask supplier. */ /* * Option 31: Perform route discovery. */ /* * Option 32: Router solicitation address. */ /* * Option 33: Static route. */ /* * Option 34: Trailer encapsulation. */ /* * Option 35: ARP cache timeout. */ /* * Option 36: Ethernet encapsulation. */ /* * Option 37: TCP Default TTL. */ /* * Option 38: TCP Keepalive Interval. */ /* * Option 39: TCP Keepalive Garbage. */ /* * Option 40: Network Information Service (NIS) Domain. */ /* * Option 41: Network Information Servers. */ /* * Option 42: Network Time Protocol Servers. */ /* * Option 43: Vendor Specific Information. */ /* * Option 44: NetBIOS over TCP/IP Name Server (NBNS). */ /* * Option 45: NetBIOS over TCP/IP Datagram distribution Server (NBDD). */ /* * Option 46: NetBIOS over TCP/IP Node Type. */ /* * Option 47: NetBIOS over TCP/IP Scope. */ /* * Option 48: X Window System Font Server. */ /* * Option 49: X Window System Display Manager. */ /** Option 51: IP Address Lease Time. */ uint32_t m_cSecLease; /* * Option 64: Network Information Service+ Domain. */ /* * Option 65: Network Information Service+ Servers. */ /** Option 66: TFTP server name. */ std::string m_TftpServer; /** Address for the bp_siaddr field corresponding to m_TftpServer. */ RTNETADDRIPV4 m_TftpServerAddr; /** Option 67: Bootfile name. */ std::string m_BootfileName; /* * Option 68: Mobile IP Home Agent. */ /* * Option 69: Simple Mail Transport Protocol (SMPT) Server. */ /* * Option 70: Post Office Protocol (POP3) Server. */ /* * Option 71: Network News Transport Protocol (NNTP) Server. */ /* * Option 72: Default World Wide Web (WWW) Server. */ /* * Option 73: Default Finger Server. */ /* * Option 74: Default Internet Relay Chat (IRC) Server. */ /* * Option 75: StreetTalk Server. */ /* * Option 119: Domain Search. */ VBoxNetDhcpCfg() { m_UpperAddr.u = UINT32_MAX; m_LowerAddr.u = UINT32_MAX; m_SubnetMask.u = UINT32_MAX; m_cSecLease = 60*60; /* 1 hour */ } /** Validates the configuration. * @returns 0 on success, exit code + error message to stderr on failure. */ int validate(void) { if ( m_UpperAddr.u == UINT32_MAX || m_LowerAddr.u == UINT32_MAX || m_SubnetMask.u == UINT32_MAX) { RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: Config is missing:"); if (m_UpperAddr.u == UINT32_MAX) RTStrmPrintf(g_pStdErr, " --upper-ip"); if (m_LowerAddr.u == UINT32_MAX) RTStrmPrintf(g_pStdErr, " --lower-ip"); if (m_SubnetMask.u == UINT32_MAX) RTStrmPrintf(g_pStdErr, " --netmask"); return 2; } if (RT_N2H_U32(m_UpperAddr.u) < RT_N2H_U32(m_LowerAddr.u)) { RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: The --upper-ip value is lower than the --lower-ip one!\n" " %d.%d.%d.%d < %d.%d.%d.%d\n", m_UpperAddr.au8[0], m_UpperAddr.au8[1], m_UpperAddr.au8[2], m_UpperAddr.au8[3], m_LowerAddr.au8[0], m_LowerAddr.au8[1], m_LowerAddr.au8[2], m_LowerAddr.au8[3]); return 3; } /* the code goes insane if we have too many atm. lazy bird */ uint32_t cIPs = RT_N2H_U32(m_UpperAddr.u) - RT_N2H_U32(m_LowerAddr.u); if (cIPs > 1024) { RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: Too many IPs between --upper-ip and --lower-ip! %d (max 1024)\n" " %d.%d.%d.%d < %d.%d.%d.%d\n", cIPs, m_UpperAddr.au8[0], m_UpperAddr.au8[1], m_UpperAddr.au8[2], m_UpperAddr.au8[3], m_LowerAddr.au8[0], m_LowerAddr.au8[1], m_LowerAddr.au8[2], m_LowerAddr.au8[3]); return 3; } return 0; } /** * Is this config for one specific client? * * @return true / false. */ bool isOneSpecificClient(void) const { return m_LowerAddr.u == m_UpperAddr.u && m_MacAddresses.size() > 0; } /** * Checks if this config matches the specified MAC address. * * @returns true / false. * * @param pMac The MAC address to match. */ bool matchesMacAddress(PCRTMAC pMac) const { size_t i = m_MacAddresses.size(); if (RT_LIKELY(i < 1)) return true; /* no entries == ALL wildcard match */ while (i--) { PCRTMAC pCur = &m_MacAddresses[i]; if ( pCur->au16[0] == pMac->au16[0] && pCur->au16[1] == pMac->au16[1] && pCur->au16[2] == pMac->au16[2]) return true; } return false; } }; /** * DHCP lease. */ class VBoxNetDhcpLease { public: typedef enum State { /** Invalid. */ kState_Invalid = 0, /** The lease is free / released. */ kState_Free, /** An offer has been made. * Expire time indicates when the offer expires. */ kState_Offer, /** The lease is active. * Expire time indicates when the lease expires. */ kState_Active } State; /** The client MAC address. */ RTMAC m_MacAddress; /** The IPv4 address. */ RTNETADDRIPV4 m_IPv4Address; /** The current lease state. */ State m_enmState; /** The lease expiration time. */ RTTIMESPEC m_ExpireTime; /** Transaction ID. */ uint32_t m_xid; /** The configuration for this lease. */ VBoxNetDhcpCfg *m_pCfg; public: /** Constructor taking an IPv4 address and a Config. */ VBoxNetDhcpLease(RTNETADDRIPV4 IPv4Addr, VBoxNetDhcpCfg *pCfg) { m_pCfg = pCfg; m_IPv4Address = IPv4Addr; m_MacAddress.au16[0] = m_MacAddress.au16[1] = m_MacAddress.au16[2] = 0xff; m_enmState = kState_Free; RTTimeSpecSetSeconds(&m_ExpireTime, 0); m_xid = UINT32_MAX; } /** Destructor. */ ~VBoxNetDhcpLease() { m_IPv4Address.u = UINT32_MAX; m_pCfg = NULL; m_MacAddress.au16[0] = m_MacAddress.au16[1] = m_MacAddress.au16[2] = 0xff; m_enmState = kState_Free; m_xid = UINT32_MAX; } void offer(uint32_t xid); void activate(void); void activate(uint32_t xid); void release(void); bool hasExpired(void) const; /** * Checks if the lease is in use or not. * * @returns true if active, false if free or expired. * * @param pNow The current time to use. Optional. */ bool isInUse(PCRTTIMESPEC pNow = NULL) const { if ( m_enmState == kState_Offer || m_enmState == kState_Active) { RTTIMESPEC Now; if (!pNow) pNow = RTTimeNow(&Now); return RTTimeSpecGetSeconds(&m_ExpireTime) > RTTimeSpecGetSeconds(pNow); } return false; } /** * Is this lease for one specific client? * * @return true/false. */ bool isOneSpecificClient(void) const { return m_pCfg && m_pCfg->isOneSpecificClient(); } /** * Is this lease currently being offered to a client. * * @returns true / false. */ bool isBeingOffered(void) const { return m_enmState == kState_Offer && isInUse(); } /** * Is the lease in the current config or not. * * When updating the config we might leave active leases behind which aren't * included in the new config. These will have m_pCfg set to NULL and should be * freed up when they expired. * * @returns true / false. */ bool isInCurrentConfig(void) const { return m_pCfg != NULL; } }; /** * DHCP server instance. */ class VBoxNetDhcp { public: VBoxNetDhcp(); virtual ~VBoxNetDhcp(); int parseArgs(int argc, char **argv); int tryGoOnline(void); int run(void); protected: int addConfig(VBoxNetDhcpCfg *pCfg); void explodeConfig(void); bool handleDhcpMsg(uint8_t uMsgType, PCRTNETBOOTP pDhcpMsg, size_t cb); bool handleDhcpReqDiscover(PCRTNETBOOTP pDhcpMsg, size_t cb); bool handleDhcpReqRequest(PCRTNETBOOTP pDhcpMsg, size_t cb); bool handleDhcpReqDecline(PCRTNETBOOTP pDhcpMsg, size_t cb); bool handleDhcpReqRelease(PCRTNETBOOTP pDhcpMsg, size_t cb); void makeDhcpReply(uint8_t uMsgType, VBoxNetDhcpLease *pLease, PCRTNETBOOTP pDhcpMsg, size_t cb); VBoxNetDhcpLease *findLeaseByMacAddress(PCRTMAC pMacAddress, bool fAnyState); VBoxNetDhcpLease *findLeaseByIpv4AndMacAddresses(RTNETADDRIPV4 IPv4Addr, PCRTMAC pMacAddress, bool fAnyState); VBoxNetDhcpLease *newLease(PCRTNETBOOTP pDhcpMsg, size_t cb); static uint8_t const *findOption(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cb, size_t *pcbMaxOpt); static bool findOptionIPv4Addr(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cb, PRTNETADDRIPV4 pIPv4Addr); inline void debugPrint( int32_t iMinLevel, bool fMsg, const char *pszFmt, ...) const; void debugPrintV(int32_t iMinLevel, bool fMsg, const char *pszFmt, va_list va) const; static const char *debugDhcpName(uint8_t uMsgType); protected: /** @name The server configuration data members. * @{ */ std::string m_Name; std::string m_Network; std::string m_TrunkName; INTNETTRUNKTYPE m_enmTrunkType; RTMAC m_MacAddress; RTNETADDRIPV4 m_Ipv4Address; std::string m_LeaseDBName; /** @} */ /** The current configs. */ std::vector m_Cfgs; /** The current leases. */ std::vector m_Leases; /** @name The network interface * @{ */ PSUPDRVSESSION m_pSession; uint32_t m_cbSendBuf; uint32_t m_cbRecvBuf; INTNETIFHANDLE m_hIf; /**< The handle to the network interface. */ PINTNETBUF m_pIfBuf; /**< Interface buffer. */ /** @} */ /** @name Debug stuff * @{ */ int32_t m_cVerbosity; uint8_t m_uCurMsgType; size_t m_cbCurMsg; PCRTNETBOOTP m_pCurMsg; VBOXNETUDPHDRS m_CurHdrs; /** @} */ }; /******************************************************************************* * Global Variables * *******************************************************************************/ /** Pointer to the DHCP server. */ static VBoxNetDhcp *g_pDhcp; /** * Offer this lease to a client. * * @param xid The transaction ID. */ void VBoxNetDhcpLease::offer(uint32_t xid) { m_enmState = kState_Offer; m_xid = xid; RTTimeNow(&m_ExpireTime); RTTimeSpecAddSeconds(&m_ExpireTime, 60); } /** * Activate this lease (i.e. a client is now using it). */ void VBoxNetDhcpLease::activate(void) { m_enmState = kState_Active; RTTimeNow(&m_ExpireTime); RTTimeSpecAddSeconds(&m_ExpireTime, m_pCfg ? m_pCfg->m_cSecLease : 60); /* m_pCfg can be NULL right now... */ } /** * Activate this lease with a new transaction ID. * * @param xid The transaction ID. * @todo check if this is really necessary. */ void VBoxNetDhcpLease::activate(uint32_t xid) { activate(); m_xid = xid; } /** * Release a lease either upon client request or because it didn't quite match a * DHCP_REQUEST. */ void VBoxNetDhcpLease::release(void) { m_enmState = kState_Free; RTTimeNow(&m_ExpireTime); RTTimeSpecAddSeconds(&m_ExpireTime, 5); } /** * Checks if the lease has expired or not. * * This just checks the expiration time not the state. This is so that this * method will work for reusing RELEASEd leases when the client comes back after * a reboot or ipconfig /renew. Callers not interested in info on released * leases should check the state first. * * @returns true if expired, false if not. */ bool VBoxNetDhcpLease::hasExpired() const { RTTIMESPEC Now; return RTTimeSpecGetSeconds(&m_ExpireTime) > RTTimeSpecGetSeconds(RTTimeNow(&Now)); } /** * Construct a DHCP server with a default configuration. */ VBoxNetDhcp::VBoxNetDhcp() { m_Name = "VBoxNetDhcp"; m_Network = "VBoxNetDhcp"; m_TrunkName = ""; m_enmTrunkType = kIntNetTrunkType_WhateverNone; m_MacAddress.au8[0] = 0x08; m_MacAddress.au8[1] = 0x00; m_MacAddress.au8[2] = 0x27; m_MacAddress.au8[3] = 0x40; m_MacAddress.au8[4] = 0x41; m_MacAddress.au8[5] = 0x42; m_Ipv4Address.u = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10, 0, 2, 5))); m_pSession = NIL_RTR0PTR; m_cbSendBuf = 8192; m_cbRecvBuf = 51200; /** @todo tune to 64 KB with help from SrvIntR0 */ m_hIf = INTNET_HANDLE_INVALID; m_pIfBuf = NULL; m_cVerbosity = 0; m_uCurMsgType = UINT8_MAX; m_cbCurMsg = 0; m_pCurMsg = NULL; memset(&m_CurHdrs, '\0', sizeof(m_CurHdrs)); #if 0 /* enable to hack the code without a mile long argument list. */ VBoxNetDhcpCfg *pDefCfg = new VBoxNetDhcpCfg(); pDefCfg->m_LowerAddr.u = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10, 0, 2,100))); pDefCfg->m_UpperAddr.u = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10, 0, 2,250))); pDefCfg->m_SubnetMask.u = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8(255,255,255, 0))); RTNETADDRIPV4 Addr; Addr.u = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10, 0, 2, 1))); pDefCfg->m_Routers.push_back(Addr); Addr.u = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10, 0, 2, 2))); pDefCfg->m_DNSes.push_back(Addr); pDefCfg->m_DomainName = "vboxnetdhcp.org"; #if 0 pDefCfg->m_cSecLease = 60*60; /* 1 hour */ #else pDefCfg->m_cSecLease = 30; /* sec */ #endif pDefCfg->m_TftpServer = "10.0.2.3"; //?? this->addConfig(pDefCfg); #endif } /** * Destruct a DHCP server. */ VBoxNetDhcp::~VBoxNetDhcp() { /* * Close the interface connection. */ if (m_hIf != INTNET_HANDLE_INVALID) { INTNETIFCLOSEREQ CloseReq; CloseReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; CloseReq.Hdr.cbReq = sizeof(CloseReq); CloseReq.pSession = m_pSession; CloseReq.hIf = m_hIf; m_hIf = INTNET_HANDLE_INVALID; int rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_CLOSE, 0, &CloseReq.Hdr); AssertRC(rc); } if (m_pSession) { SUPR3Term(false /*fForced*/); m_pSession = NIL_RTR0PTR; } } /** * Adds a config to the tail. * * @returns See VBoxNetDHCP::validate(). * @param pCfg The config too add. * This object will be consumed by this call! */ int VBoxNetDhcp::addConfig(VBoxNetDhcpCfg *pCfg) { int rc = 0; if (pCfg) { rc = pCfg->validate(); if (!rc) m_Cfgs.push_back(pCfg); else delete pCfg; } return rc; } /** * Explodes the config into leases. * * @remarks This code is brute force and not very fast nor memory efficient. * We will have to revisit this later. * * @remarks If an IP has been reconfigured for a fixed mac address and it's * already leased to a client, we it won't be available until the * client releases its lease or it expires. */ void VBoxNetDhcp::explodeConfig(void) { RTTIMESPEC Now; RTTimeNow(&Now); /* * Remove all non-active leases from the vector and zapping the * config pointers of the once left behind. */ std::vector::iterator Itr = m_Leases.begin(); while (Itr != m_Leases.end()) { if (!Itr->isInUse(&Now)) Itr = m_Leases.erase(Itr); else { Itr->m_pCfg = NULL; Itr++; } } /* * Loop thru the configurations in reverse order, giving the last * configs priority of the newer ones. */ size_t iCfg = m_Cfgs.size(); while (iCfg-- > 0) { VBoxNetDhcpCfg *pCfg = m_Cfgs[iCfg]; /* Expand the IP lease range. */ uint32_t const uLast = RT_N2H_U32(pCfg->m_UpperAddr.u); for (uint32_t i = RT_N2H_U32(pCfg->m_LowerAddr.u); i <= uLast; i++) { RTNETADDRIPV4 IPv4Addr; IPv4Addr.u = RT_H2N_U32(i); /* Check if it exists and is configured. */ VBoxNetDhcpLease *pLease = NULL; for (size_t j = 0; j < m_Leases.size(); j++) if (m_Leases[j].m_IPv4Address.u == IPv4Addr.u) { pLease = &m_Leases[j]; break; } if (pLease) { if (!pLease->m_pCfg) pLease->m_pCfg = pCfg; } else { /* add it. */ VBoxNetDhcpLease NewLease(IPv4Addr, pCfg); m_Leases.push_back(NewLease); debugPrint(10, false, "exploseConfig: new lease %d.%d.%d.%d", IPv4Addr.au8[0], IPv4Addr.au8[1], IPv4Addr.au8[2], IPv4Addr.au8[3]); } } } } /** * Parse the arguments. * * @returns 0 on success, fully bitched exit code on failure. * * @param argc Argument count. * @param argv Argument vector. */ int VBoxNetDhcp::parseArgs(int argc, char **argv) { static const RTGETOPTDEF s_aOptionDefs[] = { { "--name", 'N', RTGETOPT_REQ_STRING }, { "--network", 'n', RTGETOPT_REQ_STRING }, { "--trunk-name", 't', RTGETOPT_REQ_STRING }, { "--trunk-type", 'T', RTGETOPT_REQ_STRING }, { "--mac-address", 'a', RTGETOPT_REQ_MACADDR }, { "--ip-address", 'i', RTGETOPT_REQ_IPV4ADDR }, { "--lease-db", 'D', RTGETOPT_REQ_STRING }, { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, { "--begin-config", 'b', RTGETOPT_REQ_NOTHING }, { "--gateway", 'g', RTGETOPT_REQ_IPV4ADDR }, { "--lower-ip", 'l', RTGETOPT_REQ_IPV4ADDR }, { "--upper-ip", 'u', RTGETOPT_REQ_IPV4ADDR }, { "--netmask", 'm', RTGETOPT_REQ_IPV4ADDR }, }; RTGETOPTSTATE State; int rc = RTGetOptInit(&State, argc, argv, &s_aOptionDefs[0], RT_ELEMENTS(s_aOptionDefs), 0, 0 /*fFlags*/); AssertRCReturn(rc, 49); VBoxNetDhcpCfg *pCurCfg = NULL; for (;;) { RTGETOPTUNION Val; rc = RTGetOpt(&State, &Val); if (!rc) break; switch (rc) { case 'N': m_Name = Val.psz; break; case 'n': m_Network = Val.psz; break; case 't': m_TrunkName = Val.psz; break; case 'T': if (!strcmp(Val.psz, "none")) m_enmTrunkType = kIntNetTrunkType_None; else if (!strcmp(Val.psz, "whatever")) m_enmTrunkType = kIntNetTrunkType_WhateverNone; else if (!strcmp(Val.psz, "netflt")) m_enmTrunkType = kIntNetTrunkType_NetFlt; else if (!strcmp(Val.psz, "netadp")) m_enmTrunkType = kIntNetTrunkType_NetAdp; else if (!strcmp(Val.psz, "srvnat")) m_enmTrunkType = kIntNetTrunkType_SrvNat; else { RTStrmPrintf(g_pStdErr, "Invalid trunk type '%s'\n", Val.psz); return 1; } break; case 'a': m_MacAddress = Val.MacAddr; break; case 'i': m_Ipv4Address = Val.IPv4Addr; break; case 'd': m_LeaseDBName = Val.psz; break; case 'v': m_cVerbosity++; break; /* Begin config. */ case 'b': rc = addConfig(pCurCfg); if (rc) break; pCurCfg = NULL; /* fall thru */ /* config specific ones. */ case 'g': case 'l': case 'u': case 'm': if (!pCurCfg) { pCurCfg = new VBoxNetDhcpCfg(); if (!pCurCfg) { RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: new VBoxDhcpCfg failed\n"); return 1; } } switch (rc) { case 'g': pCurCfg->m_Routers.push_back(Val.IPv4Addr); break; case 'l': pCurCfg->m_LowerAddr = Val.IPv4Addr; break; case 'u': pCurCfg->m_UpperAddr = Val.IPv4Addr; break; case 'm': pCurCfg->m_SubnetMask = Val.IPv4Addr; break; case 0: /* ignore */ break; default: AssertMsgFailed(("%d", rc)); return 1; } break; case 'V': RTPrintf("%sr%u\n", RTBldCfgVersion(), RTBldCfgRevision()); return 1; case 'h': RTPrintf("VBoxNetDHCP Version %s\n" "(C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\n" "All rights reserved.\n" "\n" "Usage: VBoxNetDHCP \n" "\n" "Options:\n", RTBldCfgVersion()); for (size_t i = 0; i < RT_ELEMENTS(s_aOptionDefs); i++) RTPrintf(" -%c, %s\n", s_aOptionDefs[i].iShort, s_aOptionDefs[i].pszLong); return 1; default: rc = RTGetOptPrintError(rc, &Val); RTPrintf("Use --help for more information.\n"); return rc; } } /* * Do the reconfig. (move this later) */ if (!rc) explodeConfig(); return rc; } /** * Tries to connect to the internal network. * * @returns 0 on success, exit code + error message to stderr on failure. */ int VBoxNetDhcp::tryGoOnline(void) { /* * Open the session, load ring-0 and issue the request. */ int rc = SUPR3Init(&m_pSession); if (RT_FAILURE(rc)) { m_pSession = NIL_RTR0PTR; RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: SUPR3Init -> %Rrc", rc); return 1; } char szPath[RTPATH_MAX]; rc = RTPathExecDir(szPath, sizeof(szPath) - sizeof("/VMMR0.r0")); if (RT_FAILURE(rc)) { RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: RTPathProgram -> %Rrc", rc); return 1; } rc = SUPR3LoadVMM(strcat(szPath, "/VMMR0.r0")); if (RT_FAILURE(rc)) { RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: SUPR3LoadVMM(\"%s\") -> %Rrc", szPath, rc); return 1; } /* * Create the open request. */ INTNETOPENREQ OpenReq; OpenReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; OpenReq.Hdr.cbReq = sizeof(OpenReq); OpenReq.pSession = m_pSession; strncpy(OpenReq.szNetwork, m_Network.c_str(), sizeof(OpenReq.szNetwork)); OpenReq.szNetwork[sizeof(OpenReq.szNetwork) - 1] = '\0'; strncpy(OpenReq.szTrunk, m_TrunkName.c_str(), sizeof(OpenReq.szTrunk)); OpenReq.szTrunk[sizeof(OpenReq.szTrunk) - 1] = '\0'; OpenReq.enmTrunkType = m_enmTrunkType; OpenReq.fFlags = 0; /** @todo check this */ OpenReq.cbSend = m_cbSendBuf; OpenReq.cbRecv = m_cbRecvBuf; OpenReq.hIf = INTNET_HANDLE_INVALID; /* * Issue the request. */ debugPrint(2, false, "attempting to open/create network \"%s\"...", OpenReq.szNetwork); rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_OPEN, 0, &OpenReq.Hdr); if (RT_SUCCESS(rc)) { m_hIf = OpenReq.hIf; debugPrint(1, false, "successfully opened/created \"%s\" - hIf=%#x", OpenReq.szNetwork, m_hIf); /* * Get the ring-3 address of the shared interface buffer. */ INTNETIFGETBUFFERPTRSREQ GetBufferPtrsReq; GetBufferPtrsReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; GetBufferPtrsReq.Hdr.cbReq = sizeof(GetBufferPtrsReq); GetBufferPtrsReq.pSession = m_pSession; GetBufferPtrsReq.hIf = m_hIf; GetBufferPtrsReq.pRing3Buf = NULL; GetBufferPtrsReq.pRing0Buf = NIL_RTR0PTR; rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, 0, &GetBufferPtrsReq.Hdr); if (RT_SUCCESS(rc)) { PINTNETBUF pBuf = GetBufferPtrsReq.pRing3Buf; debugPrint(1, false, "pBuf=%p cbBuf=%d cbSend=%d cbRecv=%d", pBuf, pBuf->cbBuf, pBuf->cbSend, pBuf->cbRecv); m_pIfBuf = pBuf; /* * Activate the interface. */ INTNETIFSETACTIVEREQ ActiveReq; ActiveReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; ActiveReq.Hdr.cbReq = sizeof(ActiveReq); ActiveReq.pSession = m_pSession; ActiveReq.hIf = m_hIf; ActiveReq.fActive = true; rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_SET_ACTIVE, 0, &ActiveReq.Hdr); if (RT_SUCCESS(rc)) return 0; /* bail out */ RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE,) failed, rc=%Rrc\n", rc); } else RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS,) failed, rc=%Rrc\n", rc); } else RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_OPEN,) failed, rc=%Rrc\n", rc); return RT_SUCCESS(rc) ? 0 : 1; } /** * Runs the DHCP server. * * @returns exit code + error message to stderr on failure, won't return on * success (you must kill this process). */ int VBoxNetDhcp::run(void) { /* * The loop. */ PINTNETRINGBUF pRingBuf = &m_pIfBuf->Recv; for (;;) { /* * Wait for a packet to become available. */ INTNETIFWAITREQ WaitReq; WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; WaitReq.Hdr.cbReq = sizeof(WaitReq); WaitReq.pSession = m_pSession; WaitReq.hIf = m_hIf; WaitReq.cMillies = 2000; /* 2 secs - the sleep is for some reason uninterruptible... */ /** @todo fix interruptability in SrvIntNet! */ int rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_WAIT, 0, &WaitReq.Hdr); if (RT_FAILURE(rc)) { if (rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED) continue; RTStrmPrintf(g_pStdErr, "VBoxNetDHCP: VMMR0_DO_INTNET_IF_WAIT returned %Rrc\n", rc); return 1; } /* * Process the receive buffer. */ while (IntNetRingHasMoreToRead(pRingBuf)) { size_t cb; void *pv = VBoxNetUDPMatch(m_pIfBuf, RTNETIPV4_PORT_BOOTPS, &m_MacAddress, VBOXNETUDP_MATCH_UNICAST | VBOXNETUDP_MATCH_BROADCAST | VBOXNETUDP_MATCH_CHECKSUM | (m_cVerbosity > 2 ? VBOXNETUDP_MATCH_PRINT_STDERR : 0), &m_CurHdrs, &cb); if (pv && cb) { PCRTNETBOOTP pDhcpMsg = (PCRTNETBOOTP)pv; m_pCurMsg = pDhcpMsg; m_cbCurMsg = cb; uint8_t uMsgType; if (RTNetIPv4IsDHCPValid(NULL /* why is this here? */, pDhcpMsg, cb, &uMsgType)) { m_uCurMsgType = uMsgType; handleDhcpMsg(uMsgType, pDhcpMsg, cb); 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; } else if (VBoxNetArpHandleIt(m_pSession, m_hIf, m_pIfBuf, &m_MacAddress, m_Ipv4Address)) { /* nothing */ } /* Advance to the next frame. */ IntNetRingSkipFrame(pRingBuf); } } return 0; } /** * 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) { switch (uMsgType) { case RTNET_DHCP_MT_DISCOVER: return handleDhcpReqDiscover(pDhcpMsg, cb); case RTNET_DHCP_MT_REQUEST: return handleDhcpReqRequest(pDhcpMsg, cb); case RTNET_DHCP_MT_DECLINE: return handleDhcpReqDecline(pDhcpMsg, cb); case RTNET_DHCP_MT_RELEASE: return handleDhcpReqRelease(pDhcpMsg, cb); case RTNET_DHCP_MT_INFORM: debugPrint(0, true, "Should we handle this?"); break; default: debugPrint(0, true, "Unexpected."); break; } } return false; } /** * The client is requesting an offer. * * @returns true. * * @param pDhcpMsg The message. * @param cb The message size. */ bool VBoxNetDhcp::handleDhcpReqDiscover(PCRTNETBOOTP pDhcpMsg, size_t cb) { /* * The newLease() method contains logic for finding current leases * and reusing them in case the client is forgetful. */ VBoxNetDhcpLease *pLease = newLease(pDhcpMsg, cb); if (!pLease) return false; debugPrint(1, true, "Offering %d.%d.%d.%d to %.6Rhxs xid=%#x", pLease->m_IPv4Address.au8[0], pLease->m_IPv4Address.au8[1], pLease->m_IPv4Address.au8[2], pLease->m_IPv4Address.au8[3], &pDhcpMsg->bp_chaddr.Mac, pDhcpMsg->bp_xid); pLease->offer(pDhcpMsg->bp_xid); makeDhcpReply(RTNET_DHCP_MT_OFFER, pLease, pDhcpMsg, cb); return true; } /** * The client is requesting an offer. * * @returns true. * * @param pDhcpMsg The message. * @param cb The message size. */ bool VBoxNetDhcp::handleDhcpReqRequest(PCRTNETBOOTP pDhcpMsg, size_t cb) { /** @todo Probably need to match the server IP here to work correctly with * other servers. */ /** @todo This code isn't entirely correct and quite a bit of a hack, but it * will have to do for now as the right thing (tm) is very complex. * Part of the fun is verifying that the request is something we can * and should handle. */ /* * Try find the lease by the requested address + client MAC address. */ VBoxNetDhcpLease *pLease = NULL; RTNETADDRIPV4 IPv4Addr; bool fReqAddr = findOptionIPv4Addr(RTNET_DHCP_OPT_REQ_ADDR, pDhcpMsg, cb, &IPv4Addr); if (fReqAddr) { fReqAddr = true; pLease = findLeaseByIpv4AndMacAddresses(IPv4Addr, &pDhcpMsg->bp_chaddr.Mac, true /* fAnyState */); } /* * Try find the lease by the client IP address + client MAC address. */ if ( !pLease && pDhcpMsg->bp_ciaddr.u) pLease = findLeaseByIpv4AndMacAddresses(pDhcpMsg->bp_ciaddr, &pDhcpMsg->bp_chaddr.Mac, true /* fAnyState */); #if 0 /** @todo client id stuff - it doesn't make sense here imho, we need IP + MAC. What would make sense though is to compare the client id with what we've got in the lease and use it to root out bad requests. */ /* * Try find the lease by using the client id. */ if (!pLease) { size_t cbClientID = 0; uint8_t const *pbClientID = findOption(RTNET_DHCP_OPT_CLIENT_ID, pDhcpMsg, cb, &cbClientID); if ( pbClientID && cbClientID == sizeof(RTMAC) + 1 && pbClientID[0] == RTNET_ARP_ETHER && ) { pLease = findLeaseByIpv4AndMacAddresses(pDhcpMsg->bp_ciaddr, &pDhcpMsg->bp_chaddr.Mac, true /* fAnyState */); } } #endif /* * Validate the lease that's requested. * We've already check the MAC and IP addresses. */ bool fAckIt = false; if (pLease) { if (pLease->isBeingOffered()) { if (pLease->m_xid == pDhcpMsg->bp_xid) debugPrint(2, true, "REQUEST for offered lease."); else debugPrint(2, true, "REQUEST for offered lease, xid mismatch. Expected %#x, got %#x.", pLease->m_xid, pDhcpMsg->bp_xid); pLease->activate(pDhcpMsg->bp_xid); fAckIt = true; } else if (!pLease->isInCurrentConfig()) debugPrint(1, true, "REQUEST for obsolete lease -> NAK"); else if (fReqAddr != (pDhcpMsg->bp_ciaddr.u != 0)) // ??? { /** @todo this ain't safe. */ debugPrint(1, true, "REQUEST for lease not on offer, assuming renewal. lease_xid=%#x bp_xid=%#x", pLease->m_xid, pDhcpMsg->bp_xid); fAckIt = true; pLease->activate(pDhcpMsg->bp_xid); } else debugPrint(1, true, "REQUEST for lease not on offer, NAK it."); } /* * NAK if if no lease was found. */ if (fAckIt) { debugPrint(1, false, "ACK'ing DHCP_REQUEST"); makeDhcpReply(RTNET_DHCP_MT_ACK, pLease, pDhcpMsg, cb); } else { debugPrint(1, false, "NAK'ing DHCP_REQUEST"); makeDhcpReply(RTNET_DHCP_MT_NAC, NULL, pDhcpMsg, cb); } return true; } /** * The client is declining an offer we've made. * * @returns true. * * @param pDhcpMsg The message. * @param cb The message size. */ bool VBoxNetDhcp::handleDhcpReqDecline(PCRTNETBOOTP pDhcpMsg, size_t cb) { /** @todo Probably need to match the server IP here to work correctly with * other servers. */ /* * The client is supposed to pass us option 50, requested address, * from the offer. We also match the lease state. Apparently the * MAC address is not supposed to be checked here. */ /** @todo this is not required in the initial implementation, do it later. */ debugPrint(1, true, "DECLINE is not implemented"); return true; } /** * The client is releasing its lease - good boy. * * @returns true. * * @param pDhcpMsg The message. * @param cb The message size. */ bool VBoxNetDhcp::handleDhcpReqRelease(PCRTNETBOOTP pDhcpMsg, size_t cb) { /** @todo Probably need to match the server IP here to work correctly with * other servers. */ /* * The client may pass us option 61, client identifier, which we should * use to find the lease by. * * We're matching MAC address and lease state as well. */ /* * If no client identifier or if we couldn't find a lease by using it, * we will try look it up by the client IP address. */ /* * If found, release it. */ /** @todo this is not required in the initial implementation, do it later. */ debugPrint(1, true, "RELEASE is not implemented"); return true; } /** * Helper class for stuffing DHCP options into a reply packet. */ class VBoxNetDhcpWriteCursor { private: uint8_t *m_pbCur; /**< The current cursor position. */ uint8_t *m_pbEnd; /**< The end the current option space. */ uint8_t *m_pfOverload; /**< Pointer to the flags of the overload option. */ uint8_t m_fUsed; /**< Overload fields that have been used. */ PRTNETDHCPOPT m_pOpt; /**< The current option. */ PRTNETBOOTP m_pDhcp; /**< The DHCP packet. */ bool m_fOverflowed; /**< Set if we've overflowed, otherwise false. */ public: /** Instantiate an option cursor for the specified DHCP message. */ VBoxNetDhcpWriteCursor(PRTNETBOOTP pDhcp, size_t cbDhcp) : m_pbCur(&pDhcp->bp_vend.Dhcp.dhcp_opts[0]), m_pbEnd((uint8_t *)pDhcp + cbDhcp), m_pfOverload(NULL), m_fUsed(0), m_pOpt(NULL), m_pDhcp(pDhcp), m_fOverflowed(false) { AssertPtr(pDhcp); Assert(cbDhcp > RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts[10])); } /** Destructor. */ ~VBoxNetDhcpWriteCursor() { m_pbCur = m_pbEnd = m_pfOverload = NULL; m_pOpt = NULL; m_pDhcp = NULL; } /** * Try use the bp_file field. * @returns true if not overloaded, false otherwise. */ bool useBpFile(void) { if ( m_pfOverload && (*m_pfOverload & 1)) return false; m_fUsed |= 1 /* bp_file flag*/; return true; } /** * Try overload more BOOTP fields */ bool overloadMore(void) { /* switch option area. */ uint8_t *pbNew; uint8_t *pbNewEnd; uint8_t fField; if (!(m_fUsed & 1)) { fField = 1; pbNew = &m_pDhcp->bp_file[0]; pbNewEnd = &m_pDhcp->bp_file[sizeof(m_pDhcp->bp_file)]; } else if (!(m_fUsed & 2)) { fField = 2; pbNew = &m_pDhcp->bp_sname[0]; pbNewEnd = &m_pDhcp->bp_sname[sizeof(m_pDhcp->bp_sname)]; } else return false; if (!m_pfOverload) { /* Add an overload option. */ *m_pbCur++ = RTNET_DHCP_OPT_OPTION_OVERLOAD; *m_pbCur++ = fField; m_pfOverload = m_pbCur; *m_pbCur++ = 1; /* bp_file flag */ } else *m_pfOverload |= fField; /* pad current option field */ while (m_pbCur != m_pbEnd) *m_pbCur++ = RTNET_DHCP_OPT_PAD; /** @todo not sure if this stuff is at all correct... */ /* switch */ m_pbCur = pbNew; m_pbEnd = pbNewEnd; return true; } /** * Begin an option. * * @returns true on success, false if we're out of space. * * @param uOption The option number. * @param cb The amount of data. */ bool begin(uint8_t uOption, size_t cb) { /* Check that the data of the previous option has all been written. */ Assert( !m_pOpt || (m_pbCur - m_pOpt->dhcp_len == (uint8_t *)(m_pOpt + 1))); AssertMsg(cb <= 255, ("%#x\n", cb)); /* Check if we need to overload more stuff. */ if ((uintptr_t)(m_pbEnd - m_pbCur) < cb + 2 + (m_pfOverload ? 1 : 3)) { m_pOpt = NULL; if (!overloadMore()) { m_fOverflowed = true; AssertMsgFailedReturn(("%u %#x\n", uOption, cb), false); } if ((uintptr_t)(m_pbEnd - m_pbCur) < cb + 2 + 1) { m_fOverflowed = true; AssertMsgFailedReturn(("%u %#x\n", uOption, cb), false); } } /* Emit the option header. */ m_pOpt = (PRTNETDHCPOPT)m_pbCur; m_pOpt->dhcp_opt = uOption; m_pOpt->dhcp_len = (uint8_t)cb; m_pbCur += 2; return true; } /** * Puts option data. * * @param pvData The data. * @param cb The amount to put. */ void put(void const *pvData, size_t cb) { Assert(m_pOpt || m_fOverflowed); if (RT_LIKELY(m_pOpt)) { Assert((uintptr_t)m_pbCur - (uintptr_t)(m_pOpt + 1) + cb <= (size_t)m_pOpt->dhcp_len); memcpy(m_pbCur, pvData, cb); m_pbCur += cb; } } /** * Puts an IPv4 Address. * * @param IPv4Addr The address. */ void putIPv4Addr(RTNETADDRIPV4 IPv4Addr) { put(&IPv4Addr, 4); } /** * Adds an IPv4 address option. * * @returns true/false just like begin(). * * @param uOption The option number. * @param IPv4Addr The address. */ bool optIPv4Addr(uint8_t uOption, RTNETADDRIPV4 IPv4Addr) { if (!begin(uOption, 4)) return false; putIPv4Addr(IPv4Addr); return true; } /** * Adds an option taking 1 or more IPv4 address. * * If the vector contains no addresses, the option will not be added. * * @returns true/false just like begin(). * * @param uOption The option number. * @param rIPv4Addrs Reference to the address vector. */ bool optIPv4Addrs(uint8_t uOption, std::vector const &rIPv4Addrs) { size_t const c = rIPv4Addrs.size(); if (!c) return true; if (!begin(uOption, 4*c)) return false; for (size_t i = 0; i < c; i++) putIPv4Addr(rIPv4Addrs[i]); return true; } /** * Puts an 8-bit integer. * * @param u8 The integer. */ void putU8(uint8_t u8) { put(&u8, 1); } /** * Adds an 8-bit integer option. * * @returns true/false just like begin(). * * @param uOption The option number. * @param u8 The integer */ bool optU8(uint8_t uOption, uint8_t u8) { if (!begin(uOption, 1)) return false; putU8(u8); return true; } /** * Puts an 32-bit integer (network endian). * * @param u32Network The integer. */ void putU32(uint32_t u32) { put(&u32, 4); } /** * Adds an 32-bit integer (network endian) option. * * @returns true/false just like begin(). * * @param uOption The option number. * @param u32Network The integer. */ bool optU32(uint8_t uOption, uint32_t u32) { if (!begin(uOption, 4)) return false; putU32(u32); return true; } /** * Puts a std::string. * * @param rStr Reference to the string. */ void putStr(std::string const &rStr) { put(rStr.c_str(), rStr.size()); } /** * Adds an std::string option if the string isn't empty. * * @returns true/false just like begin(). * * @param uOption The option number. * @param rStr Reference to the string. */ bool optStr(uint8_t uOption, std::string const &rStr) { const size_t cch = rStr.size(); if (!cch) return true; if (!begin(uOption, cch)) return false; put(rStr.c_str(), cch); return true; } /** * Whether we've overflowed. * * @returns true on overflow, false otherwise. */ bool hasOverflowed(void) const { return m_fOverflowed; } /** * Adds the terminating END option. * * The END will always be added as we're reserving room for it, however, we * might have dropped previous options due to overflows and that is what the * return status indicates. * * @returns true on success, false on a (previous) overflow. */ bool optEnd(void) { Assert((uintptr_t)(m_pbEnd - m_pbCur) < 4096); *m_pbCur++ = RTNET_DHCP_OPT_END; return !hasOverflowed(); } }; /** * Constructs and sends a reply to a client. * * @returns * @param uMsgType The DHCP message type. * @param pLease The lease. This can be NULL for some replies. * @param pDhcpMsg The client message. We will dig out the MAC address, * transaction ID, and requested options from this. * @param cb The size of the client message. */ void VBoxNetDhcp::makeDhcpReply(uint8_t uMsgType, VBoxNetDhcpLease *pLease, PCRTNETBOOTP pDhcpMsg, size_t cb) { size_t cbReply = RTNET_DHCP_NORMAL_SIZE; /** @todo respect the RTNET_DHCP_OPT_MAX_DHCP_MSG_SIZE option */ PRTNETBOOTP pReply = (PRTNETBOOTP)alloca(cbReply); /* * The fixed bits stuff. */ pReply->bp_op = RTNETBOOTP_OP_REPLY; pReply->bp_htype = RTNET_ARP_ETHER; pReply->bp_hlen = sizeof(RTMAC); pReply->bp_hops = 0; pReply->bp_xid = pDhcpMsg->bp_xid; pReply->bp_secs = 0; pReply->bp_flags = 0; // (pDhcpMsg->bp_flags & RTNET_DHCP_FLAGS_NO_BROADCAST); ?? pReply->bp_ciaddr.u = 0; pReply->bp_yiaddr.u = pLease ? pLease->m_IPv4Address.u : 0xffffffff; pReply->bp_siaddr.u = pLease && pLease->m_pCfg ? pLease->m_pCfg->m_TftpServerAddr.u : 0; /* (next server == TFTP)*/ pReply->bp_giaddr.u = 0; memset(&pReply->bp_chaddr, '\0', sizeof(pReply->bp_chaddr)); pReply->bp_chaddr.Mac = pDhcpMsg->bp_chaddr.Mac; memset(&pReply->bp_sname[0], '\0', sizeof(pReply->bp_sname)); memset(&pReply->bp_file[0], '\0', sizeof(pReply->bp_file)); pReply->bp_vend.Dhcp.dhcp_cookie = RT_H2N_U32_C(RTNET_DHCP_COOKIE); memset(&pReply->bp_vend.Dhcp.dhcp_opts[0], '\0', RTNET_DHCP_OPT_SIZE); /* * The options - use a cursor class for dealing with the ugly stuff. */ VBoxNetDhcpWriteCursor Cursor(pReply, cbReply); /* The basics */ Cursor.optU8(RTNET_DHCP_OPT_MSG_TYPE, uMsgType); Cursor.optIPv4Addr(RTNET_DHCP_OPT_SERVER_ID, m_Ipv4Address); if (uMsgType != RTNET_DHCP_MT_NAC) { AssertReturnVoid(pLease && pLease->m_pCfg); const VBoxNetDhcpCfg *pCfg = pLease->m_pCfg; /* no need to retain it. */ /* The IP config. */ Cursor.optU32(RTNET_DHCP_OPT_LEASE_TIME, RT_H2N_U32(pCfg->m_cSecLease)); Cursor.optIPv4Addr(RTNET_DHCP_OPT_SUBNET_MASK, pCfg->m_SubnetMask); Cursor.optIPv4Addrs(RTNET_DHCP_OPT_ROUTERS, pCfg->m_Routers); Cursor.optIPv4Addrs(RTNET_DHCP_OPT_ROUTERS, pCfg->m_DNSes); Cursor.optStr(RTNET_DHCP_OPT_HOST_NAME, pCfg->m_HostName); Cursor.optStr(RTNET_DHCP_OPT_DOMAIN_NAME, pCfg->m_DomainName); /* The PXE config. */ if (pCfg->m_BootfileName.size()) { if (Cursor.useBpFile()) RTStrPrintf((char *)&pReply->bp_file[0], sizeof(pReply->bp_file), "%s", pCfg->m_BootfileName.c_str()); else Cursor.optStr(RTNET_DHCP_OPT_BOOTFILE_NAME, pCfg->m_BootfileName); } } /* Terminate the options. */ if (!Cursor.optEnd()) debugPrint(0, true, "option overflow\n"); /* * Send it. */ int rc; #if 0 if (!(pDhcpMsg->bp_flags & RTNET_DHCP_FLAGS_NO_BROADCAST)) /** @todo need to see someone set this flag to check that it's correct. */ { RTNETADDRIPV4 IPv4AddrBrdCast; IPv4AddrBrdCast.u = UINT32_C(0xffffffff); /* broadcast IP */ rc = VBoxNetUDPUnicast(m_pSession, m_hIf, m_pIfBuf, m_Ipv4Address, &m_MacAddress, RTNETIPV4_PORT_BOOTPS, /* sender */ IPv4AddrBrdCast, &pDhcpMsg->bp_chaddr.Mac, RTNETIPV4_PORT_BOOTPC, /* receiver */ pReply, cbReply); } else #endif rc = VBoxNetUDPBroadcast(m_pSession, m_hIf, m_pIfBuf, m_Ipv4Address, &m_MacAddress, RTNETIPV4_PORT_BOOTPS, /* sender */ RTNETIPV4_PORT_BOOTPC, /* receiver port */ pReply, cbReply); if (RT_FAILURE(rc)) debugPrint(0, true, "error %Rrc when sending the reply", rc); } /** * Look up a lease by MAC address. * * @returns Pointer to the lease if found, NULL if not found. * @param pMacAddress The mac address. * @param fAnyState Any state. */ VBoxNetDhcpLease *VBoxNetDhcp::findLeaseByMacAddress(PCRTMAC pMacAddress, bool fAnyState) { size_t iLease = m_Leases.size(); while (iLease-- > 0) { VBoxNetDhcpLease *pLease = &m_Leases[iLease]; if ( pLease && pLease->m_MacAddress.au16[0] == pMacAddress->au16[0] && pLease->m_MacAddress.au16[1] == pMacAddress->au16[1] && pLease->m_MacAddress.au16[2] == pMacAddress->au16[2] && ( fAnyState || (pLease->m_enmState != VBoxNetDhcpLease::kState_Free)) ) return pLease; } return NULL; } /** * Look up a lease by IPv4 and MAC addresses. * * @returns Pointer to the lease if found, NULL if not found. * @param IPv4Addr The IPv4 address. * @param pMacAddress The mac address. * @param fAnyState Any state. */ VBoxNetDhcpLease *VBoxNetDhcp::findLeaseByIpv4AndMacAddresses(RTNETADDRIPV4 IPv4Addr, PCRTMAC pMacAddress, bool fAnyState) { size_t iLease = m_Leases.size(); while (iLease-- > 0) { VBoxNetDhcpLease *pLease = &m_Leases[iLease]; if ( pLease && pLease->m_IPv4Address.u == IPv4Addr.u && pLease->m_MacAddress.au16[0] == pMacAddress->au16[0] && pLease->m_MacAddress.au16[1] == pMacAddress->au16[1] && pLease->m_MacAddress.au16[2] == pMacAddress->au16[2] && ( fAnyState || (pLease->m_enmState != VBoxNetDhcpLease::kState_Free)) ) return pLease; } return NULL; } /** * Creates a new lease for the client specified in the DHCP message. * * The caller has already made sure it doesn't already have a lease. * * @returns Pointer to the lease if found, NULL+log if not found. * @param IPv4Addr The IPv4 address. * @param pMacAddress The MAC address. */ VBoxNetDhcpLease *VBoxNetDhcp::newLease(PCRTNETBOOTP pDhcpMsg, size_t cb) { RTMAC const MacAddr = pDhcpMsg->bp_chaddr.Mac; RTTIMESPEC Now; RTTimeNow(&Now); /* * Search the possible leases. * * We'll try do all the searches in one pass, that is to say, perfect * match, old lease, and next free/expired lease. */ VBoxNetDhcpLease *pBest = NULL; VBoxNetDhcpLease *pOld = NULL; VBoxNetDhcpLease *pFree = NULL; size_t cLeases = m_Leases.size(); for (size_t i = 0; i < cLeases; i++) { VBoxNetDhcpLease *pCur = &m_Leases[i]; /* Skip it if no configuration, that means its not in the current config. */ if (!pCur->m_pCfg) continue; /* best */ if ( pCur->isOneSpecificClient() && pCur->m_pCfg->matchesMacAddress(&MacAddr)) { if ( !pBest || pBest->m_pCfg->m_MacAddresses.size() < pCur->m_pCfg->m_MacAddresses.size()) pBest = pCur; } /* old lease */ if ( pCur->m_MacAddress.au16[0] == MacAddr.au16[0] && pCur->m_MacAddress.au16[1] == MacAddr.au16[1] && pCur->m_MacAddress.au16[2] == MacAddr.au16[2]) { if ( !pOld || RTTimeSpecGetSeconds(&pCur->m_ExpireTime) > RTTimeSpecGetSeconds(&pFree->m_ExpireTime)) pOld = pCur; } /* expired lease */ if (!pCur->isInUse(&Now)) { if ( !pFree || RTTimeSpecGetSeconds(&pCur->m_ExpireTime) < RTTimeSpecGetSeconds(&pFree->m_ExpireTime)) pFree = pCur; } } VBoxNetDhcpLease *pNew = pBest; if (!pNew) pNew = pOld; if (!pNew) pNew = pFree; if (!pNew) { debugPrint(0, true, "No more leases."); return NULL; } /* * Init the lease. */ pNew->m_MacAddress = MacAddr; pNew->m_xid = pDhcpMsg->bp_xid; /** @todo extract the client id. */ return pNew; } /** * Finds an option. * * @returns On success, a pointer to the first byte in the option data (no none * then it'll be the byte following the 0 size field) and *pcbOpt set * to the option length. * On failure, NULL is returned and *pcbOpt unchanged. * * @param uOption The option to search for. * @param pDhcpMsg The DHCP message. * @param cb The size of the message. * @param pcbOpt Where to store the option size size. Optional. Note * that this is adjusted if the option length is larger * than the message buffer. */ /* static */ const uint8_t * VBoxNetDhcp::findOption(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cb, size_t *pcbOpt) { Assert(uOption != RTNET_DHCP_OPT_PAD); /* * Validate the DHCP bits and figure the max size of the options in the vendor field. */ if (cb <= RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts)) return NULL; if (pDhcpMsg->bp_vend.Dhcp.dhcp_cookie != RT_H2N_U32_C(RTNET_DHCP_COOKIE)) return NULL; size_t cbLeft = cb - RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts); if (cbLeft > RTNET_DHCP_OPT_SIZE) cbLeft = RTNET_DHCP_OPT_SIZE; /* * Search the vendor field. */ bool fExtended = false; uint8_t const *pb = &pDhcpMsg->bp_vend.Dhcp.dhcp_opts[0]; while (pb && cbLeft > 0) { uint8_t uCur = *pb; if (uCur == RTNET_DHCP_OPT_PAD) { cbLeft--; pb++; } else if (cbLeft <= 1) break; else { size_t cbCur = pb[1]; if (cbCur > cbLeft - 2) cbCur = cbLeft - 2; if (uCur == uOption) { if (pcbOpt) *pcbOpt = cbCur; return pb+2; } pb += cbCur + 2; cbLeft -= cbCur - 2; } } /** @todo search extended dhcp option field(s) when present */ return NULL; } /** * Locates an option with an IPv4 address in the DHCP message. * * @returns true and *pIpv4Addr if found, false if not. * * @param uOption The option to find. * @param pDhcpMsg The DHCP message. * @param cb The size of the message. * @param pIPv4Addr Where to put the address. */ /* static */ bool VBoxNetDhcp::findOptionIPv4Addr(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cb, PRTNETADDRIPV4 pIPv4Addr) { size_t cbOpt; uint8_t const *pbOpt = findOption(uOption, pDhcpMsg, cb, &cbOpt); if (pbOpt) { if (cbOpt >= sizeof(RTNETADDRIPV4)) { *pIPv4Addr = *(PCRTNETADDRIPV4)pbOpt; return true; } } 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 ... Optional arguments. */ inline void VBoxNetDhcp::debugPrint(int32_t iMinLevel, bool fMsg, const char *pszFmt, ...) const { if (iMinLevel <= m_cVerbosity) { va_list va; va_start(va, pszFmt); debugPrintV(iMinLevel, fMsg, pszFmt, va); va_end(va); } } /** * 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) { 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"; } } /** * Entry point. */ extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp) { /* * 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; /* * Try connect the server to the network. */ rc = pDhcp->tryGoOnline(); if (rc) { delete pDhcp; return rc; } /* * Process requests. */ g_pDhcp = pDhcp; rc = pDhcp->run(); g_pDhcp = NULL; delete pDhcp; return rc; } #ifndef VBOX_WITH_HARDENING int main(int argc, char **argv, char **envp) { int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); if (RT_FAILURE(rc)) return RTMsgInitFailure(rc); return TrustedMain(argc, argv, envp); } # 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 LPCSTR g_WndClassName = "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, environ); } # endif /* RT_OS_WINDOWS */ #endif /* !VBOX_WITH_HARDENING */