diff options
| author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2012-10-26 16:25:44 +0000 |
|---|---|---|
| committer | <> | 2012-11-12 12:15:52 +0000 |
| commit | 58ed4748338f9466599adfc8a9171280ed99e23f (patch) | |
| tree | 02027d99ded4fb56a64aa9489ac2eb487e7858ab /src/VBox/NetworkServices | |
| download | VirtualBox-58ed4748338f9466599adfc8a9171280ed99e23f.tar.gz | |
Imported from /home/lorry/working-area/delta_VirtualBox/VirtualBox-4.2.4.tar.bz2.VirtualBox-4.2.4
Diffstat (limited to 'src/VBox/NetworkServices')
| -rw-r--r-- | src/VBox/NetworkServices/DHCP/Makefile.kmk | 56 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/DHCP/VBoxNetDHCP.cpp | 2147 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/DHCP/VBoxNetDHCPHardened.cpp | 25 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/Makefile.kmk | 28 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/NAT/Makefile.kmk | 58 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/NAT/VBoxNetNAT.cpp | 815 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp | 24 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/NetLib/Makefile.kup | 0 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/NetLib/VBoxNetARP.cpp | 154 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/NetLib/VBoxNetBaseService.cpp | 363 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/NetLib/VBoxNetBaseService.h | 61 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/NetLib/VBoxNetIntIf.cpp | 137 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/NetLib/VBoxNetLib.h | 69 | ||||
| -rw-r--r-- | src/VBox/NetworkServices/NetLib/VBoxNetUDP.cpp | 303 |
14 files changed, 4240 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/DHCP/Makefile.kmk b/src/VBox/NetworkServices/DHCP/Makefile.kmk new file mode 100644 index 00000000..f04b6285 --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/Makefile.kmk @@ -0,0 +1,56 @@ + # $Id: Makefile.kmk $ +## @file +# Sub-Makefile for VBoxNetDHCP. +# + +# +# Copyright (C) 2009-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; +# 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. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Targets. +# +ifdef VBOX_WITH_HARDENING + PROGRAMS += VBoxNetDHCPHardened + DLLS += VBoxNetDHCP +else + PROGRAMS += VBoxNetDHCP +endif + + +# +# Hardened VBoxNetDHCP. +# +VBoxNetDHCPHardened_TEMPLATE = VBOXR3HARDENEDEXE +VBoxNetDHCPHardened_SOURCES = VBoxNetDHCPHardened.cpp +VBoxNetDHCPHardened_NAME = VBoxNetDHCP + + +# +# VBoxNetDHCP +# +VBoxNetDHCP_TEMPLATE = +VBoxNetDHCP_TEMPLATE := VBOXR3$(if-expr defined(VBOX_WITH_HARDENING),,EXE) +VBoxNetDHCP_SOURCES = \ + VBoxNetDHCP.cpp \ + ../NetLib/VBoxNetIntIf.cpp \ + ../NetLib/VBoxNetUDP.cpp \ + ../NetLib/VBoxNetARP.cpp +VBoxNetDHCP_LIBS = \ + $(LIB_RUNTIME) +VBoxNetDHCP_LDFLAGS.win = /SUBSYSTEM:windows + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/NetworkServices/DHCP/VBoxNetDHCP.cpp b/src/VBox/NetworkServices/DHCP/VBoxNetDHCP.cpp new file mode 100644 index 00000000..2ea54b66 --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/VBoxNetDHCP.cpp @@ -0,0 +1,2147 @@ +/* $Id: VBoxNetDHCP.cpp $ */ +/** @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 <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 <vector> +#include <string> + +#ifdef RT_OS_WINDOWS /* WinMain */ +# include <Windows.h> +# include <stdlib.h> +#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<RTMAC> 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<RTNETADDRIPV4> m_Routers; + /* * Option 4: Time server. */ + /* * Option 5: Name server. */ + /** Option 6: Domain Name Server (DNS) */ + std::vector<RTNETADDRIPV4> 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<VBoxNetDhcpCfg *> m_Cfgs; + + /** The current leases. */ + std::vector<VBoxNetDhcpLease> 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<VBoxNetDhcpLease>::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 <options>\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<RTNETADDRIPV4> 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 */ + diff --git a/src/VBox/NetworkServices/DHCP/VBoxNetDHCPHardened.cpp b/src/VBox/NetworkServices/DHCP/VBoxNetDHCPHardened.cpp new file mode 100644 index 00000000..7d0e1bef --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/VBoxNetDHCPHardened.cpp @@ -0,0 +1,25 @@ +/* $Id: VBoxNetDHCPHardened.cpp $ */ +/** @file + * VBoxNetDHCP - Hardened main(). + */ + +/* + * 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. + */ + +#include <VBox/sup.h> + + +int main(int argc, char **argv, char **envp) +{ + return SUPR3HardenedMain("VBoxNetDHCP", 0 /* fFlags */, argc, argv, envp); +} + diff --git a/src/VBox/NetworkServices/Makefile.kmk b/src/VBox/NetworkServices/Makefile.kmk new file mode 100644 index 00000000..7d0e87e3 --- /dev/null +++ b/src/VBox/NetworkServices/Makefile.kmk @@ -0,0 +1,28 @@ +# $Id: Makefile.kmk $ +## @file +# Top-level makefile for the VBox Network Services. +# + +# +# Copyright (C) 2009-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; +# 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. +# + +SUB_DEPTH = ../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefiles. +include $(PATH_SUB_CURRENT)/DHCP/Makefile.kmk +ifdef VBOX_WITH_NAT_SERVICE +include $(PATH_SUB_CURRENT)/NAT/Makefile.kmk +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/NetworkServices/NAT/Makefile.kmk b/src/VBox/NetworkServices/NAT/Makefile.kmk new file mode 100644 index 00000000..9c821cab --- /dev/null +++ b/src/VBox/NetworkServices/NAT/Makefile.kmk @@ -0,0 +1,58 @@ +# $Id: Makefile.kmk $ +## @file +# + +# +# 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; +# 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. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +ifdef VBOX_WITH_HARDENING + PROGRAMS += VBoxNetNATHardened + DLLS += VBoxNetNAT +else + PROGRAMS += VBoxNetNAT +endif +VBoxNetNAT_TEMPLATE = +VBoxNetNAT_TEMPLATE := VBOXR3$(if-expr defined(VBOX_WITH_HARDENING),,EXE) +VBoxNetNAT_INCS = ../../Devices/Network/slirp +VBoxNetNAT_DEFS = VBOX_WITH_NAT_SERVICE +VBoxNetNAT_SOURCES += VBoxNetNAT.cpp +VBoxNetNAT_DEFS += VBOX_WITH_NAT_SERVICE + +#define def_vbox_slirp_service_cflags +# $(file)_DEFS += VBOX_WITH_NAT_SERVICE +#endef + +define def_vbox_slirp_service_sources + VBoxNetNAT_SOURCES += $1/$(file) +endef +VBOX_NOT_IN_NATSERVICE = Network/DrvNAT.cpp +$(foreach file,$(filter-out $(VBOX_NOT_IN_NATSERVICE), $(VBOX_SLIRP_SOURCES)),$(eval $(call def_vbox_slirp_service_sources, ../../Devices))) +$(foreach file,$(VBOX_SLIRP_ALIAS_SOURCES),$(eval $(call def_vbox_slirp_service_sources, ../../Devices))) +$(foreach file,$(VBOX_SLIRP_BSD_SOURCES),$(eval $(call def_vbox_slirp_service_sources, ../../Devices))) + +$(foreach file,$(addprefix ../../Devices/, $(VBOX_SLIRP_BSD_SOURCES)),$(eval $(call def_vbox_slirp_cflags, ../../Devices/Network))) +$(foreach file,$(addprefix ../../Devices/, $(VBOX_SLIRP_BSD_SOURCES)),$(eval $(call def_vbox_slirp_bsd_cflags, ../../Devices/Network))) +$(foreach file,$(addprefix ../../Devices/, $(filter-out $(VBOX_WITH_NAT_SERVICE), $(VBOX_SLIRP_SOURCES))),$(eval $(call def_vbox_slirp_service_cflags, ../../Devices/Network))) +$(foreach file,$(addprefix ../../Devices/, $(filter-out $(VBOX_WITH_NAT_SERVICE), $(VBOX_SLIRP_SOURCES))),$(eval $(call def_vbox_slirp_cflags, ../../Devices/Network))) +$(foreach file,$(addprefix ../../Devices/, $(VBOX_SLIRP_ALIAS_SOURCES)),$(eval $(call def_vbox_slirp_cflags, ../../Devices/Network))) +$(foreach file,$(addprefix ../../Devices/, $(VBOX_SLIRP_ALIAS_SOURCES)),$(eval $(call def_vbox_slirp_alias_cflags, ../../Devices/Network))) + +VBoxNetNAT_SOURCES += ../NetLib/VBoxNetBaseService.cpp + +VBoxNetNAT_LIBS = \ + $(LIB_RUNTIME) +VBoxNetNAT_LDFLAGS.win = /SUBSYSTEM:windows + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/NetworkServices/NAT/VBoxNetNAT.cpp b/src/VBox/NetworkServices/NAT/VBoxNetNAT.cpp new file mode 100644 index 00000000..d031777c --- /dev/null +++ b/src/VBox/NetworkServices/NAT/VBoxNetNAT.cpp @@ -0,0 +1,815 @@ +/* $Id: VBoxNetNAT.cpp $ */ +/** @file + * VBoxNetNAT - NAT 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_nat VBoxNetNAT + * + * Write a few words... + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include <iprt/net.h> +#include <iprt/initterm.h> +#include <iprt/alloca.h> +#include <iprt/err.h> +#include <iprt/time.h> +#include <iprt/timer.h> +#include <iprt/thread.h> +#include <iprt/stream.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/pipe.h> +#include <iprt/getopt.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/req.h> +#include <iprt/file.h> +#include <iprt/semaphore.h> +#define LOG_GROUP LOG_GROUP_NAT_SERVICE +#include <VBox/log.h> + +#include <VBox/sup.h> +#include <VBox/intnet.h> +#include <VBox/intnetinline.h> +#include <VBox/vmm/pdmnetinline.h> +#include <VBox/vmm/vmm.h> +#include <VBox/version.h> + +#include <vector> +#include <string> + +#include "../NetLib/VBoxNetLib.h" +#include "../NetLib/VBoxNetBaseService.h" +#include <libslirp.h> + +#ifdef RT_OS_WINDOWS /* WinMain */ +# include <Windows.h> +# include <stdlib.h> +#else +# include <errno.h> +#endif + + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +static RTGETOPTDEF g_aGetOptDef[] = +{ + { "--pf", 'p', RTGETOPT_REQ_STRING } +}; + +typedef struct NATSEVICEPORTFORWARDRULE +{ + char *pszPortForwardRuleName; + struct in_addr IpV4HostAddr; + uint16_t u16HostPort; + struct in_addr IpV4GuestAddr; + uint16_t u16GuestPort; + bool fUdp; + char *pszStrRaw; +} NATSEVICEPORTFORWARDRULE, *PNATSEVICEPORTFORWARDRULE; + +class VBoxNetNAT : public VBoxNetBaseService +{ +public: + VBoxNetNAT(); + virtual ~VBoxNetNAT(); + void usage(void); + void run(void); + virtual int init(void); + virtual int parseOpt(int rc, const RTGETOPTUNION& getOptVal); + +public: + PNATState m_pNATState; + RTNETADDRIPV4 m_Ipv4Netmask; + bool m_fPassDomain; + RTTHREAD m_ThrNAT; + RTTHREAD m_ThrSndNAT; + RTTHREAD m_ThrUrgSndNAT; +#ifdef RT_OS_WINDOWS + HANDLE m_hWakeupEvent; +#else + RTPIPE m_hPipeWrite; + RTPIPE m_hPipeRead; +#endif + /** Queue for NAT-thread-external events. */ + /** event to wakeup the guest receive thread */ + RTSEMEVENT m_EventSend; + /** event to wakeup the guest urgent receive thread */ + RTSEMEVENT m_EventUrgSend; + + RTREQQUEUE m_hReqQueue; + RTREQQUEUE m_hSendQueue; + RTREQQUEUE m_hUrgSendQueue; + volatile uint32_t cUrgPkt; + volatile uint32_t cPkt; + bool fIsRunning; + std::vector<PNATSEVICEPORTFORWARDRULE> m_vecPortForwardRuleFromCmdLine; +}; + + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +/** Pointer to the NAT server. */ +class VBoxNetNAT *g_pNAT; +static DECLCALLBACK(int) AsyncIoThread(RTTHREAD pThread, void *pvUser); +static DECLCALLBACK(int) natSndThread(RTTHREAD pThread, void *pvUser); +static DECLCALLBACK(int) natUrgSndThread(RTTHREAD pThread, void *pvUser); +static void SendWorker(struct mbuf *m, size_t cb); +static void IntNetSendWorker(bool urg, void *pvFrame, size_t cbFrame, struct mbuf *m); + + +static void natNotifyNATThread(void) +{ + int rc; +#ifndef RT_OS_WINDOWS + /* kick select() */ + size_t cbIgnored; + rc = RTPipeWrite(g_pNAT->m_hPipeWrite, "", 1, &cbIgnored); +#else + /* kick WSAWaitForMultipleEvents */ + rc = WSASetEvent(g_pNAT->hWakeupEvent); +#endif + AssertRC(rc); +} + +VBoxNetNAT::VBoxNetNAT() +{ +#if defined(RT_OS_WINDOWS) + /*@todo check if we can remove this*/ + VBoxNetBaseService(); +#endif + m_enmTrunkType = kIntNetTrunkType_WhateverNone; + m_TrunkName = ""; + 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, 2))); + m_Ipv4Netmask.u = RT_H2N_U32_C(0xffffff); + cPkt = 0; + cUrgPkt = 0; + VBoxNetBaseService::init(); + for(unsigned int i = 0; i < RT_ELEMENTS(g_aGetOptDef); ++i) + m_vecOptionDefs.push_back(&g_aGetOptDef[i]); +} + +VBoxNetNAT::~VBoxNetNAT() { } +int VBoxNetNAT::init() +{ + int rc; +#if 0 + using namespace com; + HRESULT hrc = com::Initialize(); + if (FAILED(hrc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to initialize COM!"); +#endif + + /* + * Initialize slirp. + */ + rc = slirp_init(&m_pNATState, RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10, 0, 2, 0))), m_Ipv4Netmask.u, m_fPassDomain, false, 0x40, 100, this); + AssertReleaseRC(rc); + + /* Why ? */ + slirp_set_ethaddr_and_activate_port_forwarding(m_pNATState, &m_MacAddress.au8[0], INADDR_ANY); +#if 0 + in_addr ipv4HostAddr; + in_addr ipv4GuestAddr; + ipv4GuestAddr.s_addr = RT_H2N_U32_C(RT_BSWAP_U32_C(RT_MAKE_U32_FROM_U8( 10, 0, 2, 15))); + ipv4HostAddr.s_addr = INADDR_ANY; + slirp_add_redirect(m_pNATState, false, ipv4HostAddr, 2022, ipv4GuestAddr , 22, NULL); +#endif + std::vector<PNATSEVICEPORTFORWARDRULE>::iterator it; + for (it = m_vecPortForwardRuleFromCmdLine.begin(); it != m_vecPortForwardRuleFromCmdLine.end(); ++it) + { + slirp_add_redirect(m_pNATState, (*it)->fUdp, (*it)->IpV4HostAddr, (*it)->u16HostPort, (*it)->IpV4GuestAddr , (*it)->u16GuestPort, NULL); + RTStrFree((*it)->pszStrRaw); + RTMemFree((*it)); + } + m_vecPortForwardRuleFromCmdLine.clear(); +#ifndef RT_OS_WINDOWS + /* + * Create the control pipe. + */ + rc = RTPipeCreate(&m_hPipeRead, &m_hPipeWrite, 0 /*fFlags*/); + AssertReleaseRC(rc); +#else + m_hWakeupEvent = CreateEvent(NULL, FALSE, FALSE, NULL); /* auto-reset event */ + AssertReleaseRC(m_hWakeupEvent != NULL); + slirp_register_external_event(m_pNATState, m_hWakeupEvent, VBOX_WAKEUP_EVENT_INDEX); +#endif + rc = RTReqQueueCreate(&m_hReqQueue); + AssertReleaseRC(rc); + + rc = RTReqQueueCreate(&m_hSendQueue); + AssertReleaseRC(rc); + + rc = RTReqQueueCreate(&m_hUrgSendQueue); + AssertReleaseRC(rc); + + g_pNAT->fIsRunning = true; + rc = RTThreadCreate(&m_ThrNAT, AsyncIoThread, this, 128 * _1K, RTTHREADTYPE_DEFAULT, 0, "NAT"); + rc = RTThreadCreate(&m_ThrSndNAT, natSndThread, this, 128 * _1K, RTTHREADTYPE_DEFAULT, 0, "SndNAT"); + rc = RTThreadCreate(&m_ThrUrgSndNAT, natUrgSndThread, this, 128 * _1K, RTTHREADTYPE_DEFAULT, 0, "UrgSndNAT"); + rc = RTSemEventCreate(&m_EventSend); + rc = RTSemEventCreate(&m_EventUrgSend); + AssertReleaseRC(rc); + return VINF_SUCCESS; +} + +/* Mandatory functions */ +void VBoxNetNAT::run() +{ + + /* + * The loop. + */ + fIsRunning = true; + PINTNETRINGBUF pRingBuf = &m_pIfBuf->Recv; + RTThreadSetType(RTThreadSelf(), RTTHREADTYPE_IO); + 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! */ +#if 0 + RTReqProcess(m_hSendQueue, 0); + RTReqProcess(m_hUrgSendQueue, 0); +#endif + 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) + { + natNotifyNATThread(); + continue; + } + LogRel(("VBoxNetNAT: VMMR0_DO_INTNET_IF_WAIT returned %Rrc\n", rc)); + return; + } + + /* + * Process the receive buffer. + */ + PCINTNETHDR pHdr; + while ((pHdr = IntNetRingGetNextFrameToRead(pRingBuf)) != NULL) + { + uint16_t const u16Type = pHdr->u16Type; + size_t cbFrame = pHdr->cbFrame; + size_t cbIgnored; + void *pvSlirpFrame; + struct mbuf *m; + switch (u16Type) + { + case INTNETHDR_TYPE_FRAME: + m = slirp_ext_m_get(g_pNAT->m_pNATState, cbFrame, &pvSlirpFrame, &cbIgnored); + if (!m) + { + LogRel(("NAT: Can't allocate send buffer cbFrame=%u\n", cbFrame)); + break; + } + memcpy(pvSlirpFrame, IntNetHdrGetFramePtr(pHdr, m_pIfBuf), cbFrame); +#if 0 + IntNetRingSkipFrame(&m_pIfBuf->Recv); +#endif + + /* don't wait, we may have to wakeup the NAT thread first */ + rc = RTReqQueueCallEx(m_hReqQueue, NULL /*ppReq*/, 0 /*cMillies*/, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)SendWorker, 2, m, cbFrame); + natNotifyNATThread(); + AssertReleaseRC(rc); + break; + case INTNETHDR_TYPE_GSO: +#if 1 + { + /** @todo pass these unmodified. */ + PCPDMNETWORKGSO pGso = IntNetHdrGetGsoContext(pHdr, m_pIfBuf); + if (!PDMNetGsoIsValid(pGso, cbFrame, cbFrame - sizeof(*pGso))) + { + IntNetRingSkipFrame(&m_pIfBuf->Recv); + STAM_REL_COUNTER_INC(&m_pIfBuf->cStatBadFrames); + continue; + } + + uint8_t abHdrScratch[256]; + cbFrame -= sizeof(PDMNETWORKGSO); + uint32_t const cSegs = PDMNetGsoCalcSegmentCount(pGso, cbFrame); + for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++) + { + uint32_t cbSegFrame; + void *pvSegFrame = PDMNetGsoCarveSegmentQD(pGso, (uint8_t *)(pGso + 1), cbFrame, abHdrScratch, + iSeg, cSegs, &cbSegFrame); + m = slirp_ext_m_get(g_pNAT->m_pNATState, cbSegFrame, &pvSlirpFrame, &cbIgnored); + if (!m) + { + LogRel(("NAT: Can't allocate send buffer cbSegFrame=%u seg=%u/%u\n", + cbSegFrame, iSeg, cSegs)); + break; + } + memcpy(pvSlirpFrame, pvSegFrame, cbSegFrame); + + rc = RTReqQueueCallEx(m_hReqQueue, NULL /*ppReq*/, 0 /*cMillies*/, + RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)SendWorker, 2, m, cbSegFrame); + natNotifyNATThread(); + AssertReleaseRC(rc); + } + } + break; +#endif + case INTNETHDR_TYPE_PADDING: + break; + default: + STAM_REL_COUNTER_INC(&m_pIfBuf->cStatBadFrames); + break; + } + + IntNetRingSkipFrame(&m_pIfBuf->Recv); + } + } + fIsRunning = false; +} + +void VBoxNetNAT::usage() +{ +} + +int VBoxNetNAT::parseOpt(int rc, const RTGETOPTUNION& Val) +{ + switch (rc) + { + case 'p': + { +#define ITERATE_TO_NEXT_TERM(ch, pRule, strRaw) \ + do { \ + while (*ch != ',') \ + { \ + if (*ch == 0) \ + { \ + if (pRule) \ + RTMemFree(pRule); \ + if(strRaw) \ + RTStrFree(strRaw); \ + return VERR_INVALID_PARAMETER; \ + } \ + ch++; \ + } \ + *ch = '\0'; \ + ch++; \ + } while(0) + PNATSEVICEPORTFORWARDRULE pRule = (PNATSEVICEPORTFORWARDRULE)RTMemAlloc(sizeof(NATSEVICEPORTFORWARDRULE)); + if (!pRule) + return VERR_NO_MEMORY; + char *strName; + char *strProto; + char *strHostIp; + char *strHostPort; + char *strGuestIp; + char *strGuestPort; + char *strRaw = RTStrDup(Val.psz); + char *ch = strRaw; + if (!strRaw) + { + RTMemFree(pRule); + return VERR_NO_MEMORY; + } + + strName = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch, pRule, strRaw); + strProto = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch, pRule, strRaw); + strHostIp = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch, pRule, strRaw); + strHostPort = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch, pRule, strRaw); + strGuestIp = RTStrStrip(ch); + ITERATE_TO_NEXT_TERM(ch, pRule, strRaw); + strGuestPort = RTStrStrip(ch); + if (RTStrICmp(strProto, "udp") == 0) + pRule->fUdp = true; + else if (RTStrICmp(strProto, "tcp") == 0) + pRule->fUdp = false; + else + { + RTStrFree(strRaw); + RTMemFree(pRule); + return VERR_INVALID_PARAMETER; + } + if ( strHostIp == NULL + || inet_aton(strHostIp, &pRule->IpV4HostAddr) == 0) + pRule->IpV4HostAddr.s_addr = INADDR_ANY; + if ( strGuestIp == NULL + || inet_aton(strGuestIp, &pRule->IpV4GuestAddr) == 0) + { + RTMemFree(pRule); + RTMemFree(strRaw); + return VERR_INVALID_PARAMETER; + } + pRule->u16HostPort = RTStrToUInt16(strHostPort); + pRule->u16GuestPort = RTStrToUInt16(strGuestPort); + if ( !pRule->u16HostPort + || !pRule->u16GuestPort) + { + RTMemFree(pRule); + RTMemFree(strRaw); + return VERR_INVALID_PARAMETER; + } + pRule->pszStrRaw = strRaw; + m_vecPortForwardRuleFromCmdLine.push_back(pRule); + return VINF_SUCCESS; +#undef ITERATE_TO_NEXT_TERM + } + default:; + } + return VERR_NOT_FOUND; +} + +/** + * Entry point. + */ +extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp) +{ + Log2(("NAT: main\n")); + g_pNAT = new VBoxNetNAT(); + Log2(("NAT: initialization\n")); + int rc = g_pNAT->parseArgs(argc - 1, argv + 1); + if (!rc) + { + g_pNAT->init(); + Log2(("NAT: parsing command line\n")); + Log2(("NAT: try go online\n")); + g_pNAT->tryGoOnline(); + Log2(("NAT: main loop\n")); + g_pNAT->run(); + } + delete g_pNAT; + return 0; +} + + +/** slirp's hooks */ +extern "C" int slirp_can_output(void * pvUser) +{ + return 1; +} + +extern "C" void slirp_urg_output(void *pvUser, struct mbuf *m, const uint8_t *pu8Buf, int cb) +{ + LogFlowFunc(("ENTER: m:%p, pu8Buf:%p, cb:%d\n", m, pu8Buf, cb)); + int rc = RTReqQueueCallEx(g_pNAT->m_hUrgSendQueue, NULL /*ppReq*/, 0 /*cMillies*/, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)IntNetSendWorker, 4, (uintptr_t)1, (uintptr_t)pu8Buf, (uintptr_t)cb, (uintptr_t)m); + ASMAtomicIncU32(&g_pNAT->cUrgPkt); + RTSemEventSignal(g_pNAT->m_EventUrgSend); + AssertReleaseRC(rc); + LogFlowFuncLeave(); +} +extern "C" void slirp_output(void *pvUser, struct mbuf *m, const uint8_t *pu8Buf, int cb) +{ + LogFlowFunc(("ENTER: m:%p, pu8Buf:%p, cb:%d\n", m, pu8Buf, cb)); + AssertRelease(g_pNAT == pvUser); + int rc = RTReqQueueCallEx(g_pNAT->m_hSendQueue, NULL /*ppReq*/, 0 /*cMillies*/, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT, + (PFNRT)IntNetSendWorker, 4, (uintptr_t)0, (uintptr_t)pu8Buf, (uintptr_t)cb, (uintptr_t)m); + ASMAtomicIncU32(&g_pNAT->cPkt); + RTSemEventSignal(g_pNAT->m_EventSend); + AssertReleaseRC(rc); + LogFlowFuncLeave(); +} + +extern "C" void slirp_output_pending(void *pvUser) +{ + AssertMsgFailed(("Unimplemented")); +} + +/** + * Worker function for drvNATSend(). + * @thread "NAT" thread. + */ +static void SendWorker(struct mbuf *m, size_t cb) +{ + LogFlowFunc(("ENTER: m:%p ,cb:%d\n", m, cb)); + slirp_input(g_pNAT->m_pNATState, m, cb); + LogFlowFuncLeave(); +} + +static void IntNetSendWorker(bool fUrg, void *pvFrame, size_t cbFrame, struct mbuf *m) +{ + VBoxNetNAT *pThis = g_pNAT; + INTNETIFSENDREQ SendReq; + int rc; + + LogFlowFunc(("ENTER: urg:%RTbool ,pvFrame:%p, cbFrame:%d, m:%p\n", fUrg, pvFrame, cbFrame, m)); + if (!fUrg) + { + /* non-urgent datagramm sender */ + while ( ASMAtomicReadU32(&g_pNAT->cUrgPkt) != 0 + || ASMAtomicReadU32(&g_pNAT->cPkt) == 0) + rc = RTSemEventWait(g_pNAT->m_EventSend, RT_INDEFINITE_WAIT); + } + else + { + while (ASMAtomicReadU32(&g_pNAT->cUrgPkt) == 0) + rc = RTSemEventWait(g_pNAT->m_EventUrgSend, RT_INDEFINITE_WAIT); + } + rc = IntNetRingWriteFrame(&pThis->m_pIfBuf->Send, pvFrame, cbFrame); + if (RT_FAILURE(rc)) + { + SendReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + SendReq.Hdr.cbReq = sizeof(SendReq); + SendReq.pSession = pThis->m_pSession; + SendReq.hIf = pThis->m_hIf; + rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_SEND, 0, &SendReq.Hdr); + + rc = IntNetRingWriteFrame(&pThis->m_pIfBuf->Send, pvFrame, cbFrame); + + } + if (RT_SUCCESS(rc)) + { + SendReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + SendReq.Hdr.cbReq = sizeof(SendReq); + SendReq.pSession = pThis->m_pSession; + SendReq.hIf = pThis->m_hIf; + rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_SEND, 0, &SendReq.Hdr); + } + AssertRC((rc)); + if (RT_FAILURE(rc)) + Log2(("VBoxNetNAT: Failed to send packet; rc=%Rrc\n", rc)); + + if (!fUrg) + { + ASMAtomicDecU32(&g_pNAT->cPkt); + } + else { + if (ASMAtomicDecU32(&g_pNAT->cUrgPkt) == 0) + RTSemEventSignal(g_pNAT->m_EventSend); + } + slirp_ext_m_free(pThis->m_pNATState, m, (uint8_t *)pvFrame); + natNotifyNATThread(); + LogFlowFuncLeave(); +} + +static DECLCALLBACK(int) AsyncIoThread(RTTHREAD pThread, void *pvUser) +{ + VBoxNetNAT *pThis = (VBoxNetNAT *)pvUser; + int nFDs = -1; +#ifdef RT_OS_WINDOWS + HANDLE *pahEvents = slirp_get_events(pThis->m_pNATState); +#else /* RT_OS_WINDOWS */ + unsigned int cPollNegRet = 0; +#endif /* !RT_OS_WINDOWS */ + + LogFlow(("drvNATAsyncIoThread: pThis=%p\n", pThis)); + + /* + * Polling loop. + */ + for(;;) + { + /* + * To prevent concurrent execution of sending/receiving threads + */ +#ifndef RT_OS_WINDOWS + nFDs = slirp_get_nsock(pThis->m_pNATState); + /* allocation for all sockets + Management pipe */ + struct pollfd *polls = (struct pollfd *)RTMemAlloc((1 + nFDs) * sizeof(struct pollfd) + sizeof(uint32_t)); + if (polls == NULL) + return VERR_NO_MEMORY; + + /* don't pass the management pipe */ + slirp_select_fill(pThis->m_pNATState, &nFDs, &polls[1]); + unsigned int cMsTimeout = slirp_get_timeout_ms(pThis->m_pNATState); + + polls[0].fd = RTPipeToNative(pThis->m_hPipeRead); + /* POLLRDBAND usually doesn't used on Linux but seems used on Solaris */ + polls[0].events = POLLRDNORM|POLLPRI|POLLRDBAND; + polls[0].revents = 0; + + int cChangedFDs = poll(polls, nFDs + 1, cMsTimeout); + if (cChangedFDs < 0) + { + if (errno == EINTR) + { + 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) + { + LogRel(("NAT:Poll returns (%s) suppressed %d\n", strerror(errno), cPollNegRet)); + cPollNegRet = 0; + } + } + + if (cChangedFDs >= 0) + { + slirp_select_poll(pThis->m_pNATState, &polls[1], nFDs); + if (polls[0].revents & (POLLRDNORM|POLLPRI|POLLRDBAND)) + { + /* 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; + size_t cbRead; + RTPipeRead(pThis->m_hPipeRead, &ch, 1, &cbRead); + } + } + /* process _all_ outstanding requests but don't wait */ + RTReqQueueProcess(pThis->m_hReqQueue, 0); + RTMemFree(polls); + +#else /* RT_OS_WINDOWS */ + nFDs = -1; + slirp_select_fill(pThis->m_pNATState, &nFDs); + DWORD dwEvent = WSAWaitForMultipleEvents(nFDs, pahEvents, FALSE, + slirp_get_timeout_ms(pThis->m_pNATState), + FALSE); + if ( (dwEvent < WSA_WAIT_EVENT_0 || dwEvent > WSA_WAIT_EVENT_0 + nFDs - 1) + && dwEvent != WSA_WAIT_TIMEOUT) + { + int error = WSAGetLastError(); + LogRel(("NAT: WSAWaitForMultipleEvents returned %d (error %d)\n", dwEvent, error)); + RTAssertReleasePanic(); + } + + if (dwEvent == WSA_WAIT_TIMEOUT) + { + /* only check for slow/fast timers */ + slirp_select_poll(pThis->m_pNATState, /* fTimeout=*/true, /*fIcmp=*/false); + continue; + } + + /* poll the sockets in any case */ + slirp_select_poll(pThis->m_pNATState, /* fTimeout=*/false, /* fIcmp=*/(dwEvent == WSA_WAIT_EVENT_0)); + /* process _all_ outstanding requests but don't wait */ + RTReqQueueProcess(pThis->m_hReqQueue, 0); +#endif /* RT_OS_WINDOWS */ + } + + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) natSndThread(RTTHREAD pThread, void *pvUser) +{ + while (g_pNAT->fIsRunning) + RTReqQueueProcess(g_pNAT->m_hSendQueue, 0); + return VINF_SUCCESS; +} +static DECLCALLBACK(int) natUrgSndThread(RTTHREAD pThread, void *pvUser) +{ + while (g_pNAT->fIsRunning) + RTReqQueueProcess(g_pNAT->m_hUrgSendQueue, 0); + return VINF_SUCCESS; +} + +#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); +} + +# if defined(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 = "VBoxNetNatClass"; + +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) +{ +#if 0 + 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); + +#endif + return main(__argc, __argv, environ); +} +# endif /* RT_OS_WINDOWS */ + +#endif /* !VBOX_WITH_HARDENING */ + diff --git a/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp b/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp new file mode 100644 index 00000000..b0d74e73 --- /dev/null +++ b/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp @@ -0,0 +1,24 @@ +/* $Id: VBoxNetNATHardened.cpp $ */ +/** @file + * VBoxNetNAT - Hardened main(). + */ + +/* + * 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. + */ + +#include <VBox/sup.h> + + +int main(int argc, char **argv, char **envp) +{ + return SUPR3HardenedMain("VBoxNetNAT", 0 /* fFlags */, argc, argv, envp); +} diff --git a/src/VBox/NetworkServices/NetLib/Makefile.kup b/src/VBox/NetworkServices/NetLib/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/NetworkServices/NetLib/Makefile.kup diff --git a/src/VBox/NetworkServices/NetLib/VBoxNetARP.cpp b/src/VBox/NetworkServices/NetLib/VBoxNetARP.cpp new file mode 100644 index 00000000..ec5ed275 --- /dev/null +++ b/src/VBox/NetworkServices/NetLib/VBoxNetARP.cpp @@ -0,0 +1,154 @@ +/* $Id: VBoxNetARP.cpp $ */ +/** @file + * VBoxNetARP - IntNet ARP Client Routines. + */ + +/* + * 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. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEFAULT +#include "VBoxNetLib.h" +#include <iprt/string.h> +#include <VBox/intnetinline.h> +#include <VBox/log.h> + + +/** + * Deal with ARP queries. + * + * @returns true if ARP. + * + * @param pSession The support driver session. + * @param hIf The internal network interface handle. + * @param pBuf The internal network interface buffer. + * @param pMacAddr Our MAC address. + * @param IPv4Addr Our IPv4 address. + */ +bool VBoxNetArpHandleIt(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf, PINTNETBUF pBuf, PCRTMAC pMacAddr, RTNETADDRIPV4 IPv4Addr) +{ + /* + * Valid IntNet Ethernet frame? Skip GSO, no ARP in there. + */ + PCINTNETHDR pHdr = IntNetRingGetNextFrameToRead(&pBuf->Recv); + if ( !pHdr + || pHdr->u16Type != INTNETHDR_TYPE_FRAME) + return false; + + size_t cbFrame = pHdr->cbFrame; + const void *pvFrame = IntNetHdrGetFramePtr(pHdr, pBuf); + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pvFrame; + + /* + * Arp frame? + */ + if (pEthHdr->EtherType != RT_H2N_U16_C(RTNET_ETHERTYPE_ARP)) + return false; + if ( ( pEthHdr->DstMac.au16[0] != 0xffff + || pEthHdr->DstMac.au16[1] != 0xffff + || pEthHdr->DstMac.au16[2] != 0xffff) + && ( pEthHdr->DstMac.au16[0] != pMacAddr->au16[0] + || pEthHdr->DstMac.au16[1] != pMacAddr->au16[1] + || pEthHdr->DstMac.au16[2] != pMacAddr->au16[2]) + ) + return false; + if (cbFrame < sizeof(RTNETARPIPV4) + sizeof(RTNETETHERHDR)) + return false; + + PCRTNETARPHDR pArpHdr = (PCRTNETARPHDR)(pEthHdr + 1); + if (pArpHdr->ar_htype != RT_H2N_U16_C(RTNET_ARP_ETHER)) + return false; + if (pArpHdr->ar_hlen != sizeof(RTMAC)) + return false; + if (pArpHdr->ar_ptype != RT_H2N_U16_C(RTNET_ETHERTYPE_IPV4)) + return false; + if (pArpHdr->ar_plen != sizeof(RTNETADDRIPV4)) + return false; + + /* It's ARP, alright. Anything we need to do something about. */ + PCRTNETARPIPV4 pArp = (PCRTNETARPIPV4)pArpHdr; + switch (pArp->Hdr.ar_oper) + { + case RT_H2N_U16_C(RTNET_ARPOP_REQUEST): + case RT_H2N_U16_C(RTNET_ARPOP_REVREQUEST): + case RT_H2N_U16_C(RTNET_ARPOP_INVREQUEST): + break; + default: + return true; + } + + /* + * Deal with the queries. + */ + RTNETARPIPV4 Reply; + switch (pArp->Hdr.ar_oper) + { + /* 'Who has ar_tpa? Tell ar_spa.' */ + case RT_H2N_U16_C(RTNET_ARPOP_REQUEST): + if (pArp->ar_tpa.u != IPv4Addr.u) + return true; + Reply.Hdr.ar_oper = RT_H2N_U16_C(RTNET_ARPOP_REPLY); + break; + + case RT_H2N_U16_C(RTNET_ARPOP_REVREQUEST): + if ( pArp->ar_tha.au16[0] != pMacAddr->au16[0] + || pArp->ar_tha.au16[1] != pMacAddr->au16[1] + || pArp->ar_tha.au16[2] != pMacAddr->au16[2]) + return true; + Reply.Hdr.ar_oper = RT_H2N_U16_C(RTNET_ARPOP_REVREPLY); + break; + + case RT_H2N_U16_C(RTNET_ARPOP_INVREQUEST): + /** @todo RTNET_ARPOP_INVREQUEST */ + return true; + //Reply.Hdr.ar_oper = RT_H2N_U16_C(RTNET_ARPOP_INVREPLY); + //break; + } + + /* + * Complete the reply and send it. + */ + Reply.Hdr.ar_htype = RT_H2N_U16_C(RTNET_ARP_ETHER); + Reply.Hdr.ar_ptype = RT_H2N_U16_C(RTNET_ETHERTYPE_IPV4); + Reply.Hdr.ar_hlen = sizeof(RTMAC); + Reply.Hdr.ar_plen = sizeof(RTNETADDRIPV4); + Reply.ar_sha = *pMacAddr; + Reply.ar_spa = IPv4Addr; + Reply.ar_tha = pArp->ar_sha; + Reply.ar_tpa = pArp->ar_spa; + + + RTNETETHERHDR EthHdr; + EthHdr.DstMac = pArp->ar_sha; + EthHdr.SrcMac = *pMacAddr; + EthHdr.EtherType = RT_H2N_U16_C(RTNET_ETHERTYPE_ARP); + + uint8_t abTrailer[60 - sizeof(Reply) - sizeof(EthHdr)]; + memset(abTrailer, '\0', sizeof(abTrailer)); + + INTNETSEG aSegs[3]; + aSegs[0].cb = sizeof(EthHdr); + aSegs[0].pv = &EthHdr; + + aSegs[1].pv = &Reply; + aSegs[1].cb = sizeof(Reply); + + aSegs[2].pv = &abTrailer[0]; + aSegs[2].cb = sizeof(abTrailer); + + VBoxNetIntIfSend(pSession, hIf, pBuf, RT_ELEMENTS(aSegs), &aSegs[0], true /* fFlush */); + + return true; +} + diff --git a/src/VBox/NetworkServices/NetLib/VBoxNetBaseService.cpp b/src/VBox/NetworkServices/NetLib/VBoxNetBaseService.cpp new file mode 100644 index 00000000..435a1cb8 --- /dev/null +++ b/src/VBox/NetworkServices/NetLib/VBoxNetBaseService.cpp @@ -0,0 +1,363 @@ +/* $Id: VBoxNetBaseService.cpp $ */ +/** @file + * VBoxNetDHCP - DHCP Service for connecting to IntNet. + */ +/** @todo r=bird: Cut&Past rules... Please fix DHCP refs! */ + +/* + * 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. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#define LOG_GROUP LOG_GROUP_NET_SERVICE + +#include <iprt/alloca.h> +#include <iprt/buildconfig.h> +#include <iprt/err.h> +#include <iprt/net.h> /* must come before getopt.h. */ +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include <iprt/mem.h> + +#include <VBox/sup.h> +#include <VBox/intnet.h> +#include <VBox/vmm/vmm.h> +#include <VBox/version.h> + +#include <vector> +#include <string> + +#include <VBox/log.h> + +#include "VBoxNetLib.h" +#include "VBoxNetBaseService.h" + +#ifdef RT_OS_WINDOWS /* WinMain */ +# include <Windows.h> +# include <stdlib.h> +#endif + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +static RTGETOPTDEF g_aGetOptDef[] = +{ + { "--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 }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, +}; +VBoxNetBaseService::VBoxNetBaseService() +{ +} +VBoxNetBaseService::~VBoxNetBaseService() +{ + /* + * 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_RTCPUID, VMMR0_DO_INTNET_IF_CLOSE, 0, &CloseReq.Hdr); + AssertRC(rc); + } + + if (m_pSession) + { + SUPR3Term(false /*fForced*/); + m_pSession = NIL_RTR0PTR; + } +} + +int VBoxNetBaseService::init() +{ + /* numbers from DrvIntNet */ + m_cbSendBuf = 36 * _1K; + m_cbRecvBuf = 218 * _1K; + m_hIf = INTNET_HANDLE_INVALID; + m_pIfBuf = NULL; + + m_cVerbosity = 0; + m_Name = "VBoxNetNAT"; + m_Network = "intnet"; + for(unsigned int i = 0; i < RT_ELEMENTS(g_aGetOptDef); ++i) + m_vecOptionDefs.push_back(&g_aGetOptDef[i]); + return VINF_SUCCESS; +} +/** + * Parse the arguments. + * + * @returns 0 on success, fully bitched exit code on failure. + * + * @param argc Argument count. + * @param argv Argument vector. + */ +int VBoxNetBaseService::parseArgs(int argc, char **argv) +{ + + RTGETOPTSTATE State; + PRTGETOPTDEF paOptionArray = getOptionsPtr(); + int rc = RTGetOptInit(&State, argc, argv, paOptionArray, m_vecOptionDefs.size(), 0, 0 /*fFlags*/); + AssertRCReturn(rc, 49); + Log2(("BaseService: parseArgs enter\n")); + + 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 'v': + m_cVerbosity++; + 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 <options>\n" + "\n" + "Options:\n", + RTBldCfgVersion()); + for (unsigned int i = 0; i < m_vecOptionDefs.size(); i++) + RTPrintf(" -%c, %s\n", m_vecOptionDefs[i]->iShort, m_vecOptionDefs[i]->pszLong); + usage(); /* to print Service Specific usage */ + return 1; + + default: + int rc1 = parseOpt(rc, Val); + if (RT_FAILURE(rc1)) + { + rc = RTGetOptPrintError(rc, &Val); + RTPrintf("Use --help for more information.\n"); + return rc; + } + } + } + + RTMemFree(paOptionArray); + return rc; +} + +int VBoxNetBaseService::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; + LogRel(("VBoxNetBaseService: SUPR3Init -> %Rrc\n", rc)); + return 1; + } + + char szPath[RTPATH_MAX]; + rc = RTPathExecDir(szPath, sizeof(szPath) - sizeof("/VMMR0.r0")); + if (RT_FAILURE(rc)) + { + LogRel(("VBoxNetBaseService: RTPathExecDir -> %Rrc\n", rc)); + return 1; + } + + rc = SUPR3LoadVMM(strcat(szPath, "/VMMR0.r0")); + if (RT_FAILURE(rc)) + { + LogRel(("VBoxNetBaseService: SUPR3LoadVMM(\"%s\") -> %Rrc\n", szPath, rc)); + return 1; + } + + /* + * Create the open request. + */ + PINTNETBUF pBuf; + 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. + */ + Log2(("attempting to open/create network \"%s\"...\n", OpenReq.szNetwork)); + rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_OPEN, 0, &OpenReq.Hdr); + if (RT_FAILURE(rc)) + { + Log2(("VBoxNetBaseService: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_OPEN,) failed, rc=%Rrc\n", rc)); + goto bad; + } + m_hIf = OpenReq.hIf; + Log2(("successfully opened/created \"%s\" - hIf=%#x\n", 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_FAILURE(rc)) + { + Log2(("VBoxNetBaseService: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS,) failed, rc=%Rrc\n", rc)); + goto bad; + } + pBuf = GetBufferPtrsReq.pRing3Buf; + Log2(("pBuf=%p cbBuf=%d cbSend=%d cbRecv=%d\n", + 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 */ + Log2(("VBoxNetBaseService: SUPR3CallVMMR0Ex(,VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE,) failed, rc=%Rrc\n", rc)); + + return 0; + bad: + return 1; +} + +void VBoxNetBaseService::shutdown(void) +{ +} + +/** + * 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 VBoxNetBaseService::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 VBoxNetBaseService::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); + } + +} + +PRTGETOPTDEF VBoxNetBaseService::getOptionsPtr() +{ + PRTGETOPTDEF pOptArray = NULL; + pOptArray = (PRTGETOPTDEF)RTMemAlloc(sizeof(RTGETOPTDEF) * m_vecOptionDefs.size()); + if (!pOptArray) + return NULL; + for (unsigned int i = 0; i < m_vecOptionDefs.size(); ++i) + { + PRTGETOPTDEF pOpt = m_vecOptionDefs[i]; + memcpy(&pOptArray[i], m_vecOptionDefs[i], sizeof(RTGETOPTDEF)); + } + return pOptArray; +} diff --git a/src/VBox/NetworkServices/NetLib/VBoxNetBaseService.h b/src/VBox/NetworkServices/NetLib/VBoxNetBaseService.h new file mode 100644 index 00000000..eab9a515 --- /dev/null +++ b/src/VBox/NetworkServices/NetLib/VBoxNetBaseService.h @@ -0,0 +1,61 @@ +/* $Id: VBoxNetBaseService.h $ */ +/** @file + * VBoxNetUDP - IntNet Client Library. + */ + +/* + * 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. + */ + +#ifndef ___VBoxNetBaseService_h___ +#define ___VBoxNetBaseService_h___ +class VBoxNetBaseService +{ +public: + VBoxNetBaseService(); + virtual ~VBoxNetBaseService(); + int parseArgs(int argc, char **argv); + int tryGoOnline(void); + void shutdown(void); + virtual void usage(void) = 0; + virtual void run(void) = 0; + virtual int init(void); + virtual int parseOpt(int rc, const RTGETOPTUNION& getOptVal) = 0; + + 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; +public: + /** @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; + /** @} */ + /** @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. */ + std::vector<PRTGETOPTDEF> m_vecOptionDefs; + /** @} */ + /** @name Debug stuff + * @{ */ + int32_t m_cVerbosity; +private: + PRTGETOPTDEF getOptionsPtr(); + /** @} */ +}; +#endif diff --git a/src/VBox/NetworkServices/NetLib/VBoxNetIntIf.cpp b/src/VBox/NetworkServices/NetLib/VBoxNetIntIf.cpp new file mode 100644 index 00000000..74a3998a --- /dev/null +++ b/src/VBox/NetworkServices/NetLib/VBoxNetIntIf.cpp @@ -0,0 +1,137 @@ +/* $Id: VBoxNetIntIf.cpp $ */ +/** @file + * VBoxNetIntIf - IntNet Interface Client Routines. + */ + +/* + * 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. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEFAULT +#include "VBoxNetLib.h" +#include <VBox/intnet.h> +#include <VBox/intnetinline.h> +#include <VBox/sup.h> +#include <VBox/vmm/vmm.h> +#include <VBox/err.h> +#include <VBox/log.h> + +#include <iprt/string.h> + + + +/** + * Flushes the send buffer. + * + * @returns VBox status code. + * @param pSession The support driver session. + * @param hIf The interface handle to flush. + */ +int VBoxNetIntIfFlush(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf) +{ + INTNETIFSENDREQ SendReq; + SendReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + SendReq.Hdr.cbReq = sizeof(SendReq); + SendReq.pSession = pSession; + SendReq.hIf = hIf; + return SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_INTNET_IF_SEND, 0, &SendReq.Hdr); +} + + +/** + * Copys the SG segments into the specified fram. + * + * @param pvFrame The frame buffer. + * @param cSegs The number of segments. + * @param paSegs The segments. + */ +static void vboxnetIntIfCopySG(void *pvFrame, size_t cSegs, PCINTNETSEG paSegs) +{ + uint8_t *pbDst = (uint8_t *)pvFrame; + for (size_t iSeg = 0; iSeg < cSegs; iSeg++) + { + memcpy(pbDst, paSegs[iSeg].pv, paSegs[iSeg].cb); + pbDst += paSegs[iSeg].cb; + } +} + + +/** + * Writes a frame packet to the buffer. + * + * @returns VBox status code. + * @param pBuf The buffer. + * @param pRingBuf The ring buffer to read from. + * @param cSegs The number of segments. + * @param paSegs The segments. + */ +int VBoxNetIntIfRingWriteFrame(PINTNETBUF pBuf, PINTNETRINGBUF pRingBuf, size_t cSegs, PCINTNETSEG paSegs) +{ + /* + * Validate input. + */ + AssertPtr(pBuf); + AssertPtr(pRingBuf); + AssertPtr(paSegs); + Assert(cSegs > 0); + + /* + * Calc frame size. + */ + uint32_t cbFrame = 0; + for (size_t iSeg = 0; iSeg < cSegs; iSeg++) + cbFrame += paSegs[iSeg].cb; + Assert(cbFrame >= sizeof(RTMAC) * 2); + + /* + * Allocate a frame, copy the data and commit it. + */ + PINTNETHDR pHdr = NULL; + void *pvFrame = NULL; + int rc = IntNetRingAllocateFrame(pRingBuf, cbFrame, &pHdr, &pvFrame); + if (RT_SUCCESS(rc)) + { + vboxnetIntIfCopySG(pvFrame, cSegs, paSegs); + IntNetRingCommitFrame(pRingBuf, pHdr); + return VINF_SUCCESS; + } + + return rc; +} + + +/** + * Sends a frame + * + * @returns VBox status code. + * @param pSession The support driver session. + * @param hIf The interface handle. + * @param pBuf The interface buffer. + * @param cSegs The number of segments. + * @param paSegs The segments. + * @param fFlush Whether to flush the write. + */ +int VBoxNetIntIfSend(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf, PINTNETBUF pBuf, + size_t cSegs, PCINTNETSEG paSegs, bool fFlush) +{ + int rc = VBoxNetIntIfRingWriteFrame(pBuf, &pBuf->Send, cSegs, paSegs); + if (rc == VERR_BUFFER_OVERFLOW) + { + VBoxNetIntIfFlush(pSession, hIf); + rc = VBoxNetIntIfRingWriteFrame(pBuf, &pBuf->Send, cSegs, paSegs); + } + if (RT_SUCCESS(rc) && fFlush) + rc = VBoxNetIntIfFlush(pSession, hIf); + return rc; +} diff --git a/src/VBox/NetworkServices/NetLib/VBoxNetLib.h b/src/VBox/NetworkServices/NetLib/VBoxNetLib.h new file mode 100644 index 00000000..e1150bfb --- /dev/null +++ b/src/VBox/NetworkServices/NetLib/VBoxNetLib.h @@ -0,0 +1,69 @@ +/* $Id: VBoxNetLib.h $ */ +/** @file + * VBoxNetUDP - IntNet Client Library. + */ + +/* + * 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. + */ + +#ifndef ___VBoxNetUDP_h___ +#define ___VBoxNetUDP_h___ + +#include <iprt/net.h> +#include <VBox/intnet.h> + +RT_C_DECLS_BEGIN + + +/** + * Header pointers optionally returned by VBoxNetUDPMatch. + */ +typedef struct VBOXNETUDPHDRS +{ + PCRTNETETHERHDR pEth; /**< Pointer to the ethernet header. */ + PCRTNETIPV4 pIpv4; /**< Pointer to the IPV4 header if IPV4 packet. */ + PCRTNETUDP pUdp; /**< Pointer to the UDP header. */ +} VBOXNETUDPHDRS; +/** Pointer to a VBOXNETUDPHDRS structure. */ +typedef VBOXNETUDPHDRS *PVBOXNETUDPHDRS; + + +/** @name VBoxNetUDPMatch flags. + * @{ */ +#define VBOXNETUDP_MATCH_UNICAST RT_BIT_32(0) +#define VBOXNETUDP_MATCH_BROADCAST RT_BIT_32(1) +#define VBOXNETUDP_MATCH_CHECKSUM RT_BIT_32(2) +#define VBOXNETUDP_MATCH_REQUIRE_CHECKSUM RT_BIT_32(3) +#define VBOXNETUDP_MATCH_PRINT_STDERR RT_BIT_32(31) +/** @} */ + +void * VBoxNetUDPMatch(PINTNETBUF pBuf, unsigned uDstPort, PCRTMAC pDstMac, uint32_t fFlags, PVBOXNETUDPHDRS pHdrs, size_t *pcb); +int VBoxNetUDPUnicast(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf, PINTNETBUF pBuf, + RTNETADDRIPV4 SrcIPv4Addr, PCRTMAC SrcMacAddr, unsigned uSrcPort, + RTNETADDRIPV4 DstIPv4Addr, PCRTMAC DstMacAddr, unsigned uDstPort, + void const *pvData, size_t cbData); +int VBoxNetUDPBroadcast(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf, PINTNETBUF pBuf, + RTNETADDRIPV4 SrcIPv4Addr, PCRTMAC SrcMacAddr, unsigned uSrcPort, + unsigned uDstPort, + void const *pvData, size_t cbData); + +bool VBoxNetArpHandleIt(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf, PINTNETBUF pBuf, PCRTMAC pMacAddr, RTNETADDRIPV4 IPv4Addr); + +int VBoxNetIntIfFlush(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf); +int VBoxNetIntIfRingWriteFrame(PINTNETBUF pBuf, PINTNETRINGBUF pRingBuf, size_t cSegs, PCINTNETSEG paSegs); +int VBoxNetIntIfSend(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf, PINTNETBUF pBuf, size_t cSegs, PCINTNETSEG paSegs, bool fFlush); + + +RT_C_DECLS_END + +#endif + diff --git a/src/VBox/NetworkServices/NetLib/VBoxNetUDP.cpp b/src/VBox/NetworkServices/NetLib/VBoxNetUDP.cpp new file mode 100644 index 00000000..79c40c27 --- /dev/null +++ b/src/VBox/NetworkServices/NetLib/VBoxNetUDP.cpp @@ -0,0 +1,303 @@ +/* $Id: VBoxNetUDP.cpp $ */ +/** @file + * VBoxNetUDP - IntNet UDP Client Routines. + */ + +/* + * 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. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEFAULT +#include "VBoxNetLib.h" +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/rand.h> +#include <VBox/log.h> +#include <VBox/vmm/pdmnetinline.h> +#include <VBox/intnetinline.h> + + +/** + * Checks if the head of the receive ring is a UDP packet matching the given + * criteria. + * + * @returns Pointer to the data if it matches. + * @param pBuf The IntNet buffers. + * @param uDstPort The destination port to match. + * @param pDstMac The destination address to match if + * VBOXNETUDP_MATCH_UNICAST is specied. + * @param fFlags Flags indicating what to match and some debug stuff. + * See VBOXNETUDP_MATCH_*. + * @param pHdrs Where to return the pointers to the headers. + * Optional. + * @param pcb Where to return the size of the data on success. + */ +void *VBoxNetUDPMatch(PINTNETBUF pBuf, unsigned uDstPort, PCRTMAC pDstMac, uint32_t fFlags, PVBOXNETUDPHDRS pHdrs, size_t *pcb) +{ + /* + * Clear return values so we can return easier on mismatch. + */ + *pcb = 0; + if (pHdrs) + { + pHdrs->pEth = NULL; + pHdrs->pIpv4 = NULL; + pHdrs->pUdp = NULL; + } + + /* + * Valid IntNet Ethernet frame? + */ + PCINTNETHDR pHdr = IntNetRingGetNextFrameToRead(&pBuf->Recv); + if ( !pHdr + || ( pHdr->u16Type != INTNETHDR_TYPE_FRAME + && pHdr->u16Type != INTNETHDR_TYPE_GSO)) + return NULL; + + size_t cbFrame = pHdr->cbFrame; + const void *pvFrame = IntNetHdrGetFramePtr(pHdr, pBuf); + PCPDMNETWORKGSO pGso = NULL; + if (pHdr->u16Type == INTNETHDR_TYPE_GSO) + { + pGso = (PCPDMNETWORKGSO)pvFrame; + if (!PDMNetGsoIsValid(pGso, cbFrame, cbFrame - sizeof(*pGso))) + return NULL; + /** @todo IPv6 UDP support, goes for this entire function really. Not really + * important yet since this is currently only used by the DHCP server. */ + if (pGso->u8Type != PDMNETWORKGSOTYPE_IPV4_UDP) + return NULL; + pvFrame = pGso + 1; + cbFrame -= sizeof(*pGso); + } + + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pvFrame; + if (pHdrs) + pHdrs->pEth = pEthHdr; + +#ifdef IN_RING3 + /* Dump if to stderr/log if that's wanted. */ + if (fFlags & VBOXNETUDP_MATCH_PRINT_STDERR) + { + RTStrmPrintf(g_pStdErr, "frame: cb=%04x dst=%.6Rhxs src=%.6Rhxs type=%04x%s\n", + cbFrame, &pEthHdr->DstMac, &pEthHdr->SrcMac, RT_BE2H_U16(pEthHdr->EtherType), + !memcmp(&pEthHdr->DstMac, pDstMac, sizeof(*pDstMac)) ? " Mine!" : ""); + } +#endif + + /* + * Ethernet matching. + */ + + /* Ethernet min frame size. */ + if (cbFrame < 64) + return NULL; + + /* Match Ethertype: IPV4? */ + /** @todo VLAN tagging? */ + if (pEthHdr->EtherType != RT_H2BE_U16_C(RTNET_ETHERTYPE_IPV4)) + return NULL; + + /* Match destination address (ethernet) */ + if ( ( !(fFlags & VBOXNETUDP_MATCH_UNICAST) + || memcmp(&pEthHdr->DstMac, pDstMac, sizeof(pEthHdr->DstMac))) + && ( !(fFlags & VBOXNETUDP_MATCH_BROADCAST) + || pEthHdr->DstMac.au16[0] != 0xffff + || pEthHdr->DstMac.au16[1] != 0xffff + || pEthHdr->DstMac.au16[2] != 0xffff)) + return NULL; + + /* + * If we're working on a GSO frame, we need to make sure the length fields + * are set correctly (they are usually set to 0). + */ + if (pGso) + PDMNetGsoPrepForDirectUse(pGso, (void *)pvFrame, cbFrame, PDMNETCSUMTYPE_NONE); + + /* + * IP validation and matching. + */ + PCRTNETIPV4 pIpHdr = (PCRTNETIPV4)(pEthHdr + 1); + if (pHdrs) + pHdrs->pIpv4 = pIpHdr; + + /* Protocol: UDP */ + if (pIpHdr->ip_p != RTNETIPV4_PROT_UDP) + return NULL; + + /* Valid IPv4 header? */ + size_t const offIpHdr = (uintptr_t)pIpHdr - (uintptr_t)pEthHdr; + if (!RTNetIPv4IsHdrValid(pIpHdr, cbFrame - offIpHdr, cbFrame - offIpHdr, !pGso /*fChecksum*/)) + return NULL; + + /* + * UDP matching and validation. + */ + PCRTNETUDP pUdpHdr = (PCRTNETUDP)((uint32_t *)pIpHdr + pIpHdr->ip_hl); + if (pHdrs) + pHdrs->pUdp = pUdpHdr; + + /* Destination port */ + if (RT_BE2H_U16(pUdpHdr->uh_dport) != uDstPort) + return NULL; + + if (!pGso) + { + /* Validate the UDP header according to flags. */ + size_t offUdpHdr = (uintptr_t)pUdpHdr - (uintptr_t)pEthHdr; + if (fFlags & (VBOXNETUDP_MATCH_CHECKSUM | VBOXNETUDP_MATCH_REQUIRE_CHECKSUM)) + { + if (!RTNetIPv4IsUDPValid(pIpHdr, pUdpHdr, pUdpHdr + 1, cbFrame - offUdpHdr, true /*fChecksum*/)) + return NULL; + if ( (fFlags & VBOXNETUDP_MATCH_REQUIRE_CHECKSUM) + && !pUdpHdr->uh_sum) + return NULL; + } + else + { + if (!RTNetIPv4IsUDPSizeValid(pIpHdr, pUdpHdr, cbFrame - offUdpHdr)) + return NULL; + } + } + + /* + * We've got a match! + */ + *pcb = pUdpHdr->uh_ulen - sizeof(*pUdpHdr); + return (void *)(pUdpHdr + 1); +} + + +/** Internal worker for VBoxNetUDPUnicast and VBoxNetUDPBroadcast. */ +static int vboxnetudpSend(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf, PINTNETBUF pBuf, + RTNETADDRIPV4 SrcIPv4Addr, PCRTMAC pSrcMacAddr, unsigned uSrcPort, + RTNETADDRIPV4 DstIPv4Addr, PCRTMAC pDstMacAddr, unsigned uDstPort, + void const *pvData, size_t cbData) +{ + INTNETSEG aSegs[4]; + + /* the Ethernet header */ + RTNETETHERHDR EtherHdr; + EtherHdr.DstMac = *pDstMacAddr; + EtherHdr.SrcMac = *pSrcMacAddr; + EtherHdr.EtherType = RT_H2BE_U16_C(RTNET_ETHERTYPE_IPV4); + + aSegs[0].pv = &EtherHdr; + aSegs[0].cb = sizeof(EtherHdr); + aSegs[0].Phys = NIL_RTHCPHYS; + + /* the IP header */ + RTNETIPV4 IpHdr; + unsigned cbIdHdr = RT_UOFFSETOF(RTNETIPV4, ip_options); + IpHdr.ip_v = 4; + IpHdr.ip_hl = cbIdHdr >> 2; + IpHdr.ip_tos = 0; + IpHdr.ip_len = RT_H2BE_U16((uint16_t)(cbData + sizeof(RTNETUDP) + cbIdHdr)); + IpHdr.ip_id = (uint16_t)RTRandU32(); + IpHdr.ip_off = 0; + IpHdr.ip_ttl = 255; + IpHdr.ip_p = RTNETIPV4_PROT_UDP; + IpHdr.ip_sum = 0; + IpHdr.ip_src = SrcIPv4Addr; + IpHdr.ip_dst = DstIPv4Addr; + IpHdr.ip_sum = RTNetIPv4HdrChecksum(&IpHdr); + + aSegs[1].pv = &IpHdr; + aSegs[1].cb = cbIdHdr; + aSegs[1].Phys = NIL_RTHCPHYS; + + + /* the UDP bit */ + RTNETUDP UdpHdr; + UdpHdr.uh_sport = RT_H2BE_U16(uSrcPort); + UdpHdr.uh_dport = RT_H2BE_U16(uDstPort); + UdpHdr.uh_ulen = RT_H2BE_U16((uint16_t)(cbData + sizeof(RTNETUDP))); +#if 0 + UdpHdr.uh_sum = 0; /* pretend checksumming is disabled */ +#else + UdpHdr.uh_sum = RTNetIPv4UDPChecksum(&IpHdr, &UdpHdr, pvData); +#endif + + aSegs[2].pv = &UdpHdr; + aSegs[2].cb = sizeof(UdpHdr); + aSegs[2].Phys = NIL_RTHCPHYS; + + /* the payload */ + aSegs[3].pv = (void *)pvData; + aSegs[3].cb = (uint32_t)cbData; + aSegs[3].Phys = NIL_RTHCPHYS; + + + /* send it */ + return VBoxNetIntIfSend(pSession, hIf, pBuf, RT_ELEMENTS(aSegs), &aSegs[0], true /* fFlush */); +} + + +/** + * Sends an unicast UDP packet. + * + * @returns VBox status code. + * @param pSession The support driver session handle. + * @param hIf The interface handle. + * @param pBuf The interface buffer. + * @param SrcIPv4Addr The source IPv4 address. + * @param pSrcMacAddr The source MAC address. + * @param uSrcPort The source port number. + * @param DstIPv4Addr The destination IPv4 address. Can be broadcast. + * @param pDstMacAddr The destination MAC address. + * @param uDstPort The destination port number. + * @param pvData The data payload. + * @param cbData The size of the data payload. + */ +int VBoxNetUDPUnicast(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf, PINTNETBUF pBuf, + RTNETADDRIPV4 SrcIPv4Addr, PCRTMAC pSrcMacAddr, unsigned uSrcPort, + RTNETADDRIPV4 DstIPv4Addr, PCRTMAC pDstMacAddr, unsigned uDstPort, + void const *pvData, size_t cbData) +{ + return vboxnetudpSend(pSession, hIf, pBuf, + SrcIPv4Addr, pSrcMacAddr, uSrcPort, + DstIPv4Addr, pDstMacAddr, uDstPort, + pvData, cbData); +} + + +/** + * Sends a broadcast UDP packet. + * + * @returns VBox status code. + * @param pSession The support driver session handle. + * @param hIf The interface handle. + * @param pBuf The interface buffer. + * @param SrcIPv4Addr The source IPv4 address. + * @param pSrcMacAddr The source MAC address. + * @param uSrcPort The source port number. + * @param uDstPort The destination port number. + * @param pvData The data payload. + * @param cbData The size of the data payload. + */ +int VBoxNetUDPBroadcast(PSUPDRVSESSION pSession, INTNETIFHANDLE hIf, PINTNETBUF pBuf, + RTNETADDRIPV4 SrcIPv4Addr, PCRTMAC pSrcMacAddr, unsigned uSrcPort, + unsigned uDstPort, + void const *pvData, size_t cbData) +{ + RTNETADDRIPV4 IPv4AddrBrdCast; + IPv4AddrBrdCast.u = UINT32_C(0xffffffff); + RTMAC MacBrdCast; + MacBrdCast.au16[0] = MacBrdCast.au16[1] = MacBrdCast.au16[2] = UINT16_C(0xffff); + + return vboxnetudpSend(pSession, hIf, pBuf, + SrcIPv4Addr, pSrcMacAddr, uSrcPort, + IPv4AddrBrdCast, &MacBrdCast, uDstPort, + pvData, cbData); +} + |
