dhcp4_srv.cc 99.9 KB
Newer Older
1
// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
2
3
4
5
6
7
8
9
10
11
12
13
14
//
// 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
#include <config.h>
16
#include <asiolink/io_address.h>
17
#include <dhcp/dhcp4.h>
18
19
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
20
#include <dhcp/iface_mgr.h>
21
#include <dhcp/option4_addrlst.h>
22
#include <dhcp/option_int.h>
23
#include <dhcp/option_int_array.h>
24
#include <dhcp/option_vendor.h>
25
#include <dhcp/option_string.h>
26
#include <dhcp/pkt4.h>
27
#include <dhcp/docsis3_option_defs.h>
28
29
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4_srv.h>
30
31
#include <dhcpsrv/addr_utilities.h>
#include <dhcpsrv/callout_handle_store.h>
32
#include <dhcpsrv/cfgmgr.h>
33
#include <dhcpsrv/cfg_subnets4.h>
34
35
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
36
#include <dhcpsrv/ncr_generator.h>
37
#include <dhcpsrv/subnet.h>
38
#include <dhcpsrv/subnet_selector.h>
39
#include <dhcpsrv/utils.h>
40
#include <dhcpsrv/utils.h>
41
42
#include <eval/evaluate.h>
#include <eval/eval_messages.h>
43
#include <hooks/callout_handle.h>
44
#include <hooks/hooks_log.h>
45
#include <hooks/hooks_manager.h>
46
#include <stats/stats_mgr.h>
47
#include <util/strutil.h>
48
#include <stats/stats_mgr.h>
49
50
#include <log/logger.h>
#include <cryptolink/cryptolink.h>
51
#include <cfgrpt/config_report.h>
52

53
54
55
56
57
58
59
60
#ifdef HAVE_MYSQL
#include <dhcpsrv/mysql_lease_mgr.h>
#endif
#ifdef HAVE_PGSQL
#include <dhcpsrv/pgsql_lease_mgr.h>
#endif
#include <dhcpsrv/memfile_lease_mgr.h>

61
#include <boost/asio.hpp>
62
#include <boost/bind.hpp>
63
#include <boost/foreach.hpp>
64
#include <boost/shared_ptr.hpp>
65
66
67

#include <iomanip>

68
69
using namespace isc;
using namespace isc::asiolink;
70
using namespace isc::cryptolink;
71
using namespace isc::dhcp;
72
using namespace isc::dhcp_ddns;
73
using namespace isc::hooks;
74
using namespace isc::log;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
75
using namespace isc::stats;
76
using namespace std;
77

78
/// Structure that holds registered hook indexes
79
80
struct Dhcp4Hooks {
    int hook_index_buffer4_receive_;///< index for "buffer4_receive" hook point
81
82
    int hook_index_pkt4_receive_;   ///< index for "pkt4_receive" hook point
    int hook_index_subnet4_select_; ///< index for "subnet4_select" hook point
83
    int hook_index_lease4_release_; ///< index for "lease4_release" hook point
84
    int hook_index_pkt4_send_;      ///< index for "pkt4_send" hook point
85
    int hook_index_buffer4_send_;   ///< index for "buffer4_send" hook point
86
    int hook_index_lease4_decline_; ///< index for "lease4_decline" hook point
87

88
89
90
    /// Constructor that registers hook points for DHCPv4 engine
    Dhcp4Hooks() {
        hook_index_buffer4_receive_= HooksManager::registerHook("buffer4_receive");
91
92
93
        hook_index_pkt4_receive_   = HooksManager::registerHook("pkt4_receive");
        hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
        hook_index_pkt4_send_      = HooksManager::registerHook("pkt4_send");
94
95
        hook_index_lease4_release_ = HooksManager::registerHook("lease4_release");
        hook_index_buffer4_send_   = HooksManager::registerHook("buffer4_send");
96
        hook_index_lease4_decline_ = HooksManager::registerHook("lease4_decline");
97
98
99
100
101
102
103
    }
};

// Declare a Hooks object. As this is outside any function or method, it
// will be instantiated (and the constructor run) when the module is loaded.
// As a result, the hook indexes will be defined before any method in this
// module is called.
104
Dhcp4Hooks Hooks;
105

