rrset.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # Copyright (C) 2003-2017 Nominum, Inc.
  3. #
  4. # Permission to use, copy, modify, and distribute this software and its
  5. # documentation for any purpose with or without fee is hereby granted,
  6. # provided that the above copyright notice and this permission notice
  7. # appear in all copies.
  8. #
  9. # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
  10. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
  12. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  15. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. """DNS RRsets (an RRset is a named rdataset)"""
  17. from typing import Any, Collection, Dict, Optional, Union, cast
  18. import dns.name
  19. import dns.rdataclass
  20. import dns.rdataset
  21. import dns.renderer
  22. class RRset(dns.rdataset.Rdataset):
  23. """A DNS RRset (named rdataset).
  24. RRset inherits from Rdataset, and RRsets can be treated as
  25. Rdatasets in most cases. There are, however, a few notable
  26. exceptions. RRsets have different to_wire() and to_text() method
  27. arguments, reflecting the fact that RRsets always have an owner
  28. name.
  29. """
  30. __slots__ = ["name", "deleting"]
  31. def __init__(
  32. self,
  33. name: dns.name.Name,
  34. rdclass: dns.rdataclass.RdataClass,
  35. rdtype: dns.rdatatype.RdataType,
  36. covers: dns.rdatatype.RdataType = dns.rdatatype.NONE,
  37. deleting: Optional[dns.rdataclass.RdataClass] = None,
  38. ):
  39. """Create a new RRset."""
  40. super().__init__(rdclass, rdtype, covers)
  41. self.name = name
  42. self.deleting = deleting
  43. def _clone(self):
  44. obj = super()._clone()
  45. obj.name = self.name
  46. obj.deleting = self.deleting
  47. return obj
  48. def __repr__(self):
  49. if self.covers == 0:
  50. ctext = ""
  51. else:
  52. ctext = "(" + dns.rdatatype.to_text(self.covers) + ")"
  53. if self.deleting is not None:
  54. dtext = " delete=" + dns.rdataclass.to_text(self.deleting)
  55. else:
  56. dtext = ""
  57. return (
  58. "<DNS "
  59. + str(self.name)
  60. + " "
  61. + dns.rdataclass.to_text(self.rdclass)
  62. + " "
  63. + dns.rdatatype.to_text(self.rdtype)
  64. + ctext
  65. + dtext
  66. + " RRset: "
  67. + self._rdata_repr()
  68. + ">"
  69. )
  70. def __str__(self):
  71. return self.to_text()
  72. def __eq__(self, other):
  73. if isinstance(other, RRset):
  74. if self.name != other.name:
  75. return False
  76. elif not isinstance(other, dns.rdataset.Rdataset):
  77. return False
  78. return super().__eq__(other)
  79. def match(self, *args: Any, **kwargs: Any) -> bool: # type: ignore[override]
  80. """Does this rrset match the specified attributes?
  81. Behaves as :py:func:`full_match()` if the first argument is a
  82. ``dns.name.Name``, and as :py:func:`dns.rdataset.Rdataset.match()`
  83. otherwise.
  84. (This behavior fixes a design mistake where the signature of this
  85. method became incompatible with that of its superclass. The fix
  86. makes RRsets matchable as Rdatasets while preserving backwards
  87. compatibility.)
  88. """
  89. if isinstance(args[0], dns.name.Name):
  90. return self.full_match(*args, **kwargs) # type: ignore[arg-type]
  91. else:
  92. return super().match(*args, **kwargs) # type: ignore[arg-type]
  93. def full_match(
  94. self,
  95. name: dns.name.Name,
  96. rdclass: dns.rdataclass.RdataClass,
  97. rdtype: dns.rdatatype.RdataType,
  98. covers: dns.rdatatype.RdataType,
  99. deleting: Optional[dns.rdataclass.RdataClass] = None,
  100. ) -> bool:
  101. """Returns ``True`` if this rrset matches the specified name, class,
  102. type, covers, and deletion state.
  103. """
  104. if not super().match(rdclass, rdtype, covers):
  105. return False
  106. if self.name != name or self.deleting != deleting:
  107. return False
  108. return True
  109. # pylint: disable=arguments-differ
  110. def to_text( # type: ignore[override]
  111. self,
  112. origin: Optional[dns.name.Name] = None,
  113. relativize: bool = True,
  114. **kw: Dict[str, Any],
  115. ) -> str:
  116. """Convert the RRset into DNS zone file format.
  117. See ``dns.name.Name.choose_relativity`` for more information
  118. on how *origin* and *relativize* determine the way names
  119. are emitted.
  120. Any additional keyword arguments are passed on to the rdata
  121. ``to_text()`` method.
  122. *origin*, a ``dns.name.Name`` or ``None``, the origin for relative
  123. names.
  124. *relativize*, a ``bool``. If ``True``, names will be relativized
  125. to *origin*.
  126. """
  127. return super().to_text(
  128. self.name, origin, relativize, self.deleting, **kw # type: ignore
  129. )
  130. def to_wire( # type: ignore[override]
  131. self,
  132. file: Any,
  133. compress: Optional[dns.name.CompressType] = None, # type: ignore
  134. origin: Optional[dns.name.Name] = None,
  135. **kw: Dict[str, Any],
  136. ) -> int:
  137. """Convert the RRset to wire format.
  138. All keyword arguments are passed to ``dns.rdataset.to_wire()``; see
  139. that function for details.
  140. Returns an ``int``, the number of records emitted.
  141. """
  142. return super().to_wire(
  143. self.name, file, compress, origin, self.deleting, **kw # type:ignore
  144. )
  145. # pylint: enable=arguments-differ
  146. def to_rdataset(self) -> dns.rdataset.Rdataset:
  147. """Convert an RRset into an Rdataset.
  148. Returns a ``dns.rdataset.Rdataset``.
  149. """
  150. return dns.rdataset.from_rdata_list(self.ttl, list(self))
  151. def from_text_list(
  152. name: Union[dns.name.Name, str],
  153. ttl: int,
  154. rdclass: Union[dns.rdataclass.RdataClass, str],
  155. rdtype: Union[dns.rdatatype.RdataType, str],
  156. text_rdatas: Collection[str],
  157. idna_codec: Optional[dns.name.IDNACodec] = None,
  158. origin: Optional[dns.name.Name] = None,
  159. relativize: bool = True,
  160. relativize_to: Optional[dns.name.Name] = None,
  161. ) -> RRset:
  162. """Create an RRset with the specified name, TTL, class, and type, and with
  163. the specified list of rdatas in text format.
  164. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
  165. encoder/decoder to use; if ``None``, the default IDNA 2003
  166. encoder/decoder is used.
  167. *origin*, a ``dns.name.Name`` (or ``None``), the
  168. origin to use for relative names.
  169. *relativize*, a ``bool``. If true, name will be relativized.
  170. *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use
  171. when relativizing names. If not set, the *origin* value will be used.
  172. Returns a ``dns.rrset.RRset`` object.
  173. """
  174. if isinstance(name, str):
  175. name = dns.name.from_text(name, None, idna_codec=idna_codec)
  176. rdclass = dns.rdataclass.RdataClass.make(rdclass)
  177. rdtype = dns.rdatatype.RdataType.make(rdtype)
  178. r = RRset(name, rdclass, rdtype)
  179. r.update_ttl(ttl)
  180. for t in text_rdatas:
  181. rd = dns.rdata.from_text(
  182. r.rdclass, r.rdtype, t, origin, relativize, relativize_to, idna_codec
  183. )
  184. r.add(rd)
  185. return r
  186. def from_text(
  187. name: Union[dns.name.Name, str],
  188. ttl: int,
  189. rdclass: Union[dns.rdataclass.RdataClass, str],
  190. rdtype: Union[dns.rdatatype.RdataType, str],
  191. *text_rdatas: Any,
  192. ) -> RRset:
  193. """Create an RRset with the specified name, TTL, class, and type and with
  194. the specified rdatas in text format.
  195. Returns a ``dns.rrset.RRset`` object.
  196. """
  197. return from_text_list(
  198. name, ttl, rdclass, rdtype, cast(Collection[str], text_rdatas)
  199. )
  200. def from_rdata_list(
  201. name: Union[dns.name.Name, str],
  202. ttl: int,
  203. rdatas: Collection[dns.rdata.Rdata],
  204. idna_codec: Optional[dns.name.IDNACodec] = None,
  205. ) -> RRset:
  206. """Create an RRset with the specified name and TTL, and with
  207. the specified list of rdata objects.
  208. *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
  209. encoder/decoder to use; if ``None``, the default IDNA 2003
  210. encoder/decoder is used.
  211. Returns a ``dns.rrset.RRset`` object.
  212. """
  213. if isinstance(name, str):
  214. name = dns.name.from_text(name, None, idna_codec=idna_codec)
  215. if len(rdatas) == 0:
  216. raise ValueError("rdata list must not be empty")
  217. r = None
  218. for rd in rdatas:
  219. if r is None:
  220. r = RRset(name, rd.rdclass, rd.rdtype)
  221. r.update_ttl(ttl)
  222. r.add(rd)
  223. assert r is not None
  224. return r
  225. def from_rdata(name: Union[dns.name.Name, str], ttl: int, *rdatas: Any) -> RRset:
  226. """Create an RRset with the specified name and TTL, and with
  227. the specified rdata objects.
  228. Returns a ``dns.rrset.RRset`` object.
  229. """
  230. return from_rdata_list(name, ttl, cast(Collection[dns.rdata.Rdata], rdatas))