dhcp6_srv.cc 38.1 KB
Newer Older
1
// Copyright (C) 2011-2013 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 16
#include <config.h>

17
#include <asiolink/io_address.h>
18
#include <dhcp/dhcp6.h>
19
#include <dhcp/duid.h>
20
#include <dhcp/iface_mgr.h>
21
#include <dhcp/libdhcp++.h>
22
#include <dhcp/option6_addrlst.h>
23
#include <dhcp/option6_ia.h>
24 25
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaaddr.h>
26
#include <dhcp/option_custom.h>
27
#include <dhcp/option_int_array.h>
28
#include <dhcp/pkt6.h>
29 30 31 32 33 34
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
35
#include <dhcpsrv/utils.h>
36
#include <exceptions/exceptions.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
37
#include <util/io_utilities.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
38
#include <util/range_utilities.h>
39
#include <util/encode/hex.h>
40

41
#include <boost/foreach.hpp>
42 43
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string/erase.hpp>
44

45 46
#include <stdlib.h>
#include <time.h>
47 48
#include <iomanip>
#include <fstream>
49

50
using namespace isc;
51
using namespace isc::asiolink;
52
using namespace isc::dhcp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
53
using namespace isc::util;
54
using namespace std;
55

56 57
namespace isc {
namespace dhcp {
58

59
Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
60
    : alloc_engine_(), serverid_(), shutdown_(true) {
61

62
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
63

64
    // Initialize objects required for DHCP server operation.
65
    try {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
66 67
        // Port 0 is used for testing purposes. It means that the server should
        // not open any sockets at all. Some tests, e.g. configuration parser,
68 69
        // require Dhcpv6Srv object, but they don't really need it to do
        // anything. This speed up and simplifies the tests.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
70
        if (port > 0) {
71 72 73 74 75
            if (IfaceMgr::instance().countIfaces() == 0) {
                LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
                return;
            }
            IfaceMgr::instance().openSockets6(port);
76
        }
77

78 79 80
        string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
        if (loadServerID(duid_file)) {
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_SERVERID_LOADED)
81
                .arg(duidToString(getServerID()))
82 83 84 85 86 87 88 89 90 91 92 93 94
                .arg(duid_file);
        } else {
            generateServerID();
            LOG_INFO(dhcp6_logger, DHCP6_SERVERID_GENERATED)
                .arg(duidToString(getServerID()))
                .arg(duid_file);

            if (!writeServerID(duid_file)) {
                LOG_WARN(dhcp6_logger, DHCP6_SERVERID_WRITE_FAIL)
                    .arg(duid_file);
            }

        }
95

96 97 98
        // Instantiate allocation engine
        alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));

99
    } catch (const std::exception &e) {
100
        LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
101 102
        return;
    }
103

104 105
    // All done, so can proceed
    shutdown_ = false;
106 107
}

108
Dhcpv6Srv::~Dhcpv6Srv() {
109
    IfaceMgr::instance().closeSockets();
110

111
    LeaseMgrFactory::destroy();
112 113
}

