Kea 3.1.1
client_exchange.cc
Go to the documentation of this file.
1// Copyright (C) 2023-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 <asiolink/io_service.h>
12#include <asiolink/udp_socket.h>
15#include <util/unlock_guard.h>
16#include <client_exchange.h>
17#include <radius.h>
18#include <radius_log.h>
19
20#include <cerrno>
21#include <chrono>
22#include <limits>
23#include <sstream>
24
25using namespace isc;
26using namespace isc::asiolink;
27using namespace isc::data;
28using namespace isc::util;
29using namespace std;
30using namespace std::chrono;
31namespace ph = std::placeholders;
32
33namespace isc {
34namespace radius {
35
36string
37exchangeRCtoText(const int rc) {
38 ostringstream result;
39 switch (rc) {
40 case BADRESP_RC:
41 return ("bad response");
42 case ERROR_RC:
43 return ("error");
44 case OK_RC:
45 return ("ok");
46 case TIMEOUT_RC:
47 return ("timeout");
48 case REJECT_RC:
49 return ("reject");
50 case READBLOCK_RC:
51 return ("temporarily unavailable");
52 default:
53 result << (rc < 0 ? "error " : "unknown ") << rc;
54 return (result.str());
55 }
56}
57
59 const MessagePtr& request,
60 unsigned maxretries,
61 const Servers& servers,
62 Handler handler)
63 : identifier_(""), io_service_(io_service), sync_(false),
64 started_(false), terminated_(false), rc_(ERROR_RC),
65 start_time_(std::chrono::steady_clock().now()),
66 socket_(), ep_(), timer_(), server_(), idx_(0),
67 request_(request), sent_(), received_(), buffer_(), size_(0),
68 retries_(0), maxretries_(maxretries), servers_(servers),
69 postponed_(), handler_(handler), mutex_(new std::mutex()) {
70 if (!io_service) {
71 isc_throw(BadValue, "null IO service");
72 }
73 if (!request) {
74 isc_throw(BadValue, "null request");
75 }
76 if (servers.empty()) {
77 isc_throw(BadValue, "no server");
78 }
79 if (!handler) {
80 isc_throw(BadValue, "null handler");
81 }
83}
84
86 unsigned maxretries,
87 const Servers& servers)
88 : identifier_(""), io_service_(new IOService()), sync_(true),
89 started_(false), terminated_(false), rc_(ERROR_RC),
90 start_time_(std::chrono::steady_clock().now()),
91 socket_(), ep_(), timer_(), server_(), idx_(0),
92 request_(request), sent_(), received_(), buffer_(), size_(0),
93 retries_(0), maxretries_(maxretries), servers_(servers), postponed_(),
94 handler_(), mutex_(new std::mutex()) {
95
96 if (!request) {
97 isc_throw(BadValue, "null request");
98 }
99 if (servers.empty()) {
100 isc_throw(BadValue, "no server");
101 }
103}
104
108 timer_.reset();
109 socket_.reset();
110 if (sync_ && io_service_) {
111 // As a best practice, call any remaining handlers.
112 io_service_->stopAndPoll();
113 io_service_.reset();
114 }
115}
116
117void
119 vector<uint8_t> rv = cryptolink::random(sizeof(uint32_t));
120 if (rv.size() != sizeof(uint32_t)) {
121 isc_throw(Unexpected, "random failed");
122 }
123 uint32_t ri;
124 memmove(&ri, &rv[0], sizeof(uint32_t));
125 ostringstream rs;
126 rs << hex << setfill('0') << setw(8) << ri;
127 identifier_ = rs.str();
128}
129
130void
132 if (!received_) {
133 return;
134 }
135 const AttributesPtr& attrs = received_->getAttributes();
136 if (!attrs || (attrs->count(PW_REPLY_MESSAGE) == 0)) {
137 return;
138 }
139 for (const ConstAttributePtr& attr : *attrs) {
140 if (!attr || (attr->getType() != PW_REPLY_MESSAGE)) {
141 continue;
142 }
144 .arg(static_cast<int>(received_->getIdentifier()))
145 .arg(identifier_)
146 .arg(attr->toString());
147 }
148}
149
150void
153
154 if (started_) {
155 return;
156 } else {
157 started_ = true;
158 }
160 .arg(identifier_);
161
162 open();
163
164 if (sync_) {
165 // Run() will return when syncHandler will be called.
166 io_service_->run();
167
168 // Done.
169 io_service_.reset();
170
172 .arg(identifier_)
173 .arg(rc_);
174 }
175}
176
177void
179 // Avoid multiple terminations.
182}
183
184void
186 if (terminated_) {
187 return;
188 } else {
189 terminated_ = true;
190 }
191 // Same as terminate but not calling callback.
192 cancelTimer();
193 if (socket_) {
194 socket_->cancel();
195 }
196 handler_ = Handler();
197
198 if (io_service_) {
199 if (sync_) {
200 io_service_->stopWork();
201 } else {
202 io_service_.reset();
203 }
204 }
205}
206
207void
209 if (!server_) {
210 isc_throw(Unexpected, "no server");
211 }
212
213 // Prepare message to send.
214 sent_.reset(new Message(*request_));
215
216 // Randomize the identifier.
217 sent_->randomIdentifier();
218
219 // Randomize or zero the authenticator.
220 if (sent_->getCode() == PW_ACCESS_REQUEST) {
221 sent_->randomAuth();
222 } else {
223 sent_->zeroAuth();
224 }
225
226 // Set the secret.
227 sent_->setSecret(server_->getSecret());
228
229 // Get attributes.
230 AttributesPtr attrs = sent_->getAttributes();
231 if (!attrs) {
232 attrs.reset(new Attributes());
233 sent_->setAttributes(attrs);
234 }
235
236 // Add Acct-Delay-Time to Accounting-Request message.
237 if ((sent_->getCode() == PW_ACCOUNTING_REQUEST) &&
238 (attrs->count(PW_ACCT_DELAY_TIME) == 0)) {
239 auto delta = steady_clock().now() - start_time_;
240 seconds secs = duration_cast<seconds>(delta);
242 static_cast<uint32_t>(secs.count())));
243 }
244
245 // Add NAS-IP[v6]-Address with the local address.
246 IOAddress local_addr = server_->getLocalAddress();
247 short family = local_addr.getFamily();
248 if (family == AF_INET) {
249 if (attrs->count(PW_NAS_IP_ADDRESS) == 0) {
250 attrs->add(Attribute::fromIpAddr(PW_NAS_IP_ADDRESS, local_addr));
251 }
252 } else if (family == AF_INET6) {
253 if (attrs->count(PW_NAS_IPV6_ADDRESS) == 0) {
255 local_addr));
256 }
257 }
258
259 // Encode the request.
260 sent_->encode();
261}
262
263void
267 return;
268 }
269
270 if (terminated_) {
271 return;
272 }
273 // In order:
274 // - no current server: open the next one.
275 // - last try.
276 // - next try.
277
278 if (!server_) {
279 // No server: get the next server.
280 if (idx_ < servers_.size()) {
281 // First pass.
283 // Null pointer (should not happen).
284 if (!server_) {
285 isc_throw(Unexpected, "null server at " << idx_);
286 }
287 // Server still in hold-down: postpone it.
288 if ((server_->getDeadtime() > 0) &&
289 (server_->getDeadtimeEnd() > start_time_)) {
290 postponed_.push_back(idx_);
291 ++idx_;
292 io_service_->post(std::bind(&Exchange::openNext,
293 shared_from_this()));
294 return;
295 }
296 } else {
297 // Second pass: try postponed servers.
298 if (postponed_.empty()) {
299 io_service_->post(std::bind(&Exchange::terminate,
300 shared_from_this()));
301 return;
302 }
303 size_t cur_idx = postponed_.front();
304 // Out-of-range (should not happen).
305 if (cur_idx >= servers_.size()) {
306 isc_throw(Unexpected, "out of range server " << cur_idx
307 << " >= " << servers_.size());
308 }
309 server_ = servers_[cur_idx];
310 // Null pointer (should not happen).
311 if (!server_) {
312 isc_throw(Unexpected, "null server at " << cur_idx);
313 }
314 }
315
316 // Have a new server.
317 try {
318 // Reset error code.
319 rc_ = ERROR_RC;
320
321 // Build to be send request message.
322 buildRequest();
323
324 // Set end-point.
325 ep_.reset(new UDPEndpoint(server_->getPeerAddress(),
326 server_->getPeerPort()));
327
328 // Set socket.
329 if (socket_) {
330 socket_->close();
331 }
332 socket_.reset(new RadiusSocket(io_service_));
333
334 // Launch timer.
335 setTimer();
336
337 // Open socket.
338 socket_->open(ep_.get(), SocketCallback());
339
340 // Should bind the socket but it is not (yet) in the API.
341 // Anyway the kernel should choose the same address...
342
343 // Better to use a connected socket...
344
345 // Send request message.
346 buffer_ = sent_->getBuffer();
347 size_ = buffer_.size();
348
351 .arg(identifier_)
352 .arg(buffer_.size())
353 .arg(idx_)
354 .arg(ep_->getAddress().toText())
355 .arg(ep_->getPort());
356
357 socket_->asyncSend(&buffer_[0], buffer_.size(), ep_.get(),
358 std::bind(&Exchange::sentHandler,
359 shared_from_this(),
360 ph::_1, // error_code.
361 ph::_2)); // size.
362 return;
363 } catch (const Exception& exc) {
365 .arg(identifier_)
366 .arg(exc.what());
367 cancelTimer();
368 rc_ = ERROR_RC;
369 if (socket_) {
370 socket_->close();
371 socket_.reset();
372 }
373 io_service_->post(std::bind(&Exchange::openNext,
374 shared_from_this()));
375 return;
376 }
377 }
378
379 // No other try?
380 if (retries_++ >= maxretries_) {
381 if ((rc_ == TIMEOUT_RC) && (idx_ < servers_.size())) {
382 // On timeout hold-down the server.
383 unsigned deadtime = server_->getDeadtime();
384 if (deadtime > 0) {
385 server_->setDeadtimeEnd(start_time_ + seconds(deadtime));
386 }
387 }
388 retries_ = 0;
389 server_.reset();
390 ep_.reset();
391 // Try postponed servers?
392 if (idx_ == servers_.size()) {
393 // Postponed servers are exhausted.
394 if (postponed_.size() < 2) {
395 io_service_->post(std::bind(&Exchange::terminate,
396 shared_from_this()));
397 return;
398 }
399 // Try next postponed server.
400 postponed_.pop_front();
401 } else {
402 // Try next server.
403 ++idx_;
404 if ((idx_ == servers_.size()) && (postponed_.empty())) {
405 io_service_->post(std::bind(&Exchange::terminate,
406 shared_from_this()));
407 return;
408 }
409 }
410 // Call again open to try the next server.
411 io_service_->post(std::bind(&Exchange::openNext,
412 shared_from_this()));
413 return;
414 }
415
416 // Next try.
417 try {
418 if (!ep_) {
419 isc_throw(Unexpected, "endpoint is null");
420 }
421
422 // Build to be send request message.
423 buildRequest();
424
425 // Set socket.
426 if (socket_) {
427 socket_->close();
428 }
429 socket_.reset(new RadiusSocket(io_service_));
430
431 // Launch timer.
432 setTimer();
433
434 // Open socket.
435 socket_->open(ep_.get(), SocketCallback());
436
437 // Should bind the socket but it is not (yet) in the API.
438 // Anyway the kernel should choose the same address...
439
440 // Better to use a connected socket...
441
442 // Send request message.
443 buffer_ = sent_->getBuffer();
444 size_ = buffer_.size();
445
447 .arg(identifier_)
448 .arg(buffer_.size())
449 .arg(retries_);
450
451 socket_->asyncSend(&buffer_[0],
452 buffer_.size(),
453 ep_.get(),
454 std::bind(&Exchange::sentHandler,
455 shared_from_this(),
456 ph::_1, // error_code.
457 ph::_2)); // size.
458 return;
459 } catch (const Exception& exc) {
461 .arg(identifier_)
462 .arg(exc.what());
463 cancelTimer();
464 rc_ = ERROR_RC;
465 if (socket_) {
466 socket_->close();
467 socket_.reset();
468 }
469 io_service_->post(std::bind(&Exchange::openNext,
470 shared_from_this()));
471 return;
472 }
473}
474
475void
477 const boost::system::error_code ec,
478 const size_t size) {
479 if (!ex) {
480 isc_throw(Unexpected, "null exchange in sentHandler");
481 }
482
484 ex->shutdown();
485 return;
486 }
487
488 MultiThreadingLock lock(*ex->mutex_);
489
490 if (ex->terminated_) {
491 return;
492 }
493
494 // Check error code.
495 if (ec) {
497 .arg(ex->identifier_)
498 .arg(ec.message());
499 ex->cancelTimer();
500 if (ex->socket_) {
501 ex->socket_->close();
502 ex->socket_.reset();
503 }
504 ex->io_service_->post(std::bind(&Exchange::openNext, ex));
505 return;
506 }
507
508 // No error: receive response.
510 .arg(ex->identifier_)
511 .arg(size);
512 ex->buffer_.clear();
513 ex->buffer_.resize(BUF_LEN);
514 ex->size_ = ex->buffer_.size();
515 ex->socket_->asyncReceive(&(ex->buffer_)[0], ex->size_, 0, ex->ep_.get(),
516 std::bind(&Exchange::receivedHandler, ex,
517 ph::_1, // error_code.
518 ph::_2)); // size.
519}
520
521void
523 const boost::system::error_code ec,
524 const size_t size) {
525 if (!ex) {
526 isc_throw(Unexpected, "null exchange in receivedHandler");
527 }
528
530 ex->shutdown();
531 return;
532 }
533
534 MultiThreadingLock lock(*ex->mutex_);
535
536 // This was the action on the socket.
537 ex->cancelTimer();
538 if (ex->socket_) {
539 ex->socket_->close();
540 ex->socket_.reset();
541 }
542
543 if (ex->terminated_) {
544 return;
545 }
546
547 // Check error code.
548 if (ec) {
550 .arg(ex->identifier_)
551 .arg(ec.message());
552 ex->io_service_->post(std::bind(&Exchange::openNext, ex));
553 return;
554 }
555
556 // Remove the server from hold-down.
557 if (ex->server_ &&
558 (ex->server_->getDeadtime() > 0) &&
559 (ex->server_->getDeadtimeEnd() > ex->start_time_)) {
560 ex->server_->setDeadtimeEnd(ex->start_time_);
561 }
562
563 // Create message.
565 .arg(ex->identifier_)
566 .arg(size);
567 ex->buffer_.resize(size);
568 ex->received_.reset(new Message(ex->buffer_, ex->sent_->getAuth(),
569 ex->server_->getSecret()));
570
571 // Decode message.
572 ex->rc_ = OK_RC;
573 try {
574 // In order:
575 // - decode message.
576 // - verify that identifiers match.
577 // - verify that message codes match.
578 ex->received_->decode();
579 unsigned got = ex->received_->getIdentifier();
580 unsigned expected = ex->sent_->getIdentifier();
581 if (got != expected) {
583 .arg(ex->identifier_)
584 .arg(got)
585 .arg(expected);
586 ex->rc_ = BADRESP_RC;
587 } else if (ex->request_->getCode() == PW_ACCESS_REQUEST) {
588 if (ex->received_->getCode() == PW_ACCESS_REJECT) {
591 .arg(ex->identifier_);
592 ex->rc_ = REJECT_RC;
593 } else if (ex->received_->getCode() != PW_ACCESS_ACCEPT) {
595 .arg(ex->identifier_)
596 .arg(msgCodeToText(ex->request_->getCode()))
597 .arg(msgCodeToText(ex->received_->getCode()));
598 ex->rc_ = BADRESP_RC;
599 } else {
602 .arg(ex->identifier_);
603 }
604 } else if (ex->request_->getCode() == PW_ACCOUNTING_REQUEST) {
605 if (ex->received_->getCode() != PW_ACCOUNTING_RESPONSE) {
607 .arg(ex->identifier_)
608 .arg(msgCodeToText(ex->request_->getCode()))
609 .arg(msgCodeToText(ex->received_->getCode()));
610 ex->rc_ = BADRESP_RC;
611 } else {
614 .arg(ex->identifier_);
615 }
616 }
617 } catch (const Exception& exc) {
619 .arg(ex->identifier_)
620 .arg(exc.what());
621 ex->rc_ = BADRESP_RC;
622 }
623
626 .arg(ex->identifier_)
627 .arg(exchangeRCtoText(ex->rc_));
628
629 // If bad then retry, if not including reject it is done.
630 if ((ex->rc_ != OK_RC) && ( ex->rc_ != REJECT_RC)) {
631 ex->io_service_->post(std::bind(&Exchange::openNext, ex));
632 } else {
633 ex->logReplyMessages();
634 ex->io_service_->post(std::bind(&Exchange::terminate, ex));
635 }
636}
637
638void
640 // Avoid multiple terminations.
642
643 if (terminated_) {
644 return;
645 } else {
646 terminated_ = true;
647 }
648
649 // Should have been done before.
650 cancelTimer();
651 if (socket_) {
652 socket_->close();
653 socket_.reset();
654 }
655
656 if ((rc_ != OK_RC) && (rc_ != REJECT_RC)) {
658 .arg(identifier_)
659 .arg(exchangeRCtoText(rc_));
660 } else {
662 .arg(identifier_)
663 .arg(exchangeRCtoText(rc_));
664 }
665
666 if (io_service_) {
667 if (sync_) {
668 io_service_->stopWork();
669 } else {
670 io_service_.reset();
671 }
672 }
673
674 // Call handler.
675 if (handler_) {
676 auto handler = handler_;
677 // Avoid to keep a circular reference.
678 handler_ = Handler();
679 if (MultiThreadingMgr::instance().getMode()) {
681 handler(shared_from_this());
682 } else {
683 handler(shared_from_this());
684 }
685 }
686}
687
688void
690 cancelTimer();
691 timer_.reset(new IntervalTimer(io_service_));
692 timer_->setup(std::bind(&Exchange::timeoutHandler, shared_from_this()),
693 server_->getTimeout() * 1000, IntervalTimer::ONE_SHOT);
694}
695
696void
698 if (timer_) {
699 timer_->cancel();
700 timer_.reset();
701 }
702}
703
704void
706 MultiThreadingLock lock(*ex->mutex_);
708 .arg(ex->identifier_);
709 ex->rc_ = TIMEOUT_RC;
710 ex->cancelTimer();
711 if (ex->socket_) {
712 ex->socket_->cancel();
713 }
714}
715
716} // end of namespace isc::radius
717} // end of namespace isc
if(!(yy_init))
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
This is a base class for exceptions thrown from the DNS library module.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown when an unexpected error condition occurs.
static AttributePtr fromInt(const uint8_t type, const uint32_t value)
From integer with type.
static AttributePtr fromIpAddr(const uint8_t type, const asiolink::IOAddress &value)
From IPv4 address with type.
static AttributePtr fromIpv6Addr(const uint8_t type, const asiolink::IOAddress &value)
From IPv6 address with type.
Collection of attributes.
std::list< size_t > postponed_
List of postponed server indexes.
asiolink::UDPSocket< const SocketCallback > RadiusSocket
Type of RADIUS UDP sockets.
Servers servers_
Servers (a copy which is what we need).
virtual ~Exchange()
Destructor.
virtual void start()
Start.
Handler handler_
Termination handler.
MessagePtr request_
Request message.
Exchange(const asiolink::IOServicePtr io_service, const MessagePtr &request, unsigned maxretries, const Servers &servers, Handler handler)
Constructor.
size_t size_
Number of transmitted octests;.
ServerPtr server_
Current server.
asiolink::IntervalTimerPtr timer_
Interval timer.
std::function< void(const ExchangePtr ex)> Handler
Termination handler.
bool sync_
Sync / async flag.
void buildRequest()
Build request.
static void sentHandler(ExchangePtr ex, const boost::system::error_code ec, const size_t size)
Sent handler.
static constexpr size_t BUF_LEN
Receive buffer size.
void createIdentifier()
Create identifier.
MessagePtr received_
Received message.
void terminate()
Terminate.
void setTimer()
Set timer.
boost::scoped_ptr< std::mutex > mutex_
State change mutex.
int rc_
Error/return code.
bool started_
Started flag.
boost::scoped_ptr< asiolink::UDPEndpoint > ep_
UDP endpoint.
static void openNext(ExchangePtr ex)
Class open / open next.
std::chrono::steady_clock::time_point start_time_
Start time.
static void receivedHandler(ExchangePtr ex, const boost::system::error_code ec, const size_t size)
Received handler.
std::string identifier_
The identifier (random value in hexadecimal).
boost::scoped_ptr< RadiusSocket > socket_
Socket.
std::function< void(const boost::system::error_code ec, const size_t size)> SocketCallback
Type of UDP socket callback functions.
MessagePtr sent_
Sent message.
bool terminated_
Terminated flag.
virtual void shutdown()
Shutdown.
void open()
Instance open.
virtual void shutdownInternal()
Shutdown.
asiolink::IOServicePtr io_service_
IO service (argument for async or internal for sync).
static void timeoutHandler(ExchangePtr ex)
Timeout handler.
std::vector< uint8_t > buffer_
Buffer.
void logReplyMessages() const
Log reply messages.
void cancelTimer()
Cancel timer.
size_t idx_
Current server index.
unsigned retries_
Retry counter.
unsigned maxretries_
Maximum number of retries for a server.
RADIUS Message.
static std::atomic< bool > shutdown_
Flag which indicates that the instance is shutting down.
Definition radius.h:211
static MultiThreadingMgr & instance()
Returns a single instance of Multi Threading Manager.
#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_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
const isc::log::MessageID RADIUS_EXCHANGE_RECEIVED_MISMATCH
const isc::log::MessageID RADIUS_EXCHANGE_OPEN_FAILED
boost::shared_ptr< Attributes > AttributesPtr
Shared pointers to attribute collection.
boost::shared_ptr< const Attribute > ConstAttributePtr
const isc::log::MessageID RADIUS_EXCHANGE_RECEIVED_ACCESS_REJECT
const isc::log::MessageID RADIUS_EXCHANGE_FAILED
const isc::log::MessageID RADIUS_EXCHANGE_SENT
string exchangeRCtoText(const int rc)
ExchangeRC value -> name function.
const isc::log::MessageID RADIUS_EXCHANGE_TIMEOUT
const isc::log::MessageID RADIUS_EXCHANGE_RECEIVED_ACCESS_ACCEPT
std::vector< ServerPtr > Servers
Type of RADIUS server collection.
const isc::log::MessageID RADIUS_EXCHANGE_RECEIVED
boost::shared_ptr< Exchange > ExchangePtr
Type of shared pointers to RADIUS exchange object.
string msgCodeToText(const uint8_t code)
MsgCode value -> name function.
const int RADIUS_DBG_TRACE
Radius logging levels.
Definition radius_log.h:26
const isc::log::MessageID RADIUS_EXCHANGE_RECEIVED_ACCOUNTING_RESPONSE
const isc::log::MessageID RADIUS_EXCHANGE_SEND_NEW
const isc::log::MessageID RADIUS_REPLY_MESSAGE_ATTRIBUTE
const isc::log::MessageID RADIUS_EXCHANGE_SEND_RETRY
isc::log::Logger radius_logger("radius-hooks")
Radius Logger.
Definition radius_log.h:35
boost::shared_ptr< Message > MessagePtr
Shared pointers to message.
const isc::log::MessageID RADIUS_EXCHANGE_RECEIVED_UNEXPECTED
const isc::log::MessageID RADIUS_EXCHANGE_RECEIVED_BAD_RESPONSE
const isc::log::MessageID RADIUS_EXCHANGE_SYNC_RETURN
const isc::log::MessageID RADIUS_EXCHANGE_RECEIVED_RESPONSE
const isc::log::MessageID RADIUS_EXCHANGE_START
const isc::log::MessageID RADIUS_EXCHANGE_SEND_FAILED
const isc::log::MessageID RADIUS_EXCHANGE_TERMINATE
const isc::log::MessageID RADIUS_EXCHANGE_RECEIVE_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.