summaryrefslogtreecommitdiffstats
path: root/src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/NetworkServices/Dhcpd/DhcpMessage.cpp412
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;
+}