114
void Dhcpv6Srv::shutdown() {
115
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
116 117 118
    shutdown_ = true;
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
119
bool Dhcpv6Srv::run() {
120
    while (!shutdown_) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
121 122 123 124 125 126
        /// @todo: calculate actual timeout to the next event (e.g. lease
        /// expiration) once we have lease database. The idea here is that
        /// it is possible to do everything in a single process/thread.
        /// For now, we are just calling select for 1000 seconds. There
        /// were some issues reported on some systems when calling select()
        /// with too large values. Unfortunately, I don't recall the details.
127
        int timeout = 1000;
128

Tomek Mrugalski's avatar
Tomek Mrugalski committed
129
        // client's message and server's response
130
        Pkt6Ptr query;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
131
        Pkt6Ptr rsp;
132

133 134
        try {
            query = IfaceMgr::instance().receive6(timeout);
Marcin Siodelski's avatar
Marcin Siodelski committed
135
        } catch (const std::exception& e) {
136 137 138
            LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
        }

139 140
        if (query) {
            if (!query->unpack()) {
141 142
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                          DHCP6_PACKET_PARSE_FAIL);
143 144
                continue;
            }
145
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
146
                      .arg(query->getName());
147
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
148
                      .arg(static_cast<int>(query->getType()))
149 150 151
                      .arg(query->getBuffer().getLength())
                      .arg(query->toText());

152 153 154 155 156
            try {
                switch (query->getType()) {
                case DHCPV6_SOLICIT:
                    rsp = processSolicit(query);
                    break;
157

158 159 160
                case DHCPV6_REQUEST:
                    rsp = processRequest(query);
                    break;
161

162 163 164
                case DHCPV6_RENEW:
                    rsp = processRenew(query);
                    break;
165

166 167 168
                case DHCPV6_REBIND:
                    rsp = processRebind(query);
                    break;
169

170 171 172
                case DHCPV6_CONFIRM:
                    rsp = processConfirm(query);
                    break;
173

174
                case DHCPV6_RELEASE:
175 176
                    rsp = processRelease(query);
                    break;
177

178 179 180
                case DHCPV6_DECLINE:
                    rsp = processDecline(query);
                    break;
181

182 183 184
                case DHCPV6_INFORMATION_REQUEST:
                    rsp = processInfRequest(query);
                    break;
185

186 187 188 189 190 191
                default:
                    // Only action is to output a message if debug is enabled,
                    // and that will be covered by the debug statement before
                    // the "switch" statement.
                    ;
                }
192

193 194 195 196 197
            } catch (const RFCViolation& e) {
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
                    .arg(query->getName())
                    .arg(query->getRemoteAddr())
                    .arg(e.what());
198 199 200 201 202 203 204 205 206 207 208 209 210 211

            } catch (const isc::Exception& e) {

                // Catch-all exception (at least for ones based on the isc
                // Exception class, which covers more or less all that
                // are explicitly raised in the BIND 10 code).  Just log
                // the problem and ignore the packet. (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.)
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
                    .arg(query->getName())
                    .arg(query->getRemoteAddr())
                    .arg(e.what());
212 213
            }

214
            if (rsp) {
215 216 217 218 219 220
                rsp->setRemoteAddr(query->getRemoteAddr());
                rsp->setLocalAddr(query->getLocalAddr());
                rsp->setRemotePort(DHCP6_CLIENT_PORT);
                rsp->setLocalPort(DHCP6_SERVER_PORT);
                rsp->setIndex(query->getIndex());
                rsp->setIface(query->getIface());
221 222 223

                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
                          DHCP6_RESPONSE_DATA)
224
                    .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
225 226

                if (rsp->pack()) {
227 228
                    try {
                        IfaceMgr::instance().send(rsp);
Marcin Siodelski's avatar
Marcin Siodelski committed
229 230
                    } catch (const std::exception& e) {
                        LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL).arg(e.what());
231
                    }
232 233
                } else {
                    LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL);
234
                }
235
            }
236 237 238
        }
    }

239
    return (true);
240
}
241

242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
bool Dhcpv6Srv::loadServerID(const std::string& file_name) {

    // load content of the file into a string
    fstream f(file_name.c_str(), ios::in);
    if (!f.is_open()) {
        return (false);
    }

    string hex_string;
    f >> hex_string;
    f.close();

    // remove any spaces
    boost::algorithm::erase_all(hex_string, " ");

    // now remove :
    /// @todo: We should check first if the format is sane.
    /// Otherwise 1:2:3:4 will be converted to 0x12, 0x34
    boost::algorithm::erase_all(hex_string, ":");

    std::vector<uint8_t> bin;

    // Decode the hex string and store it in bin (which happens
    // to be OptionBuffer format)
    isc::util::encode::decodeHex(hex_string, bin);

    // Now create server-id option
    serverid_.reset(new Option(Option::V6, D6O_SERVERID, bin));

    return (true);
}

std::string Dhcpv6Srv::duidToString(const OptionPtr& opt) {
    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
276

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
    OptionBuffer data = opt->getData();

    bool colon = false;
    for (OptionBufferConstIter it = data.begin(); it != data.end(); ++it) {
        if (colon) {
            tmp << ":";
        }
        tmp << hex << setw(2) << setfill('0') << static_cast<uint16_t>(*it);
        if (!colon) {
            colon = true;
        }
    }

    return tmp.str();
}

bool Dhcpv6Srv::writeServerID(const std::string& file_name) {
    fstream f(file_name.c_str(), ios::out | ios::trunc);
    if (!f.good()) {
        return (false);
    }
    f << duidToString(getServerID());
    f.close();
}
Tomek Mrugalski's avatar
Tomek Mrugalski committed
301

302
void Dhcpv6Srv::generateServerID() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
303 304 305 306 307 308 309

    /// @todo: This code implements support for DUID-LLT (the recommended one).
    /// We should eventually add support for other DUID types: DUID-LL, DUID-EN
    /// and DUID-UUID

    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();

