dhcp4_srv.cc 101 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 825 826 827 828 829
void
Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
    CfgOptionList& co_list = ex.getCfgOptionList();

    // First subnet configured options
    Subnet4Ptr subnet = ex.getContext()->subnet_;
830
    if (subnet && !subnet->getCfgOption()->empty()) {
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
        co_list.push_back(subnet->getCfgOption());
    }

    // Each class in the incoming packet
    const ClientClasses& classes = ex.getQuery()->getClasses();
    for (ClientClasses::const_iterator cclass = classes.begin();
         cclass != classes.end(); ++cclass) {
        // Find the client class definition for this class
        const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
            getClientClassDictionary()->findClass(*cclass);
        if (!ccdef) {
            // Not found: the class is not configured
            LOG_DEBUG(options4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNCONFIGURED)
                .arg(ex.getQuery()->getLabel())
                .arg(*cclass);
            continue;
        }
848 849 850 851
        if (ccdef->getCfgOption()->empty()) {
            // Skip classes which don't configure options
            continue;
        }
852 853 854 855
        co_list.push_back(ccdef->getCfgOption());
    }

    // Last global options
856 857 858
    if (!CfgMgr::instance().getCurrentCfg()->getCfgOption()->empty()) {
        co_list.push_back(CfgMgr::instance().getCurrentCfg()->getCfgOption());
    }
859 860
}

861
void
862
Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
863 864
    // Get the subnet relevant for the client. We will need it
    // to get the options associated with it.
865
    Subnet4Ptr subnet = ex.getContext()->subnet_;
866 867 868 869 870 871 872
    // 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;
    }
873

874 875 876 877 878 879
    // Unlikely short cut
    const CfgOptionList& co_list = ex.getCfgOptionList();
    if (co_list.empty()) {
        return;
    }

880 881
    Pkt4Ptr query = ex.getQuery();

882 883 884
    // try to get the 'Parameter Request List' option which holds the
    // codes of requested options.
    OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
885
        OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
886 887 888 889 890
    // If there is no PRL option in the message from the client then
    // there is nothing to do.
    if (!option_prl) {
        return;
    }
891

892 893
    Pkt4Ptr resp = ex.getResponse();

894 895 896 897 898 899
    // 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) {
900
        // Add nothing when it is already there
901
        if (!resp->getOption(*opt)) {
902 903 904 905 906
            // Iterate on the configured option list
            for (CfgOptionList::const_iterator copts = co_list.begin();
                 copts != co_list.end(); ++copts) {
                OptionDescriptor desc = (*copts)->get("dhcp4", *opt);
                // Got it: add it and jump to the outer loop
907 908
                if (desc.option_) {
                    resp->addOption(desc.option_);
909
                    break;
910 911 912 913
                }
            }
        }
    }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
914
}
915

916
void
917
Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
918
    // Get the configured subnet suitable for the incoming packet.
919
    Subnet4Ptr subnet = ex.getContext()->subnet_;
920 921 922 923 924 925 926 927 928
    // 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;
    }

929 930 931 932 933 934
    // Unlikely short cut
    const CfgOptionList& co_list = ex.getCfgOptionList();
    if (co_list.empty()) {
        return;
    }

935
    // Try to get the vendor option
936 937
    boost::shared_ptr<OptionVendor> vendor_req = boost::dynamic_pointer_cast<
        OptionVendor>(ex.getQuery()->getOption(DHO_VIVSO_SUBOPTIONS));
938 939 940 941 942 943 944
    if (!vendor_req) {
        return;
    }

    uint32_t vendor_id = vendor_req->getVendorId();

    // Let's try to get ORO within that vendor-option
945 946
    /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other
    /// vendors may have different policies.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
947 948
    OptionUint8ArrayPtr oro =
        boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO));
949 950 951 952 953 954 955 956 957