// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace isc::asiolink; using namespace isc::config; using namespace isc::data; using namespace isc::dhcp; using namespace isc::dhcp::test; using namespace isc::hooks; using namespace isc::stats; using namespace isc::util; using namespace std; namespace { // Checks if hooks are implemented properly. TEST_F(Dhcpv4SrvTest, Hooks) { NakedDhcpv4Srv srv(0); // check if appropriate hooks are registered int hook_index_dhcp4_srv_configured = -1; int hook_index_buffer4_receive = -1; int hook_index_buffer4_send = -1; int hook_index_lease4_renew = -1; int hook_index_lease4_release = -1; int hook_index_lease4_decline = -1; int hook_index_pkt4_receive = -1; int hook_index_pkt4_send = -1; int hook_index_select_subnet = -1; int hook_index_leases4_committed = -1; int hook_index_host4_identifier = -1; // check if appropriate indexes are set EXPECT_NO_THROW(hook_index_dhcp4_srv_configured = ServerHooks::getServerHooks() .getIndex("dhcp4_srv_configured")); EXPECT_NO_THROW(hook_index_buffer4_receive = ServerHooks::getServerHooks() .getIndex("buffer4_receive")); EXPECT_NO_THROW(hook_index_buffer4_send = ServerHooks::getServerHooks() .getIndex("buffer4_send")); EXPECT_NO_THROW(hook_index_lease4_renew = ServerHooks::getServerHooks() .getIndex("lease4_renew")); EXPECT_NO_THROW(hook_index_lease4_release = ServerHooks::getServerHooks() .getIndex("lease4_release")); EXPECT_NO_THROW(hook_index_lease4_decline = ServerHooks::getServerHooks() .getIndex("lease4_decline")); EXPECT_NO_THROW(hook_index_pkt4_receive = ServerHooks::getServerHooks() .getIndex("pkt4_receive")); EXPECT_NO_THROW(hook_index_pkt4_send = ServerHooks::getServerHooks() .getIndex("pkt4_send")); EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks() .getIndex("subnet4_select")); EXPECT_NO_THROW(hook_index_leases4_committed = ServerHooks::getServerHooks() .getIndex("leases4_committed")); EXPECT_NO_THROW(hook_index_host4_identifier = ServerHooks::getServerHooks() .getIndex("host4_identifier")); EXPECT_TRUE(hook_index_dhcp4_srv_configured > 0); EXPECT_TRUE(hook_index_buffer4_receive > 0); EXPECT_TRUE(hook_index_buffer4_send > 0); EXPECT_TRUE(hook_index_lease4_renew > 0); EXPECT_TRUE(hook_index_lease4_release > 0); EXPECT_TRUE(hook_index_lease4_decline > 0); EXPECT_TRUE(hook_index_pkt4_receive > 0); EXPECT_TRUE(hook_index_pkt4_send > 0); EXPECT_TRUE(hook_index_select_subnet > 0); EXPECT_TRUE(hook_index_leases4_committed > 0); EXPECT_TRUE(hook_index_host4_identifier > 0); } // 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() { HooksManager::setTestMode(false); bool status = HooksManager::unloadLibraries(); if (!status) { cerr << "(fixture ctor) unloadLibraries failed" << endl; } // Allocate new DHCPv4 Server srv_.reset(new NakedDhcpv4Srv(0)); // Clear static buffers resetCalloutBuffers(); io_service_ = boost::make_shared(); // Reset the hook system in its original state HooksManager::unloadLibraries(); // Clear statistics. StatsMgr::instance().removeAll(); } /// @brief destructor (deletes Dhcpv4Srv) virtual ~HooksDhcpv4SrvTest() { // Clear static buffers resetCalloutBuffers(); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("dhcp4_srv_configured"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_receive"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_send"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_receive"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_send"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("subnet4_select"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("leases4_committed"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_renew"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_release"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_decline"); HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("host4_identifier"); HooksManager::setTestMode(false); bool status = HooksManager::unloadLibraries(); if (!status) { cerr << "(fixture dtor) unloadLibraries failed" << endl; } // Clear statistics. StatsMgr::instance().removeAll(); } /// @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 HooksDhcpv4SrvTest object /// /// @param option_code code of option to be created /// /// @return pointer to create option object static OptionPtr createOption(uint16_t option_code) { uint8_t 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 192, 0, 2, 50, // 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)); Pkt4Ptr dis(new Pkt4(&buf[0], buf.size())); // Interface must be selected for a Discover. Server will use the interface // name to select a subnet for a client. This test is using fake interfaces // and the fake eth0 interface has IPv4 address matching the subnet // currently configured for this test. dis->setIface("eth1"); dis->setIndex(ETH1_INDEX); return (dis); } /// @brief Checks if the state of the callout handle associated with a query /// was reset after the callout invocation. /// /// The check includes verification if the status was set to 'continue' and /// that all arguments were deleted. /// /// @param query pointer to the query which callout handle is associated /// with. void checkCalloutHandleReset(const Pkt4Ptr& query) { CalloutHandlePtr callout_handle = query->getCalloutHandle(); ASSERT_TRUE(callout_handle); EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus()); EXPECT_TRUE(callout_handle->getArgumentNames().empty()); } /// @brief Test callback that stores callout name and passed parameters. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int buffer4_receive_callout(CalloutHandle& callout_handle) { callback_name_ = string("buffer4_receive"); callout_handle.getArgument("query4", callback_qry_pkt4_); callback_argument_names_ = callout_handle.getArgumentNames(); if (callback_qry_pkt4_) { callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions(); } return (0); } /// @brief Test callback that changes hwaddr value. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int buffer4_receive_change_hwaddr_callout(CalloutHandle& callout_handle) { Pkt4Ptr pkt; callout_handle.getArgument("query4", pkt); // If there is at least one option with data if (pkt->data_.size() >= Pkt4::DHCPV4_PKT_HDR_LEN) { // Offset of the first byte of the CHADDR field. Let's the first // byte to some new value that we could later check pkt->data_[28] = 0xff; } // Carry on as usual return buffer4_receive_callout(callout_handle); } /// @brief Test callback that sets skip flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int buffer4_receive_skip_callout(CalloutHandle& callout_handle) { callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); // Carry on as usual return buffer4_receive_callout(callout_handle); } /// @brief Test callback that sets drop flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int buffer4_receive_drop_callout(CalloutHandle& callout_handle) { callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP); // Carry on as usual return buffer4_receive_callout(callout_handle); } /// @brief Test callback that stores callout name and passed parameters. /// /// @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_qry_pkt4_); callback_argument_names_ = callout_handle.getArgumentNames(); if (callback_qry_pkt4_) { callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions(); } return (0); } /// @brief 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_callout(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); } /// @brief Test callback that deletes client-id. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int pkt4_receive_delete_clientid_callout(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, hardware len = 0 // Carry on as usual return pkt4_receive_callout(callout_handle); } /// @brief Test callback that sets skip flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int pkt4_receive_skip_callout(CalloutHandle& callout_handle) { Pkt4Ptr pkt; callout_handle.getArgument("query4", pkt); callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); // Carry on as usual return pkt4_receive_callout(callout_handle); } /// @brief Test callback that sets drop flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int pkt4_receive_drop_callout(CalloutHandle& callout_handle) { Pkt4Ptr pkt; callout_handle.getArgument("query4", pkt); callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP); // Carry on as usual return pkt4_receive_callout(callout_handle); } /// @brief Test callback that stores callout name and passed parameters. /// /// @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_resp_pkt4_); callout_handle.getArgument("query4", callback_qry_pkt4_); callback_argument_names_ = callout_handle.getArgumentNames(); if (callback_qry_pkt4_) { callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions(); } if (callback_resp_pkt4_) { callback_resp_options_copy_ = callback_resp_pkt4_->isCopyRetrievedOptions(); } return (0); } /// @brief Test callback that changes server-id. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int pkt4_send_change_serverid_callout(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); } /// @brief Test callback that deletes server-id. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int pkt4_send_delete_serverid_callout(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); } /// @brief Test callback that sets skip flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int pkt4_send_skip_callout(CalloutHandle& callout_handle) { Pkt4Ptr pkt; callout_handle.getArgument("response4", pkt); callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); // carry on as usual return pkt4_send_callout(callout_handle); } /// @brief Test callback that sets drop flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int pkt4_send_drop_callout(CalloutHandle& callout_handle) { Pkt4Ptr pkt; callout_handle.getArgument("response4", pkt); callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP); // carry on as usual return pkt4_send_callout(callout_handle); } /// @brief Test callback that stores callout name and passed parameters. /// /// @param callout_handle handle passed by the hooks framework. /// @return always 0 static int buffer4_send_callout(CalloutHandle& callout_handle) { callback_name_ = string("buffer4_send"); callout_handle.getArgument("response4", callback_resp_pkt4_); callback_argument_names_ = callout_handle.getArgumentNames(); if (callback_resp_pkt4_) { callback_resp_options_copy_ = callback_resp_pkt4_->isCopyRetrievedOptions(); } return (0); } /// @brief Test callback changes the output buffer to a hardcoded value. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int buffer4_send_change_callout(CalloutHandle& callout_handle) { Pkt4Ptr pkt; callout_handle.getArgument("response4", pkt); // modify buffer to set a different payload pkt->getBuffer().clear(); pkt->getBuffer().writeData(dummyFile, sizeof(dummyFile)); return (0); } /// @brief Test callback that sets skip flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int buffer4_send_skip_callout(CalloutHandle& callout_handle) { callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); // Carry on as usual return buffer4_send_callout(callout_handle); } /// @brief Test callback that sets drop flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int buffer4_send_drop_callout(CalloutHandle& callout_handle) { callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP); // carry on as usual return buffer4_send_callout(callout_handle); } /// @brief Test callback that stores callout name and passed parameters. /// /// @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_qry_pkt4_); callout_handle.getArgument("subnet4", callback_subnet4_); callout_handle.getArgument("subnet4collection", callback_subnet4collection_); callback_argument_names_ = callout_handle.getArgumentNames(); if (callback_qry_pkt4_) { callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions(); } return (0); } /// @brief 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 callout 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 = *std::next(subnets->begin()); // Let's pick the other subnet callout_handle.setArgument("subnet4", subnet); } return (0); } /// @brief Test callback that sets skip flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int subnet4_select_skip_callout(CalloutHandle& callout_handle) { callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); // Carry on as usual return subnet4_select_callout(callout_handle); } /// @brief Test callback that sets drop flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int subnet4_select_drop_callout(CalloutHandle& callout_handle) { callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP); // Carry on as usual return subnet4_select_callout(callout_handle); } /// @brief Test callback that stores callout name and passed parameters. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int lease4_renew_callout(CalloutHandle& callout_handle) { callback_name_ = string("lease4_renew"); callout_handle.getArgument("query4", callback_qry_pkt4_); callout_handle.getArgument("subnet4", callback_subnet4_); callout_handle.getArgument("lease4", callback_lease4_); callout_handle.getArgument("hwaddr", callback_hwaddr_); callout_handle.getArgument("clientid", callback_clientid_); callback_argument_names_ = callout_handle.getArgumentNames(); if (callback_qry_pkt4_) { callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions(); } return (0); } /// @brief Test callback that sets the skip flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int lease4_renew_skip_callout(CalloutHandle& callout_handle) { callback_name_ = string("lease4_renew"); callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); return (0); } /// @brief Test callback that stores callout name and passed parameters. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int lease4_release_callout(CalloutHandle& callout_handle) { callback_name_ = string("lease4_release"); callout_handle.getArgument("query4", callback_qry_pkt4_); callout_handle.getArgument("lease4", callback_lease4_); callback_argument_names_ = callout_handle.getArgumentNames(); if (callback_qry_pkt4_) { callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions(); } return (0); } /// @brief Test callback that sets the skip flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int lease4_release_skip_callout(CalloutHandle& callout_handle) { callback_name_ = string("lease4_release"); callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); return (0); } /// @brief Test callback that sets the drop flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int lease4_release_drop_callout(CalloutHandle& callout_handle) { callback_name_ = string("lease4_release"); callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP); return (0); } /// @brief Test callback that stores callout name and passed parameters. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int lease4_decline_callout(CalloutHandle& callout_handle) { callback_name_ = string("lease4_decline"); callout_handle.getArgument("query4", callback_qry_pkt4_); callout_handle.getArgument("lease4", callback_lease4_); if (callback_qry_pkt4_) { callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions(); } return (0); } /// @brief Test callback that sets the skip flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int lease4_decline_skip_callout(CalloutHandle& callout_handle) { callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); return (lease4_decline_callout(callout_handle)); } /// @brief Test callback that sets the drop flag. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int lease4_decline_drop_callout(CalloutHandle& callout_handle) { callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP); return (lease4_decline_callout(callout_handle)); } /// @brief Test callback that stores callout name and passed parameters. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int leases4_committed_callout(CalloutHandle& callout_handle) { callback_name_ = string("leases4_committed"); callout_handle.getArgument("query4", callback_qry_pkt4_); Lease4CollectionPtr leases4; callout_handle.getArgument("leases4", leases4); if (leases4->size() > 0) { callback_lease4_ = leases4->at(0); } Lease4CollectionPtr deleted_leases4; callout_handle.getArgument("deleted_leases4", deleted_leases4); if (deleted_leases4->size() > 0) { callback_deleted_lease4_ = deleted_leases4->at(0); } callback_argument_names_ = callout_handle.getArgumentNames(); sort(callback_argument_names_.begin(), callback_argument_names_.end()); if (callback_qry_pkt4_) { callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions(); } return (0); } /// @brief Test callback which asks the server to unpark the packet. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static void leases4_committed_unpark_callout(ParkingLotHandlePtr parking_lot, Pkt4Ptr query) { parking_lot->unpark(query); } /// @brief Test callback which asks the server to park the packet. /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int leases4_committed_park_callout(CalloutHandle& callout_handle) { callback_name_ = string("leases4_committed"); callout_handle.getArgument("query4", callback_qry_pkt4_); io_service_->post(std::bind(&HooksDhcpv4SrvTest::leases4_committed_unpark_callout, callout_handle.getParkingLotHandlePtr(), callback_qry_pkt4_)); callout_handle.getParkingLotHandlePtr()->reference(callback_qry_pkt4_); callout_handle.setStatus(CalloutHandle::NEXT_STEP_PARK); Lease4CollectionPtr leases4; callout_handle.getArgument("leases4", leases4); if (leases4->size() > 0) { callback_lease4_ = leases4->at(0); } Lease4CollectionPtr deleted_leases4; callout_handle.getArgument("deleted_leases4", deleted_leases4); if (deleted_leases4->size() > 0) { callback_deleted_lease4_ = deleted_leases4->at(0); } callback_argument_names_ = callout_handle.getArgumentNames(); sort(callback_argument_names_.begin(), callback_argument_names_.end()); if (callback_qry_pkt4_) { callback_qry_options_copy_ = callback_qry_pkt4_->isCopyRetrievedOptions(); } return (0); } /// @brief Test host4_identifier callback by setting identifier to "foo". /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int host4_identifier_foo_callout(CalloutHandle& handle) { callback_name_ = string("host4_identifier"); // Make sure the query4 parameter is passed. handle.getArgument("query4", callback_qry_pkt4_); // Make sure id_type parameter is passed. Host::IdentifierType type = Host::IDENT_FLEX; handle.getArgument("id_type", type); // Make sure id_value parameter is passed. std::vector id_test; handle.getArgument("id_value", id_test); // Ok, now set the identifier. std::vector id = { 0x66, 0x6f, 0x6f }; // foo handle.setArgument("id_value", id); handle.setArgument("id_type", Host::IDENT_FLEX); return (0); } /// @brief Test host4_identifier callout by setting identifier to hwaddr. /// /// This callout always returns fixed HWADDR: 00:01:02:03:04:05 /// /// @param callout_handle handle passed by the hooks framework /// @return always 0 static int host4_identifier_hwaddr_callout(CalloutHandle& handle) { callback_name_ = string("host4_identifier"); // Make sure the query4 parameter is passed. handle.getArgument("query4", callback_qry_pkt4_); // Make sure id_type parameter is passed. Host::IdentifierType type = Host::IDENT_FLEX; handle.getArgument("id_type", type); // Make sure id_value parameter is passed. std::vector id_test; handle.getArgument("id_value", id_test); // Ok, now set the identifier to 00:01:02:03:04:05 std::vector id = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; handle.setArgument("id_value", id); handle.setArgument("id_type", Host::IDENT_HWADDR); return (0); } /// Resets buffers used to store data received by callouts void resetCalloutBuffers() { callback_name_ = string(""); callback_qry_pkt4_.reset(); callback_resp_pkt4_.reset(); callback_subnet4_.reset(); callback_lease4_.reset(); callback_deleted_lease4_.reset(); callback_hwaddr_.reset(); callback_clientid_.reset(); callback_subnet4collection_ = NULL; callback_argument_names_.clear(); callback_qry_options_copy_ = false; callback_resp_options_copy_ = false; } /// @brief Fetches the current value of the given statistic. /// @param name name of the desired statistic. /// @return Current value of the statistic, or zero if the /// statistic is not found. uint64_t getStatistic(const std::string& name) { ObservationPtr stat = StatsMgr::instance().getObservation(name); if (!stat) { return (0); } return (stat->getInteger().first); } /// Pointer to Dhcpv4Srv that is used in tests boost::shared_ptr srv_; /// Pointer to the IO service used in the tests. static IOServicePtr io_service_; /// The following fields are used in testing pkt4_receive_callout /// String name of the received callout static string callback_name_; /// Client's query Pkt4 structure returned in the callout static Pkt4Ptr callback_qry_pkt4_; /// Server's response Pkt4 structure returned in the callout static Pkt4Ptr callback_resp_pkt4_; /// Pointer to lease4 structure returned in the leases4_committed callout static Lease4Ptr callback_lease4_; /// Pointer to lease4 structure returned in the leases4_committed callout static Lease4Ptr callback_deleted_lease4_; /// Hardware address returned in the callout static HWAddrPtr callback_hwaddr_; /// Client-id returned in the callout static ClientIdPtr callback_clientid_; /// 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_; /// Flag indicating if copying retrieved options was enabled for /// a query during callout execution. static bool callback_qry_options_copy_; /// Flag indicating if copying retrieved options was enabled for /// a response during callout execution. static bool callback_resp_options_copy_; }; // The following fields are used in testing pkt4_receive_callout. // See fields description in the class for details IOServicePtr HooksDhcpv4SrvTest::io_service_; string HooksDhcpv4SrvTest::callback_name_; Pkt4Ptr HooksDhcpv4SrvTest::callback_qry_pkt4_; Pkt4Ptr HooksDhcpv4SrvTest::callback_resp_pkt4_; Subnet4Ptr HooksDhcpv4SrvTest::callback_subnet4_; const Subnet4Collection* HooksDhcpv4SrvTest::callback_subnet4collection_; HWAddrPtr HooksDhcpv4SrvTest::callback_hwaddr_; ClientIdPtr HooksDhcpv4SrvTest::callback_clientid_; Lease4Ptr HooksDhcpv4SrvTest::callback_lease4_; Lease4Ptr HooksDhcpv4SrvTest::callback_deleted_lease4_; vector HooksDhcpv4SrvTest::callback_argument_names_; bool HooksDhcpv4SrvTest::callback_qry_options_copy_; bool HooksDhcpv4SrvTest::callback_resp_options_copy_; /// @brief Fixture class used to do basic library load/unload tests class LoadUnloadDhcpv4SrvTest : public ::testing::Test { public: /// @brief Pointer to the tested server object boost::shared_ptr server_; LoadUnloadDhcpv4SrvTest() { reset(); MultiThreadingMgr::instance().setMode(false); } /// @brief Destructor ~LoadUnloadDhcpv4SrvTest() { server_.reset(); reset(); MultiThreadingMgr::instance().setMode(false); }; /// @brief Reset hooks data /// /// Resets the data for the hooks-related portion of the test by ensuring /// that no libraries are loaded and that any marker files are deleted. void reset() { // Unload any previously-loaded libraries. EXPECT_TRUE(HooksManager::unloadLibraries()); // Get rid of any marker files. static_cast(remove(LOAD_MARKER_FILE)); static_cast(remove(UNLOAD_MARKER_FILE)); static_cast(remove(SRV_CONFIG_MARKER_FILE)); CfgMgr::instance().clear(); } }; // Checks if callouts installed on buffer4_receive 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 "buffer4_receive". TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveSimple) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install buffer4_receive_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "buffer4_receive", buffer4_receive_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 buffer4_receive callback. srv_->run(); // Check that the callback called is indeed the one we installed EXPECT_EQ("buffer4_receive", callback_name_); // Check that pkt4 argument passing was successful and returned proper value EXPECT_TRUE(callback_qry_pkt4_.get() == discover.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_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on buffer4_receive is able to change // the values and the parameters are indeed used by the server. TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveValueChange) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install buffer4_receive_change_hwaddr_callout that modifies MAC addr of incoming packet EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "buffer4_receive", buffer4_receive_change_hwaddr_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 buffer4_receive callback. srv_->run(); // Check that the server did send a response ASSERT_EQ(1, srv_->fake_sent_.size()); // Make sure that we received a response Pkt4Ptr offer = srv_->fake_sent_.front(); ASSERT_TRUE(offer); // Get client-id... HWAddrPtr hwaddr = offer->getHWAddr(); ASSERT_TRUE(hwaddr); // basic sanity check. HWAddr is always present // ... and check if it is the modified value ASSERT_FALSE(hwaddr->hwaddr_.empty()); // there must be a MAC address EXPECT_EQ(0xff, hwaddr->hwaddr_[0]); // check that its first byte was modified // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on buffer4_receive 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, buffer4ReceiveSkip) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install buffer4_receive_skip_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "buffer4_receive", buffer4_receive_skip_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 buffer4_receive callback. srv_->run(); // Check that the server dropped the packet and did not produce any response ASSERT_EQ(0, srv_->fake_sent_.size()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on buffer4_receive is able to set drop flag that // will cause the server to not process the packet (drop), even though it is valid. TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveDrop) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install buffer4_receive_drop_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "buffer4_receive", buffer4_receive_drop_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 buffer4_receive callback. srv_->run(); // Check that the server dropped the packet and did not produce any response ASSERT_EQ(0, srv_->fake_sent_.size()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on pkt4_receive 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, pkt4ReceiveSimple) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install pkt4_receive_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "pkt4_receive", pkt4_receive_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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_qry_pkt4_.get() == discover.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_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // 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, pkt4ReceiveValueChange) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install pkt4_receive_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "pkt4_receive", pkt4_receive_change_clientid_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 response 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->equals(expected)); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // 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, pkt4ReceiveDeleteClientId) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install pkt4_receive_delete_clientid_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "pkt4_receive", pkt4_receive_delete_clientid_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // 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, pkt4ReceiveSkip) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install pkt4_receive_skip_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "pkt4_receive", pkt4_receive_skip_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on pkt4_received is able to set drop flag that // will cause the server to not process the packet (drop), even though it is valid. TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveDrop) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install pkt4_receive_drop_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "pkt4_receive", pkt4_receive_drop_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on pkt4_send are indeed called and the // all necessary parameters are passed. TEST_F(HooksDhcpv4SrvTest, pkt4SendSimple) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install pkt4_send_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "pkt4_send", pkt4_send_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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_send 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 // values ASSERT_TRUE(callback_qry_pkt4_); EXPECT_TRUE(callback_qry_pkt4_.get() == discover.get()); ASSERT_TRUE(callback_resp_pkt4_); EXPECT_TRUE(callback_resp_pkt4_.get() == adv.get()); // Check that all expected parameters are there vector expected_argument_names; expected_argument_names.push_back(string("query4")); expected_argument_names.push_back(string("response4")); sort(callback_argument_names_.begin(), callback_argument_names_.end()); sort(expected_argument_names.begin(), expected_argument_names.end()); EXPECT_TRUE(expected_argument_names == callback_argument_names_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); EXPECT_TRUE(callback_resp_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on pkt4_send is able to change // the values and the packet sent contains those changes TEST_F(HooksDhcpv4SrvTest, pkt4SendValueChange) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install pkt4_send_change_serverid_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "pkt4_send", pkt4_send_change_serverid_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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_send callback. srv_->run(); // Check that the server did send a response 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->equals(expected)); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // 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, pkt4SendDeleteServerId) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install pkt4_send_delete_serverid_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "pkt4_send", pkt4_send_delete_serverid_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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_send 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)); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // 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, pkt4SendSkip) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install pkt4_send_skip_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "pkt4_send", pkt4_send_skip_callout)); // Let's create a simple REQUEST Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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_send callback. srv_->run(); // Check that the server sent the packet ASSERT_EQ(1, srv_->fake_sent_.size()); // Get the first packet and check that it has zero length (i.e. the server // did not do packing on its own) Pkt4Ptr sent = srv_->fake_sent_.front(); // The actual size of sent packet should be 0 EXPECT_EQ(0, sent->getBuffer().getLength()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on pkt4_drop is able to set drop flag that // will cause the server to not process the packet (drop), even though it is valid. TEST_F(HooksDhcpv4SrvTest, pkt4SendDrop) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install pkt4_send_drop_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "pkt4_send", pkt4_send_drop_callout)); // Let's create a simple REQUEST Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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_send callback. srv_->run(); // Check that the server does not send the packet EXPECT_EQ(0, srv_->fake_sent_.size()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on buffer4_send are indeed called and the // all necessary parameters are passed. TEST_F(HooksDhcpv4SrvTest, buffer4SendSimple) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install buffer4_send_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "buffer4_send", buffer4_send_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 buffer4_send callback. srv_->run(); // Check that the callback called is indeed the one we installed EXPECT_EQ("buffer4_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_resp_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_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_resp_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on buffer4_send are indeed called and that // the output buffer can be changed. TEST_F(HooksDhcpv4SrvTest, buffer4SendChange) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install buffer4_send_change_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "buffer4_send", buffer4_send_change_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 buffer4_send callback. srv_->run(); // Check that there is one packet sent ASSERT_EQ(1, srv_->fake_sent_.size()); Pkt4Ptr adv = srv_->fake_sent_.front(); // The callout is supposed to fill the output buffer with dummyFile content ASSERT_EQ(sizeof(dummyFile), adv->getBuffer().getLength()); EXPECT_EQ(0, memcmp(adv->getBuffer().getData(), dummyFile, sizeof(dummyFile))); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on buffer4_send can set skip flag and that flag // causes the packet to not be sent TEST_F(HooksDhcpv4SrvTest, buffer4SendSkip) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install buffer4_send_skip_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "buffer4_send", buffer4_send_skip_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 buffer4_send callback. srv_->run(); // Check that the callback called is indeed the one we installed EXPECT_EQ("buffer4_send", callback_name_); // Check that there is no packet sent. ASSERT_EQ(0, srv_->fake_sent_.size()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callouts installed on buffer4_send can set drop flag and that flag // causes the packet to not be sent TEST_F(HooksDhcpv4SrvTest, buffer4SendDrop) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install buffer4_send_drop_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "buffer4_send", buffer4_send_drop_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 buffer4_send callback. srv_->run(); // Check that the callback called is indeed the one we installed EXPECT_EQ("buffer4_send", callback_name_); // Check that there is no packet sent EXPECT_EQ(0, srv_->fake_sent_.size()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // This test checks if subnet4_select callout is triggered and reports // valid parameters TEST_F(HooksDhcpv4SrvTest, subnet4SelectSimple) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Configure 2 subnets, both directly reachable over local interface // (let's not complicate the matter with relays) string config = "{ \"interfaces-config\": {" " \"interfaces\": [ \"*\" ]" "}," "\"rebind-timer\": 2000, " "\"renew-timer\": 1000, " "\"subnet4\": [ { " " \"id\": 1, " " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ]," " \"subnet\": \"192.0.2.0/24\", " " \"interface\": \"eth0\" " " }, {" " \"id\": 2, " " \"pools\": [ { \"pool\": \"192.0.3.0/25\" } ]," " \"subnet\": \"192.0.3.0/24\" " " } ]," "\"valid-lifetime\": 4000 }"; ConstElementPtr json; EXPECT_NO_THROW(json = parseDHCP4(config)); ConstElementPtr status; // Configure the server and make sure the config is accepted EXPECT_NO_THROW(status = Dhcpv4SrvTest::configure(*srv_, json)); ASSERT_TRUE(status); comment_ = parseAnswer(rcode_, status); ASSERT_EQ(0, rcode_); // Commit the config CfgMgr::instance().commit(); // Install subnet4_select_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "subnet4_select", subnet4_select_callout)); // Prepare discover packet. Server should select first subnet for it Pkt4Ptr discover = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234)); discover->setRemoteAddr(IOAddress("192.0.2.1")); discover->setIface("eth1"); discover->setIndex(ETH1_INDEX); OptionPtr clientid = generateClientId(); discover->addOption(clientid); // Pass it to the server and get an advertise Pkt4Ptr adv = srv_->processDiscover(discover); // 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_qry_pkt4_.get() == discover.get()); const Subnet4Collection* exp_subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll(); // 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(callback_subnet4_.get(), exp_subnets->begin()->get()); // Server is supposed to report two subnets ASSERT_EQ(exp_subnets->size(), callback_subnet4collection_->size()); ASSERT_GE(exp_subnets->size(), 2); // Compare that the available subnets are reported as expected EXPECT_TRUE((*exp_subnets->begin())->get() == (*callback_subnet4collection_->begin())->get()); EXPECT_TRUE((*std::next(exp_subnets->begin()))->get() == (*std::next(callback_subnet4collection_->begin()))->get()); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // This test checks if callout installed on subnet4_select hook point can pick // a different subnet. TEST_F(HooksDhcpv4SrvTest, subnet4SelectChange) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Configure 2 subnets, both directly reachable over local interface // (let's not complicate the matter with relays) string config = "{ \"interfaces-config\": {" " \"interfaces\": [ \"*\" ]" "}," "\"rebind-timer\": 2000, " "\"renew-timer\": 1000, " "\"subnet4\": [ { " " \"id\": 1, " " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ]," " \"subnet\": \"192.0.2.0/24\", " " \"interface\": \"eth0\" " " }, {" " \"id\": 2, " " \"pools\": [ { \"pool\": \"192.0.3.0/25\" } ]," " \"subnet\": \"192.0.3.0/24\" " " } ]," "\"valid-lifetime\": 4000 }"; ConstElementPtr json; EXPECT_NO_THROW(json = parseDHCP4(config)); ConstElementPtr status; // Configure the server and make sure the config is accepted EXPECT_NO_THROW(status = Dhcpv4SrvTest::configure(*srv_, json)); ASSERT_TRUE(status); comment_ = parseAnswer(rcode_, status); ASSERT_EQ(0, rcode_); CfgMgr::instance().commit(); // Install subnet4_select_different_subnet_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "subnet4_select", subnet4_select_different_subnet_callout)); // Prepare discover packet. Server should select first subnet for it Pkt4Ptr discover = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234)); discover->setRemoteAddr(IOAddress("192.0.2.1")); discover->setIface("eth0"); discover->setIndex(ETH0_INDEX); OptionPtr clientid = generateClientId(); discover->addOption(clientid); // Pass it to the server and get an advertise Pkt4Ptr adv = srv_->processDiscover(discover); // 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().getCurrentCfg()->getCfgSubnets4()->getAll(); ASSERT_EQ(2, subnets->size()); // Advertised address must belong to the second pool (in subnet's range, // in dynamic pool) auto subnet = subnets->begin(); ++subnet; EXPECT_TRUE((*subnet)->inRange(addr)); EXPECT_TRUE((*subnet)->inPool(Lease::TYPE_V4, addr)); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks that subnet4_select is able to drop the packet. TEST_F(HooksDhcpv4SrvTest, subnet4SelectDrop) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install subnet4_select_drop_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "subnet4_select", subnet4_select_drop_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 subnet4_select callback. srv_->run(); // Check that the server dropped the packet and did not produce any response ASSERT_EQ(0, srv_->fake_sent_.size()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // This test verifies that the leases4_committed hook point is not triggered // for the DHCPDISCOVER. TEST_F(HooksDhcpv4SrvTest, leases4CommittedDiscover) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install leases4_committed_callout ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "leases4_committed", leases4_committed_callout)); Dhcp4Client client(Dhcp4Client::SELECTING); client.setIfaceName("eth1"); client.setIfaceIndex(ETH1_INDEX); ASSERT_NO_THROW(client.doDiscover()); // Make sure that we received a response ASSERT_TRUE(client.getContext().response_); // Make sure that the callout wasn't called. EXPECT_TRUE(callback_name_.empty()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); } // This test verifies that the leases4_committed hook point is not triggered // for the DHCPINFORM. TEST_F(HooksDhcpv4SrvTest, leases4CommittedInform) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "leases4_committed", leases4_committed_callout)); Dhcp4Client client(Dhcp4Client::SELECTING); client.useRelay(); ASSERT_NO_THROW(client.doInform()); // Make sure that we received a response ASSERT_TRUE(client.getContext().response_); // Make sure that the callout wasn't called. EXPECT_TRUE(callback_name_.empty()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); } // This test verifies that the callout installed on the leases4_committed hook // point is executed as a result of DHCPREQUEST message sent to allocate new // lease or renew an existing lease. TEST_F(HooksDhcpv4SrvTest, leases4CommittedRequest) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "leases4_committed", leases4_committed_callout)); Dhcp4Client client(Dhcp4Client::SELECTING); client.setIfaceName("eth1"); client.setIfaceIndex(ETH1_INDEX); ASSERT_NO_THROW(client.doDORA(boost::shared_ptr(new IOAddress("192.0.2.100")))); // Make sure that we received a response ASSERT_TRUE(client.getContext().response_); // Check that the callback called is indeed the one we installed EXPECT_EQ("leases4_committed", callback_name_); // Check if all expected parameters were really received vector expected_argument_names; expected_argument_names.push_back("query4"); expected_argument_names.push_back("deleted_leases4"); expected_argument_names.push_back("leases4"); sort(expected_argument_names.begin(), expected_argument_names.end()); EXPECT_TRUE(callback_argument_names_ == expected_argument_names); // Newly allocated lease should be returned. ASSERT_TRUE(callback_lease4_); EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText()); // Deleted lease must not be present, because it is a new allocation. EXPECT_FALSE(callback_deleted_lease4_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); resetCalloutBuffers(); // Renew the lease and make sure that the callout has been executed. client.setState(Dhcp4Client::RENEWING); ASSERT_NO_THROW(client.doRequest()); // Make sure that we received a response ASSERT_TRUE(client.getContext().response_); // Check that the callback called is indeed the one we installed EXPECT_EQ("leases4_committed", callback_name_); // Renewed lease should be returned. ASSERT_TRUE(callback_lease4_); EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText()); // Deleted lease must not be present, because it is a new allocation. EXPECT_FALSE(callback_deleted_lease4_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); resetCalloutBuffers(); // Let's try to renew again but force the client to request a different // address. client.ciaddr_ = IOAddress("192.0.2.101"); ASSERT_NO_THROW(client.doRequest()); // Make sure that we received a response ASSERT_TRUE(client.getContext().response_); // Check that the callback called is indeed the one we installed EXPECT_EQ("leases4_committed", callback_name_); // New lease should be returned. ASSERT_TRUE(callback_lease4_); EXPECT_EQ("192.0.2.101", callback_lease4_->addr_.toText()); // The old lease should have been deleted. ASSERT_TRUE(callback_deleted_lease4_); EXPECT_EQ("192.0.2.100", callback_deleted_lease4_->addr_.toText()); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); resetCalloutBuffers(); // Now request an address that can't be allocated. client.ciaddr_ = IOAddress("10.0.0.1"); ASSERT_NO_THROW(client.doRequest()); // Make sure that we did not receive a response. Since we're // not authoritative, there should not be a DHCPNAK. ASSERT_FALSE(client.getContext().response_); // Check that no callback was not called. EXPECT_EQ("", callback_name_); EXPECT_FALSE(callback_lease4_); EXPECT_FALSE(callback_deleted_lease4_); } // This test verifies that the leases4_committed callout is executed // with declined leases as argument when DHCPDECLINE is processed. TEST_F(HooksDhcpv4SrvTest, leases4CommittedDecline) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "leases4_committed", leases4_committed_callout)); Dhcp4Client client(Dhcp4Client::SELECTING); client.useRelay(); ASSERT_NO_THROW(client.doDORA(boost::shared_ptr(new IOAddress("192.0.2.100")))); // Make sure that we received a response ASSERT_TRUE(client.getContext().response_); resetCalloutBuffers(); ASSERT_NO_THROW(client.doDecline()); // Check that the callback called is indeed the one we installed EXPECT_EQ("leases4_committed", callback_name_); // Check if all expected parameters were really received vector expected_argument_names; expected_argument_names.push_back("query4"); expected_argument_names.push_back("deleted_leases4"); expected_argument_names.push_back("leases4"); sort(expected_argument_names.begin(), expected_argument_names.end()); EXPECT_TRUE(callback_argument_names_ == expected_argument_names); // No new allocations. ASSERT_TRUE(callback_lease4_); EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText()); EXPECT_EQ(Lease::STATE_DECLINED, callback_lease4_->state_); // Released lease should be returned. EXPECT_FALSE(callback_deleted_lease4_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); } // This test verifies that the leases4_committed callout is executed // with deleted leases as argument when DHCPRELEASE is processed. TEST_F(HooksDhcpv4SrvTest, leases4CommittedRelease) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "leases4_committed", leases4_committed_callout)); Dhcp4Client client(Dhcp4Client::SELECTING); client.setIfaceName("eth1"); client.setIfaceIndex(ETH1_INDEX); ASSERT_NO_THROW(client.doDORA(boost::shared_ptr(new IOAddress("192.0.2.100")))); // Make sure that we received a response ASSERT_TRUE(client.getContext().response_); resetCalloutBuffers(); ASSERT_NO_THROW(client.doRelease()); // Check that the callback called is indeed the one we installed EXPECT_EQ("leases4_committed", callback_name_); // Check if all expected parameters were really received vector expected_argument_names; expected_argument_names.push_back("query4"); expected_argument_names.push_back("deleted_leases4"); expected_argument_names.push_back("leases4"); sort(expected_argument_names.begin(), expected_argument_names.end()); EXPECT_TRUE(callback_argument_names_ == expected_argument_names); // No new allocations. EXPECT_FALSE(callback_lease4_); // Released lease should be returned. ASSERT_TRUE(callback_deleted_lease4_); EXPECT_EQ("192.0.2.100", callback_deleted_lease4_->addr_.toText()); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); } // This test verifies that the callout installed on the leases4_committed hook // point is executed as a result of DHCPREQUEST message sent to reuse an // existing lease. TEST_F(HooksDhcpv4SrvTest, leases4CommittedCache) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "leases4_committed", leases4_committed_callout)); // Modify the subnet to reuse leases. subnet_->setCacheThreshold(.25); Dhcp4Client client(Dhcp4Client::SELECTING); client.setIfaceName("eth1"); client.setIfaceIndex(ETH1_INDEX); ASSERT_NO_THROW(client.doDORA(boost::shared_ptr(new IOAddress("192.0.2.100")))); // Make sure that we received a response ASSERT_TRUE(client.getContext().response_); // Check that the callback called is indeed the one we installed EXPECT_EQ("leases4_committed", callback_name_); // Check if all expected parameters were really received vector expected_argument_names; expected_argument_names.push_back("query4"); expected_argument_names.push_back("deleted_leases4"); expected_argument_names.push_back("leases4"); sort(expected_argument_names.begin(), expected_argument_names.end()); EXPECT_TRUE(callback_argument_names_ == expected_argument_names); // Newly allocated lease should be returned. ASSERT_TRUE(callback_lease4_); EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText()); // Deleted lease must not be present, because it is a new allocation. EXPECT_FALSE(callback_deleted_lease4_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); resetCalloutBuffers(); // Renew the lease and make sure that the callout has been executed. client.setState(Dhcp4Client::RENEWING); ASSERT_NO_THROW(client.doRequest()); // Make sure that we received a response ASSERT_TRUE(client.getContext().response_); // Check that the callback called is indeed the one we installed EXPECT_EQ("leases4_committed", callback_name_); // Renewed lease should not be present because it was reused. EXPECT_FALSE(callback_lease4_); // Deleted lease must not be present, because it renews the same address. EXPECT_FALSE(callback_deleted_lease4_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); } // This test verifies that it is possible to park a packet as a result of // the leases4_committed callouts. TEST_F(HooksDhcpv4SrvTest, leases4CommittedParkRequests) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // This callout uses provided IO service object to post a function // that unparks the packet. The packet is parked and can be unparked // by simply calling IOService::poll. ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "leases4_committed", leases4_committed_park_callout)); // Create first client and perform DORA. Dhcp4Client client1(Dhcp4Client::SELECTING); client1.setIfaceName("eth1"); client1.setIfaceIndex(ETH1_INDEX); ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr(new IOAddress("192.0.2.100")))); // We should be offered an address but the DHCPACK should not arrive // at this point, because the packet is parked. ASSERT_FALSE(client1.getContext().response_); // Check that the callback called is indeed the one we installed EXPECT_EQ("leases4_committed", callback_name_); // Check if all expected parameters were really received vector expected_argument_names; expected_argument_names.push_back("query4"); expected_argument_names.push_back("deleted_leases4"); expected_argument_names.push_back("leases4"); sort(expected_argument_names.begin(), expected_argument_names.end()); EXPECT_TRUE(callback_argument_names_ == expected_argument_names); // Newly allocated lease should be passed to the callout. ASSERT_TRUE(callback_lease4_); EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText()); // Deleted lease must not be present, because it is a new allocation. EXPECT_FALSE(callback_deleted_lease4_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client1.getContext().query_); // Reset all indicators because we'll be now creating a second client. resetCalloutBuffers(); // Create the second client to test that it may communicate with the // server while the previous packet is parked. Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING); client2.setIfaceName("eth1"); client2.setIfaceIndex(ETH1_INDEX); ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr(new IOAddress("192.0.2.101")))); // The DHCPOFFER should have been returned but not DHCPACK, as this // packet got parked too. ASSERT_FALSE(client2.getContext().response_); // Check that the callback called is indeed the one we installed. EXPECT_EQ("leases4_committed", callback_name_); // There should be now two actions scheduled on our IO service // by the invoked callouts. They unpark both DHCPACK messages. ASSERT_NO_THROW(io_service_->poll()); // Receive and check the first response. ASSERT_NO_THROW(client1.receiveResponse()); Pkt4Ptr rsp = client1.getContext().response_; ASSERT_TRUE(rsp); EXPECT_EQ(DHCPACK, rsp->getType()); EXPECT_EQ("192.0.2.100", rsp->getYiaddr().toText()); // Receive and check the second response. ASSERT_NO_THROW(client2.receiveResponse()); rsp = client2.getContext().response_; ASSERT_TRUE(rsp); EXPECT_EQ(DHCPACK, rsp->getType()); EXPECT_EQ("192.0.2.101", rsp->getYiaddr().toText()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client2.getContext().query_); } // This test verifies that incoming (positive) REQUEST/Renewing can be handled // properly and that callout installed on lease4_renew is triggered with // expected parameters. TEST_F(HooksDhcpv4SrvTest, lease4RenewSimple) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); const IOAddress addr("192.0.2.106"); const uint32_t temp_valid = 100; const time_t temp_timestamp = time(NULL) - 10; // Install lease4_renew_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "lease4_renew", lease4_renew_callout)); // Generate client-id also sets client_id_ member OptionPtr clientid = generateClientId(); // Check that the address we are about to use is indeed in pool ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr)); // let's create a lease and put it in the LeaseMgr uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER)); Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, &client_id_->getClientId()[0], client_id_->getClientId().size(), temp_valid, temp_timestamp, subnet_->getID())); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used)); // Check that the lease is really in the database Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(l); // Let's create a RENEW Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234)); req->setRemoteAddr(IOAddress(addr)); req->setYiaddr(addr); req->setCiaddr(addr); // client's address req->setIface("eth0"); req->setIndex(ETH0_INDEX); req->setHWAddr(hwaddr2); req->addOption(clientid); req->addOption(srv_->getServerID()); // Pass it to the server and hope for a response Pkt4Ptr ack = srv_->processRequest(req); // Check if we get response at all checkResponse(ack, DHCPACK, 1234); // Check that the lease is really in the database l = checkLease(ack, clientid, req->getHWAddr(), addr); ASSERT_TRUE(l); // Check that preferred, valid and cltt were really updated EXPECT_EQ(l->valid_lft_, subnet_->getValid()); // Check that the callback called is indeed the one we installed EXPECT_EQ("lease4_renew", callback_name_); // Check that query4 argument passing was successful and // returned proper value EXPECT_TRUE(callback_qry_pkt4_.get() == req.get()); // Check that hwaddr parameter is passed properly ASSERT_TRUE(callback_hwaddr_); EXPECT_TRUE(*callback_hwaddr_ == *req->getHWAddr()); // Check that the subnet is passed properly ASSERT_TRUE(callback_subnet4_); EXPECT_EQ(callback_subnet4_->toText(), subnet_->toText()); ASSERT_TRUE(callback_clientid_); ASSERT_TRUE(client_id_); EXPECT_TRUE(*client_id_ == *callback_clientid_); // Check if all expected parameters were really received vector expected_argument_names; expected_argument_names.push_back("query4"); expected_argument_names.push_back("subnet4"); expected_argument_names.push_back("clientid"); expected_argument_names.push_back("hwaddr"); expected_argument_names.push_back("lease4"); sort(callback_argument_names_.begin(), callback_argument_names_.end()); sort(expected_argument_names.begin(), expected_argument_names.end()); EXPECT_TRUE(callback_argument_names_ == expected_argument_names); Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(addr); EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease)); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(req); } // This test verifies that a callout installed on lease4_renew can trigger // the server to not renew a lease. TEST_F(HooksDhcpv4SrvTest, lease4RenewSkip) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); const IOAddress addr("192.0.2.106"); const uint32_t temp_valid = 100; const time_t temp_timestamp = time(NULL) - 10; // Install lease4_renew_skip_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "lease4_renew", lease4_renew_skip_callout)); // Generate client-id also sets client_id_ member OptionPtr clientid = generateClientId(); // Check that the address we are about to use is indeed in pool ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr)); // let's create a lease and put it in the LeaseMgr uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER)); Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, &client_id_->getClientId()[0], client_id_->getClientId().size(), temp_valid, temp_timestamp, subnet_->getID())); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used)); // Check that the lease is really in the database Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(l); // Check that preferred, valid and cltt really set. // Constructed lease looks as if it was assigned 10 seconds ago EXPECT_EQ(l->valid_lft_, temp_valid); EXPECT_EQ(l->cltt_, temp_timestamp); // Let's create a RENEW Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234)); req->setRemoteAddr(IOAddress(addr)); req->setYiaddr(addr); req->setCiaddr(addr); // client's address req->setIface("eth0"); req->setIndex(ETH0_INDEX); req->setHWAddr(hwaddr2); req->addOption(clientid); req->addOption(srv_->getServerID()); // Pass it to the server and hope for a response Pkt4Ptr ack = srv_->processRequest(req); ASSERT_TRUE(ack); // Check that the lease is really in the database l = checkLease(ack, clientid, req->getHWAddr(), addr); ASSERT_TRUE(l); // Check that valid and cltt were NOT updated EXPECT_EQ(temp_valid, l->valid_lft_); EXPECT_EQ(temp_timestamp, l->cltt_); Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(addr); EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease)); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(req); } // This test verifies that valid RELEASE triggers lease4_release callouts TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimple) { IfaceMgrTestConfig test_config(true); CfgMgr::instance().getCurrentCfg()->getCfgExpiration()->setFlushReclaimedTimerWaitTime(0); CfgMgr::instance().getCurrentCfg()->getCfgExpiration()->setHoldReclaimedTime(0); IfaceMgr::instance().openSockets4(); const IOAddress addr("192.0.2.106"); const uint32_t temp_valid = 100; const time_t temp_timestamp = time(NULL) - 10; // Install lease4_release_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "lease4_release", lease4_release_callout)); // Generate client-id also duid_ OptionPtr clientid = generateClientId(); // Check that the address we are about to use is indeed in pool ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr)); // Let's create a lease and put it in the LeaseMgr uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER)); Lease4Ptr used(new Lease4(addr, hw, &client_id_->getClientId()[0], client_id_->getClientId().size(), temp_valid, temp_timestamp, subnet_->getID())); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used)); // Check that the lease is really in the database Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(l); // Let's create a RELEASE // Generate client-id also duid_ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234)); rel->setRemoteAddr(addr); rel->setCiaddr(addr); rel->addOption(clientid); rel->addOption(srv_->getServerID()); rel->setHWAddr(hw); // Note: there is no response to RELEASE in DHCPv4 EXPECT_NO_THROW(srv_->processRelease(rel)); // The lease should be gone from LeaseMgr l = LeaseMgrFactory::instance().getLease4(addr); EXPECT_FALSE(l); // Try to get the lease by hardware address Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*hw); EXPECT_EQ(leases.size(), 0); // Try to get it by hw/subnet_id combination l = LeaseMgrFactory::instance().getLease4(*hw, subnet_->getID()); EXPECT_FALSE(l); // Try by client-id leases = LeaseMgrFactory::instance().getLease4(*client_id_); EXPECT_EQ(leases.size(), 0); // Try by client-id/subnet-id l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID()); EXPECT_FALSE(l); // Ok, the lease is *really* not there. // Check that the callback called is indeed the one we installed EXPECT_EQ("lease4_release", callback_name_); // Check that pkt4 argument passing was successful and returned proper value EXPECT_TRUE(callback_qry_pkt4_.get() == rel.get()); // Check if all expected parameters were really received vector expected_argument_names; expected_argument_names.push_back("query4"); expected_argument_names.push_back("lease4"); sort(callback_argument_names_.begin(), callback_argument_names_.end()); sort(expected_argument_names.begin(), expected_argument_names.end()); EXPECT_TRUE(callback_argument_names_ == expected_argument_names); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(rel); } // This test verifies that valid RELEASE triggers lease4_release callouts // This test is using infinite lease with lease affinity enabled. TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimpleInfiniteLease) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); const IOAddress addr("192.0.2.106"); const uint32_t temp_valid = Lease::INFINITY_LFT; const time_t temp_timestamp = time(NULL) - 10; // Install lease4_release_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "lease4_release", lease4_release_callout)); // Generate client-id also duid_ OptionPtr clientid = generateClientId(); // Check that the address we are about to use is indeed in pool ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr)); // Let's create a lease and put it in the LeaseMgr uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER)); Lease4Ptr used(new Lease4(addr, hw, &client_id_->getClientId()[0], client_id_->getClientId().size(), temp_valid, temp_timestamp, subnet_->getID())); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used)); // Check that the lease is really in the database Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(l); // Let's create a RELEASE // Generate client-id also duid_ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234)); rel->setRemoteAddr(addr); rel->setCiaddr(addr); rel->addOption(clientid); rel->addOption(srv_->getServerID()); rel->setHWAddr(hw); // Note: there is no response to RELEASE in DHCPv4 EXPECT_NO_THROW(srv_->processRelease(rel)); // The lease should be gone from LeaseMgr l = LeaseMgrFactory::instance().getLease4(addr); EXPECT_FALSE(l); // Try to get the lease by hardware address Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*hw); EXPECT_EQ(leases.size(), 0); // Try to get it by hw/subnet_id combination l = LeaseMgrFactory::instance().getLease4(*hw, subnet_->getID()); EXPECT_FALSE(l); // Try by client-id leases = LeaseMgrFactory::instance().getLease4(*client_id_); EXPECT_EQ(leases.size(), 0); // Try by client-id/subnet-id l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID()); EXPECT_FALSE(l); // Ok, the lease is *really* not there. // Check that the callback called is indeed the one we installed EXPECT_EQ("lease4_release", callback_name_); // Check that pkt4 argument passing was successful and returned proper value EXPECT_TRUE(callback_qry_pkt4_.get() == rel.get()); // Check if all expected parameters were really received vector expected_argument_names; expected_argument_names.push_back("query4"); expected_argument_names.push_back("lease4"); sort(callback_argument_names_.begin(), callback_argument_names_.end()); sort(expected_argument_names.begin(), expected_argument_names.end()); EXPECT_TRUE(callback_argument_names_ == expected_argument_names); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(rel); } // This test verifies that valid RELEASE triggers lease4_release callouts TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimpleNoDelete) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); const IOAddress addr("192.0.2.106"); const uint32_t temp_valid = 100; const time_t temp_timestamp = time(NULL) - 10; // Install lease4_release_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "lease4_release", lease4_release_callout)); // Generate client-id also duid_ OptionPtr clientid = generateClientId(); // Check that the address we are about to use is indeed in pool ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr)); // Let's create a lease and put it in the LeaseMgr uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER)); Lease4Ptr used(new Lease4(addr, hw, &client_id_->getClientId()[0], client_id_->getClientId().size(), temp_valid, temp_timestamp, subnet_->getID())); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used)); // Check that the lease is really in the database Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(l); // Let's create a RELEASE // Generate client-id also duid_ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234)); rel->setRemoteAddr(addr); rel->setCiaddr(addr); rel->addOption(clientid); rel->addOption(srv_->getServerID()); rel->setHWAddr(hw); // Note: there is no response to RELEASE in DHCPv4 EXPECT_NO_THROW(srv_->processRelease(rel)); // The lease should not be gone from LeaseMgr l = LeaseMgrFactory::instance().getLease4(addr); EXPECT_TRUE(l); // Try to get the lease by hardware address Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*hw); EXPECT_EQ(leases.size(), 1); // Try to get it by hw/subnet_id combination l = LeaseMgrFactory::instance().getLease4(*hw, subnet_->getID()); EXPECT_TRUE(l); // Try by client-id leases = LeaseMgrFactory::instance().getLease4(*client_id_); EXPECT_EQ(leases.size(), 1); // Try by client-id/subnet-id l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID()); EXPECT_TRUE(l); // Ok, the lease is *really* there. // Check that the callback called is indeed the one we installed EXPECT_EQ("lease4_release", callback_name_); // Check that pkt4 argument passing was successful and returned proper value EXPECT_TRUE(callback_qry_pkt4_.get() == rel.get()); // Check if all expected parameters were really received vector expected_argument_names; expected_argument_names.push_back("query4"); expected_argument_names.push_back("lease4"); sort(callback_argument_names_.begin(), callback_argument_names_.end()); sort(expected_argument_names.begin(), expected_argument_names.end()); EXPECT_TRUE(callback_argument_names_ == expected_argument_names); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(rel); } // This test verifies that skip flag returned by a callout installed on the // lease4_release hook point will keep the lease. TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSkip) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); const IOAddress addr("192.0.2.106"); const uint32_t temp_valid = 100; const time_t temp_timestamp = time(NULL) - 10; // Install lease4_release_skip_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "lease4_release", lease4_release_skip_callout)); // Generate client-id also duid_ OptionPtr clientid = generateClientId(); // Check that the address we are about to use is indeed in pool ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr)); // Let's create a lease and put it in the LeaseMgr uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER)); Lease4Ptr used(new Lease4(addr, hw, &client_id_->getClientId()[0], client_id_->getClientId().size(), temp_valid, temp_timestamp, subnet_->getID())); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used)); // Check that the lease is really in the database Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(l); // Let's create a RELEASE // Generate client-id also duid_ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234)); rel->setRemoteAddr(addr); rel->setYiaddr(addr); rel->addOption(clientid); rel->addOption(srv_->getServerID()); rel->setHWAddr(hw); // Note: there is no response to RELEASE in DHCPv4 EXPECT_NO_THROW(srv_->processRelease(rel)); // The lease should be still there l = LeaseMgrFactory::instance().getLease4(addr); EXPECT_TRUE(l); // Try by client-id/subnet-id l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID()); EXPECT_TRUE(l); // Try to get the lease by hardware address, should succeed Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*hw); EXPECT_EQ(leases.size(), 1); // Try by client-id, should be successful as well. leases = LeaseMgrFactory::instance().getLease4(*client_id_); EXPECT_EQ(leases.size(), 1); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(rel); } // This test verifies that drop flag returned by a callout installed on the // lease4_release hook point will keep the lease. TEST_F(HooksDhcpv4SrvTest, lease4ReleaseDrop) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); const IOAddress addr("192.0.2.106"); const uint32_t temp_valid = 100; const time_t temp_timestamp = time(NULL) - 10; // Install lease4_release_drop_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "lease4_release", lease4_release_drop_callout)); // Generate client-id also duid_ OptionPtr clientid = generateClientId(); // Check that the address we are about to use is indeed in pool ASSERT_TRUE(subnet_->inPool(Lease::TYPE_V4, addr)); // Let's create a lease and put it in the LeaseMgr uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER)); Lease4Ptr used(new Lease4(addr, hw, &client_id_->getClientId()[0], client_id_->getClientId().size(), temp_valid, temp_timestamp, subnet_->getID())); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used)); // Check that the lease is really in the database Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(l); // Let's create a RELEASE // Generate client-id also duid_ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234)); rel->setRemoteAddr(addr); rel->setYiaddr(addr); rel->addOption(clientid); rel->addOption(srv_->getServerID()); rel->setHWAddr(hw); // Note: there is no response to RELEASE in DHCPv4 EXPECT_NO_THROW(srv_->processRelease(rel)); // The lease should be still there l = LeaseMgrFactory::instance().getLease4(addr); EXPECT_TRUE(l); // Try by client-id/subnet-id l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID()); EXPECT_TRUE(l); // Try to get the lease by hardware address, should succeed Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*hw); EXPECT_EQ(leases.size(), 1); // Try by client-id, should be successful as well. leases = LeaseMgrFactory::instance().getLease4(*client_id_); EXPECT_EQ(leases.size(), 1); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(rel); } // This test checks that the basic decline hook (lease4_decline) is // triggered properly. TEST_F(HooksDhcpv4SrvTest, lease4DeclineSimple) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install lease4_decline_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "lease4_decline", lease4_decline_callout)); HooksManager::setTestMode(true); // Conduct the actual DORA + Decline. Dhcp4Client client(Dhcp4Client::SELECTING); acquireAndDecline(client, "01:02:03:04:05:06", "12:14", "01:02:03:04:05:06", "12:14", SHOULD_PASS); EXPECT_EQ("lease4_decline", callback_name_); // Verifying DHCPDECLINE is a bit tricky, as it is created somewhere in // acquireAndDecline. We'll just verify that it's really a DECLINE // and that its address is equal to what we have in LeaseMgr. ASSERT_TRUE(callback_qry_pkt4_); ASSERT_TRUE(callback_lease4_); // Check that it's the proper packet that was reported. EXPECT_EQ(DHCPDECLINE, callback_qry_pkt4_->getType()); // Extract the address being declined. OptionCustomPtr opt_declined_addr = boost::dynamic_pointer_cast< OptionCustom>(callback_qry_pkt4_->getOption(DHO_DHCP_REQUESTED_ADDRESS)); ASSERT_TRUE(opt_declined_addr); IOAddress addr(opt_declined_addr->readAddress()); // And try to get a matching lease from the lease manager. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(from_mgr); EXPECT_EQ(Lease::STATE_DECLINED, from_mgr->state_); // Let's now check that those 3 things (packet, lease returned and lease from // the lease manager) all match. EXPECT_EQ(addr, from_mgr->addr_); EXPECT_EQ(addr, callback_lease4_->addr_); // Pkt passed to a callout must be configured to copy retrieved options. EXPECT_TRUE(callback_qry_options_copy_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); } // Test that the lease4_decline hook point can handle SKIP status. TEST_F(HooksDhcpv4SrvTest, lease4DeclineSkip) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install lease4_decline_skip_callout. It will set the status to skip EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "lease4_decline", lease4_decline_skip_callout)); HooksManager::setTestMode(true); // Conduct the actual DORA + Decline. The DECLINE should fail, as the // hook will set the status to SKIP. Dhcp4Client client(Dhcp4Client::SELECTING); acquireAndDecline(client, "01:02:03:04:05:06", "12:14", "01:02:03:04:05:06", "12:14", SHOULD_FAIL); EXPECT_EQ("lease4_decline", callback_name_); // Verifying DHCPDECLINE is a bit tricky, as it is created somewhere in // acquireAndDecline. We'll just verify that it's really a DECLINE // and that its address is equal to what we have in LeaseMgr. ASSERT_TRUE(callback_qry_pkt4_); ASSERT_TRUE(callback_lease4_); // Check that it's the proper packet that was reported. EXPECT_EQ(DHCPDECLINE, callback_qry_pkt4_->getType()); // Extract the address being declined. OptionCustomPtr opt_declined_addr = boost::dynamic_pointer_cast< OptionCustom>(callback_qry_pkt4_->getOption(DHO_DHCP_REQUESTED_ADDRESS)); ASSERT_TRUE(opt_declined_addr); IOAddress addr(opt_declined_addr->readAddress()); // And try to get a matching lease from the lease manager. The lease should // still be there in default state, not in declined state. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(from_mgr); EXPECT_EQ(Lease::STATE_DEFAULT, from_mgr->state_); // As a final sanity check, let's now check that those 3 things (packet, // lease returned and lease from the lease manager) all match. EXPECT_EQ(addr, from_mgr->addr_); EXPECT_EQ(addr, callback_lease4_->addr_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); } // Test that the lease4_decline hook point can handle DROP status. TEST_F(HooksDhcpv4SrvTest, lease4DeclineDrop) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Install lease4_decline_drop_callout. It will set the status to drop EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "lease4_decline", lease4_decline_drop_callout)); HooksManager::setTestMode(true); // Conduct the actual DORA + Decline. The DECLINE should fail, as the // hook will set the status to DROP. Dhcp4Client client(Dhcp4Client::SELECTING); acquireAndDecline(client, "01:02:03:04:05:06", "12:14", "01:02:03:04:05:06", "12:14", SHOULD_FAIL); EXPECT_EQ("lease4_decline", callback_name_); // Verifying DHCPDECLINE is a bit tricky, as it is created somewhere in // acquireAndDecline. We'll just verify that it's really a DECLINE // and that its address is equal to what we have in LeaseMgr. ASSERT_TRUE(callback_qry_pkt4_); ASSERT_TRUE(callback_lease4_); // Check that it's the proper packet that was reported. EXPECT_EQ(DHCPDECLINE, callback_qry_pkt4_->getType()); // Extract the address being declined. OptionCustomPtr opt_declined_addr = boost::dynamic_pointer_cast< OptionCustom>(callback_qry_pkt4_->getOption(DHO_DHCP_REQUESTED_ADDRESS)); ASSERT_TRUE(opt_declined_addr); IOAddress addr(opt_declined_addr->readAddress()); // And try to get a matching lease from the lease manager. The lease should // still be there in default state, not in declined state. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(from_mgr); EXPECT_EQ(Lease::STATE_DEFAULT, from_mgr->state_); // As a final sanity check, let's now check that those 3 things (packet, // lease returned and lease from the lease manager) all match. EXPECT_EQ(addr, from_mgr->addr_); EXPECT_EQ(addr, callback_lease4_->addr_); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(client.getContext().query_); } // Checks if callout installed on host4_identifier can generate an // identifier and whether that identifier is actually used. TEST_F(HooksDhcpv4SrvTest, host4Identifier) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Configure a subnet with host reservation. The reservation is based on // flexible identifier value of 'foo'. That's exactly what the // host4_identifier_foo_callout sets. string config = "{ \"interfaces-config\": {" " \"interfaces\": [ \"*\" ]" "}," "\"rebind-timer\": 2000, " "\"renew-timer\": 1000, " "\"host-reservation-identifiers\": [ \"flex-id\" ], " "\"subnet4\": [ { " " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ]," " \"subnet\": \"192.0.2.0/24\", " " \"id\": 1, " " \"interface\": \"eth0\", " " \"reservations\": [" " {" " \"flex-id\": \"'foo'\"," " \"ip-address\": \"192.0.2.201\"" " }" " ]" "} ]," "\"valid-lifetime\": 4000 }"; ConstElementPtr json; EXPECT_NO_THROW(json = parseDHCP4(config)); ASSERT_TRUE(json); ConstElementPtr status; // Configure the server and make sure the config is accepted EXPECT_NO_THROW(status = Dhcpv4SrvTest::configure(*srv_, json)); ASSERT_TRUE(status); comment_ = parseAnswer(rcode_, status); ASSERT_EQ(0, rcode_); CfgMgr::instance().commit(); // Install host4_identifier_foo_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "host4_identifier", host4_identifier_foo_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 response ASSERT_EQ(1, srv_->fake_sent_.size()); // Make sure that we received a response Pkt4Ptr adv = srv_->fake_sent_.front(); ASSERT_TRUE(adv); // Make sure the address offered is the one that was reserved. EXPECT_EQ("192.0.2.201", adv->getYiaddr().toText()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Checks if callout installed on host4_identifier can generate an identifier of // other type. This particular callout always returns hwaddr. TEST_F(HooksDhcpv4SrvTest, host4IdentifierHWAddr) { IfaceMgrTestConfig test_config(true); IfaceMgr::instance().openSockets4(); // Configure a subnet with host reservation. The reservation is based on // flexible identifier value of 'foo'. That's exactly what the // host4_identifier_foo_callout sets. string config = "{ \"interfaces-config\": {" " \"interfaces\": [ \"*\" ]" "}," "\"rebind-timer\": 2000, " "\"renew-timer\": 1000, " "\"host-reservation-identifiers\": [ \"flex-id\" ], " "\"subnet4\": [ { " " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ]," " \"subnet\": \"192.0.2.0/24\", " " \"id\": 1, " " \"interface\": \"eth0\", " " \"reservations\": [" " {" " \"hw-address\": \"00:01:02:03:04:05\"," " \"ip-address\": \"192.0.2.201\"" " }" " ]" "} ]," "\"valid-lifetime\": 4000 }"; ConstElementPtr json; EXPECT_NO_THROW(json = parseDHCP4(config)); ASSERT_TRUE(json); ConstElementPtr status; // Configure the server and make sure the config is accepted EXPECT_NO_THROW(status = Dhcpv4SrvTest::configure(*srv_, json)); ASSERT_TRUE(status); comment_ = parseAnswer(rcode_, status); ASSERT_EQ(0, rcode_); CfgMgr::instance().commit(); // Install host4_identifier_hwaddr_callout EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "host4_identifier", host4_identifier_hwaddr_callout)); // Let's create a simple DISCOVER Pkt4Ptr discover = generateSimpleDiscover(); // Simulate that we have received that traffic srv_->fakeReceive(discover); // 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 response ASSERT_EQ(1, srv_->fake_sent_.size()); // Make sure that we received a response Pkt4Ptr adv = srv_->fake_sent_.front(); ASSERT_TRUE(adv); // Make sure the address offered is the one that was reserved. EXPECT_EQ("192.0.2.201", adv->getYiaddr().toText()); // Check if the callout handle state was reset after the callout. checkCalloutHandleReset(discover); } // Verifies that libraries are unloaded by server destruction // The callout libraries write their library index number to a marker // file upon load and unload, making it simple to test whether or not // the load and unload callouts have been invoked. TEST_F(LoadUnloadDhcpv4SrvTest, unloadLibraries) { ASSERT_NO_THROW(server_.reset(new NakedDhcpv4Srv())); // Ensure no marker files to start with. ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE)); ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE)); // Load the test libraries HookLibsCollection libraries; libraries.push_back(make_pair(std::string(CALLOUT_LIBRARY_1), ConstElementPtr())); libraries.push_back(make_pair(std::string(CALLOUT_LIBRARY_2), ConstElementPtr())); ASSERT_TRUE(HooksManager::loadLibraries(libraries)); // Verify that they load functions created the LOAD_MARKER_FILE // and that its contents are correct: "12" - the first library // appends "1" to the file, the second appends "2"). Also // check that the unload marker file does not yet exist. EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12")); EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE)); // Destroy the server, instance which should unload the libraries. server_.reset(); // Check that the libraries were unloaded. The libraries are // unloaded in the reverse order to which they are loaded, and // this should be reflected in the unload file. EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21")); EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12")); } // Verifies that libraries incompatible with multi threading are not loaded by // the server. // The callout libraries write their library index number to a marker // file upon load and unload, making it simple to test whether or not // the load and unload callouts have been invoked. TEST_F(LoadUnloadDhcpv4SrvTest, failLoadIncompatibleLibraries) { ASSERT_NO_THROW(server_.reset(new NakedDhcpv4Srv())); // Ensure no marker files to start with. ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE)); ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE)); // Load the test libraries HookLibsCollection libraries; libraries.push_back(make_pair(std::string(CALLOUT_LIBRARY_2), ConstElementPtr())); ASSERT_FALSE(HooksManager::loadLibraries(libraries, true)); // The library is missing multi_threading_compatible function so loading // should fail EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE)); EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE)); libraries.clear(); libraries.push_back(make_pair(std::string(CALLOUT_LIBRARY_3), ConstElementPtr())); ASSERT_FALSE(HooksManager::loadLibraries(libraries, true)); // The library is not multi threading compatible so loading should fail EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE)); EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE)); // Destroy the server, instance which should unload the libraries. server_.reset(); // Check that the libraries were unloaded. The libraries are // unloaded in the reverse order to which they are loaded, and // this should be reflected in the unload file. EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE)); EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE)); } // Checks if callouts installed on the dhcp4_srv_configured ared indeed called // and all the necessary parameters are passed. TEST_F(LoadUnloadDhcpv4SrvTest, Dhcpv4SrvConfigured) { for (string parameters : { "", R"(, "parameters": { "mode": "fail-without-error" } )", R"(, "parameters": { "mode": "fail-with-error" } )"}) { reset(); boost::shared_ptr srv(new ControlledDhcpv4Srv(0)); // Ensure no marker files to start with. ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE)); ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE)); ASSERT_FALSE(checkMarkerFileExists(SRV_CONFIG_MARKER_FILE)); // Minimal valid configuration for the server. It includes the // section which loads the callout library #3, which implements // dhcp4_srv_configured callout. MT needs to be disabled // since the library is single-threaded. string config_str = "{" " \"interfaces-config\": {" " \"interfaces\": [ ]" " }," " \"rebind-timer\": 2000," " \"renew-timer\": 1000," " \"subnet4\": [ ]," " \"valid-lifetime\": 4000," " \"lease-database\": {" " \"type\": \"memfile\"," " \"persist\": false" " }," " \"hooks-libraries\": [" " {" " \"library\": \"" + std::string(CALLOUT_LIBRARY_3) + "\"" + parameters + " }" R"( ], "multi-threading": { "enable-multi-threading": false } })"; ConstElementPtr config = Element::fromJSON(config_str); // Configure the server. ConstElementPtr answer; ASSERT_NO_THROW(answer = srv->processConfig(config)); // Make sure there were no errors. int status_code; parseAnswer(status_code, answer); if (parameters.empty()) { EXPECT_EQ(0, status_code); string expected = "{ \"arguments\": { \"hash\": \""; ConstElementPtr config = CfgMgr::instance().getStagingCfg()->toElement(); expected += BaseCommandMgr::getHash(config); expected += "\" }, \"result\": 0, \"text\": "; expected += "\"Configuration successful.\" }"; EXPECT_EQ(answer->str(), expected); } else { EXPECT_EQ(1, status_code); if (parameters.find("fail-without-error") != string::npos) { EXPECT_EQ(answer->str(), R"({ "result": 1, "text": "unknown error" })"); } else if (parameters.find("fail-with-error") != string::npos) { EXPECT_EQ(answer->str(), R"({ "result": 1, "text": "user explicitly configured me to fail" })"); } else { GTEST_FAIL() << "unchecked test case"; } } // The hook library should have been loaded. EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "3")); EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE)); // The dhcp4_srv_configured should have been invoked and the provided // parameters should be recorded. EXPECT_TRUE(checkMarkerFile(SRV_CONFIG_MARKER_FILE, "3io_contextjson_confignetwork_stateserver_config")); // Destroy the server, instance which should unload the libraries. srv.reset(); // The server was destroyed, so the unload() function should now // include the library number in its marker file. EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "3")); EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "3")); EXPECT_TRUE(checkMarkerFile(SRV_CONFIG_MARKER_FILE, "3io_contextjson_confignetwork_stateserver_config")); } } // This test verifies that parked-packet-limit is properly enforced. TEST_F(HooksDhcpv4SrvTest, leases4ParkedPacketLimit) { IfaceMgrTestConfig test_config(true); string config = "{ \"interfaces-config\": {" " \"interfaces\": [ \"*\" ]" "}," "\"rebind-timer\": 2000, " "\"renew-timer\": 1000, " "\"parked-packet-limit\": 1," "\"subnet4\": [ { " " \"pools\": [ { \"pool\": \"192.0.2.0/24\" } ]," " \"subnet\": \"192.0.2.0/24\", " " \"id\": 1, " " \"interface\": \"eth1\" " " } ]," "\"valid-lifetime\": 4000" "}"; ConstElementPtr json; EXPECT_NO_THROW(json = parseDHCP4(config)); ConstElementPtr status; // Configure the server and make sure the config is accepted EXPECT_NO_THROW(status = Dhcpv4SrvTest::configure(*srv_, json)); ASSERT_TRUE(status); comment_ = parseAnswer(rcode_, status); ASSERT_EQ(0, rcode_); // Commit the config CfgMgr::instance().commit(); IfaceMgr::instance().openSockets4(); // This callout uses the provided IO service object to post a function // that unparks the packet. Once the packet is parked, it can be unparked // by simply calling IOService::poll. ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout( "leases4_committed", leases4_committed_park_callout)); // Statistic should not show any drops. EXPECT_EQ(0, getStatistic("pkt4-receive-drop")); // Create a client and initiate a DORA cycle for it. Dhcp4Client client(Dhcp4Client::SELECTING); client.setIfaceName("eth1"); client.setIfaceIndex(ETH1_INDEX); ASSERT_NO_THROW(client.doDORA(boost::shared_ptr(new IOAddress("192.0.2.100")))); // Check that the callback called is indeed the one we installed ASSERT_EQ("leases4_committed", callback_name_); // Make sure that we have not received a response. ASSERT_FALSE(client.getContext().response_); // Verify we have a packet parked. const auto& parking_lot = ServerHooks::getServerHooks().getParkingLotPtr("leases4_committed"); ASSERT_TRUE(parking_lot); ASSERT_EQ(1, parking_lot->size()); // Clear callout buffers. resetCalloutBuffers(); // Create a second client and initiate a DORA for it. // Since the parking lot limit has been reached, the packet // should be dropped with no response. Dhcp4Client client2(Dhcp4Client::SELECTING); client2.setIfaceName("eth1"); client2.setIfaceIndex(ETH1_INDEX); ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr(new IOAddress("192.0.2.101")))); // Check that no callback was called. ASSERT_EQ("", callback_name_); // Make sure that we have not received a response. ASSERT_FALSE(client2.getContext().response_); // Verify we have not parked another packet. ASSERT_EQ(1, parking_lot->size()); // Statistic should show one drop. EXPECT_EQ(1, getStatistic("pkt4-receive-drop")); // Invoking poll should run the scheduled action only for // the first client. ASSERT_NO_THROW(io_service_->poll()); // Receive and check the first response. ASSERT_NO_THROW(client.receiveResponse()); Pkt4Ptr rsp = client.getContext().response_; ASSERT_TRUE(rsp); EXPECT_EQ(DHCPACK, rsp->getType()); EXPECT_EQ("192.0.2.100", rsp->getYiaddr().toText()); // Verify we have no parked packets. ASSERT_EQ(0, parking_lot->size()); resetCalloutBuffers(); // Try client2 again. ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr(new IOAddress("192.0.2.101")))); // Check that the callback called is indeed the one we installed ASSERT_EQ("leases4_committed", callback_name_); // Make sure that we have not received a response. ASSERT_FALSE(client2.getContext().response_); // Verify we parked the packet. ASSERT_EQ(1, parking_lot->size()); // Invoking poll should run the scheduled action. ASSERT_NO_THROW(io_service_->poll()); // Receive and check the first response. ASSERT_NO_THROW(client2.receiveResponse()); rsp = client2.getContext().response_; ASSERT_TRUE(rsp); EXPECT_EQ(DHCPACK, rsp->getType()); EXPECT_EQ("192.0.2.101", rsp->getYiaddr().toText()); // Verify we have no parked packets. ASSERT_EQ(0, parking_lot->size()); // Statistic should still show one drop. EXPECT_EQ(1, getStatistic("pkt4-receive-drop")); } } // namespace