Kea 3.1.1
radius.cc
Go to the documentation of this file.
1// Copyright (C) 2020-2025 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
10#include <dhcpsrv/cfgmgr.h>
11#include <dhcpsrv/host_mgr.h>
15#include <radius.h>
16#include <radius_access.h>
17#include <radius_accounting.h>
18#include <radius_log.h>
19#include <radius_parsers.h>
20
21#include <exception>
22
23using namespace std;
24using namespace isc;
25using namespace isc::asiolink;
26using namespace isc::data;
27using namespace isc::db;
28using namespace isc::dhcp;
29using namespace isc::util;
30
31namespace isc {
32namespace radius {
33
34std::atomic<bool> RadiusImpl::shutdown_(false);
35
38 return (*instancePtr());
39}
40
41const RadiusImplPtr&
43 static RadiusImplPtr impl(new RadiusImpl());
44 return (impl);
45}
46
51 deadtime_(0), extract_duid_(true),
54 id_type4_(Host::IDENT_CLIENT_ID), id_type6_(Host::IDENT_DUID),
55 io_context_(new IOService()), io_service_(io_context_) {
56}
57
59 try {
60 cleanup();
61 } catch(exception const& exception) {
63 .arg(exception.what());
64 }
65}
66
68 MultiThreadingLock lock(mutex_);
69 exchange_list_.push_back(exchange);
70}
71
73 MultiThreadingLock lock(mutex_);
74 exchange_list_.remove(exchange);
75}
76
81 timeout_ = 10;
82 retries_ = 3;
85 extract_duid_ = true;
86 clientid_printable_ = false;
87 clientid_pop0_ = false;
89 cache_.reset();
90 bindaddr_ = "*";
92 remap_.clear();
93
94 if (backend_) {
96 HostMgr::delBackend("radius");
97 backend_.reset();
98 }
99
100 auth_.reset(new RadiusAccess());
101 acct_.reset(new RadiusAccounting());
102
103 if (thread_pool_) {
105 thread_pool_->stop();
106 for (auto const& exchange : exchange_list_) {
107 exchange->shutdown();
108 }
109 thread_pool_->getIOService()->stopAndPoll();
110 thread_pool_.reset();
111 } else {
112 for (auto const& exchange : exchange_list_) {
113 exchange->shutdown();
114 }
115 }
116 io_context_.reset(new IOService());
117 exchange_list_.clear();
118 if (getIOService()) {
119 getIOService()->stopAndPoll();
120 }
121 io_service_ = io_context_;
122}
123
126 std::unique_ptr<void, void(*)(void*)> p(static_cast<void*>(this), [](void*) { RadiusImpl::shutdown_ = false; });
127 cleanup();
128}
129
131 auth_.reset(new RadiusAccess());
132 acct_.reset(new RadiusAccounting());
133 RadiusConfigParser parser;
134 parser.parse(config);
137 if (auth_->enabled_) {
140 isc_throw(Unexpected, "Configuring access failed: host cache library not loaded.");
141 return;
142 }
143 backend_.reset(new RadiusBackend());
144 auto radius_factory = [this](const DatabaseConnection::ParameterMap&) {
145 return (backend_);
146 };
147 HostDataSourceFactory::registerFactory("radius", radius_factory);
148 }
149 if (acct_->enabled_) {
151 }
152}
153
154void
156 // Check if Kea core is multi-threaded.
157 ConstElementPtr const& dhcp_config(
158 CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading());
159 bool multi_threaded(false);
160 uint32_t dhcp_threads(0);
161 uint32_t dummy_queue_size(0);
162 CfgMultiThreading::extract(dhcp_config, multi_threaded, dhcp_threads,
163 dummy_queue_size);
164
165 if (multi_threaded) {
166 // When threads are configured as zero, use the same number as DHCP
167 // threads. If that is also zero, auto-detect.
168 unsigned thread_pool_size(thread_pool_size_);
169 if (thread_pool_size_ == 0) {
170 if (dhcp_threads == 0) {
171 uint32_t const hardware_threads(
173 if (hardware_threads == 0) {
174 // Keep it single-threaded.
175 return;
176 } else {
177 thread_pool_size = hardware_threads;
178 }
179 } else {
180 thread_pool_size = dhcp_threads;
181 }
182 }
183
184 // Schedule a start of the services. This ensures we begin after
185 // the dust has settled and Kea MT mode has been firmly established.
186 io_service_->post([this, thread_pool_size]() {
187 // Initialize thread pool.
189 boost::make_shared<IoServiceThreadPool>(IOServicePtr(),
190 thread_pool_size);
191 io_context_ = thread_pool_->getIOService();
192
193 // Add critical section callbacks.
195 [this]() { checkPausePermissions(); },
196 [this]() { pauseThreadPool(); },
197 [this]() { resumeThreadPool(); });
198
199 // Run the thread pool.
200 thread_pool_->run();
201
203 .arg(thread_pool_size);
204 });
205 }
206}
207
208void
210 // Since this function is used as CS callback all exceptions must be
211 // suppressed, unlikely though they may be.
212 try {
213 if (thread_pool_) {
214 thread_pool_->checkPausePermissions();
215 }
216 } catch (const isc::MultiThreadingInvalidOperation& ex) {
218 .arg(ex.what());
219 // The exception needs to be propagated to the caller of the
220 // MultiThreadingCriticalSection constructor.
221 throw;
222 } catch (const exception& ex) {
224 .arg(ex.what());
225 }
226}
227
228void
230 // Since this function is used as CS callback all exceptions must be
231 // suppressed, unlikely though they may be.
232 try {
233 // Pause the thread pool.
234 if (thread_pool_) {
235 thread_pool_->pause();
236 }
237 } catch (const exception& ex) {
239 .arg(ex.what());
240 }
241}
242
243void
245 // Since this function is used as CS callback all exceptions must be
246 // suppressed, unlikely though they may be.
247 try {
248 if (thread_pool_) {
249 thread_pool_->run();
250 }
251 } catch (const exception& ex) {
253 .arg(ex.what());
254 }
255}
256
257namespace {
258
267bool isHostReservationModeGlobal(SubnetPtr subnet, NetworkPtr network) {
268 auto subnet_hr_global = subnet->getReservationsGlobal(Network::Inheritance::NONE);
269 auto subnet_hr_subnet = subnet->getReservationsInSubnet(Network::Inheritance::NONE);
270 if (!subnet_hr_global.unspecified() && !subnet_hr_subnet.unspecified()) {
271 return (subnet_hr_global && !subnet_hr_subnet);
272 }
273 if (!subnet_hr_global.unspecified() || !subnet_hr_subnet.unspecified()) {
274 return (false);
275 }
276 auto network_hr_global = network->getReservationsGlobal(Network::Inheritance::NONE);
277 auto network_hr_subnet = network->getReservationsInSubnet(Network::Inheritance::NONE);
278 if (!network_hr_global.unspecified() && !network_hr_subnet.unspecified()) {
279 return (network_hr_global && !network_hr_subnet);
280 }
281 if (!network_hr_global.unspecified() || !network_hr_subnet.unspecified()) {
282 return (false);
283 }
284 // Inherit from staging (vs current) config for globals.
285 auto global_hr_mode_elem = CfgMgr::instance().getStagingCfg()->
286 getConfiguredGlobal("reservations-global");
287 // Default reservations-global is false.
288 if (!global_hr_mode_elem) {
289 return (false);
290 }
291 auto subnet_hr_mode_elem = CfgMgr::instance().getStagingCfg()->
292 getConfiguredGlobal("reservations-in-subnet");
293 // Default reservations-in-subnet is true.
294 if (!subnet_hr_mode_elem) {
295 return (false);
296 }
297 if (global_hr_mode_elem->getType() != Element::boolean) {
298 isc_throw(Unexpected, "'reservations-global' global value must be a boolean");
299 }
300 if (!global_hr_mode_elem->boolValue()) {
301 return (false);
302 }
303 if (subnet_hr_mode_elem->getType() != Element::boolean) {
304 isc_throw(Unexpected, "'reservations-in-subnet' global value must be a boolean");
305 }
306 if (subnet_hr_mode_elem->boolValue()) {
307 return (false);
308 }
309 return (true);
310}
311
312} // end of anonymous namespace
313
315 auto flag = CfgMgr::instance().getStagingCfg()->
317 if (flag && (flag->boolValue())) {
318 isc_throw(ConfigError, "early-global-reservations-lookup is not "
319 "compatible with RADIUS");
320 }
321}
322
324 bool need_disable_single_query = false;
325 if (CfgMgr::instance().getFamily() == AF_INET) {
326 auto networks = CfgMgr::instance().getStagingCfg()->
327 getCfgSharedNetworks4()->getAll();
328 if (networks->empty()) {
329 return;
330 }
331 need_disable_single_query = true;
332 for (auto const& network : *networks) {
333 auto subnets = network->getAllSubnets();
334 if (subnets->size() <= 1) {
335 continue;
336 }
337 for (auto const& subnet : *subnets) {
338 if (!isHostReservationModeGlobal(subnet, network)) {
339 isc_throw(ConfigError, "subnet " << subnet->getID()
340 << " '" << subnet->toText() << "' of shared "
341 << "network " << network->getName()
342 << " does not use only global host reservations "
343 << "which are required for subnets in "
344 << "shared networks by RADIUS");
345 }
346 }
347 }
348 } else {
349 auto networks = CfgMgr::instance().getStagingCfg()->
350 getCfgSharedNetworks6()->getAll();
351 if (networks->empty()) {
352 return;
353 }
354 need_disable_single_query = true;
355 for (auto const& network : *networks) {
356 auto subnets = network->getAllSubnets();
357 if (subnets->size() <= 1) {
358 continue;
359 }
360 for (auto const& subnet : *subnets) {
361 if (!isHostReservationModeGlobal(subnet, network)) {
362 isc_throw(ConfigError, "subnet " << subnet->getID()
363 << " '" << subnet->toText() << "' of shared "
364 << "network " << network->getName()
365 << " does not use only global host reservations "
366 << "which are required for subnets in "
367 << "shared networks by RADIUS");
368 }
369 }
370 }
371 }
372 if (need_disable_single_query) {
374 }
375}
376
378 if (cache_) {
379 return (true);
380 }
381 // Try only once.
382 static bool already_tried = false;
383 if (already_tried) {
384 return (false);
385 }
386 already_tried = true;
387 // Add backends
388 try {
389 // createManagers can reset the host manager so re-add host cache.
390 // Note that init already checked the factory was registered.
391 if (!HostMgr::instance().getHostDataSource()) {
392 HostMgr::instance().addBackend("type=cache");
393 }
394 HostMgr::instance().addBackend("type=radius");
395 } catch (const std::exception& ex) {
397 .arg("radius")
398 .arg(ex.what());
399 return (false);
400 }
401 // Get a pointer to host cache backend
403 cache_ = boost::dynamic_pointer_cast<CacheHostDataSource>(cache);
404 if (!cache_) {
406 return (false);
407 }
408 return (true);
409}
410
413
414 // dictionary.
415 result->set("dictionary", Element::create(dictionary_));
416
417 // bindaddr.
418 result->set("bindaddr", Element::create(bindaddr_));
419
420 // canonical-mac-address.
421 result->set("canonical-mac-address",
423
424 // client-id-pop0.
425 result->set("client-id-pop0", Element::create(clientid_pop0_));
426
427 // client-id-printable.
428 result->set("client-id-printable", Element::create(clientid_printable_));
429
430 // deadtime.
431 result->set("deadtime", Element::create(deadtime_));
432
433 // extract-duid.
434 result->set("extract-duid", Element::create(extract_duid_));
435
436 // identifier-type4.
437 result->set("identifier-type4",
439
440 // identifier-type6.
441 result->set("identifier-type6",
443
444 // realm.
445
446 // reselect-subnet-address.
447 result->set("reselect-subnet-address",
449
450 // reselect-subnet-pool.
451 result->set("reselect-subnet-pool",
453
454 // retries.
455 result->set("retries", Element::create(retries_));
456
457 // session-history.
458 result->set("session-history", Element::create(session_history_filename_));
459
460 // thread-pool-size.
461 result->set("thread-pool-size", Element::create(thread_pool_size_));
462
463 // timeout.
464 result->set("timeout", Element::create(timeout_));
465
466 // services.
467 result->set("access", auth_->toElement());
468 result->set("accounting", acct_->toElement());
469
470 // NAS ports.
471 if (!remap_.empty()) {
473 for (auto const& item : remap_) {
475 if (item.first != 0) {
476 entry->set("subnet-id",
477 Element::create(static_cast<int64_t>(item.first)));
478 }
479 entry->set("port",
480 Element::create(static_cast<int64_t>(item.second)));
481 ports->add(entry);
482 }
483 result->set("nas-ports", ports);
484 }
485
486 return (result);
487}
488
489unordered_set<thread::id> InHook::set_;
490
491mutex InHook::mutex_;
492
494 const auto& id = this_thread::get_id();
495 MultiThreadingLock lock(mutex_);
496 auto ret = set_.insert(id);
497 if (!ret.second) {
498 std::cerr << "InHook insert error on " << id << "\n";
499 }
500}
501
503 const auto& id = this_thread::get_id();
504 MultiThreadingLock lock(mutex_);
505 size_t ret = set_.erase(id);
506 if (ret != 1) {
507 std::cerr << "InHook erase error on " << id << "\n";
508 }
509}
510
512 const auto& id = this_thread::get_id();
513 MultiThreadingLock lock(mutex_);
514 size_t ret = set_.count(id);
515 return (ret == 1);
516}
517
518} // end of namespace isc::radius
519} // end of namespace isc
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition data.cc:249
@ boolean
Definition data.h:142
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:304
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:299
An exception that is thrown if an error occurs while configuring any server.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Exception thrown when a worker thread is trying to stop or pause the respective thread pool (which wo...
A generic exception that is thrown when an unexpected error condition occurs.
std::map< std::string, std::string > ParameterMap
Database configuration parameter map.
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition cfgmgr.cc:29
SrvConfigPtr getStagingCfg()
Returns a pointer to the staging configuration.
Definition cfgmgr.cc:121
static void extract(data::ConstElementPtr value, bool &enabled, uint32_t &thread_count, uint32_t &queue_size)
Extract multi-threading parameters from a given configuration.
static bool deregisterFactory(const std::string &db_type, bool no_log=false)
Deregister a host data source factory.
static bool registerFactory(const std::string &db_type, const Factory &factory, bool no_log=false, DBVersion db_version=DBVersion())
Register a host data source factory.
static bool registeredFactory(const std::string &db_type)
Check if a host data source factory was registered.
void setDisableSingleQuery(bool disable_single_query)
Sets the disable single query flag.
Definition host_mgr.h:802
static bool delBackend(const std::string &db_type)
Delete an alternate host backend (aka host data source).
Definition host_mgr.cc:62
static void addBackend(const std::string &access)
Add an alternate host backend (aka host data source).
Definition host_mgr.cc:57
HostDataSourcePtr getHostDataSource() const
Returns the first host data source.
Definition host_mgr.cc:84
static HostMgr & instance()
Returns a sole instance of the HostMgr.
Definition host_mgr.cc:114
Represents a device with IPv4 and/or IPv6 reservations.
Definition host.h:327
@ IDENT_CLIENT_ID
Definition host.h:341
static std::string getIdentifierName(const IdentifierType &type)
Returns name of the identifier of a specified type.
Definition host.cc:349
~InHook()
Destructor.
Definition radius.cc:502
static bool check()
Check if the current thread is in hook code or not.
Definition radius.cc:511
InHook()
Constructor.
Definition radius.cc:493
Radius access class.
Radius accounting class.
Host backend for Radius.
Configuration parser for Radius.
void parse(data::ElementPtr &config)
Parses Radius configuration.
Radius hooks library implementation.
Definition radius.h:50
static std::atomic< bool > shutdown_
Flag which indicates that the instance is shutting down.
Definition radius.h:211
unsigned thread_pool_size_
Thread pool size.
Definition radius.h:196
void resumeThreadPool()
Resume the thread pool.
Definition radius.cc:244
~RadiusImpl()
Destructor.
Definition radius.cc:58
void checkSharedNetworks()
Check shared network server configuration.
Definition radius.cc:323
std::string dictionary_
Dictionary path.
Definition radius.h:148
bool checkHostBackends()
Check host backends (cache and radius).
Definition radius.cc:377
RadiusImpl()
Protected constructor.
Definition radius.cc:47
dhcp::CacheHostDataSourcePtr cache_
Host cache.
Definition radius.h:160
std::string bindaddr_
bindaddr.
Definition radius.h:166
bool clientid_pop0_
Client Id pop leading zero(s).
Definition radius.h:172
dhcp::Host::IdentifierType id_type4_
Identifier type for IPv4.
Definition radius.h:202
void reset()
Reset the state as it was just created.
Definition radius.cc:124
void unregisterExchange(ExchangePtr exchange)
Unregister Exchange.
Definition radius.cc:72
void pauseThreadPool()
Pause the thread pool.
Definition radius.cc:229
bool reselect_subnet_address_
Reselect subnet using address.
Definition radius.h:187
void init(data::ElementPtr &config)
Initialize.
Definition radius.cc:130
void registerExchange(ExchangePtr exchange)
Register Exchange.
Definition radius.cc:67
boost::shared_ptr< RadiusAccess > auth_
Definition radius.h:154
bool extract_duid_
Extract Duid from Client Id.
Definition radius.h:181
void startServices()
Start the I/O mechanisms.
Definition radius.cc:155
unsigned timeout_
Timeout.
Definition radius.h:199
dhcp::Host::IdentifierType id_type6_
Identifier type for IPv6.
Definition radius.h:205
bool canonical_mac_address_
Canonical MAC address.
Definition radius.h:169
unsigned deadtime_
Deadtime.
Definition radius.h:178
RadiusBackendPtr backend_
Radius backend.
Definition radius.h:163
boost::shared_ptr< RadiusAccounting > acct_
Pointer to accounting (never null).
Definition radius.h:157
void cleanup()
Clean up members.
Definition radius.cc:77
asiolink::IoServiceThreadPoolPtr thread_pool_
Thread pool.
Definition radius.h:208
data::ElementPtr toElement() const override
Unparse implementation configuration.
Definition radius.cc:411
unsigned retries_
Retries.
Definition radius.h:190
std::map< uint32_t, uint32_t > remap_
Subnet ID to NAS port map.
Definition radius.h:151
static const RadiusImplPtr & instancePtr()
Returns pointer to the sole instance of radius implementation.
Definition radius.cc:42
std::string session_history_filename_
Session history filename.
Definition radius.h:193
bool reselect_subnet_pool_
Reselect subnet using pool.
Definition radius.h:184
void checkEarlyGlobalResvLookup()
Check the early global host reservations lookup flag.
Definition radius.cc:314
bool clientid_printable_
Client Id try printable.
Definition radius.h:175
isc::asiolink::IOServicePtr getIOService()
Get the hook I/O service.
Definition radius.h:121
static RadiusImpl & instance()
RadiusImpl is a singleton class.
Definition radius.cc:37
void checkPausePermissions()
Check if the current thread can transition the thread pool to the paused state.
Definition radius.cc:209
static MultiThreadingMgr & instance()
Returns a single instance of Multi Threading Manager.
void removeCriticalSectionCallbacks(const std::string &name)
Removes the set of callbacks associated with a given name from the list of CriticalSection callbacks.
static uint32_t detectThreadCount()
The system current detected hardware concurrency thread count.
void addCriticalSectionCallbacks(const std::string &name, const CSCallbackSet::Callback &check_cb, const CSCallbackSet::Callback &entry_cb, const CSCallbackSet::Callback &exit_cb)
Adds a set of callbacks to the list of CriticalSection callbacks.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition macros.h:26
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:29
boost::shared_ptr< Element > ElementPtr
Definition data.h:28
boost::shared_ptr< BaseHostDataSource > HostDataSourcePtr
HostDataSource pointer.
boost::shared_ptr< Subnet > SubnetPtr
A generic pointer to either Subnet4 or Subnet6 object.
Definition subnet.h:449
boost::shared_ptr< Network > NetworkPtr
Pointer to the Network object.
Definition network.h:73
const isc::log::MessageID RADIUS_ACCESS_HOST_BACKEND_ERROR
const isc::log::MessageID RADIUS_RESUME_FAILED
boost::shared_ptr< RadiusImpl > RadiusImplPtr
Definition radius.h:47
boost::shared_ptr< Exchange > ExchangePtr
Type of shared pointers to RADIUS exchange object.
const isc::log::MessageID RADIUS_CLEANUP_EXCEPTION
const isc::log::MessageID RADIUS_PAUSE_FAILED
const isc::log::MessageID RADIUS_ACCESS_NO_HOST_CACHE
isc::log::Logger radius_logger("radius-hooks")
Radius Logger.
Definition radius_log.h:35
const isc::log::MessageID RADIUS_THREAD_POOL_STARTED
const isc::log::MessageID RADIUS_PAUSE_ILLEGAL
const isc::log::MessageID RADIUS_PAUSE_PERMISSIONS_FAILED
Defines the logger used by the top-level component of kea-lfc.
RAII lock object to protect the code in the same scope with a mutex.