VirtualBox

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

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

NetworkServices/Dhcpd: implement reading config from a file.

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