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
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
// Copyright (C) 2013-2022 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/.

/// @file pkt_send_co.cc Defines the pkt4_send and pkt6_send callout functions.

#include <config.h>
#include <asiolink/io_address.h>
#include <hooks/hooks.h>
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option_string.h>
#include <dhcp/option_custom.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaprefix.h>
#include <dhcp/docsis3_option_defs.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
#include <user_chk.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc::dhcp;
using namespace isc::hooks;
using namespace user_chk;
using namespace std;

// prototypes for local helper functions
void generate_output_record(const std::string& id_type_str,
                            const std::string& id_val_str,
                            const std::string& addr_str,
                            const bool& registered);
std::string getV6AddrStr (Pkt6Ptr response);
std::string getAddrStrIA_NA(OptionPtr options);
std::string getAddrStrIA_PD(OptionPtr options);
bool checkIAStatus(boost::shared_ptr<Option6IA>& ia_opt);

void add4Options(Pkt4Ptr& response, const UserPtr& user);
void add4Option(Pkt4Ptr& response, uint8_t opt_code, std::string& opt_value);
void add6Options(Pkt6Ptr& response, const UserPtr& user);
void add6Option(OptionPtr& vendor, uint8_t opt_code, std::string& opt_value);
const UserPtr& getDefaultUser4();
const UserPtr& getDefaultUser6();

// Functions accessed by the hooks framework use C linkage to avoid the name
// mangling that accompanies use of the C++ compiler as well as to avoid
// issues related to namespaces.
extern "C" {

/// @brief  This callout is called at the "pkt4_send" hook.
///
/// This function generates the user check outcome and modifies options
/// to the IPv4 response packet based on whether the user is registered or not.
///
/// It retrieves a pointer to the registered user from the callout context.
/// This value should have been set upstream.  If the registered user pointer
/// is non-null (i.e the user is registered), then a registered user outcome
/// is recorded in the outcome output and the vendor properties are altered
/// based upon this user's properties.
///
/// A null value means the user is not registered and a unregistered user
/// outcome is recorded in the outcome output and the vendor properties
/// are altered based upon the default IPv4 user in the registry (if defined).
///
/// @param handle CalloutHandle which provides access to context.
///
/// @return 0 upon success, non-zero otherwise.
int pkt4_send(CalloutHandle& handle) {<--- Parameter 'handle' can be declared as reference to const
    CalloutHandle::CalloutNextStep status = handle.getStatus();
    if (status == CalloutHandle::NEXT_STEP_DROP) {
        return (0);
    }

    if (status == CalloutHandle::NEXT_STEP_SKIP) {
        isc_throw(isc::InvalidOperation, "packet pack already handled");
    }

    try {
        Pkt4Ptr response;
        handle.getArgument("response4", response);

        uint8_t packet_type = response->getType();
        if (packet_type == DHCPNAK) {
            std::cout << "DHCP UserCheckHook : pkt4_send"
                      << "skipping packet type: "
                      << static_cast<int>(packet_type) << std::endl;
            return (0);
        }

        // Get the user id saved from the query packet.
        HWAddrPtr hwaddr;
        handle.getContext(query_user_id_label, hwaddr);

        // Get registered_user pointer.
        UserPtr registered_user;
        handle.getContext(registered_user_label, registered_user);

        // Fetch the lease address.
        isc::asiolink::IOAddress addr = response->getYiaddr();

        if (registered_user) {
            // add options based on user
            // then generate registered output record
            std::cout << "DHCP UserCheckHook : pkt4_send registered_user is: "
                      << registered_user->getUserId() << std::endl;

            // Add the outcome entry to the output file.
            generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
                                   addr.toText(), true);
            add4Options(response, registered_user);
        } else {
            // add default options based
            // then generate not registered output record
            std::cout << "DHCP UserCheckHook : pkt4_send no registered_user"
                      << std::endl;
            // Add the outcome entry to the output file.
            generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
                                   addr.toText(), false);

            add4Options(response, getDefaultUser4());
        }
    } catch (const std::exception& ex) {
        std::cout << "DHCP UserCheckHook : pkt4_send unexpected error: "
                  << ex.what() << std::endl;
        return (1);
    }

    return (0);
}

