diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/NetworkServices/DHCP/Config.h | |
parent | Initial commit. (diff) | |
download | virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/NetworkServices/DHCP/Config.h')
-rw-r--r-- | src/VBox/NetworkServices/DHCP/Config.h | 845 |
1 files changed, 845 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/DHCP/Config.h b/src/VBox/NetworkServices/DHCP/Config.h new file mode 100644 index 00000000..a8431686 --- /dev/null +++ b/src/VBox/NetworkServices/DHCP/Config.h @@ -0,0 +1,845 @@ +/* $Id: Config.h $ */ +/** @file + * Config.h + */ + +/* + * Copyright (C) 2013-2019 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 VBOX_INCLUDED_SRC_DHCP_Config_h +#define VBOX_INCLUDED_SRC_DHCP_Config_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/asm-math.h> +#include <iprt/cpp/utils.h> + +#include <VBox/com/ptr.h> +#include <VBox/com/string.h> +#include <VBox/com/VirtualBox.h> + +#include "../NetLib/cpp/utils.h" + + +class RawOption +{ +public: + RawOption() + { + /** @todo r=bird: this is crazy. */ + RT_ZERO(*this); + } + uint8_t u8OptId; + uint8_t cbRawOpt; + uint8_t au8RawOpt[255]; +}; + +class ClientData; +class Client; +class Lease; +class BaseConfigEntity; + +class NetworkConfigEntity; +class HostConfigEntity; +class ClientMatchCriteria; +class ConfigurationManager; + +/* + * it's a basic representation of + * of out undestanding what client is + * XXX: Client might sends Option 61 (RFC2132 9.14 "Client-identifier") signalling + * that we may identify it in special way + * + * XXX: Client might send Option 60 (RFC2132 9.13 "Vendor class undentifier") + * in response it's expected server sends Option 43 (RFC2132 8.4. "Vendor Specific Information") + */ +class Client +{ + friend class Lease; + friend class ConfigurationManager; + + public: + Client(); + void initWithMac(const RTMAC& mac); + bool operator== (const RTMAC& mac) const; + const RTMAC& getMacAddress() const; + + /** Dumps client query */ + void dump(); + + Lease lease(); + const Lease lease() const; + + public: + static const Client NullClient; + + private: + Client(ClientData *); + SharedPtr<ClientData> m; +}; + + +bool operator== (const Lease&, const Lease&); +bool operator!= (const Lease&, const Lease&); +bool operator< (const Lease&, const Lease&); + + +typedef std::map<uint8_t, RawOption> MapOptionId2RawOption; +typedef MapOptionId2RawOption::iterator MapOptionId2RawOptionIterator; +typedef MapOptionId2RawOption::const_iterator MapOptionId2RawOptionConstIterator; +typedef MapOptionId2RawOption::value_type MapOptionId2RawOptionValue; + +namespace xml { + class ElementNode; +} + +class Lease +{ + friend class Client; + friend bool operator== (const Lease&, const Lease&); + //friend int ConfigurationManager::loadFromFile(const std::string&); + friend class ConfigurationManager; + + public: + Lease(); + Lease(const Client&); + + bool isExpired() const; + void expire(); + + /* Depending on phase *Expiration and phaseStart initialize different values. */ + void bindingPhase(bool); + void phaseStart(uint64_t u64Start); + bool isInBindingPhase() const; + /* returns 0 if in binding state */ + uint64_t issued() const; + + void setExpiration(uint32_t); + uint32_t getExpiration() const; + + RTNETADDRIPV4 getAddress() const; + void setAddress(RTNETADDRIPV4); + + const NetworkConfigEntity *getConfig() const; + void setConfig(NetworkConfigEntity *); + + const MapOptionId2RawOption& options() const; + + bool toXML(xml::ElementNode *) const; + bool fromXML(const xml::ElementNode *); + + public: + static const Lease NullLease; + + private: + Lease(ClientData *); + SharedPtr<ClientData> m; +}; + + +typedef std::vector<Client> VecClient; +typedef VecClient::iterator VecClientIterator; +typedef VecClient::const_iterator VecClientConstIterator; + +typedef std::vector<RTMAC> MacAddressContainer; +typedef MacAddressContainer::iterator MacAddressIterator; + +typedef std::vector<RTNETADDRIPV4> Ipv4AddressContainer; +typedef Ipv4AddressContainer::iterator Ipv4AddressIterator; +typedef Ipv4AddressContainer::const_iterator Ipv4AddressConstIterator; + +typedef std::map<Lease, RTNETADDRIPV4> MapLease2Ip4Address; +typedef MapLease2Ip4Address::iterator MapLease2Ip4AddressIterator; +typedef MapLease2Ip4Address::const_iterator MapLease2Ip4AddressConstIterator; +typedef MapLease2Ip4Address::value_type MapLease2Ip4AddressPair; + +/** + * + */ +class ClientMatchCriteria +{ + public: + virtual bool check(const Client&) const {return false;}; +}; + + +class ORClientMatchCriteria: ClientMatchCriteria +{ + ClientMatchCriteria* m_left; + ClientMatchCriteria* m_right; + ORClientMatchCriteria(ClientMatchCriteria *left, ClientMatchCriteria *right) + { + m_left = left; + m_right = right; + } + + virtual bool check(const Client& client) const + { + return (m_left->check(client) || m_right->check(client)); + } +}; + + +class ANDClientMatchCriteria: ClientMatchCriteria +{ +public: + ANDClientMatchCriteria(ClientMatchCriteria *left, ClientMatchCriteria *right) + { + m_left = left; + m_right = right; + } + + virtual bool check(const Client& client) const + { + return (m_left->check(client) && m_right->check(client)); + } + +private: + ClientMatchCriteria* m_left; + ClientMatchCriteria* m_right; + +}; + + +class AnyClientMatchCriteria: public ClientMatchCriteria +{ +public: + virtual bool check(const Client&) const + { + return true; + } +}; + + +class MACClientMatchCriteria: public ClientMatchCriteria +{ +public: + MACClientMatchCriteria(const RTMAC& mac):m_mac(mac){} + + virtual bool check(const Client& client) const; + +private: + RTMAC m_mac; +}; + + +#if 0 +/* XXX: Later */ +class VmSlotClientMatchCriteria: public ClientMatchCriteria +{ + str::string VmName; + uint8_t u8Slot; + virtual bool check(const Client& client) + { + return ( client.VmName == VmName + && ( u8Slot == (uint8_t)~0 /* any */ + || client.u8Slot == u8Slot)); + } +}; +#endif + + +/* Option 60 */ +class ClassClientMatchCriteria: ClientMatchCriteria{}; +/* Option 61 */ +class ClientIdentifierMatchCriteria: ClientMatchCriteria{}; + + +class BaseConfigEntity +{ + public: + BaseConfigEntity(const ClientMatchCriteria *criteria = NULL, + int matchingLevel = 0) + : m_criteria(criteria), + m_MatchLevel(matchingLevel){}; + virtual ~BaseConfigEntity(){}; + /* XXX */ + int add(BaseConfigEntity *cfg) + { + m_children.push_back(cfg); + return 0; + } + + /* Should return how strong matching */ + virtual int match(Client& client, BaseConfigEntity **cfg); + virtual uint32_t expirationPeriod() const = 0; + + protected: + const ClientMatchCriteria *m_criteria; + int m_MatchLevel; + std::vector<BaseConfigEntity *> m_children; +}; + + +class NullConfigEntity: public BaseConfigEntity +{ + public: + NullConfigEntity(){} + virtual ~NullConfigEntity(){} + int add(BaseConfigEntity *) const { return 0;} + virtual uint32_t expirationPeriod() const {return 0;} +}; + + +class ConfigEntity: public BaseConfigEntity +{ + public: + /* range */ + /* match conditions */ + ConfigEntity(std::string& name, + const BaseConfigEntity *cfg, + const ClientMatchCriteria *criteria, + int matchingLevel = 0): + BaseConfigEntity(criteria, matchingLevel), + m_name(name), + m_parentCfg(cfg), + m_u32ExpirationPeriod(0) + { + unconst(m_parentCfg)->add(this); + } + + virtual uint32_t expirationPeriod() const + { + if (!m_u32ExpirationPeriod) + return m_parentCfg->expirationPeriod(); + else + return m_u32ExpirationPeriod; + } + + /* XXX: private:*/ + std::string m_name; + const BaseConfigEntity *m_parentCfg; + uint32_t m_u32ExpirationPeriod; +}; + + +/** + * Network specific entries + */ +class NetworkConfigEntity:public ConfigEntity +{ +public: + /* Address Pool matching with network declaration */ + NetworkConfigEntity(std::string name, + const BaseConfigEntity *cfg, + const ClientMatchCriteria *criteria, + int matchlvl, + const RTNETADDRIPV4& networkID, + const RTNETADDRIPV4& networkMask, + const RTNETADDRIPV4& lowerIP, + const RTNETADDRIPV4& upperIP): + ConfigEntity(name, cfg, criteria, matchlvl), + m_NetworkID(networkID), + m_NetworkMask(networkMask), + m_UpperIP(upperIP), + m_LowerIP(lowerIP) + { + }; + + NetworkConfigEntity(std::string name, + const BaseConfigEntity *cfg, + const ClientMatchCriteria *criteria, + const RTNETADDRIPV4& networkID, + const RTNETADDRIPV4& networkMask): + ConfigEntity(name, cfg, criteria, 5), + m_NetworkID(networkID), + m_NetworkMask(networkMask) + { + m_UpperIP.u = m_NetworkID.u | (~m_NetworkMask.u); + m_LowerIP.u = m_NetworkID.u; + }; + + const RTNETADDRIPV4& upperIp() const {return m_UpperIP;} + const RTNETADDRIPV4& lowerIp() const {return m_LowerIP;} + const RTNETADDRIPV4& networkId() const {return m_NetworkID;} + const RTNETADDRIPV4& netmask() const {return m_NetworkMask;} + + private: + RTNETADDRIPV4 m_NetworkID; + RTNETADDRIPV4 m_NetworkMask; + RTNETADDRIPV4 m_UpperIP; + RTNETADDRIPV4 m_LowerIP; +}; + + +/** + * Host specific entry + * Address pool is contains one element + */ +class HostConfigEntity: public NetworkConfigEntity +{ +public: + HostConfigEntity(const RTNETADDRIPV4& addr, + std::string name, + const NetworkConfigEntity *cfg, + const ClientMatchCriteria *criteria): + NetworkConfigEntity(name, + static_cast<const ConfigEntity*>(cfg), criteria, 10, + cfg->networkId(), cfg->netmask(), addr, addr) + { + /* upper addr == lower addr */ + } +}; + +class RootConfigEntity: public NetworkConfigEntity +{ +public: + RootConfigEntity(std::string name, uint32_t expirationPeriod); + virtual ~RootConfigEntity(){}; +}; + + +#if 0 +/** + * Shared regions e.g. some of configured networks declarations + * are cover each other. + * XXX: Shared Network is join on Network config entities with possible + * overlaps in address pools. for a moment we won't configure and use them them + */ +class SharedNetworkConfigEntity: public NetworkEntity +{ +public: + SharedNetworkConfigEntity(){} + int match(const Client& client) const { return m_criteria.match(client)? 3 : 0;} + + SharedNetworkConfigEntity(NetworkEntity& network) + { + Networks.push_back(network); + } + virtual ~SharedNetworkConfigEntity(){} + + std::vector<NetworkConfigEntity> Networks; +}; +#endif + +class ConfigurationManager +{ +public: + static ConfigurationManager* getConfigurationManager(); + static int extractRequestList(PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg, RawOption& rawOpt); + + int loadFromFile(const com::Utf8Str&); + int saveToFile(); + /** + * + */ + Client getClientByDhcpPacket(const RTNETBOOTP *pDhcpMsg, size_t cbDhcpMsg); + + /** + * XXX: it's could be done on DHCPOFFER or on DHCPACK (rfc2131 gives freedom here + * 3.1.2, what is strict that allocation should do address check before real + * allocation)... + */ + Lease allocateLease4Client(const Client& client, PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg); + + /** + * We call this before DHCPACK sent and after DHCPREQUEST received ... + * when requested configuration is acceptable. + */ + int commitLease4Client(Client& client); + + /** + * Expires client lease. + */ + int expireLease4Client(Client& client); + + static int findOption(uint8_t uOption, PCRTNETBOOTP pDhcpMsg, size_t cbDhcpMsg, RawOption& opt); + + NetworkConfigEntity *addNetwork(NetworkConfigEntity *pCfg, + const RTNETADDRIPV4& networkId, + const RTNETADDRIPV4& netmask, + RTNETADDRIPV4& UpperAddress, + RTNETADDRIPV4& LowerAddress); + + HostConfigEntity *addHost(NetworkConfigEntity*, const RTNETADDRIPV4&, ClientMatchCriteria*); + int addToAddressList(uint8_t u8OptId, RTNETADDRIPV4& address); + int flushAddressList(uint8_t u8OptId); + int setString(uint8_t u8OptId, const std::string& str); + const std::string& getString(uint8_t u8OptId); + const Ipv4AddressContainer& getAddressList(uint8_t u8OptId); + +private: + ConfigurationManager():m(NULL){} + void init(); + + ~ConfigurationManager(); + bool isAddressTaken(const RTNETADDRIPV4& addr, Lease& lease); + bool isAddressTaken(const RTNETADDRIPV4& addr); + +public: + /* nulls */ + const Ipv4AddressContainer m_empty; + const std::string m_noString; + +private: + struct Data; + Data *m; +}; + + +class NetworkManager +{ +public: + static NetworkManager *getNetworkManager(ComPtr<IDHCPServer> aDhcpServer = ComPtr<IDHCPServer>()); + + const RTNETADDRIPV4& getOurAddress() const; + const RTNETADDRIPV4& getOurNetmask() const; + const RTMAC& getOurMac() const; + + void setOurAddress(const RTNETADDRIPV4& aAddress); + void setOurNetmask(const RTNETADDRIPV4& aNetmask); + void setOurMac(const RTMAC& aMac); + + 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 setService(const VBoxNetHlpUDPService *); +private: + NetworkManager(); + ~NetworkManager(); + + int offer4Client(const Client& lease, uint32_t u32Xid, uint8_t *pu8ReqList, int cReqList); + int ack(const Client& lease, uint32_t u32Xid, uint8_t *pu8ReqList, int cReqList); + int nak(const Client& lease, uint32_t u32Xid); + + int prepareReplyPacket4Client(const Client& client, uint32_t u32Xid); + int doReply(const Client& client, const std::vector<RawOption>& extra); + int processParameterReqList(const Client& client, const uint8_t *pu8ReqList, int cReqList, std::vector<RawOption>& extra); + +private: + static NetworkManager *g_NetworkManager; + +private: + struct Data; + Data *m; + +}; + + +extern const ClientMatchCriteria *g_AnyClient; +extern RootConfigEntity *g_RootConfig; +extern const NullConfigEntity *g_NullConfig; + +/** + * 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 u32 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 u32 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(); + } +}; + +#endif /* !VBOX_INCLUDED_SRC_DHCP_Config_h */ |