VirtualBox

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

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

Dhcpd: Eliminated use of std::string (probably my mistake in the original code). Various other cleanups. bugref:9288

  • 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 79514 2019-07-04 08:01:58Z 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 /* Lease/@network seems to always have bogus value, ignore it. */
226
227 /*
228 * Lease/@mac
229 */
230 RTCString strMac;
231 bool fHasMac = ndLease->getAttributeValue("mac", &strMac);
232 if (!fHasMac)
233 return NULL;
234
235 RTMAC mac;
236 int rc = RTNetStrToMacAddr(strMac.c_str(), &mac);
237 if (RT_FAILURE(rc))
238 return NULL;
239
240 OptClientId id;
241 RTCString strId;
242 bool fHasId = ndLease->getAttributeValue("id", &strId);
243 if (fHasId)
244 {
245 /*
246 * Decode from "de:ad:be:ef".
247 * XXX: RTStrConvertHexBytes() doesn't grok colons
248 */
249 size_t cbBytes = strId.length() / 2;
250 uint8_t *pBytes = new uint8_t[cbBytes];
251 rc = RTStrConvertHexBytes(strId.c_str(), pBytes, cbBytes, 0);
252 if (RT_SUCCESS(rc))
253 {
254 std::vector<uint8_t> rawopt(pBytes, pBytes + cbBytes);
255 id = OptClientId(rawopt);
256 }
257 delete[] pBytes;
258 }
259
260 /*
261 * Lease/@state - not present in old leases file. We will try to
262 * infer from lease time below.
263 */
264 RTCString strState;
265 bool fHasState = ndLease->getAttributeValue("state", &strState);
266
267 /*
268 * Lease/Address
269 */
270 const xml::ElementNode *ndAddress = ndLease->findChildElement("Address");
271 if (ndAddress == NULL)
272 return NULL;
273
274 /*
275 * Lease/Address/@value
276 */
277 RTCString strAddress;
278 bool fHasValue = ndAddress->getAttributeValue("value", &strAddress);
279 if (!fHasValue)
280 return NULL;
281
282 RTNETADDRIPV4 addr;
283 rc = RTNetStrToIPv4Addr(strAddress.c_str(), &addr);
284 if (RT_FAILURE(rc))
285 return NULL;
286
287 /*
288 * Lease/Time
289 */
290 const xml::ElementNode *ndTime = ndLease->findChildElement("Time");
291 if (ndTime == NULL)
292 return NULL;
293
294 /*
295 * Lease/Time/@issued
296 */
297 int64_t issued;
298 bool fHasIssued = ndTime->getAttributeValue("issued", &issued);
299 if (!fHasIssued)
300 return NULL;
301
302 /*
303 * Lease/Time/@expiration
304 */
305 uint32_t duration;
306 bool fHasExpiration = ndTime->getAttributeValue("expiration", &duration);
307 if (!fHasExpiration)
308 return NULL;
309
310 std::unique_ptr<Binding> b(new Binding(addr));
311 b->m_id = ClientId(mac, id);
312
313 if (fHasState)
314 {
315 b->m_issued = TimeStamp::absSeconds(issued);
316 b->m_secLease = duration;
317 b->setState(strState.c_str());
318 }
319 else
320 { /* XXX: old code wrote timestamps instead of absolute time. */
321 /* pretend that lease has just ended */
322 TimeStamp fakeIssued = TimeStamp::now();
323 fakeIssued.subSeconds(duration);
324 b->m_issued = fakeIssued;
325 b->m_secLease = duration;
326 b->m_state = Binding::EXPIRED;
327 }
328
329 return b.release();
330}
331
332
333void Db::expire()
334{
335 const TimeStamp now = TimeStamp::now();
336
337 for (bindings_t::iterator it = m_bindings.begin();
338 it != m_bindings.end(); ++it)
339 {
340 Binding *b = *it;
341 b->expire(now);
342 }
343}
344
345
346Binding *Db::createBinding(const ClientId &id)
347{
348 RTNETADDRIPV4 addr = m_pool.allocate();
349 if (addr.u == 0)
350 return NULL;
351
352 Binding *b = new Binding(addr, id);
353 m_bindings.push_front(b);
354 return b;
355}
356
357
358Binding *Db::createBinding(RTNETADDRIPV4 addr, const ClientId &id)
359{
360 bool fAvailable = m_pool.allocate(addr);
361 if (!fAvailable)
362 {
363 /*
364 * XXX: this should not happen. If the address is from the
365 * pool, which we have verified before, then either it's in
366 * the free pool or there's an binding (possibly free) for it.
367 */
368 return NULL;
369 }
370
371 Binding *b = new Binding(addr, id);
372 m_bindings.push_front(b);
373 return b;
374}
375
376
377Binding *Db::allocateAddress(const ClientId &id, RTNETADDRIPV4 addr)
378{
379 Assert(addr.u == 0 || addressBelongs(addr));
380
381 Binding *addrBinding = NULL;
382 Binding *freeBinding = NULL;
383 Binding *reuseBinding = NULL;
384
385 if (addr.u != 0)
386 LogDHCP(("> allocateAddress %RTnaipv4 to client %R[id]\n", addr.u, &id));
387 else
388 LogDHCP(("> allocateAddress to client %R[id]\n", &id));
389
390 /*
391 * Allocate existing address if client has one. Ignore requested
392 * address in that case. While here, look for free addresses and
393 * addresses that can be reused.
394 */
395 const TimeStamp now = TimeStamp::now();
396 for (bindings_t::iterator it = m_bindings.begin();
397 it != m_bindings.end(); ++it)
398 {
399 Binding *b = *it;
400 b->expire(now);
401
402 /*
403 * We've already seen this client, give it its old binding.
404 */
405 if (b->m_id == id)
406 {
407 LogDHCP(("> ... found existing binding %R[binding]\n", b));
408 return b;
409 }
410
411 if (addr.u != 0 && b->m_addr.u == addr.u)
412 {
413 Assert(addrBinding == NULL);
414 addrBinding = b;
415 LogDHCP(("> .... noted existing binding %R[binding]\n", addrBinding));
416 }
417
418 /* if we haven't found a free binding yet, keep looking */
419 if (freeBinding == NULL)
420 {
421 if (b->m_state == Binding::FREE)
422 {
423 freeBinding = b;
424 LogDHCP(("> .... noted free binding %R[binding]\n", freeBinding));
425 continue;
426 }
427
428 /* still no free binding, can this one be reused? */
429 if (b->m_state == Binding::RELEASED)
430 {
431 if ( reuseBinding == NULL
432 /* released binding is better than an expired one */
433 || reuseBinding->m_state == Binding::EXPIRED)
434 {
435 reuseBinding = b;
436 LogDHCP(("> .... noted released binding %R[binding]\n", reuseBinding));
437 }
438 }
439 else if (b->m_state == Binding::EXPIRED)
440 {
441 if ( reuseBinding == NULL
442 /* long expired binding is bettern than a recent one */
443 /* || (reuseBinding->m_state == Binding::EXPIRED && b->olderThan(reuseBinding)) */)
444 {
445 reuseBinding = b;
446 LogDHCP(("> .... noted expired binding %R[binding]\n", reuseBinding));
447 }
448 }
449 }
450 }
451
452 /*
453 * Allocate requested address if we can.
454 */
455 if (addr.u != 0)
456 {
457 if (addrBinding == NULL)
458 {
459 addrBinding = createBinding(addr, id);
460 Assert(addrBinding != NULL);
461 LogDHCP(("> .... creating new binding for this address %R[binding]\n",
462 addrBinding));
463 return addrBinding;
464 }
465
466 if (addrBinding->m_state <= Binding::EXPIRED) /* not in use */
467 {
468 LogDHCP(("> .... reusing %s binding for this address\n",
469 addrBinding->stateName()));
470 addrBinding->giveTo(id);
471 return addrBinding;
472 }
473 LogDHCP(("> .... cannot reuse %s binding for this address\n",
474 addrBinding->stateName()));
475 }
476
477 /*
478 * Allocate new (or reuse).
479 */
480 Binding *idBinding = NULL;
481 if (freeBinding != NULL)
482 {
483 idBinding = freeBinding;
484 LogDHCP(("> .... reusing free binding\n"));
485 }
486 else
487 {
488 idBinding = createBinding();
489 if (idBinding != NULL)
490 {
491 LogDHCP(("> .... creating new binding\n"));
492 }
493 else
494 {
495 idBinding = reuseBinding;
496 LogDHCP(("> .... reusing %s binding %R[binding]\n",
497 reuseBinding->stateName(), reuseBinding));
498 }
499 }
500
501 if (idBinding == NULL)
502 {
503 LogDHCP(("> .... failed to allocate binding\n"));
504 return NULL;
505 }
506
507 idBinding->giveTo(id);
508 LogDHCP(("> .... allocated %R[binding]\n", idBinding));
509
510 return idBinding;
511}
512
513
514
515Binding *Db::allocateBinding(const DhcpClientMessage &req)
516{
517 /** @todo XXX: handle fixed address assignments */
518 OptRequestedAddress reqAddr(req);
519 if (reqAddr.present() && !addressBelongs(reqAddr.value()))
520 {
521 if (req.messageType() == RTNET_DHCP_MT_DISCOVER)
522 {
523 LogDHCP(("DISCOVER: ignoring invalid requested address\n"));
524 reqAddr = OptRequestedAddress();
525 }
526 else
527 {
528 LogDHCP(("rejecting invalid requested address\n"));
529 return NULL;
530 }
531 }
532
533 const ClientId &id(req.clientId());
534
535 Binding *b = allocateAddress(id, reqAddr.value());
536 if (b == NULL)
537 return NULL;
538
539 Assert(b->id() == id);
540
541 /** @todo
542 * XXX: handle requests for specific lease time!
543 * XXX: old lease might not have expired yet?
544 */
545 // OptLeaseTime reqLeaseTime(req);
546 b->setLeaseTime(1200);
547 return b;
548}
549
550
551int Db::addBinding(Binding *newb)
552{
553 if (!addressBelongs(newb->m_addr))
554 {
555 LogDHCP(("Binding for out of range address %RTnaipv4 ignored\n",
556 newb->m_addr.u));
557 return VERR_INVALID_PARAMETER;
558 }
559
560 for (bindings_t::iterator it = m_bindings.begin();
561 it != m_bindings.end(); ++it)
562 {
563 Binding *b = *it;
564
565 if (newb->m_addr.u == b->m_addr.u)
566 {
567 LogDHCP(("> ADD: %R[binding]\n", newb));
568 LogDHCP(("> .... duplicate ip: %R[binding]\n", b));
569 return VERR_INVALID_PARAMETER;
570 }
571
572 if (newb->m_id == b->m_id)
573 {
574 LogDHCP(("> ADD: %R[binding]\n", newb));
575 LogDHCP(("> .... duplicate id: %R[binding]\n", b));
576 return VERR_INVALID_PARAMETER;
577 }
578 }
579
580 bool ok = m_pool.allocate(newb->m_addr);
581 if (!ok)
582 {
583 LogDHCP(("> ADD: failed to claim IP %R[binding]\n", newb));
584 return VERR_INVALID_PARAMETER;
585 }
586
587 m_bindings.push_back(newb);
588 return VINF_SUCCESS;
589}
590
591
592void Db::cancelOffer(const DhcpClientMessage &req)
593{
594 const OptRequestedAddress reqAddr(req);
595 if (!reqAddr.present())
596 return;
597
598 const RTNETADDRIPV4 addr = reqAddr.value();
599 const ClientId &id(req.clientId());
600
601 for (bindings_t::iterator it = m_bindings.begin();
602 it != m_bindings.end(); ++it)
603 {
604 Binding *b = *it;
605
606 if (b->addr().u == addr.u && b->id() == id)
607 {
608 if (b->state() == Binding::OFFERED)
609 {
610 b->setLeaseTime(0);
611 b->setState(Binding::RELEASED);
612 }
613 return;
614 }
615 }
616}
617
618
619bool Db::releaseBinding(const DhcpClientMessage &req)
620{
621 const RTNETADDRIPV4 addr = req.ciaddr();
622 const ClientId &id(req.clientId());
623
624 for (bindings_t::iterator it = m_bindings.begin();
625 it != m_bindings.end(); ++it)
626 {
627 Binding *b = *it;
628
629 if (b->addr().u == addr.u && b->id() == id)
630 {
631 b->setState(Binding::RELEASED);
632 return true;
633 }
634 }
635
636 return false;
637}
638
639
640int Db::writeLeases(const RTCString &strFileName) const
641{
642 LogDHCP(("writing leases to %s\n", strFileName.c_str()));
643
644 xml::Document doc;
645
646 xml::ElementNode *root = doc.createRootElement("Leases");
647 if (root == NULL)
648 return VERR_INTERNAL_ERROR;
649
650 root->setAttribute("version", "1.0");
651
652 for (bindings_t::const_iterator it = m_bindings.begin();
653 it != m_bindings.end(); ++it)
654 {
655 const Binding *b = *it;
656 b->toXML(root);
657 }
658
659 try {
660 xml::XmlFileWriter writer(doc);
661 writer.write(strFileName.c_str(), true);
662 }
663 catch (const xml::EIPRTFailure &e)
664 {
665 LogDHCP(("%s\n", e.what()));
666 return e.rc();
667 }
668 catch (const RTCError &e)
669 {
670 LogDHCP(("%s\n", e.what()));
671 return VERR_GENERAL_FAILURE;
672 }
673 catch (...)
674 {
675 LogDHCP(("Unknown exception while writing '%s'\n",
676 strFileName.c_str()));
677 return VERR_GENERAL_FAILURE;
678 }
679
680 return VINF_SUCCESS;
681}
682
683
684int Db::loadLeases(const RTCString &strFileName)
685{
686 LogDHCP(("loading leases from %s\n", strFileName.c_str()));
687
688 xml::Document doc;
689 try
690 {
691 xml::XmlFileParser parser;
692 parser.read(strFileName.c_str(), doc);
693 }
694 catch (const xml::EIPRTFailure &e)
695 {
696 LogDHCP(("%s\n", e.what()));
697 return e.rc();
698 }
699 catch (const RTCError &e)
700 {
701 LogDHCP(("%s\n", e.what()));
702 return VERR_GENERAL_FAILURE;
703 }
704 catch (...)
705 {
706 LogDHCP(("Unknown exception while reading and parsing '%s'\n",
707 strFileName.c_str()));
708 return VERR_GENERAL_FAILURE;
709 }
710
711 xml::ElementNode *ndRoot = doc.getRootElement();
712 if (ndRoot == NULL || !ndRoot->nameEquals("Leases"))
713 {
714 return VERR_NOT_FOUND;
715 }
716
717 xml::NodesLoop it(*ndRoot);
718 const xml::ElementNode *node;
719 while ((node = it.forAllNodes()) != NULL)
720 {
721 if (!node->nameEquals("Lease"))
722 continue;
723
724 loadLease(node);
725 }
726
727 return VINF_SUCCESS;
728}
729
730
731void Db::loadLease(const xml::ElementNode *ndLease)
732{
733 Binding *b = Binding::fromXML(ndLease);
734 bool expired = b->expire();
735
736 if (!expired)
737 LogDHCP(("> LOAD: lease %R[binding]\n", b));
738 else
739 LogDHCP(("> LOAD: EXPIRED lease %R[binding]\n", b));
740
741 addBinding(b);
742}
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