106
107
108
namespace isc {
namespace dhcp {

109
Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
110
111
                               const Pkt4Ptr& query,
                               const Subnet4Ptr& subnet)
112
113
    : alloc_engine_(alloc_engine), query_(query), resp_(),
      context_(new AllocEngine::ClientContext4()) {
114

115
116
117
118
119
120
121
122
    if (!alloc_engine_) {
        isc_throw(BadValue, "alloc_engine value must not be NULL"
                  " when creating an instance of the Dhcpv4Exchange");
    }

    if (!query_) {
        isc_throw(BadValue, "query value must not be NULL when"
                  " creating an instance of the Dhcpv4Exchange");
123
124
    }

125
126
127
    // Create response message.
    initResponse();
    // Select subnet for the query message.
128
    context_->subnet_ = subnet;
129
    // Hardware address.
130
    context_->hwaddr_ = query->getHWAddr();
131
132
    // Pointer to client's query.
    context_->query_ = query;
133

134
    // Set client identifier if the match-client-id flag is enabled (default).
135
136
137
    // If the subnet wasn't found it doesn't matter because we will not be
    // able to allocate a lease anyway so this context will not be used.
    if (subnet) {
138
        if (subnet->getMatchClientId()) {
139
140
141
142
143
144
145
146
147
148
            OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
            if (opt_clientid) {
                context_->clientid_.reset(new ClientId(opt_clientid->getData()));
            }
        } else {
            /// @todo When merging with #3806 use different logger.
            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENTID_IGNORED_FOR_LEASES)
                .arg(query->getLabel())
                .arg(subnet->getID());
        }
149
150
    }
    // Check for static reservations.
151
    alloc_engine->findReservation(*context_);
152
153
};

154
void
155
Dhcpv4Exchange::initResponse() {
156
157
158
159
160
161
162
163
164
165
166
167
    uint8_t resp_type = 0;
    switch (getQuery()->getType()) {
    case DHCPDISCOVER:
        resp_type = DHCPOFFER;
        break;
    case DHCPREQUEST:
    case DHCPINFORM:
        resp_type = DHCPACK;
        break;
    default:
        ;
    }
168
    // Only create a response if one is required.
169
170
    if (resp_type > 0) {
        resp_.reset(new Pkt4(resp_type, getQuery()->getTransid()));
171
        copyDefaultFields();
Francis Dupont's avatar
Francis Dupont committed
172
        copyDefaultOptions();
173
174
175
176
177
178
179
180
    }
}

void
Dhcpv4Exchange::copyDefaultFields() {
    resp_->setIface(query_->getIface());
    resp_->setIndex(query_->getIndex());

181
182
    // explicitly set this to 0
    resp_->setSiaddr(IOAddress::IPV4_ZERO_ADDRESS());
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
    // ciaddr is always 0, except for the Renew/Rebind state when it may
    // be set to the ciaddr sent by the client.
    resp_->setCiaddr(IOAddress::IPV4_ZERO_ADDRESS());
    resp_->setHops(query_->getHops());

    // copy MAC address
    resp_->setHWAddr(query_->getHWAddr());

    // relay address
    resp_->setGiaddr(query_->getGiaddr());

    // If src/dest HW addresses are used by the packet filtering class
    // we need to copy them as well. There is a need to check that the
    // address being set is not-NULL because an attempt to set the NULL
    // HW would result in exception. If these values are not set, the
    // the default HW addresses (zeroed) should be generated by the
    // packet filtering class when creating Ethernet header for
    // outgoing packet.
    HWAddrPtr src_hw_addr = query_->getLocalHWAddr();
    if (src_hw_addr) {
        resp_->setLocalHWAddr(src_hw_addr);
    }
    HWAddrPtr dst_hw_addr = query_->getRemoteHWAddr();
    if (dst_hw_addr) {
        resp_->setRemoteHWAddr(dst_hw_addr);
    }
209
}
210

211
212
void
Dhcpv4Exchange::copyDefaultOptions() {
213
214
215
216
217
218
219
    // Let's copy client-id to response. See RFC6842.
    // It is possible to disable RFC6842 to keep backward compatibility
    bool echo = CfgMgr::instance().echoClientId();
    OptionPtr client_id = query_->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
    if (client_id && echo) {
        resp_->addOption(client_id);
    }
220
221
222
223
224

    // If this packet is relayed, we want to copy Relay Agent Info option
    OptionPtr rai = query_->getOption(DHO_DHCP_AGENT_OPTIONS);
    if (rai) {
        resp_->addOption(rai);
225
    }
226
227
228
229
230
231
232
233
234
235
236
237
238

    // RFC 3011 states about the Subnet Selection Option

    // "Servers configured to support this option MUST return an
    //  identical copy of the option to any client that sends it,
    //  regardless of whether or not the client requests the option in
    //  a parameter request list. Clients using this option MUST
    //  discard DHCPOFFER or DHCPACK packets that do not contain this
    //  option."
    OptionPtr subnet_sel = query_->getOption(DHO_SUBNET_SELECTION);
    if (subnet_sel) {
        resp_->addOption(subnet_sel);
    }
239
240
}

