1# Copyright (C) 2001-2007, 2009, 2010 Nominum, Inc. 2# 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. 7# 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. 15 16"""DNS nodes. A node is a set of rdatasets.""" 17 18import StringIO 19 20import dns.rdataset 21import dns.rdatatype 22import dns.renderer 23 24class Node(object): 25 """A DNS node. 26 27 A node is a set of rdatasets 28 29 @ivar rdatasets: the node's rdatasets 30 @type rdatasets: list of dns.rdataset.Rdataset objects""" 31 32 __slots__ = ['rdatasets'] 33 34 def __init__(self): 35 """Initialize a DNS node. 36 """ 37 38 self.rdatasets = []; 39 40 def to_text(self, name, **kw): 41 """Convert a node to text format. 42 43 Each rdataset at the node is printed. Any keyword arguments 44 to this method are passed on to the rdataset's to_text() method. 45 @param name: the owner name of the rdatasets 46 @type name: dns.name.Name object 47 @rtype: string 48 """ 49 50 s = StringIO.StringIO() 51 for rds in self.rdatasets: 52 print >> s, rds.to_text(name, **kw) 53 return s.getvalue()[:-1] 54 55 def __repr__(self): 56 return '<DNS node ' + str(id(self)) + '>' 57 58 def __eq__(self, other): 59 """Two nodes are equal if they have the same rdatasets. 60 61 @rtype: bool 62 """ 63 # 64 # This is inefficient. Good thing we don't need to do it much. 65 # 66 for rd in self.rdatasets: 67 if rd not in other.rdatasets: 68 return False 69 for rd in other.rdatasets: 70 if rd not in self.rdatasets: 71 return False 72 return True 73 74 def __ne__(self, other): 75 return not self.__eq__(other) 76 77 def __len__(self): 78 return len(self.rdatasets) 79 80 def __iter__(self): 81 return iter(self.rdatasets) 82 83 def find_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE, 84 create=False): 85 """Find an rdataset matching the specified properties in the 86 current node. 87 88 @param rdclass: The class of the rdataset 89 @type rdclass: int 90 @param rdtype: The type of the rdataset 91 @type rdtype: int 92 @param covers: The covered type. Usually this value is 93 dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or 94 dns.rdatatype.RRSIG, then the covers value will be the rdata 95 type the SIG/RRSIG covers. The library treats the SIG and RRSIG 96 types as if they were a family of 97 types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much 98 easier to work with than if RRSIGs covering different rdata 99 types were aggregated into a single RRSIG rdataset. 100 @type covers: int 101 @param create: If True, create the rdataset if it is not found. 102 @type create: bool 103 @raises KeyError: An rdataset of the desired type and class does 104 not exist and I{create} is not True. 105 @rtype: dns.rdataset.Rdataset object 106 """ 107 108 for rds in self.rdatasets: 109 if rds.match(rdclass, rdtype, covers): 110 return rds 111 if not create: 112 raise KeyError 113 rds = dns.rdataset.Rdataset(rdclass, rdtype) 114 self.rdatasets.append(rds) 115 return rds 116 117 def get_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE, 118 create=False): 119 """Get an rdataset matching the specified properties in the 120 current node. 121 122 None is returned if an rdataset of the specified type and 123 class does not exist and I{create} is not True. 124 125 @param rdclass: The class of the rdataset 126 @type rdclass: int 127 @param rdtype: The type of the rdataset 128 @type rdtype: int 129 @param covers: The covered type. 130 @type covers: int 131 @param create: If True, create the rdataset if it is not found. 132 @type create: bool 133 @rtype: dns.rdataset.Rdataset object or None 134 """ 135 136 try: 137 rds = self.find_rdataset(rdclass, rdtype, covers, create) 138 except KeyError: 139 rds = None 140 return rds 141 142 def delete_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE): 143 """Delete the rdataset matching the specified properties in the 144 current node. 145 146 If a matching rdataset does not exist, it is not an error. 147 148 @param rdclass: The class of the rdataset 149 @type rdclass: int 150 @param rdtype: The type of the rdataset 151 @type rdtype: int 152 @param covers: The covered type. 153 @type covers: int 154 """ 155 156 rds = self.get_rdataset(rdclass, rdtype, covers) 157 if not rds is None: 158 self.rdatasets.remove(rds) 159 160 def replace_rdataset(self, replacement): 161 """Replace an rdataset. 162 163 It is not an error if there is no rdataset matching I{replacement}. 164 165 Ownership of the I{replacement} object is transferred to the node; 166 in other words, this method does not store a copy of I{replacement} 167 at the node, it stores I{replacement} itself. 168 """ 169 170 self.delete_rdataset(replacement.rdclass, replacement.rdtype, 171 replacement.covers) 172 self.rdatasets.append(replacement) 173