Tomek Mrugalski's avatar
Tomek Mrugalski committed
310
    // Let's find suitable interface.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
311 312
    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
         iface != ifaces.end(); ++iface) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
313
        // All the following checks could be merged into one multi-condition
Tomek Mrugalski's avatar
Tomek Mrugalski committed
314 315 316 317 318 319 320 321
        // statement, but let's keep them separated as perhaps one day
        // we will grow knobs to selectively turn them on or off. Also,
        // this code is used only *once* during first start on a new machine
        // and then server-id is stored. (or at least it will be once
        // DUID storage is implemente

        // I wish there was a this_is_a_real_physical_interface flag...

Tomek Mrugalski's avatar
Tomek Mrugalski committed
322 323 324 325 326
        // MAC address should be at least 6 bytes. Although there is no such
        // requirement in any RFC, all decent physical interfaces (Ethernet,
        // WiFi, Infiniband, etc.) have 6 bytes long MAC address. We want to
        // base our DUID on real hardware address, rather than virtual
        // interface that pretends that underlying IP address is its MAC.
327
        if (iface->getMacLen() < MIN_MAC_LEN) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
328 329 330
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
331
        // Let's don't use loopback.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
332 333 334 335
        if (iface->flag_loopback_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
336
        // Let's skip downed interfaces. It is better to use working ones.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
337 338 339 340
        if (!iface->flag_up_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
341
        // Some interfaces (like lo on Linux) report 6-bytes long
Tomek Mrugalski's avatar
Tomek Mrugalski committed
342 343
        // MAC adress 00:00:00:00:00:00. Let's not use such weird interfaces
        // to generate DUID.
344
        if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
345 346 347 348 349 350 351 352 353 354 355
            continue;
        }

        // Ok, we have useful MAC. Let's generate DUID-LLT based on
        // it. See RFC3315, Section 9.2 for details.

        // DUID uses seconds since midnight of 01-01-2000, time() returns
        // seconds since 01-01-1970. DUID_TIME_EPOCH substution corrects that.
        time_t seconds = time(NULL);
        seconds -= DUID_TIME_EPOCH;

356
        OptionBuffer srvid(8 + iface->getMacLen());
357
        writeUint16(DUID::DUID_LLT, &srvid[0]);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
358 359
        writeUint16(HWTYPE_ETHERNET, &srvid[2]);
        writeUint32(static_cast<uint32_t>(seconds), &srvid[4]);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
360
        memcpy(&srvid[0] + 8, iface->getMac(), iface->getMacLen());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
361 362 363 364

        serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
                                         srvid.begin(), srvid.end()));
        return;
365
    }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
366

Tomek Mrugalski's avatar
Tomek Mrugalski committed
367
    // If we reached here, there are no suitable interfaces found.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
368 369 370 371 372
    // Either interface detection is not supported on this platform or
    // this is really weird box. Let's use DUID-EN instead.
    // See Section 9.3 of RFC3315 for details.

    OptionBuffer srvid(12);
373
    writeUint16(DUID::DUID_EN, &srvid[0]);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
374 375
    writeUint32(ENTERPRISE_ID_ISC, &srvid[2]);

Tomek Mrugalski's avatar
Tomek Mrugalski committed
376 377
    // Length of the identifier is company specific. I hereby declare
    // ISC "standard" of 6 bytes long pseudo-random numbers.
378
    srandom(time(NULL));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
379
    fillRandom(&srvid[6], &srvid[12]);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
380

Tomek Mrugalski's avatar
Tomek Mrugalski committed
381 382
    serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
                                     srvid.begin(), srvid.end()));
383 384
}

385
void Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
386 387
    // Add client-id.
    OptionPtr clientid = question->getOption(D6O_CLIENTID);
388 389 390
    if (clientid) {
        answer->addOption(clientid);
    }
391

392 393 394
    // TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
}

395
void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
396 397 398
    // add server-id
    answer->addOption(getServerID());

399 400 401 402 403
    // Get the subnet object. It holds options to be sent to the client
    // that belongs to the particular subnet.
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
    // Warn if subnet is not supported and quit.
    if (!subnet) {
404
        LOG_WARN(dhcp6_logger, DHCP6_NO_SUBNET_DEF_OPT)
405 406 407
            .arg(question->getRemoteAddr().toText());
        return;
    }
408

409 410
}

