database.cc 11.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Copyright (C) 2011  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.

15
16
#include <vector>

17
18
#include <datasrc/database.h>

19
20
#include <exceptions/exceptions.h>
#include <dns/name.h>
21
22
#include <dns/rrttl.h>
#include <dns/rdata.h>
23
24
#include <dns/rdataclass.h>

25
#include <datasrc/data_source.h>
Jelte Jansen's avatar
Jelte Jansen committed
26
#include <datasrc/logger.h>
27

Jelte Jansen's avatar
Jelte Jansen committed
28
29
#include <boost/foreach.hpp>

30
31
using isc::dns::Name;

32
33
34
namespace isc {
namespace datasrc {

35
36
DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseConnection>
                               connection) :
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    connection_(connection)
{
    if (connection_.get() == NULL) {
        isc_throw(isc::InvalidParameter,
                  "No connection provided to DatabaseClient");
    }
}

DataSourceClient::FindResult
DatabaseClient::findZone(const Name& name) const {
    std::pair<bool, int> zone(connection_->getZone(name));
    // Try exact first
    if (zone.first) {
        return (FindResult(result::SUCCESS,
51
                           ZoneFinderPtr(new Finder(connection_,
52
53
54
55
56
57
58
59
                                                    zone.second))));
    }
    // Than super domains
    // Start from 1, as 0 is covered above
    for (size_t i(1); i < name.getLabelCount(); ++i) {
        zone = connection_->getZone(name.split(i));
        if (zone.first) {
            return (FindResult(result::PARTIALMATCH,
60
                               ZoneFinderPtr(new Finder(connection_,
61
62
63
64
65
66
67
                                                        zone.second))));
        }
    }
    // No, really nothing
    return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}

68
69
DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseConnection>
                               connection, int zone_id) :
70
71
72
73
    connection_(connection),
    zone_id_(zone_id)
{ }

Jelte Jansen's avatar
Jelte Jansen committed
74
namespace {
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// Adds the given Rdata to the given RRset
// If the rrset is an empty pointer, a new one is
// created with the given name, class, type and ttl
// The type is checked if the rrset exists, but the
// name is not.
//
// Then adds the given rdata to the set
//
// Raises a DataSourceError if the type does not
// match, or if the given rdata string does not
// parse correctly for the given type and class
void addOrCreate(isc::dns::RRsetPtr& rrset,
                    const isc::dns::Name& name,
                    const isc::dns::RRClass& cls,
                    const isc::dns::RRType& type,
                    const isc::dns::RRTTL& ttl,
                    const std::string& rdata_str)
{
    if (!rrset) {
        rrset.reset(new isc::dns::RRset(name, cls, type, ttl));
    } else {
        if (ttl < rrset->getTTL()) {
            rrset->setTTL(ttl);
Jelte Jansen's avatar
Jelte Jansen committed
98
        }
Jelte Jansen's avatar
Jelte Jansen committed
99
100
        // This is a check to make sure find() is not messing things up
        assert(type == rrset->getType());
Jelte Jansen's avatar
Jelte Jansen committed
101
    }
102
103
104
105
106
107
108
109
110
111
112
    try {
        rrset->addRdata(isc::dns::rdata::createRdata(type, cls, rdata_str));
    } catch (const isc::dns::rdata::InvalidRdataText& ivrt) {
        // at this point, rrset may have been initialised for no reason,
        // and won't be used. But the caller would drop the shared_ptr
        // on such an error anyway, so we don't care.
        isc_throw(DataSourceError,
                    "bad rdata in database for " << name << " "
                    << type << ": " << ivrt.what());
    }
}
Jelte Jansen's avatar
Jelte Jansen committed
113

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// This class keeps a short-lived store of RRSIG records encountered
// during a call to find(). If the backend happens to return signatures
// before the actual data, we might not know which signatures we will need
// So if they may be relevant, we store the in this class.
//
// (If this class seems useful in other places, we might want to move
// it to util. That would also provide an opportunity to add unit tests)
class RRsigStore {
public:
    // Adds the given signature Rdata to the store
    // The signature rdata MUST be of the RRSIG rdata type
    // (the caller must make sure of this).
    // NOTE: if we move this class to a public namespace,
    // we should add a type_covered argument, so as not
    // to have to do this cast here.
    void addSig(isc::dns::rdata::RdataPtr sig_rdata) {
        const isc::dns::RRType& type_covered =
            static_cast<isc::dns::rdata::generic::RRSIG*>(
                sig_rdata.get())->typeCovered();
        sigs[type_covered].push_back(sig_rdata);
    }
Jelte Jansen's avatar
Jelte Jansen committed
135

136
137
138
139
140
141
142
143
144
    // If the store contains signatures for the type of the given
    // rrset, they are appended to it.
    void appendSignatures(isc::dns::RRsetPtr& rrset) const {
        std::map<isc::dns::RRType,
                 std::vector<isc::dns::rdata::RdataPtr> >::const_iterator
            found = sigs.find(rrset->getType());
        if (found != sigs.end()) {
            BOOST_FOREACH(isc::dns::rdata::RdataPtr sig, found->second) {
                rrset->addRRsig(sig);
Jelte Jansen's avatar
Jelte Jansen committed
145
146
            }
        }
147
    }
Jelte Jansen's avatar
Jelte Jansen committed
148

149
150
151
private:
    std::map<isc::dns::RRType, std::vector<isc::dns::rdata::RdataPtr> > sigs;
};
Jelte Jansen's avatar
Jelte Jansen committed
152
153
154
}