241
242
const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");

243
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,
244
                     const bool direct_response_desired)
245
246
247
    : shutdown_(true), alloc_engine_(), port_(port),
      use_bcast_(use_bcast), hook_index_pkt4_receive_(-1),
      hook_index_subnet4_select_(-1), hook_index_pkt4_send_(-1) {
248

249
    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
250
    try {
251
252
253
        // Port 0 is used for testing purposes where we don't open broadcast
        // capable sockets. So, set the packet filter handling direct traffic
        // only if we are in non-test mode.
254
        if (port) {
255
256
257
258
259
260
261
262
            // First call to instance() will create IfaceMgr (it's a singleton)
            // it may throw something if things go wrong.
            // The 'true' value of the call to setMatchingPacketFilter imposes
            // that IfaceMgr will try to use the mechanism to respond directly
            // to the client which doesn't have address assigned. This capability
            // may be lacking on some OSes, so there is no guarantee that server
            // will be able to respond directly.
            IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
263
        }
264

265
266
267
268
        // Instantiate allocation engine. The number of allocation attempts equal
        // to zero indicates that the allocation engine will use the number of
        // attempts depending on the pool size.
        alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 0,
269
                                            false /* false = IPv4 */));
270

271
272
273
274
275
276
277
        // Register hook points
        hook_index_pkt4_receive_   = Hooks.hook_index_pkt4_receive_;
        hook_index_subnet4_select_ = Hooks.hook_index_subnet4_select_;
        hook_index_pkt4_send_      = Hooks.hook_index_pkt4_send_;

        /// @todo call loadLibraries() when handling configuration changes

278
    } catch (const std::exception &e) {
279
        LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
280
281
282
        shutdown_ = true;
        return;
    }
283

Tomek Mrugalski's avatar
Tomek Mrugalski committed
284
    shutdown_ = false;
285
286
287
}

Dhcpv4Srv::~Dhcpv4Srv() {
288
289
290
291
292
293
    try {
        stopD2();
    } catch(const std::exception& ex) {
        // Highly unlikely, but lets Report it but go on
        LOG_ERROR(dhcp4_logger, DHCP4_SRV_D2STOP_ERROR).arg(ex.what());
    }
294

Tomek Mrugalski's avatar
Tomek Mrugalski committed
295
    IfaceMgr::instance().closeSockets();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
296

297
298
    // The lease manager was instantiated during DHCPv4Srv configuration,
    // so we should clean up after ourselves.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
299
    LeaseMgrFactory::destroy();
300
301
}

302
303
void
Dhcpv4Srv::shutdown() {
304
    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_SHUTDOWN_REQUEST);
305
306
307
    shutdown_ = true;
}

308
isc::dhcp::Subnet4Ptr
309
Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query) const {
310
311
312
313
314
315
316
317
318
319
320

    Subnet4Ptr subnet;

    SubnetSelector selector;
    selector.ciaddr_ = query->getCiaddr();
    selector.giaddr_ = query->getGiaddr();
    selector.local_address_ = query->getLocalAddr();
    selector.remote_address_ = query->getRemoteAddr();
    selector.client_classes_ = query->classes_;
    selector.iface_name_ = query->getIface();

321
322
323
324
325
326
    // If the link-selection sub-option is present, extract its value.
    // "The link-selection sub-option is used by any DHCP relay agent
    // that desires to specify a subnet/link for a DHCP client request
    // that it is relaying but needs the subnet/link specification to
    // be different from the IP address the DHCP server should use
    // when communicating with the relay agent." (RFC 3257)
327
    //
328
    // Try first Relay Agent Link Selection sub-option
329
330
    OptionPtr rai = query->getOption(DHO_DHCP_AGENT_OPTIONS);
    if (rai) {
331
332
333
334
335
336
337
338
        OptionCustomPtr rai_custom =
            boost::dynamic_pointer_cast<OptionCustom>(rai);
        if (rai_custom) {
            OptionPtr link_select =
                rai_custom->getOption(RAI_OPTION_LINK_SELECTION);
            if (link_select) {
                OptionBuffer link_select_buf = link_select->getData();
                if (link_select_buf.size() == sizeof(uint32_t)) {
339
                    selector.option_select_ =
340
                        IOAddress::fromBytes(AF_INET, &link_select_buf[0]);
341
342
343
                }
            }
        }
344
345
346
347
348
349
350
351
352
353
    } else {
        // Or Subnet Selection option
        OptionPtr sbnsel = query->getOption(DHO_SUBNET_SELECTION);
        if (sbnsel) {
            OptionCustomPtr oc =
                boost::dynamic_pointer_cast<OptionCustom>(sbnsel);
            if (oc) {
                selector.option_select_ = oc->readAddress();
            }
        }
354
355
    }

356
357
358
359
    CfgMgr& cfgmgr = CfgMgr::instance();
    subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);

    // Let's execute all callouts registered for subnet4_select
