/* $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 #include 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 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(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(("", 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: \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; }