1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
// Copyright (C) 2014-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef DHCP4_CLIENT_H
#define DHCP4_CLIENT_H

#include <asiolink/io_address.h>
#include <dhcp/hwaddr.h>
#include <dhcp/option.h>
#include <dhcp/pkt4.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <util/optional.h>
#include <boost/noncopyable.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/shared_ptr.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <set><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

namespace isc {
namespace dhcp {
namespace test {

/// @brief General error emitted by the DHCP4 test client.
class Dhcp4ClientError : public isc::Exception {
public:
    Dhcp4ClientError(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what) { }
};

/// @brief DHCPv4 client used for unit testing.
///
/// This class implements a DHCPv4 "client" which interoperates with the
/// @c NakedDhcpv4Srv class. It calls @c NakedDhcpv4Srv::fakeReceive to
/// deliver client messages to the server for processing. The server places
/// the response in the @c NakedDhcpv4Srv::fake_sent_ container. The client
/// pops messages from this container which simulates reception of the
/// response from the server.
///
/// The client maintains the leases it acquired from the server.
///
/// The client exposes a set of functions which simulate different exchange
/// types between the client and the server. It also provides the access to
/// the objects encapsulating responses from the server so as it is possible
/// to verify from the unit test that the server's response is correct.
class Dhcp4Client : public boost::noncopyable {
public:

    /// @brief States of the DHCP client.
    enum State {
        SELECTING,
        INIT_REBOOT,
        RENEWING,
        REBINDING
    };

    /// @brief Holds the DHCPv4 messages taking part in transaction between
    /// the client and the server.
    struct Context {
        /// @brief Holds the last sent message from the client to the server.
        Pkt4Ptr query_;
        /// @brief Holds the last sent message by the server to the client.
        Pkt4Ptr response_;
    };

    /// @brief Holds the configuration of the client received from the
    /// DHCP server.
    struct Configuration {
        /// @brief Holds IP addresses received in the Routers option.
        Option4AddrLst::AddressContainer routers_;
        /// @brief Holds IP addresses received in the DNS Servers option.
        Option4AddrLst::AddressContainer dns_servers_;
        /// @brief Holds IP addresses received in the Log Servers option.
        Option4AddrLst::AddressContainer log_servers_;
        /// @brief Holds IP addresses received in the Quotes Servers option.
        Option4AddrLst::AddressContainer quotes_servers_;
        /// @brief Vendor Specific options
        OptionCollection vendor_suboptions_;
        /// @brief Holds a lease obtained by the client.
        Lease4 lease_;
        /// @brief Holds server id of the server which responded to the client's
        /// request.
        asiolink::IOAddress serverid_;
        /// @brief Holds returned siaddr.
        asiolink::IOAddress siaddr_;
        /// @brief Holds returned sname.
        std::string sname_;
        /// @brief Holds returned (boot)file.
        std::string boot_file_name_;

        /// @brief Constructor.
        Configuration();

        /// @brief Sets configuration values to defaults.
        void reset();
    };

    /// @brief Creates a new client.
    ///
    /// @param Initial client's state.
    Dhcp4Client(const State& state = SELECTING);

    /// @brief Creates a new client that communicates with a specified server.
    ///
    /// @param srv An instance of the DHCPv4 server to be used.
    /// @param state Initial client's state.
    Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
                const State& state = SELECTING);

    /// @brief Creates a lease for the client using the specified address
    /// and valid lifetime.
    ///
    /// This method creates the lease using the specified address and
    /// valid lease lifetime. The client will use this lease in any
    /// future communication with the DHCP server. One of the use cases
    /// for this method is to pre-configure the client with the explicitly
    /// given address before it sends the DHCPINFORM to the DHCP server.
    /// The client will inject the leased address into the ciaddr field
    /// of the DHCPINFORM message.
    ///
    /// @param addr Lease address.
    /// @param valid_lft Valid lifetime.
    void createLease(const asiolink::IOAddress& addr, const uint32_t valid_lft);

    /// @brief Sends DHCPDISCOVER message to the server and receives response.
    ///
    /// The message being sent to the server includes Parameter Request List
    /// option if any options to be requested have been specified using the
    /// @c requestOptions or @c requestOption methods.
    ///
    /// The configuration returned by the server in the DHCPOFFER message is
    /// NOT stored in the client configuration: @c config_.
    ///
    /// @param requested_addr A pointer to the IP Address to be sent in the
    /// Requested IP Address option or NULL if the option should not be
    /// included.
    void doDiscover(const boost::shared_ptr<asiolink::IOAddress>&
                    requested_addr = boost::shared_ptr<asiolink::IOAddress>());

