1
# Copyright (C) 2001-2007, 2009, 2010 Nominum, Inc.
3
# Permission to use, copy, modify, and distribute this software and its
4
# documentation for any purpose with or without fee is hereby granted,
5
# provided that the above copyright notice and this permission notice
6
# appear in all copies.
8
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
9
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
11
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
14
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
"""DNS rdatasets (an rdataset is a set of rdatas of a given type and class)"""
28
# define SimpleSet here for backwards compatibility
29
SimpleSet = dns.set.Set
31
class DifferingCovers(dns.exception.DNSException):
32
"""Raised if an attempt is made to add a SIG/RRSIG whose covered type
33
is not the same as that of the other rdatas in the rdataset."""
36
class IncompatibleTypes(dns.exception.DNSException):
37
"""Raised if an attempt is made to add rdata of an incompatible type."""
40
class Rdataset(dns.set.Set):
43
@ivar rdclass: The class of the rdataset
45
@ivar rdtype: The type of the rdataset
47
@ivar covers: The covered type. Usually this value is
48
dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
49
dns.rdatatype.RRSIG, then the covers value will be the rdata
50
type the SIG/RRSIG covers. The library treats the SIG and RRSIG
51
types as if they were a family of
52
types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
53
easier to work with than if RRSIGs covering different rdata
54
types were aggregated into a single RRSIG rdataset.
56
@ivar ttl: The DNS TTL (Time To Live) value
60
__slots__ = ['rdclass', 'rdtype', 'covers', 'ttl']
62
def __init__(self, rdclass, rdtype, covers=dns.rdatatype.NONE):
63
"""Create a new rdataset of the specified class and type.
65
@see: the description of the class instance variables for the
66
meaning of I{rdclass} and I{rdtype}"""
68
super(Rdataset, self).__init__()
69
self.rdclass = rdclass
75
obj = super(Rdataset, self)._clone()
76
obj.rdclass = self.rdclass
77
obj.rdtype = self.rdtype
78
obj.covers = self.covers
82
def update_ttl(self, ttl):
83
"""Set the TTL of the rdataset to be the lesser of the set's current
84
TTL or the specified TTL. If the set contains no rdatas, set the TTL
94
def add(self, rd, ttl=None):
95
"""Add the specified rdata to the rdataset.
97
If the optional I{ttl} parameter is supplied, then
98
self.update_ttl(ttl) will be called prior to adding the rdata.
101
@type rd: dns.rdata.Rdata object
106
# If we're adding a signature, do some special handling to
107
# check that the signature covers the same type as the
108
# other rdatas in this rdataset. If this is the first rdata
109
# in the set, initialize the covers field.
111
if self.rdclass != rd.rdclass or self.rdtype != rd.rdtype:
112
raise IncompatibleTypes
115
if self.rdtype == dns.rdatatype.RRSIG or \
116
self.rdtype == dns.rdatatype.SIG:
118
if len(self) == 0 and self.covers == dns.rdatatype.NONE:
120
elif self.covers != covers:
121
raise DifferingCovers
122
if dns.rdatatype.is_singleton(rd.rdtype) and len(self) > 0:
124
super(Rdataset, self).add(rd)
126
def union_update(self, other):
127
self.update_ttl(other.ttl)
128
super(Rdataset, self).union_update(other)
130
def intersection_update(self, other):
131
self.update_ttl(other.ttl)
132
super(Rdataset, self).intersection_update(other)
134
def update(self, other):
135
"""Add all rdatas in other to self.
137
@param other: The rdataset from which to update
138
@type other: dns.rdataset.Rdataset object"""
140
self.update_ttl(other.ttl)
141
super(Rdataset, self).update(other)
147
ctext = '(' + dns.rdatatype.to_text(self.covers) + ')'
148
return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \
149
dns.rdatatype.to_text(self.rdtype) + ctext + ' rdataset>'
152
return self.to_text()
154
def __eq__(self, other):
155
"""Two rdatasets are equal if they have the same class, type, and
156
covers, and contain the same rdata.
159
if not isinstance(other, Rdataset):
161
if self.rdclass != other.rdclass or \
162
self.rdtype != other.rdtype or \
163
self.covers != other.covers:
165
return super(Rdataset, self).__eq__(other)
167
def __ne__(self, other):
168
return not self.__eq__(other)
170
def to_text(self, name=None, origin=None, relativize=True,
171
override_rdclass=None, **kw):
172
"""Convert the rdataset into DNS master file format.
174
@see: L{dns.name.Name.choose_relativity} for more information
175
on how I{origin} and I{relativize} determine the way names
178
Any additional keyword arguments are passed on to the rdata
181
@param name: If name is not None, emit a RRs with I{name} as
183
@type name: dns.name.Name object
184
@param origin: The origin for relative names, or None.
185
@type origin: dns.name.Name object
186
@param relativize: True if names should names be relativized
187
@type relativize: bool"""
189
name = name.choose_relativity(origin, relativize)
195
s = StringIO.StringIO()
196
if not override_rdclass is None:
197
rdclass = override_rdclass
199
rdclass = self.rdclass
202
# Empty rdatasets are used for the question section, and in
203
# some dynamic updates, so we don't need to print out the TTL
204
# (which is meaningless anyway).
206
print >> s, '%s%s%s %s' % (ntext, pad,
207
dns.rdataclass.to_text(rdclass),
208
dns.rdatatype.to_text(self.rdtype))
211
print >> s, '%s%s%d %s %s %s' % \
212
(ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass),
213
dns.rdatatype.to_text(self.rdtype),
214
rd.to_text(origin=origin, relativize=relativize, **kw))
216
# We strip off the final \n for the caller's convenience in printing
218
return s.getvalue()[:-1]
220
def to_wire(self, name, file, compress=None, origin=None,
221
override_rdclass=None, want_shuffle=True):
222
"""Convert the rdataset to wire format.
224
@param name: The owner name of the RRset that will be emitted
225
@type name: dns.name.Name object
226
@param file: The file to which the wire format data will be appended
228
@param compress: The compression table to use; the default is None.
230
@param origin: The origin to be appended to any relative names when
231
they are emitted. The default is None.
232
@returns: the number of records emitted
236
if not override_rdclass is None:
237
rdclass = override_rdclass
240
rdclass = self.rdclass
243
name.to_wire(file, compress, origin)
244
stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)
254
name.to_wire(file, compress, origin)
255
stuff = struct.pack("!HHIH", self.rdtype, rdclass,
259
rd.to_wire(file, compress, origin)
261
assert end - start < 65536
263
stuff = struct.pack("!H", end - start)
268
def match(self, rdclass, rdtype, covers):
269
"""Returns True if this rdataset matches the specified class, type,
271
if self.rdclass == rdclass and \
272
self.rdtype == rdtype and \
273
self.covers == covers:
277
def from_text_list(rdclass, rdtype, ttl, text_rdatas):
278
"""Create an rdataset with the specified class, type, and TTL, and with
279
the specified list of rdatas in text format.
281
@rtype: dns.rdataset.Rdataset object
284
if isinstance(rdclass, (str, unicode)):
285
rdclass = dns.rdataclass.from_text(rdclass)
286
if isinstance(rdtype, (str, unicode)):
287
rdtype = dns.rdatatype.from_text(rdtype)
288
r = Rdataset(rdclass, rdtype)
290
for t in text_rdatas:
291
rd = dns.rdata.from_text(r.rdclass, r.rdtype, t)
295
def from_text(rdclass, rdtype, ttl, *text_rdatas):
296
"""Create an rdataset with the specified class, type, and TTL, and with
297
the specified rdatas in text format.
299
@rtype: dns.rdataset.Rdataset object
302
return from_text_list(rdclass, rdtype, ttl, text_rdatas)
304
def from_rdata_list(ttl, rdatas):
305
"""Create an rdataset with the specified TTL, and with
306
the specified list of rdata objects.
308
@rtype: dns.rdataset.Rdataset object
312
raise ValueError("rdata list must not be empty")
316
r = Rdataset(rd.rdclass, rd.rdtype)
322
def from_rdata(ttl, *rdatas):
323
"""Create an rdataset with the specified TTL, and with
324
the specified rdata objects.
326
@rtype: dns.rdataset.Rdataset object
329
return from_rdata_list(ttl, rdatas)