360
    if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
        CalloutHandlePtr callout_handle = getCalloutHandle(query);

        // We're reusing callout_handle from previous calls
        callout_handle->deleteAllArguments();

        // Set new arguments
        callout_handle->setArgument("query4", query);
        callout_handle->setArgument("subnet4", subnet);
        callout_handle->setArgument("subnet4collection",
                                    cfgmgr.getCurrentCfg()->
                                    getCfgSubnets4()->getAll());

        // Call user (and server-side) callouts
        HooksManager::callCallouts(hook_index_subnet4_select_,
                                   *callout_handle);

        // Callouts decided to skip this step. This means that no subnet
        // will be selected. Packet processing will continue, but it will
        // be severely limited (i.e. only global options will be assigned)
380
        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
381
            LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
382
383
                      DHCP4_HOOK_SUBNET4_SELECT_SKIP)
                .arg(query->getLabel());
384
385
386
            return (Subnet4Ptr());
        }

387
388
        /// @todo: Add support for DROP status

389
390
391
392
        // Use whatever subnet was specified by the callout
        callout_handle->getArgument("subnet4", subnet);
    }

393
394
    if (subnet) {
        // Log at higher debug level that subnet has been found.
395
        LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_SELECTED)
396
397
398
399
            .arg(query->getLabel())
            .arg(subnet->getID());
        // Log detailed information about the selected subnet at the
        // lower debug level.
400
        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_DATA)
401
402
403
404
            .arg(query->getLabel())
            .arg(subnet->toText());

    } else {
405
        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL,
406
407
408
409
                  DHCP4_SUBNET_SELECTION_FAILED)
            .arg(query->getLabel());
    }

410
    return (subnet);
411
412
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
413
414
Pkt4Ptr
Dhcpv4Srv::receivePacket(int timeout) {
415
416
417
    return (IfaceMgr::instance().receive4(timeout));
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
418
419
void
Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
420
421
422
    IfaceMgr::instance().send(packet);
}

423
424
bool
Dhcpv4Srv::run() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
425
    while (!shutdown_) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
426
        // client's message and server's response
427
        Pkt4Ptr query;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
428
        Pkt4Ptr rsp;
429

430
        try {
431
            uint32_t timeout = 1000;
432
            LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_BUFFER_WAIT).arg(timeout);
433
            query = receivePacket(timeout);
434

435
436
437
438
439
440
441
            // Log if packet has arrived. We can't log the detailed information
            // about the DHCP message because it hasn't been unpacked/parsed
            // yet, and it can't be parsed at this point because hooks will
            // have to process it first. The only information available at this
            // point are: the interface, source address and destination addresses
            // and ports.
            if (query) {
442
                LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_BUFFER_RECEIVED)
443
444
445
446
447
448
449
                    .arg(query->getRemoteAddr().toText())
                    .arg(query->getRemotePort())
                    .arg(query->getLocalAddr().toText())
                    .arg(query->getLocalPort())
                    .arg(query->getIface());

            } else {
450
                LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_BUFFER_WAIT_INTERRUPTED)
451
452
453
                    .arg(timeout);
            }

454
455
456
        } catch (const SignalInterruptOnSelect) {
            // Packet reception interrupted because a signal has been received.
            // This is not an error because we might have received a SIGTERM,
457
458
            // SIGINT, SIGHUP or SIGCHILD which are handled by the server. For
            // signals that are not handled by the server we rely on the default
459
            // behavior of the system.
460
            LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_BUFFER_WAIT_SIGNAL)
461
                .arg(signal_set_->getNext());
462
        } catch (const std::exception& e) {
463
            // Log all other errors.
464
            LOG_ERROR(packet4_logger, DHCP4_BUFFER_RECEIVE_FAIL).arg(e.what());
465
466
        }

467
468
469
        // Handle next signal received by the process. It must be called after
        // an attempt to receive a packet to properly handle server shut down.
        // The SIGTERM or SIGINT will be received prior to, or during execution