411 412 413 414
void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
    // Get the subnet for a particular address.
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
    if (!subnet) {
415
        LOG_WARN(dhcp6_logger, DHCP6_NO_SUBNET_REQ_OPT)
416 417 418
            .arg(question->getRemoteAddr().toText());
        return;
    }
419

420 421
    // Client requests some options using ORO option. Try to
    // get this option from client's message.
422 423
    boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(question->getOption(D6O_ORO));
424 425 426 427 428 429 430
    // Option ORO not found. Don't do anything then.
    if (!option_oro) {
        return;
    }
    // Get the list of options that client requested.
    const std::vector<uint16_t>& requested_opts = option_oro->getValues();
    BOOST_FOREACH(uint16_t opt, requested_opts) {
431 432
        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt);
        if (desc.option) {
433 434 435
            answer->addOption(desc.option);
        }
    }
436 437
}

438
OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
439 440 441 442 443 444
    // @todo This function uses OptionCustom class to manage contents
    // of the data fields. Since this this option is frequently used
    // it may be good to implement dedicated class to avoid performance
    // impact.

    // Get the definition of the option holding status code.
445 446
    OptionDefinitionPtr status_code_def =
        LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
447
    // This definition is assumed to be initialized in LibDHCP.
448 449
    assert(status_code_def);

450 451
    // As there is no dedicated class to represent Status Code
    // the OptionCustom class should be returned here.
452 453 454 455 456
    boost::shared_ptr<OptionCustom> option_status =
        boost::dynamic_pointer_cast<
            OptionCustom>(status_code_def->optionFactory(Option::V6, D6O_STATUS_CODE));
    assert(option_status);

457
    // Set status code to 'code' (0 - means data field #0).
458
    option_status->writeInteger(code, 0);
459
    // Set a message (1 - means data field #1).
460 461
    option_status->writeString(text, 1);
    return (option_status);
462 463
}

464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
                            RequirementLevel serverid) {
    Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
    switch (clientid) {
    case MANDATORY:
        if (client_ids.size() != 1) {
            isc_throw(RFCViolation, "Exactly 1 client-id option expected in "
                      << pkt->getName() << ", but " << client_ids.size()
                      << " received");
        }
        break;
    case OPTIONAL:
        if (client_ids.size() > 1) {
            isc_throw(RFCViolation, "Too many (" << client_ids.size()
                      << ") client-id options received in " << pkt->getName());
        }
        break;

    case FORBIDDEN:
        // doesn't make sense - client-id is always allowed
        break;
    }

    Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
    switch (serverid) {
    case FORBIDDEN:
490
        if (!server_ids.empty()) {
491
            isc_throw(RFCViolation, "Server-id option was not expected, but "
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
                      << server_ids.size() << " received in " << pkt->getName());
        }
        break;

    case MANDATORY:
        if (server_ids.size() != 1) {
            isc_throw(RFCViolation, "Invalid number of server-id options received ("
                      << server_ids.size() << "), exactly 1 expected in message "
                      << pkt->getName());
        }
        break;

    case OPTIONAL:
        if (server_ids.size() > 1) {
            isc_throw(RFCViolation, "Too many (" << server_ids.size()
507
                      << ") server-id options received in " << pkt->getName());
508 509 510 511
        }
    }
}

512
Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
513 514 515 516 517 518 519 520 521 522 523

    /// @todo: pass interface information only if received direct (non-relayed) message

    // Try to find a subnet if received packet from a directly connected client
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface());
    if (subnet) {
        return (subnet);
    }

    // If no subnet was found, try to find it based on remote address
    subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
524 525

    return (subnet);
526 527
}

528
void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
529

Tomek Mrugalski's avatar
Tomek Mrugalski committed
530 531
    // We need to allocate addresses for all IA_NA options in the client's
    // question (i.e. SOLICIT or REQUEST) message.
532 533
    // @todo add support for IA_TA
    // @todo add support for IA_PD
Tomek Mrugalski's avatar
Tomek Mrugalski committed
534 535

    // We need to select a subnet the client is connected in.
536
    Subnet6Ptr subnet = selectSubnet(question);
537
    if (!subnet) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
538 539 540 541 542 543 544
        // This particular client is out of luck today. We do not have
        // information about the subnet he is connected to. This likely means
        // misconfiguration of the server (or some relays). We will continue to
        // process this message, but our response will be almost useless: no
        // addresses or prefixes, no subnet specific configuration etc. The only
        // thing this client can get is some global information (like DNS
        // servers).
545

546 547
        // perhaps this should be logged on some higher level? This is most likely
        // configuration bug.
