1 // Copyright (C) 2014-2020 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 7 #ifndef DHCP4_CLIENT_H 8 #define DHCP4_CLIENT_H 9 10 #include <asiolink/io_address.h> 11 #include <dhcp/hwaddr.h> 12 #include <dhcp/option.h> 13 #include <dhcp/pkt4.h> 14 #include <dhcp4/tests/dhcp4_test_utils.h> 15 #include <util/optional.h> 16 #include <boost/noncopyable.hpp> 17 #include <boost/shared_ptr.hpp> 18 #include <set> 19 20 namespace isc { 21 namespace dhcp { 22 namespace test { 23 24 /// @brief General error emitted by the DHCP4 test client. 25 class Dhcp4ClientError : public isc::Exception { 26 public: Dhcp4ClientError(const char * file,size_t line,const char * what)27 Dhcp4ClientError(const char* file, size_t line, const char* what) : 28 isc::Exception(file, line, what) { }; 29 }; 30 31 /// @brief DHCPv4 client used for unit testing. 32 /// 33 /// This class implements a DHCPv4 "client" which interoperates with the 34 /// @c NakedDhcpv4Srv class. It calls @c NakedDhcpv4Srv::fakeReceive to 35 /// deliver client messages to the server for processing. The server places 36 /// the response in the @c NakedDhcpv4Srv::fake_sent_ container. The client 37 /// pops messages from this container which simulates reception of the 38 /// response from the server. 39 /// 40 /// The client maintains the leases it acquired from the server. 41 /// 42 /// The client exposes a set of functions which simulate different exchange 43 /// types between the client and the server. It also provides the access to 44 /// the objects encapsulating responses from the server so as it is possible 45 /// to verify from the unit test that the server's response is correct. 46 class Dhcp4Client : public boost::noncopyable { 47 public: 48 49 /// @brief States of the DHCP client. 50 enum State { 51 SELECTING, 52 INIT_REBOOT, 53 RENEWING, 54 REBINDING 55 }; 56 57 /// @brief Holds the DHCPv4 messages taking part in transaction between 58 /// the client and the server. 59 struct Context { 60 /// @brief Holds the last sent message from the client to the server. 61 Pkt4Ptr query_; 62 /// @brief Holds the last sent message by the server to the client. 63 Pkt4Ptr response_; 64 }; 65 66 /// @brief Holds the configuration of the client received from the 67 /// DHCP server. 68 struct Configuration { 69 /// @brief Holds IP addresses received in the Routers option. 70 Option4AddrLst::AddressContainer routers_; 71 /// @brief Holds IP addresses received in the DNS Servers option. 72 Option4AddrLst::AddressContainer dns_servers_; 73 /// @brief Holds IP addresses received in the Log Servers option. 74 Option4AddrLst::AddressContainer log_servers_; 75 /// @brief Holds IP addresses received in the Quotes Servers option. 76 Option4AddrLst::AddressContainer quotes_servers_; 77 /// @brief Vendor Specific options 78 OptionCollection vendor_suboptions_; 79 /// @brief Holds a lease obtained by the client. 80 Lease4 lease_; 81 /// @brief Holds server id of the server which responded to the client's 82 /// request. 83 asiolink::IOAddress serverid_; 84 /// @brief Holds returned siaddr. 85 asiolink::IOAddress siaddr_; 86 /// @brief Holds returned sname. 87 std::string sname_; 88 /// @brief Holds returned (boot)file. 89 std::string boot_file_name_; 90 91 /// @brief Constructor. 92 Configuration(); 93 94 /// @brief Sets configuration values to defaults. 95 void reset(); 96 }; 97 98 /// @brief Creates a new client. 99 /// 100 /// @param Initial client's state. 101 Dhcp4Client(const State& state = SELECTING); 102 103 /// @brief Creates a new client that communicates with a specified server. 104 /// 105 /// @param srv An instance of the DHCPv4 server to be used. 106 /// @param state Initial client's state. 107 Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv, 108 const State& state = SELECTING); 109 110 /// @brief Creates a lease for the client using the specified address 111 /// and valid lifetime. 112 /// 113 /// This method creates the lease using the specified address and 114 /// valid lease lifetime. The client will use this lease in any 115 /// future communication with the DHCP server. One of the use cases 116 /// for this method is to pre-configure the client with the explicitly 117 /// given address before it sends the DHCPINFORM to the DHCP server. 118 /// The client will inject the leased address into the ciaddr field 119 /// of the DHCPINFORM message. 120 /// 121 /// @param addr Lease address. 122 /// @param valid_lft Valid lifetime. 123 void createLease(const asiolink::IOAddress& addr, const uint32_t valid_lft); 124 125 /// @brief Sends DHCPDISCOVER message to the server and receives response. 126 /// 127 /// The message being sent to the server includes Parameter Request List 128 /// option if any options to be requested have been specified using the 129 /// @c requestOptions or @c requestOption methods. 130 /// 131 /// The configuration returned by the server in the DHCPOFFER message is 132 /// NOT stored in the client configuration: @c config_. 133 /// 134 /// @param requested_addr A pointer to the IP Address to be sent in the 135 /// Requested IP Address option or NULL if the option should not be 136 /// included. 137 void doDiscover(const boost::shared_ptr<asiolink::IOAddress>& 138 requested_addr = boost::shared_ptr<asiolink::IOAddress>()); 139 140 /// @brief Perform 4-way exchange with a server. 141 /// 142 /// This method calls @c doDiscover and @c doRequest to perform the 4-way 143 /// exchange with the server. 144 /// 145 /// @param requested_addr A pointer to the address to be requested using the 146 /// Requested IP Address option. 147 void doDORA(const boost::shared_ptr<asiolink::IOAddress>& 148 requested_addr = boost::shared_ptr<asiolink::IOAddress>()); 149 150 /// @brief Sends DHCPINFORM message to the server and receives response. 151 /// 152 /// This function simulates sending the DHCPINFORM message to the server 153 /// and receiving server's response (if any). The server's response and the 154 /// message sent to the server is stored in the context structure and can 155 /// be accessed using @c getContext function. 156 /// 157 /// The configuration returned by the server is stored in the 158 /// @c config_ public member and can be accessed directly. 159 /// 160 /// @param set_ciaddr Indicates if the ciaddr should be set for an 161 /// outgoing message and defaults to true. Note, that the RFC2131 mandates 162 /// setting the ciaddr for DHCPINFORM but the server may still want to 163 /// respond if the ciaddr is not set. 164 /// 165 /// @throw This function doesn't thrown exceptions on its own, but it calls 166 /// functions that are not exception safe, so it may emit an exception if 167 /// an error occurs. 168 void doInform(const bool set_ciaddr = true); 169 170 /// @brief Sends DHCPRELEASE Message to the server. 171 /// 172 /// This method simulates sending the DHCPRELEASE message to the server. 173 /// The released lease is removed from the client's configuration. 174 void doRelease(); 175 176 177 /// @brief Sends DHCPDECLINE Message to the server. 178 /// 179 /// This method simulates sending the DHCPDECLINE message to the server. 180 /// The released lease is removed from the client's configuration. 181 void doDecline(); 182 183 /// @brief Sends DHCPREQUEST Message to the server and receives a response. 184 /// 185 /// This method simulates sending the DHCPREQUEST message to the server and 186 /// receiving a response. The DHCPREQUEST message can be used by the client 187 /// being in various states: 188 /// - SELECTING - client is trying to obtain a new lease and it has selected 189 /// the server using the DHCPDISCOVER. 190 /// - INIT-REBOOT - client cached an address it was previously using and is 191 /// now trying to verify if this address is still valid. 192 /// - RENEW - client's renewal timer has passed and the client is trying to 193 /// extend the lifetime of the lease. 194 /// - REBIND - client's rebind timer has passed and the client is trying to 195 /// extend the lifetime of the lease from any server. 196 /// 197 /// Depending on the state that the client is in, different combinations of 198 /// - ciaddr 199 /// - Requested IP Address option 200 /// - server identifier 201 /// are used (as per RFC2131, section 4.3.2). Therefore, the unit tests 202 /// must set the appropriate state of the client prior to calling this 203 /// method using the @c setState function. 204 /// 205 /// When the server returns the DHCPACK the configuration carried in the 206 /// DHCPACK message is applied and can be obtained from the @c config_. 207 void doRequest(); 208 209 /// @brief Receives a response from the server. 210 /// 211 /// This method is useful to receive response from the server after 212 /// parking a packet. In this case, the packet is not received as a 213 /// result of initial exchange, e.g. @c doRequest. The test can call 214 /// this method to complete the transaction when it expects that the 215 /// packet has been unparked. 216 void receiveResponse(); 217 218 /// @brief Generates a hardware address used by the client. 219 /// 220 /// It assigns random values to the bytes of the hardware address. 221 /// 222 /// @param htype hardware address type. Currently the only type 223 /// supported is Ethernet hardware address. 224 /// 225 /// @return Pointer to the generated hardware address. 226 HWAddrPtr generateHWAddr(const uint8_t htype = HTYPE_ETHER) const; 227 228 /// @brief Returns HW address used by the client. getHWAddress()229 HWAddrPtr getHWAddress() const { 230 return (hwaddr_); 231 } 232 233 /// @brief Returns current context. getContext()234 const Context& getContext() const { 235 return (context_); 236 } 237 238 /// @brief Returns the server that the client is communicating with. getServer()239 boost::shared_ptr<NakedDhcpv4Srv> getServer() const { 240 return (srv_); 241 } 242 243 /// @brief Creates the client id from the client id in the textual format. 244 /// 245 /// The generated client id will be added to the client's messages to the 246 /// server. 247 /// 248 /// @param clientid Client id in the textual format. Use the empty client id 249 /// value to not include the client id. 250 void includeClientId(const std::string& clientid); 251 252 /// @brief Creates an instance of the Client FQDN option to be included 253 /// in the client's message. 254 /// 255 /// @param flags Flags. 256 /// @param fqdn_name Name in the textual format. 257 /// @param fqdn_type Type of the name (fully qualified or partial). 258 void includeFQDN(const uint8_t flags, const std::string& fqdn_name, 259 Option4ClientFqdn::DomainNameType fqdn_type); 260 261 /// @brief Creates an instance of the Hostname option to be included 262 /// in the client's message. 263 /// 264 /// @param name Name to be stored in the option. 265 void includeHostname(const std::string& name); 266 267 /// @brief Modifies the client's HW address (adds one to it). 268 /// 269 /// The HW address should be modified to test negative scenarios when the 270 /// client acquires a lease and tries to renew it with a different HW 271 /// address. The server should detect the HW address mismatch and react 272 /// accordingly. 273 /// 274 /// The HW address modification affects the value returned by the 275 /// @c Dhcp4Client::getHWAddress. 276 void modifyHWAddr(); 277 278 /// @brief Specify an option to be requested by a client. 279 /// 280 /// This function adds option code to the collection of option 281 /// codes to be requested by a client. 282 /// 283 /// @param option Option code to be requested. The value of 0 is 284 /// ignored and the function is no-op. 285 void requestOption(const uint8_t option); 286 287 /// @brief Specifies options to be requested by the client. 288 /// 289 /// This function configures the client to request options having 290 /// specified codes using Parameter Request List option. The default 291 /// value of 0 specify that the option is not requested. 292 /// 293 /// If there are options specified to be requested before the function 294 /// is called, the new option codes override previously specified ones. 295 /// In order to clear the list of requested options call 296 /// @c requestOptions(0). 297 /// 298 /// @param option1 First option to be requested. 299 /// @param option2 Second option to be requested (optional). 300 /// @param option3 Third option to be requested (optional). 301 void requestOptions(const uint8_t option1, 302 const uint8_t option2 = 0, 303 const uint8_t option3 = 0); 304 305 /// @brief Sets circuit-id value to be included in the circuit-id 306 /// sub option of the RAI option. 307 /// 308 /// @param circuit_id New circuit-id value. setCircuitId(const std::string & circuit_id)309 void setCircuitId(const std::string& circuit_id) { 310 circuit_id_ = circuit_id; 311 } 312 313 /// @brief Sets destination address for the messages being sent by the 314 /// client. 315 /// 316 /// By default, the client uses broadcast address 255.255.255.255 to 317 /// communicate with the server. In certain cases it may be desired 318 /// that different address is used. This function sets the new address 319 /// for all future exchanges with the server. 320 /// 321 /// @param dest_addr New destination address. setDestAddress(const asiolink::IOAddress & dest_addr)322 void setDestAddress(const asiolink::IOAddress& dest_addr) { 323 dest_addr_ = dest_addr; 324 } 325 326 /// @brief Sets the explicit hardware address for the client. 327 /// 328 /// @param hwaddr_str String representation of the HW address. Use an 329 /// empty string to set the NULL hardware address. 330 void setHWAddress(const std::string& hwaddr_str); 331 332 /// @brief Sets the interface over which the messages should be sent. 333 /// 334 /// @param iface_name Name of the interface over which the messages should 335 /// be sent. setIfaceName(const std::string & iface_name)336 void setIfaceName(const std::string& iface_name) { 337 iface_name_ = iface_name; 338 } 339 340 /// @brief Sets the interface over which the messages should be sent. 341 /// 342 /// @param iface_index Index of the interface over which the 343 /// messages should be sent. setIfaceIndex(uint32_t iface_index)344 void setIfaceIndex(uint32_t iface_index) { 345 iface_index_ = iface_index; 346 } 347 348 /// @brief Sets client state. 349 /// 350 /// Depending on the current state the client's behavior is different 351 /// when sending Request messages as per RFC2131, section 4.3.2. 352 /// 353 /// @param state New client's state. setState(const State & state)354 void setState(const State& state) { 355 state_ = state; 356 } 357 358 /// @brief Simulate sending messages through a relay. 359 /// 360 /// @param use Parameter which 'true' value indicates that client should 361 /// simulate sending messages via relay. 362 /// @param relay_addr Relay address 363 /// @param sf_relay_addr Server facing relay address. 364 void useRelay(const bool use = true, 365 const asiolink::IOAddress& relay_addr = 366 asiolink::IOAddress("192.0.2.2"), 367 const asiolink::IOAddress& sf_relay_addr = 368 asiolink::IOAddress("10.0.0.2")) { 369 use_relay_ = use; 370 relay_addr_ = relay_addr; 371 server_facing_relay_addr_ = sf_relay_addr; 372 } 373 374 /// @brief Current client's configuration obtained from the server. 375 Configuration config_; 376 377 /// @brief Specific ciaddr to be used in client's messages. 378 /// 379 /// If this value is "unspecified" the default values will be used 380 /// by the client. If this value is specified, it will override ciaddr 381 /// in the client's messages. 382 isc::util::Optional<asiolink::IOAddress> ciaddr_; 383 384 /// @brief Adds extra option (an option the client will always send) 385 /// 386 /// @param opt additional option to be sent 387 void addExtraOption(const OptionPtr& opt); 388 389 /// @brief Add a client class. 390 /// 391 /// @param client_class name of the class to be added. 392 void addClass(const ClientClass& client_class); 393 394 private: 395 /// @brief Appends extra options, previously added with addExtraOption() 396 /// 397 /// @brief Copies options from extra_options_ into outgoing message 398 void appendExtraOptions(); 399 400 /// @brief Appends extra classes, previously added with addClass() 401 /// 402 /// @brief Add client classes from classes_ to incoming message 403 void appendClasses(); 404 405 /// @brief Creates and adds Requested IP Address option to the client's 406 /// query. 407 /// 408 /// @param addr Address to be added in the Requested IP Address option. 409 void addRequestedAddress(const asiolink::IOAddress& addr); 410 411 /// @brief Stores configuration received from the server. 412 /// 413 /// This methods stores the configuration obtained from the DHCP server 414 /// in the @c Configuration structure. This configuration includes: 415 /// - obtained lease 416 /// - server id of the server that provided the configuration 417 /// - lease 418 /// - selected options (used by unit tests): 419 /// - DNS Servers 420 /// - Routers 421 /// - Log Servers 422 /// - Quotes Servers 423 void applyConfiguration(); 424 425 /// @brief Creates client's side DHCP message. 426 /// 427 /// @param msg_type Type of the message to be created. 428 /// @return An instance of the message created. 429 Pkt4Ptr createMsg(const uint8_t msg_type); 430 431 /// @brief Includes the Client Identifier option in the client's message. 432 /// 433 /// This function creates an instance of the Client Identifier option 434 /// if the client identifier has been specified and includes this 435 /// option in the client's message to the server. 436 void appendClientId(); 437 438 /// @brief Includes the Server Identifier option in the client's message. 439 /// 440 /// This function creates an instance of the Server Identifier option. 441 /// It uses whatever information is stored in config_.serverid_. 442 void appendServerId(); 443 444 /// @brief Includes FQDN or Hostname option in the client's message. 445 /// 446 /// This method checks if @c fqdn_ or @c hostname_ is specified and 447 /// includes it in the client's message. If both are specified, the 448 /// @c fqdn_ will be used. 449 void appendName(); 450 451 /// @brief Include PRL Option in the query message. 452 /// 453 /// This function creates the instance of the PRL (Parameter Request List) 454 /// option and adds option codes from the @c requested_options_ to it. 455 /// It later adds the PRL option to the @c context_.query_ message 456 /// if it is non-NULL. 457 void appendPRL(); 458 459 /// @brief Simulates reception of the message from the server. 460 /// 461 /// @return Received message. 462 Pkt4Ptr receiveOneMsg(); 463 464 /// @brief Simulates sending a message to the server. 465 /// 466 /// This function instantly triggers processing of the message by the 467 /// server. The server's response can be gathered by invoking the 468 /// @c receiveOneMsg function. 469 /// 470 /// @param msg Message to be sent. 471 void sendMsg(const Pkt4Ptr& msg); 472 473 /// @brief Current context (sent and received message). 474 Context context_; 475 476 /// @biref Current transaction id (altered on each send). 477 uint32_t curr_transid_; 478 479 /// @brief Currently used destination address. 480 asiolink::IOAddress dest_addr_; 481 482 /// @brief FQDN requested by the client. 483 Option4ClientFqdnPtr fqdn_; 484 485 /// @brief Hostname requested by the client. 486 OptionStringPtr hostname_; 487 488 /// @brief Current hardware address of the client. 489 HWAddrPtr hwaddr_; 490 491 /// @brief Current client identifier. 492 ClientIdPtr clientid_; 493 494 /// @brief Interface to be used to send the messages (name). 495 std::string iface_name_; 496 497 /// @brief Interface to be used to send the messages (index). 498 uint32_t iface_index_; 499 500 /// @brief Relay address to use. 501 asiolink::IOAddress relay_addr_; 502 503 /// @brief Collection of options codes to be requested by the client. 504 std::set<uint8_t> requested_options_; 505 506 /// @brief Address of the relay interface connected to the server. 507 asiolink::IOAddress server_facing_relay_addr_; 508 509 /// @brief Pointer to the server that the client is communicating with. 510 boost::shared_ptr<NakedDhcpv4Srv> srv_; 511 512 /// @brief Current state of the client. 513 State state_; 514 515 /// @brief Enable relaying messages to the server. 516 bool use_relay_; 517 518 /// @brief Specifies value to be inserted into circuit-id sub option 519 /// of the RAI option. 520 std::string circuit_id_; 521 522 /// @brief Extra options the client will send. 523 OptionCollection extra_options_; 524 525 /// @brief Extra classes to add to the query. 526 ClientClasses classes_; 527 }; 528 529 } // end of namespace isc::dhcp::test 530 } // end of namespace isc::dhcp 531 } // end of namespace isc 532 533 #endif // DHCP4_CLIENT_H 534