1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """DNS rdatasets (an rdataset is a set of rdatas of a given type and class)"""
19
20 import random
21 from io import StringIO
22 import struct
23
24 import dns.exception
25 import dns.rdatatype
26 import dns.rdataclass
27 import dns.rdata
28 import dns.set
29 from ._compat import string_types
30
31
32 SimpleSet = dns.set.Set
33
34
36 """An attempt was made to add a DNS SIG/RRSIG whose covered type
37 is not the same as that of the other rdatas in the rdataset."""
38
39
41 """An attempt was made to add DNS RR data of an incompatible type."""
42
43
45
46 """A DNS rdataset."""
47
48 __slots__ = ['rdclass', 'rdtype', 'covers', 'ttl']
49
51 """Create a new rdataset of the specified class and type.
52
53 *rdclass*, an ``int``, the rdataclass.
54
55 *rdtype*, an ``int``, the rdatatype.
56
57 *covers*, an ``int``, the covered rdatatype.
58
59 *ttl*, an ``int``, the TTL.
60 """
61
62 super(Rdataset, self).__init__()
63 self.rdclass = rdclass
64 self.rdtype = rdtype
65 self.covers = covers
66 self.ttl = ttl
67
75
77 """Perform TTL minimization.
78
79 Set the TTL of the rdataset to be the lesser of the set's current
80 TTL or the specified TTL. If the set contains no rdatas, set the TTL
81 to the specified TTL.
82
83 *ttl*, an ``int``.
84 """
85
86 if len(self) == 0:
87 self.ttl = ttl
88 elif ttl < self.ttl:
89 self.ttl = ttl
90
91 - def add(self, rd, ttl=None):
92 """Add the specified rdata to the rdataset.
93
94 If the optional *ttl* parameter is supplied, then
95 ``self.update_ttl(ttl)`` will be called prior to adding the rdata.
96
97 *rd*, a ``dns.rdata.Rdata``, the rdata
98
99 *ttl*, an ``int``, the TTL.
100
101 Raises ``dns.rdataset.IncompatibleTypes`` if the type and class
102 do not match the type and class of the rdataset.
103
104 Raises ``dns.rdataset.DifferingCovers`` if the type is a signature
105 type and the covered type does not match that of the rdataset.
106 """
107
108
109
110
111
112
113
114 if self.rdclass != rd.rdclass or self.rdtype != rd.rdtype:
115 raise IncompatibleTypes
116 if ttl is not None:
117 self.update_ttl(ttl)
118 if self.rdtype == dns.rdatatype.RRSIG or \
119 self.rdtype == dns.rdatatype.SIG:
120 covers = rd.covers()
121 if len(self) == 0 and self.covers == dns.rdatatype.NONE:
122 self.covers = covers
123 elif self.covers != covers:
124 raise DifferingCovers
125 if dns.rdatatype.is_singleton(rd.rdtype) and len(self) > 0:
126 self.clear()
127 super(Rdataset, self).add(rd)
128
132
136
138 """Add all rdatas in other to self.
139
140 *other*, a ``dns.rdataset.Rdataset``, the rdataset from which
141 to update.
142 """
143
144 self.update_ttl(other.ttl)
145 super(Rdataset, self).update(other)
146
154
157
166
168 return not self.__eq__(other)
169
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.
173
174 See ``dns.name.Name.choose_relativity`` for more information
175 on how *origin* and *relativize* determine the way names
176 are emitted.
177
178 Any additional keyword arguments are passed on to the rdata
179 ``to_text()`` method.
180
181 *name*, a ``dns.name.Name``. If name is not ``None``, emit RRs with
182 *name* as the owner name.
183
184 *origin*, a ``dns.name.Name`` or ``None``, the origin for relative
185 names.
186
187 *relativize*, a ``bool``. If ``True``, names will be relativized
188 to *origin*.
189 """
190
191 if name is not None:
192 name = name.choose_relativity(origin, relativize)
193 ntext = str(name)
194 pad = ' '
195 else:
196 ntext = ''
197 pad = ''
198 s = StringIO()
199 if override_rdclass is not None:
200 rdclass = override_rdclass
201 else:
202 rdclass = self.rdclass
203 if len(self) == 0:
204
205
206
207
208
209 s.write(u'{}{}{} {}\n'.format(ntext, pad,
210 dns.rdataclass.to_text(rdclass),
211 dns.rdatatype.to_text(self.rdtype)))
212 else:
213 for rd in self:
214 s.write(u'%s%s%d %s %s %s\n' %
215 (ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass),
216 dns.rdatatype.to_text(self.rdtype),
217 rd.to_text(origin=origin, relativize=relativize,
218 **kw)))
219
220
221
222 return s.getvalue()[:-1]
223
224 - def to_wire(self, name, file, compress=None, origin=None,
225 override_rdclass=None, want_shuffle=True):
226 """Convert the rdataset to wire format.
227
228 *name*, a ``dns.name.Name`` is the owner name to use.
229
230 *file* is the file where the name is emitted (typically a
231 BytesIO file).
232
233 *compress*, a ``dict``, is the compression table to use. If
234 ``None`` (the default), names will not be compressed.
235
236 *origin* is a ``dns.name.Name`` or ``None``. If the name is
237 relative and origin is not ``None``, then *origin* will be appended
238 to it.
239
240 *override_rdclass*, an ``int``, is used as the class instead of the
241 class of the rdataset. This is useful when rendering rdatasets
242 associated with dynamic updates.
243
244 *want_shuffle*, a ``bool``. If ``True``, then the order of the
245 Rdatas within the Rdataset will be shuffled before rendering.
246
247 Returns an ``int``, the number of records emitted.
248 """
249
250 if override_rdclass is not None:
251 rdclass = override_rdclass
252 want_shuffle = False
253 else:
254 rdclass = self.rdclass
255 file.seek(0, 2)
256 if len(self) == 0:
257 name.to_wire(file, compress, origin)
258 stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)
259 file.write(stuff)
260 return 1
261 else:
262 if want_shuffle:
263 l = list(self)
264 random.shuffle(l)
265 else:
266 l = self
267 for rd in l:
268 name.to_wire(file, compress, origin)
269 stuff = struct.pack("!HHIH", self.rdtype, rdclass,
270 self.ttl, 0)
271 file.write(stuff)
272 start = file.tell()
273 rd.to_wire(file, compress, origin)
274 end = file.tell()
275 assert end - start < 65536
276 file.seek(start - 2)
277 stuff = struct.pack("!H", end - start)
278 file.write(stuff)
279 file.seek(0, 2)
280 return len(self)
281
282 - def match(self, rdclass, rdtype, covers):
283 """Returns ``True`` if this rdataset matches the specified class,
284 type, and covers.
285 """
286 if self.rdclass == rdclass and \
287 self.rdtype == rdtype and \
288 self.covers == covers:
289 return True
290 return False
291
292
293 -def from_text_list(rdclass, rdtype, ttl, text_rdatas):
294 """Create an rdataset with the specified class, type, and TTL, and with
295 the specified list of rdatas in text format.
296
297 Returns a ``dns.rdataset.Rdataset`` object.
298 """
299
300 if isinstance(rdclass, string_types):
301 rdclass = dns.rdataclass.from_text(rdclass)
302 if isinstance(rdtype, string_types):
303 rdtype = dns.rdatatype.from_text(rdtype)
304 r = Rdataset(rdclass, rdtype)
305 r.update_ttl(ttl)
306 for t in text_rdatas:
307 rd = dns.rdata.from_text(r.rdclass, r.rdtype, t)
308 r.add(rd)
309 return r
310
311
312 -def from_text(rdclass, rdtype, ttl, *text_rdatas):
313 """Create an rdataset with the specified class, type, and TTL, and with
314 the specified rdatas in text format.
315
316 Returns a ``dns.rdataset.Rdataset`` object.
317 """
318
319 return from_text_list(rdclass, rdtype, ttl, text_rdatas)
320
321
323 """Create an rdataset with the specified TTL, and with
324 the specified list of rdata objects.
325
326 Returns a ``dns.rdataset.Rdataset`` object.
327 """
328
329 if len(rdatas) == 0:
330 raise ValueError("rdata list must not be empty")
331 r = None
332 for rd in rdatas:
333 if r is None:
334 r = Rdataset(rd.rdclass, rd.rdtype)
335 r.update_ttl(ttl)
336 r.add(rd)
337 return r
338
339
341 """Create an rdataset with the specified TTL, and with
342 the specified rdata objects.
343
344 Returns a ``dns.rdataset.Rdataset`` object.
345 """
346
347 return from_rdata_list(ttl, rdatas)
348