    /// @brief Perform 4-way exchange with a server.
    ///
    /// This method calls @c doDiscover and @c doRequest to perform the 4-way
    /// exchange with the server.
    ///
    /// @param requested_addr A pointer to the address to be requested using the
    /// Requested IP Address option.
    void doDORA(const boost::shared_ptr<asiolink::IOAddress>&
                requested_addr = boost::shared_ptr<asiolink::IOAddress>());

    /// @brief Sends DHCPINFORM message to the server and receives response.
    ///
    /// This function simulates sending the DHCPINFORM message to the server
    /// and receiving server's response (if any). The server's response and the
    /// message sent to the server is stored in the context structure and can
    /// be accessed using @c getContext function.
    ///
    /// The configuration returned by the server is stored in the
    /// @c config_ public member and can be accessed directly.
    ///
    /// @param set_ciaddr Indicates if the ciaddr should be set for an
    /// outgoing message and defaults to true. Note, that the RFC2131 mandates
    /// setting the ciaddr for DHCPINFORM but the server may still want to
    /// respond if the ciaddr is not set.
    ///
    /// @throw This function doesn't thrown exceptions on its own, but it calls
    /// functions that are not exception safe, so it may emit an exception if
    /// an error occurs.
    void doInform(const bool set_ciaddr = true);

    /// @brief Sends DHCPRELEASE Message to the server.
    ///
    /// This method simulates sending the DHCPRELEASE message to the server.
    /// The released lease is removed from the client's configuration.
    void doRelease();


    /// @brief Sends DHCPDECLINE Message to the server.
    ///
    /// This method simulates sending the DHCPDECLINE message to the server.
    /// The released lease is removed from the client's configuration.
    void doDecline();

    /// @brief Sends DHCPREQUEST Message to the server and receives a response.
    ///
    /// This method simulates sending the DHCPREQUEST message to the server and
    /// receiving a response. The DHCPREQUEST message can be used by the client
    /// being in various states:
    /// - SELECTING - client is trying to obtain a new lease and it has selected
    /// the server using the DHCPDISCOVER.
    /// - INIT-REBOOT - client cached an address it was previously using and is
    /// now trying to verify if this address is still valid.
    /// - RENEW - client's renewal timer has passed and the client is trying to
    /// extend the lifetime of the lease.
    /// - REBIND - client's rebind timer has passed and the client is trying to
    /// extend the lifetime of the lease from any server.
    ///
    /// Depending on the state that the client is in, different combinations of
    /// - ciaddr
    /// - Requested IP Address option
    /// - server identifier
    /// are used (as per RFC2131, section 4.3.2). Therefore, the unit tests
    /// must set the appropriate state of the client prior to calling this
    /// method using the @c setState function.
    ///
    /// When the server returns the DHCPACK the configuration carried in the
    /// DHCPACK message is applied and can be obtained from the @c config_.
    void doRequest();

    /// @brief Receives a response from the server.
    ///
    /// This method is useful to receive response from the server after
    /// parking a packet. In this case, the packet is not received as a
    /// result of initial exchange, e.g. @c doRequest. The test can call
    /// this method to complete the transaction when it expects that the
    /// packet has been unparked.
    void receiveResponse();

    /// @brief Generates a hardware address used by the client.
    ///
    /// It assigns random values to the bytes of the hardware address.
    ///
    /// @param htype hardware address type. Currently the only type
    /// supported is Ethernet hardware address.
    ///
    /// @return Pointer to the generated hardware address.
    HWAddrPtr generateHWAddr(const uint8_t htype = HTYPE_ETHER) const;

    /// @brief Returns HW address used by the client.
    HWAddrPtr getHWAddress() const {
        return (hwaddr_);
    }

    /// @brief Returns current context.
    const Context& getContext() const {
        return (context_);
    }

    /// @brief Returns the server that the client is communicating with.
    boost::shared_ptr<NakedDhcpv4Srv> getServer() const {
        return (srv_);
    }

    /// @brief Creates the client id from the client id in the textual format.
    ///
    /// The generated client id will be added to the client's messages to the
    /// server.
    ///
    /// @param clientid Client id in the textual format. Use the empty client id
    /// value to not include the client id.
    void includeClientId(const std::string& clientid);

