pkt_send_co.cc 19.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15
/// @file pkt_send.cc Defines the pkt4_send and pkt6_send callout functions.
16
17
18

#include <asiolink/io_address.h>
#include <hooks/hooks.h>
19
#include <dhcp/dhcp4.h>
20
#include <dhcp/dhcp6.h>
21
#include <dhcp/option_string.h>
22
23
24
25
#include <dhcp/option_custom.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaprefix.h>
26
#include <dhcp/docsis3_option_defs.h>
27
#include <dhcp/pkt4.h>
28
29
30
31
32
#include <dhcp/pkt6.h>
#include <user_chk.h>

using namespace isc::dhcp;
using namespace isc::hooks;
33
using namespace user_chk;
34
35
using namespace std;

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 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();

53
54
55
56
57
58
59
// 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.
///
60
/// This function generates the user check outcome and modifies options
61
/// to the IPv4 response packet based on whether the user is registered or not.
62
///
63
/// It retrieves a pointer to the registered user from the callout context.
64
65
66
/// 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
67
/// based upon this user's properties.
68
69
///
/// A null value means the user is not registered and a unregistered user
70
71
/// outcome is recorded in the outcome output and the vendor properties
/// are altered based upon the default IPv4 user in the registry (if defined).
72
73
74
75
76
77
78
79
80
///
/// @param handle CalloutHandle which provides access to context.
///
/// @return 0 upon success, non-zero otherwise.
int pkt4_send(CalloutHandle& handle) {
    try {
        Pkt4Ptr response;
        handle.getArgument("response4", response);

81
        uint8_t packet_type = response->getType();
82
83
84
        if (packet_type == DHCPNAK) {
            std::cout << "DHCP UserCheckHook : pkt4_send"
                      << "skipping packet type: "
85
86
87
88
                      << static_cast<int>(packet_type) << std::endl;
            return (0);
        }

89
90
        // Get the user id saved from the query packet.
        HWAddrPtr hwaddr;
91
        handle.getContext(query_user_id_label, hwaddr);
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

        // 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);
109
            add4Options(response, registered_user);
110
        } else {
111
            // add default options based
112
            // then generate not registered output record
113
            std::cout << "DHCP UserCheckHook : pkt4_send no registered_user"
114
115
116
117
                      << std::endl;
            // Add the outcome entry to the output file.
            generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
                                   addr.toText(), false);
118
119

            add4Options(response, getDefaultUser4());
120
121
122
123
124
125
126
127
128
129
130
131
        }
    } 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.
///
132
/// This function generates the user check outcome and modifies options
133
/// to the IPv6 response packet based on whether the user is registered or not.
134
///
135
/// It retrieves a pointer to the registered user from the callout context.
136
137
138
/// 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
139
/// based upon this user's properties.
140
141
///
/// A null value means the user is not registered and a unregistered user
142
143
/// outcome is recorded in the outcome output and the vendor properties
/// are altered based upon the default IPv6 user in the registry (if defined).
144
145
146
147
148
149
150
151
/// @param handle CalloutHandle which provides access to context.
///
/// @return 0 upon success, non-zero otherwise.
int pkt6_send(CalloutHandle& handle) {
    try {
        Pkt6Ptr response;
        handle.getArgument("response6", response);

152
153
154
155
156
157
158
159
        // 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);
        }
160
161
162

        // Get the user id saved from the query packet.
        DuidPtr duid;
163
        handle.getContext(query_user_id_label, duid);
164
165
166
167
168
169
170
171
172
173
174
175

        // 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(),
176
                                   addr_str, true);
177
            add6Options(response, registered_user);
178
        } else {
179
            // add default options based
180
            // then generate not registered output record
181
            std::cout << "DHCP UserCheckHook : pkt6_send no registered_user"
182
183
184
                      << std::endl;
            // Add the outcome entry to the output file.
            generate_output_record(UserId::DUID_STR, duid->toText(),
185
                                   addr_str, false);
186
            add6Options(response, getDefaultUser6());
187
188
189
190
191
192
193
194
195
196
        }
    } catch (const std::exception& ex) {
        std::cout << "DHCP UserCheckHook : pkt6_send unexpected error: "
                  << ex.what() << std::endl;
        return (1);
    }

    return (0);
}

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
} // 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);
}