Francis Dupont's avatar
Francis Dupont committed
470
471
472
473
474
475
476
        // of select() (select is invoked by receivePacket()). When that
        // happens, select will be interrupted. The signal handler will be
        // invoked immediately after select(). The handler will set the
        // shutdown flag and cause the process to terminate before the next
        // select() function is called. If the function was called before
        // receivePacket the process could wait up to the duration of timeout
        // of select() to terminate.
477
478
479
        try {
            handleSignal();
        } catch (const std::exception& e) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
480
481
482
            // Standard exception occurred. Let's be on the safe side to
            // catch std::exception.
            LOG_ERROR(dhcp4_logger, DHCP4_HANDLE_SIGNAL_EXCEPTION)
483
484
                .arg(e.what());
        }
485

486
        // Timeout may be reached or signal received, which breaks select()
487
488
        // with no reception occurred. No need to log anything here because
        // we have logged right after the call to receivePacket().
489
490
491
492
        if (!query) {
            continue;
        }

493
494
495
496
        // Log reception of the packet. We need to increase it early, as any
        // failures in unpacking will cause the packet to be dropped. We
        // will increase type specific packets further down the road.
        // See processStatsReceived().
Tomek Mrugalski's avatar
Tomek Mrugalski committed
497
        isc::stats::StatsMgr::instance().addValue("pkt4-received",
498
                                                  static_cast<int64_t>(1));
499

500
501
502
503
504
505
506
507
508
        // In order to parse the DHCP options, the server needs to use some
        // configuration information such as: existing option spaces, option
        // definitions etc. This is the kind of information which is not
        // available in the libdhcp, so we need to supply our own implementation
        // of the option parsing function here, which would rely on the
        // configuration data.
        query->setCallback(boost::bind(&Dhcpv4Srv::unpackOptions, this,
                                       _1, _2, _3));

509
510
511
        bool skip_unpack = false;

        // The packet has just been received so contains the uninterpreted wire
512
        // data; execute callouts registered for buffer4_receive.
513
        if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_receive_)) {
514
515
516
517
518
519
520
521
522
            CalloutHandlePtr callout_handle = getCalloutHandle(query);

            // Delete previously set arguments
            callout_handle->deleteAllArguments();

            // Pass incoming packet as argument
            callout_handle->setArgument("query4", query);

            // Call callouts
Tomek Mrugalski's avatar
Tomek Mrugalski committed
523
524
            HooksManager::callCallouts(Hooks.hook_index_buffer4_receive_,
                                       *callout_handle);
525
526
527
528
529

            // Callouts decided to skip the next processing step. The next
            // processing step would to parse the packet, so skip at this
            // stage means that callouts did the parsing already, so server
            // should skip parsing.
530
            if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
531
                LOG_DEBUG(hooks_logger, DBG_DHCP4_DETAIL, DHCP4_HOOK_BUFFER_RCVD_SKIP)
532
533
534
                    .arg(query->getRemoteAddr().toText())
                    .arg(query->getLocalAddr().toText())
                    .arg(query->getIface());
535
536
537
538
                skip_unpack = true;
            }

            callout_handle->getArgument("query4", query);
539
540

            /// @todo: add support for DROP status
541
542
543
544
545
        }

        // Unpack the packet information unless the buffer4_receive callouts
        // indicated they did it
        if (!skip_unpack) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
546
            try {
547
                LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, DHCP4_BUFFER_UNPACK)
548
549
550
                    .arg(query->getRemoteAddr().toText())
                    .arg(query->getLocalAddr().toText())
                    .arg(query->getIface());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
551
552
                query->unpack();
            } catch (const std::exception& e) {
553
                // Failed to parse the packet.
554
                LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
555
                          DHCP4_PACKET_DROP_0001)
556
557
558
                    .arg(query->getRemoteAddr().toText())
                    .arg(query->getLocalAddr().toText())
                    .arg(query->getIface())
559
                    .arg(e.what());
560
561

                // Increase the statistics of parse failues and dropped packets.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
562
                isc::stats::StatsMgr::instance().addValue("pkt4-parse-failed",
563
                                                          static_cast<int64_t>(1));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
564
                isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
565
                                                          static_cast<int64_t>(1));
566
567
                continue;
            }
568
        }
569

570
571
572
        // Update statistics accordingly for received packet.
        processStatsReceived(query);

Tomek Mrugalski's avatar
Tomek Mrugalski committed
573
574
575
        // Assign this packet to one or more classes if needed. We need to do
        // this before calling accept(), because getSubnet4() may need client
        // class information.
576
577
        classifyPacket(query);

