summaryrefslogtreecommitdiffstats
path: root/src/VBox/NetworkServices/Dhcpd/Db.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/NetworkServices/Dhcpd/Db.cpp747
1 files changed, 747 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/Dhcpd/Db.cpp b/src/VBox/NetworkServices/Dhcpd/Db.cpp
new file mode 100644
index 00000000..dfafc784
--- /dev/null
+++ b/src/VBox/NetworkServices/Dhcpd/Db.cpp
@@ -0,0 +1,747 @@
+/* $Id: Db.cpp $ */
+/** @file
+ * DHCP server - address database
+ */
+
+/*
+ * 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 <iprt/errcore.h>
+#include <iprt/stream.h>
+
+#include "Db.h"
+
+
+Db::Db()
+ : m_pConfig(NULL)
+{
+ return;
+}
+
+
+Db::~Db()
+{
+ /** @todo free bindings */
+}
+
+
+int Db::init(const Config *pConfig)
+{
+ Binding::registerFormat();
+
+ m_pConfig = pConfig;
+
+ m_pool.init(pConfig->getIPv4PoolFirst(),
+ pConfig->getIPv4PoolLast());
+
+ return VINF_SUCCESS;
+}
+
+
+bool Binding::g_fFormatRegistered = false;
+
+
+void Binding::registerFormat()
+{
+ if (g_fFormatRegistered)
+ return;
+
+ int rc = RTStrFormatTypeRegister("binding", rtStrFormat, NULL);
+ AssertRC(rc);
+
+ g_fFormatRegistered = true;
+}
+
+
+DECLCALLBACK(size_t)
+Binding::rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
+ const char *pszType, void const *pvValue,
+ int cchWidth, int cchPrecision, unsigned fFlags,
+ void *pvUser)
+{
+ const Binding *b = static_cast<const Binding *>(pvValue);
+ size_t cb = 0;
+
+ AssertReturn(strcmp(pszType, "binding") == 0, 0);
+ RT_NOREF(pszType);
+
+ RT_NOREF(cchWidth, cchPrecision, fFlags);
+ RT_NOREF(pvUser);
+
+ if (b == NULL)
+ {
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "<NULL>");
+ }
+
+ cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "%RTnaipv4", b->m_addr.u);
+
+ if (b->m_state == Binding::FREE)
+ {
+ cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ " free");
+ }
+ else
+ {
+ cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ " to %R[id], %s, valid from ",
+ &b->m_id, b->stateName());
+
+ TimeStamp tsIssued = b->issued();
+ cb += tsIssued.absStrFormat(pfnOutput, pvArgOutput);
+
+ cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ " for %ds until ",
+ b->leaseTime());
+
+ TimeStamp tsValid = b->issued();
+ tsValid.addSeconds(b->leaseTime());
+ cb += tsValid.absStrFormat(pfnOutput, pvArgOutput);
+ }
+
+ return cb;
+}
+
+const char *Binding::stateName() const
+{
+ switch (m_state) {
+ case FREE:
+ return "free";
+ case RELEASED:
+ return "released";
+ case EXPIRED:
+ return "expired";
+ case OFFERED:
+ return "offered";
+ case ACKED:
+ return "acked";
+ default:
+ return "released";
+ }
+}
+
+
+Binding &Binding::setState(const char *pszStateName)
+{
+ 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
+ m_state = Binding::RELEASED;
+
+ return *this;
+}
+
+
+bool Binding::expire(TimeStamp deadline)
+{
+ if (m_state <= Binding::EXPIRED)
+ return false;
+
+ TimeStamp t = m_issued;
+ t.addSeconds(m_secLease);
+
+ if (t < deadline)
+ {
+ if (m_state == Binding::OFFERED)
+ setState(Binding::FREE);
+ else
+ setState(Binding::EXPIRED);
+ }
+ return true;
+}
+
+
+int Binding::toXML(xml::ElementNode *ndParent) const
+{
+ int rc;
+
+ /*
+ * Lease
+ */
+ xml::ElementNode *ndLease = ndParent->createChild("Lease");
+ if (ndLease == NULL)
+ return VERR_GENERAL_FAILURE;
+
+ /* XXX: arrange for lease to get deleted if anything below fails */
+
+
+ ndLease->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];
+ rc = RTStrPrintHexBytes(pszId, cbStrId,
+ &m_id.id().value().front(), m_id.id().value().size(),
+ 0);
+ ndLease->setAttribute("id", pszId);
+ delete[] pszId;
+ }
+
+ /* unused but we need it to keep the old code happy */
+ ndLease->setAttribute("network", "0.0.0.0");
+
+ ndLease->setAttribute("state", stateName());
+
+
+ /*
+ * Lease/Address
+ */
+ xml::ElementNode *ndAddr = ndLease->createChild("Address");
+ ndAddr->setAttribute("value", RTCStringFmt("%RTnaipv4", m_addr.u));
+
+
+ /*
+ * Lease/Time
+ */
+ xml::ElementNode *ndTime = ndLease->createChild("Time");
+ ndTime->setAttribute("issued", m_issued.getAbsSeconds());
+ ndTime->setAttribute("expiration", m_secLease);
+
+ return VINF_SUCCESS;
+}
+
+
+Binding *Binding::fromXML(const xml::ElementNode *ndLease)
+{
+ int rc;
+
+ /* Lease/@network seems to always have bogus value, ignore it. */
+
+ /*
+ * Lease/@mac
+ */
+ RTCString strMac;
+ bool fHasMac = ndLease->getAttributeValue("mac", &strMac);
+ if (!fHasMac)
+ return NULL;
+
+ RTMAC mac;
+ rc = RTNetStrToMacAddr(strMac.c_str(), &mac);
+ if (RT_FAILURE(rc))
+ return NULL;
+
+ OptClientId id;
+ RTCString strId;
+ bool fHasId = ndLease->getAttributeValue("id", &strId);
+ if (fHasId)
+ {
+ /*
+ * Decode from "de:ad:be:ef".
+ * XXX: RTStrConvertHexBytes() doesn't grok colons
+ */
+ size_t cbBytes = strId.length() / 2;
+ uint8_t *pBytes = new uint8_t[cbBytes];
+ rc = RTStrConvertHexBytes(strId.c_str(), pBytes, cbBytes, 0);
+ if (RT_SUCCESS(rc))
+ {
+ std::vector<uint8_t> rawopt(pBytes, pBytes + cbBytes);
+ id = OptClientId(rawopt);
+ }
+ delete[] pBytes;
+ }
+
+ /*
+ * Lease/@state - not present in old leases file. We will try to
+ * infer from lease time below.
+ */
+ RTCString strState;
+ bool fHasState = ndLease->getAttributeValue("state", &strState);
+
+ /*
+ * Lease/Address
+ */
+ const xml::ElementNode *ndAddress = ndLease->findChildElement("Address");
+ if (ndAddress == NULL)
+ return NULL;
+
+ /*
+ * Lease/Address/@value
+ */
+ RTCString strAddress;
+ bool fHasValue = ndAddress->getAttributeValue("value", &strAddress);
+ if (!fHasValue)
+ return NULL;
+
+ RTNETADDRIPV4 addr;
+ rc = RTNetStrToIPv4Addr(strAddress.c_str(), &addr);
+ if (RT_FAILURE(rc))
+ return NULL;
+
+ /*
+ * Lease/Time
+ */
+ const xml::ElementNode *ndTime = ndLease->findChildElement("Time");
+ if (ndTime == NULL)
+ return NULL;
+
+ /*
+ * Lease/Time/@issued
+ */
+ int64_t issued;
+ bool fHasIssued = ndTime->getAttributeValue("issued", &issued);
+ if (!fHasIssued)
+ return NULL;
+
+ /*
+ * Lease/Time/@expiration
+ */
+ uint32_t duration;
+ bool fHasExpiration = ndTime->getAttributeValue("expiration", &duration);
+ if (!fHasExpiration)
+ return NULL;
+
+ std::unique_ptr<Binding> b(new Binding(addr));
+ b->m_id = ClientId(mac, id);
+
+ if (fHasState)
+ {
+ b->m_issued = TimeStamp::absSeconds(issued);
+ b->m_secLease = duration;
+ b->setState(strState.c_str());
+ }
+ else
+ { /* XXX: old code wrote timestamps instead of absolute time. */
+ /* pretend that lease has just ended */
+ TimeStamp fakeIssued = TimeStamp::now();
+ fakeIssued.subSeconds(duration);
+ b->m_issued = fakeIssued;
+ b->m_secLease = duration;
+ b->m_state = Binding::EXPIRED;
+ }
+
+ return b.release();
+}
+
+
+void Db::expire()
+{
+ const TimeStamp now = TimeStamp::now();
+
+ for (bindings_t::iterator it = m_bindings.begin();
+ it != m_bindings.end(); ++it)
+ {
+ Binding *b = *it;
+ b->expire(now);
+ }
+}
+
+
+Binding *Db::createBinding(const ClientId &id)
+{
+ RTNETADDRIPV4 addr = m_pool.allocate();
+ if (addr.u == 0)
+ return NULL;
+
+ Binding *b = new Binding(addr, id);
+ m_bindings.push_front(b);
+ return b;
+}
+
+
+Binding *Db::createBinding(RTNETADDRIPV4 addr, const ClientId &id)
+{
+ bool fAvailable = m_pool.allocate(addr);
+ if (!fAvailable)
+ {
+ /*
+ * XXX: this should not happen. If the address is from the
+ * pool, which we have verified before, then either it's in
+ * the free pool or there's an binding (possibly free) for it.
+ */
+ return NULL;
+ }
+
+ Binding *b = new Binding(addr, id);
+ m_bindings.push_front(b);
+ return b;
+}
+
+
+Binding *Db::allocateAddress(const ClientId &id, RTNETADDRIPV4 addr)
+{
+ Assert(addr.u == 0 || addressBelongs(addr));
+
+ Binding *addrBinding = NULL;
+ Binding *freeBinding = NULL;
+ Binding *reuseBinding = NULL;
+
+ if (addr.u != 0)
+ LogDHCP(("> allocateAddress %RTnaipv4 to client %R[id]\n", addr.u, &id));
+ else
+ LogDHCP(("> allocateAddress to client %R[id]\n", &id));
+
+ /*
+ * Allocate existing address if client has one. Ignore requested
+ * address in that case. While here, look for free addresses and
+ * addresses that can be reused.
+ */
+ const TimeStamp now = TimeStamp::now();
+ for (bindings_t::iterator it = m_bindings.begin();
+ it != m_bindings.end(); ++it)
+ {
+ Binding *b = *it;
+ b->expire(now);
+
+ /*
+ * We've already seen this client, give it its old binding.
+ */
+ if (b->m_id == id)
+ {
+ LogDHCP(("> ... found existing binding %R[binding]\n", b));
+ return b;
+ }
+
+ if (addr.u != 0 && b->m_addr.u == addr.u)
+ {
+ Assert(addrBinding == NULL);
+ addrBinding = b;
+ LogDHCP(("> .... noted existing binding %R[binding]\n", addrBinding));
+ }
+
+ /* if we haven't found a free binding yet, keep looking */
+ if (freeBinding == NULL)
+ {
+ if (b->m_state == Binding::FREE)
+ {
+ freeBinding = b;
+ LogDHCP(("> .... noted free binding %R[binding]\n", freeBinding));
+ continue;
+ }
+
+ /* still no free binding, can this one be reused? */
+ if (b->m_state == Binding::RELEASED)
+ {
+ if ( reuseBinding == NULL
+ /* released binding is better than an expired one */
+ || reuseBinding->m_state == Binding::EXPIRED)
+ {
+ reuseBinding = b;
+ LogDHCP(("> .... noted released binding %R[binding]\n", reuseBinding));
+ }
+ }
+ else if (b->m_state == Binding::EXPIRED)
+ {
+ if ( reuseBinding == NULL
+ /* long expired binding is bettern than a recent one */
+ /* || (reuseBinding->m_state == Binding::EXPIRED && b->olderThan(reuseBinding)) */)
+ {
+ reuseBinding = b;
+ LogDHCP(("> .... noted expired binding %R[binding]\n", reuseBinding));
+ }
+ }
+ }
+ }
+
+ /*
+ * Allocate requested address if we can.
+ */
+ if (addr.u != 0)
+ {
+ if (addrBinding == NULL)
+ {
+ addrBinding = createBinding(addr, id);
+ Assert(addrBinding != NULL);
+ LogDHCP(("> .... creating new binding for this address %R[binding]\n",
+ addrBinding));
+ return addrBinding;
+ }
+
+ if (addrBinding->m_state <= Binding::EXPIRED) /* not in use */
+ {
+ LogDHCP(("> .... reusing %s binding for this address\n",
+ addrBinding->stateName()));
+ addrBinding->giveTo(id);
+ return addrBinding;
+ }
+ else
+ {
+ LogDHCP(("> .... cannot reuse %s binding for this address\n",
+ addrBinding->stateName()));
+ }
+ }
+
+ /*
+ * Allocate new (or reuse).
+ */
+ Binding *idBinding = NULL;
+ if (freeBinding != NULL)
+ {
+ idBinding = freeBinding;
+ LogDHCP(("> .... reusing free binding\n"));
+ }
+ else
+ {
+ idBinding = createBinding();
+ if (idBinding != NULL)
+ {
+ LogDHCP(("> .... creating new binding\n"));
+ }
+ else
+ {
+ idBinding = reuseBinding;
+ LogDHCP(("> .... reusing %s binding %R[binding]\n",
+ reuseBinding->stateName(), reuseBinding));
+ }
+ }
+
+ if (idBinding == NULL)
+ {
+ LogDHCP(("> .... failed to allocate binding\n"));
+ return NULL;
+ }
+
+ idBinding->giveTo(id);
+ LogDHCP(("> .... allocated %R[binding]\n", idBinding));
+
+ return idBinding;
+}
+
+
+
+Binding *Db::allocateBinding(const DhcpClientMessage &req)
+{
+ /** @todo XXX: handle fixed address assignments */
+ OptRequestedAddress reqAddr(req);
+ if (reqAddr.present() && !addressBelongs(reqAddr.value()))
+ {
+ if (req.messageType() == RTNET_DHCP_MT_DISCOVER)
+ {
+ LogDHCP(("DISCOVER: ignoring invalid requested address\n"));
+ reqAddr = OptRequestedAddress();
+ }
+ else
+ {
+ LogDHCP(("rejecting invalid requested address\n"));
+ return NULL;
+ }
+ }
+
+ const ClientId &id(req.clientId());
+
+ Binding *b = allocateAddress(id, reqAddr.value());
+ if (b == NULL)
+ return NULL;
+
+ Assert(b->id() == id);
+
+ /*
+ * XXX: handle requests for specific lease time!
+ * XXX: old lease might not have expired yet?
+ */
+ // OptLeaseTime reqLeaseTime(req);
+ b->setLeaseTime(1200);
+ return b;
+}
+
+
+int Db::addBinding(Binding *newb)
+{
+ if (!addressBelongs(newb->m_addr))
+ {
+ LogDHCP(("Binding for out of range address %RTnaipv4 ignored\n",
+ newb->m_addr.u));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ for (bindings_t::iterator it = m_bindings.begin();
+ it != m_bindings.end(); ++it)
+ {
+ Binding *b = *it;
+
+ if (newb->m_addr.u == b->m_addr.u)
+ {
+ LogDHCP(("> ADD: %R[binding]\n", newb));
+ LogDHCP(("> .... duplicate ip: %R[binding]\n", b));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (newb->m_id == b->m_id)
+ {
+ LogDHCP(("> ADD: %R[binding]\n", newb));
+ LogDHCP(("> .... duplicate id: %R[binding]\n", b));
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+
+ bool ok = m_pool.allocate(newb->m_addr);
+ if (!ok)
+ {
+ LogDHCP(("> ADD: failed to claim IP %R[binding]\n", newb));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ m_bindings.push_back(newb);
+ return VINF_SUCCESS;
+}
+
+
+void Db::cancelOffer(const DhcpClientMessage &req)
+{
+ const OptRequestedAddress reqAddr(req);
+ if (!reqAddr.present())
+ return;
+
+ const RTNETADDRIPV4 addr = reqAddr.value();
+ const ClientId &id(req.clientId());
+
+ for (bindings_t::iterator it = m_bindings.begin();
+ it != m_bindings.end(); ++it)
+ {
+ Binding *b = *it;
+
+ if (b->addr().u == addr.u && b->id() == id)
+ {
+ if (b->state() == Binding::OFFERED)
+ {
+ b->setLeaseTime(0);
+ b->setState(Binding::RELEASED);
+ }
+ return;
+ }
+ }
+}
+
+
+bool Db::releaseBinding(const DhcpClientMessage &req)
+{
+ const RTNETADDRIPV4 addr = req.ciaddr();
+ const ClientId &id(req.clientId());
+
+ for (bindings_t::iterator it = m_bindings.begin();
+ it != m_bindings.end(); ++it)
+ {
+ Binding *b = *it;
+
+ if (b->addr().u == addr.u && b->id() == id)
+ {
+ b->setState(Binding::RELEASED);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+int Db::writeLeases(const std::string &strFileName) const
+{
+ LogDHCP(("writing leases to %s\n", strFileName.c_str()));
+
+ xml::Document doc;
+
+ xml::ElementNode *root = doc.createRootElement("Leases");
+ if (root == NULL)
+ return VERR_INTERNAL_ERROR;
+
+ root->setAttribute("version", "1.0");
+
+ for (bindings_t::const_iterator it = m_bindings.begin();
+ it != m_bindings.end(); ++it)
+ {
+ const Binding *b = *it;
+ b->toXML(root);
+ }
+
+ try {
+ xml::XmlFileWriter writer(doc);
+ writer.write(strFileName.c_str(), true);
+ }
+ catch (const xml::EIPRTFailure &e)
+ {
+ LogDHCP(("%s\n", e.what()));
+ return e.rc();
+ }
+ catch (const RTCError &e)
+ {
+ LogDHCP(("%s\n", e.what()));
+ return VERR_GENERAL_FAILURE;
+ }
+ catch (...)
+ {
+ LogDHCP(("Unknown exception while writing '%s'\n",
+ strFileName.c_str()));
+ return VERR_GENERAL_FAILURE;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+int Db::loadLeases(const std::string &strFileName)
+{
+ LogDHCP(("loading leases from %s\n", strFileName.c_str()));
+
+ xml::Document doc;
+ try
+ {
+ xml::XmlFileParser parser;
+ parser.read(strFileName.c_str(), doc);
+ }
+ catch (const xml::EIPRTFailure &e)
+ {
+ LogDHCP(("%s\n", e.what()));
+ return e.rc();
+ }
+ catch (const RTCError &e)
+ {
+ LogDHCP(("%s\n", e.what()));
+ return VERR_GENERAL_FAILURE;
+ }
+ catch (...)
+ {
+ LogDHCP(("Unknown exception while reading and parsing '%s'\n",
+ strFileName.c_str()));
+ return VERR_GENERAL_FAILURE;
+ }
+
+ xml::ElementNode *ndRoot = doc.getRootElement();
+ if (ndRoot == NULL || !ndRoot->nameEquals("Leases"))
+ {
+ return VERR_NOT_FOUND;
+ }
+
+ xml::NodesLoop it(*ndRoot);
+ const xml::ElementNode *node;
+ while ((node = it.forAllNodes()) != NULL)
+ {
+ if (!node->nameEquals("Lease"))
+ continue;
+
+ loadLease(node);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+void Db::loadLease(const xml::ElementNode *ndLease)
+{
+ Binding *b = Binding::fromXML(ndLease);
+ bool expired = b->expire();
+
+ if (!expired)
+ LogDHCP(("> LOAD: lease %R[binding]\n", b));
+ else
+ LogDHCP(("> LOAD: EXPIRED lease %R[binding]\n", b));
+
+ addBinding(b);
+}