/// @brief  This callout is called at the "pkt6_send" hook.
///
/// This function generates the user check outcome and modifies options
/// to the IPv6 response packet based on whether the user is registered or not.
///
/// It retrieves a pointer to the registered user from the callout context.
/// This value should have been set upstream.  If the registered user pointer
/// is non-null (i.e the user is registered), then a registered user outcome
/// is recorded in the outcome output and the vendor properties are altered
/// based upon this user's properties.
///
/// A null value means the user is not registered and a unregistered user
/// outcome is recorded in the outcome output and the vendor properties
/// are altered based upon the default IPv6 user in the registry (if defined).
/// @param handle CalloutHandle which provides access to context.
///
/// @return 0 upon success, non-zero otherwise.
int pkt6_send(CalloutHandle& handle) {<--- Parameter 'handle' can be declared as reference to const
    CalloutHandle::CalloutNextStep status = handle.getStatus();
    if (status == CalloutHandle::NEXT_STEP_DROP) {
        return (0);
    }

    if (status == CalloutHandle::NEXT_STEP_SKIP) {
        isc_throw(isc::InvalidOperation, "packet pack already handled");
    }

    try {
        Pkt6Ptr response;
        handle.getArgument("response6", response);

        // Fetch the lease address as a string
        std::string addr_str = getV6AddrStr(response);
        if (addr_str.empty()) {
            // packet did not contain an address, must be failed.
            std::cout << "pkt6_send: Skipping packet address is blank"
                      << std::endl;
            return (0);
        }

        // Get the user id saved from the query packet.
        DuidPtr duid;
        handle.getContext(query_user_id_label, duid);

        // Get registered_user pointer.
        UserPtr registered_user;
        handle.getContext(registered_user_label, registered_user);

        if (registered_user) {
            // add options based on user
            // then generate registered output record
            std::cout << "DHCP UserCheckHook : pkt6_send registered_user is: "
                      << registered_user->getUserId() << std::endl;
            // Add the outcome entry to the output file.
            generate_output_record(UserId::DUID_STR, duid->toText(),
                                   addr_str, true);
            add6Options(response, registered_user);
        } else {
            // add default options based
            // then generate not registered output record
            std::cout << "DHCP UserCheckHook : pkt6_send no registered_user"
                      << std::endl;
            // Add the outcome entry to the output file.
            generate_output_record(UserId::DUID_STR, duid->toText(),
                                   addr_str, false);
            add6Options(response, getDefaultUser6());
        }
    } catch (const std::exception& ex) {
        std::cout << "DHCP UserCheckHook : pkt6_send unexpected error: "
                  << ex.what() << std::endl;
        return (1);
    }

    return (0);
}

} // extern C

/// @brief Adds IPv4 options to the response packet based on given user
///
/// Adds or replaces IPv4 options with values from the given user, if
/// the user has corresponding properties defined. Currently it supports
/// the following options:
///
/// - DHO_BOOT_FILE_NAME from user property "bootfile"
/// - DHO_TFTP_SERVER_NAME from user property "tftp_server"
///
/// @param response IPv4 response packet
/// @param user User from whom properties are sourced
void add4Options(Pkt4Ptr& response, const UserPtr& user) {
    // If user is null, do nothing.
    if (!user) {
        return;
    }

    // If the user has bootfile property, update it in the response.
    std::string opt_value = user->getProperty("bootfile");
    if (!opt_value.empty()) {
        std::cout << "DHCP UserCheckHook : add4Options "
              << "adding boot file:" << opt_value << std::endl;

        // Add boot file to packet.
        add4Option(response, DHO_BOOT_FILE_NAME, opt_value);

        // Boot file also goes in file field.
        response->setFile((const uint8_t*)(opt_value.c_str()),
                          opt_value.length());
    }

    // If the user has tftp server property, update it in the response.
    opt_value = user->getProperty("tftp_server");
    if (!opt_value.empty()) {
        std::cout << "DHCP UserCheckHook : add4Options "
              << "adding TFTP server:" << opt_value << std::endl;

        // Add tftp server option to packet.
        add4Option(response, DHO_TFTP_SERVER_NAME, opt_value);
    }
    // add next option here
}

/// @brief Adds/updates are specific IPv4 string option in response packet.
///
/// @param response IPV4 response packet to update
/// @param opt_code DHCP standard numeric code of the option
/// @param opt_value String value of the option
void add4Option(Pkt4Ptr& response, uint8_t opt_code, std::string& opt_value) {
    // Remove the option if it exists.
    OptionPtr opt = response->getOption(opt_code);
    if (opt) {
        response->delOption(opt_code);
    }

    // Now add the option.
    opt.reset(new OptionString(Option::V4, opt_code, opt_value));
    response->addOption(opt);
}