578
579
580
        // Check whether the message should be further processed or discarded.
        // There is no need to log anything here. This function logs by itself.
        if (!accept(query)) {
581
            // Increase the statistic of dropped packets.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
582
            isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
583
                                                      static_cast<int64_t>(1));
584
            continue;
585
        }
586

587
        // We have sanity checked (in accept() that the Message Type option
588
589
        // exists, so we can safely get it here.
        int type = query->getType();
590
        LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_PACKET_RECEIVED)
591
            .arg(query->getLabel())
592
            .arg(query->getName())
593
            .arg(type)
594
595
            .arg(query->getRemoteAddr())
            .arg(query->getLocalAddr())
596
            .arg(query->getIface());
597
        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
598
            .arg(query->getLabel())
599
600
            .arg(query->toText());

Tomek Mrugalski's avatar
Tomek Mrugalski committed
601
        // Let's execute all callouts registered for pkt4_receive
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
        if (HooksManager::calloutsPresent(hook_index_pkt4_receive_)) {
            CalloutHandlePtr callout_handle = getCalloutHandle(query);

            // Delete previously set arguments
            callout_handle->deleteAllArguments();

            // Pass incoming packet as argument
            callout_handle->setArgument("query4", query);

            // Call callouts
            HooksManager::callCallouts(hook_index_pkt4_receive_,
                                       *callout_handle);

            // Callouts decided to skip the next processing step. The next
            // processing step would to process the packet, so skip at this
            // stage means drop.
618
            if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
619
                LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_RCVD_SKIP)
620
                    .arg(query->getLabel());
621
622
                continue;
            }
623

624
625
            /// @todo: Add support for DROP status

626
627
            callout_handle->getArgument("query4", query);
        }
628

629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
        try {
            switch (query->getType()) {
            case DHCPDISCOVER:
                rsp = processDiscover(query);
                break;

            case DHCPREQUEST:
                // Note that REQUEST is used for many things in DHCPv4: for
                // requesting new leases, renewing existing ones and even
                // for rebinding.
                rsp = processRequest(query);
                break;

            case DHCPRELEASE:
                processRelease(query);
                break;

            case DHCPDECLINE:
                processDecline(query);
                break;

            case DHCPINFORM:
651
                rsp = processInform(query);
652
653
654
655
656
657
658
                break;

            default:
                // Only action is to output a message if debug is enabled,
                // and that is covered by the debug statement before the
                // "switch" statement.
                ;
659
            }
660
        } catch (const std::exception& e) {
661

662
663
664
665
            // Catch-all exception (we used to call only isc::Exception, but
            // std::exception could potentially be raised and if we don't catch
            // it here, it would be caught in main() and the process would
            // terminate).  Just log the problem and ignore the packet.
666
667
668
            // (The problem is logged as a debug message because debug is
            // disabled by default - it prevents a DDOS attack based on the
            // sending of problem packets.)
669
            LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_BASIC,
670
671
672
                      DHCP4_PACKET_DROP_0007)
                .arg(query->getLabel())
                .arg(e.what());
673
674

            // Increase the statistic of dropped packets.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
675
            isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
676
                                                      static_cast<int64_t>(1));
677
        }
678

679
680
681
        if (!rsp) {
            continue;
        }
682

683

684
685
        // Specifies if server should do the packing
        bool skip_pack = false;
686

Tomek Mrugalski's avatar
Tomek Mrugalski committed
687
        // Execute all callouts registered for pkt4_send
688
689
        if (HooksManager::calloutsPresent(hook_index_pkt4_send_)) {
            CalloutHandlePtr callout_handle = getCalloutHandle(query);
690

691
692
            // Delete all previous arguments
            callout_handle->deleteAllArguments();
693

694
            // Clear skip flag if it was set in previous callouts
695
            callout_handle->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
696

697
698
            // Set our response
            callout_handle->setArgument("response4", rsp);
699

700
701
702
            // Call all installed callouts
            HooksManager::callCallouts(hook_index_pkt4_send_,
                                       *callout_handle);
703

704
705
706
            // Callouts decided to skip the next processing step. The next
            // processing step would to send the packet, so skip at this
            // stage means "drop response".
707
            if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
708
                LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_SEND_SKIP)
709
                    .arg(query->getLabel());
710
711
                skip_pack = true;
            }
712
713

            /// @todo: Add support for DROP status