548 549 550 551
        LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED)
            .arg(question->getRemoteAddr().toText())
            .arg(question->getName());

552 553 554
    } else {
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
            .arg(subnet->toText());
555 556 557 558
    }

    // @todo: We should implement Option6Duid some day, but we can do without it
    // just fine for now
Tomek Mrugalski's avatar
Tomek Mrugalski committed
559 560 561 562 563

    // Let's find client's DUID. Client is supposed to include its client-id
    // option almost all the time (the only exception is an anonymous inf-request,
    // but that is mostly a theoretical case). Our allocation engine needs DUID
    // and will refuse to allocate anything to anonymous clients.
564 565 566 567
    DuidPtr duid;
    OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
    if (opt_duid) {
        duid = DuidPtr(new DUID(opt_duid->getData()));
568
    } else {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
569
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING);
570 571
        // Let's drop the message. This client is not sane.
        isc_throw(RFCViolation, "Mandatory client-id is missing in received message");
572 573
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
574 575 576 577 578 579
    // Now that we have all information about the client, let's iterate over all
    // received options and handle IA_NA options one by one and store our
    // responses in answer message (ADVERTISE or REPLY).
    //
    // @todo: expand this to cover IA_PD and IA_TA once we implement support for
    // prefix delegation and temporary addresses.
580 581
    for (Option::OptionCollection::iterator opt = question->options_.begin();
         opt != question->options_.end(); ++opt) {
582 583
        switch (opt->second->getType()) {
        case D6O_IA_NA: {
584
            OptionPtr answer_opt = assignIA_NA(subnet, duid, question,
585
                                   boost::dynamic_pointer_cast<Option6IA>(opt->second));
586 587 588 589 590 591 592
            if (answer_opt) {
                answer->addOption(answer_opt);
            }
            break;
        }
        default:
            break;
593 594
        }
    }
595
}
596

597 598
OptionPtr Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                                 Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
599 600 601 602
    // If there is no subnet selected for handling this IA_NA, the only thing to do left is
    // to say that we are sorry, but the user won't get an address. As a convenience, we
    // use a different status text to indicate that (compare to the same status code,
    // but different wording below)
603
    if (!subnet) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
604
        // Create empty IA_NA option with IAID matching the request.
605 606 607 608
        // Note that we don't use OptionDefinition class to create this option.
        // This is because we prefer using a constructor of Option6IA that
        // initializes IAID. Otherwise we would have to use setIAID() after
        // creation of the option which has some performance implications.
609 610
        boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));

Tomek Mrugalski's avatar
Tomek Mrugalski committed
611
        // Insert status code NoAddrsAvail.
612 613 614 615
        ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail, "Sorry, no subnet available."));
        return (ia_rsp);
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
616 617 618
    // Check if the client sent us a hint in his IA_NA. Clients may send an
    // address in their IA_NA options as a suggestion (e.g. the last address
    // they used before).
619
    boost::shared_ptr<Option6IAAddr> hintOpt = boost::dynamic_pointer_cast<Option6IAAddr>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
620
                                        (ia->getOption(D6O_IAADDR));
621 622 623 624 625
    IOAddress hint("::");
    if (hintOpt) {
        hint = hintOpt->getAddress();
    }

626 627 628 629
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PROCESS_IA_NA_REQUEST)
        .arg(duid?duid->toText():"(no-duid)").arg(ia->getIAID())
        .arg(hintOpt?hint.toText():"(no hint)");

Tomek Mrugalski's avatar
Tomek Mrugalski committed
630 631 632 633 634 635
    // "Fake" allocation is processing of SOLICIT message. We pretend to do an
    // allocation, but we do not put the lease in the database. That is ok,
    // because we do not guarantee that the user will get that exact lease. If
    // the user selects this server to do actual allocation (i.e. sends REQUEST)
    // it should include this hint. That will help us during the actual lease
    // allocation.
636 637 638 639 640 641
    bool fake_allocation = false;
    if (question->getType() == DHCPV6_SOLICIT) {
        /// @todo: Check if we support rapid commit
        fake_allocation = true;
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
642 643 644 645
    // Use allocation engine to pick a lease for this client. Allocation engine
    // will try to honour the hint, but it is just a hint - some other address
    // may be used instead. If fake_allocation is set to false, the lease will
    // be inserted into the LeaseMgr as well.
646 647 648
    Lease6Ptr lease = alloc_engine_->allocateAddress6(subnet, duid, ia->getIAID(),
                                                      hint, fake_allocation);

Tomek Mrugalski's avatar
Tomek Mrugalski committed
649
    // Create IA_NA that we will put in the response.
650 651
    // Do not use OptionDefinition to create option's instance so
    // as we can initialize IAID using a constructor.
652 653 654
    boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));

    if (lease) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
