Coverage Report

Created: 2025-11-16 07:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/kea/src/bin/dhcp4/client_handler.cc
Line
Count
Source
1
// Copyright (C) 2020-2023 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 <dhcp4/client_handler.h>
10
#include <dhcp4/dhcp4_log.h>
11
#include <exceptions/exceptions.h>
12
#include <stats/stats_mgr.h>
13
#include <util/multi_threading_mgr.h>
14
15
using namespace std;
16
using namespace isc::util;
17
using namespace isc::log;
18
19
namespace isc {
20
namespace dhcp {
21
22
ClientHandler::Client::Client(Pkt4Ptr query, ClientIdPtr client_id,
23
                              HWAddrPtr hwaddr)
24
0
    : query_(query), htype_(HTYPE_ETHER), thread_(this_thread::get_id()) {
25
    // Sanity checks.
26
0
    if (!query) {
27
0
        isc_throw(InvalidParameter, "null query in ClientHandler");
28
0
    }
29
0
    if (!client_id && (!hwaddr || hwaddr->hwaddr_.empty())) {
30
0
        isc_throw(InvalidParameter,
31
0
                  "null client-id and hwaddr in ClientHandler");
32
0
    }
33
34
0
    if (client_id) {
35
0
        client_id_ = client_id->getClientId();
36
0
    }
37
0
    if (hwaddr && !hwaddr->hwaddr_.empty()) {
38
0
        htype_ = hwaddr->htype_;
39
0
        hwaddr_ = hwaddr->hwaddr_;
40
0
    }
41
0
}
42
43
mutex ClientHandler::mutex_;
44
45
ClientHandler::ClientByIdContainer ClientHandler::clients_client_id_;
46
47
ClientHandler::ClientByHWAddrContainer ClientHandler::clients_hwaddr_;
48
49
ClientHandler::ClientPtr
50
0
ClientHandler::lookup(const ClientIdPtr& client_id) {
51
    // Sanity check.
52
0
    if (!client_id) {
53
0
        isc_throw(InvalidParameter, "null client-id in ClientHandler::lookup");
54
0
    }
55
56
0
    auto it = clients_client_id_.find(client_id->getClientId());
57
0
    if (it == clients_client_id_.end()) {
58
0
        return (ClientPtr());
59
0
    }
60
0
    return (*it);
61
0
}
62
63
ClientHandler::ClientPtr
64
0
ClientHandler::lookup(const HWAddrPtr& hwaddr) {
65
    // Sanity checks.
66
0
    if (!hwaddr) {
67
0
        isc_throw(InvalidParameter, "null hwaddr in ClientHandler::lookup");
68
0
    }
69
0
    if (hwaddr->hwaddr_.empty()) {
70
0
        isc_throw(InvalidParameter, "empty hwaddr in ClientHandler::lookup");
71
0
    }
72
73
0
    auto key = boost::make_tuple(hwaddr->htype_, hwaddr->hwaddr_);
74
0
    auto it = clients_hwaddr_.find(key);
75
0
    if (it == clients_hwaddr_.end()) {
76
0
        return (ClientPtr());
77
0
    }
78
0
    return (*it);
79
0
}
80
81
void
82
0
ClientHandler::addById(const ClientPtr& client) {
83
    // Sanity check.
84
0
    if (!client) {
85
0
        isc_throw(InvalidParameter, "null client in ClientHandler::addById");
86
0
    }
87
88
    // Assume insert will never fail so not checking its result.
89
0
    clients_client_id_.insert(client);
90
0
}
91
92
void
93
0
ClientHandler::addByHWAddr(const ClientPtr& client) {
94
    // Sanity check.
95
0
    if (!client) {
96
0
        isc_throw(InvalidParameter,
97
0
                  "null client in ClientHandler::addByHWAddr");
98
0
    }
99
100
    // Assume insert will never fail so not checking its result.
101
0
    clients_hwaddr_.insert(client);
102
0
}
103
104
void
105
0
ClientHandler::del(const ClientIdPtr& client_id) {
106
    // Sanity check.
107
0
    if (!client_id) {
108
0
        isc_throw(InvalidParameter, "null duid in ClientHandler::del");
109
0
    }
110
111
    // Assume erase will never fail so not checking its result.
112
0
    clients_client_id_.erase(client_id->getClientId());
113
0
}
114
115
void
116
0
ClientHandler::del(const HWAddrPtr& hwaddr) {
117
    // Sanity checks.
118
0
    if (!hwaddr) {
119
0
        isc_throw(InvalidParameter, "null hwaddr in ClientHandler::del");
120
0
    }
121
0
    if (hwaddr->hwaddr_.empty()) {
122
0
        isc_throw(InvalidParameter, "empty hwaddr in ClientHandler::del");
123
0
    }
124
125
0
    auto key = boost::make_tuple(hwaddr->htype_, hwaddr->hwaddr_);
126
    // Assume erase will never fail so not checking its result.
127
0
    auto it = clients_hwaddr_.find(key);
128
0
    if (it == clients_hwaddr_.end()) {
129
        // Should not happen.
130
0
        return;
131
0
    }
132
0
    clients_hwaddr_.erase(it);
133
0
}
134
135
ClientHandler::ClientHandler()
136
1.00k
    : client_(), locked_client_id_(), locked_hwaddr_() {
137
1.00k
}
138
139
ClientHandler::~ClientHandler() {
140
    bool unlocked = false;
141
    lock_guard<mutex> lk(mutex_);
142
    if (locked_client_id_) {
143
        unlocked = true;
144
        unLockById();
145
    }
146
    if (locked_hwaddr_) {
147
        unlocked = true;
148
        unLockByHWAddr();
149
    }
150
    if (!unlocked || !client_ || !client_->cont_) {
151
        return;
152
    }
153
    // Try to process next query. As the caller holds the mutex of
154
    // the handler class the continuation will be resumed after.
155
    MultiThreadingMgr& mt_mgr = MultiThreadingMgr::instance();
156
    if (mt_mgr.getMode()) {
157
        if (!mt_mgr.getThreadPool().addFront(client_->cont_)) {
158
            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_QUEUE_FULL);
159
        }
160
    }
161
}
162
163
bool
164
0
ClientHandler::tryLock(Pkt4Ptr query, ContinuationPtr cont) {
165
    // Sanity checks.
166
0
    if (!query) {
167
0
        isc_throw(InvalidParameter, "null query in ClientHandler::tryLock");
168
0
    }
169
0
    if (locked_client_id_) {
170
0
        isc_throw(Unexpected,
171
0
                  "already handling client-id in ClientHandler::tryLock");
172
0
    }
173
0
    if (locked_hwaddr_) {
174
0
        isc_throw(Unexpected,
175
0
                  "already handling hwaddr in ClientHandler::tryLock");
176
0
    }
177
178
    // Get identifiers.
179
0
    OptionPtr opt_client_id = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
180
0
    ClientIdPtr client_id;
181
0
    if (opt_client_id) {
182
0
        client_id.reset(new ClientId(opt_client_id->getData()));
183
0
    }
184
0
    HWAddrPtr hwaddr = query->getHWAddr();
185
0
    if (hwaddr && hwaddr->hwaddr_.empty()) {
186
0
        hwaddr.reset();
187
0
    }
188
0
    if (!client_id && !hwaddr) {
189
        // Can't do something useful: cross fingers.
190
0
        return (true);
191
0
    }
192
193
0
    ClientPtr holder_id;
194
0
    ClientPtr holder_hw;
195
0
    Pkt4Ptr next_query_id;
196
0
    Pkt4Ptr next_query_hw;
197
0
    client_.reset(new Client(query, client_id, hwaddr));
198
199
0
    {
200
0
        lock_guard<mutex> lk(mutex_);
201
        // Try first duid.
202
0
        if (client_id) {
203
            // Try to acquire the by-client-id lock and return the holder
204
            // when it failed.
205
0
            holder_id = lookup(client_id);
206
0
            if (!holder_id) {
207
0
                locked_client_id_ = client_id;
208
0
                lockById();
209
0
            } else if (cont) {
210
0
                next_query_id = holder_id->next_query_;
211
0
                holder_id->next_query_ = query;
212
0
                holder_id->cont_ = cont;
213
0
            }
214
0
        }
215
0
        if (!holder_id) {
216
0
            if (!hwaddr) {
217
0
                return (true);
218
0
            }
219
            // Try to acquire the by-hw-addr lock and return the holder
220
            // when it failed.
221
0
            holder_hw = lookup(hwaddr);
222
0
            if (!holder_hw) {
223
0
                locked_hwaddr_ = hwaddr;
224
0
                lockByHWAddr();
225
0
                return (true);
226
0
            } else if (cont) {
227
0
                next_query_hw = holder_hw->next_query_;
228
0
                holder_hw->next_query_ = query;
229
0
                holder_hw->cont_ = cont;
230
0
            }
231
0
        }
232
0
    }
233
234
0
    if (holder_id) {
235
        // This query is a by-id duplicate so put the continuation.
236
0
        if (cont) {
237
0
            if (next_query_id) {
238
                // Logging a warning as it is supposed to be a rare event
239
                // with well behaving clients...
240
0
                LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0011)
241
0
                    .arg(next_query_id->getHWAddrLabel())
242
0
                    .arg(next_query_id->toText())
243
0
                    .arg(this_thread::get_id())
244
0
                    .arg(holder_id->query_->getHWAddrLabel())
245
0
                    .arg(holder_id->query_->toText())
246
0
                    .arg(holder_id->thread_);
247
0
                stats::StatsMgr::instance().addValue("pkt4-queue-full",
248
0
                                                     static_cast<int64_t>(1));
249
0
                stats::StatsMgr::instance().addValue("pkt4-receive-drop",
250
0
                                                     static_cast<int64_t>(1));
251
0
            }
252
0
        } else {
253
            // Logging a warning as it is supposed to be a rare event
254
            // with well behaving clients...
255
0
            LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0011)
256
0
                .arg(query->getHWAddrLabel())
257
0
                .arg(query->toText())
258
0
                .arg(this_thread::get_id())
259
0
                .arg(holder_id->query_->getHWAddrLabel())
260
0
                .arg(holder_id->query_->toText())
261
0
                .arg(holder_id->thread_);
262
0
            stats::StatsMgr::instance().addValue("pkt4-queue-full",
263
0
                                                 static_cast<int64_t>(1));
264
0
            stats::StatsMgr::instance().addValue("pkt4-receive-drop",
265
0
                                                 static_cast<int64_t>(1));
266
0
        }
267
0
    } else {
268
        // This query is a by-hw duplicate so put the continuation.
269
0
        if (cont) {
270
0
            if (next_query_hw) {
271
                // Logging a warning as it is supposed to be a rare event
272
                // with well behaving clients...
273
0
                LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0012)
274
0
                    .arg(next_query_hw->getHWAddrLabel())
275
0
                    .arg(next_query_hw->toText())
276
0
                    .arg(this_thread::get_id())
277
0
                    .arg(holder_hw->query_->getHWAddrLabel())
278
0
                    .arg(holder_hw->query_->toText())
279
0
                    .arg(holder_hw->thread_);
280
0
                stats::StatsMgr::instance().addValue("pkt4-queue-full",
281
0
                                                     static_cast<int64_t>(1));
282
0
                stats::StatsMgr::instance().addValue("pkt4-receive-drop",
283
0
                                                     static_cast<int64_t>(1));
284
0
            }
285
0
        } else {
286
            // Logging a warning as it is supposed to be a rare event
287
            // with well behaving clients...
288
0
            LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0012)