/// @brief Adds IPv6 vendor options to the response packet based on given user
///
/// Adds or replaces IPv6 vendor options with values from the given user, if
/// the user has the corresponding properties defined. Currently it supports
/// the following options:
///
/// - DOCSIS3_V6_CONFIG_FILE from user property "bootfile"
/// - DOCSIS3_V6_TFTP_SERVERS from user property "tftp_server"
///
/// @param response IPv5 response packet
/// @param user User from whom properties are sourced
void add6Options(Pkt6Ptr& response, const UserPtr& user) {
    if (!user) {
        return;
    }

    /// @todo: if packets have no vendor opt... do we need to add it
    /// if its not there?  If so how?
    OptionPtr vendor = response->getOption(D6O_VENDOR_OPTS);
    if (!vendor) {
        std::cout << "DHCP UserCheckHook : add6Options "
              << "response has no vendor option to update" << std::endl;
        return;
    }

    // If the user defines bootfile, set the option in response.
    std::string opt_value = user->getProperty("bootfile");
    if (!opt_value.empty()) {
        std::cout << "DHCP UserCheckHook : add6Options "
                  << "adding boot file:" << opt_value << std::endl;
        add6Option(vendor, DOCSIS3_V6_CONFIG_FILE, opt_value);
    }

    // If the user defines tftp server, set the option in response.
    opt_value = user->getProperty("tftp_server");
    if (!opt_value.empty()) {
        std::cout << "DHCP UserCheckHook : add6Options "
                  << "adding tftp server:" << opt_value << std::endl;

        add6Option(vendor, DOCSIS3_V6_TFTP_SERVERS, opt_value);
    }

    // add next option here
}

/// @brief Adds/updates a specific IPv6 string vendor option.
///
/// @param vendor IPv6 vendor option set to update
/// @param opt_code DHCP standard numeric code of the option
/// @param opt_value String value of the option
void add6Option(OptionPtr& vendor, uint8_t opt_code, std::string& opt_value) {
    vendor->delOption(opt_code);
    OptionPtr option(new OptionString(Option::V6, opt_code, opt_value));
    vendor->addOption(option);
}


/// @brief Adds an entry to the end of the user check outcome file.
///
/// @todo This ought to be replaced with an abstract output similar to
/// UserDataSource to allow greater flexibility.
///
/// Each user entry is written in an ini-like format, with one name-value pair
/// per line as follows:
///
/// id_type=&lt;id type&gt;<br/>
/// client=&lt;id str&gt;<br/>
/// subnet=&lt;addr str&gt;<br/>
/// registered=&lt;is registered&gt;
///
/// where:
/// &lt;id type&gt; text label of the id type: "HW_ADDR" or "DUID"
/// &lt;id str&gt; user's id formatted as either isc::dhcp::Hwaddr.toText() or
/// isc::dhcp::DUID.toText()
/// &lt;addr str&gt; selected subnet formatted as isc::dhcp::Subnet4::toText() or
/// isc::dhcp::Subnet6::toText() as appropriate.
/// &lt;is registered&gt; "yes" or "no"
///
/// Sample IPv4 entry would like this:
///
/// @code
/// id_type=DUID
/// client=00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:04
/// subnet=2001:db8:2::/64
/// registered=yes
/// id_type=duid
/// @endcode
///
/// Sample IPv4 entry would like this:
///
/// @code
/// id_type=DUID
/// id_type=HW_ADDR
/// client=hwtype=1 00:0c:01:02:03:05
/// subnet=152.77.5.0/24
/// registered=no
/// @endcode
///
/// @param id_type_str text label identify the id type
/// @param id_val_str text representation of the user id
/// @param addr_str text representation  of the selected subnet
/// @param registered boolean indicating if the user is registered or not
void generate_output_record(const std::string& id_type_str,
                            const std::string& id_val_str,
                            const std::string& addr_str,
                            const bool& registered)
{
    user_chk_output << "id_type=" << id_type_str << std::endl
                    << "client=" << id_val_str << std::endl
                    << "addr=" << addr_str << std::endl
                    << "registered=" << (registered ? "yes" : "no")
                    << std::endl
                    << std::endl;   // extra line in between

    // @todo Flush is here to ensure output is immediate for demo purposes.
    // Performance would generally dictate not using it.
    flush(user_chk_output);
}

/// @brief Stringify the lease address or prefix IPv6 response packet
///
/// Converts the lease value, either an address or a prefix, into a string
/// suitable for the user check outcome output.  Note that this will use
/// the first address or prefix in the response for responses with more than
/// one value.
///
/// @param response IPv6 response packet from which to extract the lease value.
///
/// @return A string containing the lease value.
/// @throw isc::BadValue if the response contains neither an IA_NA nor IA_PD
/// option.
std::string getV6AddrStr(Pkt6Ptr response) {
    OptionPtr tmp = response->getOption(D6O_IA_NA);
    if (tmp) {
        return(getAddrStrIA_NA(tmp));
    }

    // IA_NA not there so try IA_PD
    tmp = response->getOption(D6O_IA_PD);
    if (!tmp) {
        isc_throw (isc::BadValue, "Response has neither IA_NA nor IA_PD");
    }

    return(getAddrStrIA_PD(tmp));
}

