You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
6.2 KiB

4 years ago
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # Copyright (C) 2001-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 nodes. A node is a set of rdatasets."""
  17. from io import StringIO
  18. import dns.rdataset
  19. import dns.rdatatype
  20. import dns.renderer
  21. class Node(object):
  22. """A Node is a set of rdatasets."""
  23. __slots__ = ['rdatasets']
  24. def __init__(self):
  25. #: the set of rdatsets, represented as a list.
  26. self.rdatasets = []
  27. def to_text(self, name, **kw):
  28. """Convert a node to text format.
  29. Each rdataset at the node is printed. Any keyword arguments
  30. to this method are passed on to the rdataset's to_text() method.
  31. *name*, a ``dns.name.Name`` or ``text``, the owner name of the rdatasets.
  32. Returns a ``text``.
  33. """
  34. s = StringIO()
  35. for rds in self.rdatasets:
  36. if len(rds) > 0:
  37. s.write(rds.to_text(name, **kw))
  38. s.write(u'\n')
  39. return s.getvalue()[:-1]
  40. def __repr__(self):
  41. return '<DNS node ' + str(id(self)) + '>'
  42. def __eq__(self, other):
  43. #
  44. # This is inefficient. Good thing we don't need to do it much.
  45. #
  46. for rd in self.rdatasets:
  47. if rd not in other.rdatasets:
  48. return False
  49. for rd in other.rdatasets:
  50. if rd not in self.rdatasets:
  51. return False
  52. return True
  53. def __ne__(self, other):
  54. return not self.__eq__(other)
  55. def __len__(self):
  56. return len(self.rdatasets)
  57. def __iter__(self):
  58. return iter(self.rdatasets)
  59. def find_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
  60. create=False):
  61. """Find an rdataset matching the specified properties in the
  62. current node.
  63. *rdclass*, an ``int``, the class of the rdataset.
  64. *rdtype*, an ``int``, the type of the rdataset.
  65. *covers*, an ``int``, the covered type. Usually this value is
  66. dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
  67. dns.rdatatype.RRSIG, then the covers value will be the rdata
  68. type the SIG/RRSIG covers. The library treats the SIG and RRSIG
  69. types as if they were a family of
  70. types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
  71. easier to work with than if RRSIGs covering different rdata
  72. types were aggregated into a single RRSIG rdataset.
  73. *create*, a ``bool``. If True, create the rdataset if it is not found.
  74. Raises ``KeyError`` if an rdataset of the desired type and class does
  75. not exist and *create* is not ``True``.
  76. Returns a ``dns.rdataset.Rdataset``.
  77. """
  78. for rds in self.rdatasets:
  79. if rds.match(rdclass, rdtype, covers):
  80. return rds
  81. if not create:
  82. raise KeyError
  83. rds = dns.rdataset.Rdataset(rdclass, rdtype)
  84. self.rdatasets.append(rds)
  85. return rds
  86. def get_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
  87. create=False):
  88. """Get an rdataset matching the specified properties in the
  89. current node.
  90. None is returned if an rdataset of the specified type and
  91. class does not exist and *create* is not ``True``.
  92. *rdclass*, an ``int``, the class of the rdataset.
  93. *rdtype*, an ``int``, the type of the rdataset.
  94. *covers*, an ``int``, the covered type. Usually this value is
  95. dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or
  96. dns.rdatatype.RRSIG, then the covers value will be the rdata
  97. type the SIG/RRSIG covers. The library treats the SIG and RRSIG
  98. types as if they were a family of
  99. types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much
  100. easier to work with than if RRSIGs covering different rdata
  101. types were aggregated into a single RRSIG rdataset.
  102. *create*, a ``bool``. If True, create the rdataset if it is not found.
  103. Returns a ``dns.rdataset.Rdataset`` or ``None``.
  104. """
  105. try:
  106. rds = self.find_rdataset(rdclass, rdtype, covers, create)
  107. except KeyError:
  108. rds = None
  109. return rds
  110. def delete_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE):
  111. """Delete the rdataset matching the specified properties in the
  112. current node.
  113. If a matching rdataset does not exist, it is not an error.
  114. *rdclass*, an ``int``, the class of the rdataset.
  115. *rdtype*, an ``int``, the type of the rdataset.
  116. *covers*, an ``int``, the covered type.
  117. """
  118. rds = self.get_rdataset(rdclass, rdtype, covers)
  119. if rds is not None:
  120. self.rdatasets.remove(rds)
  121. def replace_rdataset(self, replacement):
  122. """Replace an rdataset.
  123. It is not an error if there is no rdataset matching *replacement*.
  124. Ownership of the *replacement* object is transferred to the node;
  125. in other words, this method does not store a copy of *replacement*
  126. at the node, it stores *replacement* itself.
  127. *replacement*, a ``dns.rdataset.Rdataset``.
  128. Raises ``ValueError`` if *replacement* is not a
  129. ``dns.rdataset.Rdataset``.
  130. """
  131. if not isinstance(replacement, dns.rdataset.Rdataset):
  132. raise ValueError('replacement is not an rdataset')
  133. self.delete_rdataset(replacement.rdclass, replacement.rdtype,
  134. replacement.covers)
  135. self.rdatasets.append(replacement)