Kea 3.1.1
callouts.cc
Go to the documentation of this file.
1// Copyright (C) 2017-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
9
10#include <config.h>
11#include <util/str.h>
13#include <dhcp/dhcp4.h>
14#include <dhcp/dhcp6.h>
15#include <dhcp/duid.h>
16#include <dhcp/option.h>
17#include <dhcp/option6_ia.h>
18#include <dhcp/pkt4.h>
19#include <dhcp/pkt6.h>
20#include <dhcpsrv/host.h>
21#include <hooks/hooks.h>
22#include <eval/evaluate.h>
23#include <eval/token.h>
24#include <eval/eval_context.h>
25#include <flex_id_log.h>
26#include <algorithm>
27#include <sstream>
28
29using namespace isc;
30using namespace dhcp;
31using namespace hooks;
32using namespace util;
33using namespace log;
34using namespace std;
35using namespace flex_id;
36
37namespace {
38
39bool flex_id_apply_to_leases = false;
40Expression flex_id_expr;
41bool flex_id_ignore_iaid = false;
42
43}
44
45namespace isc {
46namespace flex_id {
47
52void parseAndStoreExpression(bool v6, const std::string& expr) {
53 try {
54 EvalContext eval_ctx(v6 ? Option::V6 : Option::V4);
56 flex_id_expr = eval_ctx.expression_;
57 } catch (const std::exception& ex) {
58 // Append position if there is a failure.
60 "expression: [" << expr << "] error: " << ex.what() );
61 }
62}
63
64void storeConfiguration(bool v6, const std::string& expr,
65 const bool apply_to_leases,
66 const bool ignore_iaid) {
67 flex_id_apply_to_leases = apply_to_leases;
68 if (!expr.empty()) {
70 }
71 flex_id_ignore_iaid = ignore_iaid;
72}
73
75 flex_id_apply_to_leases = false;
76 flex_id_expr.clear();
77 flex_id_ignore_iaid = false;
78}
79
87template<typename PacketType>
88void retrieveFlexId(CalloutHandle& callout_handle, const Expression& expression,
89 PacketType& pkt, std::vector<uint8_t>& id) {
90 try {
91 // Flexible identifier could have been already computed by other callout.
92 // Let's try to retrieve it.
93 callout_handle.getContext("id_value", id);
94
95 } catch (const NoSuchCalloutContext& ex) {
96 // Calculate the flexible identifier.
97 std::string value = evaluateString(expression, pkt);
98 if (str::isPrintable(value)) {
100 .arg(value).arg(value.size());
101 } else {
102 ostringstream repr;
103 repr << hex;
104 for (const char& ch : value) {
105 repr << setw(2) << setfill('0') << static_cast<unsigned>(ch);
106 }
108 .arg(repr.str()).arg(value.size());
109 }
110
111 // ... and prepare data to be sent back to Kea.
112 id.resize(value.size());
113 if (!id.empty()) {
114 std::copy(value.begin(), value.end(), id.begin());
115
116 // Remember newly computed values for other callouts.
117 callout_handle.setContext("id_value", id);
118
119 DUID duid(id);
121 .arg(duid.toText());
122 }
123 }
124}
125
126}
127}
128
129// Functions accessed by the hooks framework use C linkage to avoid the name
130// mangling that accompanies use of the C++ compiler as well as to avoid
131// issues related to namespaces.
132extern "C" {
133
143 if (status == CalloutHandle::NEXT_STEP_DROP ||
145 return (0);
146 }
147
148 if (flex_id_expr.empty()) {
149 // Expression not specified. Nothing to do here.
150 return (0);
151 }
152
153 // Get the parameters.
154 Pkt4Ptr pkt;
156 std::vector<uint8_t> id;
157 handle.getArgument("query4", pkt);
158 handle.getArgument("id_type", type);
159 handle.getArgument("id_value", id);
160
161 // Calculate flexible identifier from the expression and the contents of
162 // the packet.
163 retrieveFlexId(handle, flex_id_expr, *pkt, id);
164 if (!id.empty()) {
165 type = Host::IDENT_FLEX;
166
167 handle.setArgument("id_value", id);
168 handle.setArgument("id_type", type);
169 }
170
171 return (0);
172}
173
184 if (status == CalloutHandle::NEXT_STEP_DROP ||
186 return (0);
187 }
188
189 if (!flex_id_apply_to_leases || flex_id_expr.empty()) {
190 // Expression not specified or flexible identifier should not be used
191 // for lease identification. Nothing to do here.
192 return (0);
193 }
194
195 // Packet will be needed to evaluate expression.
196 Pkt4Ptr pkt;
197 handle.getArgument("query4", pkt);
198
199 // Retrieve already stored flex-id (if computed by other callouts for this
200 // packet) or compute it here.
201 std::vector<uint8_t> id;
202 retrieveFlexId(handle, flex_id_expr, *pkt, id);
203
204 // Flex-id is required to proceed.
205 if (!id.empty()) {
206 // Remember client identifier supplied by the DHCP client and remove
207 // it from the packet.
208 OptionPtr client_id = pkt->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
209 if (client_id) {
210 handle.setContext("client_identifier", client_id);
211 static_cast<void>(pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER));
212 }
213
214 // Use 0 as a client identifier type.
215 OptionBuffer buf(1, 0);
216
217 // Create new client identifier from the flex-id.
218 buf.insert(buf.end(), id.begin(), id.end());
219 OptionPtr new_client_id(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER, buf));
220 pkt->addOption(new_client_id);
221
222 ClientId cid(buf);
224 .arg(cid.toText());
225 }
226
227 return (0);
228}
229
240 if (status == CalloutHandle::NEXT_STEP_DROP) {
241 return (0);
242 }
243
244 // There is nothing to do if flexible identifier is not to be used as client
245 // identifier per configuration.
246 if (!flex_id_apply_to_leases) {
247 return (0);
248 }
249
250 // The first thing to do is to check whether the flex-id is in use. If not, there
251 // might have been an error evaluating expression or the flex-id was empty. If so,
252 // there is nothing to do.
253 try {
254 std::vector<uint8_t> id;
255 handle.getContext("id_value", id);
256
257 } catch (const NoSuchCalloutContext& ex) {
258 return (0);
259 }
260
261 if (status == CalloutHandle::NEXT_STEP_SKIP) {
262 isc_throw(InvalidOperation, "packet pack already handled");
263 }
264
265 Pkt4Ptr pkt;
266 Pkt4Ptr response;
267 handle.getArgument("query4", pkt);
268 handle.getArgument("response4", response);
269
270 OptionPtr old_client_id;
271 try {
272 handle.getContext("client_identifier", old_client_id);
273
274 } catch (const NoSuchCalloutContext& ex) {
275 // Ignore that the context doesn't exist. We will examine old_client_id
276 // value instead.
277 }
278
279 // Remove currently used client identifier (presumably flex-id value).
280 static_cast<void>(response->delOption(DHO_DHCP_CLIENT_IDENTIFIER));
281
282 // Add the client supplied client identifier into the outbound packet.
283 if (old_client_id) {
284 response->addOption(old_client_id);
285
286 ClientId client_id(old_client_id->getData());
288 .arg(client_id.toText());
289 }
290
291 return (0);
292}
293
303 if (status == CalloutHandle::NEXT_STEP_DROP ||
305 return (0);
306 }
307
308 if (flex_id_expr.empty()) {
309 // Expression not specified. Nothing to do here.
310 return (0);
311 }
312
313 // Get the parameters.
314 Pkt6Ptr pkt;
316 std::vector<uint8_t> id;
317 handle.getArgument("query6", pkt);
318 handle.getArgument("id_type", type);
319 handle.getArgument("id_value", id);
320
321 // Calculate flexible identifier from the expression and the contents of
322 // the packet.
323 retrieveFlexId(handle, flex_id_expr, *pkt, id);
324 if (!id.empty()) {
325 type = Host::IDENT_FLEX;
326
327 handle.setArgument("id_value", id);
328 handle.setArgument("id_type", type);
329 }
330
331 return (0);
332}
333
344 if (status == CalloutHandle::NEXT_STEP_DROP ||
346 return (0);
347 }
348
349 // Packet will be needed to evaluate expression.
350 Pkt6Ptr pkt;
351 handle.getArgument("query6", pkt);
352
353 if (flex_id_ignore_iaid) {
354 uint32_t iana_count = 0;
355 uint32_t iapd_count = 0;
356 for (auto const& opt : pkt->options_) {
357 switch (opt.second->getType()) {
358 case D6O_IA_NA: {
359 iana_count++;
360 break;
361 }
362 case D6O_IA_PD: {
363 iapd_count++;
364 break;
365 }
366 }
367 }
368 handle.setContext("iana_count", iana_count);
369 handle.setContext("iapd_count", iapd_count);
370 if (iana_count > 1) {
373 }
374 if (iapd_count > 1) {
377 }
378 uint32_t iana_iaid = 0;
379 uint32_t iapd_iaid = 0;
380 if (iana_count == 1) {
381 for (auto const& opt : pkt->options_) {
382 switch (opt.second->getType()) {
383 case D6O_IA_NA: {
384 auto iana = boost::dynamic_pointer_cast<Option6IA>(opt.second);
385 iana_iaid = iana->getIAID();
386 iana->setIAID(0);
389 .arg(iana_iaid);
390 break;
391 }
392 }
393 }
394 handle.setContext("iana_iaid", iana_iaid);
395 }
396 if (iapd_count == 1) {
397 for (auto const& opt : pkt->options_) {
398 switch (opt.second->getType()) {
399 case D6O_IA_PD: {
400 auto iapd = boost::dynamic_pointer_cast<Option6IA>(opt.second);
401 iapd_iaid = iapd->getIAID();
402 iapd->setIAID(0);
405 .arg(iapd_iaid);
406 break;
407 }
408 }
409 }
410 handle.setContext("iapd_iaid", iapd_iaid);
411 }
412 }
413
414 if (!flex_id_apply_to_leases || flex_id_expr.empty()) {
415 // Expression not specified or flexible identifier should not be used
416 // for lease identification. Nothing to do here.
417 return (0);
418 }
419
420 // Retrieve already stored flex-id (if computed by other callouts for this
421 // packet) or compute it here.
422 std::vector<uint8_t> id;
423 retrieveFlexId(handle, flex_id_expr, *pkt, id);
424
425 // Flex-id is required to proceed.
426 if (!id.empty()) {
427 // Remember DUID supplied by the DHCP client and remove it from the
428 // packet.
429 OptionPtr opt_duid = pkt->getOption(D6O_CLIENTID);
430 if (opt_duid) {
431 handle.setContext("duid", opt_duid);
432 static_cast<void>(pkt->delOption(D6O_CLIENTID));
433 }
434
435 // Use 0 as a DUID type. Since this is a synthesized value, we do not
436 // want to use any of the currently existing DUID types (as of 2017,
437 // values 1 through 4 are defined). Zero is a safe choice here.
438 OptionBuffer buf(2, 0);
439
440 // Create new DUID from the flex-id.
441 buf.insert(buf.end(), id.begin(), id.end());
442 OptionPtr new_duid(new Option(Option::V6, D6O_CLIENTID, buf));
443 pkt->addOption(new_duid);
444
445 DUID duid(buf);
447 .arg(duid.toText());
448 }
449
450 return (0);
451}
452
463 if (status == CalloutHandle::NEXT_STEP_DROP) {
464 return (0);
465 }
466
467 Pkt6Ptr pkt;
468 Pkt6Ptr response;
469 handle.getArgument("query6", pkt);
470 handle.getArgument("response6", response);
471
472 if (flex_id_ignore_iaid) {
473 uint32_t iana_count = 0;
474 uint32_t iapd_count = 0;
475 handle.getContext("iana_count", iana_count);
476 handle.getContext("iapd_count", iapd_count);
477 if (iana_count == 1) {
478 for (auto const& opt : response->options_) {
479 switch (opt.second->getType()) {
480 case D6O_IA_NA: {
481 auto iana = boost::dynamic_pointer_cast<Option6IA>(opt.second);
482 uint32_t iana_iaid = 0;
483 handle.getContext("iana_iaid", iana_iaid);
484 iana->setIAID(iana_iaid);
485 break;
486 }
487 }
488 }
489 }
490 if (iapd_count == 1) {
491 for (auto const& opt : response->options_) {
492 switch (opt.second->getType()) {
493 case D6O_IA_PD: {
494 auto iapd = boost::dynamic_pointer_cast<Option6IA>(opt.second);
495 uint32_t iapd_iaid = 0;
496 handle.getContext("iapd_iaid", iapd_iaid);
497 iapd->setIAID(iapd_iaid);
498 break;
499 }
500 }
501 }
502 }
503 }
504
505 // There is nothing to do if flexible identifier is not to be used as client
506 // identifier per configuration.
507 if (!flex_id_apply_to_leases) {
508 return (0);
509 }
510
511 // The first thing to do is to check whether the flex-id is in use. If not, there
512 // might have been an error evaluating expression or the flex-id was empty. If so,
513 // there is nothing to do.
514 try {
515 std::vector<uint8_t> id;
516 handle.getContext("id_value", id);
517
518 } catch (const NoSuchCalloutContext& ex) {
519 return (0);
520 }
521
522 if (status == CalloutHandle::NEXT_STEP_SKIP) {
523 isc_throw(InvalidOperation, "packet pack already handled");
524 }
525
526 OptionPtr old_duid;
527 try {
528 handle.getContext("duid", old_duid);
529
530 } catch (const NoSuchCalloutContext& ex) {
531 // Ignore that the context doesn't exist. We will examine old_duid
532 // value instead.
533 }
534
535 // Remove currently used DUID (presumably flex-id value).
536 static_cast<void>(response->delOption(D6O_CLIENTID));
537
538 // Add the client supplied client identifier into the outbound packet.
539 if (old_duid) {
540 response->addOption(old_duid);
541
542 DUID duid(old_duid->getData());
544 .arg(duid.toText());
545 }
546
547 return (0);
548}
549
550} // end extern "C"
int host4_identifier(CalloutHandle &handle)
This callout is called at the "host4_identifier" hook.
Definition callouts.cc:141
int pkt6_send(CalloutHandle &handle)
This callout is called at "pkt6_send" hook point.
Definition callouts.cc:461
int pkt4_send(CalloutHandle &handle)
This callout is called at "pkt4_send" hook point.
Definition callouts.cc:238
int pkt4_receive(CalloutHandle &handle)
This callout is called at "pkt4_receive" hook point.
Definition callouts.cc:182
int pkt6_receive(CalloutHandle &handle)
This callout is called at "pkt6_receive" hook point.
Definition callouts.cc:342
int host6_identifier(CalloutHandle &handle)
This callout is called at the "host6_identifier" hook.
Definition callouts.cc:301
CalloutNextStep
Specifies allowed next steps.
@ NEXT_STEP_DROP
drop the packet
@ NEXT_STEP_SKIP
skip the next processing step
A generic exception that is thrown if a function is called in a prohibited way.
A generic exception that is thrown when an unexpected error condition occurs.
Holds Client identifier or client IPv4 address.
Definition duid.h:222
Holds DUID (DHCPv6 Unique Identifier)
Definition duid.h:142
IdentifierType
Type of the host identifier.
Definition host.h:337
@ IDENT_FLEX
Flexible host identifier.
Definition host.h:342
std::string toText() const
Returns textual representation of the identifier (e.g.
Definition duid.h:88
Evaluation context, an interface to the expression evaluation.
bool parseString(const std::string &str, ParserType type=PARSER_BOOL)
Run the parser on the string specified.
@ PARSER_STRING
expression is expected to evaluate to string
isc::dhcp::Expression expression_
Parsed expression (output tokens are stored here)
Per-packet callout handle.
void getContext(const std::string &name, T &value) const
Get context.
void setContext(const std::string &name, T value)
Set context.
CalloutNextStep getStatus() const
Returns the next processing step.
void getArgument(const std::string &name, T &value) const
Get argument.
void setArgument(const std::string &name, T value)
Set argument.
No such callout context item.
@ D6O_CLIENTID
Definition dhcp6.h:21
@ D6O_IA_NA
Definition dhcp6.h:23
@ D6O_IA_PD
Definition dhcp6.h:45
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
const isc::log::MessageID FLEX_ID_IGNORE_IAID_NOT_APPLIED_ON_PD
const isc::log::MessageID FLEX_ID_IGNORE_IAID_NOT_APPLIED_ON_NA
const isc::log::MessageID FLEX_ID_IGNORE_IAID_APPLIED_ON_PD
const isc::log::MessageID FLEX_ID_EXPRESSION_HEX
const isc::log::MessageID FLEX_ID_RESTORE_CLIENT_ID
const isc::log::MessageID FLEX_ID_EXPRESSION_EVALUATED
const isc::log::MessageID FLEX_ID_RESTORE_DUID
const isc::log::MessageID FLEX_ID_IGNORE_IAID_APPLIED_ON_NA
const isc::log::MessageID FLEX_ID_USED_AS_CLIENT_ID
const isc::log::MessageID FLEX_ID_EXPRESSION_EVALUATED_NP
const isc::log::MessageID FLEX_ID_USED_AS_DUID
#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
@ DHO_DHCP_CLIENT_IDENTIFIER
Definition dhcp4.h:130
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition pkt4.h:556
std::string evaluateString(const Expression &expr, Pkt &pkt)
Evaluate a RPN expression for a v4 or v6 packet and return a string value.
Definition evaluate.cc:45
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition pkt6.h:31
std::vector< uint8_t > OptionBuffer
buffer types used in DHCP code.
Definition option.h:24
std::vector< TokenPtr > Expression
This is a structure that holds an expression converted to RPN.
Definition token.h:29
boost::shared_ptr< Option > OptionPtr
Definition option.h:37
void retrieveFlexId(CalloutHandle &callout_handle, const Expression &expression, PacketType &pkt, std::vector< uint8_t > &id)
Retrieves flexible identifier from the context or computes it.
Definition callouts.cc:88
isc::log::Logger flex_id_logger("flex-id-hooks")
Flexible Identifier Logger.
Definition flex_id_log.h:22
void clearConfiguration()
Clears stored configuration.
Definition callouts.cc:74
void parseAndStoreExpression(bool v6, const std::string &expr)
Parses expression provided as text.
Definition callouts.cc:52
void storeConfiguration(bool v6, const std::string &expr, const bool apply_to_leases, const bool ignore_iaid)
Stores expression.
Definition callouts.cc:64
const int DBGLVL_TRACE_BASIC
Trace basic operations.
bool isPrintable(const string &content)
Check if a string is printable.
Definition str.cc:310
Defines the logger used by the top-level component of kea-lfc.