Kea 3.3.0
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;
36using namespace isc::util::str;
37
38namespace {
39
40bool flex_id_apply_to_leases = false;
41Expression flex_id_expr;
42bool flex_id_ignore_iaid = false;
43
44}
45
46namespace isc {
47namespace flex_id {
48
53void parseAndStoreExpression(bool v6, const std::string& expr) {
54 try {
55 EvalContext eval_ctx(v6 ? Option::V6 : Option::V4);
57 flex_id_expr = eval_ctx.expression_;
58 } catch (const std::exception& ex) {
59 // Append position if there is a failure.
61 "expression: [" << expr << "] error: " << ex.what() );
62 }
63}
64
65void storeConfiguration(bool v6, const std::string& expr,
66 const bool apply_to_leases,
67 const bool ignore_iaid) {
68 flex_id_apply_to_leases = apply_to_leases;
69 if (!expr.empty()) {
71 }
72 flex_id_ignore_iaid = ignore_iaid;
73}
74
76 flex_id_apply_to_leases = false;
77 flex_id_expr.clear();
78 flex_id_ignore_iaid = false;
79}
80
88template<typename PacketType>
89void retrieveFlexId(CalloutHandle& callout_handle, const Expression& expression,
90 PacketType& pkt, std::vector<uint8_t>& id) {
91 try {
92 // Flexible identifier could have been already computed by other callout.
93 // Let's try to retrieve it.
94 callout_handle.getContext("id_value", id);
95
96 } catch (const NoSuchCalloutContext& ex) {
97 // Calculate the flexible identifier.
98 std::string value = evaluateString(expression, pkt);
99 if (str::isPrintable(value)) {
101 .arg(value).arg(value.size());
102 } else {
104 .arg(dumpAsHex(value)).arg(value.size());
105 }
106
107 // ... and prepare data to be sent back to Kea.
108 id.resize(value.size());
109 if (!id.empty()) {
110 std::copy(value.begin(), value.end(), id.begin());
111
112 // Remember newly computed values for other callouts.
113 callout_handle.setContext("id_value", id);
114
115 DUID duid(id);
117 .arg(duid.toText());
118 }
119 }
120}
121
122}
123}
124
125// Functions accessed by the hooks framework use C linkage to avoid the name
126// mangling that accompanies use of the C++ compiler as well as to avoid
127// issues related to namespaces.
128extern "C" {
129
139 if (status == CalloutHandle::NEXT_STEP_DROP ||
141 return (0);
142 }
143
144 if (flex_id_expr.empty()) {
145 // Expression not specified. Nothing to do here.
146 return (0);
147 }
148
149 // Get the parameters.
150 Pkt4Ptr pkt;
152 std::vector<uint8_t> id;
153 handle.getArgument("query4", pkt);
154 handle.getArgument("id_type", type);
155 handle.getArgument("id_value", id);
156
157 // Calculate flexible identifier from the expression and the contents of
158 // the packet.
159 retrieveFlexId(handle, flex_id_expr, *pkt, id);
160 if (!id.empty()) {
161 type = Host::IDENT_FLEX;
162
163 handle.setArgument("id_value", id);
164 handle.setArgument("id_type", type);
165 }
166
167 return (0);
168}
169
180 if (status == CalloutHandle::NEXT_STEP_DROP ||
182 return (0);
183 }
184
185 if (!flex_id_apply_to_leases || flex_id_expr.empty()) {
186 // Expression not specified or flexible identifier should not be used
187 // for lease identification. Nothing to do here.
188 return (0);
189 }
190
191 // Packet will be needed to evaluate expression.
192 Pkt4Ptr pkt;
193 handle.getArgument("query4", pkt);
194
195 // Retrieve already stored flex-id (if computed by other callouts for this
196 // packet) or compute it here.
197 std::vector<uint8_t> id;
198 retrieveFlexId(handle, flex_id_expr, *pkt, id);
199
200 // Flex-id is required to proceed.
201 if (!id.empty()) {
202 // Remember client identifier supplied by the DHCP client and remove
203 // it from the packet.
204 OptionPtr client_id = pkt->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
205 if (client_id) {
206 handle.setContext("client_identifier", client_id);
207 static_cast<void>(pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER));
208 }
209
210 // Use 0 as a client identifier type.
211 OptionBuffer buf(1, 0);
212
213 // Create new client identifier from the flex-id.
214 buf.insert(buf.end(), id.begin(), id.end());
215 OptionPtr new_client_id(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER, buf));
216 pkt->addOption(new_client_id);
217
218 ClientId cid(buf);
220 .arg(cid.toText());
221 }
222
223 return (0);
224}
225
236 if (status == CalloutHandle::NEXT_STEP_DROP) {
237 return (0);
238 }
239
240 // There is nothing to do if flexible identifier is not to be used as client
241 // identifier per configuration.
242 if (!flex_id_apply_to_leases) {
243 return (0);
244 }
245
246 // The first thing to do is to check whether the flex-id is in use. If not, there
247 // might have been an error evaluating expression or the flex-id was empty. If so,
248 // there is nothing to do.
249 try {
250 std::vector<uint8_t> id;
251 handle.getContext("id_value", id);
252
253 } catch (const NoSuchCalloutContext& ex) {
254 return (0);
255 }
256
257 if (status == CalloutHandle::NEXT_STEP_SKIP) {
258 isc_throw(InvalidOperation, "packet pack already handled");
259 }
260
261 Pkt4Ptr pkt;
262 Pkt4Ptr response;
263 handle.getArgument("query4", pkt);
264 handle.getArgument("response4", response);
265
266 OptionPtr old_client_id;
267 try {
268 handle.getContext("client_identifier", old_client_id);
269
270 } catch (const NoSuchCalloutContext& ex) {
271 // Ignore that the context doesn't exist. We will examine old_client_id
272 // value instead.
273 }
274
275 // Remove currently used client identifier (presumably flex-id value).
276 static_cast<void>(response->delOption(DHO_DHCP_CLIENT_IDENTIFIER));
277
278 // Add the client supplied client identifier into the outbound packet.
279 if (old_client_id) {
280 response->addOption(old_client_id);
281
282 ClientId client_id(old_client_id->getData());
284 .arg(client_id.toText());
285 }
286
287 return (0);
288}
289
299 if (status == CalloutHandle::NEXT_STEP_DROP ||
301 return (0);
302 }
303
304 if (flex_id_expr.empty()) {
305 // Expression not specified. Nothing to do here.
306 return (0);
307 }
308
309 // Get the parameters.
310 Pkt6Ptr pkt;
312 std::vector<uint8_t> id;
313 handle.getArgument("query6", pkt);
314 handle.getArgument("id_type", type);
315 handle.getArgument("id_value", id);
316
317 // Calculate flexible identifier from the expression and the contents of
318 // the packet.
319 retrieveFlexId(handle, flex_id_expr, *pkt, id);
320 if (!id.empty()) {
321 type = Host::IDENT_FLEX;
322
323 handle.setArgument("id_value", id);
324 handle.setArgument("id_type", type);
325 }
326
327 return (0);
328}
329
340 if (status == CalloutHandle::NEXT_STEP_DROP ||
342 return (0);
343 }
344
345 // Packet will be needed to evaluate expression.
346 Pkt6Ptr pkt;
347 handle.getArgument("query6", pkt);
348
349 if (flex_id_ignore_iaid) {
350 uint32_t iana_count = 0;
351 uint32_t iapd_count = 0;
352 for (auto const& opt : pkt->options_) {
353 switch (opt.second->getType()) {
354 case D6O_IA_NA: {
355 iana_count++;
356 break;
357 }
358 case D6O_IA_PD: {
359 iapd_count++;
360 break;
361 }
362 }
363 }
364 handle.setContext("iana_count", iana_count);
365 handle.setContext("iapd_count", iapd_count);
366 if (iana_count > 1) {
369 }
370 if (iapd_count > 1) {
373 }
374 uint32_t iana_iaid = 0;
375 uint32_t iapd_iaid = 0;
376 if (iana_count == 1) {
377 for (auto const& opt : pkt->options_) {
378 switch (opt.second->getType()) {
379 case D6O_IA_NA: {
380 auto iana = boost::dynamic_pointer_cast<Option6IA>(opt.second);
381 iana_iaid = iana->getIAID();
382 iana->setIAID(0);
385 .arg(iana_iaid);
386 break;
387 }
388 }
389 }
390 handle.setContext("iana_iaid", iana_iaid);
391 }
392 if (iapd_count == 1) {
393 for (auto const& opt : pkt->options_) {
394 switch (opt.second->getType()) {
395 case D6O_IA_PD: {
396 auto iapd = boost::dynamic_pointer_cast<Option6IA>(opt.second);
397 iapd_iaid = iapd->getIAID();
398 iapd->setIAID(0);
401 .arg(iapd_iaid);
402 break;
403 }
404 }
405 }
406 handle.setContext("iapd_iaid", iapd_iaid);
407 }
408 }
409
410 if (!flex_id_apply_to_leases || flex_id_expr.empty()) {
411 // Expression not specified or flexible identifier should not be used
412 // for lease identification. Nothing to do here.
413 return (0);
414 }
415
416 // Retrieve already stored flex-id (if computed by other callouts for this
417 // packet) or compute it here.
418 std::vector<uint8_t> id;
419 retrieveFlexId(handle, flex_id_expr, *pkt, id);
420
421 // Flex-id is required to proceed.
422 if (!id.empty()) {
423 // Remember DUID supplied by the DHCP client and remove it from the
424 // packet.
425 OptionPtr opt_duid = pkt->getOption(D6O_CLIENTID);
426 if (opt_duid) {
427 handle.setContext("duid", opt_duid);
428 static_cast<void>(pkt->delOption(D6O_CLIENTID));
429 }
430
431 // Use 0 as a DUID type. Since this is a synthesized value, we do not
432 // want to use any of the currently existing DUID types (as of 2017,
433 // values 1 through 4 are defined). Zero is a safe choice here.
434 OptionBuffer buf(2, 0);
435
436 // Create new DUID from the flex-id.
437 buf.insert(buf.end(), id.begin(), id.end());
438 OptionPtr new_duid(new Option(Option::V6, D6O_CLIENTID, buf));
439 pkt->addOption(new_duid);
440
441 DUID duid(buf);
443 .arg(duid.toText());
444 }
445
446 return (0);
447}
448
459 if (status == CalloutHandle::NEXT_STEP_DROP) {
460 return (0);
461 }
462
463 Pkt6Ptr pkt;
464 Pkt6Ptr response;
465 handle.getArgument("query6", pkt);
466 handle.getArgument("response6", response);
467
468 if (flex_id_ignore_iaid) {
469 uint32_t iana_count = 0;
470 uint32_t iapd_count = 0;
471 handle.getContext("iana_count", iana_count);
472 handle.getContext("iapd_count", iapd_count);
473 if (iana_count == 1) {
474 for (auto const& opt : response->options_) {
475 switch (opt.second->getType()) {
476 case D6O_IA_NA: {
477 auto iana = boost::dynamic_pointer_cast<Option6IA>(opt.second);
478 uint32_t iana_iaid = 0;
479 handle.getContext("iana_iaid", iana_iaid);
480 iana->setIAID(iana_iaid);
481 break;
482 }
483 }
484 }
485 }
486 if (iapd_count == 1) {
487 for (auto const& opt : response->options_) {
488 switch (opt.second->getType()) {
489 case D6O_IA_PD: {
490 auto iapd = boost::dynamic_pointer_cast<Option6IA>(opt.second);
491 uint32_t iapd_iaid = 0;
492 handle.getContext("iapd_iaid", iapd_iaid);
493 iapd->setIAID(iapd_iaid);
494 break;
495 }
496 }
497 }
498 }
499 }
500
501 // There is nothing to do if flexible identifier is not to be used as client
502 // identifier per configuration.
503 if (!flex_id_apply_to_leases) {
504 return (0);
505 }
506
507 // The first thing to do is to check whether the flex-id is in use. If not, there
508 // might have been an error evaluating expression or the flex-id was empty. If so,
509 // there is nothing to do.
510 try {
511 std::vector<uint8_t> id;
512 handle.getContext("id_value", id);
513
514 } catch (const NoSuchCalloutContext& ex) {
515 return (0);
516 }
517
518 if (status == CalloutHandle::NEXT_STEP_SKIP) {
519 isc_throw(InvalidOperation, "packet pack already handled");
520 }
521
522 OptionPtr old_duid;
523 try {
524 handle.getContext("duid", old_duid);
525
526 } catch (const NoSuchCalloutContext& ex) {
527 // Ignore that the context doesn't exist. We will examine old_duid
528 // value instead.
529 }
530
531 // Remove currently used DUID (presumably flex-id value).
532 static_cast<void>(response->delOption(D6O_CLIENTID));
533
534 // Add the client supplied client identifier into the outbound packet.
535 if (old_duid) {
536 response->addOption(old_duid);
537
538 DUID duid(old_duid->getData());
540 .arg(duid.toText());
541 }
542
543 return (0);
544}
545
546} // end extern "C"
int host4_identifier(CalloutHandle &handle)
This callout is called at the "host4_identifier" hook.
Definition callouts.cc:137
int pkt6_send(CalloutHandle &handle)
This callout is called at "pkt6_send" hook point.
Definition callouts.cc:457
int pkt4_send(CalloutHandle &handle)
This callout is called at "pkt4_send" hook point.
Definition callouts.cc:234
int pkt4_receive(CalloutHandle &handle)
This callout is called at "pkt4_receive" hook point.
Definition callouts.cc:178
int pkt6_receive(CalloutHandle &handle)
This callout is called at "pkt6_receive" hook point.
Definition callouts.cc:338
int host6_identifier(CalloutHandle &handle)
This callout is called at the "host6_identifier" hook.
Definition callouts.cc:297
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_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:89
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:75
void parseAndStoreExpression(bool v6, const std::string &expr)
Parses expression provided as text.
Definition callouts.cc:53
void storeConfiguration(bool v6, const std::string &expr, const bool apply_to_leases, const bool ignore_iaid)
Stores expression.
Definition callouts.cc:65
const int DBGLVL_TRACE_BASIC
Trace basic operations.
string dumpAsHex(const uint8_t *data, size_t length)
Dumps a buffer of bytes as a string of hexadecimal digits.
Definition str.cc:330
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.