From: Tomek Mrugalski Date: Mon, 22 Jul 2013 16:46:54 +0000 (+0200) Subject: [master] Merge branch 'trac2994' (Initial hooks for DHCPv4 server) X-Git-Tag: bind10-1.2.0beta1-release~333 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=589f61f8a630643bdee4d8bb2be68a79fba64112;p=thirdparty%2Fkea.git [master] Merge branch 'trac2994' (Initial hooks for DHCPv4 server) Conflicts: ChangeLog src/bin/dhcp4/dhcp4_messages.mes src/bin/dhcp4/dhcp4_srv.cc src/bin/dhcp4/dhcp4_srv.h --- 589f61f8a630643bdee4d8bb2be68a79fba64112 diff --cc ChangeLog index 4f2814601f,b50718a517..23b98cbe45 --- a/ChangeLog +++ b/ChangeLog @@@ -1,18 -1,7 +1,23 @@@ -6XX. [func] tomek ++645. [func] tomek + Added initial set of hooks (pk4_receive, subnet4_select, + lease4_select, pkt4_send) to the DHCPv6 server. - (Trac #2994, git ABCD) ++ (Trac #2994, git be65cfba939a6a7abd3c93931ce35c33d3e8247b) ++ +644. [func] marcin + b10-dhcp4, b10-dhcp6: Implemented selection of the interfaces + that server listens on, using Configuration Manager. It is + possible to specify interface names explicitly or use asterisk + to specify that server should listen on all available interfaces. + Sockets are reopened according to the new configuration as + soon as it is committed. + (Trac #1555, git f48a3bff3fbbd15584d788a264d5966154394f04) + +643. [bug] muks + When running some unittests as root that depended on insufficient + file permissions, the tests used to fail because the root user + could still access such files. Such tests are now skipped when + they are run as the root user. + (Trac #3056, git 92ebabdbcf6168666b03d7f7fbb31f899be39322) 642. [func] tomek Added initial set of hooks (pk6_receive, subnet6_select, diff --cc src/bin/dhcp4/dhcp4_messages.mes index bcf148b2d9,6c716f9eb2..5af51f9d0d --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@@ -65,11 -60,25 +65,30 @@@ This informational message is printed e and gives both the type and name of the database being used to store lease and other information. +% DHCP4_DEACTIVATE_INTERFACE deactivate interface %1 +This message is printed when DHCPv4 server disables an interface from being +used to receive DHCPv4 traffic. Sockets on this interface will not be opened +by the Interface Manager until interface is enabled. + + % DHCP4_HOOK_PACKET_RCVD_SKIP received DHCPv4 packet was dropped, because a callout set the skip flag. + This debug message is printed when a callout installed on the pkt4_receive + hook point sets the skip flag. For this particular hook point, the + setting of the flag instructs the server to drop the packet. + + % DHCP4_HOOK_PACKET_SEND_SKIP prepared DHCPv6 response was not sent, because a callout set skip flag. + This debug message is printed when a callout installed on the pkt4_send + hook point sets the skip flag. For this particular hook point, the setting + of the flag instructs the server to drop the packet. This means that + the client will not get any response, even though the server processed + client's request and acted on it (e.g. possibly allocated a lease). + + % DHCP4_HOOK_SUBNET4_SELECT_SKIP no subnet was selected, because a callout set skip flag. + This debug message is printed when a callout installed on the + subnet4_select hook point sets the skip flag. For this particular hook + point, the setting of the flag instructs the server not to choose a + subnet, an action that severely limits further processing; the server + will be only able to offer global options - no addresses will be assigned. + % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3) This debug message indicates that the server successfully advertised a lease. It is up to the client to choose one server out of othe advertised diff --cc src/bin/dhcp4/dhcp4_srv.cc index bf877b98cd,33458f88cb..5e6147b62d --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@@ -59,7 -82,8 +82,10 @@@ static const char* SERVER_ID_FILE = "b1 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast, const bool direct_response_desired) - : port_(port), use_bcast_(use_bcast) { - :serverid_(), shutdown_(true), alloc_engine_(), hook_index_pkt4_receive_(-1), - hook_index_subnet4_select_(-1), hook_index_pkt4_send_(-1) { ++: serverid_(), shutdown_(true), alloc_engine_(), port_(port), ++ use_bcast_(use_bcast), hook_index_pkt4_receive_(-1), ++ hook_index_subnet4_select_(-1), hook_index_pkt4_send_(-1) { ++ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port); try { // First call to instance() will create IfaceMgr (it's a singleton) @@@ -819,51 -950,50 +952,94 @@@ Dhcpv4Srv::sanityCheck(const Pkt4Ptr& p // do nothing here ; } + + // If there is HWAddress set and it is non-empty, then we're good + if (pkt->getHWAddr() && !pkt->getHWAddr()->hwaddr_.empty()) { + return; + } + + // There has to be something to uniquely identify the client: + // either non-zero MAC address or client-id option present (or both) + OptionPtr client_id = pkt->getOption(DHO_DHCP_CLIENT_IDENTIFIER); + + // If there's no client-id (or a useless one is provided, i.e. 0 length) + if (!client_id || client_id->len() == client_id->getHeaderLen()) { + isc_throw(RFCViolation, "Missing or useless client-id and no HW address " + " provided in message " + << serverReceivedPacketName(pkt->getType())); + } + } + + isc::hooks::CalloutHandlePtr Dhcpv4Srv::getCalloutHandle(const Pkt4Ptr& pkt) { + // This method returns a CalloutHandle for a given packet. It is guaranteed + // to return the same callout_handle (so user library contexts are + // preserved). This method works well if the server processes one packet + // at a time. Once the server architecture is extended to cover parallel + // packets processing (e.g. delayed-ack, some form of buffering etc.), this + // method has to be extended (e.g. store callouts in a map and use pkt as + // a key). Additional code would be required to release the callout handle + // once the server finished processing. + + CalloutHandlePtr callout_handle; + static Pkt4Ptr old_pointer; + + if (!callout_handle || + old_pointer != pkt) { + // This is the first packet or a different packet than previously + // passed to getCalloutHandle() + + // Remember the pointer to this packet + old_pointer = pkt; + + callout_handle = HooksManager::createCalloutHandle(); + } + + return (callout_handle); } +void +Dhcpv4Srv::openActiveSockets(const uint16_t port, + const bool use_bcast) { + IfaceMgr::instance().closeSockets(); + + // Get the reference to the collection of interfaces. This reference should + // be valid as long as the program is run because IfaceMgr is a singleton. + // Therefore we can safely iterate over instances of all interfaces and + // modify their flags. Here we modify flags which indicate whether socket + // should be open for a particular interface or not. + const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces(); + for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin(); + iface != ifaces.end(); ++iface) { + Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName()); + if (iface_ptr == NULL) { + isc_throw(isc::Unexpected, "Interface Manager returned NULL" + << " instance of the interface when DHCPv4 server was" + << " trying to reopen sockets after reconfiguration"); + } + if (CfgMgr::instance().isActiveIface(iface->getName())) { + iface_ptr->inactive4_ = false; + LOG_INFO(dhcp4_logger, DHCP4_ACTIVATE_INTERFACE) + .arg(iface->getFullName()); + + } else { + // For deactivating interface, it should be sufficient to log it + // on the debug level because it is more useful to know what + // interface is activated which is logged on the info level. + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, + DHCP4_DEACTIVATE_INTERFACE).arg(iface->getName()); + iface_ptr->inactive4_ = true; + + } + } + // Let's reopen active sockets. openSockets4 will check internally whether + // sockets are marked active or inactive. + // @todo Optimization: we should not reopen all sockets but rather select + // those that have been affected by the new configuration. + if (!IfaceMgr::instance().openSockets4(port, use_bcast)) { + LOG_WARN(dhcp4_logger, DHCP4_NO_SOCKETS_OPEN); + } +} + + } // namespace dhcp } // namespace isc diff --cc src/bin/dhcp4/dhcp4_srv.h index 4160486377,61c83df496..e0959a595d --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@@ -350,9 -323,17 +363,20 @@@ private /// during normal operation (e.g. to use different allocators) boost::shared_ptr alloc_engine_; + uint16_t port_; ///< UDP port number on which server listens. + bool use_bcast_; ///< Should broadcast be enabled on sockets (if true). + + /// @brief returns callout handle for specified packet + /// + /// @param pkt packet for which the handle should be returned + /// + /// @return a callout handle to be used in hooks related to said packet + isc::hooks::CalloutHandlePtr getCalloutHandle(const Pkt4Ptr& pkt); + + /// Indexes for registered hook points + int hook_index_pkt4_receive_; + int hook_index_subnet4_select_; + int hook_index_pkt4_send_; }; }; // namespace isc::dhcp diff --cc src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index 2aa32c4ffa,458c5e7f41..93b0a4f02a --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@@ -1539,4 -1618,731 +1618,731 @@@ TEST_F(Dhcpv4SrvTest, ServerID) EXPECT_EQ(srvid_text, text); } + /// @todo Implement tests for subnetSelect See tests in dhcp6_srv_unittest.cc: + /// selectSubnetAddr, selectSubnetIface, selectSubnetRelayLinkaddr, + /// selectSubnetRelayInterfaceId. Note that the concept of interface-id is not + /// present in the DHCPv4, so not everything is applicable directly. + /// See ticket #3057 + + // Checks if hooks are registered properly. + TEST_F(Dhcpv4SrvTest, Hooks) { + NakedDhcpv4Srv srv(0); + + // check if appropriate hooks are registered + int hook_index_pkt4_received = -1; + int hook_index_select_subnet = -1; + int hook_index_pkt4_send = -1; + + // check if appropriate indexes are set + EXPECT_NO_THROW(hook_index_pkt4_received = ServerHooks::getServerHooks() + .getIndex("pkt4_receive")); + EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks() + .getIndex("subnet4_select")); + EXPECT_NO_THROW(hook_index_pkt4_send = ServerHooks::getServerHooks() + .getIndex("pkt4_send")); + + EXPECT_TRUE(hook_index_pkt4_received > 0); + EXPECT_TRUE(hook_index_select_subnet > 0); + EXPECT_TRUE(hook_index_pkt4_send > 0); + } + + // a dummy MAC address + const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5}; + + // A dummy MAC address, padded with 0s + const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + + // Let's use some creative test content here (128 chars + \0) + const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur " + "adipiscing elit. Proin mollis placerat metus, at " + "lacinia orci ornare vitae. Mauris amet."; + + // Yet another type of test content (64 chars + \0) + const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur " + "adipiscing elit posuere."; + + /// @brief a class dedicated to Hooks testing in DHCPv4 server + /// + /// This class has a number of static members, because each non-static + /// method has implicit 'this' parameter, so it does not match callout + /// signature and couldn't be registered. Furthermore, static methods + /// can't modify non-static members (for obvious reasons), so many + /// fields are declared static. It is still better to keep them as + /// one class rather than unrelated collection of global objects. + class HooksDhcpv4SrvTest : public Dhcpv4SrvTest { + + public: + + /// @brief creates Dhcpv4Srv and prepares buffers for callouts + HooksDhcpv4SrvTest() { + + // Allocate new DHCPv6 Server + srv_ = new NakedDhcpv4Srv(0); + + // clear static buffers + resetCalloutBuffers(); + } + + /// @brief destructor (deletes Dhcpv4Srv) + virtual ~HooksDhcpv4SrvTest() { + + HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_receive"); + HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_send"); + HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("subnet4_select"); + + delete srv_; + } + + /// @brief creates an option with specified option code + /// + /// This method is static, because it is used from callouts + /// that do not have a pointer to HooksDhcpv4SSrvTest object + /// + /// @param option_code code of option to be created + /// + /// @return pointer to create option object + static OptionPtr createOption(uint16_t option_code) { + + char payload[] = { + 0xa, 0xb, 0xc, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14 + }; + + OptionBuffer tmp(payload, payload + sizeof(payload)); + return OptionPtr(new Option(Option::V4, option_code, tmp)); + } + + /// @brief Generates test packet. + /// + /// Allocates and generates on-wire buffer that represents test packet, with all + /// fixed fields set to non-zero values. Content is not always reasonable. + /// + /// See generateTestPacket1() function that returns exactly the same packet as + /// Pkt4 object. + /// + /// @return pointer to allocated Pkt4 object + // Returns a vector containing a DHCPv4 packet header. + Pkt4Ptr + generateSimpleDiscover() { + + // That is only part of the header. It contains all "short" fields, + // larger fields are constructed separately. + uint8_t hdr[] = { + 1, 6, 6, 13, // op, htype, hlen, hops, + 0x12, 0x34, 0x56, 0x78, // transaction-id + 0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags + 192, 0, 2, 1, // ciaddr + 1, 2, 3, 4, // yiaddr + 192, 0, 2, 255, // siaddr + 255, 255, 255, 255, // giaddr + }; + + // Initialize the vector with the header fields defined above. + vector buf(hdr, hdr + sizeof(hdr)); + + // Append the large header fields. + copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf)); + copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf)); + copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf)); + + // Should now have all the header, so check. The "static_cast" is used + // to get round an odd bug whereby the linker appears not to find the + // definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ(). + EXPECT_EQ(static_cast(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size()); + + // Add magic cookie + buf.push_back(0x63); + buf.push_back(0x82); + buf.push_back(0x53); + buf.push_back(0x63); + + // Add message type DISCOVER + buf.push_back(static_cast(DHO_DHCP_MESSAGE_TYPE)); + buf.push_back(1); // length (just one byte) + buf.push_back(static_cast(DHCPDISCOVER)); + + return (Pkt4Ptr(new Pkt4(&buf[0], buf.size()))); + } + + /// test callback that stores received callout name and pkt4 value + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int + pkt4_receive_callout(CalloutHandle& callout_handle) { + callback_name_ = string("pkt4_receive"); + + callout_handle.getArgument("query4", callback_pkt4_); + + callback_argument_names_ = callout_handle.getArgumentNames(); + return (0); + } + + /// test callback that changes client-id value + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int + pkt4_receive_change_clientid(CalloutHandle& callout_handle) { + + Pkt4Ptr pkt; + callout_handle.getArgument("query4", pkt); + + // get rid of the old client-id + pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER); + + // add a new option + pkt->addOption(createOption(DHO_DHCP_CLIENT_IDENTIFIER)); + + // carry on as usual + return pkt4_receive_callout(callout_handle); + } + + /// test callback that deletes client-id + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int + pkt4_receive_delete_clientid(CalloutHandle& callout_handle) { + + Pkt4Ptr pkt; + callout_handle.getArgument("query4", pkt); + + // get rid of the old client-id (and no HWADDR) + vector mac; + pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER); + pkt->setHWAddr(1, 0, mac); // HWtype 1, hwardware len = 0 + + // carry on as usual + return pkt4_receive_callout(callout_handle); + } + + /// test callback that sets skip flag + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int + pkt4_receive_skip(CalloutHandle& callout_handle) { + + Pkt4Ptr pkt; + callout_handle.getArgument("query4", pkt); + + callout_handle.setSkip(true); + + // carry on as usual + return pkt4_receive_callout(callout_handle); + } + + /// Test callback that stores received callout name and pkt4 value + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int + pkt4_send_callout(CalloutHandle& callout_handle) { + callback_name_ = string("pkt4_send"); + + callout_handle.getArgument("response4", callback_pkt4_); + + callback_argument_names_ = callout_handle.getArgumentNames(); + return (0); + } + + // Test callback that changes server-id + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int + pkt4_send_change_serverid(CalloutHandle& callout_handle) { + + Pkt4Ptr pkt; + callout_handle.getArgument("response4", pkt); + + // get rid of the old server-id + pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER); + + // add a new option + pkt->addOption(createOption(DHO_DHCP_SERVER_IDENTIFIER)); + + // carry on as usual + return pkt4_send_callout(callout_handle); + } + + /// test callback that deletes server-id + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int + pkt4_send_delete_serverid(CalloutHandle& callout_handle) { + + Pkt4Ptr pkt; + callout_handle.getArgument("response4", pkt); + + // get rid of the old client-id + pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER); + + // carry on as usual + return pkt4_send_callout(callout_handle); + } + + /// Test callback that sets skip flag + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int + pkt4_send_skip(CalloutHandle& callout_handle) { + + Pkt4Ptr pkt; + callout_handle.getArgument("response4", pkt); + + callout_handle.setSkip(true); + + // carry on as usual + return pkt4_send_callout(callout_handle); + } + + /// Test callback that stores received callout name and subnet4 values + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int + subnet4_select_callout(CalloutHandle& callout_handle) { + callback_name_ = string("subnet4_select"); + + callout_handle.getArgument("query4", callback_pkt4_); + callout_handle.getArgument("subnet4", callback_subnet4_); + callout_handle.getArgument("subnet4collection", callback_subnet4collection_); + + callback_argument_names_ = callout_handle.getArgumentNames(); + return (0); + } + + /// Test callback that picks the other subnet if possible. + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int + subnet4_select_different_subnet_callout(CalloutHandle& callout_handle) { + + // Call the basic calllout to record all passed values + subnet4_select_callout(callout_handle); + + const Subnet4Collection* subnets; + Subnet4Ptr subnet; + callout_handle.getArgument("subnet4", subnet); + callout_handle.getArgument("subnet4collection", subnets); + + // Let's change to a different subnet + if (subnets->size() > 1) { + subnet = (*subnets)[1]; // Let's pick the other subnet + callout_handle.setArgument("subnet4", subnet); + } + + return (0); + } + + /// resets buffers used to store data received by callouts + void resetCalloutBuffers() { + callback_name_ = string(""); + callback_pkt4_.reset(); + callback_subnet4_.reset(); + callback_subnet4collection_ = NULL; + callback_argument_names_.clear(); + } + + /// pointer to Dhcpv4Srv that is used in tests + NakedDhcpv4Srv* srv_; + + // The following fields are used in testing pkt4_receive_callout + + /// String name of the received callout + static string callback_name_; + + /// Pkt4 structure returned in the callout + static Pkt4Ptr callback_pkt4_; + + /// Pointer to a subnet received by callout + static Subnet4Ptr callback_subnet4_; + + /// A list of all available subnets (received by callout) + static const Subnet4Collection* callback_subnet4collection_; + + /// A list of all received arguments + static vector callback_argument_names_; + }; + + // The following fields are used in testing pkt4_receive_callout. + // See fields description in the class for details + string HooksDhcpv4SrvTest::callback_name_; + Pkt4Ptr HooksDhcpv4SrvTest::callback_pkt4_; + Subnet4Ptr HooksDhcpv4SrvTest::callback_subnet4_; + const Subnet4Collection* HooksDhcpv4SrvTest::callback_subnet4collection_; + vector HooksDhcpv4SrvTest::callback_argument_names_; + + + // Checks if callouts installed on pkt4_received are indeed called and the + // all necessary parameters are passed. + // + // Note that the test name does not follow test naming convention, + // but the proper hook name is "pkt4_receive". + TEST_F(HooksDhcpv4SrvTest, simple_pkt4_receive) { + + // Install pkt4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "pkt4_receive", pkt4_receive_callout)); + + // Let's create a simple DISCOVER + Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover()); + + // Simulate that we have received that traffic + srv_->fakeReceive(sol); + + // Server will now process to run its normal loop, but instead of calling + // IfaceMgr::receive4(), it will read all packets from the list set by + // fakeReceive() + // In particular, it should call registered pkt4_receive callback. + srv_->run(); + + // check that the callback called is indeed the one we installed + EXPECT_EQ("pkt4_receive", callback_name_); + + // check that pkt4 argument passing was successful and returned proper value + EXPECT_TRUE(callback_pkt4_.get() == sol.get()); + + // Check that all expected parameters are there + vector expected_argument_names; + expected_argument_names.push_back(string("query4")); + + EXPECT_TRUE(expected_argument_names == callback_argument_names_); + } + + // Checks if callouts installed on pkt4_received is able to change + // the values and the parameters are indeed used by the server. + TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_receive) { + + // Install pkt4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "pkt4_receive", pkt4_receive_change_clientid)); + + // Let's create a simple DISCOVER + Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover()); + + // Simulate that we have received that traffic + srv_->fakeReceive(sol); + + // Server will now process to run its normal loop, but instead of calling + // IfaceMgr::receive4(), it will read all packets from the list set by + // fakeReceive() + // In particular, it should call registered pkt4_receive callback. + srv_->run(); + + // check that the server did send a reposonce + ASSERT_EQ(1, srv_->fake_sent_.size()); + + // Make sure that we received a response + Pkt4Ptr adv = srv_->fake_sent_.front(); + ASSERT_TRUE(adv); + + // Get client-id... + OptionPtr clientid = adv->getOption(DHO_DHCP_CLIENT_IDENTIFIER); + + // ... and check if it is the modified value + OptionPtr expected = createOption(DHO_DHCP_CLIENT_IDENTIFIER); + EXPECT_TRUE(clientid->equal(expected)); + } + + // Checks if callouts installed on pkt4_received is able to delete + // existing options and that change impacts server processing (mandatory + // client-id option is deleted, so the packet is expected to be dropped) + TEST_F(HooksDhcpv4SrvTest, deleteClientId_pkt4_receive) { + + // Install pkt4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "pkt4_receive", pkt4_receive_delete_clientid)); + + // Let's create a simple DISCOVER + Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover()); + + // Simulate that we have received that traffic + srv_->fakeReceive(sol); + + // Server will now process to run its normal loop, but instead of calling + // IfaceMgr::receive4(), it will read all packets from the list set by + // fakeReceive() + // In particular, it should call registered pkt4_receive callback. + srv_->run(); + + // Check that the server dropped the packet and did not send a response + ASSERT_EQ(0, srv_->fake_sent_.size()); + } + + // Checks if callouts installed on pkt4_received is able to set skip flag that + // will cause the server to not process the packet (drop), even though it is valid. + TEST_F(HooksDhcpv4SrvTest, skip_pkt4_receive) { + + // Install pkt4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "pkt4_receive", pkt4_receive_skip)); + + // Let's create a simple DISCOVER + Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover()); + + // Simulate that we have received that traffic + srv_->fakeReceive(sol); + + // Server will now process to run its normal loop, but instead of calling + // IfaceMgr::receive4(), it will read all packets from the list set by + // fakeReceive() + // In particular, it should call registered pkt4_receive callback. + srv_->run(); + + // check that the server dropped the packet and did not produce any response + ASSERT_EQ(0, srv_->fake_sent_.size()); + } + + + // Checks if callouts installed on pkt4_send are indeed called and the + // all necessary parameters are passed. + TEST_F(HooksDhcpv4SrvTest, simple_pkt4_send) { + + // Install pkt4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "pkt4_send", pkt4_send_callout)); + + // Let's create a simple DISCOVER + Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover()); + + // Simulate that we have received that traffic + srv_->fakeReceive(sol); + + // Server will now process to run its normal loop, but instead of calling + // IfaceMgr::receive4(), it will read all packets from the list set by + // fakeReceive() + // In particular, it should call registered pkt4_receive callback. + srv_->run(); + + // Check that the callback called is indeed the one we installed + EXPECT_EQ("pkt4_send", callback_name_); + + // Check that there is one packet sent + ASSERT_EQ(1, srv_->fake_sent_.size()); + Pkt4Ptr adv = srv_->fake_sent_.front(); + + // Check that pkt4 argument passing was successful and returned proper value + EXPECT_TRUE(callback_pkt4_.get() == adv.get()); + + // Check that all expected parameters are there + vector expected_argument_names; + expected_argument_names.push_back(string("response4")); + EXPECT_TRUE(expected_argument_names == callback_argument_names_); + } + + // Checks if callouts installed on pkt4_send is able to change + // the values and the packet sent contains those changes + TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_send) { + + // Install pkt4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "pkt4_send", pkt4_send_change_serverid)); + + // Let's create a simple DISCOVER + Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover()); + + // Simulate that we have received that traffic + srv_->fakeReceive(sol); + + // Server will now process to run its normal loop, but instead of calling + // IfaceMgr::receive4(), it will read all packets from the list set by + // fakeReceive() + // In particular, it should call registered pkt4_receive callback. + srv_->run(); + + // check that the server did send a reposonce + ASSERT_EQ(1, srv_->fake_sent_.size()); + + // Make sure that we received a response + Pkt4Ptr adv = srv_->fake_sent_.front(); + ASSERT_TRUE(adv); + + // Get client-id... + OptionPtr clientid = adv->getOption(DHO_DHCP_SERVER_IDENTIFIER); + + // ... and check if it is the modified value + OptionPtr expected = createOption(DHO_DHCP_SERVER_IDENTIFIER); + EXPECT_TRUE(clientid->equal(expected)); + } + + // Checks if callouts installed on pkt4_send is able to delete + // existing options and that server applies those changes. In particular, + // we are trying to send a packet without server-id. The packet should + // be sent + TEST_F(HooksDhcpv4SrvTest, deleteServerId_pkt4_send) { + + // Install pkt4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "pkt4_send", pkt4_send_delete_serverid)); + + // Let's create a simple DISCOVER + Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover()); + + // Simulate that we have received that traffic + srv_->fakeReceive(sol); + + // Server will now process to run its normal loop, but instead of calling + // IfaceMgr::receive4(), it will read all packets from the list set by + // fakeReceive() + // In particular, it should call registered pkt4_receive callback. + srv_->run(); + + // Check that the server indeed sent a malformed ADVERTISE + ASSERT_EQ(1, srv_->fake_sent_.size()); + + // Get that ADVERTISE + Pkt4Ptr adv = srv_->fake_sent_.front(); + ASSERT_TRUE(adv); + + // Make sure that it does not have server-id + EXPECT_FALSE(adv->getOption(DHO_DHCP_SERVER_IDENTIFIER)); + } + + // Checks if callouts installed on pkt4_skip is able to set skip flag that + // will cause the server to not process the packet (drop), even though it is valid. + TEST_F(HooksDhcpv4SrvTest, skip_pkt4_send) { + + // Install pkt4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "pkt4_send", pkt4_send_skip)); + + // Let's create a simple REQUEST + Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover()); + + // Simulate that we have received that traffic + srv_->fakeReceive(sol); + + // Server will now process to run its normal loop, but instead of calling + // IfaceMgr::receive4(), it will read all packets from the list set by + // fakeReceive() + // In particular, it should call registered pkt4_receive callback. + srv_->run(); + + // check that the server dropped the packet and did not produce any response + ASSERT_EQ(0, srv_->fake_sent_.size()); + } + + // This test checks if subnet4_select callout is triggered and reports + // valid parameters + TEST_F(HooksDhcpv4SrvTest, subnet4_select) { + + // Install pkt4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "subnet4_select", subnet4_select_callout)); + + // Configure 2 subnets, both directly reachable over local interface + // (let's not complicate the matter with relays) - string config = "{ \"interface\": [ \"all\" ]," ++ string config = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.0/25\" ]," + " \"subnet\": \"192.0.2.0/24\", " + " \"interface\": \"" + valid_iface_ + "\" " + " }, {" + " \"pool\": [ \"192.0.3.0/25\" ]," + " \"subnet\": \"192.0.3.0/24\" " + " } ]," + "\"valid-lifetime\": 4000 }"; + + ElementPtr json = Element::fromJSON(config); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json)); + ASSERT_TRUE(status); + comment_ = config::parseAnswer(rcode_, status); + ASSERT_EQ(0, rcode_); + + // Prepare discover packet. Server should select first subnet for it + Pkt4Ptr sol = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234)); + sol->setRemoteAddr(IOAddress("192.0.2.1")); + sol->setIface(valid_iface_); + OptionPtr clientid = generateClientId(); + sol->addOption(clientid); + + // Pass it to the server and get an advertise + Pkt4Ptr adv = srv_->processDiscover(sol); + + // check if we get response at all + ASSERT_TRUE(adv); + + // Check that the callback called is indeed the one we installed + EXPECT_EQ("subnet4_select", callback_name_); + + // Check that pkt4 argument passing was successful and returned proper value + EXPECT_TRUE(callback_pkt4_.get() == sol.get()); + + const Subnet4Collection* exp_subnets = CfgMgr::instance().getSubnets4(); + + // The server is supposed to pick the first subnet, because of matching + // interface. Check that the value is reported properly. + ASSERT_TRUE(callback_subnet4_); + EXPECT_EQ(exp_subnets->front().get(), callback_subnet4_.get()); + + // Server is supposed to report two subnets + ASSERT_EQ(exp_subnets->size(), callback_subnet4collection_->size()); + + // Compare that the available subnets are reported as expected + EXPECT_TRUE((*exp_subnets)[0].get() == (*callback_subnet4collection_)[0].get()); + EXPECT_TRUE((*exp_subnets)[1].get() == (*callback_subnet4collection_)[1].get()); + } + + // This test checks if callout installed on subnet4_select hook point can pick + // a different subnet. + TEST_F(HooksDhcpv4SrvTest, subnet_select_change) { + + // Install pkt4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( + "subnet4_select", subnet4_select_different_subnet_callout)); + + // Configure 2 subnets, both directly reachable over local interface + // (let's not complicate the matter with relays) - string config = "{ \"interface\": [ \"all\" ]," ++ string config = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.0/25\" ]," + " \"subnet\": \"192.0.2.0/24\", " + " \"interface\": \"" + valid_iface_ + "\" " + " }, {" + " \"pool\": [ \"192.0.3.0/25\" ]," + " \"subnet\": \"192.0.3.0/24\" " + " } ]," + "\"valid-lifetime\": 4000 }"; + + ElementPtr json = Element::fromJSON(config); + ConstElementPtr status; + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json)); + ASSERT_TRUE(status); + comment_ = config::parseAnswer(rcode_, status); + ASSERT_EQ(0, rcode_); + + // Prepare discover packet. Server should select first subnet for it + Pkt4Ptr sol = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234)); + sol->setRemoteAddr(IOAddress("192.0.2.1")); + sol->setIface(valid_iface_); + OptionPtr clientid = generateClientId(); + sol->addOption(clientid); + + // Pass it to the server and get an advertise + Pkt4Ptr adv = srv_->processDiscover(sol); + + // check if we get response at all + ASSERT_TRUE(adv); + + // The response should have an address from second pool, so let's check it + IOAddress addr = adv->getYiaddr(); + EXPECT_NE("0.0.0.0", addr.toText()); + + // Get all subnets and use second subnet for verification + const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4(); + ASSERT_EQ(2, subnets->size()); + + // Advertised address must belong to the second pool (in subnet's range, + // in dynamic pool) + EXPECT_TRUE((*subnets)[1]->inRange(addr)); + EXPECT_TRUE((*subnets)[1]->inPool(addr)); + } + + + } // end of anonymous namespace diff --cc src/bin/dhcp6/dhcp6_messages.mes index 3afb89b864,697d4990c2..f85fcb4b82 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@@ -70,19 -65,14 +70,19 @@@ This informational message is printed e is started. It indicates what database backend type is being to store lease and other information. +% DHCP6_DEACTIVATE_INTERFACE deactivate interface %1 +This message is printed when DHCPv6 server disables an interface from being +used to receive DHCPv6 traffic. Sockets on this interface will not be opened +by the Interface Manager until interface is enabled. + % DHCP6_HOOK_PACKET_RCVD_SKIP received DHCPv6 packet was dropped, because a callout set skip flag. - This debug message is printed when a callout installed on pkt6_received - hook point sets skip flag. For this particular hook point, the setting - of the flag by a callout instructs the server to drop the packet. + This debug message is printed when a callout installed on the pkt6_receive + hook point sets the skip flag. For this particular hook point, the + setting of the flag by a callout instructs the server to drop the packet. - % DHCP6_HOOK_PACKET_SEND_SKIP Prepared DHCPv6 response was not sent, because a callout set skip flag. - This debug message is printed when a callout installed on pkt6_send - hook point sets skip flag. For this particular hook point, the setting + % DHCP6_HOOK_PACKET_SEND_SKIP prepared DHCPv6 response was not sent, because a callout set skip flag. + This debug message is printed when a callout installed on the pkt6_send + hook point sets the skip flag. For this particular hook point, the setting of the flag by a callout instructs the server to drop the packet. This effectively means that the client will not get any response, even though the server processed client's request and acted on it (e.g. possibly