VirtualBox

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

Last change on this file since 79509 was 79509, checked in by vboxsync, 6 years ago

Dhcpd: Ditched std:vector<char> + catch with wrong slash non-sense in VBoxNetDhcpd::vmmInit(). Misc cleanups.

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

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