Kea 1.5.0
ha_impl.cc
Go to the documentation of this file.
1// Copyright (C) 2018 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
9#include <ha_config_parser.h>
10#include <ha_impl.h>
11#include <ha_log.h>
12#include <asiolink/io_service.h>
13#include <cc/data.h>
15#include <dhcp/pkt4.h>
16#include <dhcp/pkt6.h>
17#include <dhcpsrv/lease.h>
18#include <stats/stats_mgr.h>
19
20using namespace isc::asiolink;
21using namespace isc::config;
22using namespace isc::data;
23using namespace isc::dhcp;
24using namespace isc::hooks;
25using namespace isc::log;
26
27namespace isc {
28namespace ha {
29
31 : config_(new HAConfig()) {
32}
33
34void
35HAImpl::configure(const ConstElementPtr& input_config) {
36 HAConfigParser parser;
37 parser.parse(config_, input_config);
38}
39
40void
42 const NetworkStatePtr& network_state,
43 const HAServerType& server_type) {
44 // Create the HA service and crank up the state machine.
45 service_ = boost::make_shared<HAService>(io_service, network_state,
46 config_, server_type);
47}
48
49void
51 Pkt4Ptr query4;
52 callout_handle.getArgument("query4", query4);
53
56 try {
57 // We have to unpack the query to get access into HW address which is
58 // used to load balance the packet.
59 query4->unpack();
60
61 } catch (const SkipRemainingOptionsError& ex) {
62 // An option failed to unpack but we are to attempt to process it
63 // anyway. Log it and let's hope for the best.
65 HA_BUFFER4_RECEIVE_PACKET_OPTIONS_SKIPPED)
66 .arg(ex.what());
67
68 } catch (const std::exception& ex) {
69 // Packet parsing failed. Drop the packet.
70 LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_BUFFER4_RECEIVE_UNPACK_FAILED)
71 .arg(query4->getRemoteAddr().toText())
72 .arg(query4->getLocalAddr().toText())
73 .arg(query4->getIface())
74 .arg(ex.what());
75
76 // Increase the statistics of parse failures and dropped packets.
77 isc::stats::StatsMgr::instance().addValue("pkt4-parse-failed",
78 static_cast<int64_t>(1));
79 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
80 static_cast<int64_t>(1));
81
82
84 return;
85 }
86
87 // Check if we should process this query. If not, drop it.
88 if (!service_->inScope(query4)) {
89 LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_BUFFER4_RECEIVE_NOT_FOR_US)
90 .arg(query4->getLabel());
92
93 } else {
94 // We have successfully parsed the query so we have to signal
95 // to the server that it must not parse it.
97 }
98}
99
100void
102 // If the hook library is configured to not send lease updates to the
103 // partner, there is nothing to do because this whole callout is
104 // currently about sending lease updates.
105 if (!config_->amSendingLeaseUpdates()) {
106 // No need to log it, because it was already logged when configuration
107 // was applied.
108 return;
109 }
110
111 Pkt4Ptr query4;
112 Lease4CollectionPtr leases4;
113 Lease4CollectionPtr deleted_leases4;
114
115 // Get all arguments available for the leases4_committed hook point.
116 // If any of these arguments is not available this is a programmatic
117 // error. An exception will be thrown which will be caught by the
118 // caller and logged.
119 callout_handle.getArgument("query4", query4);
120
121 callout_handle.getArgument("leases4", leases4);
122 callout_handle.getArgument("deleted_leases4", deleted_leases4);
123
124 // In some cases we may have no leases, e.g. DHCPNAK.
125 if (leases4->empty() && deleted_leases4->empty()) {
126 LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_LEASES4_COMMITTED_NOTHING_TO_UPDATE)
127 .arg(query4->getLabel());
128 return;
129 }
130
131 // Get the parking lot for this hook point. We're going to remember this
132 // pointer until we unpark the packet.
133 ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
134
135 // Asynchronously send lease updates. In some cases no updates will be sent,
136 // e.g. when this server is in the partner-down state and there are no backup
137 // servers. In those cases we simply return without parking the DHCP query.
138 // The response will be sent to the client immediately.
139 if (service_->asyncSendLeaseUpdates(query4, leases4, deleted_leases4, parking_lot) == 0) {
140 return;
141 }
142
143 // This is required step every time we ask the server to park the packet.
144 // The reference counting is required to keep the packet parked until
145 // all callouts call unpark. Then, the packet gets unparked and the
146 // associated callback is triggered. The callback resumes packet processing.
147 parking_lot->reference(query4);
148
149 // The callout returns this status code to indicate to the server that it
150 // should park the query packet.
152}
153
154void
156 Pkt6Ptr query6;
157 callout_handle.getArgument("query6", query6);
158
161 try {
162 // We have to unpack the query to get access into DUID which is
163 // used to load balance the packet.
164 query6->unpack();
165
166 } catch (const SkipRemainingOptionsError& ex) {
167 // An option failed to unpack but we are to attempt to process it
168 // anyway. Log it and let's hope for the best.
170 HA_BUFFER6_RECEIVE_PACKET_OPTIONS_SKIPPED)
171 .arg(ex.what());
172
173 } catch (const std::exception& ex) {
174 // Packet parsing failed. Drop the packet.
175 LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_BUFFER6_RECEIVE_UNPACK_FAILED)
176 .arg(query6->getRemoteAddr().toText())
177 .arg(query6->getLocalAddr().toText())
178 .arg(query6->getIface())
179 .arg(ex.what());
180
181 // Increase the statistics of parse failures and dropped packets.
182 isc::stats::StatsMgr::instance().addValue("pkt6-parse-failed",
183 static_cast<int64_t>(1));
184 isc::stats::StatsMgr::instance().addValue("pkt6-receive-drop",
185 static_cast<int64_t>(1));
186
187
189 return;
190 }
191
192 // Check if we should process this query. If not, drop it.
193 if (!service_->inScope(query6)) {
194 LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_BUFFER6_RECEIVE_NOT_FOR_US)
195 .arg(query6->getLabel());
197
198 } else {
199 // We have successfully parsed the query so we have to signal
200 // to the server that it must not parse it.
202 }
203}
204
205void
207 // If the hook library is configured to not send lease updates to the
208 // partner, there is nothing to do because this whole callout is
209 // currently about sending lease updates.
210 if (!config_->amSendingLeaseUpdates()) {
211 // No need to log it, because it was already logged when configuration
212 // was applied.
213 return;
214 }
215
216 Pkt6Ptr query6;
217 Lease6CollectionPtr leases6;
218 Lease6CollectionPtr deleted_leases6;
219
220 // Get all arguments available for the leases6_committed hook point.
221 // If any of these arguments is not available this is a programmatic
222 // error. An exception will be thrown which will be caught by the
223 // caller and logged.
224 callout_handle.getArgument("query6", query6);
225
226 callout_handle.getArgument("leases6", leases6);
227 callout_handle.getArgument("deleted_leases6", deleted_leases6);
228
229 // In some cases we may have no leases.
230 if (leases6->empty() && deleted_leases6->empty()) {
231 LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_LEASES6_COMMITTED_NOTHING_TO_UPDATE)
232 .arg(query6->getLabel());
233 return;
234 }
235
236 // Get the parking lot for this hook point. We're going to remember this
237 // pointer until we unpark the packet.
238 ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
239
240 // Asynchronously send lease updates. In some cases no updates will be sent,
241 // e.g. when this server is in the partner-down state and there are no backup
242 // servers. In those cases we simply return without parking the DHCP query.
243 // The response will be sent to the client immediately.
244 if (service_->asyncSendLeaseUpdates(query6, leases6, deleted_leases6, parking_lot) == 0) {
245 return;
246 }
247
248 // This is required step every time we ask the server to park the packet.
249 // The reference counting is required to keep the packet parked until
250 // all callouts call unpark. Then, the packet gets unparked and the
251 // associated callback is triggered. The callback resumes packet processing.
252 parking_lot->reference(query6);
253
254 // The callout returns this status code to indicate to the server that it
255 // should park the query packet.
257}
258
259void
261 std::string command_name;
262 callout_handle.getArgument("name", command_name);
263 if (command_name == "dhcp-enable") {
264 service_->adjustNetworkState();
265 }
266}
267
268void
270 ConstElementPtr response = service_->processHeartbeat();
271 callout_handle.setArgument("response", response);
272}
273
274void
276 // Command must always be provided.
277 ConstElementPtr command;
278 callout_handle.getArgument("command", command);
279
280 // Retrieve arguments.
281 ConstElementPtr args;
282 static_cast<void>(parseCommand(args, command));
283
284 ConstElementPtr server_name;
285 unsigned int max_period_value = 0;
286
287 try {
288 // Arguments are required for the ha-sync command.
289 if (!args) {
290 isc_throw(BadValue, "arguments not found in the 'ha-sync' command");
291 }
292
293 // Arguments must be a map.
294 if (args->getType() != Element::map) {
295 isc_throw(BadValue, "arguments in the 'ha-sync' command are not a map");
296 }
297
298 // server-name is mandatory. Otherwise how can we know the server to
299 // communicate with.
300 server_name = args->get("server-name");
301 if (!server_name) {
302 isc_throw(BadValue, "'server-name' is mandatory for the 'ha-sync' command");
303 }
304
305 // server-name must obviously be a string.
306 if (server_name->getType() != Element::string) {
307 isc_throw(BadValue, "'server-name' must be a string in the 'ha-sync' command");
308 }
309
310 // max-period is optional. In fact it is optional for dhcp-disable command too.
311 ConstElementPtr max_period = args->get("max-period");
312 if (max_period) {
313 // If it is specified, it must be a positive integer.
314 if ((max_period->getType() != Element::integer) ||
315 (max_period->intValue() <= 0)) {
316 isc_throw(BadValue, "'max-period' must be a positive integer in the 'ha-sync' command");
317 }
318
319 max_period_value = static_cast<unsigned int>(max_period->intValue());
320 }
321
322 } catch (const std::exception& ex) {
323 // There was an error while parsing command arguments. Return an error status
324 // code to notify the user.
326 callout_handle.setArgument("response", response);
327 return;
328 }
329
330 // Command parsing was successful, so let's process the command.
331 ConstElementPtr response = service_->processSynchronize(server_name->stringValue(),
332 max_period_value);
333 callout_handle.setArgument("response", response);
334}
335
336void
338 // Command must always be provided.
339 ConstElementPtr command;
340 callout_handle.getArgument("command", command);
341
342 // Retrieve arguments.
343 ConstElementPtr args;
344 static_cast<void>(parseCommand(args, command));
345
346 std::vector<std::string> scopes_vector;
347
348 try {
349 // Arguments must be present.
350 if (!args) {
351 isc_throw(BadValue, "arguments not found in the 'ha-scopes' command");
352 }
353
354 // Arguments must be a map.
355 if (args->getType() != Element::map) {
356 isc_throw(BadValue, "arguments in the 'ha-scopes' command are not a map");
357 }
358
359 // scopes argument is mandatory.
360 ConstElementPtr scopes = args->get("scopes");
361 if (!scopes) {
362 isc_throw(BadValue, "'scopes' is mandatory for the 'ha-scopes' command");
363 }
364
365 // It contains a list of scope names.
366 if (scopes->getType() != Element::list) {
367 isc_throw(BadValue, "'scopes' must be a list in the 'ha-scopes' command");
368 }
369
370 // Retrieve scope names from this list. The list may be empty to clear the
371 // scopes.
372 for (size_t i = 0; i < scopes->size(); ++i) {
373 ConstElementPtr scope = scopes->get(i);
374 if (!scope || scope->getType() != Element::string) {
375 isc_throw(BadValue, "scope name must be a string in the 'scopes' argument");
376 }
377 scopes_vector.push_back(scope->stringValue());
378 }
379
380 } catch (const std::exception& ex) {
381 // There was an error while parsing command arguments. Return an error status
382 // code to notify the user.
384 callout_handle.setArgument("response", response);
385 return;
386 }
387
388 // Command parsing was successful, so let's process the command.
389 ConstElementPtr response = service_->processScopes(scopes_vector);
390 callout_handle.setArgument("response", response);
391}
392
393void
395 ConstElementPtr response = service_->processContinue();
396 callout_handle.setArgument("response", response);
397}
398
399
400} // end of namespace isc::ha
401} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Exception thrown during option unpacking This exception is thrown when an error has occurred,...
Definition: option.h:52
Configuration parser for High Availability.
void parse(const HAConfigPtr &config_storage, const data::ConstElementPtr &config)
Parses HA configuration.
Storage for High Availability configuration.
Definition: ha_config.h:29
void scopesHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-scopes command.
Definition: ha_impl.cc:337
void continueHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-continue command.
Definition: ha_impl.cc:394
HAServicePtr service_
Pointer to the high availability service (state machine).
Definition: ha_impl.h:151
void configure(const data::ConstElementPtr &input_config)
Parases configuration.
Definition: ha_impl.cc:35
void synchronizeHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-sync command.
Definition: ha_impl.cc:275
void startService(const asiolink::IOServicePtr &io_service, const dhcp::NetworkStatePtr &network_state, const HAServerType &server_type)
Creates high availability service using current configuration.
Definition: ha_impl.cc:41
void leases4Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases4_committed" callout.
Definition: ha_impl.cc:101
void buffer4Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer4_receive" callout.
Definition: ha_impl.cc:50
HAConfigPtr config_
Holds parsed configuration.
Definition: ha_impl.h:148
void buffer6Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer6_receive" callout.
Definition: ha_impl.cc:155
void commandProcessed(hooks::CalloutHandle &callout_handle)
Implementation of the "command_processed" callout.
Definition: ha_impl.cc:260
HAImpl()
Constructor.
Definition: ha_impl.cc:30
void leases6Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases6_committed" callout.
Definition: ha_impl.cc:206
void heartbeatHandler(hooks::CalloutHandle &callout_handle)
Implements handle for the heartbeat command.
Definition: ha_impl.cc:269
Per-packet callout handle.
@ NEXT_STEP_PARK
park the packet
@ NEXT_STEP_DROP
drop the packet
@ NEXT_STEP_SKIP
skip the next processing step
ParkingLotHandlePtr getParkingLotHandlePtr() const
Returns pointer to the parking lot handle for this hook point.
void setStatus(const CalloutNextStep next)
Sets the next processing step.
void getArgument(const std::string &name, T &value) const
Get argument.
void setArgument(const std::string &name, T value)
Set argument.
static StatsMgr & instance()
Statistics Manager accessor method.
Definition: stats_mgr.cc:21
This file contains several functions and constants that are used for handling commands and responses ...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
void addValue(const std::string &name, const int64_t value)
Records incremental integer observation.
Definition: stats_mgr.cc:46
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
std::string parseCommand(ConstElementPtr &arg, ConstElementPtr command)
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
boost::shared_ptr< Lease4Collection > Lease4CollectionPtr
A shared pointer to the collection of IPv4 leases.
Definition: lease.h:458
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:546
boost::shared_ptr< NetworkState > NetworkStatePtr
Pointer to the NetworkState object.
boost::shared_ptr< Lease6Collection > Lease6CollectionPtr
A shared pointer to the collection of IPv6 leases.
Definition: lease.h:608
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition: pkt6.h:31
isc::log::Logger ha_logger("ha-hooks")
Definition: ha_log.h:17
HAServerType
Lists possible server types for which HA service is created.
boost::shared_ptr< ParkingLotHandle > ParkingLotHandlePtr
Pointer to the parking lot handle.
Definition: parking_lots.h:292
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:65
Defines the logger used by the top-level component of kea-dhcp-ddns.