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