    /// @brief Creates an instance of the Client FQDN option to be included
    /// in the client's message.
    ///
    /// @param flags Flags.
    /// @param fqdn_name Name in the textual format.
    /// @param fqdn_type Type of the name (fully qualified or partial).
    void includeFQDN(const uint8_t flags, const std::string& fqdn_name,
                     Option4ClientFqdn::DomainNameType fqdn_type);

    /// @brief Creates an instance of the Hostname option to be included
    /// in the client's message.
    ///
    /// @param name Name to be stored in the option.
    void includeHostname(const std::string& name);

    /// @brief Modifies the client's HW address (adds one to it).
    ///
    /// The HW address should be modified to test negative scenarios when the
    /// client acquires a lease and tries to renew it with a different HW
    /// address. The server should detect the HW address mismatch and react
    /// accordingly.
    ///
    /// The HW address modification affects the value returned by the
    /// @c Dhcp4Client::getHWAddress.
    void modifyHWAddr();

    /// @brief Specify an option to be requested by a client.
    ///
    /// This function adds option code to the collection of option
    /// codes to be requested by a client.
    ///
    /// @param option Option code to be requested. The value of 0 is
    /// ignored and the function is no-op.
    void requestOption(const uint8_t option);

    /// @brief Specifies options to be requested by the client.
    ///
    /// This function configures the client to request options having
    /// specified codes using Parameter Request List option. The default
    /// value of 0 specify that the option is not requested.
    ///
    /// If there are options specified to be requested before the function
    /// is called, the new option codes override previously specified ones.
    /// In order to clear the list of requested options call
    /// @c requestOptions(0).
    ///
    /// @param option1 First option to be requested.
    /// @param option2 Second option to be requested (optional).
    /// @param option3 Third option to be requested (optional).
    void requestOptions(const uint8_t option1,
                        const uint8_t option2 = 0,
                        const uint8_t option3 = 0);

    /// @brief Sets circuit-id value to be included in the circuit-id
    /// sub option of the RAI option.
    ///
    /// @param circuit_id New circuit-id value.
    void setCircuitId(const std::string& circuit_id) {
        circuit_id_ = circuit_id;
    }

    /// @brief Sets destination address for the messages being sent by the
    /// client.
    ///
    /// By default, the client uses broadcast address 255.255.255.255 to
    /// communicate with the server. In certain cases it may be desired
    /// that different address is used. This function sets the new address
    /// for all future exchanges with the server.
    ///
    /// @param dest_addr New destination address.
    void setDestAddress(const asiolink::IOAddress& dest_addr) {
        dest_addr_ = dest_addr;
    }

    /// @brief Sets the explicit hardware address for the client.
    ///
    /// @param hwaddr_str String representation of the HW address. Use an
    /// empty string to set the NULL hardware address.
    void setHWAddress(const std::string& hwaddr_str);

    /// @brief Sets the interface over which the messages should be sent.
    ///
    /// @param iface_name Name of the interface over which the messages should
    /// be sent.
    void setIfaceName(const std::string& iface_name) {
        iface_name_ = iface_name;
    }

    /// @brief Sets the interface over which the messages should be sent.
    ///
    /// @param iface_index Index of the interface over which the
    /// messages should be sent.
    void setIfaceIndex(uint32_t iface_index) {
        iface_index_ = iface_index;
    }

    /// @brief Sets client state.
    ///
    /// Depending on the current state the client's behavior is different
    /// when sending Request messages as per RFC2131, section 4.3.2.
    ///
    /// @param state New client's state.
    void setState(const State& state) {
        state_ = state;
    }

    /// @brief Simulate sending messages through a relay.
    ///
    /// @param use Parameter which 'true' value indicates that client should
    /// simulate sending messages via relay.
    /// @param relay_addr Relay address
    /// @param sf_relay_addr Server facing relay address.
    void useRelay(const bool use = true,
                  const asiolink::IOAddress& relay_addr =
                  asiolink::IOAddress("192.0.2.2"),
                  const asiolink::IOAddress& sf_relay_addr =
                  asiolink::IOAddress("10.0.0.2")) {
        use_relay_ = use;
        relay_addr_ = relay_addr;
        server_facing_relay_addr_ = sf_relay_addr;
    }

    /// @brief Current client's configuration obtained from the server.
    Configuration config_;

    /// @brief Specific ciaddr to be used in client's messages.
    ///
    /// If this value is "unspecified" the default values will be used
    /// by the client. If this value is specified, it will override ciaddr
    /// in the client's messages.
    isc::util::Optional<asiolink::IOAddress> ciaddr_;

    /// @brief Adds extra option (an option the client will always send)
    ///
    /// @param opt additional option to be sent
    void addExtraOption(const OptionPtr& opt);

