VirtualBox

source: vbox/trunk/src/VBox/NetworkServices/Dhcpd/Db.cpp@ 71512

Last change on this file since 71512 was 71512, checked in by vboxsync, 7 years ago

Binding::fromXML - don't leak memory.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.3 KB
Line 
1/* $Id: Db.cpp 71512 2018-03-26 14:02:50Z vboxsync $ */
2/** @file
3 * DHCP server - address database
4 */
5
6/*
7 * Copyright (C) 2017-2018 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#include <iprt/err.h>
19#include <iprt/stream.h>
20
21#include <algorithm>
22#include <functional>
23
24#include "Db.h"
25
26
27Db::~Db()
28{
29 /** @todo free bindings */
30}
31
32
33int Db::init(const Config *pConfig)
34{
35 Binding::registerFormat();
36
37 m_pConfig = pConfig;
38
39 m_pool.init(pConfig->getIPv4PoolFirst(),
40 pConfig->getIPv4PoolLast());
41
42 return VINF_SUCCESS;
43}
44
45
46bool Binding::g_fFormatRegistered = false;
47
48
49void Binding::registerFormat()
50{
51 if (g_fFormatRegistered)
52 return;
53
54 int rc = RTStrFormatTypeRegister("binding", rtStrFormat, NULL);
55 AssertRC(rc);
56
57 g_fFormatRegistered = true;
58}
59
60
61DECLCALLBACK(size_t)
62Binding::rtStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
63 const char *pszType, void const *pvValue,
64 int cchWidth, int cchPrecision, unsigned fFlags,
65 void *pvUser)
66{
67 const Binding *b = static_cast<const Binding *>(pvValue);
68 size_t cb = 0;
69
70 AssertReturn(strcmp(pszType, "binding") == 0, 0);
71 RT_NOREF(pszType);
72
73 RT_NOREF(cchWidth, cchPrecision, fFlags);
74 RT_NOREF(pvUser);
75
76 if (b == NULL)
77 {
78 return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
79 "<NULL>");
80 }
81
82 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
83 "%RTnaipv4", b->m_addr.u);
84
85 if (b->m_state == Binding::FREE)
86 {
87 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
88 " free");
89 }
90 else
91 {
92 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
93 " to %R[id], %s, valid from ",
94 &b->m_id, b->stateName());
95
96 TimeStamp tsIssued = b->issued();
97 cb += tsIssued.absStrFormat(pfnOutput, pvArgOutput);
98
99 cb += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
100 " for %ds until ",
101 b->leaseTime());
102
103 TimeStamp tsValid = b->issued();
104 tsValid.addSeconds(b->leaseTime());
105 cb += tsValid.absStrFormat(pfnOutput, pvArgOutput);
106 }
107
108 return cb;
109}
110
111const char *Binding::stateName() const
112{
113 switch (m_state) {
114 case FREE:
115 return "free";
116 case RELEASED:
117 return "released";
118 case EXPIRED:
119 return "expired";
120 case OFFERED:
121 return "offered";
122 case ACKED:
123 return "acked";
124 default:
125 return "released";
126 }
127}
128
129
130Binding &Binding::setState(const char *pszStateName)
131{
132 if (strcmp(pszStateName, "free") == 0)
133 m_state = Binding::FREE;
134 else if (strcmp(pszStateName, "released") == 0)
135 m_state = Binding::RELEASED;
136 else if (strcmp(pszStateName, "expired") == 0)
137 m_state = Binding::EXPIRED;
138 else if (strcmp(pszStateName, "offered") == 0)
139 m_state = Binding::OFFERED;
140 else if (strcmp(pszStateName, "acked") == 0)
141 m_state = Binding::ACKED;
142 else
143 m_state = Binding::RELEASED;
144
145 return *this;
146}
147
148
149bool Binding::expire(TimeStamp deadline)
150{
151 if (m_state <= Binding::EXPIRED)
152 return false;
153
154 TimeStamp t = m_issued;
155 t.addSeconds(m_secLease);
156
157 if (t < deadline)
158 {
159 if (m_state == Binding::OFFERED)
160 setState(Binding::FREE);
161 else
162 setState(Binding::EXPIRED);
163 }
164 return true;
165}
166
167
168int Binding::toXML(xml::ElementNode *ndParent) const
169{
170 int rc;
171
172 /*
173 * Lease
174 */
175 xml::ElementNode *ndLease = ndParent->createChild("Lease");
176 if (ndLease == NULL)
177 return VERR_GENERAL_FAILURE;
178
179 /* XXX: arrange for lease to get deleted if anything below fails */
180
181
182 ndLease->setAttribute("mac", RTCStringFmt("%RTmac", &m_id.mac()));
183 if (m_id.id().present())
184 {
185 /* I'd prefer RTSTRPRINTHEXBYTES_F_SEP_COLON but there's no decoder */
186 size_t cbStrId = m_id.id().value().size() * 2 + 1;
187 char *pszId = new char[cbStrId];
188 rc = RTStrPrintHexBytes(pszId, cbStrId,
189 &m_id.id().value().front(), m_id.id().value().size(),
190 0);
191 ndLease->setAttribute("id", pszId);
192 delete[] pszId;
193 }
194
195 /* unused but we need it to keep the old code happy */
196 ndLease->setAttribute("network", "0.0.0.0");
197
198 ndLease->setAttribute("state", stateName());
199
200
201 /*
202 * Lease/Address
203 */
204 xml::ElementNode *ndAddr = ndLease->createChild("Address");
205 ndAddr->setAttribute("value", RTCStringFmt("%RTnaipv4", m_addr.u));
206
207
208 /*
209 * Lease/Time
210 */
211 xml::ElementNode *ndTime = ndLease->createChild("Time");
212 ndTime->setAttribute("issued", m_issued.getAbsSeconds());
213 ndTime->setAttribute("expiration", m_secLease);
214
215 return VINF_SUCCESS;
216}
217
218
219Binding *Binding::fromXML(const xml::ElementNode *ndLease)
220{
221 int rc;
222
223 /* Lease/@network seems to always have bogus value, ignore it. */
224
225 /*
226 * Lease/@mac
227 */
228 RTCString strMac;
229 bool fHasMac = ndLease->getAttributeValue("mac", &strMac);
230 if (!fHasMac)
231 return NULL;
232
233 RTMAC mac;
234 rc = RTNetStrToMacAddr(strMac.c_str(), &mac);
235 if (RT_FAILURE(rc))
236 return NULL;
237
238 OptClientId id;
239 RTCString strId;
240 bool fHasId = ndLease->getAttributeValue("id", &strId);
241 if (fHasId)
242 {
243 /*
244 * Decode from "de:ad:be:ef".
245 * XXX: RTStrConvertHexBytes() doesn't grok colons
246 */
247 size_t cbBytes = strId.length() / 2;
248 uint8_t *pBytes = new uint8_t[cbBytes];
249 rc = RTStrConvertHexBytes(strId.c_str(), pBytes, cbBytes, 0);
250 if (RT_SUCCESS(rc))
251 {
252 std::vector<uint8_t> rawopt(pBytes, pBytes + cbBytes);
253 id = OptClientId(rawopt);
254 }
255 delete[] pBytes;
256 }
257
258 /*
259 * Lease/@state - not present in old leases file. We will try to
260 * infer from lease time below.
261 */
262 RTCString strState;
263 bool fHasState = ndLease->getAttributeValue("state", &strState);
264
265 /*
266 * Lease/Address
267 */
268 const xml::ElementNode *ndAddress = ndLease->findChildElement("Address");
269 if (ndAddress == NULL)
270 return NULL;
271
272 /*
273 * Lease/Address/@value
274 */
275 RTCString strAddress;
276 bool fHasValue = ndAddress->getAttributeValue("value", &strAddress);
277 if (!fHasValue)
278 return NULL;
279
280 RTNETADDRIPV4 addr;
281 rc = RTNetStrToIPv4Addr(strAddress.c_str(), &addr);
282 if (RT_FAILURE(rc))
283 return NULL;
284
285 /*
286 * Lease/Time
287 */
288 const xml::ElementNode *ndTime = ndLease->findChildElement("Time");
289 if (time == NULL)
290 return NULL;
291
292 /*
293 * Lease/Time/@issued
294 */
295 int64_t issued;
296 bool fHasIssued = ndTime->getAttributeValue("issued", &issued);
297 if (!fHasIssued)
298 return NULL;
299
300 /*
301 * Lease/Time/@expiration
302 */
303 uint32_t duration;
304 bool fHasExpiration = ndTime->getAttributeValue("expiration", &duration);
305 if (!fHasExpiration)
306 return NULL;
307
308 std::unique_ptr<Binding> b(new Binding(addr));
309 b->m_id = ClientId(mac, id);
310
311 if (fHasState)
312 {
313 b->m_issued = TimeStamp::absSeconds(issued);
314 b->m_secLease = duration;
315 b->setState(strState.c_str());
316 }
317 else
318 { /* XXX: old code wrote timestamps instead of absolute time. */
319 /* pretend that lease has just ended */
320 TimeStamp fakeIssued = TimeStamp::now();
321 fakeIssued.subSeconds(duration);
322 b->m_issued = fakeIssued;
323 b->m_secLease = duration;
324 b->m_state = Binding::EXPIRED;
325 }
326
327 return b.release();
328}
329
330
331void Db::expire()
332{
333 const TimeStamp now = TimeStamp::now();
334
335 for (bindings_t::iterator it = m_bindings.begin();
336 it != m_bindings.end(); ++it)
337 {
338 Binding *b = *it;
339 b->expire(now);
340 }
341}
342
343
344Binding *Db::bindingById(const ClientId &id) const
345{
346 struct ClientMatch : public Binding::Match {
347 const ClientId &m_id;
348 ClientMatch(const ClientId &id) : m_id(id) {}
349
350 bool operator()(const Binding *b)
351 {
352 return b->m_id == m_id;
353 }
354 };
355
356 bindings_t::const_iterator found =
357 std::find_if(m_bindings.begin(), m_bindings.end(),
358 ClientMatch(id));
359
360 if (found == m_bindings.end())
361 return NULL;
362
363 Binding *b = *found;
364 return b;
365}
366
367
368Binding *Db::bindingByAddr(RTNETADDRIPV4 addr) const
369{
370 struct AddrMatch : public Binding::Match {
371 const RTNETADDRIPV4 m_addr;
372 AddrMatch(RTNETADDRIPV4 addr) : m_addr(addr) {}
373
374 bool operator()(const Binding *b)
375 {
376 return b->m_addr.u == m_addr.u;
377 }
378 };
379
380 bindings_t::const_iterator found =
381 std::find_if(m_bindings.begin(), m_bindings.end(),
382 AddrMatch(addr));
383
384 if (found == m_bindings.end())
385 return NULL;
386
387 Binding *b = *found;
388 return b;
389}
390
391
392
393Binding *Db::createBinding(const ClientId &id)
394{
395 RTNETADDRIPV4 addr = m_pool.allocate();
396 if (addr.u == 0)
397 return NULL;
398
399 Binding *b = new Binding(addr, id);
400 m_bindings.push_front(b);
401 return b;
402}
403
404
405Binding *Db::createBinding(RTNETADDRIPV4 addr, const ClientId &id)
406{
407 bool fAvailable = m_pool.allocate(addr);
408 if (!fAvailable)
409 {
410 /*
411 * XXX: this should not happen. If the address is from the
412 * pool, which we have verified before, then either it's in
413 * the free pool or there's an binding (possibly free) for it.
414 */
415 return NULL;
416 }
417
418 Binding *b = new Binding(addr, id);
419 m_bindings.push_front(b);
420 return b;
421}
422
423
424Binding *Db::allocateAddress(const ClientId &id, RTNETADDRIPV4 addr)
425{
426 Assert(addr.u == 0 || addressBelongs(addr));
427
428 Binding *addrBinding = NULL;
429 Binding *freeBinding = NULL;
430 Binding *reuseBinding = NULL;
431
432 if (addr.u != 0)
433 LogDHCP(("> allocateAddress %RTnaipv4 to client %R[id]\n", addr.u, &id));
434 else
435 LogDHCP(("> allocateAddress to client %R[id]\n", &id));
436
437 /*
438 * Allocate existing address if client has one. Ignore requested
439 * address in that case. While here, look for free addresses and
440 * addresses that can be reused.
441 */
442 const TimeStamp now = TimeStamp::now();
443 for (bindings_t::iterator it = m_bindings.begin();
444 it != m_bindings.end(); ++it)
445 {
446 Binding *b = *it;
447 b->expire(now);
448
449 /*
450 * We've already seen this client, give it its old binding.
451 */
452 if (b->m_id == id)
453 {
454 LogDHCP(("> ... found existing binding %R[binding]\n", b));
455 return b;
456 }
457
458 if (addr.u != 0 && b->m_addr.u == addr.u)
459 {
460 Assert(addrBinding == NULL);
461 addrBinding = b;
462 LogDHCP(("> .... noted existing binding %R[binding]\n", addrBinding));
463 }
464
465 /* if we haven't found a free binding yet, keep looking */
466 if (freeBinding == NULL)
467 {
468 if (b->m_state == Binding::FREE)
469 {
470 freeBinding = b;
471 LogDHCP(("> .... noted free binding %R[binding]\n", freeBinding));
472 continue;
473 }
474
475 /* still no free binding, can this one be reused? */
476 if (b->m_state == Binding::RELEASED)
477 {
478 if ( reuseBinding == NULL
479 /* released binding is better than an expired one */
480 || reuseBinding->m_state == Binding::EXPIRED)
481 {
482 reuseBinding = b;
483 LogDHCP(("> .... noted released binding %R[binding]\n", reuseBinding));
484 }
485 }
486 else if (b->m_state == Binding::EXPIRED)
487 {
488 if ( reuseBinding == NULL
489 /* long expired binding is bettern than a recent one */
490 /* || (reuseBinding->m_state == Binding::EXPIRED && b->olderThan(reuseBinding)) */)
491 {
492 reuseBinding = b;
493 LogDHCP(("> .... noted expired binding %R[binding]\n", reuseBinding));
494 }
495 }
496 }
497 }
498
499 /*
500 * Allocate requested address if we can.
501 */
502 if (addr.u != 0)
503 {
504 if (addrBinding == NULL)
505 {
506 addrBinding = createBinding(addr, id);
507 Assert(addrBinding != NULL);
508 LogDHCP(("> .... creating new binding for this address %R[binding]\n",
509 addrBinding));
510 return addrBinding;
511 }
512
513 if (addrBinding->m_state <= Binding::EXPIRED) /* not in use */
514 {
515 LogDHCP(("> .... reusing %s binding for this address\n",
516 addrBinding->stateName()));
517 addrBinding->giveTo(id);
518 return addrBinding;
519 }
520 else
521 {
522 LogDHCP(("> .... cannot reuse %s binding for this address\n",
523 addrBinding->stateName()));
524 }
525 }
526
527 /*
528 * Allocate new (or reuse).
529 */
530 Binding *idBinding = NULL;
531 if (freeBinding != NULL)
532 {
533 idBinding = freeBinding;
534 LogDHCP(("> .... reusing free binding\n"));
535 }
536 else
537 {
538 idBinding = createBinding();
539 if (idBinding != NULL)
540 {
541 LogDHCP(("> .... creating new binding\n"));
542 }
543 else
544 {
545 idBinding = reuseBinding;
546 LogDHCP(("> .... reusing %s binding %R[binding]\n",
547 reuseBinding->stateName()));
548 }
549 }
550
551 if (idBinding == NULL)
552 {
553 LogDHCP(("> .... failed to allocate binding\n"));
554 return NULL;
555 }
556
557 idBinding->giveTo(id);
558 LogDHCP(("> .... allocated %R[binding]\n", idBinding));
559
560 return idBinding;
561}
562
563
564
565Binding *Db::allocateBinding(const DhcpClientMessage &req)
566{
567 /** @todo XXX: handle fixed address assignments */
568 OptRequestedAddress reqAddr(req);
569 if (reqAddr.present() && !addressBelongs(reqAddr.value()))
570 {
571 if (req.messageType() == RTNET_DHCP_MT_DISCOVER)
572 {
573 LogDHCP(("DISCOVER: ignoring invalid requested address\n"));
574 reqAddr = OptRequestedAddress();
575 }
576 else
577 {
578 LogDHCP(("rejecting invalid requested address\n"));
579 return NULL;
580 }
581 }
582
583 const ClientId &id(req.clientId());
584
585 Binding *b = allocateAddress(id, reqAddr.value());
586 if (b == NULL)
587 return NULL;
588
589 Assert(b->id() == id);
590 Assert(b->state() == Binding::FREE || b->state() >= Binding::OFFERED);
591
592 /*
593 * XXX: handle requests for specific lease time!
594 * XXX: old lease might not have expired yet?
595 */
596 // OptLeaseTime reqLeaseTime(req);
597 b->setLeaseTime(1200);
598 return b;
599}
600
601
602int Db::addBinding(Binding *newb)
603{
604 if (!addressBelongs(newb->m_addr))
605 {
606 LogDHCP(("Binding for out of range address %RTnaipv4 ignored\n",
607 newb->m_addr.u));
608 return VERR_INVALID_PARAMETER;
609 }
610
611 for (bindings_t::iterator it = m_bindings.begin();
612 it != m_bindings.end(); ++it)
613 {
614 Binding *b = *it;
615
616 if (newb->m_addr.u == b->m_addr.u)
617 {
618 LogDHCP(("> ADD: %R[binding]\n", newb));
619 LogDHCP(("> .... duplicate ip: %R[binding]\n", b));
620 return VERR_INVALID_PARAMETER;
621 }
622
623 if (newb->m_id == b->m_id)
624 {
625 LogDHCP(("> ADD: %R[binding]\n", newb));
626 LogDHCP(("> .... duplicate id: %R[binding]\n", b));
627 return VERR_INVALID_PARAMETER;
628 }
629 }
630
631 bool ok = m_pool.allocate(newb->m_addr);
632 if (!ok)
633 {
634 LogDHCP(("> ADD: failed to claim IP %R[binding]\n", newb));
635 return VERR_INVALID_PARAMETER;
636 }
637
638 m_bindings.push_back(newb);
639 return VINF_SUCCESS;
640}
641
642
643void Db::cancelOffer(const DhcpClientMessage &req)
644{
645 const OptRequestedAddress reqAddr(req);
646 if (!reqAddr.present())
647 return;
648
649 const RTNETADDRIPV4 addr = reqAddr.value();
650 const ClientId &id(req.clientId());
651
652 for (bindings_t::iterator it = m_bindings.begin();
653 it != m_bindings.end(); ++it)
654 {
655 Binding *b = *it;
656
657 if (b->addr().u == addr.u && b->id() == id)
658 {
659 if (b->state() == Binding::OFFERED)
660 {
661 b->setLeaseTime(0);
662 b->setState(Binding::RELEASED);
663 }
664 return;
665 }
666 }
667}
668
669
670bool Db::releaseBinding(const DhcpClientMessage &req)
671{
672 const RTNETADDRIPV4 addr = req.ciaddr();
673 const ClientId &id(req.clientId());
674
675 for (bindings_t::iterator it = m_bindings.begin();
676 it != m_bindings.end(); ++it)
677 {
678 Binding *b = *it;
679
680 if (b->addr().u == addr.u && b->id() == id)
681 {
682 b->setState(Binding::RELEASED);
683 return true;
684 }
685 }
686
687 return false;
688}
689
690
691int Db::writeLeases(const std::string &strFileName) const
692{
693 LogDHCP(("writing leases to %s\n", strFileName.c_str()));
694
695 xml::Document doc;
696
697 xml::ElementNode *root = doc.createRootElement("Leases");
698 if (root == NULL)
699 return VERR_INTERNAL_ERROR;
700
701 root->setAttribute("version", "1.0");
702
703 for (bindings_t::const_iterator it = m_bindings.begin();
704 it != m_bindings.end(); ++it)
705 {
706 const Binding *b = *it;
707 b->toXML(root);
708 }
709
710 try {
711 xml::XmlFileWriter writer(doc);
712 writer.write(strFileName.c_str(), true);
713 }
714 catch (const xml::EIPRTFailure &e)
715 {
716 LogDHCP(("%s\n", e.what()));
717 return e.rc();
718 }
719 catch (const RTCError &e)
720 {
721 LogDHCP(("%s\n", e.what()));
722 return VERR_GENERAL_FAILURE;
723 }
724 catch (...)
725 {
726 LogDHCP(("Unknown exception while writing '%s'\n",
727 strFileName.c_str()));
728 return VERR_GENERAL_FAILURE;
729 }
730
731 return VINF_SUCCESS;
732}
733
734
735int Db::loadLeases(const std::string &strFileName)
736{
737 LogDHCP(("loading leases from %s\n", strFileName.c_str()));
738
739 xml::Document doc;
740 try
741 {
742 xml::XmlFileParser parser;
743 parser.read(strFileName.c_str(), doc);
744 }
745 catch (const xml::EIPRTFailure &e)
746 {
747 LogDHCP(("%s\n", e.what()));
748 return e.rc();
749 }
750 catch (const RTCError &e)
751 {
752 LogDHCP(("%s\n", e.what()));
753 return VERR_GENERAL_FAILURE;
754 }
755 catch (...)
756 {
757 LogDHCP(("Unknown exception while reading and parsing '%s'\n",
758 strFileName.c_str()));
759 return VERR_GENERAL_FAILURE;
760 }
761
762 xml::ElementNode *ndRoot = doc.getRootElement();
763 if (ndRoot == NULL || !ndRoot->nameEquals("Leases"))
764 {
765 return VERR_NOT_FOUND;
766 }
767
768 xml::NodesLoop it(*ndRoot);
769 const xml::ElementNode *node;
770 while ((node = it.forAllNodes()) != NULL)
771 {
772 if (!node->nameEquals("Lease"))
773 continue;
774
775 loadLease(node);
776 }
777
778 return VINF_SUCCESS;
779}
780
781
782void Db::loadLease(const xml::ElementNode *ndLease)
783{
784 Binding *b = Binding::fromXML(ndLease);
785 bool expired = b->expire();
786
787 if (!expired)
788 LogDHCP(("> LOAD: lease %R[binding]\n", b));
789 else
790 LogDHCP(("> LOAD: EXPIRED lease %R[binding]\n", b));
791
792 addBinding(b);
793}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette