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 io
19 import random
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
28
29 SimpleSet = dns.set.Set
30
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."""
34 pass
35
37 """Raised if an attempt is made to add rdata of an incompatible type."""
38 pass
39
41 """A DNS rdataset.
42
43 @ivar rdclass: The class of the rdataset
44 @type rdclass: int
45 @ivar rdtype: The type of the rdataset
46 @type rdtype: int
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.
55 @type covers: int
56 @ivar ttl: The DNS TTL (Time To Live) value
57 @type ttl: int
58 """
59
60 __slots__ = ['rdclass', 'rdtype', 'covers', 'ttl']
61
63 """Create a new rdataset of the specified class and type.
64
65 @see: the description of the class instance variables for the
66 meaning of I{rdclass} and I{rdtype}"""
67
68 super(Rdataset, self).__init__()
69 self.rdclass = rdclass
70 self.rdtype = rdtype
71 self.covers = covers
72 self.ttl = 0
73
81
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
85 to the specified TTL.
86 @param ttl: The TTL
87 @type ttl: int"""
88
89 if len(self) == 0:
90 self.ttl = ttl
91 elif ttl < self.ttl:
92 self.ttl = ttl
93
94 - def add(self, rd, ttl=None):
125
129
133
135 """Add all rdatas in other to self.
136
137 @param other: The rdataset from which to update
138 @type other: dns.rdataset.Rdataset object"""
139
140 self.update_ttl(other.ttl)
141 super(Rdataset, self).update(other)
142
150
153
155 """Two rdatasets are equal if they have the same class, type, and
156 covers, and contain the same rdata.
157 @rtype: bool"""
158
159 if not isinstance(other, Rdataset):
160 return False
161 if self.rdclass != other.rdclass or \
162 self.rdtype != other.rdtype or \
163 self.covers != other.covers:
164 return False
165 return super(Rdataset, self).__eq__(other)
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: L{dns.name.Name.choose_relativity} for more information
175 on how I{origin} and I{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 @param name: If name is not None, emit a RRs with I{name} as
182 the owner name.
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"""
188 if not name is None:
189 name = name.choose_relativity(origin, relativize)
190 ntext = str(name)
191 pad = ' '
192 else:
193 ntext = ''
194 pad = ''
195 s = io.StringIO()
196 if not override_rdclass is None:
197 rdclass = override_rdclass
198 else:
199 rdclass = self.rdclass
200 if len(self) == 0:
201
202
203
204
205
206 print('%s%s%s %s' % (ntext, pad,
207 dns.rdataclass.to_text(rdclass),
208 dns.rdatatype.to_text(self.rdtype)),
209 file=s)
210 else:
211 for rd in self:
212 print('%s%s%d %s %s %s' % \
213 (ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass),
214 dns.rdatatype.to_text(self.rdtype),
215 rd.to_text(origin=origin, relativize=relativize, **kw)),
216 file=s)
217
218
219
220 return s.getvalue()[:-1]
221
222 - def to_wire(self, name, file, compress=None, origin=None,
223 override_rdclass=None, want_shuffle=True):
224 """Convert the rdataset to wire format.
225
226 @param name: The owner name of the RRset that will be emitted
227 @type name: dns.name.Name object
228 @param file: The file to which the wire format data will be appended
229 @type file: file
230 @param compress: The compression table to use; the default is None.
231 @type compress: dict
232 @param origin: The origin to be appended to any relative names when
233 they are emitted. The default is None.
234 @returns: the number of records emitted
235 @rtype: int
236 """
237
238 if not override_rdclass is None:
239 rdclass = override_rdclass
240 want_shuffle = False
241 else:
242 rdclass = self.rdclass
243 file.seek(0, 2)
244 if len(self) == 0:
245 name.to_wire(file, compress, origin)
246 stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)
247 file.write(stuff)
248 return 1
249 else:
250 if want_shuffle:
251 l = list(self)
252 random.shuffle(l)
253 else:
254 l = self
255 for rd in l:
256 name.to_wire(file, compress, origin)
257 stuff = struct.pack("!HHIH", self.rdtype, rdclass,
258 self.ttl, 0)
259 file.write(stuff)
260 start = file.tell()
261 rd.to_wire(file, compress, origin)
262 end = file.tell()
263 assert end - start < 65536
264 file.seek(start - 2)
265 stuff = struct.pack("!H", end - start)
266 file.write(stuff)
267 file.seek(0, 2)
268 return len(self)
269
270 - def match(self, rdclass, rdtype, covers):
271 """Returns True if this rdataset matches the specified class, type,
272 and covers"""
273 if self.rdclass == rdclass and \
274 self.rdtype == rdtype and \
275 self.covers == covers:
276 return True
277 return False
278
279 -def from_text_list(rdclass, rdtype, ttl, text_rdatas):
280 """Create an rdataset with the specified class, type, and TTL, and with
281 the specified list of rdatas in text format.
282
283 @rtype: dns.rdataset.Rdataset object
284 """
285
286 if isinstance(rdclass, str):
287 rdclass = dns.rdataclass.from_text(rdclass)
288 if isinstance(rdtype, str):
289 rdtype = dns.rdatatype.from_text(rdtype)
290 r = Rdataset(rdclass, rdtype)
291 r.update_ttl(ttl)
292 for t in text_rdatas:
293 rd = dns.rdata.from_text(r.rdclass, r.rdtype, t)
294 r.add(rd)
295 return r
296
297 -def from_text(rdclass, rdtype, ttl, *text_rdatas):
298 """Create an rdataset with the specified class, type, and TTL, and with
299 the specified rdatas in text format.
300
301 @rtype: dns.rdataset.Rdataset object
302 """
303
304 return from_text_list(rdclass, rdtype, ttl, text_rdatas)
305
307 """Create an rdataset with the specified TTL, and with
308 the specified list of rdata objects.
309
310 @rtype: dns.rdataset.Rdataset object
311 """
312
313 if len(rdatas) == 0:
314 raise ValueError("rdata list must not be empty")
315 r = None
316 for rd in rdatas:
317 if r is None:
318 r = Rdataset(rd.rdclass, rd.rdtype)
319 r.update_ttl(ttl)
320 first_time = False
321 r.add(rd)
322 return r
323
325 """Create an rdataset with the specified TTL, and with
326 the specified rdata objects.
327
328 @rtype: dns.rdataset.Rdataset object
329 """
330
331 return from_rdata_list(ttl, rdatas)
332