dhcp4_srv.cc 102 KB
Newer Older
1
// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6

7
#include <config.h>
8
#include <asiolink/io_address.h>
9
#include <dhcp/dhcp4.h>
10 11
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
12
#include <dhcp/iface_mgr.h>
13
#include <dhcp/option4_addrlst.h>
14
#include <dhcp/option_int.h>
15
#include <dhcp/option_int_array.h>
16
#include <dhcp/option_vendor.h>
17
#include <dhcp/option_string.h>
18
#include <dhcp/pkt4.h>
19
#include <dhcp/docsis3_option_defs.h>
20 21
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4_srv.h>
22 23
#include <dhcpsrv/addr_utilities.h>
#include <dhcpsrv/callout_handle_store.h>
24
#include <dhcpsrv/cfgmgr.h>
25
#include <dhcpsrv/cfg_subnets4.h>
26 27
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
28
#include <dhcpsrv/ncr_generator.h>
29
#include <dhcpsrv/subnet.h>
30
#include <dhcpsrv/subnet_selector.h>
31
#include <dhcpsrv/utils.h>
32
#include <dhcpsrv/utils.h>
33 34
#include <eval/evaluate.h>
#include <eval/eval_messages.h>
35
#include <hooks/callout_handle.h>
36
#include <hooks/hooks_log.h>
37
#include <hooks/hooks_manager.h>
38
#include <stats/stats_mgr.h>
39
#include <util/strutil.h>
40
#include <stats/stats_mgr.h>
41 42
#include <log/logger.h>
#include <cryptolink/cryptolink.h>
43
#include <cfgrpt/config_report.h>
44

45 46 47 48 49 50 51 52
#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>

53
#include <boost/asio.hpp>
54
#include <boost/bind.hpp>
55
#include <boost/foreach.hpp>
56
#include <boost/shared_ptr.hpp>
57 58 59

#include <iomanip>

60 61
using namespace isc;
using namespace isc::asiolink;
62
using namespace isc::cryptolink;
63
using namespace isc::dhcp;
64
using namespace isc::dhcp_ddns;
65
using namespace isc::hooks;
66
using namespace isc::log;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
67
using namespace isc::stats;
68
using namespace std;
69

70
/// Structure that holds registered hook indexes
71 72
struct Dhcp4Hooks {
    int hook_index_buffer4_receive_;///< index for "buffer4_receive" hook point
73 74
    int hook_index_pkt4_receive_;   ///< index for "pkt4_receive" hook point
    int hook_index_subnet4_select_; ///< index for "subnet4_select" hook point
75
    int hook_index_lease4_release_; ///< index for "lease4_release" hook point
76
    int hook_index_pkt4_send_;      ///< index for "pkt4_send" hook point
77
    int hook_index_buffer4_send_;   ///< index for "buffer4_send" hook point
78
    int hook_index_lease4_decline_; ///< index for "lease4_decline" hook point
79

80 81 82
    /// Constructor that registers hook points for DHCPv4 engine
    Dhcp4Hooks() {
        hook_index_buffer4_receive_= HooksManager::registerHook("buffer4_receive");
83 84 85
        hook_index_pkt4_receive_   = HooksManager::registerHook("pkt4_receive");
        hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
        hook_index_pkt4_send_      = HooksManager::registerHook("pkt4_send");
86 87
        hook_index_lease4_release_ = HooksManager::registerHook("lease4_release");
        hook_index_buffer4_send_   = HooksManager::registerHook("buffer4_send");
88
        hook_index_lease4_decline_ = HooksManager::registerHook("lease4_decline");
89 90 91 92 93 94 95
    }
};

// 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.
96
Dhcp4Hooks Hooks;
97

98 99 100
namespace isc {
namespace dhcp {

101
Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
102 103
                               const Pkt4Ptr& query,
                               const Subnet4Ptr& subnet)
104 105
    : alloc_engine_(alloc_engine), query_(query), resp_(),
      context_(new AllocEngine::ClientContext4()) {
106

107 108 109 110 111 112 113 114
    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");
115 116
    }

117 118 119
    // Create response message.
    initResponse();
    // Select subnet for the query message.
120
    context_->subnet_ = subnet;
121
    // Hardware address.
122
    context_->hwaddr_ = query->getHWAddr();
123 124
    // Pointer to client's query.
    context_->query_ = query;
125

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

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

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

173 174
    // explicitly set this to 0
    resp_->setSiaddr(IOAddress::IPV4_ZERO_ADDRESS());
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
    // 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);
    }
