From c0044e3e2047e47a2bfd457cdc00a4e1ac501df9 Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Fri, 18 Sep 2015 07:37:14 +0200 Subject: [PATCH] [fd4o6] Implemented DHCPv4-over-DHCPv6 with ISC DHCP compatible IPC (checkpoint) --- src/bin/dhcp4/Makefile.am | 1 + src/bin/dhcp4/ctrl_dhcp4_srv.cc | 13 +- src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc | 76 +++++++ src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h | 74 +++++++ src/bin/dhcp4/dhcp4_messages.mes | 4 + src/bin/dhcp4/dhcp4_srv.cc | 151 ++++++++++++- src/bin/dhcp4/dhcp4_srv.h | 13 ++ src/bin/dhcp4/json_config_parser.cc | 270 +++++++++++++++++++++-- src/bin/dhcp6/Makefile.am | 1 + src/bin/dhcp6/ctrl_dhcp6_srv.cc | 11 + src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc | 71 ++++++ src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h | 61 +++++ src/bin/dhcp6/dhcp6_srv.cc | 32 +++ src/bin/dhcp6/dhcp6_srv.h | 6 + src/bin/dhcp6/json_config_parser.cc | 13 +- src/lib/dhcp/pkt4.h | 8 + src/lib/dhcp/pkt4o6.cc | 36 ++- src/lib/dhcp/pkt4o6.h | 31 ++- src/lib/dhcp/tests/libdhcp++_unittest.cc | 2 +- src/lib/dhcpsrv/Makefile.am | 1 + src/lib/dhcpsrv/dhcp4o6_ipc.cc | 206 +++++++++++++++++ src/lib/dhcpsrv/dhcp4o6_ipc.h | 81 +++++++ src/lib/dhcpsrv/srv_config.cc | 4 +- src/lib/dhcpsrv/srv_config.h | 24 ++ 24 files changed, 1152 insertions(+), 38 deletions(-) create mode 100644 src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc create mode 100644 src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h create mode 100644 src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc create mode 100644 src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h create mode 100644 src/lib/dhcpsrv/dhcp4o6_ipc.cc create mode 100644 src/lib/dhcpsrv/dhcp4o6_ipc.h diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am index 2a3810049e..bcfa2abdbc 100644 --- a/src/bin/dhcp4/Makefile.am +++ b/src/bin/dhcp4/Makefile.am @@ -62,6 +62,7 @@ libdhcp4_la_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h libdhcp4_la_SOURCES += json_config_parser.cc json_config_parser.h libdhcp4_la_SOURCES += dhcp4_log.cc dhcp4_log.h libdhcp4_la_SOURCES += dhcp4_srv.cc dhcp4_srv.h +libdhcp4_la_SOURCES += dhcp4_dhcp4o6_ipc.cc dhcp4_dhcp4o6_ipc.h libdhcp4_la_SOURCES += kea_controller.cc diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index c9abff19a3..b5f8230e6e 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014-2015 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 @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -147,6 +148,16 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) { return (isc::config::createAnswer(1, err.str())); } + // Setup DHCPv4-over-DHCPv6 IPC + try { + Dhcp4o6Ipc::instance().open(); + } catch (const std::exception& ex) { + std::ostringstream err; + err << "error starting DHCPv4-over-DHCPv6 IPC " + " after server reconfiguration: " << ex.what(); + return (isc::config::createAnswer(1, err.str())); + } + // Configuration may change active interfaces. Therefore, we have to reopen // sockets according to new configuration. It is possible that this // operation will fail for some interfaces but the openSockets function diff --git a/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc b/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc new file mode 100644 index 0000000000..f5fb503657 --- /dev/null +++ b/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc @@ -0,0 +1,76 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include +#include +#include + +using namespace std; + +namespace isc { +namespace dhcp { + +Dhcp4o6Ipc::Dhcp4o6Ipc() : Dhcp4o6IpcBase() {} + +Dhcp4o6Ipc& Dhcp4o6Ipc::instance() { + static Dhcp4o6Ipc dhcp4o6_ipc; + return (dhcp4o6_ipc); +} + +void Dhcp4o6Ipc::open() { + uint32_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port(); + if (port == 0) { + Dhcp4o6IpcBase::close(); + return; + } + if (port > 65534) { + isc_throw(OutOfRange, "DHCP4o6 port " << port); + } + + int old_fd = socket_fd_; + socket_fd_ = Dhcp4o6IpcBase::open(static_cast(port), 6); + if ((old_fd == -1) && (socket_fd_ != old_fd)) { + IfaceMgr::instance().addExternalSocket(socket_fd_, Dhcp4o6Ipc::handler); + } +} + +void Dhcp4o6Ipc::handler() { + Dhcp4o6Ipc& ipc = Dhcp4o6Ipc::instance(); + Pkt6Ptr pkt = ipc.receive(); + if (!pkt) { + return; + } + + pkt->unpack(); + OptionCollection msgs = pkt->getOptions(D6O_DHCPV4_MSG); + if (msgs.size() != 1) { + return; + } + OptionPtr msg = pkt->getOption(D6O_DHCPV4_MSG); + if (!msg) { + isc_throw(Unexpected, "Can't get DHCPv4 message option"); + } + instance().received_.reset(new Pkt4o6(msg->getData(), pkt)); +} + +Pkt4o6Ptr& Dhcp4o6Ipc::getReceived() { + return (received_); +} + +}; // namespace dhcp + +}; // namespace isc diff --git a/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h b/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h new file mode 100644 index 0000000000..5763e1264f --- /dev/null +++ b/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h @@ -0,0 +1,74 @@ +// Copyright (C) 2015 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 DHCP4_DHCP4O6_IPC_H +#define DHCP4_DHCP4O6_IPC_H + +/// @file dhcp4_dhcp4o6_ipc.h Defines the Dhcp4o6Ipc class. +/// This file defines the class Kea uses to act as the DHCPv4 server +/// side of DHCPv4-over-DHCPv6 communication between servers. +/// + +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Handles DHCPv4-over-DHCPv6 IPC on the DHCPv4 server side +class Dhcp4o6Ipc : public Dhcp4o6IpcBase { +protected: + /// @brief Constructor + /// + /// Default constructor + Dhcp4o6Ipc(); + + /// @brief Destructor. + virtual ~Dhcp4o6Ipc() { } + +public: + /// @brief Returns pointer to the sole instance of Dhcp4o6Ipc + /// + /// Dhcp4o6Ipc is a singleton class + /// + /// @return the only existing instance of DHCP4o6 IPC + static Dhcp4o6Ipc& instance(); + + /// @brief Open communication socket + /// + /// Call base open method and sets the handler/callback when needed + virtual void open(); + + /// @brief On receive handler + /// + /// The handler processes the DHCPv4-query DHCPv6 packet and + /// sends the DHCPv4-response DHCPv6 packet back to the DHCPv6 server + static void handler(); + + /// @brief Returns last received packet + /// + /// @return a reference to a shared pointer to the last received packet + /// @note This reference should be cleared after use + Pkt4o6Ptr& getReceived(); + +private: + /// @brief last received packet + Pkt4o6Ptr received_; +}; + +} // namespace isc +} // namespace dhcp + +#endif diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index 7808186894..3e318cb1bb 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -149,6 +149,10 @@ server will continue to use an old configuration. This is an informational message reporting that the configuration has been extended to include the specified IPv4 subnet. +% DHCP4_CONFIG_NEW_SUBNET6 a new subnet has been added to configuration: %1 +This is an informational message reporting that the configuration has +been extended to include the specified IPv6 subnet. + % DHCP4_CONFIG_OPTION_DUPLICATE multiple options with the code %1 added to the subnet %2 This warning message is issued on an attempt to configure multiple options with the same option code for a particular subnet. Adding multiple options diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 081539ccaa..1d935da7f2 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -24,7 +24,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -164,9 +167,33 @@ Dhcpv4Exchange::initResponse() { if (resp_type > 0) { resp_.reset(new Pkt4(resp_type, getQuery()->getTransid())); copyDefaultFields(); + + if (getQuery()->isDhcp4o6()) { + initResponse4o6(); + } } } +void +Dhcpv4Exchange::initResponse4o6() { + Pkt4o6Ptr query = boost::dynamic_pointer_cast(getQuery()); + if (!query) { + return; + } + const Pkt6Ptr& query6 = query->getPkt6(); + Pkt6Ptr resp6(new Pkt6(DHCPV6_DHCPV4_RESPONSE, query6->getTransid())); + // Don't add client-id or server-id + // But copy relay info + if (!query6->relay_info_.empty()) { + resp6->copyRelayInfo(query6); + } + // Copy interface and remote address + resp6->setIface(query6->getIface()); + resp6->setIndex(query6->getIndex()); + resp6->setRemoteAddr(query6->getRemoteAddr()); + resp_.reset(new Pkt4o6(resp_, resp6)); +} + void Dhcpv4Exchange::copyDefaultFields() { resp_->setIface(query_->getIface()); @@ -270,6 +297,13 @@ Dhcpv4Srv::~Dhcpv4Srv() { LOG_ERROR(dhcp4_logger, DHCP4_SRV_D2STOP_ERROR).arg(ex.what()); } + try { + Dhcp4o6Ipc::instance().close(); + } catch(const std::exception& ex) { + // Highly unlikely, but lets Report it but go on + // LOG_ERROR(dhcp4_logger, DHCP4_SRV_DHCP4O6_ERROR).arg(ex.what()); + } + IfaceMgr::instance().closeSockets(); } @@ -282,6 +316,11 @@ Dhcpv4Srv::shutdown() { isc::dhcp::Subnet4Ptr Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query) const { + // DHCPv4-over-DHCPv6 is a special (and complex) case + if (query->isDhcp4o6()) { + return (selectSubnet4o6(query)); + } + Subnet4Ptr subnet; SubnetSelector selector; @@ -347,14 +386,106 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query) const { return (subnet); } +isc::dhcp::Subnet4Ptr +Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query) const { + + CfgMgr& cfgmgr = CfgMgr::instance(); + Subnet4Ptr subnet; + Subnet6Ptr subnet6; + SubnetSelector selector; + selector.client_classes_ = query->classes_; + + // DHCPv4 relay or option + selector.giaddr_ = query->getGiaddr(); + if (!selector.giaddr_.isV4Zero()) { + subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector); + } + + // DHCPv6 relay + if (!subnet) { + Pkt4o6Ptr query4o6 = boost::dynamic_pointer_cast(query); + if (!query4o6) { + isc_throw(Unexpected, "Can't get DHCP4o6 message"); + } + const Pkt6Ptr& query6 = query4o6->getPkt6(); + if (query6 && !query6->relay_info_.empty()) { + selector.first_relay_linkaddr_ = query6->relay_info_.back().linkaddr_; + selector.interface_id_ = + query6->getAnyRelayOption(D6O_INTERFACE_ID, Pkt6::RELAY_GET_FIRST); + subnet6 = + cfgmgr.getCurrentCfg()->getCfgSubnets6()->selectSubnet(selector); + } + if (subnet6) { + // Rely on matching IDs + const Subnet4Collection* subnets = + cfgmgr.getCurrentCfg()->getCfgSubnets4()->getAll(); + for (Subnet4Collection::const_iterator it = subnets->begin(); + it != subnets->end(); ++it) { + if ((*it)->getID() == subnet6->getID()) { + subnet = *it; + } + } + } + } + + // Applying default DHCPv4 rules + if (!subnet) { + selector.ciaddr_ = query->getCiaddr(); + selector.remote_address_ = query->getRemoteAddr(); + selector.iface_name_ = query->getIface(); + + subnet = + cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector); + } + + // Let's execute all callouts registered for subnet4_select + // TODO + + if (subnet) { + // Log at higher debug level that subnet has been found. + LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_SELECTED) + .arg(query->getLabel()) + .arg(subnet->getID()); + // Log detailed information about the selected subnet at the + // lower debug level. + LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_DATA) + .arg(query->getLabel()) + .arg(subnet->toText()); + + } else { + LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, + DHCP4_SUBNET_SELECTION_FAILED) + .arg(query->getLabel()); + } + + return (subnet); +} + Pkt4Ptr Dhcpv4Srv::receivePacket(int timeout) { - return (IfaceMgr::instance().receive4(timeout)); + Pkt4Ptr pkt = IfaceMgr::instance().receive4(timeout); + if (!pkt) { + Pkt4o6Ptr& pkt4o6 = Dhcp4o6Ipc::instance().getReceived(); + if (pkt4o6) { + pkt = pkt4o6; + pkt4o6.reset(); + } + } + return (pkt); } void Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) { - IfaceMgr::instance().send(packet); + if (!packet->isDhcp4o6()) { + IfaceMgr::instance().send(packet); + } else { + Pkt4o6Ptr pkt4o6 = boost::dynamic_pointer_cast(packet); + // Should not happen + if (!pkt4o6) { + isc_throw(Unexpected, "Dhcp4o6 packet cast fail"); + } + Dhcp4o6Ipc::instance().send(pkt4o6->getPkt6()); + } } bool @@ -1497,7 +1628,9 @@ Dhcpv4Srv::adjustIfaceData(Dhcpv4Exchange& ex) { // Instead we will need to use the address assigned to the interface // on which the query has been received. In other cases, we will just // use this address as a source address for the response. - if (local_addr == IOAddress::IPV4_BCAST_ADDRESS()) { + // Do the same for DHCPv4-over-DHCPv6 exchanges. + if ((local_addr == IOAddress::IPV4_BCAST_ADDRESS()) || + (query->isDhcp4o6())) { SocketInfo sock_info = IfaceMgr::instance().getSocket(*query); local_addr = sock_info.addr_; } @@ -1529,6 +1662,12 @@ Dhcpv4Srv::adjustRemoteAddr(Dhcpv4Exchange& ex) { Pkt4Ptr query = ex.getQuery(); Pkt4Ptr response = ex.getResponse(); + // DHCPv4-over-DHCPv6 is simple + if (query->isDhcp4o6()) { + response->setRemoteAddr(query->getRemoteAddr()); + return; + } + // The DHCPINFORM is slightly different than other messages in a sense // that the server should always unicast the response to the ciaddr. // It appears however that some clients don't set the ciaddr. We still @@ -1912,6 +2051,12 @@ Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const { if (pkt->isRelayed()) { return (true); } + + // Accept all DHCPv4-over-DHCPv6 messages. + if (pkt->isDhcp4o6()) { + return (true); + } + // The source address must not be zero for the DHCPINFORM message from // the directly connected client because the server will not know where // to respond if the ciaddr was not present. diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index e96c0bd61a..ec2b9374f2 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -17,6 +17,8 @@ #include #include +#include +#include #include #include #include @@ -95,6 +97,11 @@ public: /// response is not initialized. void initResponse(); + /// @brief Initializes the DHCPv6 part of the response message + /// + /// Called by initResponse() when the query is a DHCP4o6 message + void initResponse4o6(); + /// @brief Returns the pointer to the query from the client. Pkt4Ptr getQuery() const { return (query_); @@ -690,6 +697,12 @@ protected: /// @return selected subnet (or NULL if no suitable subnet was found) isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& query) const; + /// @brief Selects a subnet for a given client's DHCP4o6 packet. + /// + /// @param query client's message + /// @return selected subnet (or NULL if no suitable subnet was found) + isc::dhcp::Subnet4Ptr selectSubnet4o6(const Pkt4Ptr& query) const; + /// indicates if shutdown is in progress. Setting it to true will /// initiate server shutdown procedure. volatile bool shutdown_; diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc index 36e06cb749..01be83c4cc 100644 --- a/src/bin/dhcp4/json_config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -238,6 +238,9 @@ protected: SubnetID subnet_id = static_cast(uint32_values_->getOptionalParam("id", 0)); + Subnet4Ptr subnet4(new Subnet4(addr, len, t1, t2, valid, subnet_id)); + subnet_id = subnet4->getID(); + stringstream s; s << addr << "/" << static_cast(len) << " with params: "; // t1 and t2 are optional may be not specified. @@ -247,11 +250,11 @@ protected: if (!t2.unspecified()) { s << "t2=" << t2 << ", "; } - s <<"valid-lifetime=" << valid; + s << "valid-lifetime=" << valid; + s << ", id=" << subnet_id; LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(s.str()); - Subnet4Ptr subnet4(new Subnet4(addr, len, t1, t2, valid, subnet_id)); subnet_ = subnet4; // match-client-id @@ -314,7 +317,7 @@ protected: } }; -/// @brief this class parses list of DHCP4 subnets +/// @brief this class parses list of IPv4 subnets /// /// This is a wrapper parser that handles the whole list of Subnet4 /// definitions. It iterates over all entries and creates Subnet4ConfigParser @@ -356,6 +359,225 @@ public: } }; +/// @brief This class parses a single IPv6 subnet. +/// +/// This is the IPv6 derivation of the SubnetConfigParser class and it parses +/// the whole subnet definition. It creates parsersfor received configuration +/// parameters as needed. +class Subnet6ConfigParser : public SubnetConfigParser { +public: + + /// @brief Constructor + /// + /// @param ignored first parameter + /// stores global scope parameters, options, option definitions. + Subnet6ConfigParser(const std::string&) + :SubnetConfigParser("", globalContext(), IOAddress("::")) { + } + + /// @brief Parses a single IPv6 subnet configuration and adds to the + /// Configuration Manager. + /// + /// @param subnet A new subnet being configured. + void build(ConstElementPtr subnet) { + SubnetConfigParser::build(subnet); + + if (subnet_) { + Subnet6Ptr sub6ptr = boost::dynamic_pointer_cast(subnet_); + if (!sub6ptr) { + // If we hit this, it is a programming error. + isc_throw(Unexpected, + "Invalid cast in Subnet6ConfigParser::commit"); + } + + // Set relay information if it was provided + if (relay_info_) { + sub6ptr->setRelayInfo(*relay_info_); + } + + // Adding a subnet to the Configuration Manager may fail if the + // subnet id is invalid (duplicate). Thus, we catch exceptions + // here to append a position in the configuration string. + try { + CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(sub6ptr); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, ex.what() << " (" + << subnet->getPosition() << ")"); + } + } + } + + /// @brief Commits subnet configuration. + /// + /// This function is currently no-op because subnet should already + /// be added into the Config Manager in the build(). + void commit() { } + +protected: + + /// @brief creates parsers for entries in subnet definition + /// + /// @param config_id name of the entry + /// + /// @return parser object for specified entry name. Note the caller is + /// responsible for deleting the parser created. + /// @throw isc::dhcp::DhcpConfigError if trying to create a parser + /// for unknown config element + DhcpConfigParser* createSubnetConfigParser(const std::string& config_id) { + DhcpConfigParser* parser = NULL; + if (config_id.compare("id") == 0) { + parser = new Uint32Parser(config_id, uint32_values_); + } else if ((config_id.compare("subnet") == 0) || + (config_id.compare("interface") == 0) || + (config_id.compare("client-class") == 0) || + (config_id.compare("interface-id") == 0)) { + parser = new StringParser(config_id, string_values_); + } else if (config_id.compare("relay") == 0) { + parser = new RelayInfoParser(config_id, relay_info_, Option::V6); + } else { + isc_throw(NotImplemented, "unsupported parameter: " << config_id); + } + + return (parser); + } + + /// @brief Issues a DHCP server specific warning regarding duplicate subnet + /// options. + /// + /// @param code is the numeric option code of the duplicate option + /// @param addr is the subnet address + /// @todo A means to know the correct logger and perhaps a common + /// message would allow this message to be emitted by the base class. + /// Required to make Subnet6ConfigParser not abstract. + virtual void duplicate_option_warning(uint32_t code, + isc::asiolink::IOAddress& addr) { + LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE) + .arg(code).arg(addr.toText()); + } + + /// @brief Instantiates the IPv6 Subnet based on a given IPv6 address + /// and prefix length. + /// + /// @param addr is IPv6 prefix of the subnet. + /// @param len is the prefix length + void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) { + // Subnet ID is not optional because without IPv6 subnets + // can't be used for DHCPv4-over-DHCPv6 subnet selection. + SubnetID subnet_id = + static_cast(uint32_values_->getParam("id")); + + // Get interface-id option content. For now we support string + // representation only + std::string ifaceid; + try { + ifaceid = string_values_->getParam("interface-id"); + } catch (const DhcpConfigError &) { + // interface-id is not mandatory + } + + // Specifying both interface for locally reachable subnets and + // interface id for relays is mutually exclusive. Need to test for + // this condition. + if (!ifaceid.empty()) { + std::string iface; + try { + iface = string_values_->getParam("interface"); + } catch (const DhcpConfigError &) { + // iface not mandatory + } + + if (!iface.empty()) { + isc_throw(isc::dhcp::DhcpConfigError, + "parser error: interface (defined for locally reachable " + "subnets) and interface-id (defined for subnets reachable" + " via relays) cannot be defined at the same time for " + "subnet " << addr << "/" << (int)len); + } + } + + // Create a new subnet. + Triplet t; + Subnet6* subnet6 = new Subnet6(addr, len, t, t, t, t, subnet_id); + subnet_id = subnet6->getID(); + + std::ostringstream output; + output << addr << "/" << static_cast(len) << " id=" << subnet_id; + + LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(output.str()); + + // Configure interface-id for remote interfaces, if defined + if (!ifaceid.empty()) { + OptionBuffer tmp(ifaceid.begin(), ifaceid.end()); + OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp)); + subnet6->setInterfaceId(opt); + } + + // Try setting up client class (if specified) + try { + string client_class = string_values_->getParam("client-class"); + subnet6->allowClientClass(client_class); + } catch (const DhcpConfigError&) { + // That's ok if it fails. client-class is optional. + } + + subnet_.reset(subnet6); + } + +}; + + +/// @brief this class parses a list of IPv6 subnets +/// +/// This is a wrapper parser that handles the whole list of Subnet6 +/// definitions. It iterates over all entries and creates Subnet6ConfigParser +/// for each entry. +class Subnets6ListConfigParser : public DhcpConfigParser { +public: + + /// @brief constructor + /// + /// @param dummy first argument, always ignored. All parsers accept a + /// string parameter "name" as their first argument. + Subnets6ListConfigParser(const std::string&) { + } + + /// @brief parses contents of the list + /// + /// Iterates over all entries on the list and creates a Subnet6ConfigParser + /// for each entry. + /// + /// @param subnets_list pointer to a list of IPv6 subnets + void build(ConstElementPtr subnets_list) { + BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) { + ParserPtr parser(new Subnet6ConfigParser("subnet")); + parser->build(subnet); + subnets_.push_back(parser); + } + + } + + /// @brief commits subnets definitions. + /// + /// Iterates over all Subnet6 parsers. Each parser contains definitions of + /// a single subnet and its parameters and commits each subnet separately. + void commit() { + BOOST_FOREACH(ParserPtr subnet, subnets_) { + subnet->commit(); + } + + } + + /// @brief Returns Subnet6ListConfigParser object + /// @param param_name name of the parameter + /// @return Subnets6ListConfigParser object + static DhcpConfigParser* factory(const std::string& param_name) { + return (new Subnets6ListConfigParser(param_name)); + } + + /// @brief collection of subnet parsers. + ParserCollection subnets_; +}; + } // anonymous namespace namespace isc { @@ -377,13 +599,16 @@ namespace dhcp { if ((config_id.compare("valid-lifetime") == 0) || (config_id.compare("renew-timer") == 0) || (config_id.compare("rebind-timer") == 0) || - (config_id.compare("decline-probation-period") == 0) ) { + (config_id.compare("decline-probation-period") == 0) || + (config_id.compare("dhcp4o6-port") == 0) ) { parser = new Uint32Parser(config_id, globalContext()->uint32_values_); } else if (config_id.compare("interfaces-config") == 0) { parser = new IfacesConfigParser4(); } else if (config_id.compare("subnet4") == 0) { parser = new Subnets4ListConfigParser(config_id); + } else if (config_id.compare("subnet6") == 0) { + parser = new Subnets6ListConfigParser(config_id); } else if (config_id.compare("option-data") == 0) { parser = new OptionDataListParser(config_id, CfgOptionPtr(), AF_INET); } else if (config_id.compare("option-def") == 0) { @@ -419,6 +644,7 @@ namespace dhcp { /// /// - echo-client-id /// - decline-probation-period +/// - dhcp4o6-port void setGlobalParameters4() { // Although the function is modest for now, it is certain that the number // of global switches will increase over time, hence the name. @@ -441,6 +667,15 @@ void setGlobalParameters4() { } catch (...) { // That's not really needed. } + + // Set the DHCPv4-over-DHCPv6 interserver port. + try { + uint32_t dhcp4o6_port = globalContext()->uint32_values_ + ->getOptionalParam("dhcp4o6-port", 0); + CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(dhcp4o6_port); + } catch (...) { + // Ignore errors. This flag is optional + } } isc::data::ConstElementPtr @@ -466,11 +701,12 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // depend on the global values. Also, option values configuration // must be performed after the option definitions configurations. // Thus we group parsers and will fire them in the right order: - // all parsers other than: lease-database, subnet4 and option-data parser, - // then: option-data parser, subnet4 parser, lease-database parser. + // all parsers other than: lease-database, subnet and option-data parser, + // then: option-data parser, subnet parser, lease-database parser. // Please do not change this order! ParserCollection independent_parsers; - ParserPtr subnet_parser; + ParserPtr subnet4_parser; + ParserPtr subnet6_parser; ParserPtr option_parser; ParserPtr iface_parser; ParserPtr leases_parser; @@ -508,7 +744,9 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED) .arg(config_pair.first); if (config_pair.first == "subnet4") { - subnet_parser = parser; + subnet4_parser = parser; + } else if (config_pair.first == "subnet6") { + subnet6_parser = parser; } else if (config_pair.first == "lease-database") { leases_parser = parser; } else if (config_pair.first == "option-data") { @@ -530,7 +768,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { independent_parsers.push_back(parser); parser->build(config_pair.second); // The commit operation here may modify the global storage - // but we need it so as the subnet6 parser can access the + // but we need it so as subnet parsers can access the // parsed data. parser->commit(); } @@ -545,12 +783,18 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { option_parser->commit(); } - // The subnet parser is the next one to be run. - std::map::const_iterator subnet_config = + // Subnet parsers are the next to be run. + std::map::const_iterator subnet4_config = values_map.find("subnet4"); - if (subnet_config != values_map.end()) { + if (subnet4_config != values_map.end()) { config_pair.first = "subnet4"; - subnet_parser->build(subnet_config->second); + subnet4_parser->build(subnet4_config->second); + } + std::map::const_iterator subnet6_config = + values_map.find("subnet6"); + if (subnet6_config != values_map.end()) { + config_pair.first = "subnet6"; + subnet6_parser->build(subnet6_config->second); } // Get command socket configuration from the config file. diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am index 7da28a5973..f0a2301639 100644 --- a/src/bin/dhcp6/Makefile.am +++ b/src/bin/dhcp6/Makefile.am @@ -64,6 +64,7 @@ libdhcp6_la_SOURCES += dhcp6_log.cc dhcp6_log.h libdhcp6_la_SOURCES += dhcp6_srv.cc dhcp6_srv.h libdhcp6_la_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h libdhcp6_la_SOURCES += json_config_parser.cc json_config_parser.h +libdhcp6_la_SOURCES += dhcp6_dhcp4o6_ipc.cc dhcp6_dhcp4o6_ipc.h libdhcp6_la_SOURCES += kea_controller.cc diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index 7a1e84b230..b26b000eee 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -143,6 +144,16 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { return (isc::config::createAnswer(1, err.str())); } + // Setup DHCPv4-over-DHCPv6 IPC + try { + Dhcp4o6Ipc::instance().open(); + } catch (const std::exception& ex) { + std::ostringstream err; + err << "error starting DHCPv4-over-DHCPv6 IPC " + " after server reconfiguration: " << ex.what(); + return (isc::config::createAnswer(1, err.str())); + } + // Configuration may change active interfaces. Therefore, we have to reopen // sockets according to new configuration. It is possible that this // operation will fail for some interfaces but the openSockets function diff --git a/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc b/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc new file mode 100644 index 0000000000..d3b758657f --- /dev/null +++ b/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc @@ -0,0 +1,71 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include +#include +#include + +using namespace std; + +namespace isc { +namespace dhcp { + +Dhcp4o6Ipc::Dhcp4o6Ipc() : Dhcp4o6IpcBase() {} + +Dhcp4o6Ipc& Dhcp4o6Ipc::instance() { + static Dhcp4o6Ipc dhcp4o6_ipc; + return (dhcp4o6_ipc); +} + +void Dhcp4o6Ipc::open() { + uint32_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port(); + if (port == 0) { + Dhcp4o6IpcBase::close(); + return; + } + if (port > 65534) { + isc_throw(OutOfRange, "DHCP4o6 port " << port); + } + + int old_fd = socket_fd_; + socket_fd_ = Dhcp4o6IpcBase::open(static_cast(port), 6); + if ((old_fd == -1) && (socket_fd_ != old_fd)) { + IfaceMgr::instance().addExternalSocket(socket_fd_, Dhcp4o6Ipc::handler); + } +} + +void Dhcp4o6Ipc::handler() { + Dhcp4o6Ipc& ipc = Dhcp4o6Ipc::instance(); + Pkt6Ptr pkt = ipc.receive(); + if (!pkt) { + return; + } + isc::util::OutputBuffer& buf = pkt->getBuffer(); + pkt->repack(); + uint8_t msg_type = buf[0]; + if ((msg_type == DHCPV6_RELAY_FORW) || (msg_type == DHCPV6_RELAY_REPL)) { + pkt->setRemotePort(DHCP6_SERVER_PORT); + } else { + pkt->setRemotePort(DHCP6_CLIENT_PORT); + } + IfaceMgr::instance().send(pkt); + // processStatsSent(pkt); +} + +}; // namespace dhcp + +}; // namespace isc diff --git a/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h b/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h new file mode 100644 index 0000000000..901a6f2d3e --- /dev/null +++ b/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h @@ -0,0 +1,61 @@ +// Copyright (C) 2015 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 DHCP6_DHCP4O6_IPC_H +#define DHCP6_DHCP4O6_IPC_H + +/// @file dhcp6_dhcp4o6_ipc.h Defines the Dhcp4o6Ipc class. +/// This file defines the class Kea uses to act as the DHCPv6 server +/// side of DHCPv4-over-DHCPv6 communication between servers. +/// +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Handles DHCPv4-over-DHCPv6 IPC on the DHCPv6 server side +class Dhcp4o6Ipc : public Dhcp4o6IpcBase { +protected: + /// @brief Constructor + /// + /// Default constructor + Dhcp4o6Ipc(); + + /// @brief Destructor. + virtual ~Dhcp4o6Ipc() { } + +public: + /// @brief Returns pointer to the sole instance of Dhcp4o6Ipc + /// + /// Dhcp4o6Ipc is a singleton class + /// + /// @return the only existing instance of DHCP4o6 IPC + static Dhcp4o6Ipc& instance(); + + /// @brief Open communication socket + /// + /// Call base open method and sets the handler/callback when needed + virtual void open(); + + /// @brief On receive handler + /// + /// The handler sends the DHCPv6 packet back to the remote address + static void handler(); +}; + +} // namespace isc +} // namespace dhcp + +#endif diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 639be48038..6ded7b0887 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -236,6 +237,13 @@ Dhcpv6Srv::~Dhcpv6Srv() { LOG_ERROR(dhcp6_logger, DHCP6_SRV_D2STOP_ERROR).arg(ex.what()); } + try { + Dhcp4o6Ipc::instance().close(); + } catch(const std::exception& ex) { + // Highly unlikely, but lets Report it but go on + // LOG_ERROR(dhcp6_logger, DHCP6_SRV_DHCP4O6_ERROR).arg(ex.what()); + } + IfaceMgr::instance().closeSockets(); LeaseMgrFactory::destroy(); @@ -571,6 +579,10 @@ bool Dhcpv6Srv::run() { rsp = processInfRequest(query); break; + case DHCPV6_DHCPV4_QUERY: + rsp = processDhcp4Query(query); + break; + default: // We received a packet type that we do not recognize. LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_BASIC, DHCP6_UNKNOWN_MSG_RECEIVED) @@ -2619,6 +2631,26 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) { return (reply); } +Pkt6Ptr +Dhcpv6Srv::processDhcp4Query(const Pkt6Ptr& dhcp4_query) { + + sanityCheck(dhcp4_query, OPTIONAL, OPTIONAL); + + // flags are in transid + // uint32_t flags = dhcp4_query->getTransid(); + // do nothing with DHCPV4_QUERY_FLAGS_UNICAST + + // Get the DHCPv4 message option + OptionPtr dhcp4_msg = dhcp4_query->getOption(D6O_DHCPV4_MSG); + if (dhcp4_msg) { + // Forward the whole message to the DHCPv4 server via IPC + Dhcp4o6Ipc::instance().send(dhcp4_query); + } + + // Our job is finished + return (Pkt6Ptr()); +} + size_t Dhcpv6Srv::unpackOptions(const OptionBuffer& buf, const std::string& option_space, diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 2eb0044343..606dd3ea44 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -274,6 +274,12 @@ protected: /// @return Reply message to be sent to the client. Pkt6Ptr processInfRequest(const Pkt6Ptr& inf_request); + /// @brief Processes incoming DHCPv4-query message. + /// + /// @param dhcp4_query message received from client + /// @return Reply (empty) message to (not) be sent to the client. + Pkt6Ptr processDhcp4Query(const Pkt6Ptr& dhcp4_query); + /// @brief Selects a subnet for a given client's packet. /// /// @param question client's message diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index 89a90508bd..a3a5daaf91 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -670,7 +670,8 @@ namespace dhcp { (config_id.compare("valid-lifetime") == 0) || (config_id.compare("renew-timer") == 0) || (config_id.compare("rebind-timer") == 0) || - (config_id.compare("decline-probation-period") == 0) ) { + (config_id.compare("decline-probation-period") == 0) || + (config_id.compare("dhcp4o6-port") == 0) ) { parser = new Uint32Parser(config_id, globalContext()->uint32_values_); } else if (config_id.compare("interfaces-config") == 0) { @@ -711,6 +712,7 @@ namespace dhcp { /// Currently this method sets the following global parameters: /// /// - decline-probation-period +/// - dhcp4o6-port void setGlobalParameters6() { // Set the probation period for decline handling. @@ -722,6 +724,15 @@ void setGlobalParameters6() { } catch (...) { // That's not really needed. } + + // Set the DHCPv4-over-DHCPv6 interserver port. + try { + uint32_t dhcp4o6_port = globalContext()->uint32_values_ + ->getOptionalParam("dhcp4o6-port", 0); + CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(dhcp4o6_port); + } catch (...) { + // Ignore errors. This flag is optional + } } isc::data::ConstElementPtr diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h index 549be78da8..458c7895e4 100644 --- a/src/lib/dhcp/pkt4.h +++ b/src/lib/dhcp/pkt4.h @@ -366,6 +366,14 @@ public: /// (true) or non-relayed (false). bool isRelayed() const; + /// @brief Checks if a DHCPv4 message has beeb transported over DHCPv6 + /// + /// @return Boolean value which indicates whether the message is + /// transported over DHCPv6 (true) or native DHCPv4 (false) + virtual bool isDhcp4o6() const { + return (false); + } + private: /// @brief Generic method that validates and sets HW address. diff --git a/src/lib/dhcp/pkt4o6.cc b/src/lib/dhcp/pkt4o6.cc index f328e82f7d..7fb087a6e3 100644 --- a/src/lib/dhcp/pkt4o6.cc +++ b/src/lib/dhcp/pkt4o6.cc @@ -13,20 +13,17 @@ // PERFORMANCE OF THIS SOFTWARE. #include -#include -#include -#include -#include + +#include +#include #include #include +#include -#include -#include -#include - -using namespace std; -using namespace isc::dhcp; using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; +using namespace std; namespace { @@ -37,14 +34,29 @@ const IOAddress DEFAULT_ADDRESS("0.0.0.0"); namespace isc { namespace dhcp { -Pkt4o6::Pkt4o6(const uint8_t* data, size_t len, const Pkt6Ptr& pkt6) - :Pkt4(data, len), pkt6_(pkt6) +Pkt4o6::Pkt4o6(const OptionBuffer& pkt4, const Pkt6Ptr& pkt6) + :Pkt4(&pkt4[0], pkt4.size()), pkt6_(pkt6) { + static_cast(pkt6->delOption(D6O_DHCPV4_MSG)); setIface(pkt6->getIface()); setIndex(pkt6->getIndex()); setRemoteAddr(pkt6->getRemoteAddr()); } +Pkt4o6::Pkt4o6(const Pkt4Ptr& pkt4, const Pkt6Ptr& pkt6) + :Pkt4(*pkt4), pkt6_(pkt6) { +} + +void Pkt4o6::pack() { + Pkt4::pack(); + OutputBuffer& buf = getBuffer(); + const uint8_t* ptr = static_cast(buf.getData()); + OptionBuffer msg(ptr, ptr + buf.getLength()); + OptionPtr dhcp4_msg(new Option(Option::V6, D6O_DHCPV4_MSG, msg)); + pkt6_->addOption(dhcp4_msg); + pkt6_->pack(); +} + } // end of namespace isc::dhcp } // end of namespace isc diff --git a/src/lib/dhcp/pkt4o6.h b/src/lib/dhcp/pkt4o6.h index 7e6f1877d2..f90ae4eb61 100644 --- a/src/lib/dhcp/pkt4o6.h +++ b/src/lib/dhcp/pkt4o6.h @@ -34,13 +34,34 @@ public: /// @brief Constructor, used in message reception. /// - /// @param data pointer to received data - /// @param len size of buffer to be allocated for this packet - /// @param pkt6 encapsulating DHCPv6 message. - Pkt4o6(const uint8_t* data, size_t len, const Pkt6Ptr& pkt6); + /// @param pkt4 DHCPv4 message + /// @param pkt6 encapsulating unpacked DHCPv6 message + /// the DHCPv4 message option will be removed + Pkt4o6(const OptionBuffer& pkt4, const Pkt6Ptr& pkt6); + + /// @brief Constructor, used in replying to a message + /// + /// @param pkt4 DHCPv4 message + /// @param pkt6 DHCPv6 message + Pkt4o6(const Pkt4Ptr& pkt4, const Pkt6Ptr& pkt6); /// @brief Returns encapsulating DHCPv6 message - const Pkt6Ptr& getPkt6() { return (pkt6_); } + const Pkt6Ptr& getPkt6() const { return (pkt6_); } + + /// @brief Prepares on-wire format of DHCPv4-over-DHCPv6 packet. + /// + /// Calls pack() on both DHCPv4 and DHCPv6 parts + /// Inserts the DHCPv4-message option + /// @ref pkt4::pack and @ref pkt6::pack + virtual void pack(); + + /// @brief Checks if a DHCPv4 message has beeb transported over DHCPv6 + /// + /// @return Boolean value which indicates whether the message is + /// transported over DHCPv6 (true) or native DHCPv4 (false) + virtual bool isDhcp4o6() const { + return (true); + } protected: /// Encapsulating DHCPv6 message diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc index 861ae46704..6554bc314c 100644 --- a/src/lib/dhcp/tests/libdhcp++_unittest.cc +++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc @@ -207,7 +207,7 @@ private: // And the actual object type is the one that we expect. // Note that for many options there are dedicated classes // derived from Option class to represent them. - const Option* optptr = option.get(); + const Option* optptr = option.get(); EXPECT_TRUE(typeid(*optptr) == expected_type) << "Invalid class returned for option code " << code; } diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index e5160a5ef7..9b60f48918 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -92,6 +92,7 @@ libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.h +libkea_dhcpsrv_la_SOURCES += dhcp4o6_ipc.cc dhcp4o6_ipc.h libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h libkea_dhcpsrv_la_SOURCES += host.cc host.h libkea_dhcpsrv_la_SOURCES += host_container.h diff --git a/src/lib/dhcpsrv/dhcp4o6_ipc.cc b/src/lib/dhcpsrv/dhcp4o6_ipc.cc new file mode 100644 index 0000000000..9fcd1246ee --- /dev/null +++ b/src/lib/dhcpsrv/dhcp4o6_ipc.cc @@ -0,0 +1,206 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include +#include + +#include +#include + +using namespace isc::asiolink; +using namespace isc::util; +using namespace std; + +namespace isc { +namespace dhcp { + +Dhcp4o6IpcBase::Dhcp4o6IpcBase() : port_(0), socket_fd_(-1) {} + +Dhcp4o6IpcBase::~Dhcp4o6IpcBase() { + close(); +} + +int Dhcp4o6IpcBase::open(uint16_t port, int side) { + if (port == port_) { + // No change: nothing to do + return (socket_fd_); + } + + // Port 0: closing + if (port == 0) { + port_ = 0; + if (socket_fd_ != -1) { + IfaceMgr::instance().deleteExternalSocket(socket_fd_); + ::close(socket_fd_); + socket_fd_ = -1; + } + return (socket_fd_); + } + + // Open socket + int sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (sock < 0) { + isc_throw(Unexpected, "Failed to create DHCP4o6 socket."); + } + + // Set reuse address + int flag = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char *)&flag, sizeof(flag)) < 0) { + ::close(sock); + isc_throw(Unexpected, "Failed to set SO_REUSEADDR on DHCP4o6 socket."); + } + + // Set no blocking + if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { + ::close(sock); + isc_throw(Unexpected, "Failed to set O_NONBLOCK on DHCP4o6 socket."); + } + + // Bind to the local address + struct sockaddr_in6 local6; + memset(&local6, 0, sizeof(local6)); + local6.sin6_family = AF_INET6; +#ifdef HAVE_SA_LEN + local6.sin6_len = sizeof(local6); +#endif + if (side == 6) { + local6.sin6_port = htons(port); + } else { + local6.sin6_port = htons(port + 1); + } + if (bind(sock, (struct sockaddr *)&local6, sizeof(local6)) < 0) { + ::close(sock); + isc_throw(Unexpected, "Failed to bind DHCP4o6 socket."); + } + + // Connect to the remote address + struct sockaddr_in6 remote6; + memset(&remote6, 0, sizeof(remote6)); + remote6.sin6_family = AF_INET6; +#ifdef HAVE_SA_LEN + remote6.sin6_len = sizeof(remote6); +#endif + if (side == 6) { + remote6.sin6_port = htons(port + 1); + } else { + remote6.sin6_port = htons(port); + } + if (connect(sock, (struct sockaddr *)&remote6, sizeof(remote6)) < 0) { + ::close(sock); + isc_throw(Unexpected, "Failed to connect DHCP4o6 socket."); + } + + if (socket_fd_ != -1) { + if (dup2(sock, socket_fd_) == -1) { + ::close(sock); + isc_throw(Unexpected, "Failed to duplicate DHCP4o6 socket."); + } + if (sock != socket_fd_) { + ::close(sock); + sock = socket_fd_; + } + } + + // Success + port_ = port; + socket_fd_ = sock; + return (socket_fd_); +} + +void Dhcp4o6IpcBase::close() { + static_cast(open(0, 0)); +} + +Pkt6Ptr Dhcp4o6IpcBase::receive() { + uint8_t buf[65536]; + ssize_t cc = recv(socket_fd_, buf, sizeof(buf), 0); + if (cc < 0) { + isc_throw(Unexpected, "Failed to receive on DHCP4o6 socket."); + } + if (cc < DHCP4O6_DHCP6_OFFSET + Pkt6::DHCPV6_PKT_HDR_LEN) { + // Ignore too short messages + return (Pkt6Ptr()); + } + char name[DHCP4O6_IFNAME_SIZE + 1]; + memcpy(name, buf, DHCP4O6_IFNAME_SIZE); + IfacePtr iface = IfaceMgr::instance().getIface(string(name)); + if (!iface) { + // Can't get the interface: ignore + return (Pkt6Ptr()); + } + uint8_t raddr[DHCP4O6_RADDR_SIZE]; + memcpy(raddr, buf + DHCP4O6_RADDR_OFFSET, DHCP4O6_RADDR_SIZE); + IOAddress from = IOAddress::fromBytes(AF_INET6, raddr); + Pkt6Ptr pkt; + pkt = Pkt6Ptr(new Pkt6(buf + DHCP4O6_DHCP6_OFFSET, + cc - DHCP4O6_DHCP6_OFFSET)); + pkt->updateTimestamp(); + pkt->setRemoteAddr(from); + pkt->setIface(iface->getName()); + pkt->setIndex(iface->getIndex()); + return (pkt); +} + +void Dhcp4o6IpcBase::send(Pkt6Ptr pkt) { + // No packet: nothing to send + if (!pkt) { + return; + } + + // Disabled: nowhere to send + if (socket_fd_ == -1) { + return; + } + + // Get interface name + string name = pkt->getIface(); + if (name.empty() || name.size() > DHCP4O6_IFNAME_SIZE) { + // Bad interface name: ignore + return; + } + name.resize(DHCP4O6_IFNAME_SIZE); + + // Get remote address + IOAddress from = pkt->getRemoteAddr(); + vector raddr = from.toBytes(); + if (raddr.size() != DHCP4O6_RADDR_SIZE) { + // Bad remote address: ignore + return; + } + + // Get packet content + OutputBuffer& pbuf = pkt->getBuffer(); + if (!pbuf.getLength()) { + // Empty buffer: content is not (yet) here, get it + pkt->repack(); + } + + // Fill buffer + vector buf(DHCP4O6_DHCP6_OFFSET + pbuf.getLength()); + memcpy(&buf[0], name.c_str(), DHCP4O6_IFNAME_SIZE); + memcpy(&buf[0] + DHCP4O6_RADDR_OFFSET, &raddr[0], DHCP4O6_RADDR_SIZE); + memcpy(&buf[0] + DHCP4O6_DHCP6_OFFSET, pbuf.getData(), pbuf.getLength()); + + // Send + static_cast(::send(socket_fd_, &buf[0], buf.size(), 0)); + return; +} + +}; // namespace dhcp + +}; // namespace isc diff --git a/src/lib/dhcpsrv/dhcp4o6_ipc.h b/src/lib/dhcpsrv/dhcp4o6_ipc.h new file mode 100644 index 0000000000..36b05babc0 --- /dev/null +++ b/src/lib/dhcpsrv/dhcp4o6_ipc.h @@ -0,0 +1,81 @@ +// Copyright (C) 2015 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 DHCP4O6_IPC_H +#define DHCP4O6_IPC_H + +/// @file dhcp4o6_ipc.h Defines the Dhcp4o6IpcBase class. +/// This file defines the class Kea uses as a base for +/// DHCPv4-over-DHCPv6 communication between servers. +/// +#include + +#include + +#include + +namespace isc { +namespace dhcp { + +/// @brief +/// +class Dhcp4o6IpcBase : public boost::noncopyable { +protected: + /// @brief Constructor + /// + /// Default constructor + Dhcp4o6IpcBase(); + + /// @brief Destructor. + virtual ~Dhcp4o6IpcBase(); + + /// @brief Open communication socket (from base class) + /// + /// @param port port number to use (0 for disabled) + /// @param side side of the server (4 or 6) + /// + /// @return new socket descriptor + int open(uint16_t port, int side); + +public: + /// @brief Open communication socket (for derived classes) + virtual void open() = 0; + + /// @brief Close communication socket + void close(); + + /// @brief Receive IPC message + /// + /// @return a pointer to a DHCPv6 message with interface and remote + /// address set from the IPC message + Pkt6Ptr receive(); + + /// @brief Send IPC message + /// + /// @param a pointer to a DHCPv6 message with interface and remote + /// address set for the IPC message + void send(Pkt6Ptr pkt); + +protected: + /// @brief Port number + uint16_t port_; + + /// @brief Socket descriptor + int socket_fd_; +}; + +} // namespace isc +} // namespace dhcp + +#endif diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc index bdf4ea3763..ca577827f4 100644 --- a/src/lib/dhcpsrv/srv_config.cc +++ b/src/lib/dhcpsrv/srv_config.cc @@ -31,7 +31,7 @@ SrvConfig::SrvConfig() cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()), cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()), cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()), - decline_timer_(0) { + decline_timer_(0), dhcp4o6_port_(0) { } SrvConfig::SrvConfig(const uint32_t sequence) @@ -39,7 +39,7 @@ SrvConfig::SrvConfig(const uint32_t sequence) cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()), cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()), cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()), - decline_timer_(0) { + decline_timer_(0), dhcp4o6_port_(0) { } std::string diff --git a/src/lib/dhcpsrv/srv_config.h b/src/lib/dhcpsrv/srv_config.h index b1b9dbe88e..ed22411641 100644 --- a/src/lib/dhcpsrv/srv_config.h +++ b/src/lib/dhcpsrv/srv_config.h @@ -394,6 +394,24 @@ public: return (decline_timer_); } + /// @brief Sets DHCP4o6 IPC port + /// + /// DHCPv4-over-DHCPv6 uses a UDP socket for interserver communication, + /// this socket is bound and connected to this port and port + 1 + /// + /// @param port port and port + 1 to use + void setDhcp4o6Port(uint32_t port) { + dhcp4o6_port_ = port; + } + + /// @brief Returns DHCP4o6 IPC port + /// + /// See @ref setDhcp4o6Port or brief discussion. + /// @return value of DHCP4o6 IPC port + uint32_t getDhcp4o6Port() { + return (dhcp4o6_port_); + } + private: /// @brief Sequence number identifying the configuration. @@ -449,6 +467,12 @@ private: /// This timer specifies decline probation period, the time after a declined /// lease is recovered back to available state. Expressed in seconds. uint32_t decline_timer_; + + /// @brief DHCP4o6 IPC port + /// + /// DHCPv4-over-DHCPv6 uses a UDP socket for interserver communication, + /// this socket is bound and connected to this port and port + 1 + uint32_t dhcp4o6_port_; }; /// @name Pointers to the @c SrvConfig object. -- 2.47.2