dhcp4_srv.cc 96.5 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
#include <hooks/callout_handle.h>
42
#include <hooks/hooks_log.h>
43
#include <hooks/hooks_manager.h>
44
#include <stats/stats_mgr.h>
45
#include <util/strutil.h>
46
#include <stats/stats_mgr.h>
47 48
#include <log/logger.h>
#include <cryptolink/cryptolink.h>
49
#include <cfgrpt/config_report.h>
50

51 52 53 54 55 56 57 58
#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>

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

#include <iomanip>

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

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

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

// 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.
102
Dhcp4Hooks Hooks;
103

104 105 106
namespace isc {
namespace dhcp {

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

113 114 115 116 117 118 119 120
    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");
121 122
    }

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

132
    // Set client identifier if the match-client-id flag is enabled (default).
133 134 135
    // 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) {
136
        if (subnet->getMatchClientId()) {
137 138 139 140 141 142 143 144 145 146
            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());
        }
147 148
    }
    // Check for static reservations.
149
    alloc_engine->findReservation(*context_);
150 151
};

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

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

179 180
    // explicitly set this to 0
    resp_->setSiaddr(IOAddress::IPV4_ZERO_ADDRESS());
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
    // 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);
    }
207
}
208

209 210
void
Dhcpv4Exchange::copyDefaultOptions() {
211 212 213 214 215 216 217
    // 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);
    }
218 219 220 221 222

    // 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);
223
    }
224 225 226 227 228 229 230 231 232 233 234 235 236

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

239 240
const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");

241
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,
242
                     const bool direct_response_desired)
243 244 245
    : 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) {
246

247
    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
248
    try {
249 250 251
        // 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.
252
        if (port) {
253 254 255 256 257 258 259 260
            // 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);
261
        }
262

263 264 265 266
        // 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,
267
                                            false /* false = IPv4 */));
268

269 270 271 272 273 274 275
        // 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

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
282
    shutdown_ = false;
283 284 285
}

Dhcpv4Srv::~Dhcpv4Srv() {
286 287 288 289 290 291
    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());
    }
292

Tomek Mrugalski's avatar
Tomek Mrugalski committed
293
    IfaceMgr::instance().closeSockets();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
294

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

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

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

    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();

319 320 321 322 323 324
    // 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)
325
    //
326
    // Try first Relay Agent Link Selection sub-option
327 328
    OptionPtr rai = query->getOption(DHO_DHCP_AGENT_OPTIONS);
    if (rai) {
329 330 331 332 333 334 335 336
        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)) {
337
                    selector.option_select_ =
338
                        IOAddress::fromBytes(AF_INET, &link_select_buf[0]);
339 340 341
                }
            }
        }
342 343 344 345 346 347 348 349 350 351
    } 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();
            }
        }
352 353
    }

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

    // Let's execute all callouts registered for subnet4_select
358
    if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
        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)
378
        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
379
            LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
380 381
                      DHCP4_HOOK_SUBNET4_SELECT_SKIP)
                .arg(query->getLabel());
382 383 384
            return (Subnet4Ptr());
        }

385 386
        /// @todo: Add support for DROP status

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

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

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

408
    return (subnet);
409 410
}

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

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

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

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

433 434 435 436 437 438 439
            // 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) {
440
                LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_BUFFER_RECEIVED)
441 442 443 444 445 446 447
                    .arg(query->getRemoteAddr().toText())
                    .arg(query->getRemotePort())
                    .arg(query->getLocalAddr().toText())
                    .arg(query->getLocalPort())
                    .arg(query->getIface());

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

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

465 466 467
        // 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
468 469 470 471 472 473 474
        // 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.
475 476 477
        try {
            handleSignal();
        } catch (const std::exception& e) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
478 479 480
            // Standard exception occurred. Let's be on the safe side to
            // catch std::exception.
            LOG_ERROR(dhcp4_logger, DHCP4_HANDLE_SIGNAL_EXCEPTION)
481 482
                .arg(e.what());
        }
483

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

491 492 493 494
        // 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
495
        isc::stats::StatsMgr::instance().addValue("pkt4-received",
496
                                                  static_cast<int64_t>(1));
497

498 499 500 501 502 503 504 505 506
        // 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));

507 508 509
        bool skip_unpack = false;

        // The packet has just been received so contains the uninterpreted wire
510
        // data; execute callouts registered for buffer4_receive.
511
        if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_receive_)) {
512 513 514 515 516 517 518 519 520
            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
521 522
            HooksManager::callCallouts(Hooks.hook_index_buffer4_receive_,
                                       *callout_handle);
523 524 525 526 527

            // 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.
528
            if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
529
                LOG_DEBUG(hooks_logger, DBG_DHCP4_DETAIL, DHCP4_HOOK_BUFFER_RCVD_SKIP)
530 531 532
                    .arg(query->getRemoteAddr().toText())
                    .arg(query->getLocalAddr().toText())
                    .arg(query->getIface());
533 534 535 536
                skip_unpack = true;
            }

            callout_handle->getArgument("query4", query);