655 656 657 658
        // We have a lease! Let's wrap its content into IA_NA option
        // with IAADDR suboption.
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, fake_allocation?
                  DHCP6_LEASE_ADVERT:DHCP6_LEASE_ALLOC)
659 660 661
            .arg(lease->addr_.toText())
            .arg(duid?duid->toText():"(no-duid)")
            .arg(ia->getIAID());
662 663 664 665 666 667 668 669 670 671

        ia_rsp->setT1(subnet->getT1());
        ia_rsp->setT2(subnet->getT2());

        boost::shared_ptr<Option6IAAddr>
            addr(new Option6IAAddr(D6O_IAADDR,
                                   lease->addr_,
                                   lease->preferred_lft_,
                                   lease->valid_lft_));
        ia_rsp->addOption(addr);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
672 673 674 675

        // It would be possible to insert status code=0(success) as well,
        // but this is considered waste of bandwidth as absence of status
        // code is considered a success.
676
    } else {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
677 678 679 680
        // Allocation engine did not allocate a lease. The engine logged
        // cause of that failure. The only thing left is to insert
        // status code to pass the sad news to the client.

681 682
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, fake_allocation ?
                  DHCP6_LEASE_ADVERT_FAIL : DHCP6_LEASE_ALLOC_FAIL)
683
            .arg(duid?duid->toText():"(no-duid)")
684
            .arg(ia->getIAID());
685

Tomek Mrugalski's avatar
Tomek Mrugalski committed
686 687
        ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail,
                          "Sorry, no address could be allocated."));
688 689 690 691
    }
    return (ia_rsp);
}

692 693 694 695
OptionPtr Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                                Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) {
    Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(*duid, ia->getIAID(),
                                                            subnet->getID());
696

697 698 699 700 701 702 703
    if (!lease) {
        // client renewing a lease that we don't know about.

        // Create empty IA_NA option with IAID matching the request.
        boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));

        // Insert status code NoAddrsAvail.
704
        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
705
                          "Sorry, no known leases for this duid/iaid."));
706 707 708 709 710 711

        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW)
            .arg(duid->toText())
            .arg(ia->getIAID())
            .arg(subnet->toText());

712
        return (ia_rsp);
713 714
    }

715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739
    lease->preferred_lft_ = subnet->getPreferred();
    lease->valid_lft_ = subnet->getValid();
    lease->t1_ = subnet->getT1();
    lease->t2_ = subnet->getT2();
    lease->cltt_ = time(NULL);

    LeaseMgrFactory::instance().updateLease6(lease);

    // Create empty IA_NA option with IAID matching the request.
    boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));

    ia_rsp->setT1(subnet->getT1());
    ia_rsp->setT2(subnet->getT2());

    boost::shared_ptr<Option6IAAddr> addr(new Option6IAAddr(D6O_IAADDR,
                                          lease->addr_, lease->preferred_lft_,
                                          lease->valid_lft_));
    ia_rsp->addOption(addr);
    return (ia_rsp);
}

void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {

    // We need to renew addresses for all IA_NA options in the client's
    // RENEW message.
740 741
    // @todo add support for IA_TA
    // @todo add support for IA_PD
742 743 744 745 746 747 748 749 750 751 752 753 754 755

    // We need to select a subnet the client is connected in.
    Subnet6Ptr subnet = selectSubnet(renew);
    if (!subnet) {
        // This particular client is out of luck today. We do not have
        // information about the subnet he is connected to. This likely means
        // misconfiguration of the server (or some relays). We will continue to
        // process this message, but our response will be almost useless: no
        // addresses or prefixes, no subnet specific configuration etc. The only
        // thing this client can get is some global information (like DNS
        // servers).

        // perhaps this should be logged on some higher level? This is most likely
        // configuration bug.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
756
        LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED);
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
    } else {
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
            .arg(subnet->toText());
    }

    // Let's find client's DUID. Client is supposed to include its client-id
    // option almost all the time (the only exception is an anonymous inf-request,
    // but that is mostly a theoretical case). Our allocation engine needs DUID
    // and will refuse to allocate anything to anonymous clients.
    OptionPtr opt_duid = renew->getOption(D6O_CLIENTID);
    if (!opt_duid) {
        // This should not happen. We have checked this before.
        reply->addOption(createStatusCode(STATUS_UnspecFail,
                         "You did not include mandatory client-id"));
        return;
    }
    DuidPtr duid(new DUID(opt_duid->getData()));

    for (Option::OptionCollection::iterator opt = renew->options_.begin();
         opt != renew->options_.end(); ++opt) {
        switch (opt->second->getType()) {
        case D6O_IA_NA: {
            OptionPtr answer_opt = renewIA_NA(subnet, duid, renew,
                                   boost::dynamic_pointer_cast<Option6IA>(opt->second));
            if (answer_opt) {
                reply->addOption(answer_opt);
            }
            break;
        }
        default:
            break;
        }
    }
