/* $Id: Db.cpp $ */ /** @file * DHCP server - address database */ /* * Copyright (C) 2017-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include "DhcpdInternal.h" #include #include "Db.h" /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** Indicates whether has been called successfully yet. */ bool Binding::g_fFormatRegistered = false; /** * Registers the ClientId format type callback ("%R[binding]"). */ void Binding::registerFormat() RT_NOEXCEPT { if (!g_fFormatRegistered) { int rc = RTStrFormatTypeRegister("binding", rtStrFormat, NULL); AssertRC(rc); g_fFormatRegistered = true; } } /** * @callback_method_impl{FNRTSTRFORMATTYPE, Formats ClientId via "%R[binding]".} */ DECLCALLBACK(size_t) Binding::rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, const char *pszType, void const *pvValue, int cchWidth, int cchPrecision, unsigned fFlags, void *pvUser) { AssertReturn(strcmp(pszType, "binding") == 0, 0); RT_NOREF(pszType); RT_NOREF(cchWidth, cchPrecision, fFlags); RT_NOREF(pvUser); const Binding *b = static_cast(pvValue); if (b == NULL) return pfnOutput(pvArgOutput, RT_STR_TUPLE("")); size_t cb = RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%RTnaipv4", b->m_addr.u); if (b->m_state == Binding::FREE) cb += pfnOutput(pvArgOutput, RT_STR_TUPLE(" free")); else if (b->m_fFixed) cb += pfnOutput(pvArgOutput, RT_STR_TUPLE(" fixed")); else { cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, " to %R[id], %s, valid from ", &b->m_id, b->stateName()); Timestamp tsIssued = b->issued(); cb += tsIssued.strFormatHelper(pfnOutput, pvArgOutput); cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, " for %ds until ", b->leaseTime()); Timestamp tsValid = b->issued(); tsValid.addSeconds(b->leaseTime()); cb += tsValid.strFormatHelper(pfnOutput, pvArgOutput); } return cb; } /** * Used to update the client ID of a fixed address assignment. * * We only have the MAC address when prepraring the binding, so the full client * ID must be supplied when the client requests it. * * @param a_ridClient The client ID. * @throws std::bad_alloc */ void Binding::idUpdate(const ClientId &a_ridClient) { AssertReturnVoid(isFixed()); m_id = a_ridClient; } /** * Get the state as a string for the XML lease database. */ const char *Binding::stateName() const RT_NOEXCEPT { switch (m_state) { case FREE: return "free"; case RELEASED: return "released"; case EXPIRED: return "expired"; case OFFERED: return "offered"; case ACKED: return "acked"; default: AssertMsgFailed(("%d\n", m_state)); return "released"; } } /** * Sets the state by name (reverse of Binding::stateName()). */ Binding &Binding::setState(const char *pszStateName) RT_NOEXCEPT { if (strcmp(pszStateName, "free") == 0) m_state = Binding::FREE; else if (strcmp(pszStateName, "released") == 0) m_state = Binding::RELEASED; else if (strcmp(pszStateName, "expired") == 0) m_state = Binding::EXPIRED; else if (strcmp(pszStateName, "offered") == 0) m_state = Binding::OFFERED; else if (strcmp(pszStateName, "acked") == 0) m_state = Binding::ACKED; else { AssertMsgFailed(("%d\n", m_state)); m_state = Binding::RELEASED; } return *this; } /** * Expires the binding if it's past the specified deadline. * * @returns False if already expired, released or freed, otherwise true (i.e. * does not indicate whether action was taken or not). * @param tsDeadline The expiry deadline to use. */ bool Binding::expire(Timestamp tsDeadline) RT_NOEXCEPT { if (m_state <= Binding::EXPIRED || m_fFixed) return false; Timestamp tsExpire = m_issued; tsExpire.addSeconds(m_secLease); if (tsExpire < tsDeadline) { if (m_state == Binding::OFFERED) setState(Binding::FREE); else setState(Binding::EXPIRED); } return true; } /** * Serializes the binding to XML for the lease database. * * @throw std::bad_alloc * @note DHCPServerImpl.cpp contains a reader, keep it in sync. */ void Binding::toXML(xml::ElementNode *pElmParent) const { /* * Lease */ xml::ElementNode *pElmLease = pElmParent->createChild("Lease"); pElmLease->setAttribute("mac", RTCStringFmt("%RTmac", &m_id.mac())); if (m_id.id().present()) { /* I'd prefer RTSTRPRINTHEXBYTES_F_SEP_COLON but there's no decoder */ size_t cbStrId = m_id.id().value().size() * 2 + 1; char *pszId = new char[cbStrId]; int rc = RTStrPrintHexBytes(pszId, cbStrId, &m_id.id().value().front(), m_id.id().value().size(), 0); AssertRC(rc); pElmLease->setAttribute("id", pszId); delete[] pszId; } /* unused but we need it to keep the old code happy */ pElmLease->setAttribute("network", "0.0.0.0"); pElmLease->setAttribute("state", stateName()); /* * Lease/Address */ xml::ElementNode *pElmAddr = pElmLease->createChild("Address"); pElmAddr->setAttribute("value", RTCStringFmt("%RTnaipv4", m_addr.u)); /* * Lease/Time */ xml::ElementNode *pElmTime = pElmLease->createChild("Time"); pElmTime->setAttribute("issued", m_issued.getAbsSeconds()); pElmTime->setAttribute("expiration", m_secLease); } /** * Deserializes the binding from the XML lease database. * * @param pElmLease The "Lease" element to serialize into. * @return Pointer to the resulting binding, NULL on failure. * @throw std::bad_alloc * @note DHCPServerImpl.cpp contains a similar reader, keep it in sync. */ Binding *Binding::fromXML(const xml::ElementNode *pElmLease) { /* Note! Lease/@network seems to always have bogus value, ignore it. */ /* Note! We parse the mandatory attributes and elements first, then the optional ones. This means things appear a little jumbled. */ /* * Lease/@mac - mandatory. */ const char *pszMacAddress = pElmLease->findAttributeValue("mac"); if (!pszMacAddress) DHCP_LOG_RET_NULL(("Binding::fromXML: element without 'mac' attribute! Skipping lease.\n")); RTMAC mac; int rc = RTNetStrToMacAddr(pszMacAddress, &mac); if (RT_FAILURE(rc)) DHCP_LOG_RET_NULL(("Binding::fromXML: Malformed mac address attribute value '%s': %Rrc - Skipping lease.\n", pszMacAddress, rc)); /* * Lease/Address/@value - mandatory. */ const char *pszAddress = pElmLease->findChildElementAttributeValue("Address", "value"); if (!pszAddress) DHCP_LOG_RET_NULL(("Binding::fromXML: Could not find
with a 'value' attribute! Skipping lease.\n")); RTNETADDRIPV4 addr; rc = RTNetStrToIPv4Addr(pszAddress, &addr); if (RT_FAILURE(rc)) DHCP_LOG_RET_NULL(("Binding::fromXML: Malformed IPv4 address value '%s': %Rrc - Skipping lease.\n", pszAddress, rc)); /* * Lease/Time - mandatory. */ const xml::ElementNode *pElmTime = pElmLease->findChildElement("Time"); if (pElmTime == NULL) DHCP_LOG_RET_NULL(("Binding::fromXML: No