1 // Copyright (C) 2015-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
9 #include <eval/dependency.h>
10 #include <dhcpsrv/client_class_def.h>
11 #include <dhcpsrv/cfgmgr.h>
12 #include <dhcpsrv/parsers/client_class_def_parser.h>
13 #include <boost/foreach.hpp>
14
15 #include <queue>
16
17 using namespace isc::data;
18
19 namespace isc {
20 namespace dhcp {
21
22 //********** ClientClassDef ******************//
23
ClientClassDef(const std::string & name,const ExpressionPtr & match_expr,const CfgOptionPtr & cfg_option)24 ClientClassDef::ClientClassDef(const std::string& name,
25 const ExpressionPtr& match_expr,
26 const CfgOptionPtr& cfg_option)
27 : UserContext(), CfgToElement(), StampedElement(), name_(name),
28 match_expr_(match_expr), required_(false), depend_on_known_(false),
29 cfg_option_(cfg_option), next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
30 valid_(), preferred_() {
31
32 // Name can't be blank
33 if (name_.empty()) {
34 isc_throw(BadValue, "Client Class name cannot be blank");
35 }
36
37 // We permit an empty expression for now. This will likely be useful
38 // for automatic classes such as vendor class.
39 // For classes without options, make sure we have an empty collection
40 if (!cfg_option_) {
41 cfg_option_.reset(new CfgOption());
42 }
43 }
44
ClientClassDef(const ClientClassDef & rhs)45 ClientClassDef::ClientClassDef(const ClientClassDef& rhs)
46 : UserContext(rhs), CfgToElement(rhs), StampedElement(rhs), name_(rhs.name_),
47 match_expr_(ExpressionPtr()), test_(rhs.test_), required_(rhs.required_),
48 depend_on_known_(rhs.depend_on_known_), cfg_option_(new CfgOption()),
49 next_server_(rhs.next_server_), sname_(rhs.sname_),
50 filename_(rhs.filename_), valid_(rhs.valid_), preferred_(rhs.preferred_) {
51
52 if (rhs.match_expr_) {
53 match_expr_.reset(new Expression());
54 *match_expr_ = *(rhs.match_expr_);
55 }
56
57 if (rhs.cfg_option_def_) {
58 cfg_option_def_.reset(new CfgOptionDef());
59 rhs.cfg_option_def_->copyTo(*cfg_option_def_);
60 }
61
62 if (rhs.cfg_option_) {
63 rhs.cfg_option_->copyTo(*cfg_option_);
64 }
65 }
66
~ClientClassDef()67 ClientClassDef::~ClientClassDef() {
68 }
69
70 std::string
getName() const71 ClientClassDef::getName() const {
72 return (name_);
73 }
74
75 void
setName(const std::string & name)76 ClientClassDef::setName(const std::string& name) {
77 name_ = name;
78 }
79
80 const ExpressionPtr&
getMatchExpr() const81 ClientClassDef::getMatchExpr() const {
82 return (match_expr_);
83 }
84
85 void
setMatchExpr(const ExpressionPtr & match_expr)86 ClientClassDef::setMatchExpr(const ExpressionPtr& match_expr) {
87 match_expr_ = match_expr;
88 }
89
90 std::string
getTest() const91 ClientClassDef::getTest() const {
92 return (test_);
93 }
94
95 void
setTest(const std::string & test)96 ClientClassDef::setTest(const std::string& test) {
97 test_ = test;
98 }
99
100 bool
getRequired() const101 ClientClassDef::getRequired() const {
102 return (required_);
103 }
104
105 void
setRequired(bool required)106 ClientClassDef::setRequired(bool required) {
107 required_ = required;
108 }
109
110 bool
getDependOnKnown() const111 ClientClassDef::getDependOnKnown() const {
112 return (depend_on_known_);
113 }
114
115 void
setDependOnKnown(bool depend_on_known)116 ClientClassDef::setDependOnKnown(bool depend_on_known) {
117 depend_on_known_ = depend_on_known;
118 }
119
120 const CfgOptionDefPtr&
getCfgOptionDef() const121 ClientClassDef::getCfgOptionDef() const {
122 return (cfg_option_def_);
123 }
124
125 void
setCfgOptionDef(const CfgOptionDefPtr & cfg_option_def)126 ClientClassDef::setCfgOptionDef(const CfgOptionDefPtr& cfg_option_def) {
127 cfg_option_def_ = cfg_option_def;
128 }
129
130 const CfgOptionPtr&
getCfgOption() const131 ClientClassDef::getCfgOption() const {
132 return (cfg_option_);
133 }
134
135 void
setCfgOption(const CfgOptionPtr & cfg_option)136 ClientClassDef::setCfgOption(const CfgOptionPtr& cfg_option) {
137 cfg_option_ = cfg_option;
138 }
139
140 bool
dependOnClass(const std::string & name) const141 ClientClassDef::dependOnClass(const std::string& name) const {
142 return (isc::dhcp::dependOnClass(match_expr_, name));
143 }
144
145 bool
equals(const ClientClassDef & other) const146 ClientClassDef::equals(const ClientClassDef& other) const {
147 return ((name_ == other.name_) &&
148 ((!match_expr_ && !other.match_expr_) ||
149 (match_expr_ && other.match_expr_ &&
150 (*match_expr_ == *(other.match_expr_)))) &&
151 ((!cfg_option_ && !other.cfg_option_) ||
152 (cfg_option_ && other.cfg_option_ &&
153 (*cfg_option_ == *other.cfg_option_))) &&
154 ((!cfg_option_def_ && !other.cfg_option_def_) ||
155 (cfg_option_def_ && other.cfg_option_def_ &&
156 (*cfg_option_def_ == *other.cfg_option_def_))) &&
157 (required_ == other.required_) &&
158 (depend_on_known_ == other.depend_on_known_) &&
159 (next_server_ == other.next_server_) &&
160 (sname_ == other.sname_) &&
161 (filename_ == other.filename_));
162 }
163
164 ElementPtr
toElement() const165 ClientClassDef:: toElement() const {
166 uint16_t family = CfgMgr::instance().getFamily();
167 ElementPtr result = Element::createMap();
168 // Set user-context
169 contextToElement(result);
170 // Set name
171 result->set("name", Element::create(name_));
172 // Set original match expression (empty string won't parse)
173 if (!test_.empty()) {
174 result->set("test", Element::create(test_));
175 }
176 // Set only-if-required
177 if (required_) {
178 result->set("only-if-required", Element::create(required_));
179 }
180 // Set option-def (used only by DHCPv4)
181 if (cfg_option_def_ && (family == AF_INET)) {
182 result->set("option-def", cfg_option_def_->toElement());
183 }
184 // Set option-data
185 result->set("option-data", cfg_option_->toElement());
186
187 if (family == AF_INET) {
188 // V4 only
189 // Set next-server
190 result->set("next-server", Element::create(next_server_.toText()));
191 // Set server-hostname
192 result->set("server-hostname", Element::create(sname_));
193 // Set boot-file-name
194 result->set("boot-file-name", Element::create(filename_));
195 } else {
196 // V6 only
197 // Set preferred-lifetime
198 if (!preferred_.unspecified()) {
199 result->set("preferred-lifetime",
200 Element::create(static_cast<long long>(preferred_.get())));
201 }
202
203 if (preferred_.getMin() < preferred_.get()) {
204 result->set("min-preferred-lifetime",
205 Element::create(static_cast<long long>(preferred_.getMin())));
206 }
207
208 if (preferred_.getMax() > preferred_.get()) {
209 result->set("max-preferred-lifetime",
210 Element::create(static_cast<long long>(preferred_.getMax())));
211 }
212 }
213
214 // Set valid-lifetime
215 if (!valid_.unspecified()) {
216 result->set("valid-lifetime",
217 Element::create(static_cast<long long>(valid_.get())));
218
219 if (valid_.getMin() < valid_.get()) {
220 result->set("min-valid-lifetime",
221 Element::create(static_cast<long long>(valid_.getMin())));
222 }
223
224 if (valid_.getMax() > valid_.get()) {
225 result->set("max-valid-lifetime",
226 Element::create(static_cast<long long>(valid_.getMax())));
227 }
228 }
229
230 return (result);
231 }
232
operator <<(std::ostream & os,const ClientClassDef & x)233 std::ostream& operator<<(std::ostream& os, const ClientClassDef& x) {
234 os << "ClientClassDef:" << x.getName();
235 return (os);
236 }
237
238 //********** ClientClassDictionary ******************//
239
ClientClassDictionary()240 ClientClassDictionary::ClientClassDictionary()
241 : map_(new ClientClassDefMap()), list_(new ClientClassDefList()) {
242 }
243
ClientClassDictionary(const ClientClassDictionary & rhs)244 ClientClassDictionary::ClientClassDictionary(const ClientClassDictionary& rhs)
245 : map_(new ClientClassDefMap()), list_(new ClientClassDefList()) {
246 BOOST_FOREACH(ClientClassDefPtr cclass, *(rhs.list_)) {
247 ClientClassDefPtr copy(new ClientClassDef(*cclass));
248 addClass(copy);
249 }
250 }
251
~ClientClassDictionary()252 ClientClassDictionary::~ClientClassDictionary() {
253 }
254
255 void
addClass(const std::string & name,const ExpressionPtr & match_expr,const std::string & test,bool required,bool depend_on_known,const CfgOptionPtr & cfg_option,CfgOptionDefPtr cfg_option_def,ConstElementPtr user_context,asiolink::IOAddress next_server,const std::string & sname,const std::string & filename,const Triplet<uint32_t> & valid,const Triplet<uint32_t> & preferred)256 ClientClassDictionary::addClass(const std::string& name,
257 const ExpressionPtr& match_expr,
258 const std::string& test,
259 bool required,
260 bool depend_on_known,
261 const CfgOptionPtr& cfg_option,
262 CfgOptionDefPtr cfg_option_def,
263 ConstElementPtr user_context,
264 asiolink::IOAddress next_server,
265 const std::string& sname,
266 const std::string& filename,
267 const Triplet<uint32_t>& valid,
268 const Triplet<uint32_t>& preferred) {
269 ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option));
270 cclass->setTest(test);
271 cclass->setRequired(required);
272 cclass->setDependOnKnown(depend_on_known);
273 cclass->setCfgOptionDef(cfg_option_def);
274 cclass->setContext(user_context),
275 cclass->setNextServer(next_server);
276 cclass->setSname(sname);
277 cclass->setFilename(filename);
278 cclass->setValid(valid);
279 cclass->setPreferred(preferred);
280 addClass(cclass);
281 }
282
283 void
addClass(ClientClassDefPtr & class_def)284 ClientClassDictionary::addClass(ClientClassDefPtr& class_def) {
285 if (!class_def) {
286 isc_throw(BadValue, "ClientClassDictionary::addClass "
287 " - class definition cannot be null");
288 }
289
290 if (findClass(class_def->getName())) {
291 isc_throw(DuplicateClientClassDef, "Client Class: "
292 << class_def->getName() << " has already been defined");
293 }
294
295 list_->push_back(class_def);
296 (*map_)[class_def->getName()] = class_def;
297 }
298
299 ClientClassDefPtr
findClass(const std::string & name) const300 ClientClassDictionary::findClass(const std::string& name) const {
301 ClientClassDefMap::iterator it = map_->find(name);
302 if (it != map_->end()) {
303 return (*it).second;
304 }
305
306 return (ClientClassDefPtr());
307 }
308
309 void
removeClass(const std::string & name)310 ClientClassDictionary::removeClass(const std::string& name) {
311 for (ClientClassDefList::iterator this_class = list_->begin();
312 this_class != list_->end(); ++this_class) {
313 if ((*this_class)->getName() == name) {
314 list_->erase(this_class);
315 break;
316 }
317 }
318 map_->erase(name);
319 }
320
321 void
removeClass(const uint64_t id)322 ClientClassDictionary::removeClass(const uint64_t id) {
323 // Class id equal to 0 means it wasn't set.
324 if (id == 0) {
325 return;
326 }
327 for (ClientClassDefList::iterator this_class = list_->begin();
328 this_class != list_->end(); ++this_class) {
329 if ((*this_class)->getId() == id) {
330 map_->erase((*this_class)->getName());
331 list_->erase(this_class);
332 break;
333 }
334 }
335 }
336
337 const ClientClassDefListPtr&
getClasses() const338 ClientClassDictionary::getClasses() const {
339 return (list_);
340 }
341
342 bool
empty() const343 ClientClassDictionary::empty() const {
344 return (list_->empty());
345 }
346
347 bool
dependOnClass(const std::string & name,std::string & dependent_class) const348 ClientClassDictionary::dependOnClass(const std::string& name,
349 std::string& dependent_class) const {
350 // Skip previous classes as they should not depend on name.
351 bool found = false;
352 for (ClientClassDefList::iterator this_class = list_->begin();
353 this_class != list_->end(); ++this_class) {
354 if (found) {
355 if ((*this_class)->dependOnClass(name)) {
356 dependent_class = (*this_class)->getName();
357 return (true);
358 }
359 } else {
360 if ((*this_class)->getName() == name) {
361 found = true;
362 }
363 }
364 }
365 return (false);
366 }
367
368 bool
equals(const ClientClassDictionary & other) const369 ClientClassDictionary::equals(const ClientClassDictionary& other) const {
370 if (list_->size() != other.list_->size()) {
371 return (false);
372 }
373
374 ClientClassDefList::const_iterator this_class = list_->cbegin();
375 ClientClassDefList::const_iterator other_class = other.list_->cbegin();
376 while (this_class != list_->cend() &&
377 other_class != other.list_->cend()) {
378 if (!*this_class || !*other_class ||
379 **this_class != **other_class) {
380 return false;
381 }
382
383 ++this_class;
384 ++other_class;
385 }
386
387 return (true);
388 }
389
390 void
initMatchExpr(uint16_t family)391 ClientClassDictionary::initMatchExpr(uint16_t family) {
392 std::queue<ExpressionPtr> expressions;
393 for (auto c : *list_) {
394 ExpressionPtr match_expr = boost::make_shared<Expression>();
395 if (!c->getTest().empty()) {
396 ExpressionParser parser;
397 parser.parse(match_expr, Element::create(c->getTest()), family);
398 }
399 expressions.push(match_expr);
400 }
401 // All expressions successfully initialized. Let's set them for the
402 // client classes in the dictionary.
403 for (auto c : *list_) {
404 c->setMatchExpr(expressions.front());
405 expressions.pop();
406 }
407 }
408
409 ElementPtr
toElement() const410 ClientClassDictionary::toElement() const {
411 ElementPtr result = Element::createList();
412 // Iterate on the map
413 for (ClientClassDefList::const_iterator this_class = list_->begin();
414 this_class != list_->cend(); ++this_class) {
415 result->add((*this_class)->toElement());
416 }
417 return (result);
418 }
419
420 ClientClassDictionary&
operator =(const ClientClassDictionary & rhs)421 ClientClassDictionary::operator=(const ClientClassDictionary& rhs) {
422 if (this != &rhs) {
423 list_->clear();
424 map_->clear();
425 for (auto cclass : *(rhs.list_)) {
426 ClientClassDefPtr copy(new ClientClassDef(*cclass));
427 addClass(copy);
428 }
429 }
430 return (*this);
431 }
432
433 std::list<std::string>
434 builtinNames = {
435 // DROP is not in this list because it is special but not built-in.
436 // In fact DROP is set from an expression as callouts can drop
437 // directly the incoming packet. The expression must not depend on
438 // KNOWN/UNKNOWN which are set far after the drop point.
439 "ALL", "KNOWN", "UNKNOWN", "BOOTP"
440 };
441
442 std::list<std::string>
443 builtinPrefixes = {
444 "VENDOR_CLASS_", "HA_", "AFTER_", "EXTERNAL_"
445 };
446
447 bool
isClientClassBuiltIn(const ClientClass & client_class)448 isClientClassBuiltIn(const ClientClass& client_class) {
449 for (std::list<std::string>::const_iterator bn = builtinNames.cbegin();
450 bn != builtinNames.cend(); ++bn) {
451 if (client_class == *bn) {
452 return true;
453 }
454 }
455
456 for (std::list<std::string>::const_iterator bt = builtinPrefixes.cbegin();
457 bt != builtinPrefixes.cend(); ++bt) {
458 if (client_class.size() <= bt->size()) {
459 continue;
460 }
461 auto mis = std::mismatch(bt->cbegin(), bt->cend(), client_class.cbegin());
462 if (mis.first == bt->cend()) {
463 return true;
464 }
465 }
466
467 return false;
468 }
469
470 bool
isClientClassDefined(ClientClassDictionaryPtr & class_dictionary,bool & depend_on_known,const ClientClass & client_class)471 isClientClassDefined(ClientClassDictionaryPtr& class_dictionary,
472 bool& depend_on_known,
473 const ClientClass& client_class) {
474 // First check built-in classes
475 if (isClientClassBuiltIn(client_class)) {
476 // Check direct dependency on [UN]KNOWN
477 if ((client_class == "KNOWN") || (client_class == "UNKNOWN")) {
478 depend_on_known = true;
479 }
480 return (true);
481 }
482
483 // Second check already defined, i.e. in the dictionary
484 ClientClassDefPtr def = class_dictionary->findClass(client_class);
485 if (def) {
486 // Check indirect dependency on [UN]KNOWN
487 if (def->getDependOnKnown()) {
488 depend_on_known = true;
489 }
490 return (true);
491 }
492
493 // Not defined...
494 return (false);
495 }
496
497 } // namespace isc::dhcp
498 } // namespace isc
499