zone_entry.cc 20.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Copyright (C) 2010  CZ NIC
//
// 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 <map>

17 18
#include <config.h>

19 20
#include "zone_entry.h"
#include "address_request_callback.h"
Michal Vaner's avatar
Michal Vaner committed
21 22 23 24
#include "nameserver_entry.h"

#include <algorithm>
#include <boost/foreach.hpp>
25
#include <boost/bind.hpp>
Michal Vaner's avatar
Michal Vaner committed
26
#include <dns/rrttl.h>
27
#include <dns/rcode.h>
28
#include <dns/rdataclass.h>
Michal Vaner's avatar
Michal Vaner committed
29 30

using namespace std;
31 32

namespace isc {
Michal Vaner's avatar
Michal Vaner committed
33 34 35

using namespace dns;

36 37
namespace nsas {

38 39
ZoneEntry::ZoneEntry(
    boost::shared_ptr<isc::resolve::ResolverInterface> resolver,
40 41 42 43 44 45 46 47 48 49 50 51
    const std::string& name, const isc::dns::RRClass& class_code,
    boost::shared_ptr<HashTable<NameserverEntry> > nameserver_table,
    boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru) :
    expiry_(0),
    name_(name), class_code_(class_code), resolver_(resolver),
    nameserver_table_(nameserver_table), nameserver_lru_(nameserver_lru)
{
    in_process_[ANY_OK] = false;
    in_process_[V4_ONLY] = false;
    in_process_[V6_ONLY] = false;
}

52 53
namespace {
// Shorter aliases for frequently used types
54
typedef isc::locks::scoped_lock<isc::locks::recursive_mutex> Lock; // Local lock, nameservers not locked
55
typedef boost::shared_ptr<AddressRequestCallback> CallbackPtr;
56 57 58 59 60

/*
 * Create a nameserver.
 * Called inside a mutex so it is filled in atomically.
 */
61
boost::shared_ptr<NameserverEntry>
62
newNs(const std::string* name, const RRClass* class_code) {
63
    return (boost::shared_ptr<NameserverEntry>(new NameserverEntry(*name,
64 65 66
        *class_code)));
}

67 68
}

69 70 71 72 73 74 75 76 77 78 79
/**
 * \short Callback class that ZoneEntry passes to a resolver.
 *
 * We need to ask for the list of nameservers. So we pass ResolverCallback
 * object to it, when it knows the answer, method of this thing will be
 * called.
 *
 * It is a nested friend class and should be considered as a part of ZoneEntry
 * code. It manipulates directly ZoneEntry's data members, locks it and like
 * that. Mostly eliminates C++ bad design of missing lambda functions.
 */
80 81
class ZoneEntry::ResolverCallback :
        public isc::resolve::ResolverInterface::Callback {
82
    public:
83
        /// \short Constructor. Pass "this" zone entry
84
        ResolverCallback(boost::shared_ptr<ZoneEntry> entry) :
85 86
            entry_(entry)
        { }
87 88 89 90 91 92 93 94 95 96 97
        /**
         * \short It successfully received nameserver list.
         *
         * It fills the nameservers into the ZoneEntry whose callback this is.
         * If there are in the hash table, it is used. If not, they are
         * created. This might still fail, if the list is empty.
         *
         * It then calls process, to go trough the list of nameservers,
         * examining them and seeing if some addresses are already there
         * and to ask for the rest of them.
         */
98
        virtual void success(MessagePtr response_message) {
99
            Lock lock(entry_->mutex_);
100 101 102 103 104 105 106 107 108 109 110 111 112

            // TODO: find the correct RRset, not simply the first
            if (!response_message ||
                response_message->getRcode() != isc::dns::Rcode::NOERROR() ||
                response_message->getRRCount(isc::dns::Message::SECTION_ANSWER) == 0) {
                // todo: define this
                failureInternal(300);
            }

            isc::dns::RRsetIterator rrsi =
                response_message->beginSection(isc::dns::Message::SECTION_ANSWER);
            const isc::dns::RRsetPtr answer = *rrsi;

113 114 115
            RdataIteratorPtr iterator(answer->getRdataIterator());
            // If there are no data
            if (iterator->isLast()) {
116
                failureInternal(answer->getTTL().getValue());
117 118
                return;
            } else {
119 120 121 122 123 124
                /*
                 * We store the nameservers we have currently (we might have
                 * none, at startup, but when we time out and ask again, we
                 * do), so we can just reuse them instead of looking them up in
                 * the table or creating them.
                 */
125 126 127 128
                map<string, NameserverPtr> old;
                BOOST_FOREACH(const NameserverPtr& ptr, entry_->nameservers_) {
                    old[ptr->getName()] = ptr;
                }
129 130 131 132 133 134
                /*
                 * List of original nameservers we did not ask for IP address
                 * yet.
                 */
                set<NameserverPtr> old_not_asked;
                old_not_asked.swap(entry_->nameservers_not_asked_);
135

136 137
                // Once we have them put aside, remove the original set
                // of nameservers from the entry
138
                entry_->nameservers_.clear();
139
                // And put the ones from the answer them, reusing if possible
140 141 142 143 144 145 146 147
                for (; !iterator->isLast(); iterator->next()) {
                    try {
                        // Get the name from there
                        Name ns_name(dynamic_cast<const rdata::generic::NS&>(
                            iterator->getCurrent()).getNSName());
                        // Try to find it in the old ones
                        map<string, NameserverPtr>::iterator old_ns(old.find(
                            ns_name.toText()));
148 149 150 151
                        /*
                         * We didn't have this nameserver before. So we just
                         * look it up in the hash table or create it.
                         */
152 153 154 155 156
                        if (old_ns == old.end()) {
                            // Look it up or create it
                            string ns_name_str(ns_name.toText());
                            pair<bool, NameserverPtr> from_hash(
                                entry_->nameserver_table_->getOrAdd(HashKey(
157
                                ns_name_str, entry_->class_code_), boost::bind(
158
                                newNs, &ns_name_str, &entry_->class_code_)));
159 160 161 162
                            // Make it at the front of the list
                            if (from_hash.first) {
                                entry_->nameserver_lru_->add(from_hash.second);
                            } else {
163 164 165
                                entry_->nameserver_lru_->touch(
                                    from_hash.second);
                            }
166
                            // And add it at last to the entry
167
                            entry_->nameservers_.push_back(from_hash.second);
168 169
                            entry_->nameservers_not_asked_.insert(
                                from_hash.second);
170
                        } else {
171
                            // We had it before, reuse it
172
                            entry_->nameservers_.push_back(old_ns->second);
173 174 175 176 177 178 179 180 181
                            // Did we ask it already? If not, it is still not
                            // asked (the one designing std interface must
                            // have been mad)
                            if (old_not_asked.find(old_ns->second) !=
                                old_not_asked.end())
                            {
                                entry_->nameservers_not_asked_.insert(
                                    old_ns->second);
                            }
182 183
                        }
                    }
184
                    // OK, we skip this one as it is not NS (log?)
185
                    catch (bad_cast&) { }
186 187
                }

188 189
                // It is unbelievable, but we found no nameservers there
                if (entry_->nameservers_.empty()) {
190
                    // So we fail the same way as if we got empty list
191
                    failureInternal(answer->getTTL().getValue());
192 193
                    return;
                } else {
194 195 196
                    // Ok, we have them. So set us as ready, set our
                    // expiration time and try to answer what we can, ask
                    // if there's still someone to ask.
197 198
                    entry_->setState(READY);
                    entry_->expiry_ = answer->getTTL().getValue() + time(NULL);
199
                    entry_->process(ADDR_REQ_MAX, NameserverPtr());
200 201
                    return;
                }
202
            }
Michal Vaner's avatar
Michal Vaner committed
203
        }
204
        /// \short Failed to receive answer.
205
        virtual void failure() {
206
            failureInternal(300);
207 208
        }
    private:
209 210 211 212 213 214
        /**
         * \short Common function called when "it did not work"
         *
         * It marks the ZoneEntry as unreachable and processes callbacks (by
         * calling process).
         */
215 216
        void failureInternal(time_t ttl) {
            Lock lock(entry_->mutex_);
217 218 219
            entry_->setState(UNREACHABLE);
            entry_->expiry_ = ttl + time(NULL);
            // Process all three callback lists and tell them KO
220
            entry_->process(ADDR_REQ_MAX, NameserverPtr());
221
        }
222
        /// \short The entry we are callback of
223
        boost::shared_ptr<ZoneEntry> entry_;
Michal Vaner's avatar
Michal Vaner committed
224 225 226
};

void
227
ZoneEntry::addCallback(CallbackPtr callback, AddressFamily family) {
228
    Lock lock(mutex_);
Michal Vaner's avatar
Michal Vaner committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242

    bool ask(false);

    // Look at expiration time
    if (expiry_ && time(NULL) >= expiry_) {
        setState(EXPIRED);
    }

    // We need to ask (again)
    if (getState() == EXPIRED || getState() == NOT_ASKED) {
        ask = true;
    }

    // We do not have the answer right away, just queue the callback
243 244 245 246
    bool execute(!ask && getState() != IN_PROGRESS &&
        callbacks_[family].empty());
    callbacks_[family].push_back(callback);
    if (execute) {
Michal Vaner's avatar
Michal Vaner committed
247
        // Try to process it right away, store if not possible to handle
248
        process(family, NameserverPtr());
Michal Vaner's avatar
Michal Vaner committed
249 250 251 252 253
        return;
    }

    if (ask) {
        setState(IN_PROGRESS);
254
        // Our callback might be directly called from resolve, unlock now
Michal Vaner's avatar
Michal Vaner committed
255 256
        QuestionPtr question(new Question(Name(name_), class_code_,
            RRType::NS()));
257
        boost::shared_ptr<ResolverCallback> resolver_callback(
258
            new ResolverCallback(shared_from_this()));
Michal Vaner's avatar
Michal Vaner committed
259
        resolver_->resolve(question, resolver_callback);
260
        return;
Michal Vaner's avatar
Michal Vaner committed
261 262 263
    }
}

Michal Vaner's avatar
Michal Vaner committed
264 265
namespace {

266
// This just moves items from one container to another
Michal Vaner's avatar
Michal Vaner committed
267 268 269 270 271 272 273
template<class Container>
void
move(Container& into, Container& from) {
    into.insert(into.end(), from.begin(), from.end());
    from.clear();
}

274 275 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 301 302 303 304 305 306 307 308 309 310 311 312
// Update the address selector according to the RTTs
//
// Each address has a probability to be selected if multiple addresses are available
// The weight factor is equal to 1/(rtt*rtt), then all the weight factors are normalized
// to make the sum equal to 1.0
void
updateAddressSelector(std::vector<NameserverAddress>& addresses,
    WeightedRandomIntegerGenerator& selector)
{
    vector<double> probabilities;
    BOOST_FOREACH(NameserverAddress& address, addresses) {
        uint32_t rtt = address.getAddressEntry().getRTT();
        if(rtt == 0) {
            isc_throw(RTTIsZero, "The RTT is 0");
        }

        if(rtt == AddressEntry::UNREACHABLE) {
            probabilities.push_back(0);
        } else {
            probabilities.push_back(1.0/(rtt*rtt));
        }
    }
    // Calculate the sum
    double sum = accumulate(probabilities.begin(), probabilities.end(), 0.0);

    if(sum != 0) {
        // Normalize the probabilities to make the sum equal to 1.0
        for(vector<double>::iterator it = probabilities.begin();
                it != probabilities.end(); ++it){
            (*it) /= sum;
        }
    } else if(probabilities.size() > 0){
        // If all the nameservers are unreachable, the sum will be 0
        // So give each server equal opportunity to be selected.
        for(vector<double>::iterator it = probabilities.begin();
                it != probabilities.end(); ++it){
            (*it) = 1.0/probabilities.size();
        }
    }
313

314
    selector.reset(probabilities);
315 316
}

Michal Vaner's avatar
Michal Vaner committed
317 318
}

319 320 321 322 323 324 325
/**
 * \short Sets given boolean to false when destroyed.
 *
 * This is hack eliminating C++ missing finally. We need to make sure
 * the value gets set to false when we leave the function, so we use
 * a Guard object, that sets it when it gets out of scope.
 */
326 327 328 329 330 331 332 333 334 335 336 337
class ZoneEntry::ProcessGuard {
    public:
        ProcessGuard(bool& guarded) :
            guarded_(guarded)
        { }
        ~ ProcessGuard() {
            guarded_ = false;
        }
    private:
        bool& guarded_;
};

338 339 340 341 342 343 344 345 346 347
/**
 * \short Callback from NameserverEntry to us.
 *
 * We registre object of this class whenever some ZoneEntry has a need to be
 * notified of a change (received data) inside its NameserverEntry.
 *
 * This is part of the ZoneEntry code (not visible from outside, accessing
 * private functions). It is here just because C++ does not know propper lambda
 * functions.
 */
348 349
class ZoneEntry::NameserverCallback : public NameserverEntry::Callback {
    public:
350 351 352 353 354 355 356
        /**
         * \short Constructor.
         *
         * \param entry The ZoneEntry to be notified.
         * \param family For which address family this change is, so we
         *     do not process all the nameserves and callbacks there.
         */
357
        NameserverCallback(boost::shared_ptr<ZoneEntry> entry, AddressFamily family) :
358 359 360
            entry_(entry),
            family_(family)
        { }
361 362 363 364 365 366 367
        /**
         * \short Callback method.
         *
         * This is called by NameserverEntry when the change happens.
         * We just call process to go trough relevant nameservers and call
         * any callbacks we can.
         */
368
        virtual void operator()(NameserverPtr ns) {
369
            entry_->process(family_, ns);
370 371
        }
    private:
372
        boost::shared_ptr<ZoneEntry> entry_;
373 374 375
        AddressFamily family_;
};

Michal Vaner's avatar
Michal Vaner committed
376
void
377
ZoneEntry::dispatchFailures(AddressFamily family) {
Michal Vaner's avatar
Michal Vaner committed
378 379 380 381 382 383 384 385 386 387 388 389 390
    // We extract all the callbacks
    vector<CallbackPtr> callbacks;
    if (family == ADDR_REQ_MAX) {
        move(callbacks_[ANY_OK], callbacks_[V4_ONLY]);
        move(callbacks_[ANY_OK], callbacks_[V6_ONLY]);
        family = ANY_OK;
    }
    callbacks.swap(callbacks_[family]);
    BOOST_FOREACH(const CallbackPtr& callback, callbacks) {
        callback->unreachable();
    }
}

Michal Vaner's avatar
Michal Vaner committed
391
void
392
ZoneEntry::process(AddressFamily family,
393
    const boost::shared_ptr<NameserverEntry>& nameserver)
Michal Vaner's avatar
Michal Vaner committed
394
{
395
    Lock lock(mutex_);
Michal Vaner's avatar
Michal Vaner committed
396 397 398 399 400
    switch (getState()) {
        // These are not interesting, nothing to return now
        case NOT_ASKED:
        case IN_PROGRESS:
        case EXPIRED:
401
            break;
Michal Vaner's avatar
Michal Vaner committed
402
        case UNREACHABLE: {
403
            dispatchFailures(family);
Michal Vaner's avatar
Michal Vaner committed
404
            // And we do nothing more now
405
            break;
Michal Vaner's avatar
Michal Vaner committed
406 407
        }
        case READY:
408 409
            if (family == ADDR_REQ_MAX) {
                // Just process each one separately
410
                // TODO Think this over, is it safe, to unlock in the middle?
411 412 413
                process(ANY_OK, nameserver);
                process(V4_ONLY, nameserver);
                process(V6_ONLY, nameserver);
414
            } else {
Michal Vaner's avatar
Michal Vaner committed
415 416 417 418
                // Nothing to do anyway for this family, be dormant
                if (callbacks_[family].empty()) {
                    return;
                }
419
                /*
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
                 * If we have multiple nameservers and more than 1 of them
                 * is in the cache, we want to choose from all their addresses.
                 * So we ensure this instance of process is the only one on
                 * the stack. If not, we terminate and let the outernmost
                 * one handle it when we return to it.
                 *
                 * If we didn't do it, one instance would call "resolve". If it
                 * was from cache, it would imediatelly recurse back to another
                 * process (trough the nameserver callback, etc), which would
                 * take that only one nameserver and trigger all callbacks.
                 * Only then would resolve terminate and we could ask for the
                 * second nameserver. This way, we first receive all the
                 * nameservers that are already in cache and trigger the
                 * callbacks only then.
                 *
                 * However, this does not wait for external fetches of
                 * nameserver addresses, as the callback is called after
                 * process terminates. Therefore this waits only for filling
                 * of the nameservers which we already have in cache.
439 440 441 442
                 */
                if (in_process_[family]) {
                    return;
                }
443
                // Mark we are on the stack
444 445 446 447 448 449 450 451 452 453 454 455 456 457
                ProcessGuard guard(in_process_[family]);
                in_process_[family] = true;
                // Variables to store the data to
                NameserverEntry::AddressVector addresses;
                NameserverVector to_ask;
                bool pending(false);

                // Pick info from the nameservers
                BOOST_FOREACH(const NameserverPtr& ns, nameservers_) {
                    Fetchable::State ns_state(ns->getAddresses(addresses,
                        family, ns == nameserver));
                    switch (ns_state) {
                        case IN_PROGRESS:
                            pending = true;
458 459 460 461 462 463 464
                            // Someone asked it, but not us, we don't have
                            // callback
                            if (nameservers_not_asked_.find(ns) !=
                                nameservers_not_asked_.end())
                            {
                                to_ask.push_back(ns);
                            }
465 466 467 468 469 470 471 472 473 474 475 476 477 478
                            break;
                        case NOT_ASKED:
                        case EXPIRED:
                            to_ask.push_back(ns);
                            break;
                        case UNREACHABLE:
                        case READY:
                            // Not interested, but avoiding warning
                            break;
                    }
                }

                // We have someone to ask, so do it
                if (!to_ask.empty()) {
479 480
                    // We ask everything that makes sense now
                    nameservers_not_asked_.clear();
481 482 483 484 485 486 487 488
                    /*
                     * TODO: Possible place for an optimisation. We now ask
                     * everything we can. We should limit this to something like
                     * 2 concurrent NS fetches (and fetch cache first, then
                     * fetch the remote ones). But fetching everything right
                     * away is simpler.
                     */
                    BOOST_FOREACH(const NameserverPtr& ns, to_ask) {
Michal Vaner's avatar
Michal Vaner committed
489 490 491 492 493
                        // Put all 3 callbacks there. If we put just the
                        // current family, it might not work due to missing
                        // callback for different one.
                        // If they recurse back to us (call directly), we kill
                        // it by the in_process_
494
                        insertCallback(ns, ADDR_REQ_MAX);
495 496 497
                    }
                    // Retry with all the data that might have arrived
                    in_process_[family] = false;
498
                    // We do not provide the callback again
499
                    process(family, nameserver);
500 501 502 503
                    // And be done
                    return;
                // We have some addresses to answer
                } else if (!addresses.empty()) {
504 505 506 507 508
                    // Prepare the selector of addresses
                    // TODO: Think of a way how to keep it for a while
                    //   (not update every time)
                    updateAddressSelector(addresses, address_selector);

509 510
                    // Extract the callbacks
                    vector<CallbackPtr> to_execute;
Michal Vaner's avatar
Michal Vaner committed
511 512
                    // FIXME: Think of a solution where we do not lose
                    // any callbacks upon exception
513 514
                    to_execute.swap(callbacks_[family]);

Michal Vaner's avatar
Michal Vaner committed
515
                    // Run the callbacks
516
                    BOOST_FOREACH(const CallbackPtr& callback, to_execute) {
517
                        callback->success(addresses[address_selector()]);
518
                    }
Michal Vaner's avatar
Michal Vaner committed
519 520
                    return;
                } else if (!pending) {
521
                    dispatchFailures(family);
Michal Vaner's avatar
Michal Vaner committed
522
                    return;
523 524 525
                }
            }
            return;
Michal Vaner's avatar
Michal Vaner committed
526
    }
Michal Vaner's avatar
Michal Vaner committed
527 528
}

529 530 531 532 533 534 535
void
ZoneEntry::insertCallback(NameserverPtr ns, AddressFamily family) {
    if (family == ADDR_REQ_MAX) {
        insertCallback(ns, ANY_OK);
        insertCallback(ns, V4_ONLY);
        insertCallback(ns, V6_ONLY);
    } else {
536
        boost::shared_ptr<NameserverCallback> callback(new NameserverCallback(
537 538 539 540 541
            shared_from_this(), family));
        ns->askIP(resolver_, callback, family);
    }
}

542 543
}; // namespace nsas
}; // namespace isc