data_source.cc 20.2 KB
Newer Older
JINMEI Tatuya's avatar
JINMEI Tatuya committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
//
// 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.

// $Id$

17 18 19
#include <iostream>
#include <vector>

20
#include <dns/buffer.h>
21
#include <dns/message.h>
22
#include <dns/name.h>
23
#include <dns/rdataclass.h>
24
#include <dns/rrset.h>
25
#include <dns/rrsetlist.h>
26

27
#include <cc/data.h>
28 29 30

#include "data_source.h"

31 32 33 34
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;

35
namespace isc {
36 37 38 39 40 41 42 43 44 45 46 47 48
namespace auth {

// Add a task to the query task queue to look up additional data
// (i.e., address records for the names included in NS or MX records)
static void
getAdditional(Query& q, RRsetPtr rrset) {
    if (!q.wantAdditional()) {
        return;
    }

    RdataIteratorPtr it = rrset->getRdataIterator();
    for (it->first(); !it->isLast(); it->next()) {
        const Rdata& rd(it->getCurrent());
49 50
        QueryTaskPtr newtask = QueryTaskPtr();

51 52 53
        if (rrset->getType() == RRType::NS()) {
            const generic::NS& ns = dynamic_cast<const generic::NS&>(rd);

54 55 56 57
            newtask = QueryTaskPtr(new QueryTask(ns.getNSName(), q.qclass(),
                                                 Section::ADDITIONAL(),
                                                 QueryTask::GLUE_QUERY,
                                                 QueryTask::GETADDITIONAL)); 
58 59
        } else if (rrset->getType() == RRType::MX()) {
            const generic::MX& mx = dynamic_cast<const generic::MX&>(rd);
60 61 62 63
            newtask = QueryTaskPtr(new QueryTask(mx.getMXName(), q.qclass(),
                                                 Section::ADDITIONAL(),
                                                 QueryTask::NOGLUE_QUERY,
                                                 QueryTask::GETADDITIONAL)); 
64
        }
65 66
        if (newtask) {
            q.tasks().push(newtask);
67 68 69 70 71 72 73
        }
    }
}

// Synthesize a CNAME answer, for the benefit of clients that don't
// understand DNAME
static void
74
synthesizeCname(Query& q, QueryTaskPtr task, RRsetPtr rrset, RRsetList& target) {
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
    RdataIteratorPtr it;
    it = rrset->getRdataIterator();

    // More than one DNAME RR in the RRset is illegal, so we only have
    // to process the first one.
    it->first();
    if (it->isLast()) {
        return;
    }

    const Rdata& rd(it->getCurrent());
    const generic::DNAME& dname = dynamic_cast<const generic::DNAME&>(rd);
    const Name& dname_target(dname.getDname());

    try {
90
        int qnlen = task->qname.getLabelCount();
91
        int dnlen = rrset->getName().getLabelCount();
92
        const Name& prefix(task->qname.split(0, qnlen - dnlen));
93
        const Name& newname(prefix.concatenate(dname_target));
94
        RRsetPtr cname(new RRset(task->qname, task->qclass, RRType::CNAME(),
95 96 97 98 99 100 101 102 103 104
                                 rrset->getTTL()));
        cname->addRdata(generic::CNAME(newname));
        cname->setTTL(rrset->getTTL());
        target.addRRset(cname);
    } catch (...) {}
}

// Add a task to the query task queue to look up the data pointed
// to by a CNAME record
static void
105
chaseCname(Query& q, QueryTaskPtr task, RRsetPtr rrset) {
106 107 108 109 110 111 112 113 114 115 116 117 118 119
    RdataIteratorPtr it;
    it = rrset->getRdataIterator();

    // More than one CNAME RR in the RRset is illegal, so we only have
    // to process the first one.
    it->first();
    if (it->isLast()) {
        return;
    }

    const Rdata& rd(it->getCurrent());
    const generic::CNAME& cname = dynamic_cast<const generic::CNAME&>(rd);
    const Name& target(cname.getCname());

120 121 122 123 124
    QueryTaskPtr newtask = QueryTaskPtr(new QueryTask(target, task->qclass,
                                                      task->qtype,
                                                      Section::ANSWER(),
                                                      QueryTask::FOLLOWCNAME)); 
    q.tasks().push(newtask);
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
}

// Perform the query specified in a QueryTask object
DataSrc::Result
doQueryTask(const DataSrc* ds, Query& q, QueryTask& task, RRsetList& target) {
    switch (task.op) {
    case QueryTask::AUTH_QUERY:
        return (ds->findRRset(q, task.qname, task.qclass, task.qtype,
                              target, task.flags, task.zone));

    case QueryTask::SIMPLE_QUERY:
        return (ds->findExactRRset(q, task.qname, task.qclass, task.qtype,
                                   target, task.flags, task.zone));

    case QueryTask::GLUE_QUERY:
    case QueryTask::NOGLUE_QUERY:
        return (ds->findAddrs(q, task.qname, task.qclass, target,
                              task.flags, task.zone));

    case QueryTask::REF_QUERY:
        return (ds->findReferral(q, task.qname, task.qclass, target,
                                 task.flags, task.zone));
    }
148

149 150 151 152 153 154 155
    // Not reached
    return (DataSrc::ERROR);
}

// Copy referral information into the authority section of a message
static inline void
copyAuth(Query& q, const RRsetList& auth) {
156
    Message& m = q.message();
157 158 159 160 161 162 163 164
    BOOST_FOREACH(RRsetPtr rrset, auth) {
        if (rrset->getType() == RRType::DNAME()) {
            continue;
        }
        m.addRRset(Section::AUTHORITY(), rrset, q.wantDnssec());
        getAdditional(q, rrset);
    }
}
165

166 167
// Query for referrals (i.e., NS/DS or DNAME) at a given name
static inline bool
168
refQuery(const Name& name, Query& q, QueryTaskPtr task,
169
         const DataSrc* ds, RRsetList& target) {
170 171
    QueryTask newtask(name, q.qclass(), QueryTask::REF_QUERY);
    newtask.zone = task->zone;
172

173
    DataSrc::Result result = doQueryTask(ds, q, newtask, target);
174 175 176 177 178 179 180

    // Lookup failed
    if (result != DataSrc::SUCCESS) {
        return (false);
    }
    
    // Referral bit is expected, so clear it when checking flags
181
    if ((newtask.flags & ~DataSrc::REFERRAL) != 0) {
182 183 184 185 186 187 188 189 190
        return (false);
    }

    return (true);
}

// Match downward, from the zone apex to the query name, looking for
// referrals.
static inline bool
191
hasDelegation(const DataSrc* ds, Query& q, QueryTaskPtr task) {
192
    Message& m = q.message();
193 194
    int nlen = task->qname.getLabelCount();
    int diff = nlen - task->zone->getLabelCount();
195
    if (diff > 1) {
196
        bool found = false;
197 198
        RRsetList ref;
        for(int i = diff; i > 1; i--) {
199
            Name sub(task->qname.split(i - 1, nlen - i));
200 201 202 203 204 205 206 207
            if (refQuery(sub, q, task, ds, ref)) {
                found = true;
                break;
            }
        }

        // Found a referral while getting additional data
        // for something other than NS; we skip it.
208
        if (found && task->op == QueryTask::NOGLUE_QUERY) {
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
            return (true);
        }

        // Found a referral while getting answer data;
        // send a delegation.
        if (found) {
            if (RRsetPtr r = ref[RRType::DNAME()]) {
                RRsetList syn;
                m.addRRset(Section::ANSWER(), r, q.wantDnssec());
                m.setHeaderFlag(MessageFlag::AA());
                synthesizeCname(q, task, r, syn);
                if (syn.size() == 1) {
                    m.addRRset(Section::ANSWER(),
                               syn[RRType::CNAME()],
                               q.wantDnssec());
                    chaseCname(q, task, syn[RRType::CNAME()]);
                    return (true);
                }
            }

            copyAuth(q, ref);
            return (true);
        }
    }

    // We appear to have authoritative data; set the header
    // flag.  (We may clear it later if we find a referral
    // at the actual qname node.)
237 238
    if (task->op == QueryTask::AUTH_QUERY &&
        task->state == QueryTask::GETANSWER) {
239 240 241 242 243 244 245 246
        m.setHeaderFlag(MessageFlag::AA());
    }

    return (false);
}

// Attempt a wildcard lookup
static inline DataSrc::Result
247
tryWildcard(Query& q, QueryTaskPtr task, const DataSrc* ds, bool& found) {
248 249 250 251
    Message& m = q.message();
    DataSrc::Result result;
    found = false;

252 253 254
    if ((task->flags & DataSrc::NAME_NOT_FOUND) == 0 || 
        (task->state != QueryTask::GETANSWER &&
         task->state != QueryTask::FOLLOWCNAME)) {
255 256 257
        return (DataSrc::SUCCESS);
    }

258 259
    int nlen = task->qname.getLabelCount();
    int diff = nlen - task->zone->getLabelCount();
260 261 262 263 264 265 266 267 268
    if (diff < 1) {
        return (DataSrc::SUCCESS);
    }

    RRsetList wild;
    Name star("*");
    uint32_t rflags = 0;

    for(int i = 1; i <= diff; i++) {
269 270 271 272 273
        const Name& wname(star.concatenate(task->qname.split(i, nlen - i)));
        QueryTask newtask(wname, task->qclass, task->qtype,
                          QueryTask::SIMPLE_QUERY); 
        newtask.zone = task->zone;
        result = doQueryTask(ds, q, newtask, wild);
274
        if (result == DataSrc::SUCCESS &&
275 276
            (newtask.flags == 0 || (newtask.flags & DataSrc::CNAME_FOUND))) {
            rflags = newtask.flags;
277 278 279 280 281 282 283 284 285 286 287 288 289
            found = true;
            break;
        }
    }

    // A wildcard was found.  Add the data to the answer
    // section (but with the name changed to match the
    // qname), and then continue as if this were a normal
    // answer: if a CNAME, chase the target, otherwise
    // add authority.
    if (found) {
        if (rflags & DataSrc::CNAME_FOUND) {
            if (RRsetPtr rrset = wild[RRType::CNAME()]) {
290
                rrset->setName(task->qname);
291 292 293 294 295
                m.addRRset(Section::ANSWER(), rrset, q.wantDnssec());
                chaseCname(q, task, rrset);
            }
        } else {
            BOOST_FOREACH (RRsetPtr rrset, wild) {
296
                rrset->setName(task->qname);
297 298 299 300
                m.addRRset(Section::ANSWER(), rrset, q.wantDnssec());
            }

            RRsetList auth;
301
            if (! refQuery(Name(*task->zone), q, task, ds, auth)) {
302 303 304 305 306 307 308 309
                return (DataSrc::ERROR);
            }

            copyAuth(q, auth);
        }
    } else if (q.wantDnssec()) {
        // No wildcard found; add an NSEC to prove it
        RRsetList nsec;
310
        QueryTask newtask = QueryTask(*task->zone, task->qclass, RRType::NSEC(),
311
                                QueryTask::SIMPLE_QUERY); 
312 313
        newtask.zone = task->zone;
        result = doQueryTask(ds, q, newtask, nsec);
314 315 316 317
        if (result != DataSrc::SUCCESS) {
            return (DataSrc::ERROR);
        }

318
        if (newtask.flags == 0) {
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
            m.addRRset(Section::AUTHORITY(), nsec[RRType::NSEC()], true);
        }
    }

    return (DataSrc::SUCCESS);
}

//
// doQuery: Processes a query.
// 
void
DataSrc::doQuery(Query q) {
    Result result;
    Message& m = q.message();
    vector<RRsetPtr> additional;

    // XXX: this is for testing purposes; it should be done when 
    // parsing the message for EDNS0 options
    q.setWantDnssec(true);

    m.clearHeaderFlag(MessageFlag::AA());
    while (!q.tasks().empty()) {
        RRsetList data;

343
        QueryTaskPtr task = q.tasks().front();
344 345
        q.tasks().pop();

346
        // These task types should never be on the task queue.
347 348
        if (task->op == QueryTask::SIMPLE_QUERY ||
            task->op == QueryTask::REF_QUERY) {
349 350 351 352 353 354 355 356
            m.setRcode(Rcode::SERVFAIL());
            return;
        }

        // Find the closest enclosing zone for which we are authoritative,
        // and the concrete data source which is authoritative for it.
        // (Note that RRtype DS queries need to go to the parent.)
        Name search(".");
357 358
        if (task->qtype == RRType::DS()) {
            search = task->qname.split(1, task->qname.getLabelCount() - 1);
359
        } else {
360
            search = task->qname;
361 362 363 364 365 366 367 368
        }

        NameMatch match(search);
        findClosestEnclosure(match);
        const DataSrc* ds = match.bestDataSrc();
        const Name* zone = match.closestName();

        if (ds) {
369
            task->zone = new Name(*zone);
370 371 372 373

            // For these query task types, if there is more than
            // one level between the zone name and qname, we need to
            // check the intermediate nodes for referrals.
374 375
            if ((task->op == QueryTask::AUTH_QUERY ||
                 task->op == QueryTask::NOGLUE_QUERY) &&
376 377 378 379
                  hasDelegation(ds, q, task)) {
                continue;
            }

380
            result = doQueryTask(ds, q, *task, data);
381 382 383 384 385 386 387 388
            if (result != SUCCESS) {
                m.setRcode(Rcode::SERVFAIL());
                return;
            }

            // Query found a referral; let's find out if that was expected--
            // i.e., if an NS was at the zone apex, or if we were querying
            // specifically for the NS, DS or DNAME record.
389 390 391 392 393 394
            if ((task->flags & REFERRAL) &&
                (zone->getLabelCount() == task->qname.getLabelCount() ||
                 task->qtype == RRType::NS() ||
                 task->qtype == RRType::DS() ||
                 task->qtype == RRType::DNAME())) {
                task->flags &= ~REFERRAL;
395
            }
396
        } else {
397
            task->flags = NO_SUCH_ZONE;
398 399
        }

400
        if (result == SUCCESS && task->flags == 0) {
401
            bool have_ns = false, need_auth = false;
402
            switch (task->state) {
403 404 405
            case QueryTask::GETANSWER:
            case QueryTask::FOLLOWCNAME:
                BOOST_FOREACH(RRsetPtr rrset, data) {
406
                    m.addRRset(task->section, rrset, q.wantDnssec());
407 408 409 410 411 412 413
                    if (q.tasks().empty()) {
                        need_auth = true;
                    }
                    getAdditional(q, rrset);
                    if (rrset->getType() == RRType::NS()) {
                        have_ns = true;
                    }
414
                }
415 416 417 418 419 420 421 422 423 424
                q.setStatus(Query::ANSWERED);
                if (need_auth && !have_ns) {
                    // Data found, no additional processing needed.
                    // Add the NS records for the enclosing zone to
                    // the authority section.
                    RRsetList auth;
                    if (! refQuery(Name(*zone), q, task, ds, auth)) {
                        m.setRcode(Rcode::SERVFAIL());
                        return;
                    }
425

426
                    copyAuth(q, auth);
427
                }
428
                continue;
429

430 431 432 433 434 435 436 437 438 439 440
            case QueryTask::GETADDITIONAL:
                // Got additional data.  Do not add it to the message
                // yet; instead store it and copy it in at the end
                // (this allow RRSIGs to be omitted if necessary).
                BOOST_FOREACH(RRsetPtr rrset, data) {
                    if (q.status() == Query::ANSWERED &&
                        rrset->getName() == q.qname() &&
                        rrset->getType() == q.qtype()) {
                        continue;
                    }
                    additional.push_back(rrset);
441 442 443
                }
                continue;

444 445 446 447 448 449
            default:
                dns_throw (Unexpected, "unexpected query state");
            }
        } else if (result == ERROR || result == NOT_IMPLEMENTED) {
            m.setRcode(Rcode::SERVFAIL());
            return;
450
        } else if (task->flags & CNAME_FOUND) {
451 452 453
            // The qname node contains a CNAME.  Add a new task to the
            // queue to look up its target.
            if (RRsetPtr rrset = data[RRType::CNAME()]) {
454
                m.addRRset(task->section, rrset, q.wantDnssec());
455 456 457
                chaseCname(q, task, rrset);
            }
            continue;
458
        } else if (task->flags & REFERRAL) {
459
            // The qname node contains an out-of-zone referral.
460
            if (task->state == QueryTask::GETANSWER) {
461 462
                RRsetList auth;
                m.clearHeaderFlag(MessageFlag::AA());
463
                if (! refQuery(task->qname, q, task, ds, auth)) {
464 465
                    m.setRcode(Rcode::SERVFAIL());
                    return;
466
                }
467 468 469 470 471
                BOOST_FOREACH (RRsetPtr rrset, auth) {
                    if (rrset->getType() == RRType::DNAME()) {
                        continue;
                    }
                    if (rrset->getType() == RRType::DS() &&
472
                        task->qtype == RRType::DS()) {
473 474 475 476 477 478 479 480
                        m.addRRset(Section::ANSWER(), rrset, q.wantDnssec());
                    } else {
                        m.addRRset(Section::AUTHORITY(), rrset, q.wantDnssec());
                    }
                    getAdditional(q, rrset);
                }
            } 
            continue;
481
        } else if (task->flags & NO_SUCH_ZONE) {
482 483 484
            // No such zone.  If we're chasing cnames or adding additional
            // data, that's okay, but if doing an original query, return
            // REFUSED.
485
            if (task->state == QueryTask::GETANSWER) {
486 487 488 489
                m.setRcode(Rcode::REFUSED());
                return;
            }
            continue;
490
        } else if (task->flags & (NAME_NOT_FOUND|TYPE_NOT_FOUND)) {
491 492 493 494 495 496 497 498 499 500
            // No data found at this qname/qtype.
            // If we were looking for answer data, not additional,
            // and the name was not found, we need to find out whether
            // there are any relevant wildcards.
            bool wildcard_found = false;
            result = tryWildcard(q, task, ds, wildcard_found);
            if (result != SUCCESS) {
                m.setRcode(Rcode::SERVFAIL());
                return;
            }
501

502
            if (wildcard_found) {
503
                continue;
504 505 506 507 508 509 510 511 512
            }

            // If we've reached this point, there is definitely no answer.
            // If we were chasing cnames or adding additional data, that's
            // okay, but if we were doing an original query, reply with the
            // SOA in the authority section.  For NAME_NOT_FOUND, set
            // NXDOMAIN, and also add the previous NSEC to the authority
            // section.  For TYPE_NOT_FOUND, do not set an error rcode,
            // and send the current NSEC in the authority section.
513 514 515
            Name nsecname(task->qname);
            if (task->flags & NAME_NOT_FOUND) {
                ds->findPreviousName(q, task->qname, nsecname, task->zone);
516
            }
517

518 519
            if (task->state == QueryTask::GETANSWER) {
                if (task->flags & NAME_NOT_FOUND) {
520
                    m.setRcode(Rcode::NXDOMAIN());
521 522
                }

523
                RRsetList soa;
524 525 526 527 528
                QueryTask newtask(Name(*zone), task->qclass, RRType::SOA(), 
                                  QueryTask::SIMPLE_QUERY); 
                newtask.zone = task->zone;
                result = doQueryTask(ds, q, newtask, soa);
                if (result != SUCCESS || newtask.flags != 0) {
529
                    m.setRcode(Rcode::SERVFAIL());
530
                    return;
531 532
                }

533 534 535
                m.addRRset(Section::AUTHORITY(), soa[RRType::SOA()],
                           q.wantDnssec());
            }
536

537 538
            if (q.wantDnssec()) {
                RRsetList nsec;
539 540 541 542 543
                QueryTask newtask = QueryTask(nsecname, task->qclass,
                                              RRType::NSEC(), 
                                              QueryTask::SIMPLE_QUERY); 
                newtask.zone = task->zone;
                result = doQueryTask(ds, q, newtask, nsec);
544 545 546 547
                if (result != SUCCESS) {
                    m.setRcode(Rcode::SERVFAIL());
                    return;
                }
548

549
                if (newtask.flags == 0) {
550 551 552 553
                    m.addRRset(Section::AUTHORITY(), nsec[RRType::NSEC()],
                               true);
                }
            }
554

555 556 557 558 559
            return;
        } else {
            // Should never be reached!
            m.setRcode(Rcode::SERVFAIL());
            return;
560 561 562
        }
    }

563 564 565 566 567 568 569
    // We're done, so now copy in the additional data:
    // data first, then signatures.  (If we run out of
    // space, signatures in additional section are
    // optional.)
    BOOST_FOREACH(RRsetPtr rrset, additional) {
        m.addRRset(Section::ADDITIONAL(), rrset, false);
    }
570

571 572 573 574 575 576 577 578
    if (q.wantDnssec()) {
        BOOST_FOREACH(RRsetPtr rrset, additional) {
            if (rrset->getRRsig()) {
                m.addRRset(Section::ADDITIONAL(), rrset->getRRsig(), false);
            }
        }
    }
}
579 580 581

}
}