From: Tomek Mrugalski Date: Fri, 11 Oct 2013 16:01:24 +0000 (+0200) Subject: [master] Merge branch 'trac3184' (RAI option echoed back in DHCPv4) X-Git-Tag: bind10-1.2.0beta1-release~185 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5eede0e0f040d932d1a4f4e93d46afcd39b91cd7;p=thirdparty%2Fkea.git [master] Merge branch 'trac3184' (RAI option echoed back in DHCPv4) Conflicts: ChangeLog src/bin/dhcp4/tests/dhcp4_srv_unittest.cc --- 5eede0e0f040d932d1a4f4e93d46afcd39b91cd7 diff --cc ChangeLog index 77feb7d42f,2a2dfe52d5..a8af86281f --- a/ChangeLog +++ b/ChangeLog @@@ -1,12 -1,7 +1,17 @@@ -6XX. [bug] tomek ++690. [bug] tomek + b10-dhcp4: Relay Agent Info option is now echoed back in + DHCPv4 responses. - (Trac #3184, git ABCD) ++ (Trac #3184, git 287389c049518bff66bdf6a5a49bb8768be02d8e) ++ +689. [func]* marcin + b10-dhcp4 and b10-dhcp6 install callback functions which parse options + in the received DHCP packets. + (Trac #3180, git f73fba3cde9421acbeb9486c615900b0af58fa25) + +688. [func] tomek + b10-dhcp6: Prefix Delegation support is now extended to + Renew and Release messages. + (Trac #3153,#3154, git 3207932815f58045acea84ae092e0a5aa7c4bfd7) 687. [func] tomek b10-dhcp6: Prefix Delegation (IA_PD and IAPREFIX options) is now diff --cc src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index 5ebe47e9ef,57ef2817a1..3f2da84f9b --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@@ -20,10 -21,6 +21,10 @@@ #include #include #include ++#include +#include +#include - #include +#include #include #include #include @@@ -52,596 -49,12 +53,113 @@@ using namespace isc::data using namespace isc::asiolink; using namespace isc::hooks; ++#if 0 +namespace { + +class NakedDhcpv4Srv: public Dhcpv4Srv { + // "Naked" DHCPv4 server, exposes internal fields +public: + + /// @brief Constructor. + /// + /// This constructor disables default modes of operation used by the + /// Dhcpv4Srv class: + /// - Send/receive broadcast messages through sockets on interfaces + /// which support broadcast traffic. + /// - Direct DHCPv4 traffic - communication with clients which do not + /// have IP address assigned yet. + /// + /// Enabling these modes requires root privilges so they must be + /// disabled for unit testing. + /// + /// Note, that disabling broadcast options on sockets does not impact + /// the operation of these tests because they use local loopback + /// interface which doesn't have broadcast capability anyway. It rather + /// prevents setting broadcast options on other (broadcast capable) + /// sockets which are opened on other interfaces in Dhcpv4Srv constructor. + /// + /// The Direct DHCPv4 Traffic capability can be disabled here because + /// it is tested with PktFilterLPFTest unittest. The tests which belong + /// to PktFilterLPFTest can be enabled on demand when root privileges can + /// be guaranteed. + /// + /// @param port port number to listen on; the default value 0 indicates + /// that sockets should not be opened. + NakedDhcpv4Srv(uint16_t port = 0) + : Dhcpv4Srv(port, "type=memfile", false, false) { + } + + /// @brief fakes packet reception + /// @param timeout ignored + /// + /// The method receives all packets queued in receive queue, one after + /// another. Once the queue is empty, it initiates the shutdown procedure. + /// + /// See fake_received_ field for description + virtual Pkt4Ptr receivePacket(int /*timeout*/) { + + // If there is anything prepared as fake incoming traffic, use it + if (!fake_received_.empty()) { + Pkt4Ptr pkt = fake_received_.front(); + fake_received_.pop_front(); + return (pkt); + } + + // If not, just trigger shutdown and return immediately + shutdown(); + return (Pkt4Ptr()); + } + + /// @brief fake packet sending + /// + /// Pretend to send a packet, but instead just store it in fake_send_ list + /// where test can later inspect server's response. + virtual void sendPacket(const Pkt4Ptr& pkt) { + fake_sent_.push_back(pkt); + } + + /// @brief adds a packet to fake receive queue + /// + /// See fake_received_ field for description + void fakeReceive(const Pkt4Ptr& pkt) { + fake_received_.push_back(pkt); + } + + virtual ~NakedDhcpv4Srv() { + } + + /// @brief packets we pretend to receive + /// + /// Instead of setting up sockets on interfaces that change between OSes, it + /// is much easier to fake packet reception. This is a list of packets that + /// we pretend to have received. You can schedule new packets to be received + /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods. + list fake_received_; + + list fake_sent_; + + using Dhcpv4Srv::adjustRemoteAddr; + using Dhcpv4Srv::processDiscover; + using Dhcpv4Srv::processRequest; + using Dhcpv4Srv::processRelease; + using Dhcpv4Srv::processDecline; + using Dhcpv4Srv::processInform; + using Dhcpv4Srv::getServerID; + using Dhcpv4Srv::loadServerID; + using Dhcpv4Srv::generateServerID; + using Dhcpv4Srv::writeServerID; + using Dhcpv4Srv::sanityCheck; + using Dhcpv4Srv::srvidToString; + using Dhcpv4Srv::unpackOptions; +}; ++#endif + + /// dummy server-id file location static const char* SRVID_FILE = "server-id-test.txt"; - /// @brief Dummy Packet Filtering class. - /// - /// This class reports capability to respond directly to the client which - /// doesn't have address configured yet. - /// - /// All packet and socket handling functions do nothing because they are not - /// used in unit tests. - class PktFilterTest : public PktFilter { - public: - - /// @brief Reports 'direct response' capability. - /// - /// @return always true. - virtual bool isDirectResponseSupported() const { - return (true); - } - - /// Does nothing. - virtual int openSocket(const Iface&, const IOAddress&, const uint16_t, - const bool, const bool) { - return (0); - } - - /// Does nothing. - virtual Pkt4Ptr receive(const Iface&, const SocketInfo&) { - return Pkt4Ptr(); - } - - /// Does nothing. - virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) { - return (0); - } - - }; - - class Dhcpv4SrvTest : public ::testing::Test { - public: - - /// @brief Constructor - /// - /// Initializes common objects used in many tests. - /// Also sets up initial configuration in CfgMgr. - Dhcpv4SrvTest() : - rcode_(-1) - { - subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000, - 2000, 3000)); - pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110"))); - subnet_->addPool(pool_); - - CfgMgr::instance().deleteSubnets4(); - CfgMgr::instance().addSubnet4(subnet_); - - // Add Router option. - Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS)); - opt_routers->setAddress(IOAddress("192.0.2.2")); - subnet_->addOption(opt_routers, false, "dhcp4"); - - // it's ok if that fails. There should not be such a file anyway - unlink(SRVID_FILE); - - const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces(); - - // There must be some interface detected - if (ifaces.empty()) { - // We can't use ASSERT in constructor - ADD_FAILURE() << "No interfaces detected."; - } - - valid_iface_ = ifaces.begin()->getName(); - } - - virtual ~Dhcpv4SrvTest() { - } - - /// @brief Add 'Parameter Request List' option to the packet. - /// - /// This function PRL option comprising the following option codes: - /// - 5 - Name Server - /// - 15 - Domain Name - /// - 7 - Log Server - /// - 8 - Quotes Server - /// - 9 - LPR Server - /// - /// @param pkt packet to add PRL option to. - void addPrlOption(Pkt4Ptr& pkt) { - - OptionUint8ArrayPtr option_prl = - OptionUint8ArrayPtr(new OptionUint8Array(Option::V4, - DHO_DHCP_PARAMETER_REQUEST_LIST)); - - // Let's request options that have been configured for the subnet. - option_prl->addValue(DHO_DOMAIN_NAME_SERVERS); - option_prl->addValue(DHO_DOMAIN_NAME); - option_prl->addValue(DHO_LOG_SERVERS); - option_prl->addValue(DHO_COOKIE_SERVERS); - // Let's also request the option that hasn't been configured. In such - // case server should ignore request for this particular option. - option_prl->addValue(DHO_LPR_SERVERS); - // And add 'Parameter Request List' option into the DISCOVER packet. - pkt->addOption(option_prl); - } - - /// @brief Configures options being requested in the PRL option. - /// - /// The lpr-servers option is NOT configured here although it is - /// added to the 'Parameter Request List' option in the - /// \ref addPrlOption. When requested option is not configured - /// the server should not return it in its response. The goal - /// of not configuring the requested option is to verify that - /// the server will not return it. - void configureRequestedOptions() { - // dns-servers - Option4AddrLstPtr - option_dns_servers(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS)); - option_dns_servers->addAddress(IOAddress("192.0.2.1")); - option_dns_servers->addAddress(IOAddress("192.0.2.100")); - ASSERT_NO_THROW(subnet_->addOption(option_dns_servers, false, "dhcp4")); - - // domain-name - OptionDefinition def("domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE); - OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4)); - option_domain_name->writeFqdn("example.com"); - subnet_->addOption(option_domain_name, false, "dhcp4"); - - // log-servers - Option4AddrLstPtr option_log_servers(new Option4AddrLst(DHO_LOG_SERVERS)); - option_log_servers->addAddress(IOAddress("192.0.2.2")); - option_log_servers->addAddress(IOAddress("192.0.2.10")); - ASSERT_NO_THROW(subnet_->addOption(option_log_servers, false, "dhcp4")); - - // cookie-servers - Option4AddrLstPtr option_cookie_servers(new Option4AddrLst(DHO_COOKIE_SERVERS)); - option_cookie_servers->addAddress(IOAddress("192.0.2.1")); - ASSERT_NO_THROW(subnet_->addOption(option_cookie_servers, false, "dhcp4")); - } - - /// @brief checks that the response matches request - /// @param q query (client's message) - /// @param a answer (server's message) - void messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) { - ASSERT_TRUE(q); - ASSERT_TRUE(a); - - EXPECT_EQ(q->getHops(), a->getHops()); - EXPECT_EQ(q->getIface(), a->getIface()); - EXPECT_EQ(q->getIndex(), a->getIndex()); - EXPECT_EQ(q->getGiaddr(), a->getGiaddr()); - // When processing an incoming packet the remote address - // is copied as a src address, and the source address is - // copied as a remote address to the response. - EXPECT_TRUE(q->getLocalHWAddr() == a->getLocalHWAddr()); - EXPECT_TRUE(q->getRemoteHWAddr() == a->getRemoteHWAddr()); - - // Check that bare minimum of required options are there. - // We don't check options requested by a client. Those - // are checked elsewhere. - EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK)); - EXPECT_TRUE(a->getOption(DHO_ROUTERS)); - EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER)); - EXPECT_TRUE(a->getOption(DHO_DHCP_LEASE_TIME)); - EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK)); - EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME)); - EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS)); - - // Check that something is offered - EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0"); - } - - /// @brief Check that requested options are present. - /// - /// @param pkt packet to be checked. - void optionsCheck(const Pkt4Ptr& pkt) { - // Check that the requested and configured options are returned - // in the ACK message. - EXPECT_TRUE(pkt->getOption(DHO_DOMAIN_NAME)) - << "domain-name not present in the response"; - EXPECT_TRUE(pkt->getOption(DHO_DOMAIN_NAME_SERVERS)) - << "dns-servers not present in the response"; - EXPECT_TRUE(pkt->getOption(DHO_LOG_SERVERS)) - << "log-servers not present in the response"; - EXPECT_TRUE(pkt->getOption(DHO_COOKIE_SERVERS)) - << "cookie-servers not present in the response"; - // Check that the requested but not configured options are not - // returned in the ACK message. - EXPECT_FALSE(pkt->getOption(DHO_LPR_SERVERS)) - << "domain-name present in the response but it is" - << " expected not to be present"; - } - - /// @brief generates client-id option - /// - /// Generate client-id option of specified length - /// Ids with different lengths are sufficent to generate - /// unique ids. If more fine grained control is required, - /// tests generate client-ids on their own. - /// Sets client_id_ field. - /// @param size size of the client-id to be generated - OptionPtr generateClientId(size_t size = 4) { - - OptionBuffer clnt_id(size); - for (int i = 0; i < size; i++) { - clnt_id[i] = 100 + i; - } - - client_id_ = ClientIdPtr(new ClientId(clnt_id)); - - return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER, - clnt_id.begin(), - clnt_id.begin() + size))); - } - - /// @brief generate hardware address - /// - /// @param size size of the generated MAC address - /// @param pointer to Hardware Address object - HWAddrPtr generateHWAddr(size_t size = 6) { - const uint8_t hw_type = 123; // Just a fake number (typically 6=HTYPE_ETHER, see dhcp4.h) - OptionBuffer mac(size); - for (int i = 0; i < size; ++i) { - mac[i] = 50 + i; - } - return (HWAddrPtr(new HWAddr(mac, hw_type))); - } - - /// Check that address was returned from proper range, that its lease - /// lifetime is correct, that T1 and T2 are returned properly - /// @param rsp response to be checked - /// @param subnet subnet that should be used to verify assigned address - /// and options - /// @param t1_mandatory is T1 mandatory? - /// @param t2_mandatory is T2 mandatory? - void checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet, - bool t1_mandatory = false, - bool t2_mandatory = false) { - - // Technically inPool implies inRange, but let's be on the safe - // side and check both. - EXPECT_TRUE(subnet->inRange(rsp->getYiaddr())); - EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, rsp->getYiaddr())); - - // Check lease time - OptionPtr opt = rsp->getOption(DHO_DHCP_LEASE_TIME); - if (!opt) { - ADD_FAILURE() << "Lease time option missing in response"; - } else { - EXPECT_EQ(opt->getUint32(), subnet->getValid()); - } - - // Check T1 timer - opt = rsp->getOption(DHO_DHCP_RENEWAL_TIME); - if (opt) { - EXPECT_EQ(opt->getUint32(), subnet->getT1()); - } else { - if (t1_mandatory) { - ADD_FAILURE() << "Required T1 option missing"; - } - } - - // Check T2 timer - opt = rsp->getOption(DHO_DHCP_REBINDING_TIME); - if (opt) { - EXPECT_EQ(opt->getUint32(), subnet->getT2()); - } else { - if (t2_mandatory) { - ADD_FAILURE() << "Required T2 option missing"; - } - } - } - - /// @brief Basic checks for generated response (message type and trans-id). - /// - /// @param rsp response packet to be validated - /// @param expected_message_type expected message type - /// @param expected_transid expected transaction-id - void checkResponse(const Pkt4Ptr& rsp, uint8_t expected_message_type, - uint32_t expected_transid) { - ASSERT_TRUE(rsp); - EXPECT_EQ(expected_message_type, rsp->getType()); - EXPECT_EQ(expected_transid, rsp->getTransid()); - } - - /// @brief Checks if the lease sent to client is present in the database - /// - /// @param rsp response packet to be validated - /// @param client_id expected client-identifier (or NULL) - /// @param HWAddr expected hardware address (not used now) - /// @param expected_addr expected address - Lease4Ptr checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id, - const HWAddrPtr&, const IOAddress& expected_addr) { - - ClientIdPtr id; - if (client_id) { - OptionBuffer data = client_id->getData(); - id.reset(new ClientId(data)); - } - - Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr); - if (!lease) { - cout << "Lease for " << expected_addr.toText() - << " not found in the database backend."; - return (Lease4Ptr()); - } - - EXPECT_EQ(rsp->getYiaddr().toText(), expected_addr.toText()); - - EXPECT_EQ(expected_addr.toText(), lease->addr_.toText()); - if (client_id) { - EXPECT_TRUE(*lease->client_id_ == *id); - } - EXPECT_EQ(subnet_->getID(), lease->subnet_id_); - - return (lease); - } - - /// @brief Checks if server response (OFFER, ACK, NAK) includes proper server-id - /// @param rsp response packet to be validated - /// @param expected_srvid expected value of server-id - void checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid) { - // Check that server included its server-id - OptionPtr opt = rsp->getOption(DHO_DHCP_SERVER_IDENTIFIER); - ASSERT_TRUE(opt); - EXPECT_EQ(opt->getType(), expected_srvid->getType() ); - EXPECT_EQ(opt->len(), expected_srvid->len() ); - EXPECT_TRUE(opt->getData() == expected_srvid->getData()); - } - - /// @brief Checks if server response (OFFER, ACK, NAK) includes proper client-id - /// @param rsp response packet to be validated - /// @param expected_clientid expected value of client-id - void checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid) { - // check that server included our own client-id - OptionPtr opt = rsp->getOption(DHO_DHCP_CLIENT_IDENTIFIER); - ASSERT_TRUE(opt); - EXPECT_EQ(expected_clientid->getType(), opt->getType()); - EXPECT_EQ(expected_clientid->len(), opt->len()); - EXPECT_TRUE(expected_clientid->getData() == opt->getData()); - } - - /// @brief Tests if Discover or Request message is processed correctly - /// - /// @param msg_type DHCPDISCOVER or DHCPREQUEST - void testDiscoverRequest(const uint8_t msg_type) { - // Create an instance of the tested class. - boost::scoped_ptr srv(new NakedDhcpv4Srv(0)); - - // Initialize the source HW address. - vector mac(6); - for (int i = 0; i < 6; ++i) { - mac[i] = i * 10; - } - // Initialized the destination HW address. - vector dst_mac(6); - for (int i = 0; i < 6; ++i) { - dst_mac[i] = i * 20; - } - // Create a DHCP message. It will be used to simulate the - // incoming message. - boost::shared_ptr req(new Pkt4(msg_type, 1234)); - // Create a response message. It will hold a reponse packet. - // Initially, set it to NULL. - boost::shared_ptr rsp; - // Set the name of the interface on which packet is received. - req->setIface("eth0"); - // Set the interface index. It is just a dummy value and will - // not be interpreted. - req->setIndex(17); - // Set the target HW address. This value is normally used to - // construct the data link layer header. - req->setRemoteHWAddr(1, 6, dst_mac); - // Set the HW address. This value is set on DHCP level (in chaddr). - req->setHWAddr(1, 6, mac); - // Set local HW address. It is used to construct the data link layer - // header. - req->setLocalHWAddr(1, 6, mac); - // Set target IP address. - req->setRemoteAddr(IOAddress("192.0.2.55")); - // Set relay address. - req->setGiaddr(IOAddress("192.0.2.10")); - - // We are going to test that certain options are returned - // in the response message when requested using 'Parameter - // Request List' option. Let's configure those options that - // are returned when requested. - configureRequestedOptions(); - - if (msg_type == DHCPDISCOVER) { - ASSERT_NO_THROW( - rsp = srv->processDiscover(req); - ); - - // Should return OFFER - ASSERT_TRUE(rsp); - EXPECT_EQ(DHCPOFFER, rsp->getType()); - - } else { - ASSERT_NO_THROW( - rsp = srv->processRequest(req); - ); - - // Should return ACK - ASSERT_TRUE(rsp); - EXPECT_EQ(DHCPACK, rsp->getType()); - - } - - messageCheck(req, rsp); - - // We did not request any options so these should not be present - // in the RSP. - EXPECT_FALSE(rsp->getOption(DHO_LOG_SERVERS)); - EXPECT_FALSE(rsp->getOption(DHO_COOKIE_SERVERS)); - EXPECT_FALSE(rsp->getOption(DHO_LPR_SERVERS)); - - // Repeat the test but request some options. - // Add 'Parameter Request List' option. - addPrlOption(req); - - if (msg_type == DHCPDISCOVER) { - ASSERT_NO_THROW( - rsp = srv->processDiscover(req); - ); - - // Should return non-NULL packet. - ASSERT_TRUE(rsp); - EXPECT_EQ(DHCPOFFER, rsp->getType()); - - } else { - ASSERT_NO_THROW( - rsp = srv->processRequest(req); - ); - - // Should return non-NULL packet. - ASSERT_TRUE(rsp); - EXPECT_EQ(DHCPACK, rsp->getType()); - - } - - // Check that the requested options are returned. - optionsCheck(rsp); - - } - - /// @brief This function cleans up after the test. - virtual void TearDown() { - - CfgMgr::instance().deleteSubnets4(); - - // Let's clean up if there is such a file. - unlink(SRVID_FILE); - - // Close all open sockets. - IfaceMgr::instance().closeSockets(); - - // Some unit tests override the default packet filtering class, used - // by the IfaceMgr. The dummy class, called PktFilterTest, reports the - // capability to directly respond to the clients without IP address - // assigned. This capability is not supported by the default packet - // filtering class: PktFilterInet. Therefore setting the dummy class - // allows to test scenarios, when server responds to the broadcast address - // on client's request, despite having support for direct response. - // The following call restores the use of original packet filtering class - // after the test. - try { - IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet())); - - } catch (const Exception& ex) { - FAIL() << "Failed to restore the default (PktFilterInet) packet filtering" - << " class after the test. Exception has been caught: " - << ex.what(); - } - } - - /// @brief A subnet used in most tests - Subnet4Ptr subnet_; - - /// @brief A pool used in most tests - Pool4Ptr pool_; - - /// @brief A client-id used in most tests - ClientIdPtr client_id_; - - int rcode_; - - ConstElementPtr comment_; - - // Name of a valid network interface - string valid_iface_; - }; + namespace isc { + namespace dhcp { + namespace test { // Sanity check. Verifies that both Dhcpv4Srv and its derived // class NakedDhcpv4Srv can be instantiated and destroyed. diff --cc src/bin/dhcp4/tests/dhcp4_test_utils.h index 0000000000,07fbeb6dd6..2e39a8be4c mode 000000,100644..100644 --- a/src/bin/dhcp4/tests/dhcp4_test_utils.h +++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h @@@ -1,0 -1,316 +1,317 @@@ + // Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + // + // Permission to use, copy, modify, and/or distribute this software for any + // purpose with or without fee is hereby granted, provided that the above + // copyright notice and this permission notice appear in all copies. + // + // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + // PERFORMANCE OF THIS SOFTWARE. + + /// @file dhcp4_test_utils.h + /// + /// @brief This file contains utility classes used for DHCPv4 server testing + + #ifndef DHCP4_TEST_UTILS_H + #define DHCP4_TEST_UTILS_H + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + namespace isc { + namespace dhcp { + namespace test { + + /// @brief Dummy Packet Filtering class. + /// + /// This class reports capability to respond directly to the client which + /// doesn't have address configured yet. + /// + /// All packet and socket handling functions do nothing because they are not + /// used in unit tests. + class PktFilterTest : public PktFilter { + public: + + /// @brief Reports 'direct response' capability. + /// + /// @return always true. + virtual bool isDirectResponseSupported() const { + return (true); + } + + /// Does nothing. + virtual int openSocket(const Iface&, const isc::asiolink::IOAddress&, + const uint16_t, const bool, const bool) { + return (0); + } + + /// Does nothing. + virtual Pkt4Ptr receive(const Iface&, const SocketInfo&) { + return Pkt4Ptr(); + } + + /// Does nothing. + virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) { + return (0); + } + + }; + + class Dhcpv4SrvTest : public ::testing::Test { + public: + + /// @brief Constructor + /// + /// Initializes common objects used in many tests. + /// Also sets up initial configuration in CfgMgr. + Dhcpv4SrvTest(); + + /// @brief destructor + virtual ~Dhcpv4SrvTest() { + } + + /// @brief Add 'Parameter Request List' option to the packet. + /// + /// This function adds PRL option comprising the following option codes: + /// - 5 - Name Server + /// - 15 - Domain Name + /// - 7 - Log Server + /// - 8 - Quotes Server + /// - 9 - LPR Server + /// + /// @param pkt packet to add PRL option to. + void addPrlOption(Pkt4Ptr& pkt); + + /// @brief Configures options being requested in the PRL option. + /// + /// The lpr-servers option is NOT configured here although it is + /// added to the 'Parameter Request List' option in the + /// \ref addPrlOption. When requested option is not configured + /// the server should not return it in its response. The goal + /// of not configuring the requested option is to verify that + /// the server will not return it. + void configureRequestedOptions(); + + /// @brief checks that the response matches request + /// @param q query (client's message) + /// @param a answer (server's message) + void messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a); + + /// @brief Check that requested options are present. + /// + /// @param pkt packet to be checked. + void optionsCheck(const Pkt4Ptr& pkt); + + /// @brief generates client-id option + /// + /// Generate client-id option of specified length + /// Ids with different lengths are sufficent to generate + /// unique ids. If more fine grained control is required, + /// tests generate client-ids on their own. + /// Sets client_id_ field. + /// @param size size of the client-id to be generated + OptionPtr generateClientId(size_t size = 4); + + /// @brief generate hardware address + /// + /// @param size size of the generated MAC address + /// @param pointer to Hardware Address object + HWAddrPtr generateHWAddr(size_t size = 6); + + /// Check that address was returned from proper range, that its lease + /// lifetime is correct, that T1 and T2 are returned properly + /// @param rsp response to be checked + /// @param subnet subnet that should be used to verify assigned address + /// and options + /// @param t1_mandatory is T1 mandatory? + /// @param t2_mandatory is T2 mandatory? + void checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet, + bool t1_mandatory = false, + bool t2_mandatory = false); + + /// @brief Basic checks for generated response (message type and trans-id). + /// + /// @param rsp response packet to be validated + /// @param expected_message_type expected message type + /// @param expected_transid expected transaction-id + void checkResponse(const Pkt4Ptr& rsp, uint8_t expected_message_type, + uint32_t expected_transid); + + /// @brief Checks if the lease sent to client is present in the database + /// + /// @param rsp response packet to be validated + /// @param client_id expected client-identifier (or NULL) + /// @param HWAddr expected hardware address (not used now) + /// @param expected_addr expected address + Lease4Ptr checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id, + const HWAddrPtr&, + const isc::asiolink::IOAddress& expected_addr); + + /// @brief Checks if server response (OFFER, ACK, NAK) includes proper server-id + /// @param rsp response packet to be validated + /// @param expected_srvid expected value of server-id + void checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid); + + /// @brief Checks if server response (OFFER, ACK, NAK) includes proper client-id + /// @param rsp response packet to be validated + /// @param expected_clientid expected value of client-id + void checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid); + + /// @brief sets default fields in a captured packet + /// + /// Sets UDP ports, addresses and interface. + /// + /// @param pkt packet to have default fields set + void captureSetDefaultFields(const Pkt4Ptr& pkt); + + /// @brief returns captured DISCOVER that went through a relay + /// + /// See method code for a detailed explanation. + /// + /// @return relayed DISCOVER + Pkt4Ptr captureRelayedDiscover(); + + /// @brief generates a DHCPv4 packet based on provided hex string + /// + /// @return created packet + Pkt4Ptr packetFromCapture(const std::string& hex_string); + + /// @brief Tests if Discover or Request message is processed correctly + /// + /// @param msg_type DHCPDISCOVER or DHCPREQUEST + void testDiscoverRequest(const uint8_t msg_type); + + /// @brief This function cleans up after the test. + virtual void TearDown(); + + /// @brief A subnet used in most tests + Subnet4Ptr subnet_; + + /// @brief A pool used in most tests + Pool4Ptr pool_; + + /// @brief A client-id used in most tests + ClientIdPtr client_id_; + + int rcode_; + + isc::data::ConstElementPtr comment_; + + // Name of a valid network interface + std::string valid_iface_; + }; + + /// @brief "Naked" DHCPv4 server, exposes internal fields + class NakedDhcpv4Srv: public Dhcpv4Srv { + public: + + /// @brief Constructor. + /// + /// This constructor disables default modes of operation used by the + /// Dhcpv4Srv class: + /// - Send/receive broadcast messages through sockets on interfaces + /// which support broadcast traffic. + /// - Direct DHCPv4 traffic - communication with clients which do not + /// have IP address assigned yet. + /// + /// Enabling these modes requires root privilges so they must be + /// disabled for unit testing. + /// + /// Note, that disabling broadcast options on sockets does not impact + /// the operation of these tests because they use local loopback + /// interface which doesn't have broadcast capability anyway. It rather + /// prevents setting broadcast options on other (broadcast capable) + /// sockets which are opened on other interfaces in Dhcpv4Srv constructor. + /// + /// The Direct DHCPv4 Traffic capability can be disabled here because + /// it is tested with PktFilterLPFTest unittest. The tests which belong + /// to PktFilterLPFTest can be enabled on demand when root privileges can + /// be guaranteed. + /// + /// @param port port number to listen on; the default value 0 indicates + /// that sockets should not be opened. + NakedDhcpv4Srv(uint16_t port = 0) + : Dhcpv4Srv(port, "type=memfile", false, false) { + } + + /// @brief fakes packet reception + /// @param timeout ignored + /// + /// The method receives all packets queued in receive queue, one after + /// another. Once the queue is empty, it initiates the shutdown procedure. + /// + /// See fake_received_ field for description + virtual Pkt4Ptr receivePacket(int /*timeout*/) { + + // If there is anything prepared as fake incoming traffic, use it + if (!fake_received_.empty()) { + Pkt4Ptr pkt = fake_received_.front(); + fake_received_.pop_front(); + return (pkt); + } + + // If not, just trigger shutdown and return immediately + shutdown(); + return (Pkt4Ptr()); + } + + /// @brief fake packet sending + /// + /// Pretend to send a packet, but instead just store it in fake_send_ list + /// where test can later inspect server's response. + virtual void sendPacket(const Pkt4Ptr& pkt) { + fake_sent_.push_back(pkt); + } + + /// @brief adds a packet to fake receive queue + /// + /// See fake_received_ field for description + void fakeReceive(const Pkt4Ptr& pkt) { + fake_received_.push_back(pkt); + } + + virtual ~NakedDhcpv4Srv() { + } + + /// @brief packets we pretend to receive + /// + /// Instead of setting up sockets on interfaces that change between OSes, it + /// is much easier to fake packet reception. This is a list of packets that + /// we pretend to have received. You can schedule new packets to be received + /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods. + std::list fake_received_; + + std::list fake_sent_; + + using Dhcpv4Srv::adjustRemoteAddr; + using Dhcpv4Srv::processDiscover; + using Dhcpv4Srv::processRequest; + using Dhcpv4Srv::processRelease; + using Dhcpv4Srv::processDecline; + using Dhcpv4Srv::processInform; + using Dhcpv4Srv::getServerID; + using Dhcpv4Srv::loadServerID; + using Dhcpv4Srv::generateServerID; + using Dhcpv4Srv::writeServerID; + using Dhcpv4Srv::sanityCheck; + using Dhcpv4Srv::srvidToString; ++ using Dhcpv4Srv::unpackOptions; + }; + + }; // end of isc::dhcp::test namespace + }; // end of isc::dhcp namespace + }; // end of isc namespace + + #endif // DHCP4_TEST_UTILS_H