790 791 792
}

void Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
793

794 795 796 797 798 799 800 801 802
    // We need to release addresses for all IA_NA options in the client's
    // RELEASE message.
    // @todo Add support for IA_TA
    // @todo Add support for IA_PD
    // @todo Consider supporting more than one address in a single IA_NA.
    // That was envisaged by RFC3315, but it never happened. The only
    // software that supports that is Dibbler, but its author seriously doubts
    // if anyone is really using it. Clients that want more than one address
    // just include more instances of IA_NA options.
803

804 805 806 807 808 809 810
    // Let's find client's DUID. Client is supposed to include its client-id
    // option almost all the time (the only exception is an anonymous inf-request,
    // but that is mostly a theoretical case). Our allocation engine needs DUID
    // and will refuse to allocate anything to anonymous clients.
    OptionPtr opt_duid = release->getOption(D6O_CLIENTID);
    if (!opt_duid) {
        // This should not happen. We have checked this before.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
811 812 813 814
        // see sanityCheck() called from processRelease()
        LOG_WARN(dhcp6_logger, DHCP6_RELEASE_MISSING_CLIENTID)
            .arg(release->getRemoteAddr().toText());

815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
        reply->addOption(createStatusCode(STATUS_UnspecFail,
                         "You did not include mandatory client-id"));
        return;
    }
    DuidPtr duid(new DUID(opt_duid->getData()));

    int general_status = STATUS_Success;
    for (Option::OptionCollection::iterator opt = release->options_.begin();
         opt != release->options_.end(); ++opt) {
        switch (opt->second->getType()) {
        case D6O_IA_NA: {
            OptionPtr answer_opt = releaseIA_NA(duid, release, general_status,
                                   boost::dynamic_pointer_cast<Option6IA>(opt->second));
            if (answer_opt) {
                reply->addOption(answer_opt);
            }
            break;
        }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
833 834
        // @todo: add support for IA_PD
        // @todo: add support for IA_TA
835
        default:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
836 837
            // remaining options are stateless and thus ignored in this context
            ;
838 839
        }
    }
840

841 842 843 844 845
    // To be pedantic, we should also include status code in the top-level
    // scope, not just in each IA_NA. See RFC3315, section 18.2.6.
    // This behavior will likely go away in RFC3315bis.
    reply->addOption(createStatusCode(general_status,
                     "Summary status for all processed IA_NAs"));
846 847
}

848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
OptionPtr Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
                                  int& general_status,
                                  boost::shared_ptr<Option6IA> ia) {
    // Release can be done in one of two ways:
    // Approach 1: extract address from client's IA_NA and see if it belongs
    // to this particular client.
    // Approach 2: find a subnet for this client, get a lease for
    // this subnet/duid/iaid and check if its content matches to what the
    // client is asking us to release.
    //
    // This method implements approach 1.

    // That's our response
    boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));

    boost::shared_ptr<Option6IAAddr> release_addr = boost::dynamic_pointer_cast<Option6IAAddr>
        (ia->getOption(D6O_IAADDR));
    if (!release_addr) {
        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
                                           "You did not include address in your RELEASE"));
        general_status = STATUS_NoBinding;
        return (ia_rsp);
    }

    Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(release_addr->getAddress());

    if (!lease) {
        // client releasing a lease that we don't know about.

        // Insert status code NoAddrsAvail.
        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
                          "Sorry, no known leases for this duid/iaid, can't release."));
        general_status = STATUS_NoBinding;

Tomek Mrugalski's avatar
Tomek Mrugalski committed
882
        LOG_INFO(dhcp6_logger, DHCP6_UNKNOWN_RELEASE)