155
ZoneFinder::FindResult
156
157
DatabaseClient::Finder::find(const isc::dns::Name& name,
                             const isc::dns::RRType& type,
158
                             isc::dns::RRsetList*,
Jelte Jansen's avatar
Jelte Jansen committed
159
                             const FindOptions)
160
{
Jelte Jansen's avatar
Jelte Jansen committed
161
162
    // This variable is used to determine the difference between
    // NXDOMAIN and NXRRSET
163
164
    bool records_found = false;
    isc::dns::RRsetPtr result_rrset;
Jelte Jansen's avatar
Jelte Jansen committed
165
    ZoneFinder::Result result_status = SUCCESS;
Jelte Jansen's avatar
Jelte Jansen committed
166
    RRsigStore sig_store;
Jelte Jansen's avatar
Jelte Jansen committed
167
    logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS).arg(name).arg(type);
168

169
170
    try {
        connection_->searchForRecords(zone_id_, name.toText());
Jelte Jansen's avatar
Jelte Jansen committed
171

172
173
174
175
176
177
        std::string columns[DatabaseConnection::RecordColumnCount];
        while (connection_->getNextRecord(columns,
                                        DatabaseConnection::RecordColumnCount)) {
            if (!records_found) {
                records_found = true;
            }
178

179
180
181
            try {
                const isc::dns::RRType cur_type(columns[DatabaseConnection::TYPE_COLUMN]);
                const isc::dns::RRTTL cur_ttl(columns[DatabaseConnection::TTL_COLUMN]);
182
183
184
185
186
187
188
189
190
191
                // Ths sigtype column was an optimization for finding the
                // relevant RRSIG RRs for a lookup. Currently this column is
                // not used in this revised datasource implementation. We
                // should either start using it again, or remove it from use
                // completely (i.e. also remove it from the schema and the
                // backend implementation).
                // Note that because we don't use it now, we also won't notice
                // it if the value is wrong (i.e. if the sigtype column
                // contains an rrtype that is different from the actual value
                // of the 'type covered' field in the RRSIG Rdata).
192
                //cur_sigtype(columns[SIGTYPE_COLUMN]);
193

194
                if (cur_type == type) {
Jelte Jansen's avatar
Jelte Jansen committed
195
196
197
198
199
                    if (result_rrset &&
                        result_rrset->getType() == isc::dns::RRType::CNAME()) {
                        isc_throw(DataSourceError, "CNAME found but it is not "
                                  "the only record for " + name.toText());
                    }
200
201
202
                    addOrCreate(result_rrset, name, getClass(), cur_type,
                                cur_ttl, columns[DatabaseConnection::RDATA_COLUMN]);
                } else if (cur_type == isc::dns::RRType::CNAME()) {
203
204
                    // There should be no other data, so result_rrset should
                    // be empty.
205
206
                    if (result_rrset) {
                        isc_throw(DataSourceError, "CNAME found but it is not "
Jelte Jansen's avatar
Jelte Jansen committed
207
                                  "the only record for " + name.toText());
208
209
210
211
212
213
214
215
                    }
                    addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
                                columns[DatabaseConnection::RDATA_COLUMN]);
                    result_status = CNAME;
                } else if (cur_type == isc::dns::RRType::RRSIG()) {
                    // If we get signatures before we get the actual data, we
                    // can't know which ones to keep and which to drop...
                    // So we keep a separate store of any signature that may be
216
217
218
219
                    // relevant and add them to the final RRset when we are
                    // done.
                    // A possible optimization here is to not store them for
                    // types we are certain we don't need
220
221
222
                    sig_store.addSig(isc::dns::rdata::createRdata(cur_type,
                                    getClass(),
                                    columns[DatabaseConnection::RDATA_COLUMN]));
223
                }
224
225
226
227
228
229
230
231
232
            } catch (const isc::dns::InvalidRRType& irt) {
                isc_throw(DataSourceError, "Invalid RRType in database for " <<
                        name << ": " << columns[DatabaseConnection::TYPE_COLUMN]);
            } catch (const isc::dns::InvalidRRTTL& irttl) {
                isc_throw(DataSourceError, "Invalid TTL in database for " <<
                        name << ": " << columns[DatabaseConnection::TTL_COLUMN]);
            } catch (const isc::dns::rdata::InvalidRdataText& ird) {
                isc_throw(DataSourceError, "Invalid rdata in database for " <<
                        name << ": " << columns[DatabaseConnection::RDATA_COLUMN]);
233
            }
234
        }
