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