summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcp')
-rw-r--r--src/lib/dhcp/Makefile.am1
-rw-r--r--src/lib/dhcp/docsis3_option_defs.h11
-rw-r--r--src/lib/dhcp/iface_mgr.cc255
-rw-r--r--src/lib/dhcp/iface_mgr.h86
-rw-r--r--src/lib/dhcp/iface_mgr_bsd.cc25
-rw-r--r--src/lib/dhcp/iface_mgr_error_handler.h52
-rw-r--r--src/lib/dhcp/iface_mgr_linux.cc57
-rw-r--r--src/lib/dhcp/iface_mgr_sun.cc25
-rw-r--r--src/lib/dhcp/libdhcp++.cc16
-rw-r--r--src/lib/dhcp/option.cc27
-rw-r--r--src/lib/dhcp/option4_addrlst.cc2
-rw-r--r--src/lib/dhcp/option4_client_fqdn.cc5
-rw-r--r--src/lib/dhcp/option6_addrlst.cc6
-rw-r--r--src/lib/dhcp/option6_client_fqdn.cc5
-rw-r--r--src/lib/dhcp/option6_ia.cc6
-rw-r--r--src/lib/dhcp/option6_iaaddr.cc11
-rw-r--r--src/lib/dhcp/option6_iaprefix.cc8
-rw-r--r--src/lib/dhcp/option6_iaprefix.h3
-rw-r--r--src/lib/dhcp/option_custom.cc6
-rw-r--r--src/lib/dhcp/option_data_types.h8
-rw-r--r--src/lib/dhcp/option_definition.cc72
-rw-r--r--src/lib/dhcp/option_definition.h33
-rw-r--r--src/lib/dhcp/option_int.h6
-rw-r--r--src/lib/dhcp/option_int_array.h6
-rw-r--r--src/lib/dhcp/option_vendor.cc2
-rw-r--r--src/lib/dhcp/option_vendor.h7
-rw-r--r--src/lib/dhcp/pkt4.cc14
-rw-r--r--src/lib/dhcp/pkt4.h35
-rw-r--r--src/lib/dhcp/pkt6.cc15
-rw-r--r--src/lib/dhcp/pkt6.h33
-rw-r--r--src/lib/dhcp/pkt_filter.cc6
-rw-r--r--src/lib/dhcp/pkt_filter_inet.cc3
-rw-r--r--src/lib/dhcp/std_option_defs.h5
-rw-r--r--src/lib/dhcp/tests/iface_mgr_unittest.cc422
-rw-r--r--src/lib/dhcp/tests/libdhcp++_unittest.cc46
-rw-r--r--src/lib/dhcp/tests/option4_client_fqdn_unittest.cc3
-rw-r--r--src/lib/dhcp/tests/option6_client_fqdn_unittest.cc3
-rw-r--r--src/lib/dhcp/tests/option_custom_unittest.cc12
-rw-r--r--src/lib/dhcp/tests/option_data_types_unittest.cc4
-rw-r--r--src/lib/dhcp/tests/option_definition_unittest.cc111
-rw-r--r--src/lib/dhcp/tests/pkt4_unittest.cc46
-rw-r--r--src/lib/dhcp/tests/pkt6_unittest.cc31
-rw-r--r--src/lib/dhcp/tests/protocol_util_unittest.cc4
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.