/// @brief Stringify the lease address in an D6O_IA_NA option set
///
/// Converts the IA_NA lease address into a string suitable for the user check
/// outcome output.
///
/// @param options pointer to the Option6IA instance from which to extract the
/// lease address.
///
/// @return A string containing the lease address.
///
/// @throw isc::BadValue if the lease address cannot be extracted from options.
std::string getAddrStrIA_NA(OptionPtr options) {
    boost::shared_ptr<Option6IA> ia =
        boost::dynamic_pointer_cast<Option6IA>(options);

    if (!ia) {
        isc_throw (isc::BadValue, "D6O_IA_NA option invalid");
    }

    // If status indicates a failure return a blank string.
    if (!checkIAStatus(ia)) {
        return (std::string(""));
    }

    options = ia->getOption(D6O_IAADDR);
    if (!options) {
        isc_throw (isc::BadValue, "D6O_IAADDR option missing");
    }

    boost::shared_ptr<Option6IAAddr> addr_option;
    addr_option  = boost::dynamic_pointer_cast<Option6IAAddr>(options);
    if (!addr_option) {
        isc_throw (isc::BadValue, "D6O_IAADDR Option6IAAddr missing");
    }

    isc::asiolink::IOAddress addr = addr_option->getAddress();
    return (addr.toText());
}

/// @brief Stringify the lease prefix in an D6O_IA_PD option set
///
/// Converts the IA_PD lease prefix into a string suitable for the user check
/// outcome output.
///
/// @param options pointer to the Option6IA instance from which to extract the
/// lease prefix.
///
/// @return A string containing lease prefix
///
/// @throw isc::BadValue if the prefix cannot be extracted from options.
std::string getAddrStrIA_PD(OptionPtr options) {
    boost::shared_ptr<Option6IA> ia =
        boost::dynamic_pointer_cast<Option6IA>(options);

    // Make sure we have an IA_PD option.
    if (!ia) {
        isc_throw (isc::BadValue, "D6O_IA_PD option invalid");
    }

    // Check the response for success status.  If it isn't a success response
    // there will not be a lease prefix value which is denoted by returning
    // an empty string.
    if (!checkIAStatus(ia)) {
        return (std::string(""));
    }

    // Get the prefix option the IA_PD option.
    options = ia->getOption(D6O_IAPREFIX);
    if (!options) {
        isc_throw(isc::BadValue, "D6O_IAPREFIX option is missing");
    }

    boost::shared_ptr<Option6IAPrefix> addr_option;
    addr_option = boost::dynamic_pointer_cast<Option6IAPrefix>(options);
    if (!addr_option) {
        isc_throw (isc::BadValue, "D6O_IA_PD addr option bad");
    }

    // Get the address and prefix length values.
    isc::asiolink::IOAddress addr = addr_option->getAddress();
    uint8_t prefix_len = addr_option->getLength();

    // Build the output string and return it.
    stringstream buf;
    buf << addr.toText() << "/" << static_cast<int>(prefix_len);
    return (buf.str());
}

/// @brief Tests given IA option set for successful status.
///
/// This function is used to determine if the given  Option6IA represents
/// a successful lease operation.  If it contains no status option or a status
/// option of 0 (which is defined to mean success), then the option represents
/// success and should contain a lease value (address or prefix).
///
/// @param ia pointer to the Option6IA to test
///
/// @return True if the option represents success, false otherwise.
bool checkIAStatus(boost::shared_ptr<Option6IA>& ia) {
    OptionCustomPtr status =
            boost::dynamic_pointer_cast
                <OptionCustom>(ia->getOption(D6O_STATUS_CODE));

   // If a non-zero status is present, bail.
   if (status) {
        int status_val = status->readInteger<uint16_t>(0);
        if (status_val != 0) {
            std::cout << "SKIPPING PACKET STATUS is not success:"
                      << status_val << std::endl;
            return (false);
        }
    }

    return (true);
}

/// @brief Fetches the default IPv4 user from the registry.
///
/// The default user may be used to provide default property values.
///
/// @return A pointer to the IPv4 user or null if not defined.
const UserPtr& getDefaultUser4() {
   return (user_registry->findUser(UserId(UserId::HW_ADDRESS,
                                          default_user4_id_str)));
}

/// @brief Fetches the default IPv6 user from the registry.
///
/// The default user may be used to provide default property values.
///
/// @return A pointer to the IPv6 user or null if not defined.
const UserPtr& getDefaultUser6() {
   return (user_registry->findUser(UserId(UserId::DUID,
                                          default_user6_id_str)));
}