Kea 3.3.1
callouts.cc
Go to the documentation of this file.
1// Copyright (C) 2017-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
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;
43bool flex_id_cid_as_rfc4361_duid = false;
44
45}
46
47namespace isc {
48namespace flex_id {
49
54void parseAndStoreExpression(bool v6, const std::string& expr) {
55 try {
56 EvalContext eval_ctx(v6 ? Option::V6 : Option::V4);
58 flex_id_expr = eval_ctx.expression_;
59 } catch (const std::exception& ex) {
60 // Append position if there is a failure.
62 "expression: [" << expr << "] error: " << ex.what() );
63 }
64}
65
66void storeConfiguration(bool v6, const std::string& expr,
67 const bool apply_to_leases,
68 const bool ignore_iaid,
69 const bool cid_as_rfc4361_duid /* = false */) {
70
71 flex_id_apply_to_leases = apply_to_leases;
72 if (!expr.empty()) {
74 }
75 flex_id_ignore_iaid = ignore_iaid;
76 flex_id_cid_as_rfc4361_duid = cid_as_rfc4361_duid;
77}
78
80 flex_id_apply_to_leases = false;
81 flex_id_expr.clear();
82 flex_id_ignore_iaid = false;
83 flex_id_cid_as_rfc4361_duid = false;
84}
85
93template<typename PacketType>
94void retrieveFlexId(CalloutHandle& callout_handle, const Expression& expression,
95 PacketType& pkt, std::vector<uint8_t>& id) {
96 try {
97 // Flexible identifier could have been already computed by other callout.
98 // Let's try to retrieve it.
99 callout_handle.getContext("id_value", id);
100
101 } catch (const NoSuchCalloutContext& ex) {
102 // Calculate the flexible identifier.
103 std::string value = evaluateString(expression, pkt);
104 if (str::isPrintable(value)) {
106 .arg(value).arg(value.size());
107 } else {
109 .arg(dumpAsHex(value)).arg(value.size());
110 }
111
112 // ... and prepare data to be sent back to Kea.
113 id.resize(value.size());
114 if (!id.empty()) {
115 std::copy(value.begin(), value.end(), id.begin());
116
117 // Remember newly computed values for other callouts.
118 callout_handle.setContext("id_value", id);
119
120 DUID duid(id);
122 .arg(duid.toText());
123 }
124 }
125}
126
127}
128}
129
130// Functions accessed by the hooks framework use C linkage to avoid the name
131// mangling that accompanies use of the C++ compiler as well as to avoid
132// issues related to namespaces.
133extern "C" {
134
144 if (status == CalloutHandle::NEXT_STEP_DROP ||
146 return (0);
147 }
148
149 if (flex_id_expr.empty()) {
150 // Expression not specified. Nothing to do here.
151 return (0);
152 }
153
154 // Get the parameters.
155 Pkt4Ptr pkt;
157 std::vector<uint8_t> id;
158 handle.getArgument("query4", pkt);
159 handle.getArgument("id_type", type);
160 handle.getArgument("id_value", id);
161
162 // Calculate flexible identifier from the expression and the contents of
163 // the packet.
164 retrieveFlexId(handle, flex_id_expr, *pkt, id);
165 if (!id.empty()) {
166 type = Host::IDENT_FLEX;
167
168 handle.setArgument("id_value", id);
169 handle.setArgument("id_type", type);
170 }
171
172 return (0);
173}
174
185 if (status == CalloutHandle::NEXT_STEP_DROP ||
187 return (0);
188 }
189
190 if (!flex_id_apply_to_leases || flex_id_expr.empty()) {
191 // Expression not specified or flexible identifier should not be used
192 // for lease identification. Nothing to do here.
193 return (0);
194 }
195
196 // Packet will be needed to evaluate expression.
197 Pkt4Ptr pkt;
198 handle.getArgument("query4", pkt);
199
200 // Retrieve already stored flex-id (if computed by other callouts for this
201 // packet) or compute it here.
202 std::vector<uint8_t> id;
203 retrieveFlexId(handle, flex_id_expr, *pkt, id);
204
205 // Flex-id is required to proceed.
206 if (!id.empty()) {
207 // Remember client identifier supplied by the DHCP client and remove
208 // it from the packet.
209 OptionPtr client_id = pkt->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
210 if (client_id) {
211 handle.setContext("client_identifier", client_id);
212 static_cast<void>(pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER));
213 }
214
215 OptionBuffer buf;
216 if (flex_id_cid_as_rfc4361_duid) {
217 // Type = 255 followed 4-byte dummy iaid.
218 buf.assign({ 0xFF, 0, 0, 0, 0 });
219 } else {
220 // Use 0 as a client identifier type.
221 buf.push_back(0);
222 }
223
224 // Create new client identifier from the flex-id.
225 buf.insert(buf.end(), id.begin(), id.end());
226 OptionPtr new_client_id(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER, buf));
227 pkt->addOption(new_client_id);
228
229 ClientId cid(buf);
231 .arg(cid.toText());
232 }
233
234 return (0);
235}
236
247 if (status == CalloutHandle::NEXT_STEP_DROP) {
248 return (0);
249 }
250
251 // There is nothing to do if flexible identifier is not to be used as client
252 // identifier per configuration.
253 if (!flex_id_apply_to_leases) {
254 return (0);
255 }
256
257 // The first thing to do is to check whether the flex-id is in use. If not, there
258 // might have been an error evaluating expression or the flex-id was empty. If so,
259 // there is nothing to do.
260 try {
261 std::vector<uint8_t> id;
262 handle.getContext("id_value", id);
263
264 } catch (const NoSuchCalloutContext& ex) {
265 return (0);
266 }
267
268 if (status == CalloutHandle::NEXT_STEP_SKIP) {
269 isc_throw(InvalidOperation, "packet pack already handled");
270 }
271
272 Pkt4Ptr pkt;
273 Pkt4Ptr response;
274 handle.getArgument("query4", pkt);
275 handle.getArgument("response4", response);
276
277 OptionPtr old_client_id;
278 try {
279 handle.getContext("client_identifier", old_client_id);
280
281 } catch (const NoSuchCalloutContext& ex) {
282 // Ignore that the context doesn't exist. We will examine old_client_id
283 // value instead.
284 }
285
286 // Remove currently used client identifier (presumably flex-id value).
287 static_cast<void>(response->delOption(DHO_DHCP_CLIENT_IDENTIFIER));
288
289 // Add the client supplied client identifier into the outbound packet.
290 if (old_client_id) {
291 response->addOption(old_client_id);
292
293 ClientId client_id(old_client_id->getData());
295 .arg(client_id.toText());
296 }
297
298 return (0);
299}
300
310 if (status == CalloutHandle::NEXT_STEP_DROP ||
312 return (0);
313 }
314
315 if (flex_id_expr.empty()) {
316 // Expression not specified. Nothing to do here.
317 return (0);
318 }
319
320 // Get the parameters.
321 Pkt6Ptr pkt;
323 std::vector<uint8_t> id;
324 handle.getArgument("query6", pkt);
325 handle.getArgument("id_type", type);
326 handle.getArgument("id_value", id);
327
328 // Calculate flexible identifier from the expression and the contents of
329 // the packet.
330 retrieveFlexId(handle, flex_id_expr, *pkt, id);
331 if (!id.empty()) {
332 type = Host::IDENT_FLEX;
333
334 handle.setArgument("id_value", id);
335 handle.setArgument("id_type", type);
336 }
337
338 return (0);
339}
340
351 if (status == CalloutHandle::NEXT_STEP_DROP ||
353 return (0);
354 }
355
356 // Packet will be needed to evaluate expression.
357 Pkt6Ptr pkt;
358 handle.getArgument("query6", pkt);
359
360 if (flex_id_ignore_iaid) {
361 uint32_t iana_count = 0;
362 uint32_t iapd_count = 0;
363 for (auto const& opt : pkt->options_) {
364 switch (opt.second->getType()) {
365 case D6O_IA_NA: {
366 iana_count++;
367 break;
368 }
369 case D6O_IA_PD: {
370 iapd_count++;
371 break;
372 }
373 }
374 }
375 handle.setContext("iana_count", iana_count);
376 handle.setContext("iapd_count", iapd_count);
377 if (iana_count > 1) {
380 }
381 if (iapd_count > 1) {
384 }
385 uint32_t iana_iaid = 0;
386 uint32_t iapd_iaid = 0;
387 if (iana_count == 1) {
388 for (auto const& opt : pkt->options_) {
389 switch (opt.second->getType()) {
390 case D6O_IA_NA: {
391 auto iana = boost::dynamic_pointer_cast<Option6IA>(opt.second);
392 iana_iaid = iana->getIAID();
393 iana->setIAID(0);
396 .arg(iana_iaid);
397 break;
398 }
399 }
400 }
401 handle.setContext("iana_iaid", iana_iaid);
402 }
403 if (iapd_count == 1) {
404 for (auto const& opt : pkt->options_) {
405 switch (opt.second->getType()) {
406 case D6O_IA_PD: {
407 auto iapd = boost::dynamic_pointer_cast<Option6IA>(opt.second);
408 iapd_iaid = iapd->getIAID();
409 iapd->setIAID(0);
412 .arg(iapd_iaid);
413 break;
414 }
415 }
416 }
417 handle.setContext("iapd_iaid", iapd_iaid);
418 }
419 }
420
421 if (!flex_id_apply_to_leases || flex_id_expr.empty()) {
422 // Expression not specified or flexible identifier should not be used
423 // for lease identification. Nothing to do here.
424 return (0);
425 }
426
427 // Retrieve already stored flex-id (if computed by other callouts for this
428 // packet) or compute it here.
429 std::vector<uint8_t> id;
430 retrieveFlexId(handle, flex_id_expr, *pkt, id);
431
432 // Flex-id is required to proceed.
433 if (!id.empty()) {
434 // Remember DUID supplied by the DHCP client and remove it from the
435 // packet.
436 OptionPtr opt_duid = pkt->getOption(D6O_CLIENTID);
437 if (opt_duid) {
438 handle.setContext("duid", opt_duid);
439 static_cast<void>(pkt->delOption(D6O_CLIENTID));
440 }
441
442 // Use 0 as a DUID type. Since this is a synthesized value, we do not
443 // want to use any of the currently existing DUID types (as of 2017,
444 // values 1 through 4 are defined). Zero is a safe choice here.
445 OptionBuffer buf(2, 0);
446
447 // Create new DUID from the flex-id.
448 buf.insert(buf.end(), id.begin(), id.end());
449 OptionPtr new_duid(new Option(Option::V6, D6O_CLIENTID, buf));
450 pkt->addOption(new_duid);
451
452 DUID duid(buf);
454 .arg(duid.toText());
455 }
456
457 return (0);
458}
459
470 if (status == CalloutHandle::NEXT_STEP_DROP) {
471 return (0);
472 }
473
474 Pkt6Ptr pkt;
475 Pkt6Ptr response;
476 handle.getArgument("query6", pkt);
477 handle.getArgument("response6", response);
478
479 if (flex_id_ignore_iaid) {
480 uint32_t iana_count = 0;
481 uint32_t iapd_count = 0;
482 handle.getContext("iana_count", iana_count);
483 handle.getContext("iapd_count", iapd_count);
484 if (iana_count == 1) {
485 for (auto const& opt : response->options_) {
486 switch (opt.second->getType()) {
487 case D6O_IA_NA: {
488 auto iana = boost::dynamic_pointer_cast<Option6IA>(opt.second);
489 uint32_t iana_iaid = 0;
490 handle.getContext("iana_iaid", iana_iaid);
491 iana->setIAID(iana_iaid);
492 break;
493 }
494 }
495 }
496 }
497 if (iapd_count == 1) {
498 for (auto const& opt : response->options_) {
499 switch (opt.second->getType()) {
500 case D6O_IA_PD: {
501 auto iapd = boost::dynamic_pointer_cast<Option6IA>(opt.second);
502 uint32_t iapd_iaid = 0;
503 handle.getContext("iapd_iaid", iapd_iaid);
504 iapd->setIAID(iapd_iaid);
505 break;
506 }
507 }
508 }
509 }
510 }
511
512 // There is nothing to do if flexible identifier is not to be used as client
513 // identifier per configuration.
514 if (!flex_id_apply_to_leases) {
515 return (0);
516 }
517
518 // The first thing to do is to check whether the flex-id is in use. If not, there
519 // might have been an error evaluating expression or the flex-id was empty. If so,
520 // there is nothing to do.
521 try {
522 std::vector<uint8_t> id;
523 handle.getContext("id_value", id);
524
525 } catch (const NoSuchCalloutContext& ex) {
526 return (0);
527 }
528
529 if (status == CalloutHandle::NEXT_STEP_SKIP) {
530 isc_throw(InvalidOperation, "packet pack already handled");
531 }
532
533 OptionPtr old_duid;
534 try {
535 handle.getContext("duid", old_duid);
536
537 } catch (const NoSuchCalloutContext& ex) {
538 // Ignore that the context doesn't exist. We will examine old_duid
539 // value instead.
540 }
541
542 // Remove currently used DUID (presumably flex-id value).
543 static_cast<void>(response->delOption(D6O_CLIENTID));
544
545 // Add the client supplied client identifier into the outbound packet.
546 if (old_duid) {
547 response->addOption(old_duid);
548
549 DUID duid(old_duid->getData());
551 .arg(duid.toText());
552 }
553
554 return (0);
555}
556
557} // end extern "C"
int host4_identifier(CalloutHandle &handle)
This callout is called at the "host4_identifier" hook.
Definition callouts.cc:142
int pkt6_send(CalloutHandle &handle)
This callout is called at "pkt6_send" hook point.
Definition callouts.cc:468
int pkt4_send(CalloutHandle &handle)
This callout is called at "pkt4_send" hook point.
Definition callouts.cc:245
int pkt4_receive(CalloutHandle &handle)
This callout is called at "pkt4_receive" hook point.
Definition callouts.cc:183
int pkt6_receive(CalloutHandle &handle)
This callout is called at "pkt6_receive" hook point.
Definition callouts.cc:349
int host6_identifier(CalloutHandle &handle)
This callout is called at the "host6_identifier" hook.
Definition callouts.cc:308
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 storeConfiguration(bool v6, const std::string &expr, const bool apply_to_leases, const bool ignore_iaid, const bool cid_as_rfc4361_duid)
Stores expression.
Definition callouts.cc:66
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:94
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:79
void parseAndStoreExpression(bool v6, const std::string &expr)
Parses expression provided as text.
Definition callouts.cc:54
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.