289
0
                .arg(query->getHWAddrLabel())
290
0
                .arg(query->toText())
291
0
                .arg(this_thread::get_id())
292
0
                .arg(holder_hw->query_->getHWAddrLabel())
293
0
                .arg(holder_hw->query_->toText())
294
0
                .arg(holder_hw->thread_);
295
0
            stats::StatsMgr::instance().addValue("pkt4-queue-full",
296
0
                                                 static_cast<int64_t>(1));
297
0
            stats::StatsMgr::instance().addValue("pkt4-receive-drop",
298
0
                                                 static_cast<int64_t>(1));
299
0
        }
300
0
    }
301
0
    return (false);
302
0
}
303
304
void
305
0
ClientHandler::lockById() {
306
    // Sanity check.
307
0
    if (!locked_client_id_) {
308
0
        isc_throw(Unexpected, "nothing to lock in ClientHandler::lockById");
309
0
    }
310
311
0
    addById(client_);
312
0
}
313
314
void
315
0
ClientHandler::lockByHWAddr() {
316
    // Sanity check.
317
0
    if (!locked_hwaddr_) {
318
0
        isc_throw(Unexpected,
319
0
                  "nothing to lock in ClientHandler::lockByHWAddr");
320
0
    }
321
322
0
    addByHWAddr(client_);
323
0
}
324
325
void
326
0
ClientHandler::unLockById() {
327
    // Sanity check.
328
0
    if (!locked_client_id_) {
329
0
        isc_throw(Unexpected,
330
0
                  "nothing to unlock in ClientHandler::unLockById");
331
0
    }
332
333
0
    del(locked_client_id_);
334
0
    locked_client_id_.reset();
335
0
}
336
337
void
338
0
ClientHandler::unLockByHWAddr() {
339
    // Sanity check.
340
0
    if (!locked_hwaddr_) {
341
0
        isc_throw(Unexpected,
342
0
                  "nothing to unlock in ClientHandler::unLockByHWAddr");
343
0
    }
344
345
0
    del(locked_hwaddr_);
346
0
    locked_hwaddr_.reset();
347
0
}
348
349
}  // namespace dhcp
350
}  // namespace isc