From: Stephen Morris Date: Thu, 13 Dec 2012 11:38:21 +0000 (+0000) Subject: [2546] Merge branch 'master' into trac2546 X-Git-Tag: bind10-1.0.0-beta-release~29 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=fecfe82bdfc93fedc24263a8f1bbdcedf59a7b71;p=thirdparty%2Fkea.git [2546] Merge branch 'master' into trac2546 Conflicts: doc/devel/mainpage.dox src/bin/dhcp4/ctrl_dhcp4_srv.h src/bin/dhcp4/dhcp4_srv.h src/bin/dhcp6/dhcp6.dox Also modified the following files during the resolution of the merge issues: src/bin/dhcp4/config_parser.cc src/bin/dhcp4/ctrl_dhcp4_srv.cc src/bin/dhcp4/dhcp4.dox src/bin/dhcp6/config_parser.cc src/bin/dhcp6/tests/dhcp6_srv_unittest.cc src/lib/dhcp/option_custom.h src/lib/dhcp/pkt4.h src/lib/dhcpsrv/database_backends.dox --- fecfe82bdfc93fedc24263a8f1bbdcedf59a7b71 diff --cc doc/devel/mainpage.dox index c2c69ca738,407723e3d9..295fd03986 --- a/doc/devel/mainpage.dox +++ b/doc/devel/mainpage.dox @@@ -20,12 -20,14 +20,14 @@@ * - @subpage DataScrubbing * * @section DHCP - * - @subpage dhcpv4 + * - @subpage dhcp4 - * - @subpage dhcp4-session - * - @subpage dhcp4-config-parser - * - @subpage dhcp4-config-inherit + * - @subpage dhcpv4Session - * - @subpage dhcpv6 ++ * - @subpage dhcpv4ConfigParser ++ * - @subpage dhcpv4ConfigInherit + * - @subpage dhcp6 - * - @subpage dhcp6-session - * - @subpage dhcp6-config-parser - * - @subpage dhcp6-config-inherit + * - @subpage dhcpv6Session + * - @subpage dhcpv6ConfigParser + * - @subpage dhcpv6ConfigInherit * - @subpage libdhcp * - @subpage libdhcpIntro * - @subpage libdhcpIfaceMgr @@@ -33,7 -35,7 +35,7 @@@ * - @subpage leasemgr * - @subpage cfgmgr * - @subpage allocengine -- * - @subpage dhcp-database-backends ++ * - @subpage dhcpDatabaseBackends * - @subpage perfdhcpInternals * * @section misc Miscellaneous topics diff --cc src/bin/dhcp4/config_parser.cc index 0000000000,aa2fa4f3d8..08d16f8bcb mode 000000,100644..100644 --- a/src/bin/dhcp4/config_parser.cc +++ b/src/bin/dhcp4/config_parser.cc @@@ -1,0 -1,772 +1,772 @@@ + // Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") + // + // Permission to use, copy, modify, and/or distribute this software for any + // purpose with or without fee is hereby granted, provided that the above + // copyright notice and this permission notice appear in all copies. + // + // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + // PERFORMANCE OF THIS SOFTWARE. + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + using namespace std; + using namespace isc::data; + using namespace isc::asiolink; + + namespace isc { + namespace dhcp { + + /// @brief auxiliary type used for storing element name and its parser + typedef pair ConfigPair; + + /// @brief a factory method that will create a parser for a given element name + typedef Dhcp4ConfigParser* ParserFactory(const std::string& config_id); + + /// @brief a collection of factories that creates parsers for specified element names + typedef std::map FactoryMap; + + /// @brief a collection of pools + /// + /// That type is used as intermediate storage, when pools are parsed, but there is + /// no subnet object created yet to store them. + typedef std::vector PoolStorage; + + /// @brief Global uint32 parameters that will be used as defaults. + Uint32Storage uint32_defaults; + + /// @brief global string parameters that will be used as defaults. + StringStorage string_defaults; + + /// @brief a dummy configuration parser + /// + /// It is a debugging parser. It does not configure anything, + /// will accept any configuration and will just print it out + /// on commit. Useful for debugging existing configurations and + /// adding new ones. + class DebugParser : public Dhcp4ConfigParser { + public: + + /// @brief Constructor + /// + /// See \ref Dhcp4ConfigParser class for details. + /// + /// @param param_name name of the parsed parameter + DebugParser(const std::string& param_name) + :param_name_(param_name) { + } + + /// @brief builds parameter value + /// + /// See \ref Dhcp4ConfigParser class for details. + /// + /// @param new_config pointer to the new configuration + virtual void build(ConstElementPtr new_config) { + std::cout << "Build for token: [" << param_name_ << "] = [" + << value_->str() << "]" << std::endl; + value_ = new_config; + } + + /// @brief pretends to apply the configuration + /// + /// This is a method required by base class. It pretends to apply the + /// configuration, but in fact it only prints the parameter out. + /// + /// See \ref Dhcp4ConfigParser class for details. + virtual void commit() { + // Debug message. The whole DebugParser class is used only for parser + // debugging, and is not used in production code. It is very convenient + // to keep it around. Please do not turn this cout into logger calls. + std::cout << "Commit for token: [" << param_name_ << "] = [" + << value_->str() << "]" << std::endl; + } + + /// @brief factory that constructs DebugParser objects + /// + /// @param param_name name of the parameter to be parsed + static Dhcp4ConfigParser* Factory(const std::string& param_name) { + return (new DebugParser(param_name)); + } + + private: + /// name of the parsed parameter + std::string param_name_; + + /// pointer to the actual value of the parameter + ConstElementPtr value_; + }; + + /// @brief Configuration parser for uint32 parameters + /// + /// This class is a generic parser that is able to handle any uint32 integer + /// type. By default it stores the value in external global container + /// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters + /// in subnet config), it can be pointed to a different storage, using + /// setStorage() method. This class follows the parser interface, laid out + /// in its base class, \ref Dhcp4ConfigParser. + /// + /// For overview of usability of this generic purpose parser, see -/// \ref dhcp4-config-inherit page. ++/// \ref dhcpv4ConfigInherit page. + class Uint32Parser : public Dhcp4ConfigParser { + public: + + /// @brief constructor for Uint32Parser + /// @param param_name name of the configuration parameter being parsed + Uint32Parser(const std::string& param_name) + :storage_(&uint32_defaults), param_name_(param_name) { + } + + /// @brief builds parameter value + /// + /// Parses configuration entry and stores it in a storage. See + /// \ref setStorage() for details. + /// + /// @param value pointer to the content of parsed values + /// @throw BadValue if supplied value could not be base to uint32_t + virtual void build(ConstElementPtr value) { + int64_t check; + string x = value->str(); + try { + check = boost::lexical_cast(x); + } catch (const boost::bad_lexical_cast &) { + isc_throw(BadValue, "Failed to parse value " << value->str() + << " as unsigned 32-bit integer."); + } + if (check > std::numeric_limits::max()) { + isc_throw(BadValue, "Value " << value->str() << "is too large" + << " for unsigned 32-bit integer."); + } + if (check < 0) { + isc_throw(BadValue, "Value " << value->str() << "is negative." + << " Only 0 or larger are allowed for unsigned 32-bit integer."); + } + + // value is small enough to fit + value_ = static_cast(check); + + (*storage_)[param_name_] = value_; + } + + /// @brief does nothing + /// + /// This method is required for all parsers. The value itself + /// is not commited anywhere. Higher level parsers are expected to + /// use values stored in the storage, e.g. renew-timer for a given + /// subnet is stored in subnet-specific storage. It is not commited + /// here, but is rather used by \ref Subnet4ConfigParser when constructing + /// the subnet. + virtual void commit() { + } + + /// @brief factory that constructs Uint32Parser objects + /// + /// @param param_name name of the parameter to be parsed + static Dhcp4ConfigParser* Factory(const std::string& param_name) { + return (new Uint32Parser(param_name)); + } + + /// @brief sets storage for value of this parameter + /// - /// See \ref dhcp4-config-inherit for details. ++ /// See \ref dhcpv4ConfigInherit for details. + /// + /// @param storage pointer to the storage container + void setStorage(Uint32Storage* storage) { + storage_ = storage; + } + + private: + /// pointer to the storage, where parsed value will be stored + Uint32Storage* storage_; + + /// name of the parameter to be parsed + std::string param_name_; + + /// the actual parsed value + uint32_t value_; + }; + + /// @brief Configuration parser for string parameters + /// + /// This class is a generic parser that is able to handle any string + /// parameter. By default it stores the value in external global container + /// (string_defaults). If used in smaller scopes (e.g. to parse parameters + /// in subnet config), it can be pointed to a different storage, using + /// setStorage() method. This class follows the parser interface, laid out + /// in its base class, \ref Dhcp4ConfigParser. + /// + /// For overview of usability of this generic purpose parser, see -/// \ref dhcp4-config-inherit page. ++/// \ref dhcpv4ConfigInherit page. + class StringParser : public Dhcp4ConfigParser { + public: + + /// @brief constructor for StringParser + /// @param param_name name of the configuration parameter being parsed + StringParser(const std::string& param_name) + :storage_(&string_defaults), param_name_(param_name) { + } + + /// @brief parses parameter value + /// + /// Parses configuration entry and stores it in storage. See + /// \ref setStorage() for details. + /// + /// @param value pointer to the content of parsed values + virtual void build(ConstElementPtr value) { + value_ = value->str(); + boost::erase_all(value_, "\""); + + (*storage_)[param_name_] = value_; + } + + /// @brief does nothing + /// + /// This method is required for all parser. The value itself + /// is not commited anywhere. Higher level parsers are expected to + /// use values stored in the storage, e.g. renew-timer for a given + /// subnet is stored in subnet-specific storage. It is not commited + /// here, but is rather used by its parent parser when constructing + /// an object, e.g. the subnet. + virtual void commit() { + } + + /// @brief factory that constructs StringParser objects + /// + /// @param param_name name of the parameter to be parsed + static Dhcp4ConfigParser* Factory(const std::string& param_name) { + return (new StringParser(param_name)); + } + + /// @brief sets storage for value of this parameter + /// - /// See \ref dhcp4-config-inherit for details. ++ /// See \ref dhcpv4ConfigInherit for details. + /// + /// @param storage pointer to the storage container + void setStorage(StringStorage* storage) { + storage_ = storage; + } + + private: + /// pointer to the storage, where parsed value will be stored + StringStorage* storage_; + + /// name of the parameter to be parsed + std::string param_name_; + + /// the actual parsed value + std::string value_; + }; + + + /// @brief parser for interface list definition + /// + /// This parser handles Dhcp4/interface entry. + /// It contains a list of network interfaces that the server listens on. + /// In particular, it can contain an entry called "all" or "any" that + /// designates all interfaces. + /// + /// It is useful for parsing Dhcp4/interface parameter. + class InterfaceListConfigParser : public Dhcp4ConfigParser { + public: + + /// @brief constructor + /// + /// As this is a dedicated parser, it must be used to parse + /// "interface" parameter only. All other types will throw exception. + /// + /// @param param_name name of the configuration parameter being parsed + /// @throw BadValue if supplied parameter name is not "interface" + InterfaceListConfigParser(const std::string& param_name) { + if (param_name != "interface") { + isc_throw(BadValue, "Internal error. Interface configuration " + "parser called for the wrong parameter: " << param_name); + } + } + + /// @brief parses parameters value + /// + /// Parses configuration entry (list of parameters) and adds each element + /// to the interfaces list. + /// + /// @param value pointer to the content of parsed values + virtual void build(ConstElementPtr value) { + BOOST_FOREACH(ConstElementPtr iface, value->listValue()) { + interfaces_.push_back(iface->str()); + } + } + + /// @brief commits interfaces list configuration + virtual void commit() { + /// @todo: Implement per interface listening. Currently always listening + /// on all interfaces. + } + + /// @brief factory that constructs InterfaceListConfigParser objects + /// + /// @param param_name name of the parameter to be parsed + static Dhcp4ConfigParser* Factory(const std::string& param_name) { + return (new InterfaceListConfigParser(param_name)); + } + + private: + /// contains list of network interfaces + vector interfaces_; + }; + + /// @brief parser for pool definition + /// + /// This parser handles pool definitions, i.e. a list of entries of one + /// of two syntaxes: min-max and prefix/len. Pool4 objects are created + /// and stored in chosen PoolStorage container. + /// + /// As there are no default values for pool, setStorage() must be called + /// before build(). Otherwise exception will be thrown. + /// + /// It is useful for parsing Dhcp4/subnet4[X]/pool parameters. + class PoolParser : public Dhcp4ConfigParser { + public: + + /// @brief constructor. + PoolParser(const std::string& /*param_name*/) + :pools_(NULL) { + // ignore parameter name, it is always Dhcp4/subnet4[X]/pool + } + + /// @brief parses the actual list + /// + /// This method parses the actual list of interfaces. + /// No validation is done at this stage, everything is interpreted as + /// interface name. + /// @param pools_list list of pools defined for a subnet + /// @throw InvalidOperation if storage was not specified (setStorage() not called) + /// @throw Dhcp4ConfigError when pool parsing fails + void build(ConstElementPtr pools_list) { + // setStorage() should have been called before build + if (!pools_) { + isc_throw(InvalidOperation, "Parser logic error. No pool storage set," + " but pool parser asked to parse pools"); + } + + BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) { + + // That should be a single pool representation. It should contain + // text is form prefix/len or first - last. Note that spaces + // are allowed + string txt = text_pool->stringValue(); + + // first let's remove any whitespaces + boost::erase_all(txt, " "); // space + boost::erase_all(txt, "\t"); // tabulation + + // Is this prefix/len notation? + size_t pos = txt.find("/"); + if (pos != string::npos) { + IOAddress addr("::"); + uint8_t len = 0; + try { + addr = IOAddress(txt.substr(0, pos)); + + // start with the first character after / + string prefix_len = txt.substr(pos + 1); + + // It is lexical cast to int and then downcast to uint8_t. + // Direct cast to uint8_t (which is really an unsigned char) + // will result in interpreting the first digit as output + // value and throwing exception if length is written on two + // digits (because there are extra characters left over). + + // No checks for values over 128. Range correctness will + // be checked in Pool4 constructor. + len = boost::lexical_cast(prefix_len); + } catch (...) { + isc_throw(Dhcp4ConfigError, "Failed to parse pool " + "definition: " << text_pool->stringValue()); + } + + Pool4Ptr pool(new Pool4(addr, len)); + pools_->push_back(pool); + continue; + } + + // Is this min-max notation? + pos = txt.find("-"); + if (pos != string::npos) { + // using min-max notation + IOAddress min(txt.substr(0,pos)); + IOAddress max(txt.substr(pos + 1)); + + Pool4Ptr pool(new Pool4(min, max)); + + pools_->push_back(pool); + continue; + } + + isc_throw(Dhcp4ConfigError, "Failed to parse pool definition:" + << text_pool->stringValue() << + ". Does not contain - (for min-max) nor / (prefix/len)"); + } + } + + /// @brief sets storage for value of this parameter + /// - /// See \ref dhcp4-config-inherit for details. ++ /// See \ref dhcpv4ConfigInherit for details. + /// + /// @param storage pointer to the storage container + void setStorage(PoolStorage* storage) { + pools_ = storage; + } + + /// @brief does nothing. + /// + /// This method is required for all parsers. The value itself + /// is not commited anywhere. Higher level parsers (for subnet) are expected + /// to use values stored in the storage. + virtual void commit() {} + + /// @brief factory that constructs PoolParser objects + /// + /// @param param_name name of the parameter to be parsed + static Dhcp4ConfigParser* Factory(const std::string& param_name) { + return (new PoolParser(param_name)); + } + + private: + /// @brief pointer to the actual Pools storage + /// + /// That is typically a storage somewhere in Subnet parser + /// (an upper level parser). + PoolStorage* pools_; + }; + + /// @brief this class parses a single subnet + /// + /// This class parses the whole subnet definition. It creates parsers + /// for received configuration parameters as needed. + class Subnet4ConfigParser : public Dhcp4ConfigParser { + public: + + /// @brief constructor + Subnet4ConfigParser(const std::string& ) { + // The parameter should always be "subnet", but we don't check here + // against it in case someone wants to reuse this parser somewhere. + } + + /// @brief parses parameter value + /// + /// @param subnet pointer to the content of subnet definition + void build(ConstElementPtr subnet) { + + BOOST_FOREACH(ConfigPair param, subnet->mapValue()) { + + ParserPtr parser(createSubnet4ConfigParser(param.first)); + + // if this is an Uint32 parser, tell it to store the values + // in values_, rather than in global storage + boost::shared_ptr uint_parser = + boost::dynamic_pointer_cast(parser); + if (uint_parser) { + uint_parser->setStorage(&uint32_values_); + } else { + + boost::shared_ptr string_parser = + boost::dynamic_pointer_cast(parser); + if (string_parser) { + string_parser->setStorage(&string_values_); + } else { + + boost::shared_ptr pool_parser = + boost::dynamic_pointer_cast(parser); + if (pool_parser) { + pool_parser->setStorage(&pools_); + } + } + } + + parser->build(param.second); + parsers_.push_back(parser); + } + + // Ok, we now have subnet parsed + } + + /// @brief commits received configuration. + /// + /// This method does most of the configuration. Many other parsers are just + /// storing the values that are actually consumed here. Pool definitions + /// created in other parsers are used here and added to newly created Subnet4 + /// objects. Subnet4 are then added to DHCP CfgMgr. + /// @throw Dhcp4ConfigError if there are any issues encountered during commit + void commit() { + + StringStorage::const_iterator it = string_values_.find("subnet"); + if (it == string_values_.end()) { + isc_throw(Dhcp4ConfigError, + "Mandatory subnet definition in subnet missing"); + } + string subnet_txt = it->second; + boost::erase_all(subnet_txt, " "); + boost::erase_all(subnet_txt, "\t"); + + size_t pos = subnet_txt.find("/"); + if (pos == string::npos) { + isc_throw(Dhcp4ConfigError, + "Invalid subnet syntax (prefix/len expected):" << it->second); + } + IOAddress addr(subnet_txt.substr(0, pos)); + uint8_t len = boost::lexical_cast(subnet_txt.substr(pos + 1)); + + Triplet t1 = getParam("renew-timer"); + Triplet t2 = getParam("rebind-timer"); + Triplet valid = getParam("valid-lifetime"); + + /// @todo: Convert this to logger once the parser is working reliably + stringstream tmp; + tmp << addr.toText() << "/" << (int)len + << " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid; + + LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str()); + + Subnet4Ptr subnet(new Subnet4(addr, len, t1, t2, valid)); + + for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) { + subnet->addPool4(*it); + } + + CfgMgr::instance().addSubnet4(subnet); + } + + private: + + /// @brief creates parsers for entries in subnet definition + /// + /// @todo Add subnet-specific things here (e.g. subnet-specific options) + /// + /// @param config_id name od the entry + /// @return parser object for specified entry name + /// @throw NotImplemented if trying to create a parser for unknown config element + Dhcp4ConfigParser* createSubnet4ConfigParser(const std::string& config_id) { + FactoryMap factories; + + factories["valid-lifetime"] = Uint32Parser::Factory; + factories["renew-timer"] = Uint32Parser::Factory; + factories["rebind-timer"] = Uint32Parser::Factory; + factories["subnet"] = StringParser::Factory; + factories["pool"] = PoolParser::Factory; + + FactoryMap::iterator f = factories.find(config_id); + if (f == factories.end()) { + // Used for debugging only. + // return new DebugParser(config_id); + + isc_throw(NotImplemented, + "Parser error: Subnet4 parameter not supported: " + << config_id); + } + return (f->second(config_id)); + } + + /// @brief returns value for a given parameter (after using inheritance) + /// + /// This method implements inheritance. For a given parameter name, it first + /// checks if there is a global value for it and overwrites it with specific + /// value if such value was defined in subnet. + /// + /// @param name name of the parameter + /// @return triplet with the parameter name + /// @throw Dhcp4ConfigError when requested parameter is not present + Triplet getParam(const std::string& name) { + uint32_t value = 0; + bool found = false; + Uint32Storage::iterator global = uint32_defaults.find(name); + if (global != uint32_defaults.end()) { + value = global->second; + found = true; + } + + Uint32Storage::iterator local = uint32_values_.find(name); + if (local != uint32_values_.end()) { + value = local->second; + found = true; + } + + if (found) { + return (Triplet(value)); + } else { + isc_throw(Dhcp4ConfigError, "Mandatory parameter " << name + << " missing (no global default and no subnet-" + << "specific value)"); + } + } + + /// storage for subnet-specific uint32 values + Uint32Storage uint32_values_; + + /// storage for subnet-specific integer values + StringStorage string_values_; + + /// storage for pools belonging to this subnet + PoolStorage pools_; + + /// parsers are stored here + ParserCollection parsers_; + }; + + /// @brief this class parses list of subnets + /// + /// This is a wrapper parser that handles the whole list of Subnet4 + /// definitions. It iterates over all entries and creates Subnet4ConfigParser + /// for each entry. + class Subnets4ListConfigParser : public Dhcp4ConfigParser { + public: + + /// @brief constructor + /// + Subnets4ListConfigParser(const std::string&) { + /// parameter name is ignored + } + + /// @brief parses contents of the list + /// + /// Iterates over all entries on the list and creates Subnet4ConfigParser + /// for each entry. + /// + /// @param subnets_list pointer to a list of IPv4 subnets + void build(ConstElementPtr subnets_list) { + + // No need to define FactoryMap here. There's only one type + // used: Subnet4ConfigParser + + BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) { + + ParserPtr parser(new Subnet4ConfigParser("subnet")); + parser->build(subnet); + subnets_.push_back(parser); + } + + } + + /// @brief commits subnets definitions. + /// + /// Iterates over all Subnet4 parsers. Each parser contains definitions + /// of a single subnet and its parameters and commits each subnet separately. + void commit() { + // @todo: Implement more subtle reconfiguration than toss + // the old one and replace with the new one. + + // remove old subnets + CfgMgr::instance().deleteSubnets4(); + + BOOST_FOREACH(ParserPtr subnet, subnets_) { + subnet->commit(); + } + + } + + /// @brief Returns Subnet4ListConfigParser object + /// @param param_name name of the parameter + /// @return Subnets4ListConfigParser object + static Dhcp4ConfigParser* Factory(const std::string& param_name) { + return (new Subnets4ListConfigParser(param_name)); + } + + /// @brief collection of subnet parsers. + ParserCollection subnets_; + }; + + /// @brief creates global parsers + /// + /// This method creates global parsers that parse global parameters, i.e. + /// those that take format of Dhcp4/param1, Dhcp4/param2 and so forth. + /// + /// @param config_id pointer to received global configuration entry + /// @return parser for specified global DHCPv4 parameter + /// @throw NotImplemented if trying to create a parser for unknown config element + Dhcp4ConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) { + FactoryMap factories; + + factories["valid-lifetime"] = Uint32Parser::Factory; + factories["renew-timer"] = Uint32Parser::Factory; + factories["rebind-timer"] = Uint32Parser::Factory; + factories["interface"] = InterfaceListConfigParser::Factory; + factories["subnet4"] = Subnets4ListConfigParser::Factory; + factories["version"] = StringParser::Factory; + + FactoryMap::iterator f = factories.find(config_id); + if (f == factories.end()) { + // Used for debugging only. + // return new DebugParser(config_id); + + isc_throw(NotImplemented, + "Parser error: Global configuration parameter not supported: " + << config_id); + } + return (f->second(config_id)); + } + + isc::data::ConstElementPtr + configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) { + if (!config_set) { + ConstElementPtr answer = isc::config::createAnswer(1, + string("Can't parse NULL config")); + return (answer); + } + + /// @todo: append most essential info here (like "2 new subnets configured") + string config_details; + + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str()); + + ParserCollection parsers; + try { + BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) { + + ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first)); + parser->build(config_pair.second); + parsers.push_back(parser); + } + } catch (const isc::Exception& ex) { + ConstElementPtr answer = isc::config::createAnswer(1, + string("Configuration parsing failed:") + ex.what()); + return (answer); + } catch (...) { + // for things like bad_cast in boost::lexical_cast + ConstElementPtr answer = isc::config::createAnswer(1, + string("Configuration parsing failed")); + } + + try { + BOOST_FOREACH(ParserPtr parser, parsers) { + parser->commit(); + } + } + catch (const isc::Exception& ex) { + ConstElementPtr answer = isc::config::createAnswer(2, + string("Configuration commit failed:") + ex.what()); + return (answer); + } catch (...) { + // for things like bad_cast in boost::lexical_cast + ConstElementPtr answer = isc::config::createAnswer(2, + string("Configuration commit failed")); + } + + LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details); + + ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited."); + return (answer); + } + + }; // end of isc::dhcp namespace + }; // end of isc namespace diff --cc src/bin/dhcp4/ctrl_dhcp4_srv.cc index b02bf72d1e,20eedebc05..eb5d482153 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@@ -104,9 -113,21 +113,21 @@@ void ControlledDhcpv4Srv::establishSess dhcp4CommandHandler, false); config_session_->start(); + // We initially create ModuleCCSession() without configHandler, as + // the session module is too eager to send partial configuration. + // We want to get the full configuration, so we explicitly call + // getFullConfig() and then pass it to our configHandler. + config_session_->setConfigHandler(dhcp4ConfigHandler); + + try { + configureDhcp4Server(*this, config_session_->getFullConfig()); + } catch (const Dhcp4ConfigError& ex) { + LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what()); + } + /// Integrate the asynchronous I/O model of BIND 10 configuration /// control with the "select" model of the DHCP server. This is - /// fully explained in \ref dhcp4-session. + /// fully explained in \ref dhcpv4Session. int ctrl_socket = cc_session_->getSocketDesc(); LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED) .arg(ctrl_socket); diff --cc src/bin/dhcp4/ctrl_dhcp4_srv.h index 5f450772dd,c1a26cc557..9bd261ca8e --- a/src/bin/dhcp4/ctrl_dhcp4_srv.h +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h @@@ -66,8 -66,8 +66,10 @@@ public /// @brief Session callback, processes received commands. /// + /// @param command Text represenation of the command (e.g. "shutdown") + /// @param args Optional parameters + /// @param command text represenation of the command (e.g. "shutdown") + /// @param args optional parameters /// /// @return status of the command static isc::data::ConstElementPtr diff --cc src/bin/dhcp4/dhcp4.dox index 0000000000,54f2bd36e8..05f167045f mode 000000,100644..100644 --- a/src/bin/dhcp4/dhcp4.dox +++ b/src/bin/dhcp4/dhcp4.dox @@@ -1,0 -1,68 +1,68 @@@ + /** + @page dhcp4 DHCPv4 Server Component + + BIND10 offers DHCPv4 server implementation. It is implemented as + b10-dhcp4 component. Its primary code is located in + isc::dhcp::Dhcpv4Srv class. It uses \ref libdhcp extensively, + especially isc::dhcp::Pkt4, isc::dhcp::Option and + isc::dhcp::IfaceMgr classes. Currently this code offers skeleton + functionality, i.e. it is able to receive and process incoming + requests and trasmit responses. However, it does not have database + management, so it returns only one, hardcoded lease to whoever asks + for it. + + DHCPv4 server component does not support direct traffic (relayed + only), as support for transmission to hosts without IPv4 address + assigned is not implemented in IfaceMgr yet. + -@section dhcp4-session BIND10 message queue integration ++@section dhcpv4Session BIND10 message queue integration + + DHCPv4 server component is now integrated with BIND10 message queue. + The integration is performed by establishSession() and disconnectSession() + functions in isc::dhcp::ControlledDhcpv4Srv class. main() method deifined + in the src/bin/dhcp4/main.cc file instantiates isc::dhcp::ControlledDhcpv4Srv + class that establishes connection with msgq and install necessary handlers + for receiving commands and configuration updates. It is derived from + a base isc::dhcp::Dhcpv4Srv class that implements DHCPv4 server functionality, + without any controlling mechanisms. + + ControlledDhcpv4Srv instantiates several components to make management + session possible. In particular, isc::cc::Session cc_session + object uses ASIO for establishing connection. It registers its socket + in isc::asiolink::IOService io_service object. Typically, other components + (e.g. auth or resolver) that use ASIO for their communication, register their + other sockets in the + same io_service and then just call io_service.run() method that does + not return, until one of the callback decides that it is time to shut down + the whole component cal calls io_service.stop(). DHCPv4 works in a + different way. It does receive messages using select() + (see isc::dhcp::IfaceMgr::receive4()), which is incompatible with ASIO. + To solve this problem, socket descriptor is extracted from cc_session + object and is passed to IfaceMgr by using isc::dhcp::IfaceMgr::set_session_socket(). + IfaceMgr then uses this socket in its select() call. If there is some + data to be read, it calls registered callback that is supposed to + read and process incoming data. + + This somewhat complicated approach is needed for a simple reason. In + embedded deployments there will be no message queue. Not referring directly + to anything related to message queue in isc::dhcp::Dhcpv4Srv and + isc::dhcp::IfaceMgr classes brings in two benefits. First, the can + be used with and without message queue. Second benefit is related to the + first one: \ref libdhcp is supposed to be simple and robust and not require + many dependencies. One notable example of a use case that benefits from + this approach is a perfdhcp tool. Finally, the idea is that it should be + possible to instantiate Dhcpv4Srv object directly, thus getting a server + that does not support msgq. That is useful for embedded environments. + It may also be useful in validation. + -@section dhcp4-config-parser Configuration Parser in DHCPv4 ++@section dhcpv4ConfigParser Configuration Parser in DHCPv4 + + This parser follows exactly the same logic as its DHCPv6 counterpart. -See \ref dhcp6-config-parser. ++See \ref dhcpv6ConfigParser. + -@section dhcp4-config-inherit DHCPv4 configuration inheritance ++@section dhcpv4ConfigInherit DHCPv4 configuration inheritance + + Configuration inheritance in DHCPv4 follows exactly the same logic as its DHCPv6 -counterpart. See \ref dhcp6-config-inherit. ++counterpart. See \ref dhcpv6ConfigInherit. + + */ diff --cc src/bin/dhcp6/config_parser.cc index 2038d12f9a,3db6aeceea..4c6fab15f2 --- a/src/bin/dhcp6/config_parser.cc +++ b/src/bin/dhcp6/config_parser.cc @@@ -147,7 -147,7 +147,7 @@@ private /// in its base class, \ref DhcpConfigParser. /// /// For overview of usability of this generic purpose parser, see - /// \ref dhcpv6-config-inherit page. -/// \ref dhcp6-config-inherit page. ++/// \ref dhcpv6ConfigInherit page. /// /// @todo this class should be turned into the template class which /// will handle all uintX_types of data (see ticket #2415). @@@ -222,7 -222,7 +222,7 @@@ public /// @brief sets storage for value of this parameter /// - /// See \ref dhcpv6-config-inherit for details. - /// See \ref dhcp6-config-inherit for details. ++ /// See \ref dhcpv6ConfigInherit for details. /// /// @param storage pointer to the storage container void setStorage(Uint32Storage* storage) { @@@ -250,7 -250,7 +250,7 @@@ private /// in its base class, \ref DhcpConfigParser. /// /// For overview of usability of this generic purpose parser, see - /// \ref dhcpv6-config-inherit page. -/// \ref dhcp6-config-inherit page. ++/// \ref dhcpv6ConfigInherit page. class StringParser : public DhcpConfigParser { public: @@@ -293,7 -294,7 +294,7 @@@ /// @brief sets storage for value of this parameter /// - /// See \ref dhcpv6-config-inherit for details. - /// See \ref dhcp6-config-inherit for details. ++ /// See \ref dhcpv6ConfigInherit for details. /// /// @param storage pointer to the storage container void setStorage(StringStorage* storage) { @@@ -442,7 -446,7 +446,7 @@@ public pos = txt.find("-"); if (pos != string::npos) { // using min-max notation - IOAddress min(txt.substr(0,pos - 1)); - IOAddress min(txt.substr(0,pos)); ++ IOAddress min(txt.substr(0, pos)); IOAddress max(txt.substr(pos + 1)); Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max)); @@@ -459,7 -463,7 +463,7 @@@ /// @brief sets storage for value of this parameter /// - /// See \ref dhcpv6-config-inherit for details. - /// See \ref dhcp6-config-inherit for details. ++ /// See \ref dhcpv6ConfigInherit for details. /// /// @param storage pointer to the storage container void setStorage(PoolStorage* storage) { diff --cc src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 9679700df4,03bf3093a1..de0dc28d2a --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@@ -749,50 -786,207 +786,207 @@@ TEST_F(Dhcpv6SrvTest, ManyRequests) cout << "Assigned address to client3=" << addr3->getAddress().toText() << endl; } + // This test verifies that incoming (positive) RENEW can be handled properly, that a + // REPLY is generated, that the response has an address and that address + // really belongs to the configured pool and that lease is actually renewed. + // + // expected: + // - returned REPLY message has copy of client-id + // - returned REPLY message has server-id + // - returned REPLY message has IA that includes IAADDR + // - lease is actually renewed in LeaseMgr + TEST_F(Dhcpv6SrvTest, RenewBasic) { + boost::scoped_ptr srv; + ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) ); - TEST_F(Dhcpv6SrvTest, serverReceivedPacketName) { - // Check all possible packet types - for (int itype = 0; itype < 256; ++itype) { - uint8_t type = itype; + const IOAddress addr("2001:db8:1:1::cafe:babe"); + const uint32_t iaid = 234; - switch (type) { - case DHCPV6_CONFIRM: - EXPECT_STREQ("CONFIRM", Dhcpv6Srv::serverReceivedPacketName(type)); - break; + // Generate client-id also duid_ + OptionPtr clientid = generateClientId(); - case DHCPV6_DECLINE: - EXPECT_STREQ("DECLINE", Dhcpv6Srv::serverReceivedPacketName(type)); - break; + // Check that the address we are about to use is indeed in pool + ASSERT_TRUE(subnet_->inPool(addr)); + + // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid + // value on purpose. They should be updated during RENEW. + Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid, + 501, 502, 503, 504, subnet_->getID(), 0)); + lease->cltt_ = 1234; + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Check that the lease is really in the database + Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr); + ASSERT_TRUE(l); + + // Check that T1, T2, preferred, valid and cltt really set and not using + // previous (500, 501, etc.) values + EXPECT_NE(l->t1_, subnet_->getT1()); + EXPECT_NE(l->t2_, subnet_->getT2()); + EXPECT_NE(l->preferred_lft_, subnet_->getPreferred()); + EXPECT_NE(l->valid_lft_, subnet_->getValid()); + EXPECT_NE(l->cltt_, time(NULL)); + + // Let's create a RENEW + Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234)); + req->setRemoteAddr(IOAddress("fe80::abcd")); + boost::shared_ptr ia = generateIA(iaid, 1500, 3000); - case DHCPV6_INFORMATION_REQUEST: - EXPECT_STREQ("INFORMATION_REQUEST", - Dhcpv6Srv::serverReceivedPacketName(type)); - break; + OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500)); + ia->addOption(renewed_addr_opt); + req->addOption(ia); + req->addOption(clientid); - case DHCPV6_REBIND: - EXPECT_STREQ("REBIND", Dhcpv6Srv::serverReceivedPacketName(type)); - break; + // Server-id is mandatory in RENEW + req->addOption(srv->getServerID()); - case DHCPV6_RELEASE: - EXPECT_STREQ("RELEASE", Dhcpv6Srv::serverReceivedPacketName(type)); - break; + // Pass it to the server and hope for a REPLY + Pkt6Ptr reply = srv->processRenew(req); - case DHCPV6_RENEW: - EXPECT_STREQ("RENEW", Dhcpv6Srv::serverReceivedPacketName(type)); - break; + // Check if we get response at all + checkResponse(reply, DHCPV6_REPLY, 1234); - case DHCPV6_REQUEST: - EXPECT_STREQ("REQUEST", Dhcpv6Srv::serverReceivedPacketName(type)); - break; + OptionPtr tmp = reply->getOption(D6O_IA_NA); + ASSERT_TRUE(tmp); - case DHCPV6_SOLICIT: - EXPECT_STREQ("SOLICIT", Dhcpv6Srv::serverReceivedPacketName(type)); - break; + // Check that IA_NA was returned and that there's an address included + boost::shared_ptr addr_opt = checkIA_NA(reply, 234, subnet_->getT1(), + subnet_->getT2()); - default: - EXPECT_STREQ("UNKNOWN", Dhcpv6Srv::serverReceivedPacketName(type)); - } - } + // Check that we've got the address we requested + checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid()); + + // Check DUIDs + checkServerId(reply, srv->getServerID()); + checkClientId(reply, clientid); + + // Check that the lease is really in the database + l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt); + ASSERT_TRUE(l); + + // Check that T1, T2, preferred, valid and cltt were really updated + EXPECT_EQ(l->t1_, subnet_->getT1()); + EXPECT_EQ(l->t2_, subnet_->getT2()); + EXPECT_EQ(l->preferred_lft_, subnet_->getPreferred()); + EXPECT_EQ(l->valid_lft_, subnet_->getValid()); + + // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors + int32_t cltt = static_cast(l->cltt_); + int32_t expected = static_cast(time(NULL)); + // equality or difference by 1 between cltt and expected is ok. + EXPECT_GE(1, abs(cltt - expected)); + - EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease6(addr_opt->getAddress())); ++ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr_opt->getAddress())); + } + + // This test verifies that incoming (invalid) RENEW can be handled properly. + // + // This test checks 3 scenarios: + // 1. there is no such lease at all + // 2. there is such a lease, but it is assigned to a different IAID + // 3. there is such a lease, but it belongs to a different client + // + // expected: + // - returned REPLY message has copy of client-id + // - returned REPLY message has server-id + // - returned REPLY message has IA that includes STATUS-CODE + // - No lease in LeaseMgr + TEST_F(Dhcpv6SrvTest, RenewReject) { + + boost::scoped_ptr srv; + ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) ); + + const IOAddress addr("2001:db8:1:1::dead"); + const uint32_t transid = 1234; + const uint32_t valid_iaid = 234; + const uint32_t bogus_iaid = 456; + + // Quick sanity check that the address we're about to use is ok + ASSERT_TRUE(subnet_->inPool(addr)); + + // GenerateClientId() also sets duid_ + OptionPtr clientid = generateClientId(); + + // Check that the lease is NOT in the database + Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr); + ASSERT_FALSE(l); + + // Let's create a RENEW + Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, transid)); + req->setRemoteAddr(IOAddress("fe80::abcd")); + boost::shared_ptr ia = generateIA(bogus_iaid, 1500, 3000); + + OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500)); + ia->addOption(renewed_addr_opt); + req->addOption(ia); + req->addOption(clientid); + + // Server-id is mandatory in RENEW + req->addOption(srv->getServerID()); + + // Case 1: No lease known to server + + // Pass it to the server and hope for a REPLY + Pkt6Ptr reply = srv->processRenew(req); + + // Check if we get response at all + checkResponse(reply, DHCPV6_REPLY, transid); + OptionPtr tmp = reply->getOption(D6O_IA_NA); + ASSERT_TRUE(tmp); + // Check that IA_NA was returned and that there's an address included + ia = boost::dynamic_pointer_cast(tmp); + ASSERT_TRUE(ia); + checkRejectedIA_NA(ia, STATUS_NoAddrsAvail); + + // Check that there is no lease added + l = LeaseMgrFactory::instance().getLease6(addr); + ASSERT_FALSE(l); + + // CASE 2: Lease is known and belongs to this client, but to a different IAID + + // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid + // value on purpose. They should be updated during RENEW. + Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, valid_iaid, + 501, 502, 503, 504, subnet_->getID(), 0)); + lease->cltt_ = 123; // Let's use it as an indicator that the lease + // was NOT updated. + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Pass it to the server and hope for a REPLY + reply = srv->processRenew(req); + checkResponse(reply, DHCPV6_REPLY, transid); + tmp = reply->getOption(D6O_IA_NA); + ASSERT_TRUE(tmp); + // Check that IA_NA was returned and that there's an address included + ia = boost::dynamic_pointer_cast(tmp); + ASSERT_TRUE(ia); + checkRejectedIA_NA(ia, STATUS_NoAddrsAvail); + + // There is a iaid mis-match, so server should respond that there is + // no such address to renew. + + // CASE 3: Lease belongs to a client with different client-id + req->delOption(D6O_CLIENTID); + ia = boost::dynamic_pointer_cast(req->getOption(D6O_IA_NA)); + ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr + req->addOption(generateClientId(13)); // generate different DUID + // (with length 13) + + reply = srv->processRenew(req); + checkResponse(reply, DHCPV6_REPLY, transid); + tmp = reply->getOption(D6O_IA_NA); + ASSERT_TRUE(tmp); + // Check that IA_NA was returned and that there's an address included + ia = boost::dynamic_pointer_cast(tmp); + ASSERT_TRUE(ia); + checkRejectedIA_NA(ia, STATUS_NoAddrsAvail); + + lease = LeaseMgrFactory::instance().getLease6(addr); + ASSERT_TRUE(lease); + // Verify that the lease was not updated. + EXPECT_EQ(123, lease->cltt_); + - EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease6(addr)); ++ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr)); } // This test verifies if the status code option is generated properly. diff --cc src/lib/dhcp/option_custom.h index ad248c5f38,d9a4c18417..0ee4688995 --- a/src/lib/dhcp/option_custom.h +++ b/src/lib/dhcp/option_custom.h @@@ -103,7 -161,34 +161,33 @@@ public /// /// @throw isc::OutOfRange if index is out of range. /// @return read boolean value. - bool readBoolean(const uint32_t index) const; + bool readBoolean(const uint32_t index = 0) const; + + /// @brief Write a boolean value into a buffer. + /// + /// @param value boolean value to be written. + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + void writeBoolean(const bool value, const uint32_t index = 0); + + /// @brief Read a buffer as FQDN. + /// + /// @param index buffer index. - /// @param [out] len number of bytes read from a buffer. + /// + /// @throw isc::OutOfRange if buffer index is out of range. + /// @throw isc::dhcp::BadDataTypeCast if a buffer being read + /// does not hold a valid FQDN. + /// @return string representation if FQDN. + std::string readFqdn(const uint32_t index = 0) const; + + /// @brief Write an FQDN into a buffer. + /// + /// @param fqdn text representation of FQDN. + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + void writeFqdn(const std::string& fqdn, const uint32_t index = 0); /// @brief Read a buffer as integer value. /// @@@ -156,7 -241,14 +240,14 @@@ /// /// @return string value read from buffer. /// @throw isc::OutOfRange if index is out of range. - std::string readString(const uint32_t index) const; + std::string readString(const uint32_t index = 0) const; + + /// @brief Write a string value into a buffer. + /// + /// @param text the string value to be written. - /// @param buffer index. ++ /// @param index buffer index. + void writeString(const std::string& text, + const uint32_t index = 0); /// @brief Parses received buffer. /// diff --cc src/lib/dhcp/pkt4.h index efeaf62c96,e09069cc36..5139458a60 --- a/src/lib/dhcp/pkt4.h +++ b/src/lib/dhcp/pkt4.h @@@ -269,9 -269,9 +269,9 @@@ public /// /// @param hType hardware type (will be sent in htype field) /// @param hlen hardware length (will be sent in hlen field) -- /// @param macAddr pointer to hardware address ++ /// @param mac_addr pointer to hardware address void setHWAddr(uint8_t hType, uint8_t hlen, - const std::vector& macAddr); + const std::vector& mac_addr); /// Returns htype field /// diff --cc src/lib/dhcpsrv/database_backends.dox index f954bed1b1,f954bed1b1..a3b3b0c210 --- a/src/lib/dhcpsrv/database_backends.dox +++ b/src/lib/dhcpsrv/database_backends.dox @@@ -1,5 -1,5 +1,5 @@@ /** -- @page dhcp-database-backends DHCP Database Back-Ends ++ @page dhcpDatabaseBackends DHCP Database Back-Ends All DHCP lease data is stored in some form of database, the interface to this being through the Lease Manager.