1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """DNS rdatasets (an rdataset is a set of rdatas of a given type and class)"""
17
18 import random
19 from io import StringIO
20 import struct
21
22 import dns.exception
23 import dns.rdatatype
24 import dns.rdataclass
25 import dns.rdata
26 import dns.set
27 from ._compat import string_types
28
29
30 SimpleSet = dns.set.Set
31
32
34
35 """An attempt was made to add a DNS SIG/RRSIG whose covered type
36 is not the same as that of the other rdatas in the rdataset."""
37
38
40
41 """An attempt was made to add DNS RR data of an incompatible type."""
42
43
45
46 """A DNS rdataset.
47
48 @ivar rdclass: The class of the rdataset
49 @type rdclass: int
50 @ivar rdtype: The type of the rdataset
51 @type rdtype: int
52 @ivar covers: The covered type. Usually this value is
53 dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
54 dns.rdatatype.RRSIG, then the covers value will be the rdata
55 type the SIG/RRSIG covers. The library treats the SIG and RRSIG
56 types as if they were a family of
57 types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
58 easier to work with than if RRSIGs covering different rdata
59 types were aggregated into a single RRSIG rdataset.
60 @type covers: int
61 @ivar ttl: The DNS TTL (Time To Live) value
62 @type ttl: int
63 """
64
65 __slots__ = ['rdclass', 'rdtype', 'covers', 'ttl']
66
68 """Create a new rdataset of the specified class and type.
69
70 @see: the description of the class instance variables for the
71 meaning of I{rdclass} and I{rdtype}"""
72
73 super(Rdataset, self).__init__()
74 self.rdclass = rdclass
75 self.rdtype = rdtype
76 self.covers = covers
77 self.ttl = 0
78
86
88 """Set the TTL of the rdataset to be the lesser of the set's current
89 TTL or the specified TTL. If the set contains no rdatas, set the TTL
90 to the specified TTL.
91 @param ttl: The TTL
92 @type ttl: int"""
93
94 if len(self) == 0:
95 self.ttl = ttl
96 elif ttl < self.ttl:
97 self.ttl = ttl
98
99 - def add(self, rd, ttl=None):
130
134
138
140 """Add all rdatas in other to self.
141
142 @param other: The rdataset from which to update
143 @type other: dns.rdataset.Rdataset object"""
144
145 self.update_ttl(other.ttl)
146 super(Rdataset, self).update(other)
147
155
158
160 """Two rdatasets are equal if they have the same class, type, and
161 covers, and contain the same rdata.
162 @rtype: bool"""
163
164 if not isinstance(other, Rdataset):
165 return False
166 if self.rdclass != other.rdclass or \
167 self.rdtype != other.rdtype or \
168 self.covers != other.covers:
169 return False
170 return super(Rdataset, self).__eq__(other)
171
173 return not self.__eq__(other)
174
175 - def to_text(self, name=None, origin=None, relativize=True,
176 override_rdclass=None, **kw):
177 """Convert the rdataset into DNS master file format.
178
179 @see: L{dns.name.Name.choose_relativity} for more information
180 on how I{origin} and I{relativize} determine the way names
181 are emitted.
182
183 Any additional keyword arguments are passed on to the rdata
184 to_text() method.
185
186 @param name: If name is not None, emit a RRs with I{name} as
187 the owner name.
188 @type name: dns.name.Name object
189 @param origin: The origin for relative names, or None.
190 @type origin: dns.name.Name object
191 @param relativize: True if names should names be relativized
192 @type relativize: bool"""
193 if name is not None:
194 name = name.choose_relativity(origin, relativize)
195 ntext = str(name)
196 pad = ' '
197 else:
198 ntext = ''
199 pad = ''
200 s = StringIO()
201 if override_rdclass is not None:
202 rdclass = override_rdclass
203 else:
204 rdclass = self.rdclass
205 if len(self) == 0:
206
207
208
209
210
211 s.write(u'%s%s%s %s\n' % (ntext, pad,
212 dns.rdataclass.to_text(rdclass),
213 dns.rdatatype.to_text(self.rdtype)))
214 else:
215 for rd in self:
216 s.write(u'%s%s%d %s %s %s\n' %
217 (ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass),
218 dns.rdatatype.to_text(self.rdtype),
219 rd.to_text(origin=origin, relativize=relativize,
220 **kw)))
221
222
223
224 return s.getvalue()[:-1]
225
226 - def to_wire(self, name, file, compress=None, origin=None,
227 override_rdclass=None, want_shuffle=True):
228 """Convert the rdataset to wire format.
229
230 @param name: The owner name of the RRset that will be emitted
231 @type name: dns.name.Name object
232 @param file: The file to which the wire format data will be appended
233 @type file: file
234 @param compress: The compression table to use; the default is None.
235 @type compress: dict
236 @param origin: The origin to be appended to any relative names when
237 they are emitted. The default is None.
238 @returns: the number of records emitted
239 @rtype: int
240 """
241
242 if override_rdclass is not None:
243 rdclass = override_rdclass
244 want_shuffle = False
245 else:
246 rdclass = self.rdclass
247 file.seek(0, 2)
248 if len(self) == 0:
249 name.to_wire(file, compress, origin)
250 stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)
251 file.write(stuff)
252 return 1
253 else:
254 if want_shuffle:
255 l = list(self)
256 random.shuffle(l)
257 else:
258 l = self
259 for rd in l:
260 name.to_wire(file, compress, origin)
261 stuff = struct.pack("!HHIH", self.rdtype, rdclass,
262 self.ttl, 0)
263 file.write(stuff)
264 start = file.tell()
265 rd.to_wire(file, compress, origin)
266 end = file.tell()
267 assert end - start < 65536
268 file.seek(start - 2)
269 stuff = struct.pack("!H", end - start)
270 file.write(stuff)
271 file.seek(0, 2)
272 return len(self)
273
274 - def match(self, rdclass, rdtype, covers):
275 """Returns True if this rdataset matches the specified class, type,
276 and covers"""
277 if self.rdclass == rdclass and \
278 self.rdtype == rdtype and \
279 self.covers == covers:
280 return True
281 return False
282
283
284 -def from_text_list(rdclass, rdtype, ttl, text_rdatas):
285 """Create an rdataset with the specified class, type, and TTL, and with
286 the specified list of rdatas in text format.
287
288 @rtype: dns.rdataset.Rdataset object
289 """
290
291 if isinstance(rdclass, string_types):
292 rdclass = dns.rdataclass.from_text(rdclass)
293 if isinstance(rdtype, string_types):
294 rdtype = dns.rdatatype.from_text(rdtype)
295 r = Rdataset(rdclass, rdtype)
296 r.update_ttl(ttl)
297 for t in text_rdatas:
298 rd = dns.rdata.from_text(r.rdclass, r.rdtype, t)
299 r.add(rd)
300 return r
301
302
303 -def from_text(rdclass, rdtype, ttl, *text_rdatas):
304 """Create an rdataset with the specified class, type, and TTL, and with
305 the specified rdatas in text format.
306
307 @rtype: dns.rdataset.Rdataset object
308 """
309
310 return from_text_list(rdclass, rdtype, ttl, text_rdatas)
311
312
314 """Create an rdataset with the specified TTL, and with
315 the specified list of rdata objects.
316
317 @rtype: dns.rdataset.Rdataset object
318 """
319
320 if len(rdatas) == 0:
321 raise ValueError("rdata list must not be empty")
322 r = None
323 for rd in rdatas:
324 if r is None:
325 r = Rdataset(rd.rdclass, rd.rdtype)
326 r.update_ttl(ttl)
327 r.add(rd)
328 return r
329
330
332 """Create an rdataset with the specified TTL, and with
333 the specified rdata objects.
334
335 @rtype: dns.rdataset.Rdataset object
336 """
337
338 return from_rdata_list(ttl, rdatas)
339