201
}
202

203 204
void
Dhcpv4Exchange::copyDefaultOptions() {
205 206 207 208 209 210 211
    // 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);
    }
212 213 214 215 216

    // 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);
217
    }
218 219 220 221 222 223 224 225 226 227 228 229 230

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

233 234
const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");

235
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,
236
                     const bool direct_response_desired)
237 238 239
    : 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) {
240

241
    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
242
    try {
243 244 245
        // 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.
246
        if (port) {
247 248 249 250 251 252 253 254
            // 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);
255
        }
256

257 258 259 260
        // 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,
261
                                            false /* false = IPv4 */));
262

263 264 265 266 267 268 269
        // 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

270
    } catch (const std::exception &e) {
271
        LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
272 273 274
        shutdown_ = true;
        return;
    }
275

Tomek Mrugalski's avatar
Tomek Mrugalski committed
276
    shutdown_ = false;
277 278 279
}

Dhcpv4Srv::~Dhcpv4Srv() {
280 281 282 283 284 285
    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());
    }
286

Tomek Mrugalski's avatar
Tomek Mrugalski committed
287
    IfaceMgr::instance().closeSockets();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
288

289 290
    // The lease manager was instantiated during DHCPv4Srv configuration,
    // so we should clean up after ourselves.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
291
    LeaseMgrFactory::destroy();
292 293
}

294 295
void
Dhcpv4Srv::shutdown() {
296
    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_SHUTDOWN_REQUEST);
297 298 299
    shutdown_ = true;
}

300
isc::dhcp::Subnet4Ptr
301
Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query) const {
302 303 304 305 306 307 308 309 310 311 312

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

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

348 349 350 351
    CfgMgr& cfgmgr = CfgMgr::instance();
    subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);

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

379 380
        /// @todo: Add support for DROP status

381 382 383 384
        // Use whatever subnet was specified by the callout
        callout_handle->getArgument("subnet4", subnet);
    }

385 386
    if (subnet) {
        // Log at higher debug level that subnet has been found.
387
        LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_SELECTED)
388 389 390 391
            .arg(query->getLabel())
            .arg(subnet->getID());
        // Log detailed information about the selected subnet at the
        // lower debug level.
392
        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_DATA)
393 394 395 396
            .arg(query->getLabel())
            .arg(subnet->toText());

    } else {
397
        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL,
398 399 400 401
                  DHCP4_SUBNET_SELECTION_FAILED)
            .arg(query->getLabel());
    }

402
    return (subnet);
403 404
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
405 406
Pkt4Ptr
Dhcpv4Srv::receivePacket(int timeout) {
407 408 409
    return (IfaceMgr::instance().receive4(timeout));
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
410 411
void
Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
412 413 414
    IfaceMgr::instance().send(packet);
}

415 416
bool
Dhcpv4Srv::run() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
417
    while (!shutdown_) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
418
        // client's message and server's response
419
        Pkt4Ptr query;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
420
        Pkt4Ptr rsp;
421

422 423
        try {

424
        try {
425
            uint32_t timeout = 1000;
426
            LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_BUFFER_WAIT).arg(timeout);
427
            query = receivePacket(timeout);
428

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

            } else {
444
                LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_BUFFER_WAIT_INTERRUPTED)
445 446 447
                    .arg(timeout);
            }

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

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

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

487 488 489 490
        // 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
491
        isc::stats::StatsMgr::instance().addValue("pkt4-received",
492
                                                  static_cast<int64_t>(1));
493

494 495 496 497 498 499 500 501 502
        // 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));

503 504 505
        bool skip_unpack = false;

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

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

            callout_handle->getArgument("query4", query);
533 534

            /// @todo: add support for DROP status
535 536 537 538 539
        }

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

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