883 884 885 886 887 888 889 890 891 892 893
            .arg(duid->toText())
            .arg(ia->getIAID());

        return (ia_rsp);
    }

    if (!lease->duid_) {
        // Something is gravely wrong here. We do have a lease, but it does not
        // have mandatory DUID information attached. Someone was messing with our
        // database.

Tomek Mrugalski's avatar
Tomek Mrugalski committed
894
        LOG_ERROR(dhcp6_logger, DHCP6_LEASE_WITHOUT_DUID)
895 896 897 898 899 900 901 902 903 904 905
            .arg(release_addr->getAddress().toText());

        general_status = STATUS_UnspecFail;
        ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
                          "Database consistency check failed when trying to RELEASE"));
        return (ia_rsp);
    }

    if (*duid != *(lease->duid_)) {
        // Sorry, it's not your address. You can't release it.

906
        LOG_INFO(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_DUID)
907
            .arg(duid->toText())
908
            .arg(release_addr->getAddress().toText())
909 910 911 912 913 914 915 916 917 918 919 920
            .arg(lease->duid_->toText());

        general_status = STATUS_NoBinding;
        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
                          "This address does not belong to you, you can't release it"));
        return (ia_rsp);
    }

    if (ia->getIAID() != lease->iaid_) {
        // This address belongs to this client, but to a different IA
        LOG_WARN(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_IAID)
            .arg(duid->toText())
921
            .arg(release_addr->getAddress().toText())
Tomek Mrugalski's avatar
Tomek Mrugalski committed
922 923
            .arg(lease->iaid_)
            .arg(ia->getIAID());
924 925 926 927 928 929
        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
                          "This is your address, but you used wrong IAID"));
        general_status = STATUS_NoBinding;
        return (ia_rsp);
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
930 931
    // It is not necessary to check if the address matches as we used
    // getLease6(addr) method that is supposed to return a proper lease.
932

933
    // Ok, we've passed all checks. Let's release this address.
934

935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950
    if (!LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
        ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
                          "Server failed to release a lease"));

        LOG_ERROR(dhcp6_logger, DHCP6_RELEASE_FAIL)
            .arg(lease->addr_.toText())
            .arg(duid->toText())
            .arg(lease->iaid_);
        general_status = STATUS_UnspecFail;

        return (ia_rsp);
    } else {
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RELEASE)
            .arg(lease->addr_.toText())
            .arg(duid->toText())
            .arg(lease->iaid_);
951

952 953 954 955 956
        ia_rsp->addOption(createStatusCode(STATUS_Success,
                          "Lease released. Thank you, please come again."));

        return (ia_rsp);
    }
957 958
}

959

960
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
961

962 963
    sanityCheck(solicit, MANDATORY, FORBIDDEN);

964
    Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
965 966 967 968 969 970 971 972

    copyDefaultOptions(solicit, advertise);
    appendDefaultOptions(solicit, advertise);
    appendRequestedOptions(solicit, advertise);

    assignLeases(solicit, advertise);

    return (advertise);
973 974
}

975
Pkt6Ptr Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
976 977 978

    sanityCheck(request, MANDATORY, MANDATORY);

979
    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
980 981 982 983 984 985 986 987

    copyDefaultOptions(request, reply);
    appendDefaultOptions(request, reply);
    appendRequestedOptions(request, reply);

    assignLeases(request, reply);

    return (reply);
988 989
}

990
Pkt6Ptr Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
991 992 993

    sanityCheck(renew, MANDATORY, MANDATORY);

994
    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
995 996 997 998 999 1000 1001

    copyDefaultOptions(renew, reply);
    appendDefaultOptions(renew, reply);
    appendRequestedOptions(renew, reply);

    renewLeases(renew, reply);

1002 1003 1004
    return reply;
}

1005
Pkt6Ptr Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1006 1007
    /// @todo: Implement this
    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
1008 1009 1010
    return reply;
}

1011
Pkt6Ptr Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1012
    /// @todo: Implement this
1013
    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, confirm->getTransid()));
1014 1015 1016
    return reply;
}

1017
Pkt6Ptr Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
1018 1019 1020

    sanityCheck(release, MANDATORY, MANDATORY);

1021
    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
1022 1023 1024 1025 1026 1027

    copyDefaultOptions(release, reply);
    appendDefaultOptions(release, reply);

    releaseLeases(release, reply);

1028 1029 1030
    return reply;
}

1031
Pkt6Ptr Dhcpv6Srv::processDecline(const Pkt6Ptr& decline) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1032
    /// @todo: Implement this
1033
    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, decline->getTransid()));
1034 1035
    return reply;
}