Kea 3.3.3
dhcp4/client_handler.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
10#include <dhcp4/dhcp4_log.h>
12#include <stats/stats_mgr.h>
14#include <util/str.h>
15
16using namespace std;
17using namespace isc::util;
18using namespace isc::log;
19
20namespace isc {
21namespace dhcp {
22
23namespace {
24
25string summary(ClientHandler::ClientPtr holder, bool byhw) {
26 if (!holder) {
27 return ("null");
28 }
29 stringstream tmp;
30 uint8_t msg_type = holder->msg_type_;
31 tmp << "msg_type=" << Pkt4::getName(msg_type)
32 << " (" << static_cast<int>(msg_type) << ")"
33 << ", trans_id=0x" << hex << holder->transid_ << dec;
34 if (!byhw && !holder->hwaddr_.empty()) {
35 tmp << ", hwaddr=" << str::dumpAsHex(holder->hwaddr_);
36 } else if (byhw && !holder->client_id_.empty()) {
37 tmp << ", client_id=" << str::dumpAsHex(holder->client_id_);
38 }
39 return (tmp.str());
40}
41
42} // end of anonymous namespace.
43
45 HWAddrPtr hwaddr)
46 : thread_(this_thread::get_id()) {
47 // Sanity checks.
48 if (!query) {
49 isc_throw(InvalidParameter, "null query in ClientHandler");
50 }
51 if (!client_id && (!hwaddr || hwaddr->hwaddr_.empty())) {
52 isc_throw(InvalidParameter,
53 "null client-id and hwaddr in ClientHandler");
54 }
55
56 if (client_id) {
57 client_id_ = client_id->getClientId();
58 }
59 if (hwaddr && !hwaddr->hwaddr_.empty()) {
60 htype_ = hwaddr->htype_;
61 hwaddr_ = hwaddr->hwaddr_;
62 }
63 msg_type_ = query->getType();
64 transid_ = query->getTransid();
65}
66
67mutex ClientHandler::mutex_;
68
69ClientHandler::ClientByIdContainer ClientHandler::clients_client_id_;
70
71ClientHandler::ClientByHWAddrContainer ClientHandler::clients_hwaddr_;
72
74ClientHandler::lookup(const ClientIdPtr& client_id) {
75 // Sanity check.
76 if (!client_id) {
77 isc_throw(InvalidParameter, "null client-id in ClientHandler::lookup");
78 }
79
80 auto it = clients_client_id_.find(client_id->getClientId());
81 if (it == clients_client_id_.end()) {
82 return (ClientPtr());
83 }
84 return (*it);
85}
86
88ClientHandler::lookup(const HWAddrPtr& hwaddr) {
89 // Sanity checks.
90 if (!hwaddr) {
91 isc_throw(InvalidParameter, "null hwaddr in ClientHandler::lookup");
92 }
93 if (hwaddr->hwaddr_.empty()) {
94 isc_throw(InvalidParameter, "empty hwaddr in ClientHandler::lookup");
95 }
96
97 auto key = boost::make_tuple(hwaddr->htype_, hwaddr->hwaddr_);
98 auto it = clients_hwaddr_.find(key);
99 if (it == clients_hwaddr_.end()) {
100 return (ClientPtr());
101 }
102 return (*it);
103}
104
105void
106ClientHandler::addById(const ClientPtr& client) {
107 // Sanity check.
108 if (!client) {
109 isc_throw(InvalidParameter, "null client in ClientHandler::addById");
110 }
111
112 // Assume insert will never fail so not checking its result.
113 clients_client_id_.insert(client);
114}
115
116void
117ClientHandler::addByHWAddr(const ClientPtr& client) {
118 // Sanity check.
119 if (!client) {
120 isc_throw(InvalidParameter,
121 "null client in ClientHandler::addByHWAddr");
122 }
123
124 // Assume insert will never fail so not checking its result.
125 clients_hwaddr_.insert(client);
126}
127
128void
129ClientHandler::del(const ClientIdPtr& client_id) {
130 // Sanity check.
131 if (!client_id) {
132 isc_throw(InvalidParameter, "null client id in ClientHandler::del");
133 }
134
135 // Assume erase will never fail so not checking its result.
136 clients_client_id_.erase(client_id->getClientId());
137}
138
139void
140ClientHandler::del(const HWAddrPtr& hwaddr) {
141 // Sanity checks.
142 if (!hwaddr) {
143 isc_throw(InvalidParameter, "null hwaddr in ClientHandler::del");
144 }
145 if (hwaddr->hwaddr_.empty()) {
146 isc_throw(InvalidParameter, "empty hwaddr in ClientHandler::del");
147 }
148
149 auto key = boost::make_tuple(hwaddr->htype_, hwaddr->hwaddr_);
150 // Assume erase will never fail so not checking its result.
151 auto it = clients_hwaddr_.find(key);
152 if (it == clients_hwaddr_.end()) {
153 // Should not happen.
154 return;
155 }
156 clients_hwaddr_.erase(it);
157}
158
160 : client_(), locked_client_id_(), locked_hwaddr_() {
161}
162
164 bool unlocked = false;
165 lock_guard<mutex> lk(mutex_);
166 if (locked_client_id_) {
167 unlocked = true;
168 unLockById();
169 }
170 if (locked_hwaddr_) {
171 unlocked = true;
172 unLockByHWAddr();
173 }
174 if (!unlocked || !client_ || !client_->cont_) {
175 return;
176 }
177 // Try to process next query. As the caller holds the mutex of
178 // the handler class the continuation will be resumed after.
180 if (mt_mgr.getMode()) {
181 if (!mt_mgr.getThreadPool().addFront(client_->cont_)) {
183 }
184 }
185}
186
187bool
189 // Sanity checks.
190 if (!query) {
191 isc_throw(InvalidParameter, "null query in ClientHandler::tryLock");
192 }
193 if (locked_client_id_) {
195 "already handling client-id in ClientHandler::tryLock");
196 }
197 if (locked_hwaddr_) {
199 "already handling hwaddr in ClientHandler::tryLock");
200 }
201
202 // Get identifiers.
203 OptionPtr opt_client_id = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
204 ClientIdPtr client_id;
205 if (opt_client_id) {
206 client_id.reset(new ClientId(opt_client_id->getData()));
207 }
208 HWAddrPtr hwaddr = query->getHWAddr();
209 if (hwaddr && hwaddr->hwaddr_.empty()) {
210 hwaddr.reset();
211 }
212 if (!client_id && !hwaddr) {
213 // Can't do something useful: cross fingers.
214 return (true);
215 }
216 client_.reset(new Client(query, client_id, hwaddr));
217
218 ClientPtr holder_id;
219 ClientPtr holder_hw;
220 Pkt4Ptr next_query_id;
221 Pkt4Ptr next_query_hw;
222 {
223 lock_guard<mutex> lk(mutex_);
224 // Try first client id.
225 if (client_id) {
226 // Try to acquire the by-client-id lock and return the holder
227 // when it failed.
228 holder_id = lookup(client_id);
229 if (!holder_id) {
230 locked_client_id_ = client_id;
231 lockById();
232 } else if (cont) {
233 next_query_id = holder_id->next_query_;
234 holder_id->next_query_ = query;
235 holder_id->cont_ = cont;
236 }
237 }
238 if (!holder_id) {
239 if (!hwaddr) {
240 return (true);
241 }
242 // Try to acquire the by-hw-addr lock and return the holder
243 // when it failed.
244 holder_hw = lookup(hwaddr);
245 if (!holder_hw) {
246 locked_hwaddr_ = hwaddr;
247 lockByHWAddr();
248 return (true);
249 } else if (cont) {
250 next_query_hw = holder_hw->next_query_;
251 holder_hw->next_query_ = query;
252 holder_hw->cont_ = cont;
253 }
254 }
255 }
256
257 if (holder_id) {
258 // This query is a by-id duplicate so put the continuation.
259 if (cont) {
260 if (next_query_id) {
261 // Logging a warning as it is supposed to be a rare event
262 // with well behaving clients...
263 // We replaced the next query so a packet was dropped as duplicate.
265 .arg("client_id=" + client_id->toText())
266 .arg(next_query_id->toText())
267 .arg(this_thread::get_id())
268 .arg(summary(holder_id, false))
269 .arg(holder_id->thread_);
270 stats::StatsMgr::instance().addValue("pkt4-duplicate",
271 static_cast<int64_t>(1));
272 stats::StatsMgr::instance().addValue("pkt4-receive-drop",
273 static_cast<int64_t>(1));
274 }
275 } else {
276 // Logging a warning as it is supposed to be a rare event
277 // with well behaving clients...
278 // There is no cont so the query is dropped as duplicate.
280 .arg("client_id=" + client_id->toText())
281 .arg(query->toText())
282 .arg(this_thread::get_id())
283 .arg(summary(holder_id, false))
284 .arg(holder_id->thread_);
285 stats::StatsMgr::instance().addValue("pkt4-duplicate",
286 static_cast<int64_t>(1));
287 stats::StatsMgr::instance().addValue("pkt4-receive-drop",
288 static_cast<int64_t>(1));
289 }
290 } else {
291 // This query is a by-hw duplicate so put the continuation.
292 auto makeHWAddrLabel = [hwaddr]() {
293 ostringstream label;
294 label << "hwtype=" << static_cast<int>(hwaddr->htype_)
295 << " hwaddr=" << str::dumpAsHex(hwaddr->hwaddr_);
296 return (label.str());
297 };
298 if (cont) {
299 if (next_query_hw) {
300 // Logging a warning as it is supposed to be a rare event
301 // with well behaving clients...
302 // We replaced the next query so a packet was dropped as duplicate.
304 .arg(makeHWAddrLabel())
305 .arg(next_query_hw->toText())
306 .arg(this_thread::get_id())
307 .arg(summary(holder_hw, true))
308 .arg(holder_hw->thread_);
309 stats::StatsMgr::instance().addValue("pkt4-duplicate",
310 static_cast<int64_t>(1));
311 stats::StatsMgr::instance().addValue("pkt4-receive-drop",
312 static_cast<int64_t>(1));
313 }
314 } else {
315 // Logging a warning as it is supposed to be a rare event
316 // with well behaving clients...
317 // There is no cont so the query is dropped as duplicate.
319 .arg(makeHWAddrLabel())
320 .arg(query->toText())
321 .arg(this_thread::get_id())
322 .arg(summary(holder_hw, true))
323 .arg(holder_hw->thread_);
324 stats::StatsMgr::instance().addValue("pkt4-duplicate",
325 static_cast<int64_t>(1));
326 stats::StatsMgr::instance().addValue("pkt4-receive-drop",
327 static_cast<int64_t>(1));
328 }
329 }
330 return (false);
331}
332
333void
334ClientHandler::lockById() {
335 // Sanity check.
336 if (!locked_client_id_) {
337 isc_throw(Unexpected, "nothing to lock in ClientHandler::lockById");
338 }
339
340 addById(client_);
341}
342
343void
344ClientHandler::lockByHWAddr() {
345 // Sanity check.
346 if (!locked_hwaddr_) {
348 "nothing to lock in ClientHandler::lockByHWAddr");
349 }
350
351 addByHWAddr(client_);
352}
353
354void
355ClientHandler::unLockById() {
356 // Sanity check.
357 if (!locked_client_id_) {
358 isc_throw(Unexpected,
359 "nothing to unlock in ClientHandler::unLockById");
360 }
361
362 del(locked_client_id_);
363 locked_client_id_.reset();
364}
365
366void
367ClientHandler::unLockByHWAddr() {
368 // Sanity check.
369 if (!locked_hwaddr_) {
370 isc_throw(Unexpected,
371 "nothing to unlock in ClientHandler::unLockByHWAddr");
372 }
373
374 del(locked_hwaddr_);
375 locked_hwaddr_.reset();
376}
377
378} // namespace dhcp
379} // namespace isc
A generic exception that is thrown if a parameter given to a method or function is considered invalid...
A generic exception that is thrown when an unexpected error condition occurs.
virtual ~ClientHandler()
Destructor.
boost::shared_ptr< Client > ClientPtr
The type of shared pointers to clients.
bool tryLock(Pkt4Ptr query, ContinuationPtr cont=ContinuationPtr())
Tries to acquires a client.
Holds Client identifier or client IPv4 address.
Definition duid.h:222
const char * getName() const
Returns name of the DHCP message.
Definition pkt4.cc:359
static StatsMgr & instance()
Statistics Manager accessor method.
static MultiThreadingMgr & instance()
Returns a single instance of Multi Threading Manager.
ThreadPool< std::function< void()> > & getThreadPool()
Get the dhcp thread pool.
bool getMode() const
Get the multi-threading mode.
if(!(yy_init))
Definition d2_lexer.cc:1515
Contains declarations for loggers used by the DHCPv4 server component.
#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_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
const isc::log::MessageID DHCP4_PACKET_DROP_0011
@ DHO_DHCP_CLIENT_IDENTIFIER
Definition dhcp4.h:130
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition pkt4.h:556
boost::shared_ptr< HWAddr > HWAddrPtr
Shared pointer to a hardware address structure.
Definition hwaddr.h:166
const isc::log::MessageID DHCP4_PACKET_QUEUE_FULL
isc::log::Logger bad_packet4_logger(DHCP4_BAD_PACKET_LOGGER_NAME)
Logger for rejected packets.
Definition dhcp4_log.h:97
const int DBG_DHCP4_BASIC
Debug level used to trace basic operations within the code.
Definition dhcp4_log.h:33
boost::shared_ptr< ClientId > ClientIdPtr
Shared pointer to a Client ID.
Definition duid.h:216
boost::shared_ptr< Continuation > ContinuationPtr
Define the type of shared pointers to continuations.
isc::log::Logger dhcp4_logger(DHCP4_APP_LOGGER_NAME)
Base logger for DHCPv4 server.
Definition dhcp4_log.h:90
const isc::log::MessageID DHCP4_PACKET_DROP_0012
boost::shared_ptr< Option > OptionPtr
Definition option.h:38
const int DBGLVL_PKT_HANDLING
This debug level is reserved for logging the details of packet handling, such as dropping the packet ...
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.
Class (aka static) types, methods and members.
uint32_t transid_
Cached transaction ID.
std::vector< uint8_t > client_id_
Cached binary client ID.
Client(Pkt4Ptr query, ClientIdPtr client_id, HWAddrPtr hwaddr)
Constructor.
uint8_t msg_type_
Cached message type.
std::thread::id thread_
The ID of the thread processing the query.
bool addFront(const WorkItemPtr &item)
add a work item to the thread pool at front