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