564 565 566
        // Update statistics accordingly for received packet.
        processStatsReceived(query);

Tomek Mrugalski's avatar
Tomek Mrugalski committed
567 568 569
        // 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.
570 571
        classifyPacket(query);

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

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

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

618 619
            /// @todo: Add support for DROP status

620 621
            callout_handle->getArgument("query4", query);
        }
622

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

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

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

673 674 675
        if (!rsp) {
            continue;
        }
676

677

678 679
        // Specifies if server should do the packing
        bool skip_pack = false;
680

Tomek Mrugalski's avatar
Tomek Mrugalski committed
681
        // Execute all callouts registered for pkt4_send
682 683
        if (HooksManager::calloutsPresent(hook_index_pkt4_send_)) {
            CalloutHandlePtr callout_handle = getCalloutHandle(query);
684

685 686
            // Delete all previous arguments
            callout_handle->deleteAllArguments();
687

688
            // Clear skip flag if it was set in previous callouts
689
            callout_handle->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
690

691 692
            // Set our response
            callout_handle->setArgument("response4", rsp);
693

694 695 696
            // Also pass the corresponding query packet as argument
            callout_handle->setArgument("query4", query);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        } catch (const std::exception& e) {
            // General catch-all exception that are not caught by more specific
            // catches. This one is for exceptions derived from std::exception.
            LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION)
                .arg(e.what());
        } catch (...) {
            // General catch-all exception that are not caught by more specific
            // catches. This one is for other exceptions, not derived from
            // std::exception.
            LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION)
                .arg("an unknown exception not derived from std::exception");
        }
797 798 799 800 801
    }

    return (true);
}

802
string
803
Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
    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());
820 821
}

822
void
823
Dhcpv4Srv::appendServerID(Dhcpv4Exchange& ex) {
824 825 826
    // 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.
827 828
    /// @todo: perhaps we should consider some more sophisticated server id
    /// generation, but for the current use cases, it should be ok.
829 830 831
    OptionPtr opt_srvid(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
                                           ex.getResponse()->getLocalAddr()));
    ex.getResponse()->addOption(opt_srvid);
832 833
}

834 835 836 837 838 839
void
Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
    CfgOptionList& co_list = ex.getCfgOptionList();

    // First subnet configured options
    Subnet4Ptr subnet = ex.getContext()->subnet_;
840 841 842 843 844 845
    if (!subnet) {
        // All methods using the CfgOptionList object return soon when
        // there is no subnet so do the same
        return;
    }
    if (!subnet->getCfgOption()->empty()) {
846 847 848 849 850 851 852 853 854 855 856 857
        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
858 859 860
            if (((*cclass).size() <= VENDOR_CLASS_PREFIX.size()) ||
                ((*cclass).compare(0, VENDOR_CLASS_PREFIX.size(), VENDOR_CLASS_PREFIX) != 0)) {
                // Not a VENDOR_CLASS_* so should be configured
861
                LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNCONFIGURED)
862 863 864 865
                    .arg(ex.getQuery()->getLabel())
                    .arg(*cclass);
            }
            // Skip it
866 867
            continue;
        }
868 869 870 871
        if (ccdef->getCfgOption()->empty()) {
            // Skip classes which don't configure options
            continue;
        }
872 873 874 875
        co_list.push_back(ccdef->getCfgOption());
    }

    // Last global options
876 877 878
    if (!CfgMgr::instance().getCurrentCfg()->getCfgOption()->empty()) {
        co_list.push_back(CfgMgr::instance().getCurrentCfg()->getCfgOption());
    }
879 880
}

881
void
882
Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
883 884
    // Get the subnet relevant for the client. We will need it
    // to get the options associated with it.
885
    Subnet4Ptr subnet = ex.getContext()->subnet_;
886 887 888 889 890 891 892
    // 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;
    }
893

894 895 896 897 898 899
    // Unlikely short cut
    const CfgOptionList& co_list = ex.getCfgOptionList();
    if (co_list.empty()) {
        return;
    }

900 901
    Pkt4Ptr query = ex.getQuery();

902 903 904
    // try to get the 'Parameter Request List' option which holds the
    // codes of requested options.
    OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
905
        OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));