1 // Copyright (C) 2011-2021 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 #include <kea_version.h>
9
10 #include <dhcp/dhcp4.h>
11 #include <dhcp/duid.h>
12 #include <dhcp/hwaddr.h>
13 #include <dhcp/iface_mgr.h>
14 #include <dhcp/libdhcp++.h>
15 #include <dhcp/option4_addrlst.h>
16 #include <dhcp/option_custom.h>
17 #include <dhcp/option_int.h>
18 #include <dhcp/option_int_array.h>
19 #include <dhcp/option_vendor.h>
20 #include <dhcp/option_string.h>
21 #include <dhcp/pkt4.h>
22 #include <dhcp/pkt4o6.h>
23 #include <dhcp/pkt6.h>
24 #include <dhcp/docsis3_option_defs.h>
25 #include <dhcp4/client_handler.h>
26 #include <dhcp4/dhcp4to6_ipc.h>
27 #include <dhcp4/dhcp4_log.h>
28 #include <dhcp4/dhcp4_srv.h>
29 #include <asiolink/addr_utilities.h>
30 #include <dhcpsrv/cfgmgr.h>
31 #include <dhcpsrv/cfg_host_operations.h>
32 #include <dhcpsrv/cfg_iface.h>
33 #include <dhcpsrv/cfg_shared_networks.h>
34 #include <dhcpsrv/cfg_subnets4.h>
35 #include <dhcpsrv/fuzz.h>
36 #include <dhcpsrv/lease_mgr.h>
37 #include <dhcpsrv/lease_mgr_factory.h>
38 #include <dhcpsrv/ncr_generator.h>
39 #include <dhcpsrv/shared_network.h>
40 #include <dhcpsrv/subnet.h>
41 #include <dhcpsrv/subnet_selector.h>
42 #include <dhcpsrv/utils.h>
43 #include <eval/evaluate.h>
44 #include <eval/eval_messages.h>
45 #include <hooks/callout_handle.h>
46 #include <hooks/hooks_log.h>
47 #include <hooks/hooks_manager.h>
48 #include <stats/stats_mgr.h>
49 #include <util/strutil.h>
50 #include <log/logger.h>
51 #include <cryptolink/cryptolink.h>
52 #include <cfgrpt/config_report.h>
53
54 #ifdef HAVE_MYSQL
55 #include <dhcpsrv/mysql_lease_mgr.h>
56 #endif
57 #ifdef HAVE_PGSQL
58 #include <dhcpsrv/pgsql_lease_mgr.h>
59 #endif
60 #ifdef HAVE_CQL
61 #include <dhcpsrv/cql_lease_mgr.h>
62 #endif
63 #include <dhcpsrv/memfile_lease_mgr.h>
64
65 #include <boost/algorithm/string.hpp>
66 #include <boost/foreach.hpp>
67 #include <boost/pointer_cast.hpp>
68 #include <boost/shared_ptr.hpp>
69
70 #include <functional>
71 #include <iomanip>
72 #include <set>
73 #include <cstdlib>
74
75 using namespace isc;
76 using namespace isc::asiolink;
77 using namespace isc::cryptolink;
78 using namespace isc::dhcp;
79 using namespace isc::dhcp_ddns;
80 using namespace isc::hooks;
81 using namespace isc::log;
82 using namespace isc::stats;
83 using namespace isc::util;
84 using namespace std;
85 namespace ph = std::placeholders;
86
87 namespace {
88
89 /// Structure that holds registered hook indexes
90 struct Dhcp4Hooks {
91 int hook_index_buffer4_receive_; ///< index for "buffer4_receive" hook point
92 int hook_index_pkt4_receive_; ///< index for "pkt4_receive" hook point
93 int hook_index_subnet4_select_; ///< index for "subnet4_select" hook point
94 int hook_index_leases4_committed_; ///< index for "leases4_committed" hook point
95 int hook_index_lease4_release_; ///< index for "lease4_release" hook point
96 int hook_index_pkt4_send_; ///< index for "pkt4_send" hook point
97 int hook_index_buffer4_send_; ///< index for "buffer4_send" hook point
98 int hook_index_lease4_decline_; ///< index for "lease4_decline" hook point
99 int hook_index_host4_identifier_; ///< index for "host4_identifier" hook point
100
101 /// Constructor that registers hook points for DHCPv4 engine
Dhcp4Hooks__anona20d199e0111::Dhcp4Hooks102 Dhcp4Hooks() {
103 hook_index_buffer4_receive_ = HooksManager::registerHook("buffer4_receive");
104 hook_index_pkt4_receive_ = HooksManager::registerHook("pkt4_receive");
105 hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
106 hook_index_leases4_committed_ = HooksManager::registerHook("leases4_committed");
107 hook_index_lease4_release_ = HooksManager::registerHook("lease4_release");
108 hook_index_pkt4_send_ = HooksManager::registerHook("pkt4_send");
109 hook_index_buffer4_send_ = HooksManager::registerHook("buffer4_send");
110 hook_index_lease4_decline_ = HooksManager::registerHook("lease4_decline");
111 hook_index_host4_identifier_ = HooksManager::registerHook("host4_identifier");
112 }
113 };
114
115 /// List of statistics which is initialized to 0 during the DHCPv4
116 /// server startup.
117 std::set<std::string> dhcp4_statistics = {
118 "pkt4-received",
119 "pkt4-discover-received",
120 "pkt4-offer-received",
121 "pkt4-request-received",
122 "pkt4-ack-received",
123 "pkt4-nak-received",
124 "pkt4-release-received",
125 "pkt4-decline-received",
126 "pkt4-inform-received",
127 "pkt4-unknown-received",
128 "pkt4-sent",
129 "pkt4-offer-sent",
130 "pkt4-ack-sent",
131 "pkt4-nak-sent",
132 "pkt4-parse-failed",
133 "pkt4-receive-drop"
134 };
135
136 } // end of anonymous namespace
137
138 // Declare a Hooks object. As this is outside any function or method, it
139 // will be instantiated (and the constructor run) when the module is loaded.
140 // As a result, the hook indexes will be defined before any method in this
141 // module is called.
142 Dhcp4Hooks Hooks;
143
144 namespace isc {
145 namespace dhcp {
146
Dhcpv4Exchange(const AllocEnginePtr & alloc_engine,const Pkt4Ptr & query,const Subnet4Ptr & subnet,bool & drop)147 Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
148 const Pkt4Ptr& query,
149 const Subnet4Ptr& subnet,
150 bool& drop)
151 : alloc_engine_(alloc_engine), query_(query), resp_(),
152 context_(new AllocEngine::ClientContext4()) {
153
154 if (!alloc_engine_) {
155 isc_throw(BadValue, "alloc_engine value must not be NULL"
156 " when creating an instance of the Dhcpv4Exchange");
157 }
158
159 if (!query_) {
160 isc_throw(BadValue, "query value must not be NULL when"
161 " creating an instance of the Dhcpv4Exchange");
162 }
163 // Create response message.
164 initResponse();
165 // Select subnet for the query message.
166 context_->subnet_ = subnet;
167 // Hardware address.
168 context_->hwaddr_ = query->getHWAddr();
169 // Pointer to client's query.
170 context_->query_ = query;
171
172 // If subnet found, retrieve client identifier which will be needed
173 // for allocations and search for reservations associated with a
174 // subnet/shared network.
175 SharedNetwork4Ptr sn;
176 if (subnet) {
177 OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
178 if (opt_clientid) {
179 context_->clientid_.reset(new ClientId(opt_clientid->getData()));
180 }
181
182 // Find static reservations if not disabled for our subnet.
183 if (subnet->getReservationsInSubnet() ||
184 subnet->getReservationsGlobal()) {
185 // Before we can check for static reservations, we need to prepare a set
186 // of identifiers to be used for this.
187 setHostIdentifiers();
188
189 // Check for static reservations.
190 alloc_engine->findReservation(*context_);
191
192 // Get shared network to see if it is set for a subnet.
193 subnet->getSharedNetwork(sn);
194 }
195 }
196
197 // Global host reservations are independent of a selected subnet. If the
198 // global reservations contain client classes we should use them in case
199 // they are meant to affect pool selection. Also, if the subnet does not
200 // belong to a shared network we can use the reserved client classes
201 // because there is no way our subnet could change. Such classes may
202 // affect selection of a pool within the selected subnet.
203 auto global_host = context_->globalHost();
204 auto current_host = context_->currentHost();
205 if ((global_host && !global_host->getClientClasses4().empty()) ||
206 (!sn && current_host && !current_host->getClientClasses4().empty())) {
207 // We have already evaluated client classes and some of them may
208 // be in conflict with the reserved classes. Suppose there are
209 // two classes defined in the server configuration: first_class
210 // and second_class and the test for the second_class it looks
211 // like this: "not member('first_class')". If the first_class
212 // initially evaluates to false, the second_class evaluates to
213 // true. If the first_class is now set within the hosts reservations
214 // and we don't remove the previously evaluated second_class we'd
215 // end up with both first_class and second_class evaluated to
216 // true. In order to avoid that, we have to remove the classes
217 // evaluated in the first pass and evaluate them again. As
218 // a result, the first_class set via the host reservation will
219 // replace the second_class because the second_class will this
220 // time evaluate to false as desired.
221 const ClientClassDictionaryPtr& dict =
222 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
223 const ClientClassDefListPtr& defs_ptr = dict->getClasses();
224 for (auto def : *defs_ptr) {
225 // Only remove evaluated classes. Other classes can be
226 // assigned via hooks libraries and we should not remove
227 // them because there is no way they can be added back.
228 if (def->getMatchExpr()) {
229 context_->query_->classes_.erase(def->getName());
230 }
231 }
232 setReservedClientClasses(context_);
233 evaluateClasses(context_->query_, false);
234 }
235
236 // Set KNOWN builtin class if something was found, UNKNOWN if not.
237 if (!context_->hosts_.empty()) {
238 query->addClass("KNOWN");
239 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
240 .arg(query->getLabel())
241 .arg("KNOWN");
242 } else {
243 query->addClass("UNKNOWN");
244 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
245 .arg(query->getLabel())
246 .arg("UNKNOWN");
247 }
248
249 // Perform second pass of classification.
250 evaluateClasses(query, true);
251
252 const ClientClasses& classes = query_->getClasses();
253 if (!classes.empty()) {
254 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
255 .arg(query_->getLabel())
256 .arg(classes.toText());
257 }
258
259 // Check the DROP special class.
260 if (query_->inClass("DROP")) {
261 LOG_DEBUG(packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0013)
262 .arg(query_->toText());
263 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
264 static_cast<int64_t>(1));
265 drop = true;
266 }
267 }
268
269 void
initResponse()270 Dhcpv4Exchange::initResponse() {
271 uint8_t resp_type = 0;
272 switch (getQuery()->getType()) {
273 case DHCPDISCOVER:
274 resp_type = DHCPOFFER;
275 break;
276 case DHCPREQUEST:
277 case DHCPINFORM:
278 resp_type = DHCPACK;
279 break;
280 default:
281 ;
282 }
283 // Only create a response if one is required.
284 if (resp_type > 0) {
285 resp_.reset(new Pkt4(resp_type, getQuery()->getTransid()));
286 copyDefaultFields();
287 copyDefaultOptions();
288
289 if (getQuery()->isDhcp4o6()) {
290 initResponse4o6();
291 }
292 }
293 }
294
295 void
initResponse4o6()296 Dhcpv4Exchange::initResponse4o6() {
297 Pkt4o6Ptr query = boost::dynamic_pointer_cast<Pkt4o6>(getQuery());
298 if (!query) {
299 return;
300 }
301 const Pkt6Ptr& query6 = query->getPkt6();
302 Pkt6Ptr resp6(new Pkt6(DHCPV6_DHCPV4_RESPONSE, query6->getTransid()));
303 // Don't add client-id or server-id
304 // But copy relay info
305 if (!query6->relay_info_.empty()) {
306 resp6->copyRelayInfo(query6);
307 }
308 // Copy interface, and remote address and port
309 resp6->setIface(query6->getIface());
310 resp6->setIndex(query6->getIndex());
311 resp6->setRemoteAddr(query6->getRemoteAddr());
312 resp6->setRemotePort(query6->getRemotePort());
313 resp_.reset(new Pkt4o6(resp_, resp6));
314 }
315
316 void
copyDefaultFields()317 Dhcpv4Exchange::copyDefaultFields() {
318 resp_->setIface(query_->getIface());
319 resp_->setIndex(query_->getIndex());
320
321 // explicitly set this to 0
322 resp_->setSiaddr(IOAddress::IPV4_ZERO_ADDRESS());
323 // ciaddr is always 0, except for the Renew/Rebind state and for
324 // Inform when it may be set to the ciaddr sent by the client.
325 if (query_->getType() == DHCPINFORM) {
326 resp_->setCiaddr(query_->getCiaddr());
327 } else {
328 resp_->setCiaddr(IOAddress::IPV4_ZERO_ADDRESS());
329 }
330 resp_->setHops(query_->getHops());
331
332 // copy MAC address
333 resp_->setHWAddr(query_->getHWAddr());
334
335 // relay address
336 resp_->setGiaddr(query_->getGiaddr());
337
338 // If src/dest HW addresses are used by the packet filtering class
339 // we need to copy them as well. There is a need to check that the
340 // address being set is not-NULL because an attempt to set the NULL
341 // HW would result in exception. If these values are not set, the
342 // the default HW addresses (zeroed) should be generated by the
343 // packet filtering class when creating Ethernet header for
344 // outgoing packet.
345 HWAddrPtr src_hw_addr = query_->getLocalHWAddr();
346 if (src_hw_addr) {
347 resp_->setLocalHWAddr(src_hw_addr);
348 }
349 HWAddrPtr dst_hw_addr = query_->getRemoteHWAddr();
350 if (dst_hw_addr) {
351 resp_->setRemoteHWAddr(dst_hw_addr);
352 }
353
354 // Copy flags from the request to the response per RFC 2131
355 resp_->setFlags(query_->getFlags());
356 }
357
358 void
copyDefaultOptions()359 Dhcpv4Exchange::copyDefaultOptions() {
360 // Let's copy client-id to response. See RFC6842.
361 // It is possible to disable RFC6842 to keep backward compatibility
362 bool echo = CfgMgr::instance().getCurrentCfg()->getEchoClientId();
363 OptionPtr client_id = query_->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
364 if (client_id && echo) {
365 resp_->addOption(client_id);
366 }
367
368 // If this packet is relayed, we want to copy Relay Agent Info option
369 // when it is not empty.
370 OptionPtr rai = query_->getOption(DHO_DHCP_AGENT_OPTIONS);
371 if (rai && (rai->len() > Option::OPTION4_HDR_LEN)) {
372 resp_->addOption(rai);
373 }
374
375 // RFC 3011 states about the Subnet Selection Option
376
377 // "Servers configured to support this option MUST return an
378 // identical copy of the option to any client that sends it,
379 // regardless of whether or not the client requests the option in
380 // a parameter request list. Clients using this option MUST
381 // discard DHCPOFFER or DHCPACK packets that do not contain this
382 // option."
383 OptionPtr subnet_sel = query_->getOption(DHO_SUBNET_SELECTION);
384 if (subnet_sel) {
385 resp_->addOption(subnet_sel);
386 }
387 }
388
389 void
setHostIdentifiers()390 Dhcpv4Exchange::setHostIdentifiers() {
391 const ConstCfgHostOperationsPtr cfg =
392 CfgMgr::instance().getCurrentCfg()->getCfgHostOperations4();
393
394 // Collect host identifiers. The identifiers are stored in order of preference.
395 // The server will use them in that order to search for host reservations.
396 BOOST_FOREACH(const Host::IdentifierType& id_type,
397 cfg->getIdentifierTypes()) {
398 switch (id_type) {
399 case Host::IDENT_HWADDR:
400 if (context_->hwaddr_ && !context_->hwaddr_->hwaddr_.empty()) {
401 context_->addHostIdentifier(id_type, context_->hwaddr_->hwaddr_);
402 }
403 break;
404
405 case Host::IDENT_DUID:
406 if (context_->clientid_) {
407 const std::vector<uint8_t>& vec = context_->clientid_->getDuid();
408 if (!vec.empty()) {
409 // Client identifier type = DUID? Client identifier holding a DUID
410 // comprises Type (1 byte), IAID (4 bytes), followed by the actual
411 // DUID. Thus, the minimal length is 6.
412 if ((vec[0] == CLIENT_ID_OPTION_TYPE_DUID) && (vec.size() > 5)) {
413 // Extract DUID, skip IAID.
414 context_->addHostIdentifier(id_type,
415 std::vector<uint8_t>(vec.begin() + 5,
416 vec.end()));
417 }
418 }
419 }
420 break;
421
422 case Host::IDENT_CIRCUIT_ID:
423 {
424 OptionPtr rai = query_->getOption(DHO_DHCP_AGENT_OPTIONS);
425 if (rai) {
426 OptionPtr circuit_id_opt = rai->getOption(RAI_OPTION_AGENT_CIRCUIT_ID);
427 if (circuit_id_opt) {
428 const OptionBuffer& circuit_id_vec = circuit_id_opt->getData();
429 if (!circuit_id_vec.empty()) {
430 context_->addHostIdentifier(id_type, circuit_id_vec);
431 }
432 }
433 }
434 }
435 break;
436
437 case Host::IDENT_CLIENT_ID:
438 if (context_->clientid_) {
439 const std::vector<uint8_t>& vec = context_->clientid_->getDuid();
440 if (!vec.empty()) {
441 context_->addHostIdentifier(id_type, vec);
442 }
443 }
444 break;
445 case Host::IDENT_FLEX:
446 {
447 if (!HooksManager::calloutsPresent(Hooks.hook_index_host4_identifier_)) {
448 break;
449 }
450
451 CalloutHandlePtr callout_handle = getCalloutHandle(context_->query_);
452
453 Host::IdentifierType type = Host::IDENT_FLEX;
454 std::vector<uint8_t> id;
455
456 // Use the RAII wrapper to make sure that the callout handle state is
457 // reset when this object goes out of scope. All hook points must do
458 // it to prevent possible circular dependency between the callout
459 // handle and its arguments.
460 ScopedCalloutHandleState callout_handle_state(callout_handle);
461
462 // Pass incoming packet as argument
463 callout_handle->setArgument("query4", context_->query_);
464 callout_handle->setArgument("id_type", type);
465 callout_handle->setArgument("id_value", id);
466
467 // Call callouts
468 HooksManager::callCallouts(Hooks.hook_index_host4_identifier_,
469 *callout_handle);
470
471 callout_handle->getArgument("id_type", type);
472 callout_handle->getArgument("id_value", id);
473
474 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_CONTINUE) &&
475 !id.empty()) {
476
477 LOG_DEBUG(packet4_logger, DBGLVL_TRACE_BASIC, DHCP4_FLEX_ID)
478 .arg(Host::getIdentifierAsText(type, &id[0], id.size()));
479
480 context_->addHostIdentifier(type, id);
481 }
482 break;
483 }
484 default:
485 ;
486 }
487 }
488 }
489
490 void
setReservedClientClasses(AllocEngine::ClientContext4Ptr context)491 Dhcpv4Exchange::setReservedClientClasses(AllocEngine::ClientContext4Ptr context) {
492 if (context->currentHost() && context->query_) {
493 const ClientClasses& classes = context->currentHost()->getClientClasses4();
494 for (ClientClasses::const_iterator cclass = classes.cbegin();
495 cclass != classes.cend(); ++cclass) {
496 context->query_->addClass(*cclass);
497 }
498 }
499 }
500
501 void
conditionallySetReservedClientClasses()502 Dhcpv4Exchange::conditionallySetReservedClientClasses() {
503 if (context_->subnet_) {
504 SharedNetwork4Ptr shared_network;
505 context_->subnet_->getSharedNetwork(shared_network);
506 if (shared_network) {
507 ConstHostPtr host = context_->currentHost();
508 if (host && (host->getIPv4SubnetID() != SUBNET_ID_GLOBAL)) {
509 setReservedClientClasses(context_);
510 }
511 }
512 }
513 }
514
515 void
setReservedMessageFields()516 Dhcpv4Exchange::setReservedMessageFields() {
517 ConstHostPtr host = context_->currentHost();
518 // Nothing to do if host reservations not specified for this client.
519 if (host) {
520 if (!host->getNextServer().isV4Zero()) {
521 resp_->setSiaddr(host->getNextServer());
522 }
523
524 std::string sname = host->getServerHostname();
525 if (!sname.empty()) {
526 resp_->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
527 sname.size());
528 }
529
530 std::string bootfile = host->getBootFileName();
531 if (!bootfile.empty()) {
532 resp_->setFile(reinterpret_cast<const uint8_t*>(bootfile.c_str()),
533 bootfile.size());
534 }
535 }
536 }
537
classifyByVendor(const Pkt4Ptr & pkt)538 void Dhcpv4Exchange::classifyByVendor(const Pkt4Ptr& pkt) {
539 // Built-in vendor class processing
540 boost::shared_ptr<OptionString> vendor_class =
541 boost::dynamic_pointer_cast<OptionString>(pkt->getOption(DHO_VENDOR_CLASS_IDENTIFIER));
542
543 if (!vendor_class) {
544 return;
545 }
546
547 pkt->addClass(Dhcpv4Srv::VENDOR_CLASS_PREFIX + vendor_class->getValue());
548 }
549
classifyPacket(const Pkt4Ptr & pkt)550 void Dhcpv4Exchange::classifyPacket(const Pkt4Ptr& pkt) {
551 // All packets belongs to ALL.
552 pkt->addClass("ALL");
553
554 // First: built-in vendor class processing.
555 classifyByVendor(pkt);
556
557 // Run match expressions on classes not depending on KNOWN/UNKNOWN.
558 evaluateClasses(pkt, false);
559 }
560
evaluateClasses(const Pkt4Ptr & pkt,bool depend_on_known)561 void Dhcpv4Exchange::evaluateClasses(const Pkt4Ptr& pkt, bool depend_on_known) {
562 // Note getClientClassDictionary() cannot be null
563 const ClientClassDictionaryPtr& dict =
564 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
565 const ClientClassDefListPtr& defs_ptr = dict->getClasses();
566 for (ClientClassDefList::const_iterator it = defs_ptr->cbegin();
567 it != defs_ptr->cend(); ++it) {
568 // Note second cannot be null
569 const ExpressionPtr& expr_ptr = (*it)->getMatchExpr();
570 // Nothing to do without an expression to evaluate
571 if (!expr_ptr) {
572 continue;
573 }
574 // Not the right time if only when required
575 if ((*it)->getRequired()) {
576 continue;
577 }
578 // Not the right pass.
579 if ((*it)->getDependOnKnown() != depend_on_known) {
580 continue;
581 }
582 // Evaluate the expression which can return false (no match),
583 // true (match) or raise an exception (error)
584 try {
585 bool status = evaluateBool(*expr_ptr, *pkt);
586 if (status) {
587 LOG_INFO(options4_logger, EVAL_RESULT)
588 .arg((*it)->getName())
589 .arg(status);
590 // Matching: add the class
591 pkt->addClass((*it)->getName());
592 } else {
593 LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
594 .arg((*it)->getName())
595 .arg(status);
596 }
597 } catch (const Exception& ex) {
598 LOG_ERROR(options4_logger, EVAL_RESULT)
599 .arg((*it)->getName())
600 .arg(ex.what());
601 } catch (...) {
602 LOG_ERROR(options4_logger, EVAL_RESULT)
603 .arg((*it)->getName())
604 .arg("get exception?");
605 }
606 }
607 }
608
609 const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
610
Dhcpv4Srv(uint16_t server_port,uint16_t client_port,const bool use_bcast,const bool direct_response_desired)611 Dhcpv4Srv::Dhcpv4Srv(uint16_t server_port, uint16_t client_port,
612 const bool use_bcast, const bool direct_response_desired)
613 : io_service_(new IOService()), server_port_(server_port),
614 client_port_(client_port), shutdown_(true),
615 alloc_engine_(), use_bcast_(use_bcast),
616 network_state_(new NetworkState(NetworkState::DHCPv4)),
617 cb_control_(new CBControlDHCPv4()),
618 test_send_responses_to_source_(false) {
619
620 const char* env = std::getenv("KEA_TEST_SEND_RESPONSES_TO_SOURCE");
621 if (env) {
622 LOG_WARN(dhcp4_logger, DHCP4_TESTING_MODE_SEND_TO_SOURCE_ENABLED);
623 test_send_responses_to_source_ = true;
624 }
625
626 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET)
627 .arg(server_port);
628
629 try {
630 // Port 0 is used for testing purposes where we don't open broadcast
631 // capable sockets. So, set the packet filter handling direct traffic
632 // only if we are in non-test mode.
633 if (server_port) {
634 // First call to instance() will create IfaceMgr (it's a singleton)
635 // it may throw something if things go wrong.
636 // The 'true' value of the call to setMatchingPacketFilter imposes
637 // that IfaceMgr will try to use the mechanism to respond directly
638 // to the client which doesn't have address assigned. This capability
639 // may be lacking on some OSes, so there is no guarantee that server
640 // will be able to respond directly.
641 IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
642 }
643
644 // Instantiate allocation engine. The number of allocation attempts equal
645 // to zero indicates that the allocation engine will use the number of
646 // attempts depending on the pool size.
647 alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 0,
648 false /* false = IPv4 */));
649
650 /// @todo call loadLibraries() when handling configuration changes
651
652 } catch (const std::exception &e) {
653 LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
654 shutdown_ = true;
655 return;
656 }
657
658 // Initializing all observations with default value
659 setPacketStatisticsDefaults();
660 shutdown_ = false;
661 }
662
setPacketStatisticsDefaults()663 void Dhcpv4Srv::setPacketStatisticsDefaults() {
664 isc::stats::StatsMgr& stats_mgr = isc::stats::StatsMgr::instance();
665
666 // Iterate over set of observed statistics
667 for (auto it = dhcp4_statistics.begin(); it != dhcp4_statistics.end(); ++it) {
668 // Initialize them with default value 0
669 stats_mgr.setValue((*it), static_cast<int64_t>(0));
670 }
671 }
672
~Dhcpv4Srv()673 Dhcpv4Srv::~Dhcpv4Srv() {
674 // Discard any parked packets
675 discardPackets();
676
677 try {
678 stopD2();
679 } catch (const std::exception& ex) {
680 // Highly unlikely, but lets Report it but go on
681 LOG_ERROR(dhcp4_logger, DHCP4_SRV_D2STOP_ERROR).arg(ex.what());
682 }
683
684 try {
685 Dhcp4to6Ipc::instance().close();
686 } catch (const std::exception& ex) {
687 // Highly unlikely, but lets Report it but go on
688 LOG_ERROR(dhcp4_logger, DHCP4_SRV_DHCP4O6_ERROR).arg(ex.what());
689 }
690
691 IfaceMgr::instance().closeSockets();
692
693 // The lease manager was instantiated during DHCPv4Srv configuration,
694 // so we should clean up after ourselves.
695 LeaseMgrFactory::destroy();
696
697 // Explicitly unload hooks
698 HooksManager::prepareUnloadLibraries();
699 if (!HooksManager::unloadLibraries()) {
700 auto names = HooksManager::getLibraryNames();
701 std::string msg;
702 if (!names.empty()) {
703 msg = names[0];
704 for (size_t i = 1; i < names.size(); ++i) {
705 msg += std::string(", ") + names[i];
706 }
707 }
708 LOG_ERROR(dhcp4_logger, DHCP4_SRV_UNLOAD_LIBRARIES_ERROR).arg(msg);
709 }
710 }
711
712 void
shutdown()713 Dhcpv4Srv::shutdown() {
714 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_SHUTDOWN_REQUEST);
715 shutdown_ = true;
716 }
717
718 isc::dhcp::Subnet4Ptr
selectSubnet(const Pkt4Ptr & query,bool & drop,bool sanity_only) const719 Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query, bool& drop,
720 bool sanity_only) const {
721
722 // DHCPv4-over-DHCPv6 is a special (and complex) case
723 if (query->isDhcp4o6()) {
724 return (selectSubnet4o6(query, drop, sanity_only));
725 }
726
727 Subnet4Ptr subnet;
728
729 const SubnetSelector& selector = CfgSubnets4::initSelector(query);
730
731 CfgMgr& cfgmgr = CfgMgr::instance();
732 subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
733
734 // Let's execute all callouts registered for subnet4_select
735 // (skip callouts if the selectSubnet was called to do sanity checks only)
736 if (!sanity_only &&
737 HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
738 CalloutHandlePtr callout_handle = getCalloutHandle(query);
739
740 // Use the RAII wrapper to make sure that the callout handle state is
741 // reset when this object goes out of scope. All hook points must do
742 // it to prevent possible circular dependency between the callout
743 // handle and its arguments.
744 ScopedCalloutHandleState callout_handle_state(callout_handle);
745
746 // Enable copying options from the packet within hook library.
747 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
748
749 // Set new arguments
750 callout_handle->setArgument("query4", query);
751 callout_handle->setArgument("subnet4", subnet);
752 callout_handle->setArgument("subnet4collection",
753 cfgmgr.getCurrentCfg()->
754 getCfgSubnets4()->getAll());
755
756 // Call user (and server-side) callouts
757 HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
758 *callout_handle);
759
760 // Callouts decided to skip this step. This means that no subnet
761 // will be selected. Packet processing will continue, but it will
762 // be severely limited (i.e. only global options will be assigned)
763 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
764 LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
765 DHCP4_HOOK_SUBNET4_SELECT_SKIP)
766 .arg(query->getLabel());
767 return (Subnet4Ptr());
768 }
769
770 // Callouts decided to drop the packet. It is a superset of the
771 // skip case so no subnet will be selected.
772 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
773 LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
774 DHCP4_HOOK_SUBNET4_SELECT_DROP)
775 .arg(query->getLabel());
776 drop = true;
777 return (Subnet4Ptr());
778 }
779
780 // Use whatever subnet was specified by the callout
781 callout_handle->getArgument("subnet4", subnet);
782 }
783
784 if (subnet) {
785 // Log at higher debug level that subnet has been found.
786 LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_SELECTED)
787 .arg(query->getLabel())
788 .arg(subnet->getID());
789 // Log detailed information about the selected subnet at the
790 // lower debug level.
791 LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_DATA)
792 .arg(query->getLabel())
793 .arg(subnet->toText());
794
795 } else {
796 LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL,
797 DHCP4_SUBNET_SELECTION_FAILED)
798 .arg(query->getLabel());
799 }
800
801 return (subnet);
802 }
803
804 isc::dhcp::Subnet4Ptr
selectSubnet4o6(const Pkt4Ptr & query,bool & drop,bool sanity_only) const805 Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query, bool& drop,
806 bool sanity_only) const {
807
808 Subnet4Ptr subnet;
809
810 SubnetSelector selector;
811 selector.ciaddr_ = query->getCiaddr();
812 selector.giaddr_ = query->getGiaddr();
813 selector.local_address_ = query->getLocalAddr();
814 selector.client_classes_ = query->classes_;
815 selector.iface_name_ = query->getIface();
816 // Mark it as DHCPv4-over-DHCPv6
817 selector.dhcp4o6_ = true;
818 // Now the DHCPv6 part
819 selector.remote_address_ = query->getRemoteAddr();
820 selector.first_relay_linkaddr_ = IOAddress("::");
821
822 // Handle a DHCPv6 relayed query
823 Pkt4o6Ptr query4o6 = boost::dynamic_pointer_cast<Pkt4o6>(query);
824 if (!query4o6) {
825 isc_throw(Unexpected, "Can't get DHCP4o6 message");
826 }
827 const Pkt6Ptr& query6 = query4o6->getPkt6();
828
829 // Initialize fields specific to relayed messages.
830 if (query6 && !query6->relay_info_.empty()) {
831 BOOST_REVERSE_FOREACH(Pkt6::RelayInfo relay, query6->relay_info_) {
832 if (!relay.linkaddr_.isV6Zero() &&
833 !relay.linkaddr_.isV6LinkLocal()) {
834 selector.first_relay_linkaddr_ = relay.linkaddr_;
835 break;
836 }
837 }
838 selector.interface_id_ =
839 query6->getAnyRelayOption(D6O_INTERFACE_ID,
840 Pkt6::RELAY_GET_FIRST);
841 }
842
843 // If the Subnet Selection option is present, extract its value.
844 OptionPtr sbnsel = query->getOption(DHO_SUBNET_SELECTION);
845 if (sbnsel) {
846 OptionCustomPtr oc = boost::dynamic_pointer_cast<OptionCustom>(sbnsel);
847 if (oc) {
848 selector.option_select_ = oc->readAddress();
849 }
850 }
851
852 CfgMgr& cfgmgr = CfgMgr::instance();
853 subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet4o6(selector);
854
855 // Let's execute all callouts registered for subnet4_select.
856 // (skip callouts if the selectSubnet was called to do sanity checks only)
857 if (!sanity_only &&
858 HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
859 CalloutHandlePtr callout_handle = getCalloutHandle(query);
860
861 // Use the RAII wrapper to make sure that the callout handle state is
862 // reset when this object goes out of scope. All hook points must do
863 // it to prevent possible circular dependency between the callout
864 // handle and its arguments.
865 ScopedCalloutHandleState callout_handle_state(callout_handle);
866
867 // Set new arguments
868 callout_handle->setArgument("query4", query);
869 callout_handle->setArgument("subnet4", subnet);
870 callout_handle->setArgument("subnet4collection",
871 cfgmgr.getCurrentCfg()->
872 getCfgSubnets4()->getAll());
873
874 // Call user (and server-side) callouts
875 HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
876 *callout_handle);
877
878 // Callouts decided to skip this step. This means that no subnet
879 // will be selected. Packet processing will continue, but it will
880 // be severely limited (i.e. only global options will be assigned)
881 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
882 LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
883 DHCP4_HOOK_SUBNET4_SELECT_SKIP)
884 .arg(query->getLabel());
885 return (Subnet4Ptr());
886 }
887
888 // Callouts decided to drop the packet. It is a superset of the
889 // skip case so no subnet will be selected.
890 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
891 LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
892 DHCP4_HOOK_SUBNET4_SELECT_DROP)
893 .arg(query->getLabel());
894 drop = true;
895 return (Subnet4Ptr());
896 }
897
898 // Use whatever subnet was specified by the callout
899 callout_handle->getArgument("subnet4", subnet);
900 }
901
902 if (subnet) {
903 // Log at higher debug level that subnet has been found.
904 LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_SELECTED)
905 .arg(query->getLabel())
906 .arg(subnet->getID());
907 // Log detailed information about the selected subnet at the
908 // lower debug level.
909 LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_DATA)
910 .arg(query->getLabel())
911 .arg(subnet->toText());
912
913 } else {
914 LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL,
915 DHCP4_SUBNET_SELECTION_FAILED)
916 .arg(query->getLabel());
917 }
918
919 return (subnet);
920 }
921
922 Pkt4Ptr
receivePacket(int timeout)923 Dhcpv4Srv::receivePacket(int timeout) {
924 return (IfaceMgr::instance().receive4(timeout));
925 }
926
927 void
sendPacket(const Pkt4Ptr & packet)928 Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
929 IfaceMgr::instance().send(packet);
930 }
931
932 int
run()933 Dhcpv4Srv::run() {
934 #ifdef ENABLE_AFL
935 // Set up structures needed for fuzzing.
936 Fuzz fuzzer(4, server_port_);
937 //
938 // The next line is needed as a signature for AFL to recognize that we are
939 // running persistent fuzzing. This has to be in the main image file.
940 while (__AFL_LOOP(fuzzer.maxLoopCount())) {
941 // Read from stdin and put the data read into an address/port on which
942 // Kea is listening, read for Kea to read it via asynchronous I/O.
943 fuzzer.transfer();
944 #else
945 while (!shutdown_) {
946 #endif // ENABLE_AFL
947 try {
948 run_one();
949 getIOService()->poll();
950 } catch (const std::exception& e) {
951 // General catch-all exception that are not caught by more specific
952 // catches. This one is for exceptions derived from std::exception.
953 LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_STD_EXCEPTION)
954 .arg(e.what());
955 } catch (...) {
956 // General catch-all exception that are not caught by more specific
957 // catches. This one is for other exceptions, not derived from
958 // std::exception.
959 LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION);
960 }
961 }
962
963 // Stop everything before we change into single-threaded mode.
964 MultiThreadingCriticalSection cs;
965
966 // destroying the thread pool
967 MultiThreadingMgr::instance().apply(false, 0, 0);
968
969 return (getExitValue());
970 }
971
972 void
973 Dhcpv4Srv::run_one() {
974 // client's message and server's response
975 Pkt4Ptr query;
976
977 try {
978 // Set select() timeout to 1s. This value should not be modified
979 // because it is important that the select() returns control
980 // frequently so as the IOService can be polled for ready handlers.
981 uint32_t timeout = 1;
982 query = receivePacket(timeout);
983
984 // Log if packet has arrived. We can't log the detailed information
985 // about the DHCP message because it hasn't been unpacked/parsed
986 // yet, and it can't be parsed at this point because hooks will
987 // have to process it first. The only information available at this
988 // point are: the interface, source address and destination addresses
989 // and ports.
990 if (query) {
991 LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_BUFFER_RECEIVED)
992 .arg(query->getRemoteAddr().toText())
993 .arg(query->getRemotePort())
994 .arg(query->getLocalAddr().toText())
995 .arg(query->getLocalPort())
996 .arg(query->getIface());
997 }
998
999 // We used to log that the wait was interrupted, but this is no longer
1000 // the case. Our wait time is 1s now, so the lack of query packet more
1001 // likely means that nothing new appeared within a second, rather than
1002 // we were interrupted. And we don't want to print a message every
1003 // second.
1004
1005 } catch (const SignalInterruptOnSelect&) {
1006 // Packet reception interrupted because a signal has been received.
1007 // This is not an error because we might have received a SIGTERM,
1008 // SIGINT, SIGHUP or SIGCHLD which are handled by the server. For
1009 // signals that are not handled by the server we rely on the default
1010 // behavior of the system.
1011 LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_BUFFER_WAIT_SIGNAL);
1012 } catch (const std::exception& e) {
1013 // Log all other errors.
1014 LOG_ERROR(packet4_logger, DHCP4_BUFFER_RECEIVE_FAIL).arg(e.what());
1015 }
1016
1017 // Timeout may be reached or signal received, which breaks select()
1018 // with no reception occurred. No need to log anything here because
1019 // we have logged right after the call to receivePacket().
1020 if (!query) {
1021 return;
1022 }
1023
1024 // If the DHCP service has been globally disabled, drop the packet.
1025 if (!network_state_->isServiceEnabled()) {
1026 LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0008)
1027 .arg(query->getLabel());
1028 return;
1029 } else {
1030 if (MultiThreadingMgr::instance().getMode()) {
1031 typedef function<void()> CallBack;
1032 boost::shared_ptr<CallBack> call_back =
1033 boost::make_shared<CallBack>(std::bind(&Dhcpv4Srv::processPacketAndSendResponseNoThrow,
1034 this, query));
1035 if (!MultiThreadingMgr::instance().getThreadPool().add(call_back)) {
1036 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_QUEUE_FULL);
1037 }
1038 } else {
1039 processPacketAndSendResponse(query);
1040 }
1041 }
1042 }
1043
1044 void
1045 Dhcpv4Srv::processPacketAndSendResponseNoThrow(Pkt4Ptr& query) {
1046 try {
1047 processPacketAndSendResponse(query);
1048 } catch (const std::exception& e) {
1049 LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_STD_EXCEPTION)
1050 .arg(e.what());
1051 } catch (...) {
1052 LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION);
1053 }
1054 }
1055
1056 void
1057 Dhcpv4Srv::processPacketAndSendResponse(Pkt4Ptr& query) {
1058 Pkt4Ptr rsp;
1059 processPacket(query, rsp);
1060 if (!rsp) {
1061 return;
1062 }
1063
1064 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1065 processPacketBufferSend(callout_handle, rsp);
1066 }
1067
1068 void
1069 Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp, bool allow_packet_park) {
1070 // Log reception of the packet. We need to increase it early, as any
1071 // failures in unpacking will cause the packet to be dropped. We
1072 // will increase type specific statistic further down the road.
1073 // See processStatsReceived().
1074 isc::stats::StatsMgr::instance().addValue("pkt4-received",
1075 static_cast<int64_t>(1));
1076
1077 bool skip_unpack = false;
1078
1079 // The packet has just been received so contains the uninterpreted wire
1080 // data; execute callouts registered for buffer4_receive.
1081 if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_receive_)) {
1082 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1083
1084 // Use the RAII wrapper to make sure that the callout handle state is
1085 // reset when this object goes out of scope. All hook points must do
1086 // it to prevent possible circular dependency between the callout
1087 // handle and its arguments.
1088 ScopedCalloutHandleState callout_handle_state(callout_handle);
1089
1090 // Enable copying options from the packet within hook library.
1091 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
1092
1093 // Pass incoming packet as argument
1094 callout_handle->setArgument("query4", query);
1095
1096 // Call callouts
1097 HooksManager::callCallouts(Hooks.hook_index_buffer4_receive_,
1098 *callout_handle);
1099
1100 // Callouts decided to drop the received packet.
1101 // The response (rsp) is null so the caller (run_one) will
1102 // immediately return too.
1103 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1104 LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING,
1105 DHCP4_HOOK_BUFFER_RCVD_DROP)
1106 .arg(query->getRemoteAddr().toText())
1107 .arg(query->getLocalAddr().toText())
1108 .arg(query->getIface());
1109 return;
1110 }
1111
1112 // Callouts decided to skip the next processing step. The next
1113 // processing step would to parse the packet, so skip at this
1114 // stage means that callouts did the parsing already, so server
1115 // should skip parsing.
1116 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
1117 LOG_DEBUG(hooks_logger, DBG_DHCP4_DETAIL,
1118 DHCP4_HOOK_BUFFER_RCVD_SKIP)
1119 .arg(query->getRemoteAddr().toText())
1120 .arg(query->getLocalAddr().toText())
1121 .arg(query->getIface());
1122 skip_unpack = true;
1123 }
1124
1125 callout_handle->getArgument("query4", query);
1126 }
1127
1128 // Unpack the packet information unless the buffer4_receive callouts
1129 // indicated they did it
1130 if (!skip_unpack) {
1131 try {
1132 LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, DHCP4_BUFFER_UNPACK)
1133 .arg(query->getRemoteAddr().toText())
1134 .arg(query->getLocalAddr().toText())
1135 .arg(query->getIface());
1136 query->unpack();
1137 } catch (const SkipRemainingOptionsError& e) {
1138 // An option failed to unpack but we are to attempt to process it
1139 // anyway. Log it and let's hope for the best.
1140 LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL,
1141 DHCP4_PACKET_OPTIONS_SKIPPED)
1142 .arg(e.what());
1143 } catch (const std::exception& e) {
1144 // Failed to parse the packet.
1145 LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0001)
1146 .arg(query->getRemoteAddr().toText())
1147 .arg(query->getLocalAddr().toText())
1148 .arg(query->getIface())
1149 .arg(e.what());
1150
1151 // Increase the statistics of parse failures and dropped packets.
1152 isc::stats::StatsMgr::instance().addValue("pkt4-parse-failed",
1153 static_cast<int64_t>(1));
1154 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1155 static_cast<int64_t>(1));
1156 return;
1157 }
1158 }
1159
1160 // Update statistics accordingly for received packet.
1161 processStatsReceived(query);
1162
1163 // Assign this packet to one or more classes if needed. We need to do
1164 // this before calling accept(), because getSubnet4() may need client
1165 // class information.
1166 classifyPacket(query);
1167
1168 // Now it is classified the deferred unpacking can be done.
1169 deferredUnpack(query);
1170
1171 // Check whether the message should be further processed or discarded.
1172 // There is no need to log anything here. This function logs by itself.
1173 if (!accept(query)) {
1174 // Increase the statistic of dropped packets.
1175 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1176 static_cast<int64_t>(1));
1177 return;
1178 }
1179
1180 // We have sanity checked (in accept() that the Message Type option
1181 // exists, so we can safely get it here.
1182 int type = query->getType();
1183 LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_PACKET_RECEIVED)
1184 .arg(query->getLabel())
1185 .arg(query->getName())
1186 .arg(type)
1187 .arg(query->getRemoteAddr())
1188 .arg(query->getLocalAddr())
1189 .arg(query->getIface());
1190 LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
1191 .arg(query->getLabel())
1192 .arg(query->toText());
1193
1194 // Let's execute all callouts registered for pkt4_receive
1195 if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_receive_)) {
1196 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1197
1198 // Use the RAII wrapper to make sure that the callout handle state is
1199 // reset when this object goes out of scope. All hook points must do
1200 // it to prevent possible circular dependency between the callout
1201 // handle and its arguments.
1202 ScopedCalloutHandleState callout_handle_state(callout_handle);
1203
1204 // Enable copying options from the packet within hook library.
1205 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
1206
1207 // Pass incoming packet as argument
1208 callout_handle->setArgument("query4", query);
1209
1210 // Call callouts
1211 HooksManager::callCallouts(Hooks.hook_index_pkt4_receive_,
1212 *callout_handle);
1213
1214 // Callouts decided to skip the next processing step. The next
1215 // processing step would to process the packet, so skip at this
1216 // stage means drop.
1217 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
1218 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
1219 LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
1220 DHCP4_HOOK_PACKET_RCVD_SKIP)
1221 .arg(query->getLabel());
1222 return;
1223 }
1224
1225 callout_handle->getArgument("query4", query);
1226 }
1227
1228 // Check the DROP special class.
1229 if (query->inClass("DROP")) {
1230 LOG_DEBUG(packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0010)
1231 .arg(query->toText());
1232 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1233 static_cast<int64_t>(1));
1234 return;
1235 }
1236
1237 processDhcp4Query(query, rsp, allow_packet_park);
1238 }
1239
1240 void
1241 Dhcpv4Srv::processDhcp4QueryAndSendResponse(Pkt4Ptr& query, Pkt4Ptr& rsp,
1242 bool allow_packet_park) {
1243 try {
1244 processDhcp4Query(query, rsp, allow_packet_park);
1245 if (!rsp) {
1246 return;
1247 }
1248
1249 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1250 processPacketBufferSend(callout_handle, rsp);
1251 } catch (const std::exception& e) {
1252 LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_STD_EXCEPTION)
1253 .arg(e.what());
1254 } catch (...) {
1255 LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION);
1256 }
1257 }
1258
1259 void
1260 Dhcpv4Srv::processDhcp4Query(Pkt4Ptr& query, Pkt4Ptr& rsp,
1261 bool allow_packet_park) {
1262 // Create a client race avoidance RAII handler.
1263 ClientHandler client_handler;
1264
1265 // Check for lease modifier queries from the same client being processed.
1266 if (MultiThreadingMgr::instance().getMode() &&
1267 ((query->getType() == DHCPDISCOVER) ||
1268 (query->getType() == DHCPREQUEST) ||
1269 (query->getType() == DHCPRELEASE) ||
1270 (query->getType() == DHCPDECLINE))) {
1271 ContinuationPtr cont =
1272 makeContinuation(std::bind(&Dhcpv4Srv::processDhcp4QueryAndSendResponse,
1273 this, query, rsp, allow_packet_park));
1274 if (!client_handler.tryLock(query, cont)) {
1275 return;
1276 }
1277 }
1278
1279 AllocEngine::ClientContext4Ptr ctx;
1280
1281 try {
1282 switch (query->getType()) {
1283 case DHCPDISCOVER:
1284 rsp = processDiscover(query);
1285 break;
1286
1287 case DHCPREQUEST:
1288 // Note that REQUEST is used for many things in DHCPv4: for
1289 // requesting new leases, renewing existing ones and even
1290 // for rebinding.
1291 rsp = processRequest(query, ctx);
1292 break;
1293
1294 case DHCPRELEASE:
1295 processRelease(query, ctx);
1296 break;
1297
1298 case DHCPDECLINE:
1299 processDecline(query, ctx);
1300 break;
1301
1302 case DHCPINFORM:
1303 rsp = processInform(query);
1304 break;
1305
1306 default:
1307 // Only action is to output a message if debug is enabled,
1308 // and that is covered by the debug statement before the
1309 // "switch" statement.
1310 ;
1311 }
1312 } catch (const std::exception& e) {
1313
1314 // Catch-all exception (we used to call only isc::Exception, but
1315 // std::exception could potentially be raised and if we don't catch
1316 // it here, it would be caught in main() and the process would
1317 // terminate). Just log the problem and ignore the packet.
1318 // (The problem is logged as a debug message because debug is
1319 // disabled by default - it prevents a DDOS attack based on the
1320 // sending of problem packets.)
1321 LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0007)
1322 .arg(query->getLabel())
1323 .arg(e.what());
1324
1325 // Increase the statistic of dropped packets.
1326 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1327 static_cast<int64_t>(1));
1328 }
1329
1330 CalloutHandlePtr callout_handle = getCalloutHandle(query);
1331 if (ctx && HooksManager::calloutsPresent(Hooks.hook_index_leases4_committed_)) {
1332 // Use the RAII wrapper to make sure that the callout handle state is
1333 // reset when this object goes out of scope. All hook points must do
1334 // it to prevent possible circular dependency between the callout
1335 // handle and its arguments.
1336 ScopedCalloutHandleState callout_handle_state(callout_handle);
1337
1338 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(query);
1339
1340 // Also pass the corresponding query packet as argument
1341 callout_handle->setArgument("query4", query);
1342
1343 Lease4CollectionPtr new_leases(new Lease4Collection());
1344 // Filter out the new lease if it was reused so not committed.
1345 if (ctx->new_lease_ && (ctx->new_lease_->reuseable_valid_lft_ == 0)) {
1346 new_leases->push_back(ctx->new_lease_);
1347 }
1348 callout_handle->setArgument("leases4", new_leases);
1349
1350 Lease4CollectionPtr deleted_leases(new Lease4Collection());
1351 if (ctx->old_lease_) {
1352 if ((!ctx->new_lease_) || (ctx->new_lease_->addr_ != ctx->old_lease_->addr_)) {
1353 deleted_leases->push_back(ctx->old_lease_);
1354 }
1355 }
1356 callout_handle->setArgument("deleted_leases4", deleted_leases);
1357
1358 if (allow_packet_park) {
1359 // Get the parking limit. Parsing should ensure the value is present.
1360 uint32_t parked_packet_limit = 0;
1361 data::ConstElementPtr ppl = CfgMgr::instance().
1362 getCurrentCfg()->getConfiguredGlobal("parked-packet-limit");
1363 if (ppl) {
1364 parked_packet_limit = ppl->intValue();
1365 }
1366
1367 if (parked_packet_limit) {
1368 const auto& parking_lot = ServerHooks::getServerHooks().
1369 getParkingLotPtr("leases4_committed");
1370
1371 if (parking_lot && (parking_lot->size() >= parked_packet_limit)) {
1372 // We can't park it so we're going to throw it on the floor.
1373 LOG_DEBUG(packet4_logger, DBGLVL_PKT_HANDLING,
1374 DHCP4_HOOK_LEASES4_PARKING_LOT_FULL)
1375 .arg(parked_packet_limit)
1376 .arg(query->getLabel());
1377 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
1378 static_cast<int64_t>(1));
1379 rsp.reset();
1380 return;
1381 }
1382 }
1383
1384 // We proactively park the packet. We'll unpark it without invoking
1385 // the callback (i.e. drop) unless the callout status is set to
1386 // NEXT_STEP_PARK. Otherwise the callback we bind here will be
1387 // executed when the hook library unparks the packet.
1388 HooksManager::park("leases4_committed", query,
1389 [this, callout_handle, query, rsp]() mutable {
1390 if (MultiThreadingMgr::instance().getMode()) {
1391 typedef function<void()> CallBack;
1392 boost::shared_ptr<CallBack> call_back =
1393 boost::make_shared<CallBack>(std::bind(&Dhcpv4Srv::sendResponseNoThrow,
1394 this, callout_handle, query, rsp));
1395 MultiThreadingMgr::instance().getThreadPool().add(call_back);
1396 } else {
1397 processPacketPktSend(callout_handle, query, rsp);
1398 processPacketBufferSend(callout_handle, rsp);
1399 }
1400 });
1401 }
1402
1403 try {
1404 // Call all installed callouts
1405 HooksManager::callCallouts(Hooks.hook_index_leases4_committed_,
1406 *callout_handle);
1407 } catch (...) {
1408 // Make sure we don't orphan a parked packet.
1409 if (allow_packet_park) {
1410 HooksManager::drop("leases4_committed", query);
1411 }
1412
1413 throw;
1414 }
1415
1416 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK)
1417 && allow_packet_park) {
1418 LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_LEASES4_COMMITTED_PARK)
1419 .arg(query->getLabel());
1420 // Since the hook library(ies) are going to do the unparking, then
1421 // reset the pointer to the response to indicate to the caller that
1422 // it should return, as the packet processing will continue via
1423 // the callback.
1424 rsp.reset();
1425 } else {
1426 // Drop the park job on the packet, it isn't needed.
1427 HooksManager::drop("leases4_committed", query);
1428 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1429 LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING, DHCP4_HOOK_LEASES4_COMMITTED_DROP)
1430 .arg(query->getLabel());
1431 rsp.reset();
1432 }
1433 }
1434 }
1435
1436 // If we have a response prep it for shipment.
1437 if (rsp) {
1438 processPacketPktSend(callout_handle, query, rsp);
1439 }
1440 }
1441
1442 void
1443 Dhcpv4Srv::sendResponseNoThrow(hooks::CalloutHandlePtr& callout_handle,
1444 Pkt4Ptr& query, Pkt4Ptr& rsp) {
1445 try {
1446 processPacketPktSend(callout_handle, query, rsp);
1447 processPacketBufferSend(callout_handle, rsp);
1448 } catch (const std::exception& e) {
1449 LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_STD_EXCEPTION)
1450 .arg(e.what());
1451 } catch (...) {
1452 LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION);
1453 }
1454 }
1455
1456 void
1457 Dhcpv4Srv::processPacketPktSend(hooks::CalloutHandlePtr& callout_handle,
1458 Pkt4Ptr& query, Pkt4Ptr& rsp) {
1459 if (!rsp) {
1460 return;
1461 }
1462
1463 // Specifies if server should do the packing
1464 bool skip_pack = false;
1465
1466 // Execute all callouts registered for pkt4_send
1467 if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_send_)) {
1468
1469 // Use the RAII wrapper to make sure that the callout handle state is
1470 // reset when this object goes out of scope. All hook points must do
1471 // it to prevent possible circular dependency between the callout
1472 // handle and its arguments.
1473 ScopedCalloutHandleState callout_handle_state(callout_handle);
1474
1475 // Enable copying options from the query and response packets within
1476 // hook library.
1477 ScopedEnableOptionsCopy<Pkt4> query_resp_options_copy(query, rsp);
1478
1479 // Pass incoming packet as argument
1480 callout_handle->setArgument("query4", query);
1481
1482 // Set our response
1483 callout_handle->setArgument("response4", rsp);
1484
1485 // Call all installed callouts
1486 HooksManager::callCallouts(Hooks.hook_index_pkt4_send_,
1487 *callout_handle);
1488
1489 // Callouts decided to skip the next processing step. The next
1490 // processing step would to pack the packet (create wire data).
1491 // That step will be skipped if any callout sets skip flag.
1492 // It essentially means that the callout already did packing,
1493 // so the server does not have to do it again.
1494 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
1495 LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_SEND_SKIP)
1496 .arg(query->getLabel());
1497 skip_pack = true;
1498 }
1499
1500 /// Callouts decided to drop the packet.
1501 if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
1502 LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING, DHCP4_HOOK_PACKET_SEND_DROP)
1503 .arg(rsp->getLabel());
1504 rsp.reset();
1505 return;
1506 }
1507 }
1508
1509 if (!skip_pack) {
1510 try {
1511 LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_PACK)
1512 .arg(rsp->getLabel());
1513 rsp->pack();
1514 } catch (const std::exception& e) {
1515 LOG_ERROR(options4_logger, DHCP4_PACKET_PACK_FAIL)
1516 .arg(rsp->getLabel())
1517 .arg(e.what());
1518 }
1519 }
1520 }
1521
1522 void
1523 Dhcpv4Srv::processPacketBufferSend(CalloutHandlePtr& callout_handle,
1524 Pkt4Ptr& rsp) {
1525 if (!rsp) {
1526 return;
1527 }
1528
1529 try {
1530 // Now all fields and options are constructed into output wire buffer.
1531 // Option objects modification does not make sense anymore. Hooks
1532 // can only manipulate wire buffer at this stage.
1533 // Let's execute all callouts registered for buffer4_send
1534 if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_send_)) {
1535
1536 // Use the RAII wrapper to make sure that the callout handle state is
1537 // reset when this object goes out of scope. All hook points must do
1538 // it to prevent possible circular dependency between the callout
1539 // handle and its arguments.
1540 ScopedCalloutHandleState callout_handle_state(callout_handle);
1541
1542 // Enable copying options from the packet within hook library.
1543 ScopedEnableOptionsCopy<Pkt4> resp4_options_copy(rsp);
1544
1545 // Pass incoming packet as argument
1546 callout_handle->setArgument("response4", rsp);
1547
1548 // Call callouts
1549 HooksManager::callCallouts(Hooks.hook_index_buffer4_send_,
1550 *callout_handle);
1551
1552 // Callouts decided to skip the next processing step. The next
1553 // processing step would to parse the packet, so skip at this
1554 // stage means drop.
1555 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
1556 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
1557 LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
1558 DHCP4_HOOK_BUFFER_SEND_SKIP)
1559 .arg(rsp->getLabel());
1560 return;
1561 }
1562
1563 callout_handle->getArgument("response4", rsp);
1564 }
1565
1566 LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_SEND)
1567 .arg(rsp->getLabel())
1568 .arg(rsp->getName())
1569 .arg(static_cast<int>(rsp->getType()))
1570 .arg(rsp->getLocalAddr().isV4Zero() ? "*" : rsp->getLocalAddr().toText())
1571 .arg(rsp->getLocalPort())
1572 .arg(rsp->getRemoteAddr())
1573 .arg(rsp->getRemotePort())
1574 .arg(rsp->getIface().empty() ? "to be determined from routing" :
1575 rsp->getIface());
1576
1577 LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA,
1578 DHCP4_RESPONSE_DATA)
1579 .arg(rsp->getLabel())
1580 .arg(rsp->getName())
1581 .arg(static_cast<int>(rsp->getType()))
1582 .arg(rsp->toText());
1583 sendPacket(rsp);
1584
1585 // Update statistics accordingly for sent packet.
1586 processStatsSent(rsp);
1587
1588 } catch (const std::exception& e) {
1589 LOG_ERROR(packet4_logger, DHCP4_PACKET_SEND_FAIL)
1590 .arg(rsp->getLabel())
1591 .arg(e.what());
1592 }
1593 }
1594
1595 string
1596 Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
1597 if (!srvid) {
1598 isc_throw(BadValue, "NULL pointer passed to srvidToString()");
1599 }
1600 boost::shared_ptr<Option4AddrLst> generated =
1601 boost::dynamic_pointer_cast<Option4AddrLst>(srvid);
1602 if (!srvid) {
1603 isc_throw(BadValue, "Pointer to invalid option passed to srvidToString()");
1604 }
1605
1606 Option4AddrLst::AddressContainer addrs = generated->getAddresses();
1607 if (addrs.size() != 1) {
1608 isc_throw(BadValue, "Malformed option passed to srvidToString(). "
1609 << "Expected to contain a single IPv4 address.");
1610 }
1611
1612 return (addrs[0].toText());
1613 }
1614
1615 void
1616 Dhcpv4Srv::appendServerID(Dhcpv4Exchange& ex) {
1617
1618 // Do not append generated server identifier if there is one appended already.
1619 // This is when explicitly configured server identifier option is present.
1620 if (ex.getResponse()->getOption(DHO_DHCP_SERVER_IDENTIFIER)) {
1621 return;
1622 }
1623
1624 // Use local address on which the packet has been received as a
1625 // server identifier. In some cases it may be a different address,
1626 // e.g. broadcast packet or DHCPv4o6 packet.
1627 IOAddress local_addr = ex.getQuery()->getLocalAddr();
1628 Pkt4Ptr query = ex.getQuery();
1629
1630 if (local_addr.isV4Bcast() || query->isDhcp4o6()) {
1631 local_addr = IfaceMgr::instance().getSocket(query).addr_;
1632 }
1633
1634 OptionPtr opt_srvid(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
1635 local_addr));
1636 ex.getResponse()->addOption(opt_srvid);
1637 }
1638
1639 void
1640 Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
1641 CfgOptionList& co_list = ex.getCfgOptionList();
1642
1643 // Retrieve subnet.
1644 Subnet4Ptr subnet = ex.getContext()->subnet_;
1645 if (!subnet) {
1646 // All methods using the CfgOptionList object return soon when
1647 // there is no subnet so do the same
1648 return;
1649 }
1650
1651 // Firstly, host specific options.
1652 const ConstHostPtr& host = ex.getContext()->currentHost();
1653 if (host && !host->getCfgOption4()->empty()) {
1654 co_list.push_back(host->getCfgOption4());
1655 }
1656
1657 // Secondly, pool specific options.
1658 Pkt4Ptr resp = ex.getResponse();
1659 IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS();
1660 if (resp) {
1661 addr = resp->getYiaddr();
1662 }
1663 if (!addr.isV4Zero()) {
1664 PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
1665 if (pool && !pool->getCfgOption()->empty()) {
1666 co_list.push_back(pool->getCfgOption());
1667 }
1668 }
1669
1670 // Thirdly, subnet configured options.
1671 if (!subnet->getCfgOption()->empty()) {
1672 co_list.push_back(subnet->getCfgOption());
1673 }
1674
1675 // Fourthly, shared network specific options.
1676 SharedNetwork4Ptr network;
1677 subnet->getSharedNetwork(network);
1678 if (network && !network->getCfgOption()->empty()) {
1679 co_list.push_back(network->getCfgOption());
1680 }
1681
1682 // Each class in the incoming packet
1683 const ClientClasses& classes = ex.getQuery()->getClasses();
1684 for (ClientClasses::const_iterator cclass = classes.cbegin();
1685 cclass != classes.cend(); ++cclass) {
1686 // Find the client class definition for this class
1687 const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
1688 getClientClassDictionary()->findClass(*cclass);
1689 if (!ccdef) {
1690 // Not found: the class is built-in or not configured
1691 if (!isClientClassBuiltIn(*cclass)) {
1692 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNCONFIGURED)
1693 .arg(ex.getQuery()->getLabel())
1694 .arg(*cclass);
1695 }
1696 // Skip it
1697 continue;
1698 }
1699
1700 if (ccdef->getCfgOption()->empty()) {
1701 // Skip classes which don't configure options
1702 continue;
1703 }
1704
1705 co_list.push_back(ccdef->getCfgOption());
1706 }
1707
1708 // Last global options
1709 if (!CfgMgr::instance().getCurrentCfg()->getCfgOption()->empty()) {
1710 co_list.push_back(CfgMgr::instance().getCurrentCfg()->getCfgOption());
1711 }
1712 }
1713
1714 void
1715 Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
1716 // Get the subnet relevant for the client. We will need it
1717 // to get the options associated with it.
1718 Subnet4Ptr subnet = ex.getContext()->subnet_;
1719 // If we can't find the subnet for the client there is no way
1720 // to get the options to be sent to a client. We don't log an
1721 // error because it will be logged by the assignLease method
1722 // anyway.
1723 if (!subnet) {
1724 return;
1725 }
1726
1727 // Unlikely short cut
1728 const CfgOptionList& co_list = ex.getCfgOptionList();
1729 if (co_list.empty()) {
1730 return;
1731 }
1732
1733 Pkt4Ptr query = ex.getQuery();
1734 Pkt4Ptr resp = ex.getResponse();
1735 std::vector<uint8_t> requested_opts;
1736
1737 // try to get the 'Parameter Request List' option which holds the
1738 // codes of requested options.
1739 OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
1740 OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
1741 // Get the codes of requested options.
1742 if (option_prl) {
1743 requested_opts = option_prl->getValues();
1744 }
1745 // Iterate on the configured option list to add persistent options
1746 for (CfgOptionList::const_iterator copts = co_list.begin();
1747 copts != co_list.end(); ++copts) {
1748 const OptionContainerPtr& opts = (*copts)->getAll(DHCP4_OPTION_SPACE);
1749 if (!opts) {
1750 continue;
1751 }
1752 // Get persistent options
1753 const OptionContainerPersistIndex& idx = opts->get<2>();
1754 const OptionContainerPersistRange& range = idx.equal_range(true);
1755 for (OptionContainerPersistIndex::const_iterator desc = range.first;
1756 desc != range.second; ++desc) {
1757 // Add the persistent option code to requested options
1758 if (desc->option_) {
1759 uint8_t code = static_cast<uint8_t>(desc->option_->getType());
1760 requested_opts.push_back(code);
1761 }
1762 }
1763 }
1764
1765 // For each requested option code get the instance of the option
1766 // to be returned to the client.
1767 for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
1768 opt != requested_opts.end(); ++opt) {
1769 // Add nothing when it is already there
1770 if (!resp->getOption(*opt)) {
1771 // Iterate on the configured option list
1772 for (CfgOptionList::const_iterator copts = co_list.begin();
1773 copts != co_list.end(); ++copts) {
1774 OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE, *opt);
1775 // Got it: add it and jump to the outer loop
1776 if (desc.option_) {
1777 resp->addOption(desc.option_);
1778 break;
1779 }
1780 }
1781 }
1782 }
1783 }
1784
1785 void
1786 Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
1787 // Get the configured subnet suitable for the incoming packet.
1788 Subnet4Ptr subnet = ex.getContext()->subnet_;
1789 // Leave if there is no subnet matching the incoming packet.
1790 // There is no need to log the error message here because
1791 // it will be logged in the assignLease() when it fails to
1792 // pick the suitable subnet. We don't want to duplicate
1793 // error messages in such case.
1794 if (!subnet) {
1795 return;
1796 }
1797
1798 // Unlikely short cut
1799 const CfgOptionList& co_list = ex.getCfgOptionList();
1800 if (co_list.empty()) {
1801 return;
1802 }
1803
1804 uint32_t vendor_id = 0;
1805
1806 // Try to get the vendor option from the client packet. This is how it's
1807 // supposed to be done. Client sends vivso, we look at the vendor-id and
1808 // then send back the vendor options specific to that client.
1809 boost::shared_ptr<OptionVendor> vendor_req = boost::dynamic_pointer_cast<
1810 OptionVendor>(ex.getQuery()->getOption(DHO_VIVSO_SUBOPTIONS));
1811 if (vendor_req) {
1812 vendor_id = vendor_req->getVendorId();
1813 }
1814
1815 // Something is fishy. Client was supposed to send vivso, but didn't.
1816 // Let's try an alternative. It's possible that the server already
1817 // inserted vivso in the response message, (e.g. by using client
1818 // classification or perhaps a hook inserted it).
1819 boost::shared_ptr<OptionVendor> vendor_rsp = boost::dynamic_pointer_cast<
1820 OptionVendor>(ex.getResponse()->getOption(DHO_VIVSO_SUBOPTIONS));
1821 if (vendor_rsp) {
1822 vendor_id = vendor_rsp->getVendorId();
1823 }
1824
1825 if (!vendor_req && !vendor_rsp) {
1826 // Ok, we're out of luck today. Neither client nor server packets
1827 // have vivso. There is no way to figure out vendor-id here.
1828 // We give up.
1829 return;
1830 }
1831
1832 std::vector<uint8_t> requested_opts;
1833
1834 // Let's try to get ORO within that vendor-option.
1835 // This is specific to vendor-id=4491 (Cable Labs). Other vendors may have
1836 // different policies.
1837 OptionUint8ArrayPtr oro;
1838 if (vendor_id == VENDOR_ID_CABLE_LABS && vendor_req) {
1839 OptionPtr oro_generic = vendor_req->getOption(DOCSIS3_V4_ORO);
1840 if (oro_generic) {
1841 // Vendor ID 4491 makes Kea look at DOCSIS3_V4_OPTION_DEFINITIONS
1842 // when parsing options. Based on that, oro_generic will have been
1843 // created as an OptionUint8Array, but might not be for other
1844 // vendor IDs.
1845 oro = boost::dynamic_pointer_cast<OptionUint8Array>(oro_generic);
1846 // Get the list of options that client requested.
1847 if (oro) {
1848 requested_opts = oro->getValues();
1849 }
1850 }
1851 }
1852
1853 // Iterate on the configured option list to add persistent options
1854 for (CfgOptionList::const_iterator copts = co_list.begin();
1855 copts != co_list.end(); ++copts) {
1856 const OptionContainerPtr& opts = (*copts)->getAll(vendor_id);
1857 if (!opts) {
1858 continue;
1859 }
1860
1861 // Get persistent options
1862 const OptionContainerPersistIndex& idx = opts->get<2>();
1863 const OptionContainerPersistRange& range = idx.equal_range(true);
1864 for (OptionContainerPersistIndex::const_iterator desc = range.first;
1865 desc != range.second; ++desc) {
1866 // Add the persistent option code to requested options
1867 if (desc->option_) {
1868 uint8_t code = static_cast<uint8_t>(desc->option_->getType());
1869 requested_opts.push_back(code);
1870 }
1871 }
1872 }
1873
1874 // If there is nothing to add don't do anything then.
1875 if (requested_opts.empty()) {
1876 return;
1877 }
1878
1879 if (!vendor_rsp) {
1880 // It's possible that vivso was inserted already by client class or
1881 // a hook. If that is so, let's use it.
1882 vendor_rsp.reset(new OptionVendor(Option::V4, vendor_id));
1883 }
1884
1885 // Get the list of options that client requested.
1886 bool added = false;
1887 for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
1888 code != requested_opts.end(); ++code) {
1889 if (!vendor_rsp->getOption(*code)) {
1890 for (CfgOptionList::const_iterator copts = co_list.begin();
1891 copts != co_list.end(); ++copts) {
1892 OptionDescriptor desc = (*copts)->get(vendor_id, *code);
1893 if (desc.option_) {
1894 vendor_rsp->addOption(desc.option_);
1895 added = true;
1896 break;
1897 }
1898 }
1899 }
1900 }
1901
1902 // If we added some sub-options and the vivso option is not in
1903 // the response already, then add it.
1904 if (added && !ex.getResponse()->getOption(DHO_VIVSO_SUBOPTIONS)) {
1905 ex.getResponse()->addOption(vendor_rsp);
1906 }
1907 }
1908
1909 void
1910 Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
1911 // Identify options that we always want to send to the
1912 // client (if they are configured).
1913 static const uint16_t required_options[] = {
1914 DHO_ROUTERS,
1915 DHO_DOMAIN_NAME_SERVERS,
1916 DHO_DOMAIN_NAME,
1917 DHO_DHCP_SERVER_IDENTIFIER };
1918
1919 static size_t required_options_size =
1920 sizeof(required_options) / sizeof(required_options[0]);
1921
1922 // Get the subnet.
1923 Subnet4Ptr subnet = ex.getContext()->subnet_;
1924 if (!subnet) {
1925 return;
1926 }
1927
1928 // Unlikely short cut
1929 const CfgOptionList& co_list = ex.getCfgOptionList();
1930 if (co_list.empty()) {
1931 return;
1932 }
1933
1934 Pkt4Ptr resp = ex.getResponse();
1935
1936 // Try to find all 'required' options in the outgoing
1937 // message. Those that are not present will be added.
1938 for (int i = 0; i < required_options_size; ++i) {
1939 OptionPtr opt = resp->getOption(required_options[i]);
1940 if (!opt) {
1941 // Check whether option has been configured.
1942 for (CfgOptionList::const_iterator copts = co_list.begin();
1943 copts != co_list.end(); ++copts) {
1944 OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE,
1945 required_options[i]);
1946 if (desc.option_) {
1947 resp->addOption(desc.option_);
1948 break;
1949 }
1950 }
1951 }
1952 }
1953 }
1954
1955 void
1956 Dhcpv4Srv::processClientName(Dhcpv4Exchange& ex) {
1957 // It is possible that client has sent both Client FQDN and Hostname
1958 // option. In that the server should prefer Client FQDN option and
1959 // ignore the Hostname option.
1960 try {
1961 Pkt4Ptr resp = ex.getResponse();
1962 Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>
1963 (ex.getQuery()->getOption(DHO_FQDN));
1964 if (fqdn) {
1965 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_FQDN_PROCESS)
1966 .arg(ex.getQuery()->getLabel());
1967 processClientFqdnOption(ex);
1968
1969 } else {
1970 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL,
1971 DHCP4_CLIENT_HOSTNAME_PROCESS)
1972 .arg(ex.getQuery()->getLabel());
1973 processHostnameOption(ex);
1974 }
1975
1976 // Based on the output option added to the response above, we figure out
1977 // the values for the hostname and dns flags to set in the context. These
1978 // will be used to populate the lease.
1979 std::string hostname;
1980 bool fqdn_fwd = false;
1981 bool fqdn_rev = false;
1982 OptionStringPtr opt_hostname;
1983 fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
1984 if (fqdn) {
1985 hostname = fqdn->getDomainName();
1986 CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn, fqdn_fwd, fqdn_rev);
1987 } else {
1988 opt_hostname = boost::dynamic_pointer_cast<OptionString>
1989 (resp->getOption(DHO_HOST_NAME));
1990
1991 if (opt_hostname) {
1992 hostname = opt_hostname->getValue();
1993 // DHO_HOST_NAME is string option which cannot be blank,
1994 // we use "." to know we should replace it with a fully
1995 // generated name. The local string variable needs to be
1996 // blank in logic below.
1997 if (hostname == ".") {
1998 hostname = "";
1999 }
2000
2001 /// @todo It could be configurable what sort of updates the
2002 /// server is doing when Hostname option was sent.
2003 if (ex.getContext()->getDdnsParams()->getEnableUpdates()) {
2004 fqdn_fwd = true;
2005 fqdn_rev = true;
2006 }
2007 }
2008 }
2009
2010 // Update the context
2011 auto ctx = ex.getContext();
2012 ctx->fwd_dns_update_ = fqdn_fwd;
2013 ctx->rev_dns_update_ = fqdn_rev;
2014 ctx->hostname_ = hostname;
2015
2016 } catch (const Exception& e) {
2017 // In some rare cases it is possible that the client's name processing
2018 // fails. For example, the Hostname option may be malformed, or there
2019 // may be an error in the server's logic which would cause multiple
2020 // attempts to add the same option to the response message. This
2021 // error message aggregates all these errors so they can be diagnosed
2022 // from the log. We don't want to throw an exception here because,
2023 // it will impact the processing of the whole packet. We rather want
2024 // the processing to continue, even if the client's name is wrong.
2025 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_CLIENT_NAME_PROC_FAIL)
2026 .arg(ex.getQuery()->getLabel())
2027 .arg(e.what());
2028 }
2029
2030 }
2031
2032 void
2033 Dhcpv4Srv::processClientFqdnOption(Dhcpv4Exchange& ex) {
2034 // Obtain the FQDN option from the client's message.
2035 Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
2036 Option4ClientFqdn>(ex.getQuery()->getOption(DHO_FQDN));
2037
2038 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_CLIENT_FQDN_DATA)
2039 .arg(ex.getQuery()->getLabel())
2040 .arg(fqdn->toText());
2041
2042 // Create the DHCPv4 Client FQDN Option to be included in the server's
2043 // response to a client.
2044 Option4ClientFqdnPtr fqdn_resp(new Option4ClientFqdn(*fqdn));
2045
2046 // Set the server S, N, and O flags based on client's flags and
2047 // current configuration.
2048 D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
2049 d2_mgr.adjustFqdnFlags<Option4ClientFqdn>(*fqdn, *fqdn_resp,
2050 *(ex.getContext()->getDdnsParams()));
2051 // Carry over the client's E flag.
2052 fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E,
2053 fqdn->getFlag(Option4ClientFqdn::FLAG_E));
2054
2055 if (ex.getContext()->currentHost() &&
2056 !ex.getContext()->currentHost()->getHostname().empty()) {
2057 D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
2058 fqdn_resp->setDomainName(d2_mgr.qualifyName(ex.getContext()->currentHost()->getHostname(),
2059 *(ex.getContext()->getDdnsParams()), true),
2060 Option4ClientFqdn::FULL);
2061
2062 } else {
2063 // Adjust the domain name based on domain name value and type sent by the
2064 // client and current configuration.
2065 d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp,
2066 *(ex.getContext()->getDdnsParams()));
2067 }
2068
2069 // Add FQDN option to the response message. Note that, there may be some
2070 // cases when server may choose not to include the FQDN option in a
2071 // response to a client. In such cases, the FQDN should be removed from the
2072 // outgoing message. In theory we could cease to include the FQDN option
2073 // in this function until it is confirmed that it should be included.
2074 // However, we include it here for simplicity. Functions used to acquire
2075 // lease for a client will scan the response message for FQDN and if it
2076 // is found they will take necessary actions to store the FQDN information
2077 // in the lease database as well as to generate NameChangeRequests to DNS.
2078 // If we don't store the option in the response message, we will have to
2079 // propagate it in the different way to the functions which acquire the
2080 // lease. This would require modifications to the API of this class.
2081 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_RESPONSE_FQDN_DATA)
2082 .arg(ex.getQuery()->getLabel())
2083 .arg(fqdn_resp->toText());
2084 ex.getResponse()->addOption(fqdn_resp);
2085 }
2086
2087 void
2088 Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) {
2089 // Fetch D2 configuration.
2090 D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
2091
2092 // Obtain the Hostname option from the client's message.
2093 OptionStringPtr opt_hostname = boost::dynamic_pointer_cast<OptionString>
2094 (ex.getQuery()->getOption(DHO_HOST_NAME));
2095
2096 if (opt_hostname) {
2097 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_CLIENT_HOSTNAME_DATA)
2098 .arg(ex.getQuery()->getLabel())
2099 .arg(opt_hostname->getValue());
2100 }
2101
2102 AllocEngine::ClientContext4Ptr ctx = ex.getContext();
2103
2104 // Hostname reservations take precedence over any other configuration,
2105 // i.e. DDNS configuration. If we have a reserved hostname we should
2106 // use it and send it back.
2107 if (ctx->currentHost() && !ctx->currentHost()->getHostname().empty()) {
2108 // Qualify if there is an a suffix configured.
2109 std::string hostname = d2_mgr.qualifyName(ctx->currentHost()->getHostname(),
2110 *(ex.getContext()->getDdnsParams()), false);
2111 // Convert it to lower case.
2112 boost::algorithm::to_lower(hostname);
2113 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_RESERVED_HOSTNAME_ASSIGNED)
2114 .arg(ex.getQuery()->getLabel())
2115 .arg(hostname);
2116
2117 // Add it to the response
2118 OptionStringPtr opt_hostname_resp(new OptionString(Option::V4, DHO_HOST_NAME, hostname));
2119 ex.getResponse()->addOption(opt_hostname_resp);
2120
2121 // We're done here.
2122 return;
2123 }
2124
2125 // There is no reservation for this client however there is still a
2126 // possibility that we'll have to send hostname option to this client
2127 // if the client has included hostname option or the configuration of
2128 // the server requires that we send the option regardless.
2129 D2ClientConfig::ReplaceClientNameMode replace_name_mode =
2130 ex.getContext()->getDdnsParams()->getReplaceClientNameMode();
2131
2132 // If we don't have a hostname then either we'll supply it or do nothing.
2133 if (!opt_hostname) {
2134 // If we're configured to supply it then add it to the response.
2135 // Use the root domain to signal later on that we should replace it.
2136 if (replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
2137 replace_name_mode == D2ClientConfig::RCM_WHEN_NOT_PRESENT) {
2138 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA,
2139 DHCP4_GENERATE_FQDN)
2140 .arg(ex.getQuery()->getLabel());
2141 OptionStringPtr opt_hostname_resp(new OptionString(Option::V4,
2142 DHO_HOST_NAME,
2143 "."));
2144 ex.getResponse()->addOption(opt_hostname_resp);
2145 }
2146
2147 return;
2148 }
2149
2150 // Client sent us a hostname option so figure out what to do with it.
2151 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_CLIENT_HOSTNAME_DATA)
2152 .arg(ex.getQuery()->getLabel())
2153 .arg(opt_hostname->getValue());
2154
2155 std::string hostname = isc::util::str::trim(opt_hostname->getValue());
2156 unsigned int label_count;
2157
2158 try {
2159 // Parsing into labels can throw on malformed content so we're
2160 // going to explicitly catch that here.
2161 label_count = OptionDataTypeUtil::getLabelCount(hostname);
2162 } catch (const std::exception& exc) {
2163 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_HOSTNAME_MALFORMED)
2164 .arg(ex.getQuery()->getLabel())
2165 .arg(exc.what());
2166 return;
2167 }
2168
2169 // The hostname option sent by the client should be at least 1 octet long.
2170 // If it isn't we ignore this option. (Per RFC 2131, section 3.14)
2171 /// @todo It would be more liberal to accept this and let it fall into
2172 /// the case of replace or less than two below.
2173 if (label_count == 0) {
2174 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_EMPTY_HOSTNAME)
2175 .arg(ex.getQuery()->getLabel());
2176 return;
2177 }
2178
2179 // Stores the value we eventually use, so we can send it back.
2180 OptionStringPtr opt_hostname_resp;
2181
2182 // The hostname option may be unqualified or fully qualified. The lab_count
2183 // holds the number of labels for the name. The number of 1 means that
2184 // there is only root label "." (even for unqualified names, as the
2185 // getLabelCount function treats each name as a fully qualified one).
2186 // By checking the number of labels present in the hostname we may infer
2187 // whether client has sent the fully qualified or unqualified hostname.
2188
2189 if ((replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
2190 replace_name_mode == D2ClientConfig::RCM_WHEN_PRESENT)
2191 || label_count < 2) {
2192 // Set to root domain to signal later on that we should replace it.
2193 // DHO_HOST_NAME is a string option which cannot be empty.
2194 /// @todo We may want to reconsider whether it is appropriate for the
2195 /// client to send a root domain name as a Hostname. There are
2196 /// also extensions to the auto generation of the client's name,
2197 /// e.g. conversion to the puny code which may be considered at some
2198 /// point.
2199 /// For now, we just remain liberal and expect that the DNS will handle
2200 /// conversion if needed and possible.
2201 opt_hostname_resp.reset(new OptionString(Option::V4, DHO_HOST_NAME, "."));
2202 } else {
2203 // Sanitize the name the client sent us, if we're configured to do so.
2204 isc::util::str::StringSanitizerPtr sanitizer =
2205 ex.getContext()->getDdnsParams()->getHostnameSanitizer();
2206
2207 if (sanitizer) {
2208 hostname = sanitizer->scrub(hostname);
2209 }
2210
2211 // Convert hostname to lower case.
2212 boost::algorithm::to_lower(hostname);
2213
2214 if (label_count == 2) {
2215 // If there are two labels, it means that the client has specified
2216 // the unqualified name. We have to concatenate the unqualified name
2217 // with the domain name. The false value passed as a second argument
2218 // indicates that the trailing dot should not be appended to the
2219 // hostname. We don't want to append the trailing dot because
2220 // we don't know whether the hostname is partial or not and some
2221 // clients do not handle the hostnames with the trailing dot.
2222 opt_hostname_resp.reset(
2223 new OptionString(Option::V4, DHO_HOST_NAME,
2224 d2_mgr.qualifyName(hostname, *(ex.getContext()->getDdnsParams()),
2225 false)));
2226 } else {
2227 opt_hostname_resp.reset(new OptionString(Option::V4, DHO_HOST_NAME, hostname));
2228 }
2229 }
2230
2231 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_RESPONSE_HOSTNAME_DATA)
2232 .arg(ex.getQuery()->getLabel())
2233 .arg(opt_hostname_resp->getValue());
2234 ex.getResponse()->addOption(opt_hostname_resp);
2235 }
2236
2237 void
2238 Dhcpv4Srv::createNameChangeRequests(const Lease4Ptr& lease,
2239 const Lease4Ptr& old_lease,
2240 const DdnsParams& ddns_params) {
2241 if (!lease) {
2242 isc_throw(isc::Unexpected,
2243 "NULL lease specified when creating NameChangeRequest");
2244 }
2245
2246 // Nothing to do if updates are not enabled.
2247 if (!ddns_params.getEnableUpdates()) {
2248 return;
2249 }
2250
2251 if (!old_lease || ddns_params.getUpdateOnRenew() || !lease->hasIdenticalFqdn(*old_lease)) {
2252 if (old_lease) {
2253 // Queue's up a remove of the old lease's DNS (if needed)
2254 queueNCR(CHG_REMOVE, old_lease);
2255 }
2256
2257 // We may need to generate the NameChangeRequest for the new lease. It
2258 // will be generated only if hostname is set and if forward or reverse
2259 // update has been requested.
2260 queueNCR(CHG_ADD, lease);
2261 }
2262 }
2263
2264 void
2265 Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
2266 // Get the pointers to the query and the response messages.
2267 Pkt4Ptr query = ex.getQuery();
2268 Pkt4Ptr resp = ex.getResponse();
2269
2270 // Get the context.
2271 AllocEngine::ClientContext4Ptr ctx = ex.getContext();
2272
2273 // Subnet should have been already selected when the context was created.
2274 Subnet4Ptr subnet = ctx->subnet_;
2275 if (!subnet) {
2276 // This particular client is out of luck today. We do not have
2277 // information about the subnet he is connected to. This likely means
2278 // misconfiguration of the server (or some relays).
2279
2280 // Perhaps this should be logged on some higher level?
2281 LOG_ERROR(bad_packet4_logger, DHCP4_PACKET_NAK_0001)
2282 .arg(query->getLabel())
2283 .arg(query->getRemoteAddr().toText())
2284 .arg(query->getName());
2285 resp->setType(DHCPNAK);
2286 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
2287 return;
2288 }
2289
2290 // Get the server identifier. It will be used to determine the state
2291 // of the client.
2292 OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
2293 OptionCustom>(query->getOption(DHO_DHCP_SERVER_IDENTIFIER));
2294
2295 // Check if the client has sent a requested IP address option or
2296 // ciaddr.
2297 OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
2298 OptionCustom>(query->getOption(DHO_DHCP_REQUESTED_ADDRESS));
2299 IOAddress hint(IOAddress::IPV4_ZERO_ADDRESS());
2300 if (opt_requested_address) {
2301 hint = opt_requested_address->readAddress();
2302
2303 } else if (!query->getCiaddr().isV4Zero()) {
2304 hint = query->getCiaddr();
2305
2306 }
2307
2308 HWAddrPtr hwaddr = query->getHWAddr();
2309
2310 // "Fake" allocation is processing of DISCOVER message. We pretend to do an
2311 // allocation, but we do not put the lease in the database. That is ok,
2312 // because we do not guarantee that the user will get that exact lease. If
2313 // the user selects this server to do actual allocation (i.e. sends REQUEST)
2314 // it should include this hint. That will help us during the actual lease
2315 // allocation.
2316 bool fake_allocation = (query->getType() == DHCPDISCOVER);
2317
2318 // Get client-id. It is not mandatory in DHCPv4.
2319 ClientIdPtr client_id = ex.getContext()->clientid_;
2320
2321 // If there is no server id and there is a Requested IP Address option
2322 // the client is in the INIT-REBOOT state in which the server has to
2323 // determine whether the client's notion of the address is correct
2324 // and whether the client is known, i.e., has a lease.
2325 if (!fake_allocation && !opt_serverid && opt_requested_address) {
2326
2327 LOG_INFO(lease4_logger, DHCP4_INIT_REBOOT)
2328 .arg(query->getLabel())
2329 .arg(hint.toText());
2330
2331 Lease4Ptr lease;
2332 Subnet4Ptr original_subnet = subnet;
2333
2334 // We used to issue a separate query (two actually: one for client-id
2335 // and another one for hw-addr for) each subnet in the shared network.
2336 // That was horribly inefficient if the client didn't have any lease
2337 // (or there were many subnets and the client happened to be in one
2338 // of the last subnets).
2339 //
2340 // We now issue at most two queries: get all the leases for specific
2341 // client-id and then get all leases for specific hw-address.
2342 if (client_id) {
2343
2344 // Get all the leases for this client-id
2345 Lease4Collection leases_client_id = LeaseMgrFactory::instance().getLease4(*client_id);
2346 if (!leases_client_id.empty()) {
2347 Subnet4Ptr s = original_subnet;
2348
2349 // Among those returned try to find a lease that belongs to
2350 // current shared network.
2351 while (s) {
2352 for (auto l = leases_client_id.begin(); l != leases_client_id.end(); ++l) {
2353 if ((*l)->subnet_id_ == s->getID()) {
2354 lease = *l;
2355 break;
2356 }
2357 }
2358
2359 if (lease) {
2360 break;
2361
2362 } else {
2363 s = s->getNextSubnet(original_subnet, query->getClasses());
2364 }
2365 }
2366 }
2367 }
2368
2369 // If we haven't found a lease yet, try again by hardware-address.
2370 // The logic is the same.
2371 if (!lease && hwaddr) {
2372
2373 // Get all leases for this particular hw-address.
2374 Lease4Collection leases_hwaddr = LeaseMgrFactory::instance().getLease4(*hwaddr);
2375 if (!leases_hwaddr.empty()) {
2376 Subnet4Ptr s = original_subnet;
2377
2378 // Pick one that belongs to a subnet in this shared network.
2379 while (s) {
2380 for (auto l = leases_hwaddr.begin(); l != leases_hwaddr.end(); ++l) {
2381 if ((*l)->subnet_id_ == s->getID()) {
2382 lease = *l;
2383 break;
2384 }
2385 }
2386
2387 if (lease) {
2388 break;
2389
2390 } else {
2391 s = s->getNextSubnet(original_subnet, query->getClasses());
2392 }
2393 }
2394 }
2395 }
2396
2397 // Check the first error case: unknown client. We check this before
2398 // validating the address sent because we don't want to respond if
2399 // we don't know this client, except if we're authoritative.
2400 bool authoritative = original_subnet->getAuthoritative();
2401 bool known_client = lease && lease->belongsToClient(hwaddr, client_id);
2402 if (!authoritative && !known_client) {
2403 LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
2404 DHCP4_NO_LEASE_INIT_REBOOT)
2405 .arg(query->getLabel())
2406 .arg(hint.toText());
2407
2408 ex.deleteResponse();
2409 return;
2410 }
2411
2412 // If we know this client, check if his notion of the IP address is
2413 // correct, if we don't know him, check if we are authoritative.
2414 if ((known_client && (lease->addr_ != hint)) ||
2415 (!known_client && authoritative)) {
2416 LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
2417 DHCP4_PACKET_NAK_0002)
2418 .arg(query->getLabel())
2419 .arg(hint.toText());
2420
2421 resp->setType(DHCPNAK);
2422 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
2423 return;
2424 }
2425 }
2426
2427 CalloutHandlePtr callout_handle = getCalloutHandle(query);
2428
2429 // We need to set these values in the context as they haven't been set yet.
2430 ctx->requested_address_ = hint;
2431 ctx->fake_allocation_ = fake_allocation;
2432 ctx->callout_handle_ = callout_handle;
2433
2434 // If client query contains an FQDN or Hostname option, server
2435 // should respond to the client with the appropriate FQDN or Hostname
2436 // option to indicate if it takes responsibility for the DNS updates.
2437 // This is also the source for the hostname and dns flags that are
2438 // initially added to the lease. In most cases, this information is
2439 // good now. If we end up changing subnets in allocation we'll have to
2440 // do it again and then update the lease.
2441 processClientName(ex);
2442
2443 // Get a lease.
2444 Lease4Ptr lease = alloc_engine_->allocateLease4(*ctx);
2445
2446 // Tracks whether or not the client name (FQDN or host) has changed since
2447 // the lease was allocated.
2448 bool client_name_changed = false;
2449
2450 // Subnet may be modified by the allocation engine, if the initial subnet
2451 // belongs to a shared network.
2452 if (subnet->getID() != ctx->subnet_->getID()) {
2453 SharedNetwork4Ptr network;
2454 subnet->getSharedNetwork(network);
2455 LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_DYNAMICALLY_CHANGED)
2456 .arg(query->getLabel())
2457 .arg(subnet->toText())
2458 .arg(ctx->subnet_->toText())
2459 .arg(network ? network->getName() : "<no network?>");
2460
2461 subnet = ctx->subnet_;
2462
2463 if (lease) {
2464 // We changed subnets and that means DDNS parameters might be different
2465 // so we need to rerun client name processing logic. Arguably we could
2466 // compare DDNS parameters for both subnets and then decide if we need
2467 // to rerun the name logic, but that's not likely to be any faster than
2468 // just re-running the name logic. @todo When inherited parameter
2469 // performance is improved this argument could be revisited.
2470 // Another case is the new subnet has a reserved hostname.
2471
2472 // First, we need to remove the prior values from the response and reset
2473 // those in context, to give processClientName a clean slate.
2474 resp->delOption(DHO_FQDN);
2475 resp->delOption(DHO_HOST_NAME);
2476 ctx->hostname_ = "";
2477 ctx->fwd_dns_update_ = false;
2478 ctx->rev_dns_update_ = false;
2479
2480 // Regenerate the name and dns flags.
2481 processClientName(ex);
2482
2483 // If the results are different from the values already on the
2484 // lease, flag it so the lease gets updated down below.
2485 if ((lease->hostname_ != ctx->hostname_) ||
2486 (lease->fqdn_fwd_ != ctx->fwd_dns_update_) ||
2487 (lease->fqdn_rev_ != ctx->rev_dns_update_)) {
2488 lease->hostname_ = ctx->hostname_;
2489 lease->fqdn_fwd_ = ctx->fwd_dns_update_;
2490 lease->fqdn_rev_ = ctx->rev_dns_update_;
2491 client_name_changed = true;
2492 }
2493 }
2494 }
2495
2496 if (lease) {
2497 // We have a lease! Let's set it in the packet and send it back to
2498 // the client.
2499 if (fake_allocation) {
2500 LOG_INFO(lease4_logger, DHCP4_LEASE_ADVERT)
2501 .arg(query->getLabel())
2502 .arg(lease->addr_.toText());
2503 } else {
2504 LOG_INFO(lease4_logger, DHCP4_LEASE_ALLOC)
2505 .arg(query->getLabel())
2506 .arg(lease->addr_.toText())
2507 .arg(Lease::lifetimeToText(lease->valid_lft_));
2508 }
2509
2510 // We're logging this here, because this is the place where we know
2511 // which subnet has been actually used for allocation. If the
2512 // client identifier matching is disabled, we want to make sure that
2513 // the user is notified.
2514 if (!ctx->subnet_->getMatchClientId()) {
2515 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENTID_IGNORED_FOR_LEASES)
2516 .arg(ctx->query_->getLabel())
2517 .arg(ctx->subnet_->getID());
2518 }
2519
2520 resp->setYiaddr(lease->addr_);
2521
2522 /// @todo The server should check what ciaddr the client has supplied
2523 /// in ciaddr. Currently the ciaddr is ignored except for the subnet
2524 /// selection. If the client supplied an invalid address, the server
2525 /// will also return an invalid address here.
2526 if (!fake_allocation) {
2527 // If this is a renewing client it will set a ciaddr which the
2528 // server may include in the response. If this is a new allocation
2529 // the client will set ciaddr to 0 and this will also be propagated
2530 // to the server's resp.
2531 resp->setCiaddr(query->getCiaddr());
2532 }
2533
2534 // We may need to update FQDN or hostname if the server is to generate
2535 // a new name from the allocated IP address or if the allocation engine
2536 // switched to a different subnet within a shared network.
2537 postAllocateNameUpdate(ctx, lease, query, resp, client_name_changed);
2538
2539 // Reuse the lease if possible.
2540 if (lease->reuseable_valid_lft_ > 0) {
2541 lease->valid_lft_ = lease->reuseable_valid_lft_;
2542 LOG_INFO(lease4_logger, DHCP4_LEASE_REUSE)
2543 .arg(query->getLabel())
2544 .arg(lease->addr_.toText())
2545 .arg(Lease::lifetimeToText(lease->valid_lft_));
2546 }
2547
2548 // IP Address Lease time (type 51)
2549 OptionPtr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME,
2550 lease->valid_lft_));
2551 resp->addOption(opt);
2552
2553 // Subnet mask (type 1)
2554 resp->addOption(getNetmaskOption(subnet));
2555
2556 // Set T1 and T2 per configuration.
2557 setTeeTimes(lease, subnet, resp);
2558
2559 // Create NameChangeRequests if this is a real allocation.
2560 if (!fake_allocation) {
2561 try {
2562 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_NCR_CREATE)
2563 .arg(query->getLabel());
2564 createNameChangeRequests(lease, ctx->old_lease_,
2565 *ex.getContext()->getDdnsParams());
2566 } catch (const Exception& ex) {
2567 LOG_ERROR(ddns4_logger, DHCP4_NCR_CREATION_FAILED)
2568 .arg(query->getLabel())
2569 .arg(ex.what());
2570 }
2571 }
2572
2573 } else {
2574 // Allocation engine did not allocate a lease. The engine logged
2575 // cause of that failure.
2576 LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL, fake_allocation ?
2577 DHCP4_PACKET_NAK_0003 : DHCP4_PACKET_NAK_0004)
2578 .arg(query->getLabel())
2579 .arg(query->getCiaddr().toText())
2580 .arg(opt_requested_address ?
2581 opt_requested_address->readAddress().toText() : "(no address)");
2582
2583 resp->setType(DHCPNAK);
2584 resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
2585
2586 resp->delOption(DHO_FQDN);
2587 resp->delOption(DHO_HOST_NAME);
2588 }
2589 }
2590
2591 void
2592 Dhcpv4Srv::postAllocateNameUpdate(const AllocEngine::ClientContext4Ptr& ctx, const Lease4Ptr& lease,
2593 const Pkt4Ptr& query, const Pkt4Ptr& resp, bool client_name_changed) {
2594 // We may need to update FQDN or hostname if the server is to generate
2595 // new name from the allocated IP address or if the allocation engine
2596 // has switched to a different subnet within a shared network. Get
2597 // FQDN and hostname options from the response.
2598 OptionStringPtr opt_hostname;
2599 Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
2600 Option4ClientFqdn>(resp->getOption(DHO_FQDN));
2601 if (!fqdn) {
2602 opt_hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
2603 if (!opt_hostname) {
2604 // We don't have either one, nothing to do.
2605 return;
2606 }
2607 }
2608
2609 // Empty hostname on the lease means we need to generate it.
2610 if (lease->hostname_.empty()) {
2611 // Note that if we have received the hostname option, rather than
2612 // Client FQDN the trailing dot is not appended to the generated
2613 // hostname because some clients don't handle the trailing dot in
2614 // the hostname. Whether the trailing dot is appended or not is
2615 // controlled by the second argument to the generateFqdn().
2616 lease->hostname_ = CfgMgr::instance().getD2ClientMgr()
2617 .generateFqdn(lease->addr_, *(ctx->getDdnsParams()), static_cast<bool>(fqdn));
2618
2619 LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_RESPONSE_HOSTNAME_GENERATE)
2620 .arg(query->getLabel())
2621 .arg(lease->hostname_);
2622
2623 client_name_changed = true;
2624 }
2625
2626 if (client_name_changed) {
2627 // The operations below are rather safe, but we want to catch
2628 // any potential exceptions (e.g. invalid lease database backend
2629 // implementation) and log an error.
2630 try {
2631 if (!ctx->fake_allocation_) {
2632 // The lease can't be reused.
2633 lease->reuseable_valid_lft_ = 0;
2634
2635 // The lease update should be safe, because the lease should
2636 // be already in the database. In most cases the exception
2637 // would be thrown if the lease was missing.
2638 LeaseMgrFactory::instance().updateLease4(lease);
2639 }
2640
2641 // The name update in the outbound option should be also safe,
2642 // because the generated name is well formed.
2643 if (fqdn) {
2644 fqdn->setDomainName(lease->hostname_, Option4ClientFqdn::FULL);
2645 } else {
2646 opt_hostname->setValue(lease->hostname_);
2647 }
2648 } catch (const Exception& ex) {
2649 LOG_ERROR(ddns4_logger, DHCP4_POST_ALLOCATION_NAME_UPDATE_FAIL)
2650 .arg(query->getLabel())
2651 .arg(lease->hostname_)
2652 .arg(ex.what());
2653 }
2654 }
2655 }
2656
2657 /// @todo This logic to be modified if we decide to support infinite lease times.
2658 void
2659 Dhcpv4Srv::setTeeTimes(const Lease4Ptr& lease, const Subnet4Ptr& subnet, Pkt4Ptr resp) {
2660
2661 uint32_t t2_time = 0;
2662 // If T2 is explicitly configured we'll use try value.
2663 if (!subnet->getT2().unspecified()) {
2664 t2_time = subnet->getT2();
2665 } else if (subnet->getCalculateTeeTimes()) {
2666 // Calculating tee times is enabled, so calculated it.
2667 t2_time = static_cast<uint32_t>(round(subnet->getT2Percent() * (lease->valid_lft_)));
2668 }
2669
2670 // Send the T2 candidate value only if it's sane: to be sane it must be less than
2671 // the valid life time.
2672 uint32_t timer_ceiling = lease->valid_lft_;
2673 if (t2_time > 0 && t2_time < timer_ceiling) {
2674 OptionUint32Ptr t2(new OptionUint32(Option::V4, DHO_DHCP_REBINDING_TIME, t2_time));
2675 resp->addOption(t2);
2676 // When we send T2, timer ceiling for T1 becomes T2.
2677 timer_ceiling = t2_time;
2678 }
2679
2680 uint32_t t1_time = 0;
2681 // If T1 is explicitly configured we'll use try value.
2682 if (!subnet->getT1().unspecified()) {
2683 t1_time = subnet->getT1();
2684 } else if (subnet->getCalculateTeeTimes()) {
2685 // Calculating tee times is enabled, so calculate it.
2686 t1_time = static_cast<uint32_t>(round(subnet->getT1Percent() * (lease->valid_lft_)));
2687 }
2688
2689 // Send T1 if it's sane: If we sent T2, T1 must be less than that. If not it must be
2690 // less than the valid life time.
2691 if (t1_time > 0 && t1_time < timer_ceiling) {
2692 OptionUint32Ptr t1(new OptionUint32(Option::V4, DHO_DHCP_RENEWAL_TIME, t1_time));
2693 resp->addOption(t1);
2694 }
2695 }
2696
2697 uint16_t
2698 Dhcpv4Srv::checkRelayPort(const Dhcpv4Exchange& ex) {
2699
2700 // Look for a relay-port RAI sub-option in the query.
2701 const Pkt4Ptr& query = ex.getQuery();
2702 const OptionPtr& rai = query->getOption(DHO_DHCP_AGENT_OPTIONS);
2703 if (rai && rai->getOption(RAI_OPTION_RELAY_PORT)) {
2704 // Got the sub-option so use the remote port set by the relay.
2705 return (query->getRemotePort());
2706 }
2707 return (0);
2708 }
2709
2710 void
2711 Dhcpv4Srv::adjustIfaceData(Dhcpv4Exchange& ex) {
2712 adjustRemoteAddr(ex);
2713
2714 // Initialize the pointers to the client's message and the server's
2715 // response.
2716 Pkt4Ptr query = ex.getQuery();
2717 Pkt4Ptr response = ex.getResponse();
2718
2719 // The DHCPINFORM is generally unicast to the client. The only situation
2720 // when the server is unable to unicast to the client is when the client
2721 // doesn't include ciaddr and the message is relayed. In this case the
2722 // server has to reply via relay agent. For other messages we send back
2723 // through relay if message is relayed, and unicast to the client if the
2724 // message is not relayed.
2725 // If client port was set from the command line enforce all responses
2726 // to it. Of course it is only for testing purposes.
2727 // Note that the call to this function may throw if invalid combination
2728 // of hops and giaddr is found (hops = 0 if giaddr = 0 and hops != 0 if
2729 // giaddr != 0). The exception will propagate down and eventually cause the
2730 // packet to be discarded.
2731 if (client_port_) {
2732 response->setRemotePort(client_port_);
2733 } else if (((query->getType() == DHCPINFORM) &&
2734 ((!query->getCiaddr().isV4Zero()) ||
2735 (!query->isRelayed() && !query->getRemoteAddr().isV4Zero()))) ||
2736 ((query->getType() != DHCPINFORM) && !query->isRelayed())) {
2737 response->setRemotePort(DHCP4_CLIENT_PORT);
2738
2739 } else {
2740 // RFC 8357 section 5.1
2741 uint16_t relay_port = checkRelayPort(ex);
2742 response->setRemotePort(relay_port ? relay_port : DHCP4_SERVER_PORT);
2743 }
2744
2745 CfgIfacePtr cfg_iface = CfgMgr::instance().getCurrentCfg()->getCfgIface();
2746 if (query->isRelayed() &&
2747 (cfg_iface->getSocketType() == CfgIface::SOCKET_UDP) &&
2748 (cfg_iface->getOutboundIface() == CfgIface::USE_ROUTING)) {
2749
2750 // Mark the response to follow routing
2751 response->setLocalAddr(IOAddress::IPV4_ZERO_ADDRESS());
2752 response->resetIndex();
2753 // But keep the interface name
2754 response->setIface(query->getIface());
2755
2756 } else {
2757
2758 IOAddress local_addr = query->getLocalAddr();
2759
2760 // In many cases the query is sent to a broadcast address. This address
2761 // appears as a local address in the query message. We can't simply copy
2762 // this address to a response message and use it as a source address.
2763 // Instead we will need to use the address assigned to the interface
2764 // on which the query has been received. In other cases, we will just
2765 // use this address as a source address for the response.
2766 // Do the same for DHCPv4-over-DHCPv6 exchanges.
2767 if (local_addr.isV4Bcast() || query->isDhcp4o6()) {
2768 local_addr = IfaceMgr::instance().getSocket(query).addr_;
2769 }
2770
2771 // We assume that there is an appropriate socket bound to this address
2772 // and that the address is correct. This is safe assumption because
2773 // the local address of the query is set when the query is received.
2774 // The query sent to an incorrect address wouldn't have been received.
2775 // However, if socket is closed for this address between the reception
2776 // of the query and sending a response, the IfaceMgr should detect it
2777 // and return an error.
2778 response->setLocalAddr(local_addr);
2779 // In many cases the query is sent to a broadcast address. This address
2780 // appears as a local address in the query message. Therefore we can't
2781 // simply copy local address from the query and use it as a source
2782 // address for the response. Instead, we have to check what address our
2783 // socket is bound to and use it as a source address. This operation
2784 // may throw if for some reason the socket is closed.
2785 /// @todo Consider an optimization that we use local address from
2786 /// the query if this address is not broadcast.
2787 response->setIndex(query->getIndex());
2788 response->setIface(query->getIface());
2789 }
2790
2791 if (server_port_) {
2792 response->setLocalPort(server_port_);
2793 } else {
2794 response->setLocalPort(DHCP4_SERVER_PORT);
2795 }
2796 }
2797
2798 void
2799 Dhcpv4Srv::adjustRemoteAddr(Dhcpv4Exchange& ex) {
2800 // Initialize the pointers to the client's message and the server's
2801 // response.
2802 Pkt4Ptr query = ex.getQuery();
2803 Pkt4Ptr response = ex.getResponse();
2804
2805 // DHCPv4-over-DHCPv6 is simple
2806 if (query->isDhcp4o6()) {
2807 response->setRemoteAddr(query->getRemoteAddr());
2808 return;
2809 }
2810
2811 // The DHCPINFORM is slightly different than other messages in a sense
2812 // that the server should always unicast the response to the ciaddr.
2813 // It appears however that some clients don't set the ciaddr. We still
2814 // want to provision these clients and we do what we can't to send the
2815 // packet to the address where client can receive it.
2816 if (query->getType() == DHCPINFORM) {
2817 // If client adheres to RFC2131 it will set the ciaddr and in this
2818 // case we always unicast our response to this address.
2819 if (!query->getCiaddr().isV4Zero()) {
2820 response->setRemoteAddr(query->getCiaddr());
2821
2822 // If we received DHCPINFORM via relay and the ciaddr is not set we
2823 // will try to send the response via relay. The caveat is that the
2824 // relay will not have any idea where to forward the packet because
2825 // the yiaddr is likely not set. So, the broadcast flag is set so
2826 // as the response may be broadcast.
2827 } else if (query->isRelayed()) {
2828 response->setRemoteAddr(query->getGiaddr());
2829 response->setFlags(response->getFlags() | BOOTP_BROADCAST);
2830
2831 // If there is no ciaddr and no giaddr the only thing we can do is
2832 // to use the source address of the packet.
2833 } else {
2834 response->setRemoteAddr(query->getRemoteAddr());
2835 }
2836 // Remote address is now set so return.
2837 return;
2838 }
2839
2840 // If received relayed message, server responds to the relay address.
2841 if (query->isRelayed()) {
2842 // The client should set the ciaddr when sending the DHCPINFORM
2843 // but in case he didn't, the relay may not be able to determine the
2844 // address of the client, because yiaddr is not set when responding
2845 // to Confirm and the only address available was the source address
2846 // of the client. The source address is however not used here because
2847 // the message is relayed. Therefore, we set the BROADCAST flag so
2848 // as the relay can broadcast the packet.
2849 if ((query->getType() == DHCPINFORM) &&
2850 query->getCiaddr().isV4Zero()) {
2851 response->setFlags(BOOTP_BROADCAST);
2852 }
2853 response->setRemoteAddr(query->getGiaddr());
2854
2855 // If giaddr is 0 but client set ciaddr, server should unicast the
2856 // response to ciaddr.
2857 } else if (!query->getCiaddr().isV4Zero()) {
2858 response->setRemoteAddr(query->getCiaddr());
2859
2860 // We can't unicast the response to the client when sending NAK,
2861 // because we haven't allocated address for him. Therefore,
2862 // NAK is broadcast.
2863 } else if (response->getType() == DHCPNAK) {
2864 response->setRemoteAddr(IOAddress::IPV4_BCAST_ADDRESS());
2865
2866 // If yiaddr is set it means that we have created a lease for a client.
2867 } else if (!response->getYiaddr().isV4Zero()) {
2868 // If the broadcast bit is set in the flags field, we have to
2869 // send the response to broadcast address. Client may have requested it
2870 // because it doesn't support reception of messages on the interface
2871 // which doesn't have an address assigned. The other case when response
2872 // must be broadcasted is when our server does not support responding
2873 // directly to a client without address assigned.
2874 const bool bcast_flag = ((query->getFlags() & Pkt4::FLAG_BROADCAST_MASK) != 0);
2875 if (!IfaceMgr::instance().isDirectResponseSupported() || bcast_flag) {
2876 response->setRemoteAddr(IOAddress::IPV4_BCAST_ADDRESS());
2877
2878 // Client cleared the broadcast bit and we support direct responses
2879 // so we should unicast the response to a newly allocated address -
2880 // yiaddr.
2881 } else {
2882 response->setRemoteAddr(response ->getYiaddr());
2883
2884 }
2885
2886 // In most cases, we should have the remote address found already. If we
2887 // found ourselves at this point, the rational thing to do is to respond
2888 // to the address we got the query from.
2889 } else {
2890 response->setRemoteAddr(query->getRemoteAddr());
2891 }
2892
2893 // For testing *only*.
2894 if (getSendResponsesToSource()) {
2895 response->setRemoteAddr(query->getRemoteAddr());
2896 }
2897 }
2898
2899 void
2900 Dhcpv4Srv::setFixedFields(Dhcpv4Exchange& ex) {
2901 Pkt4Ptr query = ex.getQuery();
2902 Pkt4Ptr response = ex.getResponse();
2903
2904 // Step 1: Start with fixed fields defined on subnet level.
2905 Subnet4Ptr subnet = ex.getContext()->subnet_;
2906 if (subnet) {
2907 IOAddress subnet_next_server = subnet->getSiaddr();
2908 if (!subnet_next_server.isV4Zero()) {
2909 response->setSiaddr(subnet_next_server);
2910 }
2911
2912 const string& sname = subnet->getSname();
2913 if (!sname.empty()) {
2914 // Converting string to (const uint8_t*, size_t len) format is
2915 // tricky. reinterpret_cast is not the most elegant solution,
2916 // but it does avoid us making unnecessary copy. We will convert
2917 // sname and file fields in Pkt4 to string one day and life
2918 // will be easier.
2919 response->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
2920 sname.size());
2921 }
2922
2923 const string& filename = subnet->getFilename();
2924 if (!filename.empty()) {
2925 // Converting string to (const uint8_t*, size_t len) format is
2926 // tricky. reinterpret_cast is not the most elegant solution,
2927 // but it does avoid us making unnecessary copy. We will convert
2928 // sname and file fields in Pkt4 to string one day and life
2929 // will be easier.
2930 response->setFile(reinterpret_cast<const uint8_t*>(filename.c_str()),
2931 filename.size());
2932 }
2933 }
2934
2935 // Step 2: Try to set the values based on classes.
2936 // Any values defined in classes will override those from subnet level.
2937 const ClientClasses classes = query->getClasses();
2938 if (!classes.empty()) {
2939
2940 // Let's get class definitions
2941 const ClientClassDictionaryPtr& dict =
2942 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
2943
2944 // Now we need to iterate over the classes assigned to the
2945 // query packet and find corresponding class definitions for it.
2946 // We want the first value found for each field. We track how
2947 // many we've found so we can stop if we have all three.
2948 IOAddress next_server = IOAddress::IPV4_ZERO_ADDRESS();
2949 string sname;
2950 string filename;
2951 size_t found_cnt = 0; // How many fields we have found.
2952 for (ClientClasses::const_iterator name = classes.cbegin();
2953 name != classes.cend() && found_cnt < 3; ++name) {
2954
2955 ClientClassDefPtr cl = dict->findClass(*name);
2956 if (!cl) {
2957 // Let's skip classes that don't have definitions. Currently
2958 // these are automatic classes VENDOR_CLASS_something, but there
2959 // may be other classes assigned under other circumstances, e.g.
2960 // by hooks.
2961 continue;
2962 }
2963
2964 if (next_server == IOAddress::IPV4_ZERO_ADDRESS()) {
2965 next_server = cl->getNextServer();
2966 if (!next_server.isV4Zero()) {
2967 response->setSiaddr(next_server);
2968 found_cnt++;
2969 }
2970 }
2971
2972 if (sname.empty()) {
2973 sname = cl->getSname();
2974 if (!sname.empty()) {
2975 // Converting string to (const uint8_t*, size_t len) format is
2976 // tricky. reinterpret_cast is not the most elegant solution,
2977 // but it does avoid us making unnecessary copy. We will convert
2978 // sname and file fields in Pkt4 to string one day and life
2979 // will be easier.
2980 response->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
2981 sname.size());
2982 found_cnt++;
2983 }
2984 }
2985
2986 if (filename.empty()) {
2987 filename = cl->getFilename();
2988 if (!filename.empty()) {
2989 // Converting string to (const uint8_t*, size_t len) format is
2990 // tricky. reinterpret_cast is not the most elegant solution,
2991 // but it does avoid us making unnecessary copy. We will convert
2992 // sname and file fields in Pkt4 to string one day and life
2993 // will be easier.
2994 response->setFile(reinterpret_cast<const uint8_t*>(filename.c_str()),
2995 filename.size());
2996 found_cnt++;
2997 }
2998 }
2999 }
3000 }
3001
3002 // Step 3: try to set values using HR. Any values coming from there will override
3003 // the subnet or class values.
3004 ex.setReservedMessageFields();
3005 }
3006
3007 OptionPtr
3008 Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
3009 uint32_t netmask = getNetmask4(subnet->get().second).toUint32();
3010
3011 OptionPtr opt(new OptionInt<uint32_t>(Option::V4,
3012 DHO_SUBNET_MASK, netmask));
3013
3014 return (opt);
3015 }
3016
3017 Pkt4Ptr
3018 Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
3019 // server-id is forbidden.
3020 sanityCheck(discover, FORBIDDEN);
3021
3022 bool drop = false;
3023 Subnet4Ptr subnet = selectSubnet(discover, drop);
3024
3025 // Stop here if selectSubnet decided to drop the packet
3026 if (drop) {
3027 return (Pkt4Ptr());
3028 }
3029
3030 Dhcpv4Exchange ex(alloc_engine_, discover, subnet, drop);
3031
3032 // Stop here if Dhcpv4Exchange constructor decided to drop the packet
3033 if (drop) {
3034 return (Pkt4Ptr());
3035 }
3036
3037 if (MultiThreadingMgr::instance().getMode()) {
3038 // The lease reclamation cannot run at the same time.
3039 ReadLockGuard share(alloc_engine_->getReadWriteMutex());
3040
3041 assignLease(ex);
3042 } else {
3043 assignLease(ex);
3044 }
3045
3046 if (!ex.getResponse()) {
3047 // The offer is empty so return it *now*!
3048 return (Pkt4Ptr());
3049 }
3050
3051 // Adding any other options makes sense only when we got the lease.
3052 if (!ex.getResponse()->getYiaddr().isV4Zero()) {
3053 // If this is global reservation or the subnet doesn't belong to a shared
3054 // network we have already fetched it and evaluated the classes.
3055 ex.conditionallySetReservedClientClasses();
3056
3057 // Required classification
3058 requiredClassify(ex);
3059
3060 buildCfgOptionList(ex);
3061 appendRequestedOptions(ex);
3062 appendRequestedVendorOptions(ex);
3063 // There are a few basic options that we always want to
3064 // include in the response. If client did not request
3065 // them we append them for him.
3066 appendBasicOptions(ex);
3067
3068 // Set fixed fields (siaddr, sname, filename) if defined in
3069 // the reservation, class or subnet specific configuration.
3070 setFixedFields(ex);
3071
3072 } else {
3073 // If the server can't offer an address, it drops the packet.
3074 return (Pkt4Ptr());
3075
3076 }
3077
3078 // Set the src/dest IP address, port and interface for the outgoing
3079 // packet.
3080 adjustIfaceData(ex);
3081
3082 appendServerID(ex);
3083
3084 return (ex.getResponse());
3085 }
3086
3087 Pkt4Ptr
3088 Dhcpv4Srv::processRequest(Pkt4Ptr& request, AllocEngine::ClientContext4Ptr& context) {
3089 // Since we cannot distinguish between client states
3090 // we'll make server-id is optional for REQUESTs.
3091 sanityCheck(request, OPTIONAL);
3092
3093 bool drop = false;
3094 Subnet4Ptr subnet = selectSubnet(request, drop);
3095
3096 // Stop here if selectSubnet decided to drop the packet
3097 if (drop) {
3098 return (Pkt4Ptr());
3099 }
3100
3101 Dhcpv4Exchange ex(alloc_engine_, request, subnet, drop);
3102
3103 // Stop here if Dhcpv4Exchange constructor decided to drop the packet
3104 if (drop) {
3105 return (Pkt4Ptr());
3106 }
3107
3108 // Note that we treat REQUEST message uniformly, regardless if this is a
3109 // first request (requesting for new address), renewing existing address
3110 // or even rebinding.
3111 if (MultiThreadingMgr::instance().getMode()) {
3112 // The lease reclamation cannot run at the same time.
3113 ReadLockGuard share(alloc_engine_->getReadWriteMutex());
3114
3115 assignLease(ex);
3116 } else {
3117 assignLease(ex);
3118 }
3119
3120 Pkt4Ptr response = ex.getResponse();
3121 if (!response) {
3122 // The ack is empty so return it *now*!
3123 return (Pkt4Ptr());
3124 } else if (request->inClass("BOOTP")) {
3125 // Put BOOTP responses in the BOOTP class.
3126 response->addClass("BOOTP");
3127 }
3128
3129 // Adding any other options makes sense only when we got the lease.
3130 if (!response->getYiaddr().isV4Zero()) {
3131 // If this is global reservation or the subnet doesn't belong to a shared
3132 // network we have already fetched it and evaluated the classes.
3133 ex.conditionallySetReservedClientClasses();
3134
3135 // Required classification
3136 requiredClassify(ex);
3137
3138 buildCfgOptionList(ex);
3139 appendRequestedOptions(ex);
3140 appendRequestedVendorOptions(ex);
3141 // There are a few basic options that we always want to
3142 // include in the response. If client did not request
3143 // them we append them for him.
3144 appendBasicOptions(ex);
3145
3146 // Set fixed fields (siaddr, sname, filename) if defined in
3147 // the reservation, class or subnet specific configuration.
3148 setFixedFields(ex);
3149 }
3150
3151 // Set the src/dest IP address, port and interface for the outgoing
3152 // packet.
3153 adjustIfaceData(ex);
3154
3155 appendServerID(ex);
3156
3157 // Return the pointer to the context, which will be required by the
3158 // leases4_committed callouts.
3159 context = ex.getContext();
3160
3161 return (ex.getResponse());
3162 }
3163
3164 void
3165 Dhcpv4Srv::processRelease(Pkt4Ptr& release, AllocEngine::ClientContext4Ptr& context) {
3166 // Server-id is mandatory in DHCPRELEASE (see table 5, RFC2131)
3167 // but ISC DHCP does not enforce this, so we'll follow suit.
3168 sanityCheck(release, OPTIONAL);
3169
3170 // Try to find client-id. Note that for the DHCPRELEASE we don't check if the
3171 // match-client-id configuration parameter is disabled because this parameter
3172 // is configured for subnets and we don't select subnet for the DHCPRELEASE.
3173 // Bogus clients usually generate new client identifiers when they first
3174 // connect to the network, so whatever client identifier has been used to
3175 // acquire the lease, the client identifier carried in the DHCPRELEASE is
3176 // likely to be the same and the lease will be correctly identified in the
3177 // lease database. If supplied client identifier differs from the one used
3178 // to acquire the lease then the lease will remain in the database and
3179 // simply expire.
3180 ClientIdPtr client_id;
3181 OptionPtr opt = release->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
3182 if (opt) {
3183 client_id = ClientIdPtr(new ClientId(opt->getData()));
3184 }
3185
3186 try {
3187 // Do we have a lease for that particular address?
3188 Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(release->getCiaddr());
3189
3190 if (!lease) {
3191 // No such lease - bogus release
3192 LOG_DEBUG(lease4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_NO_LEASE)
3193 .arg(release->getLabel())
3194 .arg(release->getCiaddr().toText());
3195 return;
3196 }
3197
3198 if (!lease->belongsToClient(release->getHWAddr(), client_id)) {
3199 LOG_DEBUG(lease4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_CLIENT)
3200 .arg(release->getLabel())
3201 .arg(release->getCiaddr().toText());
3202 return;
3203 }
3204
3205 bool skip = false;
3206
3207 // Execute all callouts registered for lease4_release
3208 if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_release_)) {
3209 CalloutHandlePtr callout_handle = getCalloutHandle(release);
3210
3211 // Use the RAII wrapper to make sure that the callout handle state is
3212 // reset when this object goes out of scope. All hook points must do
3213 // it to prevent possible circular dependency between the callout
3214 // handle and its arguments.
3215 ScopedCalloutHandleState callout_handle_state(callout_handle);
3216
3217 // Enable copying options from the packet within hook library.
3218 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(release);
3219
3220 // Pass the original packet
3221 callout_handle->setArgument("query4", release);
3222
3223 // Pass the lease to be updated
3224 callout_handle->setArgument("lease4", lease);
3225
3226 // Call all installed callouts
3227 HooksManager::callCallouts(Hooks.hook_index_lease4_release_,
3228 *callout_handle);
3229
3230 // Callouts decided to skip the next processing step. The next
3231 // processing step would to send the packet, so skip at this
3232 // stage means "drop response".
3233 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
3234 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
3235 skip = true;
3236 LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING,
3237 DHCP4_HOOK_LEASE4_RELEASE_SKIP)
3238 .arg(release->getLabel());
3239 }
3240 }
3241
3242 // Callout didn't indicate to skip the release process. Let's release
3243 // the lease.
3244 if (!skip) {
3245 bool success = LeaseMgrFactory::instance().deleteLease(lease);
3246
3247 if (success) {
3248
3249 context.reset(new AllocEngine::ClientContext4());
3250 context->old_lease_ = lease;
3251
3252 // Release successful
3253 LOG_INFO(lease4_logger, DHCP4_RELEASE)
3254 .arg(release->getLabel())
3255 .arg(lease->addr_.toText());
3256
3257 // Need to decrease statistic for assigned addresses.
3258 StatsMgr::instance().addValue(
3259 StatsMgr::generateName("subnet", lease->subnet_id_, "assigned-addresses"),
3260 static_cast<int64_t>(-1));
3261
3262 // Remove existing DNS entries for the lease, if any.
3263 queueNCR(CHG_REMOVE, lease);
3264
3265 } else {
3266 // Release failed
3267 LOG_ERROR(lease4_logger, DHCP4_RELEASE_FAIL)
3268 .arg(release->getLabel())
3269 .arg(lease->addr_.toText());
3270 }
3271 }
3272 } catch (const isc::Exception& ex) {
3273 LOG_ERROR(lease4_logger, DHCP4_RELEASE_EXCEPTION)
3274 .arg(release->getLabel())
3275 .arg(release->getCiaddr())
3276 .arg(ex.what());
3277 }
3278 }
3279
3280 void
3281 Dhcpv4Srv::processDecline(Pkt4Ptr& decline, AllocEngine::ClientContext4Ptr& context) {
3282 // Server-id is mandatory in DHCPDECLINE (see table 5, RFC2131)
3283 // but ISC DHCP does not enforce this, so we'll follow suit.
3284 sanityCheck(decline, OPTIONAL);
3285
3286 // Client is supposed to specify the address being declined in
3287 // Requested IP address option, but must not set its ciaddr.
3288 // (again, see table 5 in RFC2131).
3289
3290 OptionCustomPtr opt_requested_address = boost::dynamic_pointer_cast<
3291 OptionCustom>(decline->getOption(DHO_DHCP_REQUESTED_ADDRESS));
3292 if (!opt_requested_address) {
3293
3294 isc_throw(RFCViolation, "Mandatory 'Requested IP address' option missing"
3295 " in DHCPDECLINE sent from " << decline->getLabel());
3296 }
3297 IOAddress addr(opt_requested_address->readAddress());
3298
3299 // We could also extract client's address from ciaddr, but that's clearly
3300 // against RFC2131.
3301
3302 // Now we need to check whether this address really belongs to the client
3303 // that attempts to decline it.
3304 const Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(addr);
3305
3306 if (!lease) {
3307 // Client tried to decline an address, but we don't have a lease for
3308 // that address. Let's ignore it.
3309 //
3310 // We could assume that we're recovering from a mishandled migration
3311 // to a new server and mark the address as declined, but the window of
3312 // opportunity for that to be useful is small and the attack vector
3313 // would be pretty severe.
3314 LOG_WARN(dhcp4_logger, DHCP4_DECLINE_LEASE_NOT_FOUND)
3315 .arg(addr.toText()).arg(decline->getLabel());
3316 return;
3317 }
3318
3319 // Get client-id, if available.
3320 OptionPtr opt_clientid = decline->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
3321 ClientIdPtr client_id;
3322 if (opt_clientid) {
3323 client_id.reset(new ClientId(opt_clientid->getData()));
3324 }
3325
3326 // Check if the client attempted to decline a lease it doesn't own.
3327 if (!lease->belongsToClient(decline->getHWAddr(), client_id)) {
3328
3329 // Get printable hardware addresses
3330 string client_hw = decline->getHWAddr() ?
3331 decline->getHWAddr()->toText(false) : "(none)";
3332 string lease_hw = lease->hwaddr_ ?
3333 lease->hwaddr_->toText(false) : "(none)";
3334
3335 // Get printable client-ids
3336 string client_id_txt = client_id ? client_id->toText() : "(none)";
3337 string lease_id_txt = lease->client_id_ ?
3338 lease->client_id_->toText() : "(none)";
3339
3340 // Print the warning and we're done here.
3341 LOG_WARN(dhcp4_logger, DHCP4_DECLINE_LEASE_MISMATCH)
3342 .arg(addr.toText()).arg(decline->getLabel())
3343 .arg(client_hw).arg(lease_hw).arg(client_id_txt).arg(lease_id_txt);
3344
3345 return;
3346 }
3347
3348 // Ok, all is good. The client is reporting its own address. Let's
3349 // process it.
3350 declineLease(lease, decline, context);
3351 }
3352
3353 void
3354 Dhcpv4Srv::declineLease(const Lease4Ptr& lease, const Pkt4Ptr& decline,
3355 AllocEngine::ClientContext4Ptr& context) {
3356
3357 // Let's check if there are hooks installed for decline4 hook point.
3358 // If they are, let's pass the lease and client's packet. If the hook
3359 // sets status to drop, we reject this Decline.
3360 if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_decline_)) {
3361 CalloutHandlePtr callout_handle = getCalloutHandle(decline);
3362
3363 // Use the RAII wrapper to make sure that the callout handle state is
3364 // reset when this object goes out of scope. All hook points must do
3365 // it to prevent possible circular dependency between the callout
3366 // handle and its arguments.
3367 ScopedCalloutHandleState callout_handle_state(callout_handle);
3368
3369 // Enable copying options from the packet within hook library.
3370 ScopedEnableOptionsCopy<Pkt4> query4_options_copy(decline);
3371
3372 // Pass the original packet
3373 callout_handle->setArgument("query4", decline);
3374
3375 // Pass the lease to be updated
3376 callout_handle->setArgument("lease4", lease);
3377
3378 // Call callouts
3379 HooksManager::callCallouts(Hooks.hook_index_lease4_decline_,
3380 *callout_handle);
3381
3382 // Check if callouts decided to skip the next processing step.
3383 // If any of them did, we will drop the packet.
3384 if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
3385 (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
3386 LOG_DEBUG(hooks_logger, DBGLVL_PKT_HANDLING, DHCP4_HOOK_DECLINE_SKIP)
3387 .arg(decline->getLabel()).arg(lease->addr_.toText());
3388 return;
3389 }
3390 }
3391
3392 Lease4Ptr old_values = boost::make_shared<Lease4>(*lease);
3393
3394 // @todo: Call hooks.
3395
3396 // We need to disassociate the lease from the client. Once we move a lease
3397 // to declined state, it is no longer associated with the client in any
3398 // way.
3399 lease->decline(CfgMgr::instance().getCurrentCfg()->getDeclinePeriod());
3400
3401 try {
3402 LeaseMgrFactory::instance().updateLease4(lease);
3403 } catch (const Exception& ex) {
3404 // Update failed.
3405 LOG_ERROR(lease4_logger, DHCP4_DECLINE_FAIL)
3406 .arg(decline->getLabel())
3407 .arg(lease->addr_.toText())
3408 .arg(ex.what());
3409 return;
3410 }
3411
3412 // Remove existing DNS entries for the lease, if any.
3413 // queueNCR will do the necessary checks and will skip the update, if not needed.
3414 queueNCR(CHG_REMOVE, old_values);
3415
3416 // Bump up the statistics.
3417
3418 // Per subnet declined addresses counter.
3419 StatsMgr::instance().addValue(
3420 StatsMgr::generateName("subnet", lease->subnet_id_, "declined-addresses"),
3421 static_cast<int64_t>(1));
3422
3423 // Global declined addresses counter.
3424 StatsMgr::instance().addValue("declined-addresses", static_cast<int64_t>(1));
3425
3426 // We do not want to decrease the assigned-addresses at this time. While
3427 // technically a declined address is no longer allocated, the primary usage
3428 // of the assigned-addresses statistic is to monitor pool utilization. Most
3429 // people would forget to include declined-addresses in the calculation,
3430 // and simply do assigned-addresses/total-addresses. This would have a bias
3431 // towards under-representing pool utilization, if we decreased allocated
3432 // immediately after receiving DHCPDECLINE, rather than later when we recover
3433 // the address.
3434
3435 context.reset(new AllocEngine::ClientContext4());
3436 context->new_lease_ = lease;
3437
3438 LOG_INFO(lease4_logger, DHCP4_DECLINE_LEASE).arg(lease->addr_.toText())
3439 .arg(decline->getLabel()).arg(lease->valid_lft_);
3440 }
3441
3442 Pkt4Ptr
3443 Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
3444 // server-id is supposed to be forbidden (as is requested address)
3445 // but ISC DHCP does not enforce either. So neither will we.
3446 sanityCheck(inform, OPTIONAL);
3447
3448 bool drop = false;
3449 Subnet4Ptr subnet = selectSubnet(inform, drop);
3450
3451 // Stop here if selectSubnet decided to drop the packet
3452 if (drop) {
3453 return (Pkt4Ptr());
3454 }
3455
3456 Dhcpv4Exchange ex(alloc_engine_, inform, subnet, drop);
3457
3458 // Stop here if Dhcpv4Exchange constructor decided to drop the packet
3459 if (drop) {
3460 return (Pkt4Ptr());
3461 }
3462
3463 Pkt4Ptr ack = ex.getResponse();
3464
3465 // If this is global reservation or the subnet doesn't belong to a shared
3466 // network we have already fetched it and evaluated the classes.
3467 ex.conditionallySetReservedClientClasses();
3468
3469 requiredClassify(ex);
3470
3471 buildCfgOptionList(ex);
3472 appendRequestedOptions(ex);
3473 appendRequestedVendorOptions(ex);
3474 appendBasicOptions(ex);
3475 adjustIfaceData(ex);
3476
3477 // Set fixed fields (siaddr, sname, filename) if defined in
3478 // the reservation, class or subnet specific configuration.
3479 setFixedFields(ex);
3480
3481 // There are cases for the DHCPINFORM that the server receives it via
3482 // relay but will send the response to the client's unicast address
3483 // carried in the ciaddr. In this case, the giaddr and hops field should
3484 // be cleared (these fields were copied by the copyDefaultFields function).
3485 // Also Relay Agent Options should be removed if present.
3486 if (ack->getRemoteAddr() != inform->getGiaddr()) {
3487 LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_INFORM_DIRECT_REPLY)
3488 .arg(inform->getLabel())
3489 .arg(ack->getRemoteAddr())
3490 .arg(ack->getIface());
3491 ack->setHops(0);
3492 ack->setGiaddr(IOAddress::IPV4_ZERO_ADDRESS());
3493 ack->delOption(DHO_DHCP_AGENT_OPTIONS);
3494 }
3495
3496 // The DHCPACK must contain server id.
3497 appendServerID(ex);
3498
3499 return (ex.getResponse());
3500 }
3501
3502 bool
3503 Dhcpv4Srv::accept(const Pkt4Ptr& query) const {
3504 // Check that the message type is accepted by the server. We rely on the
3505 // function called to log a message if needed.
3506 if (!acceptMessageType(query)) {
3507 return (false);
3508 }
3509 // Check if the message from directly connected client (if directly
3510 // connected) should be dropped or processed.
3511 if (!acceptDirectRequest(query)) {
3512 LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0002)
3513 .arg(query->getLabel())
3514 .arg(query->getIface());
3515 return (false);
3516 }
3517
3518 // Check if the DHCPv4 packet has been sent to us or to someone else.
3519 // If it hasn't been sent to us, drop it!
3520 if (!acceptServerId(query)) {
3521 LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0003)
3522 .arg(query->getLabel())
3523 .arg(query->getIface());
3524 return (false);
3525 }
3526
3527 return (true);
3528 }
3529
3530 bool
3531 Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const {
3532 // Accept all relayed messages.
3533 if (pkt->isRelayed()) {
3534 return (true);
3535 }
3536
3537 // Accept all DHCPv4-over-DHCPv6 messages.
3538 if (pkt->isDhcp4o6()) {
3539 return (true);
3540 }
3541
3542 // The source address must not be zero for the DHCPINFORM message from
3543 // the directly connected client because the server will not know where
3544 // to respond if the ciaddr was not present.
3545 try {
3546 if (pkt->getType() == DHCPINFORM) {
3547 if (pkt->getRemoteAddr().isV4Zero() &&
3548 pkt->getCiaddr().isV4Zero()) {
3549 return (false);
3550 }
3551 }
3552 } catch (...) {
3553 // If we got here, it is probably because the message type hasn't
3554 // been set. But, this should not really happen assuming that
3555 // we validate the message type prior to calling this function.
3556 return (false);
3557 }
3558 bool drop = false;
3559 bool result = (!pkt->getLocalAddr().isV4Bcast() ||
3560 selectSubnet(pkt, drop, true));
3561 if (drop) {
3562 // The packet must be dropped but as sanity_only is true it is dead code.
3563 return (false);
3564 }
3565 return (result);
3566 }
3567
3568 bool
3569 Dhcpv4Srv::acceptMessageType(const Pkt4Ptr& query) const {
3570 // When receiving a packet without message type option, getType() will
3571 // throw.
3572 int type;
3573 try {
3574 type = query->getType();
3575
3576 } catch (...) {
3577 LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0004)
3578 .arg(query->getLabel())
3579 .arg(query->getIface());
3580 return (false);
3581 }
3582
3583 // Once we know that the message type is within a range of defined DHCPv4
3584 // messages, we do a detailed check to make sure that the received message
3585 // is targeted at server. Note that we could have received some Offer
3586 // message broadcasted by the other server to a relay. Even though, the
3587 // server would rather unicast its response to a relay, let's be on the
3588 // safe side. Also, we want to drop other messages which we don't support.
3589 // All these valid messages that we are not going to process are dropped
3590 // silently.
3591
3592 switch(type) {
3593 case DHCPDISCOVER:
3594 case DHCPREQUEST:
3595 case DHCPRELEASE:
3596 case DHCPDECLINE:
3597 case DHCPINFORM:
3598 return (true);
3599 break;
3600
3601 case DHCP_NOTYPE:
3602 LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0009)
3603 .arg(query->getLabel());
3604 break;
3605
3606 default:
3607 // If we receive a message with a non-existing type, we are logging it.
3608 if (type >= DHCP_TYPES_EOF) {
3609 LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0005)
3610 .arg(query->getLabel())
3611 .arg(type);
3612 } else {
3613 // Exists but we don't support it.
3614 LOG_DEBUG(bad_packet4_logger, DBGLVL_PKT_HANDLING, DHCP4_PACKET_DROP_0006)
3615 .arg(query->getLabel())
3616 .arg(type);
3617 }
3618 break;
3619 }
3620
3621 return (false);
3622 }
3623
3624 bool
3625 Dhcpv4Srv::acceptServerId(const Pkt4Ptr& query) const {
3626 // This function is meant to be called internally by the server class, so
3627 // we rely on the caller to sanity check the pointer and we don't check
3628 // it here.
3629
3630 // Check if server identifier option is present. If it is not present
3631 // we accept the message because it is targeted to all servers.
3632 // Note that we don't check cases that server identifier is mandatory
3633 // but not present. This is meant to be sanity checked in other
3634 // functions.
3635 OptionPtr option = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
3636 if (!option) {
3637 return (true);
3638 }
3639 // Server identifier is present. Let's convert it to 4-byte address
3640 // and try to match with server identifiers used by the server.
3641 OptionCustomPtr option_custom =
3642 boost::dynamic_pointer_cast<OptionCustom>(option);
3643 // Unable to convert the option to the option type which encapsulates it.
3644 // We treat this as non-matching server id.
3645 if (!option_custom) {
3646 return (false);
3647 }
3648 // The server identifier option should carry exactly one IPv4 address.
3649 // If the option definition for the server identifier doesn't change,
3650 // the OptionCustom object should have exactly one IPv4 address and
3651 // this check is somewhat redundant. On the other hand, if someone
3652 // breaks option it may be better to check that here.
3653 if (option_custom->getDataFieldsNum() != 1) {
3654 return (false);
3655 }
3656
3657 // The server identifier MUST be an IPv4 address. If given address is
3658 // v6, it is wrong.
3659 IOAddress server_id = option_custom->readAddress();
3660 if (!server_id.isV4()) {
3661 return (false);
3662 }
3663
3664 // This function iterates over all interfaces on which the
3665 // server is listening to find the one which has a socket bound
3666 // to the address carried in the server identifier option.
3667 // This has some performance implications. However, given that
3668 // typically there will be just a few active interfaces the
3669 // performance hit should be acceptable. If it turns out to
3670 // be significant, we will have to cache server identifiers
3671 // when sockets are opened.
3672 if (IfaceMgr::instance().hasOpenSocket(server_id)) {
3673 return (true);
3674 }
3675
3676 // There are some cases when an administrator explicitly sets server
3677 // identifier (option 54) that should be used for a given, subnet,
3678 // network etc. It doesn't have to be an address assigned to any of
3679 // the server interfaces. Thus, we have to check if the server
3680 // identifier received is the one that we explicitly set in the
3681 // server configuration. At this point, we don't know which subnet
3682 // the client belongs to so we can't match the server id with any
3683 // subnet. We simply check if this server identifier is configured
3684 // anywhere. This should be good enough to eliminate exchanges
3685 // with other servers in the same network.
3686
3687 /// @todo Currently we only check server identifiers configured at the
3688 /// subnet, shared network, client class and global levels.
3689 /// This should be sufficient for most of cases. At this point, trying to
3690 /// support server identifiers on the host reservations level seems to be an
3691 /// overkill and is probably not needed. In fact, at this point we don't
3692 /// know the reservations for the client communicating with the server.
3693 /// We may revise some of these choices in the future.
3694
3695 SrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
3696
3697 // Check if there is at least one subnet configured with this server
3698 // identifier.
3699 ConstCfgSubnets4Ptr cfg_subnets = cfg->getCfgSubnets4();
3700 if (cfg_subnets->hasSubnetWithServerId(server_id)) {
3701 return (true);
3702 }
3703
3704 // This server identifier is not configured for any of the subnets, so
3705 // check on the shared network level.
3706 CfgSharedNetworks4Ptr cfg_networks = cfg->getCfgSharedNetworks4();
3707 if (cfg_networks->hasNetworkWithServerId(server_id)) {
3708 return (true);
3709 }
3710
3711 // Check if the server identifier is configured at client class level.
3712 const ClientClasses& classes = query->getClasses();
3713 for (ClientClasses::const_iterator cclass = classes.cbegin();
3714 cclass != classes.cend(); ++cclass) {
3715 // Find the client class definition for this class
3716 const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
3717 getClientClassDictionary()->findClass(*cclass);
3718 if (!ccdef) {
3719 continue;
3720 }
3721
3722 if (ccdef->getCfgOption()->empty()) {
3723 // Skip classes which don't configure options
3724 continue;
3725 }
3726
3727 OptionCustomPtr context_opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
3728 (ccdef->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
3729 if (context_opt_server_id && (context_opt_server_id->readAddress() == server_id)) {
3730 return (true);
3731 }
3732 }
3733
3734 // Finally, it is possible that the server identifier is specified
3735 // on the global level.
3736 ConstCfgOptionPtr cfg_global_options = cfg->getCfgOption();
3737 OptionCustomPtr opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
3738 (cfg_global_options->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
3739
3740 return (opt_server_id && (opt_server_id->readAddress() == server_id));
3741 }
3742
3743 void
3744 Dhcpv4Srv::sanityCheck(const Pkt4Ptr& query, RequirementLevel serverid) {
3745 OptionPtr server_id = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
3746 switch (serverid) {
3747 case FORBIDDEN:
3748 if (server_id) {
3749 isc_throw(RFCViolation, "Server-id option was not expected, but"
3750 << " received in message "
3751 << query->getName());
3752 }
3753 break;
3754
3755 case MANDATORY:
3756 if (!server_id) {
3757 isc_throw(RFCViolation, "Server-id option was expected, but not"
3758 " received in message "
3759 << query->getName());
3760 }
3761 break;
3762
3763 case OPTIONAL:
3764 // do nothing here
3765 ;
3766 }
3767
3768 // If there is HWAddress set and it is non-empty, then we're good
3769 if (query->getHWAddr() && !query->getHWAddr()->hwaddr_.empty()) {
3770 return;
3771 }
3772
3773 // There has to be something to uniquely identify the client:
3774 // either non-zero MAC address or client-id option present (or both)
3775 OptionPtr client_id = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
3776
3777 // If there's no client-id (or a useless one is provided, i.e. 0 length)
3778 if (!client_id || client_id->len() == client_id->getHeaderLen()) {
3779 isc_throw(RFCViolation, "Missing or useless client-id and no HW address"
3780 " provided in message "
3781 << query->getName());
3782 }
3783 }
3784
3785 void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
3786 Dhcpv4Exchange::classifyPacket(pkt);
3787 }
3788
3789 void Dhcpv4Srv::requiredClassify(Dhcpv4Exchange& ex) {
3790 // First collect required classes
3791 Pkt4Ptr query = ex.getQuery();
3792 ClientClasses classes = query->getClasses(true);
3793 Subnet4Ptr subnet = ex.getContext()->subnet_;
3794
3795 if (subnet) {
3796 // Begin by the shared-network
3797 SharedNetwork4Ptr network;
3798 subnet->getSharedNetwork(network);
3799 if (network) {
3800 const ClientClasses& to_add = network->getRequiredClasses();
3801 for (ClientClasses::const_iterator cclass = to_add.cbegin();
3802 cclass != to_add.cend(); ++cclass) {
3803 classes.insert(*cclass);
3804 }
3805 }
3806
3807 // Followed by the subnet
3808 const ClientClasses& to_add = subnet->getRequiredClasses();
3809 for(ClientClasses::const_iterator cclass = to_add.cbegin();
3810 cclass != to_add.cend(); ++cclass) {
3811 classes.insert(*cclass);
3812 }
3813
3814 // And finish by the pool
3815 Pkt4Ptr resp = ex.getResponse();
3816 IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS();
3817 if (resp) {
3818 addr = resp->getYiaddr();
3819 }
3820 if (!addr.isV4Zero()) {
3821 PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
3822 if (pool) {
3823 const ClientClasses& to_add = pool->getRequiredClasses();
3824 for (ClientClasses::const_iterator cclass = to_add.cbegin();
3825 cclass != to_add.cend(); ++cclass) {
3826 classes.insert(*cclass);
3827 }
3828 }
3829 }
3830
3831 // host reservation???
3832 }
3833
3834 // Run match expressions
3835 // Note getClientClassDictionary() cannot be null
3836 const ClientClassDictionaryPtr& dict =
3837 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
3838 for (ClientClasses::const_iterator cclass = classes.cbegin();
3839 cclass != classes.cend(); ++cclass) {
3840 const ClientClassDefPtr class_def = dict->findClass(*cclass);
3841 if (!class_def) {
3842 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNDEFINED)
3843 .arg(*cclass);
3844 continue;
3845 }
3846 const ExpressionPtr& expr_ptr = class_def->getMatchExpr();
3847 // Nothing to do without an expression to evaluate
3848 if (!expr_ptr) {
3849 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNTESTABLE)
3850 .arg(*cclass);
3851 continue;
3852 }
3853 // Evaluate the expression which can return false (no match),
3854 // true (match) or raise an exception (error)
3855 try {
3856 bool status = evaluateBool(*expr_ptr, *query);
3857 if (status) {
3858 LOG_INFO(options4_logger, EVAL_RESULT)
3859 .arg(*cclass)
3860 .arg(status);
3861 // Matching: add the class
3862 query->addClass(*cclass);
3863 } else {
3864 LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
3865 .arg(*cclass)
3866 .arg(status);
3867 }
3868 } catch (const Exception& ex) {
3869 LOG_ERROR(options4_logger, EVAL_RESULT)
3870 .arg(*cclass)
3871 .arg(ex.what());
3872 } catch (...) {
3873 LOG_ERROR(options4_logger, EVAL_RESULT)
3874 .arg(*cclass)
3875 .arg("get exception?");
3876 }
3877 }
3878 }
3879
3880 void
3881 Dhcpv4Srv::deferredUnpack(Pkt4Ptr& query) {
3882 // Iterate on the list of deferred option codes
3883 BOOST_FOREACH(const uint16_t& code, query->getDeferredOptions()) {
3884 OptionDefinitionPtr def;
3885 // Iterate on client classes
3886 const ClientClasses& classes = query->getClasses();
3887 for (ClientClasses::const_iterator cclass = classes.cbegin();
3888 cclass != classes.cend(); ++cclass) {
3889 // Get the client class definition for this class
3890 const ClientClassDefPtr& ccdef =
3891 CfgMgr::instance().getCurrentCfg()->
3892 getClientClassDictionary()->findClass(*cclass);
3893 // If not found skip it
3894 if (!ccdef) {
3895 continue;
3896 }
3897 // If there is no option definition skip it
3898 if (!ccdef->getCfgOptionDef()) {
3899 continue;
3900 }
3901 def = ccdef->getCfgOptionDef()->get(DHCP4_OPTION_SPACE, code);
3902 // Stop at the first client class with a definition
3903 if (def) {
3904 break;
3905 }
3906 }
3907 // If not found try the global definition
3908 if (!def) {
3909 def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, code);
3910 }
3911 if (!def) {
3912 def = LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, code);
3913 }
3914 // Finish by last resort definition
3915 if (!def) {
3916 def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, code);
3917 }
3918 // If not defined go to the next option
3919 if (!def) {
3920 continue;
3921 }
3922 // Get the existing option for its content and remove all
3923 OptionPtr opt = query->getOption(code);
3924 if (!opt) {
3925 // should not happen but do not crash anyway
3926 LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
3927 DHCP4_DEFERRED_OPTION_MISSING)
3928 .arg(code);
3929 continue;
3930 }
3931 const OptionBuffer buf = opt->getData();
3932 try {
3933 // Unpack the option
3934 opt = def->optionFactory(Option::V4, code, buf);
3935 } catch (const std::exception& e) {
3936 // Failed to parse the option.
3937 LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
3938 DHCP4_DEFERRED_OPTION_UNPACK_FAIL)
3939 .arg(code)
3940 .arg(e.what());
3941 continue;
3942 }
3943 while (query->delOption(code)) {
3944 // continue
3945 }
3946 // Add the unpacked option.
3947 query->addOption(opt);
3948 }
3949 }
3950
3951 void
3952 Dhcpv4Srv::startD2() {
3953 D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
3954 if (d2_mgr.ddnsEnabled()) {
3955 // Updates are enabled, so lets start the sender, passing in
3956 // our error handler.
3957 // This may throw so wherever this is called needs to ready.
3958 d2_mgr.startSender(std::bind(&Dhcpv4Srv::d2ClientErrorHandler,
3959 this, ph::_1, ph::_2));
3960 }
3961 }
3962
3963 void
3964 Dhcpv4Srv::stopD2() {
3965 D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
3966 if (d2_mgr.ddnsEnabled()) {
3967 // Updates are enabled, so lets stop the sender
3968 d2_mgr.stopSender();
3969 }
3970 }
3971
3972 void
3973 Dhcpv4Srv::d2ClientErrorHandler(const
3974 dhcp_ddns::NameChangeSender::Result result,
3975 dhcp_ddns::NameChangeRequestPtr& ncr) {
3976 LOG_ERROR(ddns4_logger, DHCP4_DDNS_REQUEST_SEND_FAILED).
3977 arg(result).arg((ncr ? ncr->toText() : " NULL "));
3978 // We cannot communicate with kea-dhcp-ddns, suspend further updates.
3979 /// @todo We may wish to revisit this, but for now we will simply turn
3980 /// them off.
3981 CfgMgr::instance().getD2ClientMgr().suspendUpdates();
3982 }
3983
3984 // Refer to config_report so it will be embedded in the binary
3985 const char* const* dhcp4_config_report = isc::detail::config_report;
3986
3987 std::string
3988 Dhcpv4Srv::getVersion(bool extended) {
3989 std::stringstream tmp;
3990
3991 tmp << VERSION;
3992 if (extended) {
3993 tmp << endl << EXTENDED_VERSION << endl;
3994 tmp << "linked with:" << endl;
3995 tmp << Logger::getVersion() << endl;
3996 tmp << CryptoLink::getVersion() << endl;
3997 tmp << "database:" << endl;
3998 #ifdef HAVE_MYSQL
3999 tmp << MySqlLeaseMgr::getDBVersion() << endl;
4000 #endif
4001 #ifdef HAVE_PGSQL
4002 tmp << PgSqlLeaseMgr::getDBVersion() << endl;
4003 #endif
4004 #ifdef HAVE_CQL
4005 tmp << CqlLeaseMgr::getDBVersion() << endl;
4006 #endif
4007 tmp << Memfile_LeaseMgr::getDBVersion();
4008
4009 // @todo: more details about database runtime
4010 }
4011
4012 return (tmp.str());
4013 }
4014
4015 void Dhcpv4Srv::processStatsReceived(const Pkt4Ptr& query) {
4016 // Note that we're not bumping pkt4-received statistic as it was
4017 // increased early in the packet reception code.
4018
4019 string stat_name = "pkt4-unknown-received";
4020 try {
4021 switch (query->getType()) {
4022 case DHCPDISCOVER:
4023 stat_name = "pkt4-discover-received";
4024 break;
4025 case DHCPOFFER:
4026 // Should not happen, but let's keep a counter for it
4027 stat_name = "pkt4-offer-received";
4028 break;
4029 case DHCPREQUEST:
4030 stat_name = "pkt4-request-received";
4031 break;
4032 case DHCPACK:
4033 // Should not happen, but let's keep a counter for it
4034 stat_name = "pkt4-ack-received";
4035 break;
4036 case DHCPNAK:
4037 // Should not happen, but let's keep a counter for it
4038 stat_name = "pkt4-nak-received";
4039 break;
4040 case DHCPRELEASE:
4041 stat_name = "pkt4-release-received";
4042 break;
4043 case DHCPDECLINE:
4044 stat_name = "pkt4-decline-received";
4045 break;
4046 case DHCPINFORM:
4047 stat_name = "pkt4-inform-received";
4048 break;
4049 default:
4050 ; // do nothing
4051 }
4052 }
4053 catch (...) {
4054 // If the incoming packet doesn't have option 53 (message type)
4055 // or a hook set pkt4_receive_skip, then Pkt4::getType() may
4056 // throw an exception. That's ok, we'll then use the default
4057 // name of pkt4-unknown-received.
4058 }
4059
4060 isc::stats::StatsMgr::instance().addValue(stat_name,
4061 static_cast<int64_t>(1));
4062 }
4063
4064 void Dhcpv4Srv::processStatsSent(const Pkt4Ptr& response) {
4065 // Increase generic counter for sent packets.
4066 isc::stats::StatsMgr::instance().addValue("pkt4-sent",
4067 static_cast<int64_t>(1));
4068
4069 // Increase packet type specific counter for packets sent.
4070 string stat_name;
4071 switch (response->getType()) {
4072 case DHCPOFFER:
4073 stat_name = "pkt4-offer-sent";
4074 break;
4075 case DHCPACK:
4076 stat_name = "pkt4-ack-sent";
4077 break;
4078 case DHCPNAK:
4079 stat_name = "pkt4-nak-sent";
4080 break;
4081 default:
4082 // That should never happen
4083 return;
4084 }
4085
4086 isc::stats::StatsMgr::instance().addValue(stat_name,
4087 static_cast<int64_t>(1));
4088 }
4089
4090 int Dhcpv4Srv::getHookIndexBuffer4Receive() {
4091 return (Hooks.hook_index_buffer4_receive_);
4092 }
4093
4094 int Dhcpv4Srv::getHookIndexPkt4Receive() {
4095 return (Hooks.hook_index_pkt4_receive_);
4096 }
4097
4098 int Dhcpv4Srv::getHookIndexSubnet4Select() {
4099 return (Hooks.hook_index_subnet4_select_);
4100 }
4101
4102 int Dhcpv4Srv::getHookIndexLease4Release() {
4103 return (Hooks.hook_index_lease4_release_);
4104 }
4105
4106 int Dhcpv4Srv::getHookIndexPkt4Send() {
4107 return (Hooks.hook_index_pkt4_send_);
4108 }
4109
4110 int Dhcpv4Srv::getHookIndexBuffer4Send() {
4111 return (Hooks.hook_index_buffer4_send_);
4112 }
4113
4114 int Dhcpv4Srv::getHookIndexLease4Decline() {
4115 return (Hooks.hook_index_lease4_decline_);
4116 }
4117
4118 void Dhcpv4Srv::discardPackets() {
4119 // Dump all of our current packets, anything that is mid-stream
4120 HooksManager::clearParkingLots();
4121 }
4122
4123 std::list<std::list<std::string>> Dhcpv4Srv::jsonPathsToRedact() const {
4124 static std::list<std::list<std::string>> const list({
4125 {"config-control", "config-databases", "[]"},
4126 {"hooks-libraries", "[]", "parameters", "*"},
4127 {"hosts-database"},
4128 {"hosts-databases", "[]"},
4129 {"lease-database"},
4130 });
4131 return list;
4132 }
4133
4134 } // namespace dhcp
4135 } // namespace isc
4136