VirtualBox

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

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

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.2 KB
Line 
1/* $Id: Db.cpp 76553 2019-01-01 01:45:53Z 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 else
476 {
477 LogDHCP(("> .... cannot reuse %s binding for this address\n",
478 addrBinding->stateName()));
479 }
480 }
481
482 /*
483 * Allocate new (or reuse).
484 */
485 Binding *idBinding = NULL;
486 if (freeBinding != NULL)
487 {
488 idBinding = freeBinding;
489 LogDHCP(("> .... reusing free binding\n"));
490 }
491 else
492 {
493 idBinding = createBinding();
494 if (idBinding != NULL)
495 {
496 LogDHCP(("> .... creating new binding\n"));
497 }
498 else
499 {
500 idBinding = reuseBinding;
501 LogDHCP(("> .... reusing %s binding %R[binding]\n",
502 reuseBinding->stateName(), reuseBinding));
503 }
504 }
505
506 if (idBinding == NULL)
507 {
508 LogDHCP(("> .... failed to allocate binding\n"));
509 return NULL;
510 }
511
512 idBinding->giveTo(id);
513 LogDHCP(("> .... allocated %R[binding]\n", idBinding));
514
515 return idBinding;
516}
517
518
519
520Binding *Db::allocateBinding(const DhcpClientMessage &req)
521{
522 /** @todo XXX: handle fixed address assignments */
523 OptRequestedAddress reqAddr(req);
524 if (reqAddr.present() && !addressBelongs(reqAddr.value()))
525 {
526 if (req.messageType() == RTNET_DHCP_MT_DISCOVER)
527 {
528 LogDHCP(("DISCOVER: ignoring invalid requested address\n"));
529 reqAddr = OptRequestedAddress();
530 }
531 else
532 {
533 LogDHCP(("rejecting invalid requested address\n"));
534 return NULL;
535 }
536 }
537
538 const ClientId &id(req.clientId());
539
540 Binding *b = allocateAddress(id, reqAddr.value());
541 if (b == NULL)
542 return NULL;
543
544 Assert(b->id() == id);
545
546 /*
547 * XXX: handle requests for specific lease time!
548 * XXX: old lease might not have expired yet?
549 */
550 // OptLeaseTime reqLeaseTime(req);
551 b->setLeaseTime(1200);
552 return b;
553}
554
555
556int Db::addBinding(Binding *newb)
557{
558 if (!addressBelongs(newb->m_addr))
559 {
560 LogDHCP(("Binding for out of range address %RTnaipv4 ignored\n",
561 newb->m_addr.u));
562 return VERR_INVALID_PARAMETER;
563 }
564
565 for (bindings_t::iterator it = m_bindings.begin();
566 it != m_bindings.end(); ++it)
567 {
568 Binding *b = *it;
569
570 if (newb->m_addr.u == b->m_addr.u)
571 {
572 LogDHCP(("> ADD: %R[binding]\n", newb));
573 LogDHCP(("> .... duplicate ip: %R[binding]\n", b));
574 return VERR_INVALID_PARAMETER;
575 }
576
577 if (newb->m_id == b->m_id)
578 {
579 LogDHCP(("> ADD: %R[binding]\n", newb));
580 LogDHCP(("> .... duplicate id: %R[binding]\n", b));
581 return VERR_INVALID_PARAMETER;
582 }
583 }
584
585 bool ok = m_pool.allocate(newb->m_addr);
586 if (!ok)
587 {
588 LogDHCP(("> ADD: failed to claim IP %R[binding]\n", newb));
589 return VERR_INVALID_PARAMETER;
590 }
591
592 m_bindings.push_back(newb);
593 return VINF_SUCCESS;
594}
595
596
597void Db::cancelOffer(const DhcpClientMessage &req)
598{
599 const OptRequestedAddress reqAddr(req);
600 if (!reqAddr.present())
601 return;
602
603 const RTNETADDRIPV4 addr = reqAddr.value();
604 const ClientId &id(req.clientId());
605
606 for (bindings_t::iterator it = m_bindings.begin();
607 it != m_bindings.end(); ++it)
608 {
609 Binding *b = *it;
610
611 if (b->addr().u == addr.u && b->id() == id)
612 {
613 if (b->state() == Binding::OFFERED)
614 {
615 b->setLeaseTime(0);
616 b->setState(Binding::RELEASED);
617 }
618 return;
619 }
620 }
621}
622
623
624bool Db::releaseBinding(const DhcpClientMessage &req)
625{
626 const RTNETADDRIPV4 addr = req.ciaddr();
627 const ClientId &id(req.clientId());
628
629 for (bindings_t::iterator it = m_bindings.begin();
630 it != m_bindings.end(); ++it)
631 {
632 Binding *b = *it;
633
634 if (b->addr().u == addr.u && b->id() == id)
635 {
636 b->setState(Binding::RELEASED);
637 return true;
638 }
639 }
640
641 return false;
642}
643
644
645int Db::writeLeases(const std::string &strFileName) const
646{
647 LogDHCP(("writing leases to %s\n", strFileName.c_str()));
648
649 xml::Document doc;
650
651 xml::ElementNode *root = doc.createRootElement("Leases");
652 if (root == NULL)
653 return VERR_INTERNAL_ERROR;
654
655 root->setAttribute("version", "1.0");
656
657 for (bindings_t::const_iterator it = m_bindings.begin();
658 it != m_bindings.end(); ++it)
659 {
660 const Binding *b = *it;
661 b->toXML(root);
662 }
663
664 try {
665 xml::XmlFileWriter writer(doc);
666 writer.write(strFileName.c_str(), true);
667 }
668 catch (const xml::EIPRTFailure &e)
669 {
670 LogDHCP(("%s\n", e.what()));
671 return e.rc();
672 }
673 catch (const RTCError &e)
674 {
675 LogDHCP(("%s\n", e.what()));
676 return VERR_GENERAL_FAILURE;
677 }
678 catch (...)
679 {
680 LogDHCP(("Unknown exception while writing '%s'\n",
681 strFileName.c_str()));
682 return VERR_GENERAL_FAILURE;
683 }
684
685 return VINF_SUCCESS;
686}
687
688
689int Db::loadLeases(const std::string &strFileName)
690{
691 LogDHCP(("loading leases from %s\n", strFileName.c_str()));
692
693 xml::Document doc;
694 try
695 {
696 xml::XmlFileParser parser;
697 parser.read(strFileName.c_str(), doc);
698 }
699 catch (const xml::EIPRTFailure &e)
700 {
701 LogDHCP(("%s\n", e.what()));
702 return e.rc();
703 }
704 catch (const RTCError &e)
705 {
706 LogDHCP(("%s\n", e.what()));
707 return VERR_GENERAL_FAILURE;
708 }
709 catch (...)
710 {
711 LogDHCP(("Unknown exception while reading and parsing '%s'\n",
712 strFileName.c_str()));
713 return VERR_GENERAL_FAILURE;
714 }
715
716 xml::ElementNode *ndRoot = doc.getRootElement();
717 if (ndRoot == NULL || !ndRoot->nameEquals("Leases"))
718 {
719 return VERR_NOT_FOUND;
720 }
721
722 xml::NodesLoop it(*ndRoot);
723 const xml::ElementNode *node;
724 while ((node = it.forAllNodes()) != NULL)
725 {
726 if (!node->nameEquals("Lease"))
727 continue;
728
729 loadLease(node);
730 }
731
732 return VINF_SUCCESS;
733}
734
735
736void Db::loadLease(const xml::ElementNode *ndLease)
737{
738 Binding *b = Binding::fromXML(ndLease);
739 bool expired = b->expire();
740
741 if (!expired)
742 LogDHCP(("> LOAD: lease %R[binding]\n", b));
743 else
744 LogDHCP(("> LOAD: EXPIRED lease %R[binding]\n", b));
745
746 addBinding(b);
747}
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