diff options
Diffstat (limited to 'src/lib/dhcp')
43 files changed, 1211 insertions, 323 deletions
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am index 17489278ee..640bac1b88 100644 --- a/src/lib/dhcp/Makefile.am +++ b/src/lib/dhcp/Makefile.am @@ -19,6 +19,7 @@ libb10_dhcp___la_SOURCES += duid.cc duid.h libb10_dhcp___la_SOURCES += hwaddr.cc hwaddr.h libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc +libb10_dhcp___la_SOURCES += iface_mgr_error_handler.h libb10_dhcp___la_SOURCES += iface_mgr_linux.cc libb10_dhcp___la_SOURCES += iface_mgr_sun.cc libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h diff --git a/src/lib/dhcp/docsis3_option_defs.h b/src/lib/dhcp/docsis3_option_defs.h index 19bdaa0cb9..6031f8d611 100644 --- a/src/lib/dhcp/docsis3_option_defs.h +++ b/src/lib/dhcp/docsis3_option_defs.h @@ -18,8 +18,8 @@ #include <dhcp/std_option_defs.h> #include <dhcp/option_data_types.h> - -namespace { +namespace isc { +namespace dhcp { #define VENDOR_ID_CABLE_LABS 4491 @@ -61,6 +61,11 @@ const OptionDefParams DOCSIS3_V6_DEFS[] = { /// Number of option definitions defined. const int DOCSIS3_V6_DEFS_SIZE = sizeof(DOCSIS3_V6_DEFS) / sizeof(OptionDefParams); -}; // anonymous namespace +/// The class as specified in vendor-class option by the devices +extern const char* DOCSIS3_CLASS_EROUTER; +extern const char* DOCSIS3_CLASS_MODEM; + +}; // isc::dhcp namespace +}; // isc namespace #endif // DOCSIS3_OPTION_DEFS_H diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc index d97614bae1..a5311343e7 100644 --- a/src/lib/dhcp/iface_mgr.cc +++ b/src/lib/dhcp/iface_mgr.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2014 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 @@ -22,6 +22,7 @@ #include <dhcp/dhcp4.h> #include <dhcp/dhcp6.h> #include <dhcp/iface_mgr.h> +#include <dhcp/iface_mgr_error_handler.h> #include <dhcp/pkt_filter_inet.h> #include <dhcp/pkt_filter_inet6.h> #include <exceptions/exceptions.h> @@ -37,40 +38,6 @@ #include <string.h> #include <sys/select.h> -/// @brief A macro which handles an error in IfaceMgr. -/// -/// There are certain cases when IfaceMgr may hit an error which shouldn't -/// result in interruption of the function processing. A typical case is -/// the function which opens sockets on available interfaces for a DHCP -/// server. If this function fails to open a socket on a specific interface -/// (for example, there is another socket already open on this interface -/// and bound to the same address and port), it is desired that the server -/// logs a warning but will try to open sockets on other interfaces. In order -/// to log an error, the IfaceMgr will use the error handler function provided -/// by the server and pass an error string to it. When the handler function -/// returns, the IfaceMgr will proceed to open other sockets. It is allowed -/// that the error handler function is not installed (is NULL). In these -/// cases it is expected that the exception is thrown instead. A possible -/// solution would be to enclose this conditional behavior in a function. -/// However, despite the hate for macros, the macro seems to be a bit -/// better solution in this case as it allows to convenietly pass an -/// error string in a stream (not as a string). -/// -/// @param ex_type Exception to be thrown if error_handler is NULL. -/// @param handler Error handler function to be called or NULL to indicate -/// that exception should be thrown instead. -/// @param stream stream object holding an error string. -#define IFACEMGR_ERROR(ex_type, handler, stream) \ -{ \ - std::ostringstream oss__; \ - oss__ << stream; \ - if (handler) { \ - handler(oss__.str()); \ - } else { \ - isc_throw(ex_type, oss__); \ - } \ -} \ - using namespace std; using namespace isc::asiolink; using namespace isc::util::io::internal; @@ -183,7 +150,7 @@ bool Iface::delAddress(const isc::asiolink::IOAddress& addr) { return (false); } -bool Iface::delSocket(uint16_t sockfd) { +bool Iface::delSocket(const uint16_t sockfd) { list<SocketInfo>::iterator sock = sockets_.begin(); while (sock!=sockets_.end()) { if (sock->sockfd_ == sockfd) { @@ -203,7 +170,6 @@ bool Iface::delSocket(uint16_t sockfd) { IfaceMgr::IfaceMgr() :control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))), control_buf_(new char[control_buf_len_]), - session_socket_(INVALID_SOCKET), session_callback_(NULL), packet_filter_(new PktFilterInet()), packet_filter6_(new PktFilterInet6()) { @@ -226,7 +192,7 @@ void Iface::addUnicast(const isc::asiolink::IOAddress& addr) { for (Iface::AddressCollection::const_iterator i = unicasts_.begin(); i != unicasts_.end(); ++i) { if (*i == addr) { - isc_throw(BadValue, "Address " << addr.toText() + isc_throw(BadValue, "Address " << addr << " already defined on the " << name_ << " interface."); } } @@ -261,6 +227,37 @@ IfaceMgr::isDirectResponseSupported() const { } void +IfaceMgr::addExternalSocket(int socketfd, SocketCallback callback) { + for (SocketCallbackInfoContainer::iterator s = callbacks_.begin(); + s != callbacks_.end(); ++s) { + + // There's such a socket description there already. + // Update the callback and we're done + if (s->socket_ == socketfd) { + s->callback_ = callback; + return; + } + } + + // Add a new entry to the callbacks vector + SocketCallbackInfo x; + x.socket_ = socketfd; + x.callback_ = callback; + callbacks_.push_back(x); +} + +void +IfaceMgr::deleteExternalSocket(int socketfd) { + for (SocketCallbackInfoContainer::iterator s = callbacks_.begin(); + s != callbacks_.end(); ++s) { + if (s->socket_ == socketfd) { + callbacks_.erase(s); + return; + } + } +} + +void IfaceMgr::setPacketFilter(const PktFilterPtr& packet_filter) { // Do not allow NULL pointer. if (!packet_filter) { @@ -321,6 +318,24 @@ IfaceMgr::hasOpenSocket(const uint16_t family) const { return (false); } +bool +IfaceMgr::hasOpenSocket(const IOAddress& addr) const { + // Iterate over all interfaces and search for open sockets. + for (IfaceCollection::const_iterator iface = ifaces_.begin(); + iface != ifaces_.end(); ++iface) { + const Iface::SocketCollection& sockets = iface->getSockets(); + for (Iface::SocketCollection::const_iterator sock = sockets.begin(); + sock != sockets.end(); ++sock) { + // Check if the socket address matches the specified address. + if (sock->addr_ == addr) { + return (true); + } + } + } + // There are no open sockets found for the specified family. + return (false); +} + void IfaceMgr::stubDetectIfaces() { string ifaceName; const string v4addr("127.0.0.1"), v6addr("::1"); @@ -481,7 +496,6 @@ IfaceMgr::openSockets6(const uint16_t port, try { openSocket(iface->getName(), *addr, port); - } catch (const Exception& ex) { IFACEMGR_ERROR(SocketConfigError, error_handler, "Failed to open unicast socket on interface " @@ -509,64 +523,17 @@ IfaceMgr::openSockets6(const uint16_t port, // with interface with 2 global addresses, we would bind 3 sockets // (one for link-local and two for global). That would result in // getting each message 3 times. - if (!addr->getAddress().to_v6().is_link_local()){ + if (!addr->isV6LinkLocal()){ continue; } - // Open socket and join multicast group only if the interface - // is multicast-capable. - // @todo The DHCPv6 requires multicast so we may want to think - // whether we want to open the socket on a multicast-incapable - // interface or not. For now, we prefer to be liberal and allow - // it for some odd use cases which may utilize non-multicast - // interfaces. Perhaps a warning should be emitted if the - // interface is not a multicast one. - - // The sock variable will hold a socket descriptor. It may be - // used to close a socket if the function fails to bind to - // multicast address on Linux systems. Because we only bind - // a socket to multicast address on Linux, on other systems - // the sock variable will be initialized but unused. We have - // to suppress the cppcheck warning which shows up on non-Linux - // systems. - // cppcheck-suppress variableScope - int sock; - try { - // cppcheck-suppress unreadVariable - sock = openSocket(iface->getName(), *addr, port, - iface->flag_multicast_); - - } catch (const Exception& ex) { - IFACEMGR_ERROR(SocketConfigError, error_handler, - "Failed to open link-local socket on " - " interface " << iface->getName() << ": " - << ex.what()); - continue; - + // Run OS-specific function to open a socket on link-local address + // and join multicast group (non-Linux OSes), or open two sockets and + // bind one to link-local, another one to multicast address. + if (openMulticastSocket(*iface, *addr, port, error_handler)) { + ++count; } - count++; - - /// @todo: Remove this ifdef once we start supporting BSD systems. -#if defined(OS_LINUX) - // To receive multicast traffic, Linux requires binding socket to - // a multicast group. That in turn doesn't work on NetBSD. - if (iface->flag_multicast_) { - try { - openSocket(iface->getName(), - IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS), - port); - } catch (const Exception& ex) { - // Delete previously opened socket. - iface->delSocket(sock); - IFACEMGR_ERROR(SocketConfigError, error_handler, - "Failed to open multicast socket on" - " interface " << iface->getName() - << ", reason: " << ex.what()); - continue; - } - } -#endif } } return (count > 0); @@ -644,7 +611,7 @@ int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr, } else { isc_throw(BadValue, "Failed to detect family of address: " - << addr.toText()); + << addr); } } @@ -719,14 +686,14 @@ int IfaceMgr::openSocketFromAddress(const IOAddress& addr, } // If we got here it means that we did not find specified address // on any available interface. - isc_throw(BadValue, "There is no such address " << addr.toText()); + isc_throw(BadValue, "There is no such address " << addr); } int IfaceMgr::openSocketFromRemoteAddress(const IOAddress& remote_addr, const uint16_t port) { try { // Get local address to be used to connect to remote location. - IOAddress local_address(getLocalAddress(remote_addr, port).getAddress()); + IOAddress local_address(getLocalAddress(remote_addr, port)); return openSocketFromAddress(local_address, port); } catch (const Exception& e) { isc_throw(SocketConfigError, e.what()); @@ -791,7 +758,6 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) { return IOAddress(local_address); } - int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port, const bool join_multicast) { @@ -853,7 +819,6 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) { IfaceCollection::const_iterator iface; fd_set sockets; int maxfd = 0; - stringstream names; FD_ZERO(&sockets); @@ -868,7 +833,6 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) { // Only deal with IPv4 addresses. if (s->addr_.isV4()) { - names << s->sockfd_ << "(" << iface->getName() << ") "; // Add this socket to listening set FD_SET(s->sockfd_, &sockets); @@ -879,13 +843,15 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) { } } - // if there is session socket registered... - if (session_socket_ != INVALID_SOCKET) { - // at it to the set as well - FD_SET(session_socket_, &sockets); - if (maxfd < session_socket_) - maxfd = session_socket_; - names << session_socket_ << "(session)"; + // if there are any callbacks for external sockets registered... + if (!callbacks_.empty()) { + for (SocketCallbackInfoContainer::const_iterator s = callbacks_.begin(); + s != callbacks_.end(); ++s) { + FD_SET(s->socket_, &sockets); + if (maxfd < s->socket_) { + maxfd = s->socket_; + } + } } struct timeval select_timeout; @@ -902,18 +868,22 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) { } // Let's find out which socket has the data - if ((session_socket_ != INVALID_SOCKET) && (FD_ISSET(session_socket_, &sockets))) { - // something received over session socket - if (session_callback_) { - // in theory we could call io_service.run_one() here, instead of - // implementing callback mechanism, but that would introduce - // asiolink dependency to libdhcp++ and that is something we want - // to avoid (see CPE market and out long term plans for minimalistic - // implementations. - session_callback_(); + for (SocketCallbackInfoContainer::iterator s = callbacks_.begin(); + s != callbacks_.end(); ++s) { + if (!FD_ISSET(s->socket_, &sockets)) { + continue; } - return (Pkt4Ptr()); // NULL + // something received over external socket + + // Calling the external socket's callback provides its service + // layer access without integrating any specific features + // in IfaceMgr + if (s->callback_) { + s->callback_(); + } + + return (Pkt4Ptr()); } // Let's find out which interface/socket has the data @@ -950,7 +920,6 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ const SocketInfo* candidate = 0; fd_set sockets; int maxfd = 0; - stringstream names; FD_ZERO(&sockets); @@ -965,7 +934,6 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ // Only deal with IPv6 addresses. if (s->addr_.isV6()) { - names << s->sockfd_ << "(" << iface->getName() << ") "; // Add this socket to listening set FD_SET(s->sockfd_, &sockets); @@ -976,13 +944,17 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ } } - // if there is session socket registered... - if (session_socket_ != INVALID_SOCKET) { - // at it to the set as well - FD_SET(session_socket_, &sockets); - if (maxfd < session_socket_) - maxfd = session_socket_; - names << session_socket_ << "(session)"; + // if there are any callbacks for external sockets registered... + if (!callbacks_.empty()) { + for (SocketCallbackInfoContainer::const_iterator s = callbacks_.begin(); + s != callbacks_.end(); ++s) { + + // Add it to the set as well + FD_SET(s->socket_, &sockets); + if (maxfd < s->socket_) { + maxfd = s->socket_; + } + } } struct timeval select_timeout; @@ -999,18 +971,22 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ } // Let's find out which socket has the data - if ((session_socket_ != INVALID_SOCKET) && (FD_ISSET(session_socket_, &sockets))) { - // something received over session socket - if (session_callback_) { - // in theory we could call io_service.run_one() here, instead of - // implementing callback mechanism, but that would introduce - // asiolink dependency to libdhcp++ and that is something we want - // to avoid (see CPE market and out long term plans for minimalistic - // implementations. - session_callback_(); + for (SocketCallbackInfoContainer::iterator s = callbacks_.begin(); + s != callbacks_.end(); ++s) { + if (!FD_ISSET(s->socket_, &sockets)) { + continue; } - return (Pkt6Ptr()); // NULL + // something received over external socket + + // Calling the external socket's callback provides its service + // layer access without integrating any specific features + // in IfaceMgr + if (s->callback_) { + s->callback_(); + } + + return (Pkt6Ptr()); } // Let's find out which interface/socket has the data @@ -1057,7 +1033,7 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) { } // Sockets bound to multicast address are useless for sending anything. - if (s->addr_.getAddress().to_v6().is_multicast()) { + if (s->addr_.isV6Multicast()) { continue; } @@ -1074,10 +1050,10 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) { // If we want to send something to link-local and the socket is // bound to link-local or we want to send to global and the socket // is bound to global, then use it as candidate - if ( (pkt.getRemoteAddr().getAddress().to_v6().is_link_local() && - s->addr_.getAddress().to_v6().is_link_local()) || - (!pkt.getRemoteAddr().getAddress().to_v6().is_link_local() && - !s->addr_.getAddress().to_v6().is_link_local()) ) { + if ( (pkt.getRemoteAddr().isV6LinkLocal() && + s->addr_.isV6LinkLocal()) || + (!pkt.getRemoteAddr().isV6LinkLocal() && + !s->addr_.isV6LinkLocal()) ) { candidate = s; } } @@ -1113,6 +1089,5 @@ IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) { << " does not have any suitable IPv4 sockets open."); } - } // end of namespace isc::dhcp } // end of namespace isc diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h index 83b19dc2f2..12212c74c8 100644 --- a/src/lib/dhcp/iface_mgr.h +++ b/src/lib/dhcp/iface_mgr.h @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2014 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 @@ -34,6 +34,7 @@ namespace isc { namespace dhcp { + /// @brief IfaceMgr exception thrown thrown when interface detection fails. class IfaceDetectError : public Exception { public: @@ -393,8 +394,20 @@ boost::function<void(const std::string& errmsg)> IfaceMgrErrorMsgCallback; /// class IfaceMgr : public boost::noncopyable { public: - /// Defines callback used when commands are received over control session. - typedef void (*SessionCallback) (void); + /// Defines callback used when data is received over external sockets. + typedef boost::function<void ()> SocketCallback; + + /// Keeps callback information for external sockets. + struct SocketCallbackInfo { + /// Socket descriptor of the external socket. + int socket_; + + /// A callback that will be called when data arrives over socket_. + SocketCallback callback_; + }; + + /// Defines storage container for callbacks for external sockets + typedef std::list<SocketCallbackInfo> SocketCallbackInfoContainer; /// @brief Packet reception buffer size /// @@ -784,17 +797,18 @@ public: /// @return number of detected interfaces uint16_t countIfaces() { return ifaces_.size(); } - /// @brief Sets session socket and a callback + /// @brief Adds external socket and a callback /// - /// Specifies session socket and a callback that will be called + /// Specifies external socket and a callback that will be called /// when data will be received over that socket. /// /// @param socketfd socket descriptor /// @param callback callback function - void set_session_socket(int socketfd, SessionCallback callback) { - session_socket_ = socketfd; - session_callback_ = callback; - } + void addExternalSocket(int socketfd, SocketCallback callback); + + /// @brief Deletes external socket + + void deleteExternalSocket(int socketfd); /// @brief Set packet filter object to handle sending and receiving DHCPv4 /// messages. @@ -862,8 +876,23 @@ public: ifaces_.push_back(iface); } - /// A value of socket descriptor representing "not specified" state. - static const int INVALID_SOCKET = -1; + /// @brief Checks if there is at least one socket of the specified family + /// open. + /// + /// @param family A socket family. + /// + /// @return true if there is at least one socket open, false otherwise. + bool hasOpenSocket(const uint16_t family) const; + + /// @brief Checks if there is a socket open and bound to an address. + /// + /// This function checks if one of the sockets opened by the IfaceMgr is + /// bound to the IP address specified as the method parameter. + /// + /// @param addr Address of the socket being searched. + /// + /// @return true if there is a socket bound to the specified address. + bool hasOpenSocket(const isc::asiolink::IOAddress& addr) const; // don't use private, we need derived classes in tests protected: @@ -958,13 +987,7 @@ protected: /// @return true if successful, false otherwise bool os_receive4(struct msghdr& m, Pkt4Ptr& pkt); - /// Socket descriptor of the session socket. - int session_socket_; - - /// A callback that will be called when data arrives over session_socket_. - SessionCallback session_callback_; private: - /// @brief Identifies local network address to be used to /// connect to remote address. /// @@ -983,13 +1006,29 @@ private: getLocalAddress(const isc::asiolink::IOAddress& remote_addr, const uint16_t port); - /// @brief Checks if there is at least one socket of the specified family - /// open. + + /// @brief Open an IPv6 socket with multicast support. /// - /// @param family A socket family. + /// This function opens socket(s) to allow reception of the DHCPv6 sent + /// to multicast address. It opens an IPv6 socket, binds it to link-local + /// address and joins multicast group (on non-Linux systems) or opens two + /// IPv6 sockets and binds one of them to link-local address and another + /// one to multicast address (on Linux systems). /// - /// @return true if there is at least one socket open, false otherwise. - bool hasOpenSocket(const uint16_t family) const; + /// @note This function is intended to be called internally by the + /// @c IfaceMgr::openSockets6. It is not intended to be called from any + /// other function. + /// + /// @param iface Interface on which socket should be open. + /// @param addr Link-local address to bind the socket to. + /// @param port Port number to bind socket to. + /// @param error_handler Error handler function to be called when an + /// error occurs during opening a socket, or NULL if exception should + /// be thrown upon error. + bool openMulticastSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + IfaceMgrErrorMsgCallback error_handler = NULL); /// Holds instance of a class derived from PktFilter, used by the /// IfaceMgr to open sockets and send/receive packets through these @@ -1006,6 +1045,9 @@ private: /// messages. It is possible to supply a custom object using /// setPacketFilter method. PktFilter6Ptr packet_filter6_; + + /// @brief Contains list of callbacks for external sockets + SocketCallbackInfoContainer callbacks_; }; }; // namespace isc::dhcp diff --git a/src/lib/dhcp/iface_mgr_bsd.cc b/src/lib/dhcp/iface_mgr_bsd.cc index be73bcfc2e..7a01228256 100644 --- a/src/lib/dhcp/iface_mgr_bsd.cc +++ b/src/lib/dhcp/iface_mgr_bsd.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011, 2013-2014 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 @@ -17,6 +17,7 @@ #if defined(OS_BSD) #include <dhcp/iface_mgr.h> +#include <dhcp/iface_mgr_error_handler.h> #include <dhcp/pkt_filter_inet.h> #include <exceptions/exceptions.h> @@ -149,6 +150,28 @@ IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) { setPacketFilter(PktFilterPtr(new PktFilterInet())); } +bool +IfaceMgr::openMulticastSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + IfaceMgrErrorMsgCallback error_handler) { + try { + // This should open a socket, bound it to link-local address + // and join multicast group. + openSocket(iface.getName(), addr, port, + iface.flag_multicast_); + + } catch (const Exception& ex) { + IFACEMGR_ERROR(SocketConfigError, error_handler, + "Failed to open link-local socket on " + " interface " << iface.getName() << ": " + << ex.what()); + return (false); + + } + return (true); +} + } // end of isc::dhcp namespace } // end of dhcp namespace diff --git a/src/lib/dhcp/iface_mgr_error_handler.h b/src/lib/dhcp/iface_mgr_error_handler.h new file mode 100644 index 0000000000..c5ef5b401b --- /dev/null +++ b/src/lib/dhcp/iface_mgr_error_handler.h @@ -0,0 +1,52 @@ +// Copyright (C) 2014 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. + +#ifndef IFACE_MGR_ERROR_HANDLER_H +#define IFACE_MGR_ERROR_HANDLER_H + +/// @brief A macro which handles an error in IfaceMgr. +/// +/// There are certain cases when IfaceMgr may hit an error which shouldn't +/// result in interruption of the function processing. A typical case is +/// the function which opens sockets on available interfaces for a DHCP +/// server. If this function fails to open a socket on a specific interface +/// (for example, there is another socket already open on this interface +/// and bound to the same address and port), it is desired that the server +/// logs a warning but will try to open sockets on other interfaces. In order +/// to log an error, the IfaceMgr will use the error handler function provided +/// by the server and pass an error string to it. When the handler function +/// returns, the IfaceMgr will proceed to open other sockets. It is allowed +/// that the error handler function is not installed (is NULL). In these +/// cases it is expected that the exception is thrown instead. A possible +/// solution would be to enclose this conditional behavior in a function. +/// However, despite the hate for macros, the macro seems to be a bit +/// better solution in this case as it allows to convenietly pass an +/// error string in a stream (not as a string). +/// +/// @param ex_type Exception to be thrown if error_handler is NULL. +/// @param handler Error handler function to be called or NULL to indicate +/// that exception should be thrown instead. +/// @param stream stream object holding an error string. +#define IFACEMGR_ERROR(ex_type, handler, stream) \ +{ \ + std::ostringstream oss__; \ + oss__ << stream; \ + if (handler) { \ + handler(oss__.str()); \ + } else { \ + isc_throw(ex_type, oss__); \ + } \ +} \ + +#endif // IFACE_MGR_ERROR_HANDLER_H diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc index dddeb52921..f4b0613dd8 100644 --- a/src/lib/dhcp/iface_mgr_linux.cc +++ b/src/lib/dhcp/iface_mgr_linux.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2014 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 @@ -33,6 +33,7 @@ #include <asiolink/io_address.h> #include <dhcp/iface_mgr.h> +#include <dhcp/iface_mgr_error_handler.h> #include <dhcp/pkt_filter_inet.h> #include <dhcp/pkt_filter_lpf.h> #include <exceptions/exceptions.h> @@ -533,6 +534,60 @@ bool IfaceMgr::os_receive4(struct msghdr&, Pkt4Ptr&) { return (true); } +bool +IfaceMgr::openMulticastSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + IfaceMgrErrorMsgCallback error_handler) { + // This variable will hold a descriptor of the socket bound to + // link-local address. It may be required for us to close this + // socket if an attempt to open and bind a socket to multicast + // address fails. + int sock; + try { + sock = openSocket(iface.getName(), addr, port, + iface.flag_multicast_); + + } catch (const Exception& ex) { + IFACEMGR_ERROR(SocketConfigError, error_handler, + "Failed to open link-local socket on " + " interface " << iface.getName() << ": " + << ex.what()); + return (false); + + } + + // To receive multicast traffic, Linux requires binding socket to + // the multicast address. + + /// @todo The DHCPv6 requires multicast so we may want to think + /// whether we want to open the socket on a multicast-incapable + /// interface or not. For now, we prefer to be liberal and allow + /// it for some odd use cases which may utilize non-multicast + /// interfaces. Perhaps a warning should be emitted if the + /// interface is not a multicast one. + if (iface.flag_multicast_) { + try { + openSocket(iface.getName(), + IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS), + port); + } catch (const Exception& ex) { + // An attempt to open and bind a socket to multicast addres + // has failed. We have to close the socket we previously + // bound to link-local address - this is everything or + // nothing strategy. + iface.delSocket(sock); + IFACEMGR_ERROR(SocketConfigError, error_handler, + "Failed to open multicast socket on" + " interface " << iface.getName() + << ", reason: " << ex.what()); + return (false); + } + } + // Both sockets have opened successfully. + return (true); +} + } // end of isc::dhcp namespace } // end of isc namespace diff --git a/src/lib/dhcp/iface_mgr_sun.cc b/src/lib/dhcp/iface_mgr_sun.cc index fe2b0b3df2..a78de8f4a5 100644 --- a/src/lib/dhcp/iface_mgr_sun.cc +++ b/src/lib/dhcp/iface_mgr_sun.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011, 2013-2014 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 @@ -17,6 +17,7 @@ #if defined(OS_SUN) #include <dhcp/iface_mgr.h> +#include <dhcp/iface_mgr_error_handler.h> #include <dhcp/pkt_filter_inet.h> #include <exceptions/exceptions.h> @@ -153,6 +154,28 @@ IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) { setPacketFilter(PktFilterPtr(new PktFilterInet())); } +bool +IfaceMgr::openMulticastSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + IfaceMgrErrorMsgCallback error_handler) { + try { + // This should open a socket, bound it to link-local address + // and join multicast group. + openSocket(iface.getName(), addr, port, + iface.flag_multicast_); + + } catch (const Exception& ex) { + IFACEMGR_ERROR(SocketConfigError, error_handler, + "Failed to open link-local socket on " + " interface " << iface.getName() << ": " + << ex.what()); + return (false); + + } + return (true); +} + } // end of isc::dhcp namespace } // end of dhcp namespace diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc index 4577c642dc..f55f6bc075 100644 --- a/src/lib/dhcp/libdhcp++.cc +++ b/src/lib/dhcp/libdhcp++.cc @@ -52,6 +52,14 @@ VendorOptionDefContainers LibDHCP::vendor4_defs_; VendorOptionDefContainers LibDHCP::vendor6_defs_; +// Those two vendor classes are used for cable modems: + +/// DOCSIS3.0 compatible cable modem +const char* isc::dhcp::DOCSIS3_CLASS_MODEM = "docsis3.0"; + +/// DOCSIS3.0 cable modem that has router built-in +const char* isc::dhcp::DOCSIS3_CLASS_EROUTER = "eRouter1.0"; + // Let's keep it in .cc file. Moving it to .h would require including optionDefParams // definitions there void initOptionSpace(OptionDefContainer& defs, @@ -223,10 +231,10 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf, // The buffer being read comprises a set of options, each starting with // a two-byte type code and a two-byte length field. while (offset + 4 <= length) { - uint16_t opt_type = isc::util::readUint16(&buf[offset]); + uint16_t opt_type = isc::util::readUint16(&buf[offset], 2); offset += 2; - uint16_t opt_len = isc::util::readUint16(&buf[offset]); + uint16_t opt_len = isc::util::readUint16(&buf[offset], 2); offset += 2; if (offset + opt_len > length) { @@ -405,10 +413,10 @@ size_t LibDHCP::unpackVendorOptions6(const uint32_t vendor_id, // The buffer being read comprises a set of options, each starting with // a two-byte type code and a two-byte length field. while (offset + 4 <= length) { - uint16_t opt_type = isc::util::readUint16(&buf[offset]); + uint16_t opt_type = isc::util::readUint16(&buf[offset], 2); offset += 2; - uint16_t opt_len = isc::util::readUint16(&buf[offset]); + uint16_t opt_len = isc::util::readUint16(&buf[offset], 2); offset += 2; if (offset + opt_len > length) { diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc index f5ab75ede0..24ddc06854 100644 --- a/src/lib/dhcp/option.cc +++ b/src/lib/dhcp/option.cc @@ -250,35 +250,28 @@ uint8_t Option::getUint8() { } uint16_t Option::getUint16() { - if (data_.size() < sizeof(uint16_t) ) { - isc_throw(OutOfRange, "Attempt to read uint16 from option " << type_ - << " that has size " << data_.size()); - } - - return ( readUint16(&data_[0]) ); + // readUint16() checks and throws OutOfRange if data_ is too small. + return (readUint16(&data_[0], data_.size())); } uint32_t Option::getUint32() { - if (data_.size() < sizeof(uint32_t) ) { - isc_throw(OutOfRange, "Attempt to read uint32 from option " << type_ - << " that has size " << data_.size()); - } - return ( readUint32(&data_[0]) ); + // readUint32() checks and throws OutOfRange if data_ is too small. + return (readUint32(&data_[0], data_.size())); } void Option::setUint8(uint8_t value) { - data_.resize(1); - data_[0] = value; + data_.resize(sizeof(value)); + data_[0] = value; } void Option::setUint16(uint16_t value) { - data_.resize(2); - writeUint16(value, &data_[0]); + data_.resize(sizeof(value)); + writeUint16(value, &data_[0], data_.size()); } void Option::setUint32(uint32_t value) { - data_.resize(4); - writeUint32(value, &data_[0]); + data_.resize(sizeof(value)); + writeUint32(value, &data_[0], data_.size()); } bool Option::equal(const OptionPtr& other) const { diff --git a/src/lib/dhcp/option4_addrlst.cc b/src/lib/dhcp/option4_addrlst.cc index 436d07d0ff..223da16e1e 100644 --- a/src/lib/dhcp/option4_addrlst.cc +++ b/src/lib/dhcp/option4_addrlst.cc @@ -53,7 +53,7 @@ Option4AddrLst::Option4AddrLst(uint8_t type, OptionBufferConstIter first, while (first != last) { const uint8_t* ptr = &(*first); - addAddress(IOAddress(readUint32(ptr))); + addAddress(IOAddress(readUint32(ptr, distance(first, last)))); first += V4ADDRESS_LEN; } } diff --git a/src/lib/dhcp/option4_client_fqdn.cc b/src/lib/dhcp/option4_client_fqdn.cc index 7f93a50d33..d546ab2f02 100644 --- a/src/lib/dhcp/option4_client_fqdn.cc +++ b/src/lib/dhcp/option4_client_fqdn.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2014 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 @@ -217,7 +217,6 @@ setDomainName(const std::string& domain_name, } else { try { domain_name_.reset(new isc::dns::Name(name)); - domain_name_type_ = name_type; } catch (const Exception& ex) { isc_throw(InvalidOption4FqdnDomainName, @@ -227,6 +226,8 @@ setDomainName(const std::string& domain_name, } } + + domain_name_type_ = name_type; } void diff --git a/src/lib/dhcp/option6_addrlst.cc b/src/lib/dhcp/option6_addrlst.cc index cb14070527..45c22625ee 100644 --- a/src/lib/dhcp/option6_addrlst.cc +++ b/src/lib/dhcp/option6_addrlst.cc @@ -102,9 +102,9 @@ std::string Option6AddrLst::toText(int indent /* =0 */) { tmp << "type=" << type_ << " " << addrs_.size() << "addr(s): "; - for (AddressContainer::const_iterator addr=addrs_.begin(); - addr!=addrs_.end(); ++addr) { - tmp << addr->toText() << " "; + for (AddressContainer::const_iterator addr = addrs_.begin(); + addr != addrs_.end(); ++addr) { + tmp << *addr << " "; } return tmp.str(); } diff --git a/src/lib/dhcp/option6_client_fqdn.cc b/src/lib/dhcp/option6_client_fqdn.cc index 761acae1d1..d9eb0b230b 100644 --- a/src/lib/dhcp/option6_client_fqdn.cc +++ b/src/lib/dhcp/option6_client_fqdn.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2014 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 @@ -188,7 +188,6 @@ setDomainName(const std::string& domain_name, } else { try { domain_name_.reset(new isc::dns::Name(name, true)); - domain_name_type_ = name_type; } catch (const Exception& ex) { isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '" @@ -197,6 +196,8 @@ setDomainName(const std::string& domain_name, } } + + domain_name_type_ = name_type; } void diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc index 825a5bf130..a5751ddf11 100644 --- a/src/lib/dhcp/option6_ia.cc +++ b/src/lib/dhcp/option6_ia.cc @@ -72,12 +72,12 @@ void Option6IA::unpack(OptionBufferConstIter begin, if (distance(begin, end) < OPTION6_IA_LEN) { isc_throw(OutOfRange, "Option " << type_ << " truncated"); } - iaid_ = readUint32( &(*begin) ); + iaid_ = readUint32(&(*begin), distance(begin, end)); begin += sizeof(uint32_t); - t1_ = readUint32( &(*begin) ); + t1_ = readUint32(&(*begin), distance(begin, end)); begin += sizeof(uint32_t); - t2_ = readUint32( &(*begin) ); + t2_ = readUint32(&(*begin), distance(begin, end)); begin += sizeof(uint32_t); unpackOptions(OptionBuffer(begin, end)); diff --git a/src/lib/dhcp/option6_iaaddr.cc b/src/lib/dhcp/option6_iaaddr.cc index 39efa61824..9e02be7876 100644 --- a/src/lib/dhcp/option6_iaaddr.cc +++ b/src/lib/dhcp/option6_iaaddr.cc @@ -37,7 +37,7 @@ Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr valid_(valid) { setEncapsulatedSpace("dhcp6"); if (!addr.isV6()) { - isc_throw(isc::BadValue, addr_.toText() << " is not an IPv6 address"); + isc_throw(isc::BadValue, addr_ << " is not an IPv6 address"); } } @@ -57,8 +57,7 @@ void Option6IAAddr::pack(isc::util::OutputBuffer& buf) { buf.writeUint16(len() - getHeaderLen()); if (!addr_.isV6()) { - isc_throw(isc::BadValue, addr_.toText() - << " is not an IPv6 address"); + isc_throw(isc::BadValue, addr_ << " is not an IPv6 address"); } buf.writeData(&addr_.toBytes()[0], isc::asiolink::V6ADDRESS_LEN); @@ -79,10 +78,10 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin, addr_ = IOAddress::fromBytes(AF_INET6, &(*begin)); begin += V6ADDRESS_LEN; - preferred_ = readUint32( &(*begin) ); + preferred_ = readUint32(&(*begin), distance(begin, end)); begin += sizeof(uint32_t); - valid_ = readUint32( &(*begin) ); + valid_ = readUint32(&(*begin), distance(begin, end)); begin += sizeof(uint32_t); unpackOptions(OptionBuffer(begin, end)); @@ -93,7 +92,7 @@ std::string Option6IAAddr::toText(int indent /* =0 */) { for (int i=0; i<indent; i++) tmp << " "; - tmp << "type=" << type_ << "(IAADDR) addr=" << addr_.toText() + tmp << "type=" << type_ << "(IAADDR) addr=" << addr_ << ", preferred-lft=" << preferred_ << ", valid-lft=" << valid_ << endl; diff --git a/src/lib/dhcp/option6_iaprefix.cc b/src/lib/dhcp/option6_iaprefix.cc index a7776d7cfe..357942f8a5 100644 --- a/src/lib/dhcp/option6_iaprefix.cc +++ b/src/lib/dhcp/option6_iaprefix.cc @@ -51,7 +51,7 @@ Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator beg void Option6IAPrefix::pack(isc::util::OutputBuffer& buf) { if (!addr_.isV6()) { - isc_throw(isc::BadValue, addr_.toText() << " is not an IPv6 address"); + isc_throw(isc::BadValue, addr_ << " is not an IPv6 address"); } buf.writeUint16(type_); @@ -76,10 +76,10 @@ void Option6IAPrefix::unpack(OptionBuffer::const_iterator begin, isc_throw(OutOfRange, "Option " << type_ << " truncated"); } - preferred_ = readUint32( &(*begin) ); + preferred_ = readUint32(&(*begin), distance(begin, end)); begin += sizeof(uint32_t); - valid_ = readUint32( &(*begin) ); + valid_ = readUint32(&(*begin), distance(begin, end)); begin += sizeof(uint32_t); prefix_len_ = *begin; @@ -98,7 +98,7 @@ std::string Option6IAPrefix::toText(int indent /* =0 */) { for (int i=0; i<indent; i++) tmp << " "; - tmp << "type=" << type_ << "(IAPREFIX) prefix=" << addr_.toText() << "/" + tmp << "type=" << type_ << "(IAPREFIX) prefix=" << addr_ << "/" << prefix_len_ << ", preferred-lft=" << preferred_ << ", valid-lft=" << valid_ << endl; diff --git a/src/lib/dhcp/option6_iaprefix.h b/src/lib/dhcp/option6_iaprefix.h index 0a790beddc..63ab9f735c 100644 --- a/src/lib/dhcp/option6_iaprefix.h +++ b/src/lib/dhcp/option6_iaprefix.h @@ -90,7 +90,8 @@ public: /// sets address in this option. /// - /// @param addr address to be sent in this option + /// @param prefix prefix to be sent in this option + /// @param length prefix length void setPrefix(const isc::asiolink::IOAddress& prefix, uint8_t length) { addr_ = prefix; prefix_len_ = length; } diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc index 278c0c9bb7..0709d20110 100644 --- a/src/lib/dhcp/option_custom.cc +++ b/src/lib/dhcp/option_custom.cc @@ -54,7 +54,7 @@ OptionCustom::addArrayDataField(const asiolink::IOAddress& address) { if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) || (address.isV6() && definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) { isc_throw(BadDataTypeCast, "invalid address specified " - << address.toText() << ". Expected a valid IPv" + << address << ". Expected a valid IPv" << (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ? "4" : "6") << " address."); } @@ -375,7 +375,7 @@ OptionCustom::dataFieldToText(const OptionDataType data_type, break; case OPT_IPV4_ADDRESS_TYPE: case OPT_IPV6_ADDRESS_TYPE: - text << readAddress(index).toText(); + text << readAddress(index); break; case OPT_FQDN_TYPE: text << readFqdn(index); @@ -443,7 +443,7 @@ OptionCustom::writeAddress(const asiolink::IOAddress& address, if ((address.isV4() && buffers_[index].size() != V4ADDRESS_LEN) || (address.isV6() && buffers_[index].size() != V6ADDRESS_LEN)) { isc_throw(BadDataTypeCast, "invalid address specified " - << address.toText() << ". Expected a valid IPv" + << address << ". Expected a valid IPv" << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6") << " address."); } diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h index d9d8a52379..fb4d101a15 100644 --- a/src/lib/dhcp/option_data_types.h +++ b/src/lib/dhcp/option_data_types.h @@ -298,12 +298,12 @@ public: case 2: // Calling readUint16 works either for unsigned // or signed types. - value = isc::util::readUint16(&(*buf.begin())); + value = isc::util::readUint16(&(*buf.begin()), buf.size()); break; case 4: // Calling readUint32 works either for unsigned // or signed types. - value = isc::util::readUint32(&(*buf.begin())); + value = isc::util::readUint32(&(*buf.begin()), buf.size()); break; default: // This should not happen because we made checks on data types @@ -331,11 +331,11 @@ public: break; case 2: buf.resize(buf.size() + 2); - isc::util::writeUint16(static_cast<uint16_t>(value), &buf[buf.size() - 2]); + isc::util::writeUint16(static_cast<uint16_t>(value), &buf[buf.size() - 2], 2); break; case 4: buf.resize(buf.size() + 4); - isc::util::writeUint32(static_cast<uint32_t>(value), &buf[buf.size() - 4]); + isc::util::writeUint32(static_cast<uint32_t>(value), &buf[buf.size() - 4], 4); break; default: // The cases above cover whole range of possible data lengths because diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc index be1a5e7890..98765e6313 100644 --- a/src/lib/dhcp/option_definition.cc +++ b/src/lib/dhcp/option_definition.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012-2014 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 @@ -399,18 +399,49 @@ OptionDefinition::haveVendor6Format() const { return (getType() == OPT_UINT32_TYPE && !getEncapsulatedSpace().empty()); } +bool +OptionDefinition::convertToBool(const std::string& value_str) const { + // Case insensitve check that the input is one of: "true" or "false". + if (boost::iequals(value_str, "true")) { + return (true); + + } else if (boost::iequals(value_str, "false")) { + return (false); + + } + + // The input string is neither "true" nor "false", so let's check + // if it is not an integer wrapped in a string. + int result; + try { + result = boost::lexical_cast<int>(value_str); + + } catch (const boost::bad_lexical_cast&) { + isc_throw(BadDataTypeCast, "unable to covert the value '" + << value_str << "' to boolean data type"); + } + // The boolean value is encoded in DHCP option as 0 or 1. Therefore, + // we only allow a user to specify those values for options which + // have boolean fields. + if (result != 1 && result != 0) { + isc_throw(BadDataTypeCast, "unable to convert '" << value_str + << "' to boolean data type"); + } + return (static_cast<bool>(result)); +} + template<typename T> T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) const { - // Lexical cast in case of our data types make sense only - // for uintX_t, intX_t and bool type. - if (!OptionDataTypeTraits<T>::integer_type && - OptionDataTypeTraits<T>::type != OPT_BOOLEAN_TYPE) { + // The lexical cast should be attempted when converting to an integer + // value only. + if (!OptionDataTypeTraits<T>::integer_type) { isc_throw(BadDataTypeCast, - "unable to do lexical cast to non-integer and" - << " non-boolean data type"); + "must not convert '" << value_str + << "' to non-integer data type"); } + // We use the 64-bit value here because it has wider range than // any other type we use here and it allows to detect out of // bounds conditions e.g. negative value specified for uintX_t @@ -419,23 +450,19 @@ OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) int64_t result = 0; try { result = boost::lexical_cast<int64_t>(value_str); - } catch (const boost::bad_lexical_cast& ex) { - // Prepare error message here. - std::string data_type_str = "boolean"; - if (OptionDataTypeTraits<T>::integer_type) { - data_type_str = "integer"; - } - isc_throw(BadDataTypeCast, "unable to do lexical cast to " - << data_type_str << " data type for value " - << value_str << ": " << ex.what()); + + } catch (const boost::bad_lexical_cast&) { + isc_throw(BadDataTypeCast, "unable to convert the value '" + << value_str << "' to integer data type"); } - // Perform range checks for integer values only (exclude bool values). + // Perform range checks. if (OptionDataTypeTraits<T>::integer_type) { if (result > numeric_limits<T>::max() || result < numeric_limits<T>::min()) { - isc_throw(BadDataTypeCast, "unable to do lexical cast for value " - << value_str << ". This value is expected to be" - << " in the range of " << numeric_limits<T>::min() + isc_throw(BadDataTypeCast, "unable to convert '" + << value_str << "' to numeric type. This value is " + " expected to be in the range of " + << numeric_limits<T>::min() << ".." << numeric_limits<T>::max()); } } @@ -458,8 +485,7 @@ OptionDefinition::writeToBuffer(const std::string& value, // That way we actually waste 7 bits but it seems to be the // simpler way to encode boolean. // @todo Consider if any other encode methods can be used. - OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value), - buf); + OptionDataTypeUtil::writeBool(convertToBool(value), buf); return; case OPT_INT8_TYPE: OptionDataTypeUtil::writeInt<uint8_t> @@ -497,7 +523,7 @@ OptionDefinition::writeToBuffer(const std::string& value, asiolink::IOAddress address(value); if (!address.isV4() && !address.isV6()) { isc_throw(BadDataTypeCast, "provided address " - << address.toText() + << address << " is not a valid IPv4 or IPv6 address."); } OptionDataTypeUtil::writeAddress(address, buf); diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h index 25ce5e24e5..73c0cdfd96 100644 --- a/src/lib/dhcp/option_definition.h +++ b/src/lib/dhcp/option_definition.h @@ -573,19 +573,38 @@ private: return (type == type_); } + /// @brief Converts a string value to a boolean value. + /// + /// This function converts the value represented as string to a boolean + /// value. The following conversions are acceptable: + /// - "true" => true + /// - "false" => false + /// - "1" => true + /// - "0" => false + /// The first two conversions are case insensitive, so as conversions from + /// strings such as "TRUE", "trUE" etc. will be accepted. Note that the + /// only acceptable integer values, carried as strings are: "0" and "1". + /// For other values, e.g. "2", "3" etc. an exception will be thrown + /// during conversion. + /// + /// @param value_str Input value. + /// + /// @return boolean representation of the string specified as the parameter. + /// @throw isc::dhcp::BadDataTypeCast if failed to perform the conversion. + bool convertToBool(const std::string& value_str) const; + /// @brief Perform lexical cast of the value and validate its range. /// /// This function performs lexical cast of a string value to integer - /// or boolean value and checks if the resulting value is within a - /// range of a target type. Note that range checks are not performed - /// on boolean values. The target type should be one of the supported - /// integer types or bool. + /// value and checks if the resulting value is within a range of a + /// target type. The target type should be one of the supported + /// integer types. /// /// @param value_str input value given as string. - /// @tparam T target type for lexical cast. + /// @tparam T target integer type for lexical cast. /// - /// @return cast value. - /// @throw BadDataTypeCast if cast was not successful. + /// @return Integer value after conversion from the string. + /// @throw isc::dhcp::BadDataTypeCast if conversion was not successful. template<typename T> T lexicalCastWithRangeCheck(const std::string& value_str) const; diff --git a/src/lib/dhcp/option_int.h b/src/lib/dhcp/option_int.h index cbdbcb0a3a..f11b4eba1f 100644 --- a/src/lib/dhcp/option_int.h +++ b/src/lib/dhcp/option_int.h @@ -142,10 +142,12 @@ public: value_ = *begin; break; case 2: - value_ = isc::util::readUint16(&(*begin)); + value_ = isc::util::readUint16(&(*begin), + std::distance(begin, end)); break; case 4: - value_ = isc::util::readUint32(&(*begin)); + value_ = isc::util::readUint32(&(*begin), + std::distance(begin, end)); break; default: isc_throw(dhcp::InvalidDataType, "non-integer type"); diff --git a/src/lib/dhcp/option_int_array.h b/src/lib/dhcp/option_int_array.h index e0e93562d4..62e121b089 100644 --- a/src/lib/dhcp/option_int_array.h +++ b/src/lib/dhcp/option_int_array.h @@ -201,10 +201,12 @@ public: values_.push_back(*begin); break; case 2: - values_.push_back(isc::util::readUint16(&(*begin))); + values_.push_back(isc::util::readUint16(&(*begin), + std::distance(begin, end))); break; case 4: - values_.push_back(isc::util::readUint32(&(*begin))); + values_.push_back(isc::util::readUint32(&(*begin), + std::distance(begin, end))); break; default: isc_throw(dhcp::InvalidDataType, "non-integer type"); diff --git a/src/lib/dhcp/option_vendor.cc b/src/lib/dhcp/option_vendor.cc index 878b534a50..0d5c550ba4 100644 --- a/src/lib/dhcp/option_vendor.cc +++ b/src/lib/dhcp/option_vendor.cc @@ -54,7 +54,7 @@ void OptionVendor::unpack(OptionBufferConstIter begin, << ", length=" << distance(begin, end)); } - vendor_id_ = isc::util::readUint32(&(*begin)); + vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end)); OptionBuffer vendor_buffer(begin +4, end); diff --git a/src/lib/dhcp/option_vendor.h b/src/lib/dhcp/option_vendor.h index 5b43508d05..bb8395cce5 100644 --- a/src/lib/dhcp/option_vendor.h +++ b/src/lib/dhcp/option_vendor.h @@ -25,6 +25,11 @@ namespace isc { namespace dhcp { +/// Indexes for fields in vendor-class (17) DHCPv6 option +const int VENDOR_CLASS_ENTERPRISE_ID_INDEX = 0; +const int VENDOR_CLASS_DATA_LEN_INDEX = 1; +const int VENDOR_CLASS_STRING_INDEX = 2; + /// @brief This class represents vendor-specific information option. /// /// As specified in RFC3925, the option formatting is slightly different @@ -72,7 +77,7 @@ public: /// @brief Sets enterprise identifier /// - /// @param value vendor identifier + /// @param vendor_id vendor identifier void setVendorId(const uint32_t vendor_id) { vendor_id_ = vendor_id; } /// @brief Returns enterprise identifier diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc index e9c2093974..b641a03a40 100644 --- a/src/lib/dhcp/pkt4.cc +++ b/src/lib/dhcp/pkt4.cc @@ -278,8 +278,8 @@ void Pkt4::repack() { std::string Pkt4::toText() { stringstream tmp; - tmp << "localAddr=" << local_addr_.toText() << ":" << local_port_ - << " remoteAddr=" << remote_addr_.toText() + tmp << "localAddr=" << local_addr_ << ":" << local_port_ + << " remoteAddr=" << remote_addr_ << ":" << remote_port_ << ", msgtype=" << static_cast<int>(getType()) << ", transid=0x" << hex << transid_ << dec << endl; @@ -480,7 +480,17 @@ Pkt4::isRelayed() const { << static_cast<int>(getHops()) << ". Valid values" " are: (giaddr = 0 and hops = 0) or (giaddr != 0 and" "hops != 0)"); +} + +bool Pkt4::inClass(const std::string& client_class) { + return (classes_.find(client_class) != classes_.end()); +} +void +Pkt4::addClass(const std::string& client_class) { + if (classes_.find(client_class) == classes_.end()) { + classes_.insert(client_class); + } } } // end of namespace isc::dhcp diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h index 3d9e0abf45..485d28a9dc 100644 --- a/src/lib/dhcp/pkt4.h +++ b/src/lib/dhcp/pkt4.h @@ -26,6 +26,7 @@ #include <iostream> #include <vector> +#include <set> #include <time.h> @@ -52,6 +53,9 @@ public: /// to check whether client requested broadcast response. const static uint16_t FLAG_BROADCAST_MASK = 0x8000; + /// Container for storing client classes + typedef std::set<std::string> Classes; + /// Constructor, used in replying to a message. /// /// @param msg_type type of message (e.g. DHCPDISOVER=1) @@ -550,6 +554,37 @@ public: /// performance). std::vector<uint8_t> data_; + /// @brief Checks whether a client belongs to a given class + /// + /// @param client_class name of the class + /// @return true if belongs + bool inClass(const std::string& client_class); + + /// @brief Adds packet to a specified class + /// + /// A packet can be added to the same class repeatedly. Any additional + /// attempts to add to a class the packet already belongs to, will be + /// ignored silently. + /// + /// @note It is a matter of naming convention. Conceptually, the server + /// processes a stream of packets, with some packets belonging to given + /// classes. From that perspective, this method adds a packet to specifed + /// class. Implementation wise, it looks the opposite - the class name + /// is added to the packet. Perhaps the most appropriate name for this + /// method would be associateWithClass()? But that seems overly long, + /// so I decided to stick with addClass(). + /// + /// @param client_class name of the class to be added + void addClass(const std::string& client_class); + + /// @brief Classes this packet belongs to. + /// + /// This field is public, so the code outside of Pkt4 class can iterate over + /// existing classes. Having it public also solves the problem of returned + /// reference lifetime. It is preferred to use @ref inClass and @ref addClass + /// should be used to operate on this field. + Classes classes_; + private: /// @brief Generic method that validates and sets HW address. diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc index 308880edd8..fdbd18cd0e 100644 --- a/src/lib/dhcp/pkt6.cc +++ b/src/lib/dhcp/pkt6.cc @@ -453,8 +453,8 @@ Pkt6::unpackTCP() { std::string Pkt6::toText() { stringstream tmp; - tmp << "localAddr=[" << local_addr_.toText() << "]:" << local_port_ - << " remoteAddr=[" << remote_addr_.toText() + tmp << "localAddr=[" << local_addr_ << "]:" << local_port_ + << " remoteAddr=[" << remote_addr_ << "]:" << remote_port_ << endl; tmp << "msgtype=" << static_cast<int>(msg_type_) << ", transid=0x" << hex << transid_ << dec << endl; @@ -586,6 +586,17 @@ void Pkt6::copyRelayInfo(const Pkt6Ptr& question) { } } +bool +Pkt6::inClass(const std::string& client_class) { + return (classes_.find(client_class) != classes_.end()); +} + +void +Pkt6::addClass(const std::string& client_class) { + if (classes_.find(client_class) == classes_.end()) { + classes_.insert(client_class); + } +} } // end of isc::dhcp namespace } // end of isc namespace diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h index 702c424864..db80fb9996 100644 --- a/src/lib/dhcp/pkt6.h +++ b/src/lib/dhcp/pkt6.h @@ -23,6 +23,7 @@ #include <boost/shared_ptr.hpp> #include <iostream> +#include <set> #include <time.h> @@ -47,6 +48,9 @@ public: TCP = 1 // there are TCP DHCPv6 packets (bulk leasequery, failover) }; + /// Container for storing client classes + typedef std::set<std::string> Classes; + /// @brief defines relay search pattern /// /// Defines order in which options are searched in a message that @@ -426,6 +430,35 @@ public: /// data format change etc. OptionBuffer data_; + /// @brief Checks whether a client belongs to a given class + /// + /// @param client_class name of the class + /// @return true if belongs + bool inClass(const std::string& client_class); + + /// @brief Adds packet to a specified class + /// + /// A packet can be added to the same class repeatedly. Any additional + /// attempts to add to a class the packet already belongs to, will be + /// ignored silently. + /// + /// @note It is a matter of naming convention. Conceptually, the server + /// processes a stream of packets, with some packets belonging to given + /// classes. From that perspective, this method adds a packet to specifed + /// class. Implementation wise, it looks the opposite - the class name + /// is added to the packet. Perhaps the most appropriate name for this + /// method would be associateWithClass()? But that seems overly long, + /// so I decided to stick with addClass(). + /// + /// @param client_class name of the class to be added + void addClass(const std::string& client_class); + + /// @brief Classes this packet belongs to. + /// + /// This field is public, so code can iterate over existing classes. + /// Having it public also solves the problem of returned reference lifetime. + Classes classes_; + protected: /// Builds on wire packet for TCP transmission. /// diff --git a/src/lib/dhcp/pkt_filter.cc b/src/lib/dhcp/pkt_filter.cc index 9c1995df1b..1eef9c7ca7 100644 --- a/src/lib/dhcp/pkt_filter.cc +++ b/src/lib/dhcp/pkt_filter.cc @@ -29,7 +29,7 @@ PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr, int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { isc_throw(SocketConfigError, "failed to create fallback socket for" - " address " << addr.toText() << ", port " << port + " address " << addr << ", port " << port << ", reason: " << strerror(errno)); } // Bind the socket to a specified address and port. @@ -44,7 +44,7 @@ PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr, // Remember to close the socket if we failed to bind it. close(sock); isc_throw(SocketConfigError, "failed to bind fallback socket to" - " address " << addr.toText() << ", port " << port + " address " << addr << ", port " << port << ", reason: " << strerror(errno) << " - is another DHCP server running?"); } @@ -54,7 +54,7 @@ PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr, if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) { close(sock); isc_throw(SocketConfigError, "failed to set SO_NONBLOCK option on the" - " fallback socket, bound to " << addr.toText() << ", port " + " fallback socket, bound to " << addr << ", port " << port << ", reason: " << strerror(errno)); } // Successfully created and bound a fallback socket. Return a descriptor. diff --git a/src/lib/dhcp/pkt_filter_inet.cc b/src/lib/dhcp/pkt_filter_inet.cc index 1694798538..1af39705f6 100644 --- a/src/lib/dhcp/pkt_filter_inet.cc +++ b/src/lib/dhcp/pkt_filter_inet.cc @@ -79,7 +79,8 @@ PktFilterInet::openSocket(const Iface& iface, if (bind(sock, (struct sockaddr *)&addr4, sizeof(addr4)) < 0) { close(sock); - isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to " << addr.toText() + isc_throw(SocketConfigError, "Failed to bind socket " << sock + << " to " << addr << "/port=" << port); } diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h index 9f6455338f..6611f199e6 100644 --- a/src/lib/dhcp/std_option_defs.h +++ b/src/lib/dhcp/std_option_defs.h @@ -160,7 +160,7 @@ const OptionDefParams OPTION_DEF_PARAMS4[] = { { "dhcp-rebinding-time", DHO_DHCP_REBINDING_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" }, { "vendor-class-identifier", DHO_VENDOR_CLASS_IDENTIFIER, - OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" }, + OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, { "dhcp-client-identifier", DHO_DHCP_CLIENT_IDENTIFIER, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" }, { "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, @@ -230,7 +230,8 @@ RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE); // status-code RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE); // vendor-class -RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE); +RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_UINT16_TYPE, + OPT_STRING_TYPE); /// Standard DHCPv6 option definitions. /// diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc index 5d055424c4..b95b4de1da 100644 --- a/src/lib/dhcp/tests/iface_mgr_unittest.cc +++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc @@ -269,6 +269,7 @@ public: } } } + }; /// @brief A test fixture class for IfaceMgr. @@ -427,10 +428,10 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) { cout << " pkt = new Pkt6(" << pkt->data_len_ << ");" << endl; cout << " pkt->remote_port_ = " << pkt-> remote_port_ << ";" << endl; cout << " pkt->remote_addr_ = IOAddress(\"" - << pkt->remote_addr_.toText() << "\");" << endl; + << pkt->remote_addr_ << "\");" << endl; cout << " pkt->local_port_ = " << pkt-> local_port_ << ";" << endl; cout << " pkt->local_addr_ = IOAddress(\"" - << pkt->local_addr_.toText() << "\");" << endl; + << pkt->local_addr_ << "\");" << endl; cout << " pkt->ifindex_ = " << pkt->ifindex_ << ";" << endl; cout << " pkt->iface_ = \"" << pkt->iface_ << "\";" << endl; @@ -1014,7 +1015,7 @@ TEST_F(IfaceMgrTest, sendReceive6) { EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0], rcvPkt->data_.size())); - EXPECT_EQ(sendPkt->getRemoteAddr().toText(), rcvPkt->getRemoteAddr().toText()); + EXPECT_EQ(sendPkt->getRemoteAddr(), rcvPkt->getRemoteAddr()); // since we opened 2 sockets on the same interface and none of them is multicast, // none is preferred over the other for sending data, so we really should not @@ -1503,6 +1504,43 @@ TEST_F(IfaceMgrTest, openSocket4ErrorHandler) { } +// This test verifies that the function correctly checks that the v4 socket is +// open and bound to a specific address. +TEST_F(IfaceMgrTest, hasOpenSocketForAddress4) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + // Use the custom packet filter object. This object mimics the socket + // opening operation - the real socket is not open. + boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter()); + ASSERT_TRUE(custom_packet_filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter)); + + // Simulate opening sockets using the dummy packet filter. + ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL)); + + // Expect that the sockets are open on both eth0 and eth1. + ASSERT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size()); + ASSERT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size()); + // Socket shouldn't have been opened on loopback. + ASSERT_TRUE(ifacemgr.getIface("lo")->getSockets().empty()); + + // Check that there are sockets bound to addresses that we have + // set for interfaces. + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("192.0.2.3"))); + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("10.0.0.1"))); + // Check that there is no socket for the address which is not + // configured on any interface. + EXPECT_FALSE(ifacemgr.hasOpenSocket(IOAddress("10.1.1.1"))); + + // Check that v4 sockets are open, but no v6 socket is open. + EXPECT_TRUE(ifacemgr.hasOpenSocket(AF_INET)); + EXPECT_FALSE(ifacemgr.hasOpenSocket(AF_INET6)); + +} + // This test checks that the sockets are open and bound to link local addresses // only, if unicast addresses are not specified. TEST_F(IfaceMgrTest, openSockets6LinkLocal) { @@ -1830,32 +1868,6 @@ TEST_F(IfaceMgrTest, openSockets6NoIfaces) { EXPECT_FALSE(socket_open); } -// Test that exception is thrown when trying to bind a new socket to the port -// and address which is already in use by another socket. -TEST_F(IfaceMgrTest, openSockets6NoErrorHandler) { - NakedIfaceMgr ifacemgr; - - // Remove all real interfaces and create a set of dummy interfaces. - ifacemgr.createIfaces(); - - boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); - ASSERT_TRUE(filter); - ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); - - // Open socket on eth0. The openSockets6 should detect that this - // socket has been already open and an attempt to open another socket - // and bind to this address and port should fail. - ASSERT_NO_THROW(ifacemgr.openSocket("eth0", - IOAddress("fe80::3a60:77ff:fed5:cdef"), - DHCP6_SERVER_PORT)); - - // The function throws an exception when it tries to open a socket - // and bind it to the address in use. - EXPECT_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT), - isc::dhcp::SocketConfigError); - -} - // Test that the external error handler is called when trying to bind a new // socket to the address and port being in use. The sockets on the other // interfaces should open just fine. @@ -1897,6 +1909,41 @@ TEST_F(IfaceMgrTest, openSocket6ErrorHandler) { } +// This test verifies that the function correctly checks that the v6 socket is +// open and bound to a specific address. +TEST_F(IfaceMgrTest, hasOpenSocketForAddress6) { + NakedIfaceMgr ifacemgr; + + // Remove all real interfaces and create a set of dummy interfaces. + ifacemgr.createIfaces(); + + boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub()); + ASSERT_TRUE(filter); + ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); + + // Simulate opening sockets using the dummy packet filter. + bool success = false; + ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT)); + EXPECT_TRUE(success); + + // Make sure that the sockets are bound as expected. + ASSERT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef")); + EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd")); + + // There should be v6 sockets only, no v4 sockets. + EXPECT_TRUE(ifacemgr.hasOpenSocket(AF_INET6)); + EXPECT_FALSE(ifacemgr.hasOpenSocket(AF_INET)); + + // Check that there are sockets bound to the addresses we have configured + // for interfaces. + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:fed5:cdef"))); + EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:fed5:abcd"))); + // Check that there is no socket bound to the address which hasn't been + // configured on any interface. + EXPECT_FALSE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:feed:1"))); + +} + // Test the Iface structure itself TEST_F(IfaceMgrTest, iface) { boost::scoped_ptr<Iface> iface; @@ -2289,7 +2336,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) { const Iface::AddressCollection& addrs = i->getAddresses(); for (Iface::AddressCollection::const_iterator a= addrs.begin(); a != addrs.end(); ++a) { - cout << a->toText() << " "; + cout << *a << " "; } cout << endl; } @@ -2347,7 +2394,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) { << " address on " << detected->getFullName() << " interface." << endl; FAIL(); } - cout << "Address " << addr->toText() << " on interface " << detected->getFullName() + cout << "Address " << *addr << " on interface " << detected->getFullName() << " matched with 'ifconfig -a' output." << endl; } } @@ -2543,15 +2590,19 @@ TEST_F(IfaceMgrTest, detectIfaces) { } volatile bool callback_ok; +volatile bool callback2_ok; void my_callback(void) { - cout << "Callback triggered." << endl; callback_ok = true; } -TEST_F(IfaceMgrTest, controlSession) { - // Tests if extra control socket and its callback can be passed and - // it is supported properly by receive4() method. +void my_callback2(void) { + callback2_ok = true; +} + +// Tests if a single external socket and its callback can be passed and +// it is supported properly by receive4() method. +TEST_F(IfaceMgrTest, SingleExternalSocket4) { callback_ok = false; @@ -2560,7 +2611,7 @@ TEST_F(IfaceMgrTest, controlSession) { // Create pipe and register it as extra socket int pipefd[2]; EXPECT_TRUE(pipe(pipefd) == 0); - EXPECT_NO_THROW(ifacemgr->set_session_socket(pipefd[0], my_callback)); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); Pkt4Ptr pkt4; ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); @@ -2588,6 +2639,305 @@ TEST_F(IfaceMgrTest, controlSession) { close(pipefd[0]); } +// Tests if multiple external sockets and their callbacks can be passed and +// it is supported properly by receive4() method. +TEST_F(IfaceMgrTest, MiltipleExternalSockets4) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + Pkt4Ptr pkt4; + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // Our callbacks should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // Now, send some data over the first pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // Read the data sent, because our test callbacks are too dumb to actually + // do it. We don't care about the content read, because we're testing + // the callbacks, not pipes. + char buf[80]; + EXPECT_EQ(38, read(pipefd[0], buf, 80)); + + // Clear the status... + callback_ok = false; + callback2_ok = false; + + // And try again, using the second pipe + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + +// Tests if existing external socket can be deleted and that such deletion does +// not affect any other existing sockets. Tests uses receive4() +TEST_F(IfaceMgrTest, DeleteExternalSockets4) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + // Now delete the first session socket + EXPECT_NO_THROW(ifacemgr->deleteExternalSocket(pipefd[0])); + + // Now check whether the second callback is still functional + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + Pkt4Ptr pkt4; + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt4); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // Let's reset the status + callback_ok = false; + callback2_ok = false; + + // Now let's send something over the first callback that was unregistered. + // We should NOT receive any callback. + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // Now check that the first callback is NOT called. + ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1)); + EXPECT_FALSE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + + +// Tests if a single external socket and its callback can be passed and +// it is supported properly by receive6() method. +TEST_F(IfaceMgrTest, SingleExternalSocket6) { + + callback_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // Our callback should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // Now, send some data over pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); +} + +// Tests if multiple external sockets and their callbacks can be passed and +// it is supported properly by receive6() method. +TEST_F(IfaceMgrTest, MiltipleExternalSockets6) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // Our callbacks should not be called this time (there was no data) + EXPECT_FALSE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // Now, send some data over the first pipe (38 bytes) + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_TRUE(callback_ok); + EXPECT_FALSE(callback2_ok); + + // Read the data sent, because our test callbacks are too dumb to actually + // do it. We don't care about the content read, because we're testing + // the callbacks, not pipes. + char buf[80]; + EXPECT_EQ(38, read(pipefd[0], buf, 80)); + + // Clear the status... + callback_ok = false; + callback2_ok = false; + + // And try again, using the second pipe + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + +// Tests if existing external socket can be deleted and that such deletion does +// not affect any other existing sockets. Tests uses receive6() +TEST_F(IfaceMgrTest, DeleteExternalSockets6) { + + callback_ok = false; + callback2_ok = false; + + scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr()); + + // Create first pipe and register it as extra socket + int pipefd[2]; + EXPECT_TRUE(pipe(pipefd) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback)); + + // Let's create a second pipe and register it as well + int secondpipe[2]; + EXPECT_TRUE(pipe(secondpipe) == 0); + EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2)); + + // Now delete the first session socket + EXPECT_NO_THROW(ifacemgr->deleteExternalSocket(pipefd[0])); + + // Now check whether the second callback is still functional + EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38)); + + // ... and repeat + Pkt6Ptr pkt6; + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + + // IfaceMgr should not process control socket data as incoming packets + EXPECT_FALSE(pkt6); + + // There was some data, so this time callback should be called + EXPECT_FALSE(callback_ok); + EXPECT_TRUE(callback2_ok); + + // Let's reset the status + callback_ok = false; + callback2_ok = false; + + // Now let's send something over the first callback that was unregistered. + // We should NOT receive any callback. + EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38)); + + // Now check that the first callback is NOT called. + ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1)); + EXPECT_FALSE(callback_ok); + + // close both pipe ends + close(pipefd[1]); + close(pipefd[0]); + + close(secondpipe[1]); + close(secondpipe[0]); +} + + // Test checks if the unicast sockets can be opened. // This test is now disabled, because there is no reliable way to test it. We // can't even use loopback, beacuse openSockets() skips loopback interface diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc index ec9dd9f291..4d645a45f4 100644 --- a/src/lib/dhcp/tests/libdhcp++_unittest.cc +++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc @@ -30,6 +30,9 @@ #include <dhcp/option_string.h> #include <dhcp/option_vendor.h> #include <util/buffer.h> +#include <util/encode/hex.h> + +#include <boost/pointer_cast.hpp> #include <gtest/gtest.h> @@ -890,7 +893,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) { typeid(OptionInt<uint32_t>)); LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_CLASS_IDENTIFIER, begin, end, - typeid(Option)); + typeid(OptionString)); LibDhcpTest::testStdOptionDefs4(DHO_DHCP_CLIENT_IDENTIFIER, begin, end, typeid(Option)); @@ -1122,4 +1125,45 @@ TEST_F(LibDhcpTest, stdOptionDefs6) { typeid(Option6AddrLst)); } +// tests whether v6 vendor-class option can be parsed properly. +TEST_F(LibDhcpTest, vendorClass6) { + + isc::dhcp::OptionCollection options; // Will store parsed option here + + // Exported from wireshark: vendor-class option with enterprise-id = 4491 + // and a single data entry containing "eRouter1.0" + string vendor_class_hex = "001000100000118b000a65526f75746572312e30"; + OptionBuffer bin; + + // Decode the hex string and store it in bin (which happens + // to be OptionBuffer format) + isc::util::encode::decodeHex(vendor_class_hex, bin); + + ASSERT_NO_THROW ({ + LibDHCP::unpackOptions6(bin, "dhcp6", options); + }); + + EXPECT_EQ(options.size(), 1); // There should be 1 option. + + // Option vendor-class should be there + ASSERT_FALSE(options.find(D6O_VENDOR_CLASS) == options.end()); + + // It should be of type OptionCustom + boost::shared_ptr<OptionCustom> vclass = + boost::dynamic_pointer_cast<OptionCustom> (options.begin()->second); + ASSERT_TRUE(vclass); + + // Let's investigate if the option content is correct + + // 3 fields expected: vendor-id, data-len and data + ASSERT_EQ(3, vclass->getDataFieldsNum()); + + EXPECT_EQ(4491, vclass->readInteger<uint32_t> + (VENDOR_CLASS_ENTERPRISE_ID_INDEX)); // vendor-id=4491 + EXPECT_EQ(10, vclass->readInteger<uint16_t> + (VENDOR_CLASS_DATA_LEN_INDEX)); // data len = 10 + EXPECT_EQ("eRouter1.0", vclass->readString + (VENDOR_CLASS_STRING_INDEX)); // data="eRouter1.0" } + +} // end of anonymous space diff --git a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc index cdd7c6476d..ae658d2ccb 100644 --- a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc +++ b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2014 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 @@ -615,6 +615,7 @@ TEST(Option4ClientFqdnTest, setDomainName) { // Empty domain name (partial). This should be successful. ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL)); EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType()); // Fully qualified domain-names must not be empty. EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL), diff --git a/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc index d644a2e662..dac38e1c99 100644 --- a/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc +++ b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2014 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 @@ -537,6 +537,7 @@ TEST(Option6ClientFqdnTest, setDomainName) { // Empty domain name (partial). This should be successful. ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL)); EXPECT_TRUE(option->getDomainName().empty()); + EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType()); // Fully qualified domain-names must not be empty. EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL), diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc index 4add2d8673..6b181e661a 100644 --- a/src/lib/dhcp/tests/option_custom_unittest.cc +++ b/src/lib/dhcp/tests/option_custom_unittest.cc @@ -667,7 +667,7 @@ TEST_F(OptionCustomTest, ipv4AddressDataArray) { for (int i = 0; i < 3; ++i) { IOAddress address("10.10.10.10"); ASSERT_NO_THROW(address = option->readAddress(i)); - EXPECT_EQ(addresses[i].toText(), address.toText()); + EXPECT_EQ(addresses[i], address); } // Check that it is ok if buffer length is not a multiple of IPv4 @@ -717,7 +717,7 @@ TEST_F(OptionCustomTest, ipv6AddressDataArray) { for (int i = 0; i < 3; ++i) { IOAddress address("fe80::4"); ASSERT_NO_THROW(address = option->readAddress(i)); - EXPECT_EQ(addresses[i].toText(), address.toText()); + EXPECT_EQ(addresses[i], address); } // Check that it is ok if buffer length is not a multiple of IPv6 @@ -1451,7 +1451,7 @@ TEST_F(OptionCustomTest, unpack) { for (int i = 0; i < 3; ++i) { IOAddress address("10.10.10.10"); ASSERT_NO_THROW(address = option->readAddress(i)); - EXPECT_EQ(addresses[i].toText(), address.toText()); + EXPECT_EQ(addresses[i], address); } // Remove all addresses we had added. We are going to replace @@ -1478,7 +1478,7 @@ TEST_F(OptionCustomTest, unpack) { for (int i = 0; i < 2; ++i) { IOAddress address("10.10.10.10"); ASSERT_NO_THROW(address = option->readAddress(i)); - EXPECT_EQ(addresses[i].toText(), address.toText()); + EXPECT_EQ(addresses[i], address); } } @@ -1513,7 +1513,7 @@ TEST_F(OptionCustomTest, initialize) { for (int i = 0; i < 3; ++i) { IOAddress address("fe80::4"); ASSERT_NO_THROW(address = option->readAddress(i)); - EXPECT_EQ(addresses[i].toText(), address.toText()); + EXPECT_EQ(addresses[i], address); } // Clear addresses we had previously added. @@ -1539,7 +1539,7 @@ TEST_F(OptionCustomTest, initialize) { for (int i = 0; i < 2; ++i) { IOAddress address("10.10.10.10"); ASSERT_NO_THROW(address = option->readAddress(i)); - EXPECT_EQ(addresses[i].toText(), address.toText()); + EXPECT_EQ(addresses[i], address); } } diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc index 717a330410..a6a33b268e 100644 --- a/src/lib/dhcp/tests/option_data_types_unittest.cc +++ b/src/lib/dhcp/tests/option_data_types_unittest.cc @@ -93,7 +93,7 @@ TEST_F(OptionDataTypesTest, readAddress) { // Check that the read address matches address that // we used as input. - EXPECT_EQ(address.toText(), address_out.toText()); + EXPECT_EQ(address, address_out); // Check that an attempt to read the buffer as IPv6 address // causes an error as the IPv6 address needs at least 16 bytes @@ -109,7 +109,7 @@ TEST_F(OptionDataTypesTest, readAddress) { address = asiolink::IOAddress("2001:db8:1:0::1"); writeAddress(address, buf); EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6)); - EXPECT_EQ(address.toText(), address_out.toText()); + EXPECT_EQ(address, address_out); // Truncate the buffer and expect an error to be reported when // trying to read it. diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc index 357ed562c8..1ba68a8a7b 100644 --- a/src/lib/dhcp/tests/option_definition_unittest.cc +++ b/src/lib/dhcp/tests/option_definition_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012-2014 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 @@ -663,6 +663,112 @@ TEST_F(OptionDefinitionTest, recordIAAddr6Tokenized) { EXPECT_EQ(5678, option_cast_v6->getValid()); } +// The purpose of this test is to verify that the definition for option +// that comprises a boolean value can be created and that this definition +// can be used to create and option with a single boolean value. +TEST_F(OptionDefinitionTest, boolValue) { + // The IP Forwarding option comprises one boolean value. + OptionDefinition opt_def("ip-forwarding", DHO_IP_FORWARDING, + "boolean"); + + OptionPtr option_v4; + // Use an option buffer which holds one value of 1 (true). + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + OptionBuffer(1, 1)); + ); + ASSERT_TRUE(typeid(*option_v4) == typeid(OptionCustom)); + // Validate parsed value in the received option. + boost::shared_ptr<OptionCustom> option_cast_v4 = + boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_TRUE(option_cast_v4->readBoolean()); + + // Repeat the test above, but set the value to 0 (false). + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + OptionBuffer(1, 0)); + ); + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_FALSE(option_cast_v4->readBoolean()); + + // Try to provide zero-length buffer. Expect exception. + EXPECT_THROW( + opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, OptionBuffer()), + InvalidOptionValue + ); + +} + +// The purpose of this test is to verify that definition for option that +// comprises single boolean value can be created and that this definition +// can be used to create an option holding a single boolean value. The +// boolean value is converted from a string which is expected to hold +// the following values: "true", "false", "1" or "0". For all other +// values exception should be thrown. +TEST_F(OptionDefinitionTest, boolTokenized) { + OptionDefinition opt_def("ip-forwarding", DHO_IP_FORWARDING, "boolean"); + + OptionPtr option_v4; + std::vector<std::string> values; + // Specify a value for the option instance being created. + values.push_back("true"); + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + ASSERT_TRUE(typeid(*option_v4) == typeid(OptionCustom)); + // Validate the value. + OptionCustomPtr option_cast_v4 = + boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_TRUE(option_cast_v4->readBoolean()); + + // Repeat the test but for "false" value this time. + values[0] = "false"; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + ASSERT_TRUE(typeid(*option_v4) == typeid(OptionCustom)); + // Validate the value. + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_FALSE(option_cast_v4->readBoolean()); + + // Check if that will work for numeric values. + values[0] = "0"; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + ASSERT_TRUE(typeid(*option_v4) == typeid(OptionCustom)); + // Validate the value. + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_FALSE(option_cast_v4->readBoolean()); + + // Swap numeric values and test if it works for "true" case. + values[0] = "1"; + ASSERT_NO_THROW( + option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, + values); + ); + ASSERT_TRUE(typeid(*option_v4) == typeid(OptionCustom)); + // Validate the value. + option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4); + EXPECT_TRUE(option_cast_v4->readBoolean()); + + // A conversion of non-numeric value to boolean should fail if + // this value is neither "true" nor "false". + values[0] = "garbage"; + EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, values), + isc::dhcp::BadDataTypeCast); + + // A conversion of numeric value to boolean should fail if this value + // is neither "0" nor "1". + values[0] = "2"; + EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, values), + isc::dhcp::BadDataTypeCast); + +} + // The purpose of this test is to verify that definition for option that // comprises single uint8 value can be created and that this definition // can be used to create an option with single uint8 value. @@ -672,7 +778,8 @@ TEST_F(OptionDefinitionTest, uint8) { OptionPtr option_v6; // Try to use correct buffer length = 1 byte. ASSERT_NO_THROW( - option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer(1, 1)); + option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, + OptionBuffer(1, 1)); ); ASSERT_TRUE(typeid(*option_v6) == typeid(OptionInt<uint8_t>)); // Validate the value. diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc index b1bb85275c..bfebe77dae 100644 --- a/src/lib/dhcp/tests/pkt4_unittest.cc +++ b/src/lib/dhcp/tests/pkt4_unittest.cc @@ -17,6 +17,7 @@ #include <asiolink/io_address.h> #include <dhcp/dhcp4.h> #include <dhcp/libdhcp++.h> +#include <dhcp/docsis3_option_defs.h> #include <dhcp/option_string.h> #include <dhcp/pkt4.h> #include <exceptions/exceptions.h> @@ -316,10 +317,10 @@ TEST_F(Pkt4Test, fixedFields) { EXPECT_EQ(dummySecs, pkt->getSecs()); EXPECT_EQ(dummyFlags, pkt->getFlags()); - EXPECT_EQ(dummyCiaddr.toText(), pkt->getCiaddr().toText()); - EXPECT_EQ(dummyYiaddr.toText(), pkt->getYiaddr().toText()); - EXPECT_EQ(dummySiaddr.toText(), pkt->getSiaddr().toText()); - EXPECT_EQ(dummyGiaddr.toText(), pkt->getGiaddr().toText()); + EXPECT_EQ(dummyCiaddr, pkt->getCiaddr()); + EXPECT_EQ(dummyYiaddr, pkt->getYiaddr()); + EXPECT_EQ(dummySiaddr, pkt->getSiaddr()); + EXPECT_EQ(dummyGiaddr, pkt->getGiaddr()); // Chaddr contains link-layer addr (MAC). It is no longer always 16 bytes // long and its length depends on hlen value (it is up to 16 bytes now). @@ -382,10 +383,10 @@ TEST_F(Pkt4Test, fixedFieldsUnpack) { EXPECT_EQ(dummySecs, pkt->getSecs()); EXPECT_EQ(dummyFlags, pkt->getFlags()); - EXPECT_EQ(dummyCiaddr.toText(), pkt->getCiaddr().toText()); - EXPECT_EQ(string("1.2.3.4"), pkt->getYiaddr().toText()); - EXPECT_EQ(string("192.0.2.255"), pkt->getSiaddr().toText()); - EXPECT_EQ(string("255.255.255.255"), pkt->getGiaddr().toText()); + EXPECT_EQ(dummyCiaddr, pkt->getCiaddr()); + EXPECT_EQ("1.2.3.4", pkt->getYiaddr().toText()); + EXPECT_EQ("192.0.2.255", pkt->getSiaddr().toText()); + EXPECT_EQ("255.255.255.255", pkt->getGiaddr().toText()); // chaddr is always 16 bytes long and contains link-layer addr (MAC) EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen)); @@ -818,7 +819,36 @@ TEST_F(Pkt4Test, isRelayed) { // should throw an exception. pkt.setGiaddr(IOAddress("0.0.0.0")); EXPECT_THROW(pkt.isRelayed(), isc::BadValue); +} +// Tests whether a packet can be assigned to a class and later +// checked if it belongs to a given class +TEST_F(Pkt4Test, clientClasses) { + Pkt4 pkt(DHCPOFFER, 1234); + + // Default values (do not belong to any class) + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + EXPECT_TRUE(pkt.classes_.empty()); + + // Add to the first class + pkt.addClass(DOCSIS3_CLASS_EROUTER); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + ASSERT_FALSE(pkt.classes_.empty()); + + // Add to a second class + pkt.addClass(DOCSIS3_CLASS_MODEM); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + + // Check that it's ok to add to the same class repeatedly + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + + // Check that the packet belongs to 'foo' + EXPECT_TRUE(pkt.inClass("foo")); } } // end of anonymous namespace diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc index e18b545cbd..17cb629b2b 100644 --- a/src/lib/dhcp/tests/pkt6_unittest.cc +++ b/src/lib/dhcp/tests/pkt6_unittest.cc @@ -22,6 +22,7 @@ #include <dhcp/option_int.h> #include <dhcp/option_int_array.h> #include <dhcp/pkt6.h> +#include <dhcp/docsis3_option_defs.h> #include <util/range_utilities.h> #include <boost/bind.hpp> @@ -779,4 +780,34 @@ TEST_F(Pkt6Test, getAnyRelayOption) { EXPECT_FALSE(opt); } +// Tests whether a packet can be assigned to a class and later +// checked if it belongs to a given class +TEST_F(Pkt6Test, clientClasses) { + Pkt6 pkt(DHCPV6_ADVERTISE, 1234); + + // Default values (do not belong to any class) + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + EXPECT_TRUE(pkt.classes_.empty()); + + // Add to the first class + pkt.addClass(DOCSIS3_CLASS_EROUTER); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + ASSERT_FALSE(pkt.classes_.empty()); + + // Add to a second class + pkt.addClass(DOCSIS3_CLASS_MODEM); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER)); + EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM)); + + // Check that it's ok to add to the same class repeatedly + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + EXPECT_NO_THROW(pkt.addClass("foo")); + + // Check that the packet belongs to 'foo' + EXPECT_TRUE(pkt.inClass("foo")); +} + } diff --git a/src/lib/dhcp/tests/protocol_util_unittest.cc b/src/lib/dhcp/tests/protocol_util_unittest.cc index 199ca27d58..971eb7f820 100644 --- a/src/lib/dhcp/tests/protocol_util_unittest.cc +++ b/src/lib/dhcp/tests/protocol_util_unittest.cc @@ -359,7 +359,7 @@ TEST(ProtocolUtilTest, writeIpUdpHeader) { in_buf.readData(src_addr_data, 4); src_addr = IOAddress::fromBytes(AF_INET, src_addr_data); ); - EXPECT_EQ(IOAddress("192.0.2.1").toText(), src_addr.toText()); + EXPECT_EQ(IOAddress("192.0.2.1"), src_addr); // Validate destination address. IOAddress dest_addr("::1"); @@ -368,7 +368,7 @@ TEST(ProtocolUtilTest, writeIpUdpHeader) { in_buf.readData(dest_addr_data, 4); dest_addr = IOAddress::fromBytes(AF_INET, dest_addr_data); ); - EXPECT_EQ(IOAddress("192.0.2.111").toText(), dest_addr.toText()); + EXPECT_EQ(IOAddress("192.0.2.111"), dest_addr); // UDP header starts here. |