Kea 1.9.11
query_filter.cc
Go to the documentation of this file.
1// Copyright (C) 2018-2020 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_log.h>
10#include <query_filter.h>
11#include <dhcp/dhcp4.h>
12#include <dhcp/dhcp6.h>
13#include <dhcp/option.h>
16
17#include <array>
18#include <iostream>
19#include <sstream>
20
21using namespace isc::dhcp;
22using namespace isc::log;
23using namespace isc::util;
24
25namespace {
26
30std::array<uint8_t, 256> loadb_mx_tbl = { {
31 251, 175, 119, 215, 81, 14, 79, 191, 103, 49, 181, 143, 186, 157, 0,
32 232, 31, 32, 55, 60, 152, 58, 17, 237, 174, 70, 160, 144, 220, 90, 57,
33 223, 59, 3, 18, 140, 111, 166, 203, 196, 134, 243, 124, 95, 222, 179,
34 197, 65, 180, 48, 36, 15, 107, 46, 233, 130, 165, 30, 123, 161, 209, 23,
35 97, 16, 40, 91, 219, 61, 100, 10, 210, 109, 250, 127, 22, 138, 29, 108,
36 244, 67, 207, 9, 178, 204, 74, 98, 126, 249, 167, 116, 34, 77, 193,
37 200, 121, 5, 20, 113, 71, 35, 128, 13, 182, 94, 25, 226, 227, 199, 75,
38 27, 41, 245, 230, 224, 43, 225, 177, 26, 155, 150, 212, 142, 218, 115,
39 241, 73, 88, 105, 39, 114, 62, 255, 192, 201, 145, 214, 168, 158, 221,
40 148, 154, 122, 12, 84, 82, 163, 44, 139, 228, 236, 205, 242, 217, 11,
41 187, 146, 159, 64, 86, 239, 195, 42, 106, 198, 118, 112, 184, 172, 87,
42 2, 173, 117, 176, 229, 247, 253, 137, 185, 99, 164, 102, 147, 45, 66,
43 231, 52, 141, 211, 194, 206, 246, 238, 56, 110, 78, 248, 63, 240, 189,
44 93, 92, 51, 53, 183, 19, 171, 72, 50, 33, 104, 101, 69, 8, 252, 83, 120,
45 76, 135, 85, 54, 202, 125, 188, 213, 96, 235, 136, 208, 162, 129, 190,
46 132, 156, 38, 47, 1, 7, 254, 24, 4, 216, 131, 89, 21, 28, 133, 37, 153,
47 149, 80, 170, 68, 6, 169, 234, 151 }
48};
49
50} // end of anonymous namespace
51
52namespace isc {
53namespace ha {
54
56 : config_(config), peers_(), scopes_(), active_servers_(0),
57 mutex_(new std::mutex) {
58
59 // Make sure that the configuration is valid. We make certain
60 // assumptions about the availability of the servers' configurations
61 // in the config_ structure.
62 config_->validate();
63
64 HAConfig::PeerConfigMap peers_map = config->getAllServersConfig();
65 std::vector<HAConfig::PeerConfigPtr> backup_peers;
66
67 // The returned configurations are not ordered. Let's iterate over them
68 // and put them in the desired order.
69 for (auto peer_pair = peers_map.begin(); peer_pair != peers_map.end(); ++peer_pair) {
70 auto peer = peer_pair->second;
71 // The primary server is always first on the list.
72 if (peer->getRole() == HAConfig::PeerConfig::PRIMARY) {
73 peers_.insert(peers_.begin(), peer);
75
76 // The secondary server is always behind the primary server.
77 } else if ((peer->getRole() == HAConfig::PeerConfig::SECONDARY) ||
78 (peer->getRole() == HAConfig::PeerConfig::STANDBY)) {
79 peers_.push_back(peer);
80
81 // If this is a secondary server, we're in the load balancing
82 // mode, in which case we have two active servers.
83 if (peer->getRole() == HAConfig::PeerConfig::SECONDARY) {
85 }
86
87 // If this is neither primary nor secondary/standby, it is a backup.
88 } else {
89 backup_peers.push_back(peer);
90 }
91 }
92
93 // Append backup servers to the list.
94 if (!backup_peers.empty()) {
95 peers_.insert(peers_.end(), backup_peers.begin(), backup_peers.end());
96 }
97
98 // The query filter is initially setup to serve default scopes, i.e. for the
99 // load balancing case the primary and secondary are responsible for their
100 // own scopes. The backup servers are not responding to any queries. In the
101 // hot standby mode, the primary server is responsible for the entire traffic.
102 // The standby server is not responding.
104}
105
106void
107QueryFilter::serveScope(const std::string& scope_name) {
108 if (MultiThreadingMgr::instance().getMode()) {
109 std::lock_guard<std::mutex> lock(*mutex_);
110 serveScopeInternal(scope_name);
111 } else {
112 serveScopeInternal(scope_name);
113 }
114}
115
116void
117QueryFilter::serveScopeInternal(const std::string& scope_name) {
118 validateScopeName(scope_name);
119 scopes_[scope_name] = true;
120}
121
122void
123QueryFilter::serveScopeOnly(const std::string& scope_name) {
124 if (MultiThreadingMgr::instance().getMode()) {
125 std::lock_guard<std::mutex> lock(*mutex_);
126 serveScopeOnlyInternal(scope_name);
127 } else {
128 serveScopeOnlyInternal(scope_name);
129 }
130}
131
132void
133QueryFilter::serveScopeOnlyInternal(const std::string& scope_name) {
134 validateScopeName(scope_name);
135 serveNoScopesInternal();
136 serveScopeInternal(scope_name);
137}
138
139void
140QueryFilter::serveScopes(const std::vector<std::string>& scopes) {
141 if (MultiThreadingMgr::instance().getMode()) {
142 std::lock_guard<std::mutex> lock(*mutex_);
143 serveScopesInternal(scopes);
144 } else {
145 serveScopesInternal(scopes);
146 }
147}
148
149void
150QueryFilter::serveScopesInternal(const std::vector<std::string>& scopes) {
151 // Remember currently enabled scopes in case we fail to process
152 // the provided list of scopes.
153 auto current_scopes = scopes_;
154 try {
155 serveNoScopesInternal();
156 for (size_t i = 0; i < scopes.size(); ++i) {
157 serveScopeInternal(scopes[i]);
158 }
159
160 } catch (...) {
161 // There was an error processing scopes list. Need to revert
162 // to the previous configuration.
163 scopes_ = current_scopes;
164 throw;
165 }
166}
167
168void
170 if (MultiThreadingMgr::instance().getMode()) {
171 std::lock_guard<std::mutex> lock(*mutex_);
172 serveDefaultScopesInternal();
173 } else {
174 serveDefaultScopesInternal();
175 }
176}
177
178void
179QueryFilter::serveDefaultScopesInternal() {
180 // Get this server instance configuration.
181 HAConfig::PeerConfigPtr my_config = config_->getThisServerConfig();
182 HAConfig::PeerConfig::Role my_role = my_config->getRole();
183
184 // Clear scopes.
185 serveNoScopesInternal();
186
187 // If I am primary or secondary, then I am only responsible for my own
188 // scope. If I am standby, I am not responsible for any scope.
189 if ((my_role == HAConfig::PeerConfig::PRIMARY) ||
190 (my_role == HAConfig::PeerConfig::SECONDARY)) {
191 serveScopeInternal(my_config->getName());
192 }
193}
194
195void
197 if (MultiThreadingMgr::instance().getMode()) {
198 std::lock_guard<std::mutex> lock(*mutex_);
199 serveFailoverScopesInternal();
200 } else {
201 serveFailoverScopesInternal();
202 }
203}
204
205void
206QueryFilter::serveFailoverScopesInternal() {
207 // Clear scopes.
208 serveNoScopesInternal();
209
210 // Iterate over the roles of all servers to see which scope should
211 // be enabled.
212 for (auto peer = peers_.begin(); peer != peers_.end(); ++peer) {
213 // The scope of the primary server must always be served. If we're
214 // doing load balancing, the scope of the secondary server also
215 // has to be served. Regardless if I am primary or secondary,
216 // I will start serving queries from both scopes. If I am a
217 // standby server, I will start serving the scope of the primary
218 // server.
219 if (((*peer)->getRole() == HAConfig::PeerConfig::PRIMARY) ||
220 ((*peer)->getRole() == HAConfig::PeerConfig::SECONDARY)) {
221 serveScopeInternal((*peer)->getName());
222 }
223 }
224}
225
226void
228 if (MultiThreadingMgr::instance().getMode()) {
229 std::lock_guard<std::mutex> lock(*mutex_);
230 serveNoScopesInternal();
231 } else {
232 serveNoScopesInternal();
233 }
234}
235
236void
237QueryFilter::serveNoScopesInternal() {
238 scopes_.clear();
239
240 // Disable scope for each peer in the configuration.
241 for (auto peer = peers_.begin(); peer != peers_.end(); ++peer) {
242 scopes_[(*peer)->getName()] = false;
243 }
244}
245
246bool
247QueryFilter::amServingScope(const std::string& scope_name) const {
248 if (MultiThreadingMgr::instance().getMode()) {
249 std::lock_guard<std::mutex> lock(*mutex_);
250 return (amServingScopeInternal(scope_name));
251 } else {
252 return (amServingScopeInternal(scope_name));
253 }
254}
255
256bool
257QueryFilter::amServingScopeInternal(const std::string& scope_name) const {
258 auto scope = scopes_.find(scope_name);
259 return ((scope == scopes_.end()) || (scope->second));
260}
261
262std::set<std::string>
264 if (MultiThreadingMgr::instance().getMode()) {
265 std::lock_guard<std::mutex> lock(*mutex_);
266 return (getServedScopesInternal());
267 } else {
268 return (getServedScopesInternal());
269 }
270}
271
272std::set<std::string>
273QueryFilter::getServedScopesInternal() const {
274 std::set<std::string> scope_set;
275 for (auto scope : scopes_) {
276 if (scope.second) {
277 scope_set.insert(scope.first);
278 }
279 }
280 return (scope_set);
281}
282
283bool
284QueryFilter::inScope(const dhcp::Pkt4Ptr& query4, std::string& scope_class) const {
285 if (MultiThreadingMgr::instance().getMode()) {
286 std::lock_guard<std::mutex> lock(*mutex_);
287 return (inScopeInternal(query4, scope_class));
288 } else {
289 return (inScopeInternal(query4, scope_class));
290 }
291}
292
293bool
294QueryFilter::inScope(const dhcp::Pkt6Ptr& query6, std::string& scope_class) const {
295 if (MultiThreadingMgr::instance().getMode()) {
296 std::lock_guard<std::mutex> lock(*mutex_);
297 return (inScopeInternal(query6, scope_class));
298 } else {
299 return (inScopeInternal(query6, scope_class));
300 }
301}
302
303template<typename QueryPtrType>
304bool
305QueryFilter::inScopeInternal(const QueryPtrType& query,
306 std::string& scope_class) const {
307 if (!query) {
308 isc_throw(BadValue, "query must not be null");
309 }
310
311 int candidate_server = 0;
312
313 // If we're doing load balancing we have to check if this query
314 // belongs to us or the partner. If it belongs to a partner but
315 // we're configured to serve this scope, we should accept it.
316 if (config_->getHAMode() == HAConfig::LOAD_BALANCING) {
317 candidate_server = loadBalance(query);
318 // Malformed query received.
319 if (candidate_server < 0) {
320 return (false);
321 }
322 }
323
324 auto scope = peers_[candidate_server]->getName();
325 scope_class = makeScopeClass(scope);
326 return ((candidate_server >= 0) && amServingScopeInternal(scope));
327}
328
329int
331 uint8_t lb_hash = 0;
332 // Try to compute the hash by client identifier if the client
333 // identifier has been specified.
334 OptionPtr opt_client_id = query4->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
335 if (opt_client_id && !opt_client_id->getData().empty()) {
336 const auto& client_id_key = opt_client_id->getData();
337 lb_hash = loadBalanceHash(&client_id_key[0], client_id_key.size());
338
339 } else {
340 // No client identifier available. Use the HW address instead.
341 HWAddrPtr hwaddr = query4->getHWAddr();
342 if (hwaddr && !hwaddr->hwaddr_.empty()) {
343 lb_hash = loadBalanceHash(&hwaddr->hwaddr_[0], hwaddr->hwaddr_.size());
344
345 } else {
346 // No client identifier and no HW address. Indicate an
347 // error.
348 std::stringstream xid;
349 xid << "0x" << std::hex << query4->getTransid() << std::dec;
351 .arg(xid.str());
352 return (-1);
353 }
354 }
355
356 // The hash value modulo number of active servers gives an index
357 // of the server to process the packet.
358 return (active_servers_ > 0 ? static_cast<int>(lb_hash % active_servers_) : -1);
359}
360
361int
363 uint8_t lb_hash = 0;
364 // Compute the hash by DUID if the DUID.
365 OptionPtr opt_duid = query6->getOption(D6O_CLIENTID);
366 if (opt_duid && !opt_duid->getData().empty()) {
367 const auto& duid_key = opt_duid->getData();
368 lb_hash = loadBalanceHash(&duid_key[0], duid_key.size());
369
370 } else {
371 // No DUID. Indicate an error.
372 std::stringstream xid;
373 xid << "0x" << std::hex << query6->getTransid() << std::dec;
375 .arg(xid.str());
376 return (-1);
377 }
378
379 // The hash value modulo number of active servers gives an index
380 // of the server to process the packet.
381 return (active_servers_ > 0 ? static_cast<int>(lb_hash % active_servers_) : -1);
382}
383
384uint8_t
385QueryFilter::loadBalanceHash(const uint8_t* key, const size_t key_len) const {
386 uint8_t hash = static_cast<uint8_t>(key_len);
387
388 for (auto i = key_len; i > 0;) {
389 hash = loadb_mx_tbl[hash ^ key[--i]];
390 }
391
392 return (hash);
393}
394
395void
396QueryFilter::validateScopeName(const std::string& scope_name) const {
397 try {
398 // If there is no such server, the scope name is invalid.
399 static_cast<void>(config_->getPeerConfig(scope_name));
400
401 } catch (...) {
402 isc_throw(BadValue, "invalid server name specified '" << scope_name
403 << "' while enabling/disabling HA scopes");
404 }
405}
406
407std::string
408QueryFilter::makeScopeClass(const std::string& scope_name) const {
409 return (std::string("HA_") + scope_name);
410}
411
412} // end of namespace isc::ha
413} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Role
Server's role in the High Availability setup.
Definition: ha_config.h:70
std::map< std::string, PeerConfigPtr > PeerConfigMap
Map of the servers' configurations.
Definition: ha_config.h:232
boost::shared_ptr< PeerConfig > PeerConfigPtr
Pointer to the server's configuration.
Definition: ha_config.h:229
void serveScopes(const std::vector< std::string > &scopes)
Enables selected scopes.
bool inScope(const dhcp::Pkt4Ptr &query4, std::string &scope_class) const
Checks if this server should process the DHCPv4 query.
bool amServingScope(const std::string &scope_name) const
Checks if this server instance is configured to process traffic belonging to a particular scope.
std::string makeScopeClass(const std::string &scope_name) const
Returns scope class name for the specified scope name.
boost::scoped_ptr< std::mutex > mutex_
Mutex to protect the internal state.
Definition: query_filter.h:339
void serveFailoverScopes()
Enable scopes required in failover case.
uint8_t loadBalanceHash(const uint8_t *key, const size_t key_len) const
Compute load balancing hash.
void validateScopeName(const std::string &scope_name) const
Checks if the scope name matches a name of any of the configured servers.
int active_servers_
Number of the active servers in the given HA mode.
Definition: query_filter.h:336
HAConfigPtr config_
Pointer to the HA configuration.
Definition: query_filter.h:326
void serveScopeOnly(const std::string &scope_name)
Enable scope and disable all other scopes.
int loadBalance(const dhcp::Pkt4Ptr &query4) const
Performs load balancing of the DHCPv4 queries.
void serveDefaultScopes()
Serve default scopes for the given HA mode.
void serveNoScopes()
Disables all scopes.
std::set< std::string > getServedScopes() const
Returns served scopes.
void serveScope(const std::string &scope_name)
Enable scope.
std::vector< HAConfig::PeerConfigPtr > peers_
Vector of HA peers configurations.
Definition: query_filter.h:329
std::map< std::string, bool > scopes_
Holds mapping of the scope names to the flag which indicates if the scopes are enabled or disabled.
Definition: query_filter.h:333
QueryFilter(const HAConfigPtr &config)
Constructor.
Definition: query_filter.cc:55
@ D6O_CLIENTID
Definition: dhcp6.h:21
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
@ DHO_DHCP_CLIENT_IDENTIFIER
Definition: dhcp4.h:130
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:544
boost::shared_ptr< HWAddr > HWAddrPtr
Shared pointer to a hardware address structure.
Definition: hwaddr.h:154
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition: pkt6.h:28
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
isc::log::Logger ha_logger("ha-hooks")
Definition: ha_log.h:17
boost::shared_ptr< HAConfig > HAConfigPtr
Pointer to the High Availability configuration structure.
Definition: ha_config.h:760
const isc::log::MessageID HA_LOAD_BALANCING_DUID_MISSING
Definition: ha_messages.h:73
const isc::log::MessageID HA_LOAD_BALANCING_IDENTIFIER_MISSING
Definition: ha_messages.h:74
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:69
Definition: edns.h:19
Defines the logger used by the top-level component of kea-lfc.