dhcp4_test_utils.h 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. // Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. /// @file dhcp4_test_utils.h
  7. ///
  8. /// @brief This file contains utility classes used for DHCPv4 server testing
  9. #ifndef DHCP4_TEST_UTILS_H
  10. #define DHCP4_TEST_UTILS_H
  11. #include <gtest/gtest.h>
  12. #include <dhcp/iface_mgr.h>
  13. #include <dhcp/option4_addrlst.h>
  14. #include <dhcp/pkt4.h>
  15. #include <dhcp/pkt_filter.h>
  16. #include <dhcp/pkt_filter_inet.h>
  17. #include <dhcpsrv/subnet.h>
  18. #include <dhcpsrv/lease.h>
  19. #include <dhcpsrv/lease_mgr_factory.h>
  20. #include <dhcp4/dhcp4_srv.h>
  21. #include <dhcp4/parser_context.h>
  22. #include <asiolink/io_address.h>
  23. #include <cc/command_interpreter.h>
  24. #include <list>
  25. #include <boost/shared_ptr.hpp>
  26. namespace isc {
  27. namespace dhcp {
  28. namespace test {
  29. /// @brief Dummy Packet Filtering class.
  30. ///
  31. /// This class reports capability to respond directly to the client which
  32. /// doesn't have address configured yet.
  33. ///
  34. /// All packet and socket handling functions do nothing because they are not
  35. /// used in unit tests.
  36. class PktFilterTest : public PktFilter {
  37. public:
  38. /// @brief Constructor.
  39. ///
  40. /// Sets the 'direct response' capability to true.
  41. PktFilterTest()
  42. : direct_resp_supported_(true) {
  43. }
  44. /// @brief Reports 'direct response' capability.
  45. ///
  46. /// @return always true.
  47. virtual bool isDirectResponseSupported() const {
  48. return (direct_resp_supported_);
  49. }
  50. /// Does nothing.
  51. virtual SocketInfo openSocket(Iface&,
  52. const isc::asiolink::IOAddress& addr,
  53. const uint16_t port, const bool, const bool) {
  54. return (SocketInfo(addr, port, 0));
  55. }
  56. /// Does nothing.
  57. virtual Pkt4Ptr receive(Iface&, const SocketInfo&) {
  58. return Pkt4Ptr();
  59. }
  60. /// Does nothing.
  61. virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) {
  62. return (0);
  63. }
  64. /// @brief Holds a boolean value which indicates whether direct response
  65. /// capability is supported (true) or not (false).
  66. bool direct_resp_supported_;
  67. };
  68. typedef boost::shared_ptr<PktFilterTest> PktFilterTestPtr;
  69. /// Forward definition for Dhcp4Client defined in dhcp4_client.h
  70. /// dhcp4_client.h includes dhcp_test_utils.h (this file), so to avoid
  71. /// circular dependencies, we need a forward class declaration.
  72. class Dhcp4Client;
  73. /// @brief "Naked" DHCPv4 server, exposes internal fields
  74. class NakedDhcpv4Srv: public Dhcpv4Srv {
  75. public:
  76. /// @brief Constructor.
  77. ///
  78. /// This constructor disables default modes of operation used by the
  79. /// Dhcpv4Srv class:
  80. /// - Send/receive broadcast messages through sockets on interfaces
  81. /// which support broadcast traffic.
  82. /// - Direct DHCPv4 traffic - communication with clients which do not
  83. /// have IP address assigned yet.
  84. ///
  85. /// Enabling these modes requires root privileges so they must be
  86. /// disabled for unit testing.
  87. ///
  88. /// Note, that disabling broadcast options on sockets does not impact
  89. /// the operation of these tests because they use local loopback
  90. /// interface which doesn't have broadcast capability anyway. It rather
  91. /// prevents setting broadcast options on other (broadcast capable)
  92. /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
  93. ///
  94. /// The Direct DHCPv4 Traffic capability can be disabled here because
  95. /// it is tested with PktFilterLPFTest unittest. The tests which belong
  96. /// to PktFilterLPFTest can be enabled on demand when root privileges can
  97. /// be guaranteed.
  98. ///
  99. /// @param port port number to listen on; the default value 0 indicates
  100. /// that sockets should not be opened.
  101. NakedDhcpv4Srv(uint16_t port = 0)
  102. : Dhcpv4Srv(port, false, false) {
  103. // Create a default lease database backend.
  104. std::string dbconfig = "type=memfile universe=4 persist=false";
  105. isc::dhcp::LeaseMgrFactory::create(dbconfig);
  106. // Create fixed server id.
  107. server_id_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
  108. asiolink::IOAddress("192.0.3.1")));
  109. }
  110. /// @brief Returns fixed server identifier assigned to the naked server
  111. /// instance.
  112. OptionPtr getServerID() const {
  113. return (server_id_);
  114. }
  115. /// @brief fakes packet reception
  116. /// @param timeout ignored
  117. ///
  118. /// The method receives all packets queued in receive queue, one after
  119. /// another. Once the queue is empty, it initiates the shutdown procedure.
  120. ///
  121. /// See fake_received_ field for description
  122. virtual Pkt4Ptr receivePacket(int /*timeout*/) {
  123. // If there is anything prepared as fake incoming traffic, use it
  124. if (!fake_received_.empty()) {
  125. Pkt4Ptr pkt = fake_received_.front();
  126. fake_received_.pop_front();
  127. return (pkt);
  128. }
  129. // If not, just trigger shutdown and return immediately
  130. shutdown();
  131. return (Pkt4Ptr());
  132. }
  133. /// @brief fake packet sending
  134. ///
  135. /// Pretend to send a packet, but instead just store it in fake_send_ list
  136. /// where test can later inspect server's response.
  137. virtual void sendPacket(const Pkt4Ptr& pkt) {
  138. fake_sent_.push_back(pkt);
  139. }
  140. /// @brief adds a packet to fake receive queue
  141. ///
  142. /// See fake_received_ field for description
  143. void fakeReceive(const Pkt4Ptr& pkt) {
  144. fake_received_.push_back(pkt);
  145. }
  146. virtual ~NakedDhcpv4Srv() {
  147. }
  148. /// @brief Dummy server identifier option used by various tests.
  149. OptionPtr server_id_;
  150. /// @brief packets we pretend to receive
  151. ///
  152. /// Instead of setting up sockets on interfaces that change between OSes, it
  153. /// is much easier to fake packet reception. This is a list of packets that
  154. /// we pretend to have received. You can schedule new packets to be received
  155. /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods.
  156. std::list<Pkt4Ptr> fake_received_;
  157. std::list<Pkt4Ptr> fake_sent_;
  158. using Dhcpv4Srv::adjustIfaceData;
  159. using Dhcpv4Srv::appendServerID;
  160. using Dhcpv4Srv::processDiscover;
  161. using Dhcpv4Srv::processRequest;
  162. using Dhcpv4Srv::processRelease;
  163. using Dhcpv4Srv::processDecline;
  164. using Dhcpv4Srv::processInform;
  165. using Dhcpv4Srv::processClientName;
  166. using Dhcpv4Srv::createNameChangeRequests;
  167. using Dhcpv4Srv::acceptServerId;
  168. using Dhcpv4Srv::sanityCheck;
  169. using Dhcpv4Srv::srvidToString;
  170. using Dhcpv4Srv::classifyPacket;
  171. using Dhcpv4Srv::accept;
  172. using Dhcpv4Srv::acceptMessageType;
  173. using Dhcpv4Srv::selectSubnet;
  174. using Dhcpv4Srv::VENDOR_CLASS_PREFIX;
  175. using Dhcpv4Srv::shutdown_;
  176. using Dhcpv4Srv::alloc_engine_;
  177. };
  178. // We need to pass one reference to the Dhcp4Client, which is defined in
  179. // dhcp4_client.h. That header includes this file. To avoid circular
  180. // dependencies, we use forward declaration here.
  181. class Dhcp4Client;
  182. /// @brief Base class for DHCPv4 server testing.
  183. ///
  184. /// Currently it configures the test data path directory in
  185. /// the @c CfgMgr. When the object is destroyed, the original
  186. /// path is reverted.
  187. class BaseServerTest : public ::testing::Test {
  188. public:
  189. /// @brief Constructor.
  190. BaseServerTest();
  191. /// @brief Destructor.
  192. virtual ~BaseServerTest();
  193. private:
  194. /// @brief Holds the original data directory.
  195. std::string original_datadir_;
  196. };
  197. class Dhcpv4SrvTest : public BaseServerTest {
  198. public:
  199. enum ExpectedResult {
  200. SHOULD_PASS, // pass = accept decline, move lease to declined state.
  201. SHOULD_FAIL // fail = reject the decline
  202. };
  203. /// @brief Constructor
  204. ///
  205. /// Initializes common objects used in many tests.
  206. /// Also sets up initial configuration in CfgMgr.
  207. Dhcpv4SrvTest();
  208. /// @brief destructor
  209. virtual ~Dhcpv4SrvTest();
  210. /// @brief Add 'Parameter Request List' option to the packet.
  211. ///
  212. /// This function adds PRL option comprising the following option codes:
  213. /// - 5 - Name Server
  214. /// - 15 - Domain Name
  215. /// - 7 - Log Server
  216. /// - 8 - Quotes Server
  217. /// - 9 - LPR Server
  218. ///
  219. /// @param pkt packet to add PRL option to.
  220. void addPrlOption(Pkt4Ptr& pkt);
  221. /// @brief Configures options being requested in the PRL option.
  222. ///
  223. /// The lpr-servers option is NOT configured here although it is
  224. /// added to the 'Parameter Request List' option in the
  225. /// \ref addPrlOption. When requested option is not configured
  226. /// the server should not return it in its response. The goal
  227. /// of not configuring the requested option is to verify that
  228. /// the server will not return it.
  229. void configureRequestedOptions();
  230. /// @brief checks that the response matches request
  231. /// @param q query (client's message)
  232. /// @param a answer (server's message)
  233. void messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a);
  234. /// @brief Check that certain basic (always added when lease is acquired)
  235. /// are present in a message.
  236. ///
  237. /// @param pkt A message to be checked.
  238. /// @return Assertion result which indicates whether test passed or failed.
  239. ::testing::AssertionResult basicOptionsPresent(const Pkt4Ptr& pkt);
  240. /// @brief Check that certain basic (always added when lease is acquired)
  241. /// are not present.
  242. ///
  243. /// @param pkt A packet to be checked.
  244. /// @return Assertion result which indicates whether test passed or failed.
  245. ::testing::AssertionResult noBasicOptions(const Pkt4Ptr& pkt);
  246. /// @brief Check that certain requested options are present in the message.
  247. ///
  248. /// @param pkt A message to be checked.
  249. /// @return Assertion result which indicates whether test passed or failed.
  250. ::testing::AssertionResult requestedOptionsPresent(const Pkt4Ptr& pkt);
  251. /// @brief Check that certain options (requested with PRL option)
  252. /// are not present.
  253. ///
  254. /// @param pkt A packet to be checked.
  255. /// @return Assertion result which indicates whether test passed or failed.
  256. ::testing::AssertionResult noRequestedOptions(const Pkt4Ptr& pkt);
  257. /// @brief generates client-id option
  258. ///
  259. /// Generate client-id option of specified length
  260. /// Ids with different lengths are sufficient to generate
  261. /// unique ids. If more fine grained control is required,
  262. /// tests generate client-ids on their own.
  263. /// Sets client_id_ field.
  264. /// @param size size of the client-id to be generated
  265. OptionPtr generateClientId(size_t size = 4);
  266. /// @brief generate hardware address
  267. ///
  268. /// @param size size of the generated MAC address
  269. /// @param pointer to Hardware Address object
  270. HWAddrPtr generateHWAddr(size_t size = 6);
  271. /// Check that address was returned from proper range, that its lease
  272. /// lifetime is correct, that T1 and T2 are returned properly
  273. /// @param rsp response to be checked
  274. /// @param subnet subnet that should be used to verify assigned address
  275. /// and options
  276. /// @param t1_present check that t1 must be present (true) or must not be
  277. /// present (false)
  278. /// @param t2_present check that t2 must be present (true) or must not be
  279. /// present (false)
  280. void checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet,
  281. bool t1_present = false,
  282. bool t2_present = false);
  283. /// @brief Basic checks for generated response (message type and trans-id).
  284. ///
  285. /// @param rsp response packet to be validated
  286. /// @param expected_message_type expected message type
  287. /// @param expected_transid expected transaction-id
  288. void checkResponse(const Pkt4Ptr& rsp, int expected_message_type,
  289. uint32_t expected_transid);
  290. /// @brief Checks if the lease sent to client is present in the database
  291. ///
  292. /// @param rsp response packet to be validated
  293. /// @param client_id expected client-identifier (or NULL)
  294. /// @param HWAddr expected hardware address (not used now)
  295. /// @param expected_addr expected address
  296. Lease4Ptr checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id,
  297. const HWAddrPtr&,
  298. const isc::asiolink::IOAddress& expected_addr);
  299. /// @brief Checks if server response (OFFER, ACK, NAK) includes proper server-id
  300. /// @param rsp response packet to be validated
  301. /// @param expected_srvid expected value of server-id
  302. void checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid);
  303. /// @brief Checks if server response (OFFER, ACK, NAK) includes proper client-id
  304. ///
  305. /// This method follows values reported by CfgMgr in echoClientId() method.
  306. /// Depending on its configuration, the client-id is either mandatory or
  307. /// forbidden to appear in the response.
  308. ///
  309. /// @param rsp response packet to be validated
  310. /// @param expected_clientid expected value of client-id
  311. void checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid);
  312. /// @brief Create packet from output buffer of another packet.
  313. ///
  314. /// This function creates a packet using an output buffer from another
  315. /// packet. This imitates reception of a packet from the wire. The
  316. /// unpack function is then called to parse packet contents and to
  317. /// create a collection of the options carried by this packet.
  318. ///
  319. /// This function is useful for unit tests which verify that the received
  320. /// packet is parsed correctly. In those cases it is usually inappropriate
  321. /// to create an instance of the packet, add options, set packet
  322. /// fields and use such packet as an input to processDiscover or
  323. /// processRequest function. This is because, such a packet has certain
  324. /// options already initialized and there is no way to verify that they
  325. /// have been initialized when packet instance was created or wire buffer
  326. /// processing. By creating a packet from the buffer we guarantee that the
  327. /// new packet is entirely initialized during wire data parsing.
  328. ///
  329. /// @param src_pkt A source packet, to be copied.
  330. /// @param [out] dst_pkt A destination packet.
  331. ///
  332. /// @return assertion result indicating if a function completed with
  333. /// success or failure.
  334. static ::testing::AssertionResult
  335. createPacketFromBuffer(const isc::dhcp::Pkt4Ptr& src_pkt,
  336. isc::dhcp::Pkt4Ptr& dst_pkt);
  337. /// @brief Tests if Discover or Request message is processed correctly
  338. ///
  339. /// This test verifies that the Parameter Request List option is handled
  340. /// correctly, i.e. it checks that certain options are present in the
  341. /// server's response when they are requested and that they are not present
  342. /// when they are not requested or NAK occurs.
  343. ///
  344. /// @todo We need an additional test for PRL option using real traffic
  345. /// capture.
  346. ///
  347. /// @param msg_type DHCPDISCOVER or DHCPREQUEST
  348. void testDiscoverRequest(const uint8_t msg_type);
  349. /// @brief Runs DHCPv4 configuration from the JSON string.
  350. ///
  351. /// @param config String holding server configuration in JSON format.
  352. /// @param commit A boolean flag indicating if the new configuration
  353. /// should be committed (if true), or not (if false).
  354. void configure(const std::string& config, const bool commit = true);
  355. /// @brief Configure specified DHCP server using JSON string.
  356. ///
  357. /// @param config String holding server configuration in JSON format.
  358. /// @param srv Instance of the server to be configured.
  359. /// @param commit A boolean flag indicating if the new configuration
  360. /// should be committed (if true), or not (if false).
  361. void configure(const std::string& config, NakedDhcpv4Srv& srv,
  362. const bool commit = true);
  363. /// @brief Pretents a packet of specified type was received.
  364. ///
  365. /// Instantiates fake network interfaces, configures passed Dhcpv4Srv,
  366. /// then creates a message of specified type and sends it to the
  367. /// server and then checks whether expected statstics were set
  368. /// appropriately.
  369. ///
  370. /// @param srv the DHCPv4 server to be used
  371. /// @param config JSON configuration to be used
  372. /// @param pkt_type type of the packet to be faked
  373. /// @param stat_name name of the expected statistic
  374. void pretendReceivingPkt(NakedDhcpv4Srv& srv, const std::string& config,
  375. uint8_t pkt_type, const std::string& stat_name);
  376. /// @brief Create @c Dhcpv4Exchange from client's query.
  377. Dhcpv4Exchange createExchange(const Pkt4Ptr& query);
  378. /// @brief Performs 4-way exchange to obtain new lease.
  379. ///
  380. /// This is used as a preparatory step for Decline operation.
  381. ///
  382. /// @param client Client to be used to obtain a lease.
  383. void acquireLease(Dhcp4Client& client);
  384. /// @brief Tests if the acquired lease is or is not declined.
  385. ///
  386. /// @param client Dhcp4Client instance
  387. /// @param hw_address_1 HW Address to be used to acquire the lease.
  388. /// @param client_id_1 Client id to be used to acquire the lease.
  389. /// @param hw_address_2 HW Address to be used to decline the lease.
  390. /// @param client_id_2 Client id to be used to decline the lease.
  391. /// @param expected_result SHOULD_PASS if the lease is expected to
  392. /// be successfully declined, or SHOULD_FAIL if the lease is expected
  393. /// to not be declined.
  394. void acquireAndDecline(Dhcp4Client& client,
  395. const std::string& hw_address_1,
  396. const std::string& client_id_1,
  397. const std::string& hw_address_2,
  398. const std::string& client_id_2,
  399. ExpectedResult expected_result);
  400. /// @brief This function cleans up after the test.
  401. virtual void TearDown();
  402. /// @brief A subnet used in most tests
  403. Subnet4Ptr subnet_;
  404. /// @brief A pool used in most tests
  405. Pool4Ptr pool_;
  406. /// @brief A client-id used in most tests
  407. ClientIdPtr client_id_;
  408. int rcode_;
  409. isc::data::ConstElementPtr comment_;
  410. /// @brief Server object under test.
  411. NakedDhcpv4Srv srv_;
  412. };
  413. /// @brief Runs parser in JSON mode, useful for parser testing
  414. ///
  415. /// @param in string to be parsed
  416. /// @return ElementPtr structure representing parsed JSON
  417. inline isc::data::ConstElementPtr
  418. parseJSON(const std::string& in)
  419. {
  420. isc::dhcp::Parser4Context ctx;
  421. return (ctx.parseString(in, isc::dhcp::Parser4Context::PARSER_JSON));
  422. }
  423. /// @brief Runs parser in Dhcp4 mode
  424. ///
  425. /// This is a simplified Dhcp4 mode, so no outer { } and "Dhcp4" is
  426. /// needed. This format is used by most of the tests.
  427. ///
  428. /// @param in string to be parsed
  429. /// @param verbose display the exception message when it fails
  430. /// @return ElementPtr structure representing parsed JSON
  431. inline isc::data::ConstElementPtr
  432. parseDHCP4(const std::string& in, bool verbose = false)
  433. {
  434. try {
  435. isc::dhcp::Parser4Context ctx;
  436. return (ctx.parseString(in, isc::dhcp::Parser4Context::SUBPARSER_DHCP4));
  437. }
  438. catch (const std::exception& ex) {
  439. if (verbose) {
  440. std::cout << "EXCEPTION: " << ex.what() << std::endl;
  441. }
  442. throw;
  443. }
  444. }
  445. /// @brief Runs parser in option definition mode
  446. ///
  447. /// This function parses specified text as JSON that defines option definitions.
  448. ///
  449. /// @param in string to be parsed
  450. /// @param verbose display the exception message when it fails
  451. /// @return ElementPtr structure representing parsed JSON
  452. inline isc::data::ConstElementPtr
  453. parseOPTION_DEF(const std::string& in, bool verbose = false)
  454. {
  455. try {
  456. isc::dhcp::Parser4Context ctx;
  457. return (ctx.parseString(in, isc::dhcp::Parser4Context::PARSER_OPTION_DEF));
  458. }
  459. catch (const std::exception& ex) {
  460. if (verbose) {
  461. std::cout << "EXCEPTION: " << ex.what() << std::endl;
  462. }
  463. throw;
  464. }
  465. }
  466. }; // end of isc::dhcp::test namespace
  467. }; // end of isc::dhcp namespace
  468. }; // end of isc namespace
  469. #endif // DHCP4_TEST_UTILS_H