Kea 3.3.1
radius_access.cc
Go to the documentation of this file.
1// Copyright (C) 2020-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <dhcp/dhcp4.h>
10#include <dhcp/dhcp6.h>
12#include <dhcpsrv/cfgmgr.h>
13#include <dhcpsrv/host_mgr.h>
14#include <radius_access.h>
15#include <radius_log.h>
16#include <radius_status.h>
17#include <radius_utils.h>
18#include <stats/stats_mgr.h>
20#include <util/str.h>
21#include <stdio.h>
22#include <sstream>
23
24using namespace std;
25using namespace isc;
26using namespace isc::asiolink;
27using namespace isc::data;
28using namespace isc::dhcp;
29using namespace isc::hooks;
30using namespace isc::stats;
31using namespace isc::util;
32using namespace isc::util::str;
33namespace ph = std::placeholders;
34
35namespace {
36
37// From Dhcpv6Srv.
39getMAC(Pkt6& pkt) {
41 getMACSources().get();
42 HWAddrPtr hwaddr;
43 for (auto const& source : mac_sources) {
44 hwaddr = pkt.getMAC(source);
45 if (hwaddr) {
46 return (hwaddr);
47 }
48 }
49 return (hwaddr);
50}
51
52} // end of anonymous namespace.
53
54namespace isc {
55namespace radius {
56
58 const std::vector<uint8_t>& id,
59 AttributesPtr send_attrs)
60 : subnet_id_(subnet_id), id_(id), send_attrs_(send_attrs) {
61}
62
64 const CallbackAuth& callback)
65 : env_(env), auth_() {
66 auth_.reset(new RadiusAsyncAuth(env.subnet_id_, env_.send_attrs_, callback));
67 RadiusImpl::instance().registerExchange(auth_->getExchange());
68 // set the IO service when async access will be available.
69}
70
71void
73 auth_->start();
74}
75
78
79bool
81 std::vector<uint8_t>& id,
82 std::string& text) {
84 try {
85 HWAddrPtr hwaddr;
86 OptionPtr option;
87 ClientIdPtr clientid;
88 bool extracted = false;
89 switch (type) {
91 hwaddr = query.getHWAddr();
92 if (!hwaddr) {
93 isc_throw(BadValue, "no hardware address");
94 }
95 id = hwaddr->hwaddr_;
96 if (id.empty()) {
97 isc_throw(BadValue, "empty hardware address");
98 }
99 text = query.getHWAddr()->toText(false);
100 if (RadiusImpl::instance().canonical_mac_address_) {
101 text = canonize(text);
102 }
103 break;
104
105 case Host::IDENT_DUID:
107 if (!option) {
108 isc_throw(BadValue, "no client-id option");
109 }
110 clientid.reset(new ClientId(option->getData()));
111 id = clientid->getClientId();
112 if ((id.size() <= 5) || (id[0] != CLIENT_ID_OPTION_TYPE_DUID)) {
113 isc_throw(BadValue, "no DUID in client-id option");
114 }
115 id = vector<uint8_t>(id.begin() + 5, id.end());
116 if (RadiusImpl::instance().clientid_printable_) {
117 text = toPrintable(id);
118 } else {
119 text = dumpAsHex(id);
120 }
121 break;
122
124 option = query.getOption(DHO_DHCP_AGENT_OPTIONS);
125 if (!option) {
126 isc_throw(BadValue, "no relay agent options");
127 }
128 option = option->getOption(RAI_OPTION_AGENT_CIRCUIT_ID);
129 if (!option) {
130 isc_throw(BadValue, "no circuit-id option");
131 }
132 id = option->getData();
133 if (id.empty()) {
134 isc_throw(BadValue, "empty circuit-id option");
135 }
136 if (RadiusImpl::instance().clientid_printable_) {
137 text = toPrintable(id);
138 } else {
139 text = dumpAsHex(id);
140 }
141 break;
142
145 if (!option) {
146 isc_throw(BadValue, "no client-id option");
147 }
148 clientid.reset(new ClientId(option->getData()));
149 id = clientid->getClientId();
150 if (id.empty()) {
151 isc_throw(BadValue, "empty client-id option");
152 }
153 if (RadiusImpl::instance().extract_duid_) {
154 text = dumpAsHex(extractDuid(clientid, extracted));
155 }
156 if (extracted) {
157 break;
158 }
159 if (RadiusImpl::instance().clientid_pop0_) {
160 vector<uint8_t> popped = pop0(clientid);
161 if (RadiusImpl::instance().clientid_printable_) {
162 text = toPrintable(popped);
163 } else {
164 text = dumpAsHex(popped);
165 }
166 } else if (RadiusImpl::instance().clientid_printable_) {
167 text = toPrintable(id);
168 } else {
169 text = dumpAsHex(id);
170 }
171 break;
172
173 case Host::IDENT_FLEX:
174 // Relies on replace-client-id flex-id parameter to be true.
176 if (!option) {
177 isc_throw(BadValue, "no client-id option");
178 }
179 clientid.reset(new ClientId(option->getData()));
180 id = clientid->getClientId();
181 if ((id.size() <= 1) || (id[0] != 0)) {
182 isc_throw(BadValue, "no flex-id in client-id option");
183 }
184 id = vector<uint8_t>(id.begin() + 1, id.end());
185 if (RadiusImpl::instance().clientid_printable_) {
186 text = toPrintable(id);
187 } else {
188 text = dumpAsHex(id);
189 }
190 break;
191
192 default:
193 isc_throw(OutOfRange, "unsupported identifier type " << type);
194 }
195 } catch (const std::exception& ex) {
197 .arg(Host::getIdentifierName(type))
198 .arg(query.getLabel())
199 .arg(ex.what());
200 id.clear();
201 text.clear();
202 return (false);
203 }
205 .arg(dumpAsHex(id))
206 .arg(Host::getIdentifierName(type))
207 .arg(text)
208 .arg(query.getLabel());
209 return (true);
210}
211
212bool
214 std::vector<uint8_t>& id,
215 std::string& text) {
217 try {
218 HWAddrPtr hwaddr;
219 OptionPtr option;
220 DuidPtr duid;
221 switch (type) {
222 case Host::IDENT_DUID:
223 option = query.getOption(D6O_CLIENTID);
224 if (!option) {
225 isc_throw(BadValue, "no client-id option");
226 }
227 duid.reset(new DUID(option->getData()));
228 id = duid->getDuid();
229 if (id.empty()) {
230 isc_throw(BadValue, "empty client-id option");
231 }
232 if (RadiusImpl::instance().clientid_pop0_) {
233 vector<uint8_t> popped = pop0(duid);
234 if (RadiusImpl::instance().clientid_printable_) {
235 text = toPrintable(popped);
236 } else {
237 text = dumpAsHex(popped);
238 }
239 } else if (RadiusImpl::instance().clientid_printable_) {
240 text = toPrintable(id);
241 } else {
242 text = dumpAsHex(id);
243 }
244 break;
245
247 hwaddr = getMAC(query);
248 if (!hwaddr) {
249 isc_throw(BadValue, "no hardware address");
250 }
251 id = hwaddr->hwaddr_;
252 if (id.empty()) {
253 isc_throw(BadValue, "empty hardware address");
254 }
255 text = hwaddr->toText(false);
256 if (RadiusImpl::instance().canonical_mac_address_) {
257 text = canonize(text);
258 }
259 break;
260
261 case Host::IDENT_FLEX:
262 option = query.getOption(D6O_CLIENTID);
263 if (!option) {
264 isc_throw(BadValue, "no client-id option");
265 }
266 duid.reset(new DUID(option->getData()));
267 id = duid->getDuid();
268 if ((id.size() <= 2) || (id[0] != 0) || (id[1] != 0)) {
269 isc_throw(BadValue, "no flex-id in client-id option");
270 }
271 id = vector<uint8_t>(id.begin() + 2, id.end());
272 if (RadiusImpl::instance().clientid_printable_) {
273 text = toPrintable(id);
274 } else {
275 text = dumpAsHex(id);
276 }
277 break;
278
279 default:
280 isc_throw(OutOfRange, "unsupported identifier type " << type);
281 }
282 } catch (const std::exception& ex) {
284 .arg(Host::getIdentifierName(type))
285 .arg(query.getLabel())
286 .arg(ex.what());
287 id.clear();
288 text.clear();
289 return (false);
290 }
292 .arg(dumpAsHex(id))
293 .arg(Host::getIdentifierName(type))
294 .arg(text)
295 .arg(query.getLabel());
296 return (true);
297}
298
301 uint32_t subnet_id,
302 const std::vector<uint8_t>& id,
303 const std::string& text) {
304 AttributesPtr send(new Attributes());
305
306 try {
307 if (subnet_id == SUBNET_ID_UNUSED) {
308 isc_throw(BadValue, "subnet ID is reserved");
309 }
310
311 send->add(Attribute::fromString(PW_USER_NAME, text));
312
313 // Add hardware address.
314 HWAddrPtr hwaddr = query.getHWAddr();
315 if (RadiusImpl::instance().id_type4_ != Host::IDENT_HWADDR &&
316 hwaddr && !hwaddr->hwaddr_.empty()) {
317 string hw = hwaddr->toText(false);
318 if (RadiusImpl::instance().canonical_mac_address_) {
319 hw = canonize(hw);
320 }
322 }
323
324 // Add attributes from configuration.
325 send->append(RadiusImpl::instance().auth_->
326 attributes_.getEvalAll(query));
327
328 } catch (const std::exception& ex) {
330 .arg(ex.what())
331 .arg(query.getLabel());
332 return (RadiusAuthHandlerPtr());
333 }
334
335 // Return the handler.
336 RadiusAuthEnv env(subnet_id, id, send);
337 RadiusAuthHandlerPtr handler;
338 handler.reset(new RadiusAuthHandler(env,
339 std::bind(&RadiusAccess::terminate4,
340 env, ph::_1, ph::_2)));
341 return (handler);
342}
343
346 uint32_t subnet_id,
347 const std::vector<uint8_t>& id,
348 const std::string& text) {
349 AttributesPtr send(new Attributes());
350
351 try {
352 if (subnet_id == SUBNET_ID_UNUSED) {
353 isc_throw(BadValue, "subnet ID is reserved");
354 }
355
356 send->add(Attribute::fromString(PW_USER_NAME, text));
357
358 // Add hardware address.
359 HWAddrPtr hwaddr = getMAC(query);
360 if (RadiusImpl::instance().id_type6_ != Host::IDENT_HWADDR &&
361 hwaddr && !hwaddr->hwaddr_.empty()) {
362 string hw = hwaddr->toText(false);
363 if (RadiusImpl::instance().canonical_mac_address_) {
364 hw = canonize(hw);
365 }
367 }
368
369 // Add attributes from configuration.
370 send->append(RadiusImpl::instance().auth_->
371 attributes_.getEvalAll(query));
372
373 } catch (const std::exception& ex) {
375 .arg(ex.what())
376 .arg(query.getLabel());
377 return (RadiusAuthHandlerPtr());
378 }
379
380 // Return the handler.
381 RadiusAuthEnv env(subnet_id, id, send);
382 RadiusAuthHandlerPtr handler;
383 handler.reset(new RadiusAuthHandler(env,
384 std::bind(&RadiusAccess::terminate6,
385 env, ph::_1, ph::_2)));
386 return (handler);
387}
388
389bool
390RadiusAccess::reselectSubnet(const dhcp::Pkt4Ptr& query, uint32_t& subnet_id,
391 bool& both_global, const std::string& cclass) {
392 both_global = false;
393 ConstCfgSubnets4Ptr subnets4 =
394 CfgMgr::instance().getCurrentCfg()->getCfgSubnets4();
395
396 // Check selected subnet.
397 ConstSubnet4Ptr subnet = subnets4->getBySubnetId(subnet_id);
398 if (!subnet) {
399 return (false);
400 }
401 // If one of the pools accepts the client-class we're done.
402 if (subnet->clientSupported(query->classes_)) {
403 const PoolCollection& pools = subnet->getPools(Lease::TYPE_V4);
404 for (auto const& pool : pools) {
405 if (pool->clientSupported(cclass)) {
406 return (false);
407 }
408 }
409 }
410 // Check if this subnet uses global reservations.
411 bool use_global = subnet->getReservationsGlobal();
412
413 // Try other selectable subnets.
414 const Subnet4Collection* subnets = subnets4->getAll();
415 CfgSubnets4Ptr selectable(new CfgSubnets4());
416
417 for (auto const& iter : *subnets) {
418 // Check pools.
419 const PoolCollection& pools = iter->getPools(Lease::TYPE_V4);
420 for (auto const& pool : pools) {
421 if (pool->clientSupported(cclass)) {
422 selectable->add(iter);
423 break;
424 }
425 }
426 }
427
428 const SubnetSelector& selector = CfgSubnets4::initSelector(query);
429 subnet = selectable->selectSubnet(selector);
430
431 if (!subnet) {
432 subnet_id = SUBNET_ID_UNUSED;
433 } else {
434 subnet_id = subnet->getID();
435 if (use_global && subnet->getReservationsGlobal()) {
436 both_global = true;
437 }
438 }
439 return (true);
440}
441
442bool
443RadiusAccess::reselectSubnet(const dhcp::Pkt6Ptr& query, uint32_t& subnet_id,
444 bool& both_global, const std::string& cclass) {
445 both_global = false;
446 ConstCfgSubnets6Ptr subnets6 =
447 CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
448
449 // Check selected subnet.
450 ConstSubnet6Ptr subnet = subnets6->getBySubnetId(subnet_id);
451 if (!subnet) {
452 return (false);
453 }
454 // If one of the pools accepts the client-class we're done.
455 if (subnet->clientSupported(query->classes_)) {
456 const PoolCollection& pools = subnet->getPools(Lease::TYPE_NA);
457 for (auto const& pool : pools) {
458 if (pool->clientSupported(cclass)) {
459 return (false);
460 }
461 }
462 }
463 // Check if this subnet uses global reservations.
464 bool use_global = subnet->getReservationsGlobal();
465
466 // Try other selectable subnets.
467 const Subnet6Collection* subnets = subnets6->getAll();
468 CfgSubnets6Ptr selectable(new CfgSubnets6());
469
470 for (auto const& iter : *subnets) {
471 // Check pools.
472 const PoolCollection& pools = iter->getPools(Lease::TYPE_NA);
473 for (auto const& pool : pools) {
474 if (pool->clientSupported(cclass)) {
475 selectable->add(iter);
476 break;
477 }
478 }
479 }
480
481 const SubnetSelector& selector = CfgSubnets6::initSelector(query);
482 subnet = selectable->selectSubnet(selector);
483
484 if (!subnet) {
485 subnet_id = SUBNET_ID_UNUSED;
486 } else {
487 subnet_id = subnet->getID();
488 if (use_global && subnet->getReservationsGlobal()) {
489 both_global = true;
490 }
491 }
492 return (true);
493}
494
495bool
497 uint32_t& subnet_id,
498 bool& both_global,
499 const asiolink::IOAddress& address) {
500 both_global = false;
501 ConstCfgSubnets4Ptr subnets4 =
502 CfgMgr::instance().getCurrentCfg()->getCfgSubnets4();
503
504 // Check selected subnet.
505 ConstSubnet4Ptr subnet = subnets4->getBySubnetId(subnet_id);
506 if (!subnet) {
507 return (false);
508 }
509 // If the reserved address is in range we're done.
510 if (subnet->clientSupported(query->classes_) && subnet->inRange(address)) {
511 return (false);
512 }
513 // Check if this subnet uses global reservations.
514 bool use_global = subnet->getReservationsGlobal();
515
516 // Select subnet by reserved address only.
517 subnet = subnets4->selectSubnet(address, query->classes_);
518 if (!subnet) {
519 subnet_id = SUBNET_ID_UNUSED;
520 } else {
521 subnet_id = subnet->getID();
522 if (use_global && subnet->getReservationsGlobal()) {
523 both_global = true;
524 }
525 }
526 return (true);
527}
528
529bool
531 uint32_t& subnet_id,
532 bool& both_global,
533 const asiolink::IOAddress& address) {
534 both_global = false;
535 ConstCfgSubnets6Ptr subnets6 =
536 CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
537
538 // Check selected subnet.
539 ConstSubnet6Ptr subnet = subnets6->getBySubnetId(subnet_id);
540 if (!subnet) {
541 return (false);
542 }
543 // If the reserved address is in range we're done.
544 if (subnet->clientSupported(query->classes_) && subnet->inRange(address)) {
545 return (false);
546 }
547 // Check if this subnet uses global reservations.
548 bool use_global = subnet->getReservationsGlobal();
549
550 // Select subnet by reserved address only.
551 subnet = subnets6->selectSubnet(address, query->classes_);
552 if (!subnet) {
553 subnet_id = SUBNET_ID_UNUSED;
554 } else {
555 subnet_id = subnet->getID();
556 if (use_global && subnet->getReservationsGlobal()) {
557 both_global = true;
558 }
559 }
560 return (true);
561}
562
563void
565 AttributesPtr recv_attrs,
566 Pkt4Ptr& query, bool& drop) {
568 MultiThreadingLock lock(impl.auth_->requests4_.mutex_);
569
570 // Get the pending request.
572 impl.auth_->requests4_.get(env.id_);
573 if (!pending_request) {
575 .arg(dumpAsHex(env.id_));
576 drop = true;
577 return;
578 }
579 // Outside some unit tests the query is never null.
580 query = pending_request->query_;
581 impl.auth_->requests4_.remove(env.id_);
582
583 // Process response.
584 ConstAttributePtr ip_address;
585 ConstAttributePtr framed_pool;
586 ConstAttributePtr class_;
587 bool reselected = false;
588 bool both_global = false;
589 uint32_t original_subnet_id = env.subnet_id_;
590
591 if (result == REJECT_RC) {
592 // Create the host with no attributes.
593 // Should we saved them for the Class?
594 recv_attrs.reset();
595 // Reset subnet
596 env.subnet_id_ = SUBNET_ID_UNUSED;
597 reselected = true;
598 } else if (result != OK_RC) {
599 // Error case
601 .arg(result)
602 .arg(exchangeRCtoText(result));
603 // what to do? For now nothing!?
604 // drop = true;
605 return;
606 } else if (recv_attrs) {
607 // Pickup interesting things in received attributes.
608 ip_address = recv_attrs->get(PW_FRAMED_IP_ADDRESS);
609 framed_pool = recv_attrs->get(PW_FRAMED_POOL);
610 class_ = recv_attrs->get(PW_CLASS);
611 // etc
612 }
613
614 // Set pool.
615 string cclass;
616 if (framed_pool && (framed_pool->getValueType() == PW_TYPE_STRING)) {
617 cclass = framed_pool->toString();
618 if (query) {
619 query->addClass(cclass);
620 }
621 }
622
623 // Get IPv4 address.
625 if (ip_address && (ip_address->getValueType() == PW_TYPE_IPADDR)) {
626 addr4 = ip_address->toIpAddr();
627 }
628
629 // Check reselection using pool and client-class.
630 if (query && !reselected && !cclass.empty() &&
631 impl.reselect_subnet_pool_) {
632 reselected = reselectSubnet(query, env.subnet_id_, both_global, cclass);
633 }
634
635 // Check reselection using reserved address and range.
636 if (query && !reselected && !addr4.isV4Zero() &&
637 impl.reselect_subnet_address_) {
638 reselected = reselectSubnet(query, env.subnet_id_, both_global, addr4);
639 }
640
641 // Get host identifier.
642 Host::IdentifierType type = impl.id_type4_;
643 CacheHostDataSourcePtr cache = impl.cache_;
645 if (reselected) {
646 // Add subnet-id in user context.
647 map->set("subnet-id",
648 Element::create(static_cast<int>(env.subnet_id_)));
649 }
650
651 // Create and insert a reselecting entry.
652 uint32_t host_subnet_id = SUBNET_ID_UNUSED;
653 if (reselected && !both_global) {
655 getCfgSubnets4()->getBySubnetId(original_subnet_id);
656 if (!subnet) {
657 isc_throw(Unexpected, "no original subnet " << original_subnet_id);
658 }
659 host_subnet_id =
660 ((subnet && subnet->getReservationsGlobal()) ?
661 SUBNET_ID_GLOBAL : original_subnet_id);
662
663 // Create.
664 HostPtr host(new Host(&env.id_[0], env.id_.size(), type,
665 host_subnet_id, SUBNET_ID_UNUSED,
667
668 // Add reselect in the user-context and reset it.
669 host->setContext(map);
670 map = Element::createMap();
671
672 // Negative entry.
673 host->setNegative(true);
674
675 // Insert it.
676 if (!cache) {
677 return;
678 }
679 static_cast<void>(cache->insert(host, true));
680
681 ostringstream msg;
682 msg << "subnet-id := " << env.subnet_id_;
684 .arg(host->toText())
685 .arg(msg.str());
686 }
687
688 // Return if the subnet is null. Note in this case both_global
689 // is always false.
690 if (env.subnet_id_ == SUBNET_ID_UNUSED) {
691 return;
692 }
693
694 // Build a host entry to cache radius attributes from OK_RC answer.
696 getCfgSubnets4()->getBySubnetId(env.subnet_id_);
697 if (!subnet) {
698 isc_throw(Unexpected, "no subnet " << env.subnet_id_);
699 }
700 host_subnet_id =
701 ((subnet && subnet->getReservationsGlobal()) ?
702 SUBNET_ID_GLOBAL : env.subnet_id_);
703 HostPtr host(new Host(&env.id_[0], env.id_.size(), type,
704 host_subnet_id, SUBNET_ID_UNUSED, addr4));
705
706 // Save received attributes.
707 if (recv_attrs) {
708 map->set("radius", recv_attrs->toElement());
709 }
710 host->setContext(map);
711
712 if (addr4.isV4Zero()) {
713 // The entry has no reservation nor hostname so mark it as negative.
714 host->setNegative(true);
715 }
716
717 // Insert entry.
718 if (!cache) {
719 return;
720 }
721 cache->insert(host, true);
722
724 .arg(host->toText())
725 .arg(recv_attrs ? recv_attrs->toText() : "");
726
727 // Pass the subnet to the server code.
728 if (query) {
729 CalloutHandlePtr callout_handle = getCalloutHandle(query);
730 callout_handle->setContext("subnet4", subnet);
731 }
732}
733
734void
736 AttributesPtr recv_attrs) {
737 Pkt4Ptr query;
738 bool drop = false;
739 try {
740 terminate4Internal(env, result, recv_attrs, query, drop);
741 } catch (const std::exception& ex) {
742 // Unexpected error.
744 .arg(ex.what());
745 drop = true;
746 } catch (...) {
747 // Unexpected unknown error.
749 .arg("unknown error");
750 drop = true;
751 }
752 if (!query) {
753 return;
754 }
755 if (drop) {
758 .arg(query->getLabel());
759 StatsMgr::instance().addValue("pkt4-processing-failed",
760 static_cast<int64_t>(1));
761 StatsMgr::instance().addValue("pkt4-receive-drop",
762 static_cast<int64_t>(1));
763 HooksManager::drop("subnet4_select", query);
764 } else {
765 ostringstream msg;
766 if (env.subnet_id_ == SUBNET_ID_UNUSED) {
767 msg << "no subnet";
768 } else {
769 msg << "subnet " << env.subnet_id_;
770 }
773 .arg(query->getLabel())
774 .arg(msg.str());
775 HooksManager::unpark("subnet4_select", query);
776 }
777}
778
779void
781 AttributesPtr recv_attrs,
782 Pkt6Ptr& query, bool& drop) {
784 MultiThreadingLock lock(impl.auth_->requests6_.mutex_);
785
786 // Get the pending request.
788 impl.auth_->requests6_.get(env.id_);
789 if (!pending_request) {
791 .arg(dumpAsHex(env.id_));
792 drop = true;
793 return;
794 }
795 // Outside some unit tests the query is never null.
796 query = pending_request->query_;
797 impl.auth_->requests6_.remove(env.id_);
798
799 // Process response.
800 ConstAttributePtr ip6_address;
801 ConstAttributePtr prefix;
802 ConstAttributePtr framed_pool;
803 ConstAttributePtr class_;
804 bool reselected = false;
805 bool both_global = false;
806 uint32_t original_subnet_id = env.subnet_id_;
807
808 if (result == REJECT_RC) {
809 // Create the host with no attributes.
810 // Should we saved them for the Class?
811 recv_attrs.reset();
812 // Reset subnet
813 env.subnet_id_ = SUBNET_ID_UNUSED;
814 reselected = true;
815 } else if (result != OK_RC) {
816 // Error case
818 .arg(result)
819 .arg(exchangeRCtoText(result));
820 // what to do? For now nothing!?
821 // drop = true;
822 return;
823 } else if (recv_attrs) {
824 // Pickup interesting things in received attributes.
825 ip6_address = recv_attrs->get(PW_FRAMED_IPV6_ADDRESS);
826 prefix = recv_attrs->get(PW_DELEGATED_IPV6_PREFIX);
827 framed_pool = recv_attrs->get(PW_FRAMED_POOL);
828 class_ = recv_attrs->get(PW_CLASS);
829 // etc
830 }
831
832 // Set pool.
833 string cclass;
834 if (framed_pool && (framed_pool->getValueType() == PW_TYPE_STRING)) {
835 cclass = framed_pool->toString();
836 if (query) {
837 query->addClass(cclass);
838 }
839 }
840
841 // Get IPv6 address.
843 if (ip6_address && (ip6_address->getValueType() == PW_TYPE_IPV6ADDR)) {
844 addr6 = ip6_address->toIpv6Addr();
845 }
846
847 // Get prefix.
848 uint8_t pref_len = 0;
850 if (prefix && (prefix->getValueType() == PW_TYPE_IPV6PREFIX)) {
851 pref_len = prefix->toIpv6PrefixLen();
852 pref_addr = prefix->toIpv6Prefix();
853 }
854
855 // Check reselection using pool and client-class.
856 if (query && !reselected && !cclass.empty() &&
857 impl.reselect_subnet_pool_) {
858 reselected = reselectSubnet(query, env.subnet_id_, both_global, cclass);
859 }
860
861 // Check reselection using reserved address and range.
862 if (query && !reselected && !addr6.isV6Zero() &&
863 impl.reselect_subnet_address_) {
864 reselected = reselectSubnet(query, env.subnet_id_, both_global, addr6);
865 }
866
867 // No reselection using prefix.
868
869 // Get host identifier.
870 Host::IdentifierType type = impl.id_type6_;
871 CacheHostDataSourcePtr cache = impl.cache_;
873 if (reselected) {
874 // Add subnet-id in user context.
875 map->set("subnet-id",
876 Element::create(static_cast<int>(env.subnet_id_)));
877 }
878
879 // Create and insert a reselecting entry.
880 uint32_t host_subnet_id = SUBNET_ID_UNUSED;
881 if (reselected && !both_global) {
883 getCfgSubnets6()->getBySubnetId(original_subnet_id);
884 if (!subnet) {
885 isc_throw(Unexpected, "no original subnet " << original_subnet_id);
886 }
887 host_subnet_id =
888 ((subnet && subnet->getReservationsGlobal()) ?
889 SUBNET_ID_GLOBAL : original_subnet_id);
890
891 // Create.
892 HostPtr host(new Host(&env.id_[0], env.id_.size(), type,
893 SUBNET_ID_UNUSED, host_subnet_id,
895
896 // Add reselect in the user-context and reset it.
897 host->setContext(map);
898 map = Element::createMap();
899
900 // Negative entry.
901 host->setNegative(true);
902
903 // Insert it.
904 if (!cache) {
905 return;
906 }
907 static_cast<void>(cache->insert(host, true));
908
909 ostringstream msg;
910 msg << "subnet-id := " << env.subnet_id_;
912 .arg(host->toText())
913 .arg(msg.str());
914 }
915
916 // Return if the subnet is null. Note in this case both_global
917 // is always false.
918 if (env.subnet_id_ == SUBNET_ID_UNUSED) {
919 return;
920 }
921
922 // Build a host entry to cache radius attributes from OK_RC answer.
924 getCfgSubnets6()->getBySubnetId(env.subnet_id_);
925 if (!subnet) {
926 isc_throw(Unexpected, "no subnet " << env.subnet_id_);
927 }
928 host_subnet_id =
929 ((subnet && subnet->getReservationsGlobal()) ?
930 SUBNET_ID_GLOBAL : env.subnet_id_);
931 HostPtr host(new Host(&env.id_[0], env.id_.size(), type,
932 SUBNET_ID_UNUSED, host_subnet_id,
934
935 // Save received attributes.
936 if (recv_attrs) {
937 map->set("radius", recv_attrs->toElement());
938 }
939 host->setContext(map);
940
941 // Add IPv6 address.
942 bool has_reservation = false;
943 if (!addr6.isV6Zero()) {
944 try {
945 host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, addr6));
946 has_reservation = true;
947 } catch (...) {
948 }
949 }
950
951 // Add delegated prefix.
952 if (pref_len && !pref_addr.isV6Zero()) {
953 try {
954 host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD, pref_addr,
955 pref_len));
956 has_reservation = true;
957 } catch (...) {
958 }
959 }
960
961 if (!has_reservation) {
962 // The entry has no reservation nor hostname so mark it as negative.
963 host->setNegative(true);
964 }
965
966 // Insert entry.
967 if (!cache) {
968 return;
969 }
970 cache->insert(host, true);
971
973 .arg(host->toText())
974 .arg(recv_attrs ? recv_attrs->toText() : "");
975
976 // Pass the subnet to the server code.
977 if (query) {
978 CalloutHandlePtr callout_handle = getCalloutHandle(query);
979 callout_handle->setContext("subnet6", subnet);
980 }
981}
982
983void
985 AttributesPtr recv_attrs) {
986 Pkt6Ptr query;
987 bool drop = false;
988 try {
989 terminate6Internal(env, result, recv_attrs, query, drop);
990 } catch (const std::exception& ex) {
991 // Unexpected error.
993 .arg(ex.what());
994 drop = true;
995 } catch (...) {
996 // Unexpected unknown error.
998 .arg("unknown error");
999 drop = true;
1000 }
1001 if (!query) {
1002 return;
1003 }
1004 if (drop) {
1007 .arg(query->getLabel());
1008 StatsMgr::instance().addValue("pkt6-processing-failed",
1009 static_cast<int64_t>(1));
1010 StatsMgr::instance().addValue("pkt6-receive-drop",
1011 static_cast<int64_t>(1));
1012 HooksManager::drop("subnet6_select", query);
1013 } else {
1014 ostringstream msg;
1015 if (env.subnet_id_ == SUBNET_ID_UNUSED) {
1016 msg << "no subnet";
1017 } else {
1018 msg << "subnet " << env.subnet_id_;
1019 }
1022 .arg(query->getLabel())
1023 .arg(msg.str());
1024 HooksManager::unpark("subnet6_select", query);
1025 }
1026}
1027
1028void
1032 if (idle_timer_interval_ <= 0) {
1033 return;
1034 }
1035 // Cope to one day.
1036 long secs = idle_timer_interval_;
1037 if (secs > 24*60*60) {
1038 secs = 24*60*60;
1039 }
1040 idle_timer_.reset(new IntervalTimer(RadiusImpl::instance().getIOContext()));
1042 secs * 1000, IntervalTimer::REPEATING);
1043}
1044
1045void
1047 AttributesPtr send_attrs;
1048 RadiusAuthStatusPtr handler(new RadiusAuthStatus(send_attrs, 0));
1049 RadiusImpl::instance().registerExchange(handler->getExchange());
1050 handler->start();
1051}
1052
1053} // end of namespace isc::radius
1054} // end of namespace isc
static ElementPtr create(const Position &pos=ZERO_POSITION())
Create a NullElement.
Definition data.cc:300
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:355
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
A generic exception that is thrown when an unexpected error condition occurs.
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition cfgmgr.cc:29
SrvConfigPtr getCurrentCfg()
Returns a pointer to the current configuration.
Definition cfgmgr.cc:116
Holds subnets configured for the DHCPv4 server.
static SubnetSelector initSelector(const Pkt4Ptr &query)
Build selector from a client's message.
Holds subnets configured for the DHCPv6 server.
static SubnetSelector initSelector(const Pkt6Ptr &query)
Build selector from a client's message.
Holds Client identifier or client IPv4 address.
Definition duid.h:222
Holds DUID (DHCPv6 Unique Identifier).
Definition duid.h:142
Represents a device with IPv4 and/or IPv6 reservations.
Definition host.h:327
static std::string getIdentifierName(const IdentifierType &type)
Returns name of the identifier of a specified type.
Definition host.cc:356
IdentifierType
Type of the host identifier.
Definition host.h:337
@ IDENT_FLEX
Flexible host identifier.
Definition host.h:342
@ IDENT_CLIENT_ID
Definition host.h:341
@ IDENT_CIRCUIT_ID
Definition host.h:340
IPv6 reservation for a host.
Definition host.h:163
Represents DHCPv4 packet.
Definition pkt4.h:37
std::string getLabel() const
Returns text representation of the primary packet identifiers.
Definition pkt4.cc:367
HWAddrPtr getHWAddr() const
returns hardware address information
Definition pkt4.h:325
Represents a DHCPv6 packet.
Definition pkt6.h:44
virtual std::string getLabel() const
Returns text representation of the primary packet identifiers.
Definition pkt6.cc:730
OptionPtr getOption(const uint16_t type)
Returns the first option of specified type.
Definition pkt.cc:71
HWAddrPtr getMAC(uint32_t hw_addr_src)
Returns MAC address.
Definition pkt.cc:196
static bool unpark(const std::string &hook_name, T parked_object)
Forces unparking the object (packet).
static bool drop(const std::string &hook_name, T parked_object)
Removes parked object without calling a callback.
static AttributePtr fromString(const uint8_t type, const std::string &value)
From type specific factories.
Collection of attributes.
static void terminate6Internal(RadiusAuthEnv &env, int result, AttributesPtr recv_attrs, dhcp::Pkt6Ptr &query, bool &drop)
Termination callback body - IPv6.
static void terminate4Internal(RadiusAuthEnv &env, int result, AttributesPtr recv_attrs, dhcp::Pkt4Ptr &query, bool &drop)
Termination callback body - IPv4.
static void terminate4(RadiusAuthEnv env, int result, AttributesPtr recv_attrs)
Termination callback - IPv4.
static bool reselectSubnet(const dhcp::Pkt4Ptr &query, uint32_t &subnet_id, bool &both_global, const std::string &cclass)
Subnet reselect - class/pool IPv4.
static void terminate6(RadiusAuthEnv env, int result, AttributesPtr recv_attrs)
Termination callback - IPv6.
bool getIdentifier(dhcp::Pkt4 &query, std::vector< uint8_t > &id, std::string &text)
Get Identifier – IPv4.
void setIdleTimer()
Set idle timer.
static void IdleTimerCallback()
Idle timer callback.
RadiusAuthHandlerPtr buildAuth(dhcp::Pkt4 &query, uint32_t subnet_id, const std::vector< uint8_t > &id, const std::string &text)
Build RadiusAuth handler for Access-Request - IPv4.
class for asynchronous authentication communication with servers.
Class of Radius access environments.
RadiusAuthEnv(uint32_t subnet_id, const std::vector< uint8_t > &id, AttributesPtr send_attrs)
Constructor.
const std::vector< uint8_t > id_
Identifier.
uint32_t subnet_id_
Subnet Id (aka client/NAS port).
AttributesPtr send_attrs_
Attributes to send.
Class of Radius access communication handler.
RadiusAuthHandler(RadiusAuthEnv env, const CallbackAuth &callback)
Constructor.
RadiusAuthEnv env_
Environment.
void start()
Start communication.
RadiusAsyncAuthPtr auth_
Pointer to the communication class.
Class for communication with access servers.
Radius hooks library implementation.
Definition radius.h:151
dhcp::Host::IdentifierType id_type4_
Identifier type for IPv4.
Definition radius.h:347
void registerExchange(ExchangePtr exchange)
Register Exchange.
Definition radius.cc:225
dhcp::Host::IdentifierType id_type6_
Identifier type for IPv6.
Definition radius.h:350
static RadiusImpl & instance()
RadiusImpl is a singleton class.
Definition radius.cc:192
CfgAttributes attributes_
Attribute configurations.
RadiusService(const std::string &name)
Constructor.
asiolink::IntervalTimerPtr idle_timer_
Idle timer.
long idle_timer_interval_
Idle timer interval in seconds.
void cancelIdleTimer()
Cancel idle timer.
static std::mutex idle_timer_mutex_
Idle timer mutex.
static StatsMgr & instance()
Statistics Manager accessor method.
@ D6O_CLIENTID
Definition dhcp6.h:21
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
void addValue(const std::string &name, const int64_t value)
Records incremental integer observation.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
@ DHO_DHCP_CLIENT_IDENTIFIER
Definition dhcp4.h:130
@ DHO_DHCP_AGENT_OPTIONS
Definition dhcp4.h:151
boost::shared_ptr< const Subnet6 > ConstSubnet6Ptr
A const pointer to a Subnet6 object.
Definition subnet.h:620
std::vector< uint32_t > CfgMACSources
Container for defined MAC/hardware address sources.
boost::shared_ptr< Host > HostPtr
Pointer to the Host object.
Definition host.h:837
boost::shared_ptr< const Subnet4 > ConstSubnet4Ptr
A const pointer to a Subnet4 object.
Definition subnet.h:455
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition pkt4.h:556
boost::shared_ptr< DUID > DuidPtr
Definition duid.h:136
boost::multi_index_container< Subnet6Ptr, boost::multi_index::indexed_by< boost::multi_index::ordered_unique< boost::multi_index::tag< SubnetSubnetIdIndexTag >, boost::multi_index::const_mem_fun< Subnet, SubnetID, &Subnet::getID > >, boost::multi_index::ordered_unique< boost::multi_index::tag< SubnetPrefixIndexTag >, boost::multi_index::const_mem_fun< Subnet, std::string, &Subnet::toText > >, boost::multi_index::ordered_non_unique< boost::multi_index::tag< SubnetModificationTimeIndexTag >, boost::multi_index::const_mem_fun< data::BaseStampedElement, boost::posix_time::ptime, &data::BaseStampedElement::getModificationTime > > > > Subnet6Collection
A collection of Subnet6 objects.
Definition subnet.h:934
std::vector< PoolPtr > PoolCollection
a container for either IPv4 or IPv6 Pools
Definition pool.h:729
boost::shared_ptr< CfgSubnets6 > CfgSubnets6Ptr
Non-const pointer.
boost::shared_ptr< HWAddr > HWAddrPtr
Shared pointer to a hardware address structure.
Definition hwaddr.h:154
isc::hooks::CalloutHandlePtr getCalloutHandle(const T &pktptr)
CalloutHandle Store.
boost::multi_index_container< Subnet4Ptr, boost::multi_index::indexed_by< boost::multi_index::ordered_unique< boost::multi_index::tag< SubnetSubnetIdIndexTag >, boost::multi_index::const_mem_fun< Subnet, SubnetID, &Subnet::getID > >, boost::multi_index::ordered_unique< boost::multi_index::tag< SubnetPrefixIndexTag >, boost::multi_index::const_mem_fun< Subnet, std::string, &Subnet::toText > >, boost::multi_index::ordered_non_unique< boost::multi_index::tag< SubnetServerIdIndexTag >, boost::multi_index::const_mem_fun< Network4, asiolink::IOAddress, &Network4::getServerId > >, boost::multi_index::ordered_non_unique< boost::multi_index::tag< SubnetModificationTimeIndexTag >, boost::multi_index::const_mem_fun< data::BaseStampedElement, boost::posix_time::ptime, &data::BaseStampedElement::getModificationTime > > > > Subnet4Collection
A collection of Subnet4 objects.
Definition subnet.h:863
boost::shared_ptr< CacheHostDataSource > CacheHostDataSourcePtr
CacheHostDataSource pointer.
boost::shared_ptr< ClientId > ClientIdPtr
Shared pointer to a Client ID.
Definition duid.h:216
boost::shared_ptr< CfgSubnets4 > CfgSubnets4Ptr
Non-const pointer.
boost::shared_ptr< const CfgSubnets4 > ConstCfgSubnets4Ptr
Const pointer.
boost::shared_ptr< const CfgSubnets6 > ConstCfgSubnets6Ptr
Const pointer.
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition pkt6.h:31
@ RAI_OPTION_AGENT_CIRCUIT_ID
Definition dhcp4.h:265
boost::shared_ptr< Option > OptionPtr
Definition option.h:37
boost::shared_ptr< CalloutHandle > CalloutHandlePtr
A shared pointer to a CalloutHandle object.
@ PW_DELEGATED_IPV6_PREFIX
ipv6prefix.
@ PW_FRAMED_IPV6_ADDRESS
ipv6addr.
vector< uint8_t > extractDuid(const ClientIdPtr &client_id, bool &extracted)
Extract the duid from a RFC 4361 compliant DHCPv4 client ID.
string canonize(const string &hexdump)
Canonize hardware address textual representation.
const isc::log::MessageID RADIUS_ACCESS_BUILD_FAILED
boost::shared_ptr< Attributes > AttributesPtr
Shared pointers to attribute collection.
const isc::log::MessageID RADIUS_ACCESS_RESUME_PARKED_QUERY
boost::shared_ptr< const Attribute > ConstAttributePtr
const isc::log::MessageID RADIUS_ACCESS_TERMINATE_ERROR
boost::shared_ptr< RadiusAuthPendingRequest< PktPtrType > > RadiusAuthPendingRequestPtr
Pointer to a pending Radius access request.
string exchangeRCtoText(const int rc)
ExchangeRC value -> name function.
const isc::log::MessageID RADIUS_ACCESS_CACHE_INSERT
const isc::log::MessageID RADIUS_ACCESS_GET_IDENTIFIER_FAILED
const int RADIUS_DBG_TRACE
Radius logging levels.
Definition radius_log.h:26
boost::shared_ptr< RadiusAuthStatus > RadiusAuthStatusPtr
Pointer to access status.
string toPrintable(const vector< uint8_t > &content)
Return printable textual representation of a vector.
const isc::log::MessageID RADIUS_ACCESS_ORPHAN
const isc::log::MessageID RADIUS_ACCESS_ERROR
const isc::log::MessageID RADIUS_ACCESS_DROP_PARKED_QUERY
std::function< void(int, AttributesPtr)> CallbackAuth
Type of callback for authentication termination.
boost::shared_ptr< RadiusAuthHandler > RadiusAuthHandlerPtr
Type of pointers to Radius access communication handler.
isc::log::Logger radius_logger("radius-hooks")
Radius Logger.
Definition radius_log.h:35
vector< uint8_t > pop0(const ClientIdPtr &client_id)
Pop leading zero in a DHCPv4 client-id.
const isc::log::MessageID RADIUS_ACCESS_GET_IDENTIFIER
string dumpAsHex(const uint8_t *data, size_t length)
Dumps a buffer of bytes as a string of hexadecimal digits.
Definition str.cc:330
Defines the logger used by the top-level component of kea-lfc.
@ TYPE_V4
IPv4 lease.
Definition lease.h:50
@ TYPE_NA
the lease contains non-temporary IPv6 address
Definition lease.h:47
Subnet selector used to specify parameters used to select a subnet.
RAII lock object to protect the code in the same scope with a mutex.