diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp b/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp new file mode 100644 index 00000000..2165959f --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp @@ -0,0 +1,412 @@ +/* $Id: DhcpMessage.cpp $ */ +/** @file + * DHCP Message and its de/serialization. + */ + +/* + * Copyright (C) 2017-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. + */ + +#include "DhcpMessage.h" +#include "DhcpOptions.h" + +#include <iprt/string.h> +#include <iprt/stream.h> + + + +DhcpMessage::DhcpMessage() + : m_xid(0), m_flags(0), + m_ciaddr(), m_yiaddr(), m_siaddr(), m_giaddr(), + m_sname(), m_file(), + m_optMessageType() +{ +} + + +/* static */ +DhcpClientMessage *DhcpClientMessage::parse(bool broadcasted, const void *buf, size_t buflen) +{ + if (buflen < RT_OFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts)) + { + RTPrintf("%s: %zu bytes datagram is too short\n", __FUNCTION__, buflen); + return NULL; + } + + PCRTNETBOOTP bp = (PCRTNETBOOTP)buf; + + if (bp->bp_op != RTNETBOOTP_OP_REQUEST) + { + RTPrintf("%s: bad opcode: %d\n", __FUNCTION__, bp->bp_op); + return NULL; + } + + if (bp->bp_htype != RTNET_ARP_ETHER) + { + RTPrintf("%s: unsupported htype %d\n", __FUNCTION__, bp->bp_htype); + return NULL; + } + + if (bp->bp_hlen != sizeof(RTMAC)) + { + RTPrintf("%s: unexpected hlen %d\n", __FUNCTION__, bp->bp_hlen); + return NULL; + } + + if ( (bp->bp_chaddr.Mac.au8[0] & 0x01) != 0 + && (bp->bp_flags & RTNET_DHCP_FLAG_BROADCAST) == 0) + { + RTPrintf("%s: multicast chaddr %RTmac without broadcast flag\n", + __FUNCTION__, &bp->bp_chaddr.Mac); + } + + /* we don't want to deal with forwarding */ + if (bp->bp_giaddr.u != 0) + { + RTPrintf("%s: giaddr %RTnaipv4\n", __FUNCTION__, bp->bp_giaddr.u); + return NULL; + } + + if (bp->bp_hops != 0) + { + RTPrintf("%s: non-zero hops %d\n", __FUNCTION__, bp->bp_hops); + return NULL; + } + + std::unique_ptr<DhcpClientMessage> msg(new DhcpClientMessage()); + + msg->m_broadcasted = broadcasted; + + msg->m_xid = bp->bp_xid; + msg->m_flags = bp->bp_flags; + + msg->m_mac = bp->bp_chaddr.Mac; + + msg->m_ciaddr = bp->bp_ciaddr; + msg->m_yiaddr = bp->bp_yiaddr; + msg->m_siaddr = bp->bp_siaddr; + msg->m_giaddr = bp->bp_giaddr; + + if (bp->bp_vend.Dhcp.dhcp_cookie != RT_H2N_U32_C(RTNET_DHCP_COOKIE)) + { + RTPrintf("bad cookie\n"); + return NULL; + } + + int overload; + overload = msg->parseOptions(&bp->bp_vend.Dhcp.dhcp_opts, + buflen - RT_OFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_opts)); + if (overload < 0) + return NULL; + + /* "The 'file' field MUST be interpreted next ..." */ + if (overload & DHCP_OPTION_OVERLOAD_FILE) { + int status = msg->parseOptions(bp->bp_file, sizeof(bp->bp_file)); + if (status != 0) + return NULL; + } + else if (bp->bp_file[0] != '\0') + { + /* must be zero terminated, ignore if not */ + const char *pszFile = (const char *)bp->bp_file; + size_t len = RTStrNLen(pszFile, sizeof(bp->bp_file)); + if (len < sizeof(bp->bp_file)) + msg->m_file.assign(pszFile, len); + } + + /* "... followed by the 'sname' field." */ + if (overload & DHCP_OPTION_OVERLOAD_SNAME) { + int status = msg->parseOptions(bp->bp_sname, sizeof(bp->bp_sname)); + if (status != 0) /* NB: this includes "nested" Option Overload */ + return NULL; + } + else if (bp->bp_sname[0] != '\0') + { + /* must be zero terminated, ignore if not */ + const char *pszSName = (const char *)bp->bp_sname; + size_t len = RTStrNLen(pszSName, sizeof(bp->bp_sname)); + if (len < sizeof(bp->bp_sname)) + msg->m_sname.assign(pszSName, len); + } + + msg->m_optMessageType = OptMessageType(*msg); + if (!msg->m_optMessageType.present()) + return NULL; + + msg->m_id = ClientId(msg->m_mac, OptClientId(*msg)); + + return msg.release(); +} + + +int DhcpClientMessage::parseOptions(const void *buf, size_t buflen) +{ + uint8_t opt, optlen; + const uint8_t *data; + int overload; + + overload = 0; + + data = static_cast<const uint8_t *>(buf); + while (buflen > 0) { + opt = *data++; + --buflen; + + if (opt == RTNET_DHCP_OPT_PAD) { + continue; + } + + if (opt == RTNET_DHCP_OPT_END) { + break; + } + + if (buflen == 0) { + RTPrintf("option %d has no length field\n", opt); + return -1; + } + + optlen = *data++; + --buflen; + + if (optlen > buflen) { + RTPrintf("option %d truncated (length %d, but only %lu bytes left)\n", + opt, optlen, (unsigned long)buflen); + return -1; + } + +#if 0 + rawopts_t::const_iterator it(m_optmap.find(opt)); + if (it != m_optmap.cend()) + return -1; +#endif + if (opt == RTNET_DHCP_OPT_OPTION_OVERLOAD) { + if (optlen != 1) { + RTPrintf("Overload Option (option %d) has invalid length %d\n", + opt, optlen); + return -1; + } + + overload = *data; + + if ((overload & ~DHCP_OPTION_OVERLOAD_MASK) != 0) { + RTPrintf("Overload Option (option %d) has invalid value 0x%x\n", + opt, overload); + return -1; + } + } + else + { + m_rawopts.insert(std::make_pair(opt, octets_t(data, data + optlen))); + } + + data += optlen; + buflen -= optlen; + } + + return overload; +} + + +void DhcpClientMessage::dump() const +{ + switch (m_optMessageType.value()) + { + case RTNET_DHCP_MT_DISCOVER: + LogDHCP(("DISCOVER")); + break; + + case RTNET_DHCP_MT_REQUEST: + LogDHCP(("REQUEST")); + break; + + case RTNET_DHCP_MT_INFORM: + LogDHCP(("INFORM")); + break; + + case RTNET_DHCP_MT_DECLINE: + LogDHCP(("DECLINE")); + break; + + case RTNET_DHCP_MT_RELEASE: + LogDHCP(("RELEASE")); + break; + + default: + LogDHCP(("<Unknown Mesage Type %d>", m_optMessageType.value())); + break; + } + + if (OptRapidCommit(*this).present()) + LogDHCP((" (rapid commit)")); + + + const OptServerId sid(*this); + if (sid.present()) + LogDHCP((" for server %RTnaipv4", sid.value().u)); + + LogDHCP((" xid 0x%08x", m_xid)); + LogDHCP((" chaddr %RTmac\n", &m_mac)); + + const OptClientId cid(*this); + if (cid.present()) { + if (cid.value().size() > 0) + LogDHCP((" client id: %.*Rhxs\n", cid.value().size(), &cid.value().front())); + else + LogDHCP((" client id: <empty>\n")); + } + + LogDHCP((" ciaddr %RTnaipv4", m_ciaddr.u)); + if (m_yiaddr.u != 0) + LogDHCP((" yiaddr %RTnaipv4", m_yiaddr.u)); + if (m_siaddr.u != 0) + LogDHCP((" siaddr %RTnaipv4", m_siaddr.u)); + if (m_giaddr.u != 0) + LogDHCP((" giaddr %RTnaipv4", m_giaddr.u)); + LogDHCP(("%s\n", broadcast() ? "broadcast" : "")); + + + const OptRequestedAddress reqAddr(*this); + if (reqAddr.present()) + LogDHCP((" requested address %RTnaipv4", reqAddr.value().u)); + const OptLeaseTime reqLeaseTime(*this); + if (reqLeaseTime.present()) + LogDHCP((" requested lease time %d", reqAddr.value())); + if (reqAddr.present() || reqLeaseTime.present()) + LogDHCP(("\n")); + + const OptParameterRequest params(*this); + if (params.present()) + { + LogDHCP((" params {")); + typedef OptParameterRequest::value_t::const_iterator it_t; + for (it_t it = params.value().begin(); it != params.value().end(); ++it) + LogDHCP((" %d", *it)); + LogDHCP((" }\n")); + } + + bool fHeader = true; + for (rawopts_t::const_iterator it = m_rawopts.begin(); + it != m_rawopts.end(); ++it) + { + const uint8_t optcode = (*it).first; + switch (optcode) { + case OptMessageType::optcode: /* FALLTHROUGH */ + case OptClientId::optcode: /* FALLTHROUGH */ + case OptRequestedAddress::optcode: /* FALLTHROUGH */ + case OptLeaseTime::optcode: /* FALLTHROUGH */ + case OptParameterRequest::optcode: /* FALLTHROUGH */ + case OptRapidCommit::optcode: + break; + + default: + if (fHeader) + { + LogDHCP((" other options:")); + fHeader = false; + } + LogDHCP((" %d", optcode)); + break; + } + } + if (!fHeader) + LogDHCP(("\n")); +} + + +DhcpServerMessage::DhcpServerMessage(const DhcpClientMessage &req, + uint8_t messageTypeParam, RTNETADDRIPV4 serverAddr) + : DhcpMessage(), + m_optServerId(serverAddr) +{ + m_dst.u = 0xffffffff; /* broadcast */ + + m_optMessageType = OptMessageType(messageTypeParam); + + /* copy values from the request (cf. RFC2131 Table 3) */ + m_xid = req.xid(); + m_flags = req.flags(); + m_giaddr = req.giaddr(); + m_mac = req.mac(); + + if (req.messageType() == RTNET_DHCP_MT_REQUEST) + m_ciaddr = req.ciaddr(); +} + + +void DhcpServerMessage::maybeUnicast(const DhcpClientMessage &req) +{ + if (!req.broadcast() && req.ciaddr().u != 0) + setDst(req.ciaddr()); +} + + +void DhcpServerMessage::addOption(DhcpOption *opt) +{ + m_optmap << opt; +} + + +void DhcpServerMessage::addOptions(const optmap_t &optmap) +{ + for (optmap_t::const_iterator it ( optmap.begin() ); + it != optmap.end(); ++it) + { + m_optmap << it->second; + } +} + + +int DhcpServerMessage::encode(octets_t &data) +{ + /* + * Header, including DHCP cookie. + */ + RTNETBOOTP bp; + RT_ZERO(bp); + + bp.bp_op = RTNETBOOTP_OP_REPLY; + bp.bp_htype = RTNET_ARP_ETHER; + bp.bp_hlen = sizeof(RTMAC); + + bp.bp_xid = m_xid; + + bp.bp_ciaddr = m_ciaddr; + bp.bp_yiaddr = m_yiaddr; + bp.bp_siaddr = m_siaddr; + bp.bp_giaddr = m_giaddr; + + bp.bp_chaddr.Mac = m_mac; + + bp.bp_vend.Dhcp.dhcp_cookie = RT_H2N_U32_C(RTNET_DHCP_COOKIE); + + data.insert(data.end(), (uint8_t *)&bp, (uint8_t *)&bp.bp_vend.Dhcp.dhcp_opts); + + /* + * Options + */ + data << m_optServerId + << m_optMessageType; + + for (optmap_t::const_iterator it ( m_optmap.begin() ); + it != m_optmap.end(); ++it) + { + RTPrintf("encoding option %d\n", it->first); + DhcpOption &opt = *it->second; + data << opt; + } + + data << OptEnd(); + + if (data.size() < 548) /* XXX */ + data.resize(548); + + return VINF_SUCCESS; +} |