235
    } catch (const DataSourceError& dse) {
Jelte Jansen's avatar
Jelte Jansen committed
236
        logger.error(DATASRC_DATABASE_FIND_ERROR).arg(dse.what());
237
238
239
240
        // call cleanup and rethrow
        connection_->resetSearch();
        throw;
    } catch (const isc::Exception& isce) {
Jelte Jansen's avatar
Jelte Jansen committed
241
242
        logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ISC_ERROR).arg(isce.what());
        // cleanup, change it to a DataSourceError and rethrow
243
244
245
        connection_->resetSearch();
        isc_throw(DataSourceError, isce.what());
    } catch (const std::exception& ex) {
Jelte Jansen's avatar
Jelte Jansen committed
246
        logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ERROR).arg(ex.what());
247
248
        connection_->resetSearch();
        throw;
249
250
    }

Jelte Jansen's avatar
Jelte Jansen committed
251
252
    if (!result_rrset) {
        if (records_found) {
Jelte Jansen's avatar
Jelte Jansen committed
253
254
            logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FOUND_NXRRSET)
                        .arg(name).arg(getClass()).arg(type);
Jelte Jansen's avatar
Jelte Jansen committed
255
256
            result_status = NXRRSET;
        } else {
Jelte Jansen's avatar
Jelte Jansen committed
257
258
            logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FOUND_NXDOMAIN)
                        .arg(name).arg(getClass()).arg(type);
Jelte Jansen's avatar
Jelte Jansen committed
259
260
            result_status = NXDOMAIN;
        }
261
    } else {
Jelte Jansen's avatar
Jelte Jansen committed
262
        sig_store.appendSignatures(result_rrset);
Jelte Jansen's avatar
Jelte Jansen committed
263
264
        logger.debug(DBG_TRACE_DETAILED,
                    DATASRC_DATABASE_FOUND_RRSET).arg(*result_rrset);
265
    }
Jelte Jansen's avatar
Jelte Jansen committed
266
    return (FindResult(result_status, result_rrset));
267
268
269
270
271
272
273
274
275
276
277
278
279
280
}

Name
DatabaseClient::Finder::getOrigin() const {
    // TODO Implement
    return (Name("."));
}

isc::dns::RRClass
DatabaseClient::Finder::getClass() const {
    // TODO Implement
    return isc::dns::RRClass::IN();
}

281
282
}
}