260
261
262
263
264
265
266
267
268
269
/// @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 reponse packet
270
/// @param user User from whom properties are sourced
271
272
273
274
275
void add6Options(Pkt6Ptr& response, const UserPtr& user) {
    if (!user) {
        return;
    }

276
    /// @todo: if packets have no vendor opt... do we need to add it
277
278
279
280
    /// if its not there?  If so how?
    OptionPtr vendor = response->getOption(D6O_VENDOR_OPTS);
    if (!vendor) {
        std::cout << "DHCP UserCheckHook : add6Options "
281
              << "response has no vendor option to update" << std::endl;
282
283
284
285
286
287
288
289
        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;
290
        add6Option(vendor, DOCSIS3_V6_CONFIG_FILE, opt_value);
291
292
293
294
295
296
297
298
    }

    // 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;

299
        add6Option(vendor, DOCSIS3_V6_TFTP_SERVERS, opt_value);
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
    }

    // 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);
}


317
318
/// @brief Adds an entry to the end of the user check outcome file.
///
319
/// @todo This ought to be replaced with an abstract output similar to
320
321
/// UserDataSource to allow greater flexibility.
///
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
/// Each user entry is written in an ini-like format, with one name-value pair
/// per line as follows:
///
/// id_type=<id type>
/// client=<id str>
/// subnet=<subnet str>
/// registered=<is registered>"
///
/// where:
/// <id type> text label of the id type: "HW_ADDR" or "DUID"
/// <id str> user's id formatted as either isc::dhcp::Hwaddr.toText() or
/// isc::dhcp::DUID.toText()
/// <subnet str> selected subnet formatted as isc::dhcp::Subnet4::toText() or
/// isc::dhcp::Subnet6::toText() as appropriate.
/// <is registered> "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 subnet_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")
371
372
                    << std::endl
                    << std::endl;   // extra line in between
373
374
375
376
377
378
379

    // @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
380
381
///
/// Converts the lease value, either an address or a prefix, into a string
382
383
384
/// 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.
385
386
387
388
389
390
///
/// @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.
391
std::string getV6AddrStr(Pkt6Ptr response) {
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
    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
407
408
409
410
411
412
413
414
415
416
///
/// 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.
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
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());
}

445
446
447
448
449
450
451
452
453
454
455
/// @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.
456
457
458
459
std::string getAddrStrIA_PD(OptionPtr options) {
    boost::shared_ptr<Option6IA> ia =
        boost::dynamic_pointer_cast<Option6IA>(options);

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

465
466
467
    // 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.
468
469
470
471
    if (!checkIAStatus(ia)) {
        return (std::string(""));
    }

472
    // Get the prefix option the IA_PD option.
473
474
475
476
477
478
479
480
481
482
483
    options = ia->getOption(D6O_IAPREFIX);
    if (!options) {
        isc_throw(isc::BadValue, "D60_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");
    }

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

488
    // Build the output string and return it.
489
490
491
492
493
494
    stringstream buf;
    buf << addr.toText() << "/" << static_cast<int>(prefix_len);
    return (buf.str());
}

/// @brief Tests given IA option set for successful status.
495
496
497
498
499
500
501
502
503
///
/// 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.
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
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);
}

522
523
524
525
526
/// @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.
527
const UserPtr& getDefaultUser4() {
528
529
   return (user_registry->findUser(UserId(UserId::HW_ADDRESS,
                                          default_user4_id_str)));
530
531
}

532
533
534
535
536
/// @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.
537
const UserPtr& getDefaultUser6() {
538
539
   return (user_registry->findUser(UserId(UserId::DUID,
                                          default_user6_id_str)));
540
}
541