    /// @brief Add a client class.
    ///
    /// @param client_class name of the class to be added.
    void addClass(const ClientClass& client_class);

private:
    /// @brief Appends extra options, previously added with addExtraOption()
    ///
    /// @brief Copies options from extra_options_ into outgoing message
    void appendExtraOptions();

    /// @brief Appends extra classes, previously added with addClass()
    ///
    /// @brief Add client classes from classes_ to incoming message
    void appendClasses();

    /// @brief Creates and adds Requested IP Address option to the client's
    /// query.
    ///
    /// @param addr Address to be added in the Requested IP Address option.
    void addRequestedAddress(const asiolink::IOAddress& addr);

    /// @brief Stores configuration received from the server.
    ///
    /// This methods stores the configuration obtained from the DHCP server
    /// in the @c Configuration structure. This configuration includes:
    /// - obtained lease
    /// - server id of the server that provided the configuration
    /// - lease
    /// - selected options (used by unit tests):
    ///   - DNS Servers
    ///   - Routers
    ///   - Log Servers
    ///   - Quotes Servers
    void applyConfiguration();

    /// @brief Creates client's side DHCP message.
    ///
    /// @param msg_type Type of the message to be created.
    /// @return An instance of the message created.
    Pkt4Ptr createMsg(const uint8_t msg_type);

    /// @brief Includes the Client Identifier option in the client's message.
    ///
    /// This function creates an instance of the Client Identifier option
    /// if the client identifier has been specified and includes this
    /// option in the client's message to the server.
    void appendClientId();

    /// @brief Includes the Server Identifier option in the client's message.
    ///
    /// This function creates an instance of the Server Identifier option.
    /// It uses whatever information is stored in config_.serverid_.
    void appendServerId();

    /// @brief Includes FQDN or Hostname option in the client's message.
    ///
    /// This method checks if @c fqdn_ or @c hostname_ is specified and
    /// includes it in the client's message. If both are specified, the
    /// @c fqdn_ will be used.
    void appendName();

    /// @brief Include PRL Option in the query message.
    ///
    /// This function creates the instance of the PRL (Parameter Request List)
    /// option and adds option codes from the @c requested_options_ to it.
    /// It later adds the PRL option to the @c context_.query_ message
    /// if it is non-NULL.
    void appendPRL();

    /// @brief Simulates reception of the message from the server.
    ///
    /// @return Received message.
    Pkt4Ptr receiveOneMsg();

    /// @brief Simulates sending a message to the server.
    ///
    /// This function instantly triggers processing of the message by the
    /// server. The server's response can be gathered by invoking the
    /// @c receiveOneMsg function.
    ///
    /// @param msg Message to be sent.
    void sendMsg(const Pkt4Ptr& msg);

    /// @brief Current context (sent and received message).
    Context context_;

    /// @biref Current transaction id (altered on each send).
    uint32_t curr_transid_;

    /// @brief Currently used destination address.
    asiolink::IOAddress dest_addr_;

    /// @brief FQDN requested by the client.
    Option4ClientFqdnPtr fqdn_;

    /// @brief Hostname requested by the client.
    OptionStringPtr hostname_;

    /// @brief Current hardware address of the client.
    HWAddrPtr hwaddr_;

    /// @brief Current client identifier.
    ClientIdPtr clientid_;

    /// @brief Interface to be used to send the messages (name).
    std::string iface_name_;

    /// @brief Interface to be used to send the messages (index).
    uint32_t iface_index_;

    /// @brief Relay address to use.
    asiolink::IOAddress relay_addr_;

    /// @brief Collection of options codes to be requested by the client.
    std::set<uint8_t> requested_options_;

    /// @brief Address of the relay interface connected to the server.
    asiolink::IOAddress server_facing_relay_addr_;

    /// @brief Pointer to the server that the client is communicating with.
    boost::shared_ptr<NakedDhcpv4Srv> srv_;

    /// @brief Current state of the client.
    State state_;

    /// @brief Enable relaying messages to the server.
    bool use_relay_;

    /// @brief Specifies value to be inserted into circuit-id sub option
    /// of the RAI option.
    std::string circuit_id_;

    /// @brief Extra options the client will send.
    OptionCollection extra_options_;

    /// @brief Extra classes to add to the query.
    ClientClasses classes_;
};

} // end of namespace isc::dhcp::test
} // end of namespace isc::dhcp
} // end of namespace isc

#endif // DHCP4_CLIENT_H