537 538

            /// @todo: add support for DROP status
539 540 541 542 543
        }

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

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

568 569 570
        // Update statistics accordingly for received packet.
        processStatsReceived(query);

Tomek Mrugalski's avatar
Tomek Mrugalski committed
571 572 573
        // 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.
574 575
        classifyPacket(query);

576 577 578
        // 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)) {
579
            // Increase the statistic of dropped packets.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
580
            isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
581
                                                      static_cast<int64_t>(1));
582
            continue;
583
        }
584

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
599
        // Let's execute all callouts registered for pkt4_receive
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
        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.
616
            if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
617
                LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_RCVD_SKIP)
618
                    .arg(query->getLabel());
619 620
                continue;
            }
621

622 623
            /// @todo: Add support for DROP status

624 625
            callout_handle->getArgument("query4", query);
        }
626

627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
        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:
649
                rsp = processInform(query);
650 651 652 653 654 655 656
                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.
                ;
657
            }
658
        } catch (const std::exception& e) {
659

660 661 662 663
            // 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.
664 665 666
            // (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.)
667
            LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_BASIC,
668 669 670
                      DHCP4_PACKET_DROP_0007)
                .arg(query->getLabel())
                .arg(e.what());
671 672

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

677 678 679
        if (!rsp) {
            continue;
        }
680

681

682 683
        // Specifies if server should do the packing
        bool skip_pack = false;
684

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

689 690
            // Delete all previous arguments
            callout_handle->deleteAllArguments();
691

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

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

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

702 703 704
            // 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".
705
            if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
706
                LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_SEND_SKIP)
707
                    .arg(query->getLabel());
708 709
                skip_pack = true;
            }
710 711

            /// @todo: Add support for DROP status
712 713 714 715
        }

        if (!skip_pack) {
            try {
716
                LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_PACK)
717
                    .arg(rsp->getLabel());
718 719
                rsp->pack();
            } catch (const std::exception& e) {
720
                LOG_ERROR(options4_logger, DHCP4_PACKET_PACK_FAIL)
721
                    .arg(rsp->getLabel())
722 723 724 725 726 727 728 729
                    .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
730
            // Let's execute all callouts registered for buffer4_send
731
            if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_send_)) {
732
                CalloutHandlePtr callout_handle = getCalloutHandle(query);
733

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

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

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

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

754 755
                /// @todo: Add support for DROP status.

756
                callout_handle->getArgument("response4", rsp);
757
            }
758

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

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

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

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

    return (true);
}

790
string
791
Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
    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());
808 809
}

810
void
811
Dhcpv4Srv::appendServerID(Dhcpv4Exchange& ex) {
812 813 814
    // 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.
815 816
    /// @todo: perhaps we should consider some more sophisticated server id
    /// generation, but for the current use cases, it should be ok.
817 818 819
    OptionPtr opt_srvid(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
                                           ex.getResponse()->getLocalAddr()));
    ex.getResponse()->addOption(opt_srvid);
820 821
}

822
void
823
Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
824 825
    // Get the subnet relevant for the client. We will need it
    // to get the options associated with it.
826
    Subnet4Ptr subnet = ex.getContext()->subnet_;
827 828 829 830 831 832 833
    // 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;
    }
834

835 836
    Pkt4Ptr query = ex.getQuery();

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

847 848
    Pkt4Ptr resp = ex.getResponse();

849 850 851 852 853 854
    // 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) {
855
        if (!resp->getOption(*opt)) {
856
            OptionDescriptor desc = subnet->getCfgOption()->get("dhcp4", *opt);
857
            if (desc.option_) {
858
                resp->addOption(desc.option_);
859
            }
860 861
        }
    }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
862
}
863

864
void
865
Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
866
    // Get the configured subnet suitable for the incoming packet.
867
    Subnet4Ptr subnet = ex.getContext()->subnet_;
868 869 870 871 872 873 874 875 876 877
    // 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
878 879
    boost::shared_ptr<OptionVendor> vendor_req = boost::dynamic_pointer_cast<
        OptionVendor>(ex.getQuery()->getOption(DHO_VIVSO_SUBOPTIONS));
880 881 882 883 884 885 886
    if (!vendor_req) {
        return;
    }

    uint32_t vendor_id = vendor_req->getVendorId();

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

    // 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;
901
    const std::vector<uint8_t>& requested_opts = oro->getValues();
902

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

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

920

921
void
922
Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
923 924 925 926 927 928
    // 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 };
929