714
715
716
717
        }

        if (!skip_pack) {
            try {
718
                LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_PACK)
719
                    .arg(rsp->getLabel());
720
721
                rsp->pack();
            } catch (const std::exception& e) {
722
                LOG_ERROR(options4_logger, DHCP4_PACKET_PACK_FAIL)
723
                    .arg(rsp->getLabel())
724
725
726
727
728
729
730
731
                    .arg(e.what());
            }
        }

        try {
            // Now all fields and options are constructed into output wire buffer.
            // Option objects modification does not make sense anymore. Hooks
            // can only manipulate wire buffer at this stage.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
732
            // Let's execute all callouts registered for buffer4_send
733
            if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_send_)) {
734
                CalloutHandlePtr callout_handle = getCalloutHandle(query);
735

736
737
                // Delete previously set arguments
                callout_handle->deleteAllArguments();
738

739
740
                // Pass incoming packet as argument
                callout_handle->setArgument("response4", rsp);
741

742
                // Call callouts
Tomek Mrugalski's avatar
Tomek Mrugalski committed
743
744
                HooksManager::callCallouts(Hooks.hook_index_buffer4_send_,
                                           *callout_handle);
745

746
747
748
                // Callouts decided to skip the next processing step. The next
                // processing step would to parse the packet, so skip at this
                // stage means drop.
749
                if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
750
                    LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
751
752
                              DHCP4_HOOK_BUFFER_SEND_SKIP)
                        .arg(rsp->getLabel());
753
                    continue;
754
                }
755

756
757
                /// @todo: Add support for DROP status.

758
                callout_handle->getArgument("response4", rsp);
759
            }
760

761
            LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_SEND)
762
                .arg(rsp->getLabel())
763
                .arg(rsp->getName())
764
765
766
767
768
769
                .arg(static_cast<int>(rsp->getType()))
                .arg(rsp->getLocalAddr())
                .arg(rsp->getLocalPort())
                .arg(rsp->getRemoteAddr())
                .arg(rsp->getRemotePort())
                .arg(rsp->getIface());
770

771
            LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA,
772
                      DHCP4_RESPONSE_DATA)
773
774
775
776
                .arg(rsp->getLabel())
                .arg(rsp->getName())
                .arg(static_cast<int>(rsp->getType()))
                .arg(rsp->toText());
777
            sendPacket(rsp);
778
779
780
781

            // Update statistics accordingly for sent packet.
            processStatsSent(rsp);

782
        } catch (const std::exception& e) {
783
            LOG_ERROR(packet4_logger, DHCP4_PACKET_SEND_FAIL)
784
                .arg(rsp->getLabel())
785
                .arg(e.what());
786
        }
787
788
789
790
791
    }

    return (true);
}

792
string
793
Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
    if (!srvid) {
        isc_throw(BadValue, "NULL pointer passed to srvidToString()");
    }
    boost::shared_ptr<Option4AddrLst> generated =
        boost::dynamic_pointer_cast<Option4AddrLst>(srvid);
    if (!srvid) {
        isc_throw(BadValue, "Pointer to invalid option passed to srvidToString()");
    }

    Option4AddrLst::AddressContainer addrs = generated->getAddresses();
    if (addrs.size() != 1) {
        isc_throw(BadValue, "Malformed option passed to srvidToString(). "
                  << "Expected to contain a single IPv4 address.");
    }

    return (addrs[0].toText());
810
811
}

812
void
813
Dhcpv4Srv::appendServerID(Dhcpv4Exchange& ex) {
814
815
816
    // The source address for the outbound message should have been set already.
    // This is the address that to the best of the server's knowledge will be
    // available from the client.
817
818
    /// @todo: perhaps we should consider some more sophisticated server id
    /// generation, but for the current use cases, it should be ok.
819
820
821
    OptionPtr opt_srvid(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
                                           ex.getResponse()->getLocalAddr()));
    ex.getResponse()->addOption(opt_srvid);
822
823
}

824
void
825
Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
826
827
    // Get the subnet relevant for the client. We will need it
    // to get the options associated with it.
828
    Subnet4Ptr subnet = ex.getContext()->subnet_;
829
830
831
832
833
834
835
    // If we can't find the subnet for the client there is no way
    // to get the options to be sent to a client. We don't log an
    // error because it will be logged by the assignLease method
    // anyway.
    if (!subnet) {
        return;
    }
836

837
838
    Pkt4Ptr query = ex.getQuery();

839
840
841
    // try to get the 'Parameter Request List' option which holds the
    // codes of requested options.
    OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
842
        OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
843
844
845
846
847
    // If there is no PRL option in the message from the client then
    // there is nothing to do.
    if (!option_prl) {
        return;
    }
848

849
850
    Pkt4Ptr resp = ex.getResponse();

851
852
853
854
855
856
    // Get the codes of requested options.
    const std::vector<uint8_t>& requested_opts = option_prl->getValues();
    // For each requested option code get the instance of the option
    // to be returned to the client.
    for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
         opt != requested_opts.end(); ++opt) {
857
        if (!resp->getOption(*opt)) {
858
            OptionDescriptor desc = subnet->getCfgOption()->get("dhcp4", *opt);
859
            if (desc.option_) {
860
                resp->addOption(desc.option_);
861
            }
862
863
        }
    }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
864
}
865

866
void
867
Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
868
    // Get the configured subnet suitable for the incoming packet.
869
    Subnet4Ptr subnet = ex.getContext()->subnet_;
870
871
872
873
874
875
876
877
878
879
    // Leave if there is no subnet matching the incoming packet.
    // There is no need to log the error message here because
    // it will be logged in the assignLease() when it fails to
    // pick the suitable subnet. We don't want to duplicate
    // error messages in such case.
    if (!subnet) {
        return;
    }

    // Try to get the vendor option
880
881
    boost::shared_ptr<OptionVendor> vendor_req = boost::dynamic_pointer_cast<
        OptionVendor>(ex.getQuery()->getOption(DHO_VIVSO_SUBOPTIONS));
882
883
884
885
886
887
888
    if (!vendor_req) {
        return;
    }

    uint32_t vendor_id = vendor_req->getVendorId();

    // Let's try to get ORO within that vendor-option
889
890
    /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other
    /// vendors may have different policies.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
891
892
    OptionUint8ArrayPtr oro =
        boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO));
893
894
895
896
897
898
899
900
901
902

    // Option ORO not found. Don't do anything then.
    if (!oro) {
        return;
    }

    boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V4, vendor_id));

    // Get the list of options that client requested.
    bool added = false;
903
    const std::vector<uint8_t>& requested_opts = oro->getValues();
904

Tomek Mrugalski's avatar
Tomek Mrugalski committed
905
    for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
906
         code != requested_opts.end(); ++code) {
907
        if  (!vendor_rsp->getOption(*code)) {
908
909
            OptionDescriptor desc = subnet->getCfgOption()->get(vendor_id,
                                                                *code);
910
911
            if (desc.option_) {
                vendor_rsp->addOption(desc.option_);
912
913
                added = true;
            }
914
915
        }

916
        if (added) {
917
            ex.getResponse()->addOption(vendor_rsp);
918
919
        }
    }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
920
}
921

922

923
void
924
Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
925
926
927
928
929
930
    // Identify options that we always want to send to the
    // client (if they are configured).
    static const uint16_t required_options[] = {
        DHO_ROUTERS,
        DHO_DOMAIN_NAME_SERVERS,
        DHO_DOMAIN_NAME };
931

932
933
934
935
    static size_t required_options_size =
        sizeof(required_options) / sizeof(required_options[0]);

    // Get the subnet.
936
    Subnet4Ptr subnet = ex.getContext()->subnet_;
937
938
939
940
    if (!subnet) {
        return;
    }

941
942
    Pkt4Ptr resp = ex.getResponse();

943
944
945
    // Try to find all 'required' options in the outgoing
    // message. Those that are not present will be added.
    for (int i = 0; i < required_options_size; ++i) {
946
        OptionPtr opt = resp->getOption(required_options[i]);
947
948
        if (!opt) {
            // Check whether option has been configured.
949
950
            OptionDescriptor desc = subnet->getCfgOption()->
                get("dhcp4", required_options[i]);
951
            if (desc.option_) {
952
                resp->addOption(desc.option_);
953
954
955
            }
        }
    }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
956
}
957

958
void
959
Dhcpv4Srv::processClientName(Dhcpv4Exchange& ex) {
960
961
962
    // It is possible that client has sent both Client FQDN and Hostname
    // option. In such case, server should prefer Client FQDN option and
    // ignore the Hostname option.
963
    try {
964
        Pkt4Ptr resp = ex.getResponse();
965
        Option4ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>
966
            (ex.getQuery()->getOption(DHO_FQDN));
967
        if (fqdn) {
968
            LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_FQDN_PROCESS)
969
                .arg(ex.getQuery()->getLabel());
970
            processClientFqdnOption(ex);
971
972

        } else {
973
            OptionStringPtr hostname = boost::dynamic_pointer_cast<OptionString>
974
                (ex.getQuery()->getOption(DHO_HOST_NAME));
975
            if (hostname) {
976
                LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_HOSTNAME_PROCESS)
977
                    .arg(ex.getQuery()->getLabel());
978
                processHostnameOption(ex);
979
980
            }
        }
981
    } catch (const Exception& e) {