edns.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # Copyright (C) 2009-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. """EDNS Options"""
  17. import binascii
  18. import math
  19. import socket
  20. import struct
  21. from typing import Any, Dict, Optional, Union
  22. import dns.enum
  23. import dns.inet
  24. import dns.rdata
  25. import dns.wire
  26. class OptionType(dns.enum.IntEnum):
  27. #: NSID
  28. NSID = 3
  29. #: DAU
  30. DAU = 5
  31. #: DHU
  32. DHU = 6
  33. #: N3U
  34. N3U = 7
  35. #: ECS (client-subnet)
  36. ECS = 8
  37. #: EXPIRE
  38. EXPIRE = 9
  39. #: COOKIE
  40. COOKIE = 10
  41. #: KEEPALIVE
  42. KEEPALIVE = 11
  43. #: PADDING
  44. PADDING = 12
  45. #: CHAIN
  46. CHAIN = 13
  47. #: EDE (extended-dns-error)
  48. EDE = 15
  49. #: REPORTCHANNEL
  50. REPORTCHANNEL = 18
  51. @classmethod
  52. def _maximum(cls):
  53. return 65535
  54. class Option:
  55. """Base class for all EDNS option types."""
  56. def __init__(self, otype: Union[OptionType, str]):
  57. """Initialize an option.
  58. *otype*, a ``dns.edns.OptionType``, is the option type.
  59. """
  60. self.otype = OptionType.make(otype)
  61. def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]:
  62. """Convert an option to wire format.
  63. Returns a ``bytes`` or ``None``.
  64. """
  65. raise NotImplementedError # pragma: no cover
  66. def to_text(self) -> str:
  67. raise NotImplementedError # pragma: no cover
  68. @classmethod
  69. def from_wire_parser(cls, otype: OptionType, parser: "dns.wire.Parser") -> "Option":
  70. """Build an EDNS option object from wire format.
  71. *otype*, a ``dns.edns.OptionType``, is the option type.
  72. *parser*, a ``dns.wire.Parser``, the parser, which should be
  73. restructed to the option length.
  74. Returns a ``dns.edns.Option``.
  75. """
  76. raise NotImplementedError # pragma: no cover
  77. def _cmp(self, other):
  78. """Compare an EDNS option with another option of the same type.
  79. Returns < 0 if < *other*, 0 if == *other*, and > 0 if > *other*.
  80. """
  81. wire = self.to_wire()
  82. owire = other.to_wire()
  83. if wire == owire:
  84. return 0
  85. if wire > owire:
  86. return 1
  87. return -1
  88. def __eq__(self, other):
  89. if not isinstance(other, Option):
  90. return False
  91. if self.otype != other.otype:
  92. return False
  93. return self._cmp(other) == 0
  94. def __ne__(self, other):
  95. if not isinstance(other, Option):
  96. return True
  97. if self.otype != other.otype:
  98. return True
  99. return self._cmp(other) != 0
  100. def __lt__(self, other):
  101. if not isinstance(other, Option) or self.otype != other.otype:
  102. return NotImplemented
  103. return self._cmp(other) < 0
  104. def __le__(self, other):
  105. if not isinstance(other, Option) or self.otype != other.otype:
  106. return NotImplemented
  107. return self._cmp(other) <= 0
  108. def __ge__(self, other):
  109. if not isinstance(other, Option) or self.otype != other.otype:
  110. return NotImplemented
  111. return self._cmp(other) >= 0
  112. def __gt__(self, other):
  113. if not isinstance(other, Option) or self.otype != other.otype:
  114. return NotImplemented
  115. return self._cmp(other) > 0
  116. def __str__(self):
  117. return self.to_text()
  118. class GenericOption(Option): # lgtm[py/missing-equals]
  119. """Generic Option Class
  120. This class is used for EDNS option types for which we have no better
  121. implementation.
  122. """
  123. def __init__(self, otype: Union[OptionType, str], data: Union[bytes, str]):
  124. super().__init__(otype)
  125. self.data = dns.rdata.Rdata._as_bytes(data, True)
  126. def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]:
  127. if file:
  128. file.write(self.data)
  129. return None
  130. else:
  131. return self.data
  132. def to_text(self) -> str:
  133. return "Generic %d" % self.otype
  134. @classmethod
  135. def from_wire_parser(
  136. cls, otype: Union[OptionType, str], parser: "dns.wire.Parser"
  137. ) -> Option:
  138. return cls(otype, parser.get_remaining())
  139. class ECSOption(Option): # lgtm[py/missing-equals]
  140. """EDNS Client Subnet (ECS, RFC7871)"""
  141. def __init__(self, address: str, srclen: Optional[int] = None, scopelen: int = 0):
  142. """*address*, a ``str``, is the client address information.
  143. *srclen*, an ``int``, the source prefix length, which is the
  144. leftmost number of bits of the address to be used for the
  145. lookup. The default is 24 for IPv4 and 56 for IPv6.
  146. *scopelen*, an ``int``, the scope prefix length. This value
  147. must be 0 in queries, and should be set in responses.
  148. """
  149. super().__init__(OptionType.ECS)
  150. af = dns.inet.af_for_address(address)
  151. if af == socket.AF_INET6:
  152. self.family = 2
  153. if srclen is None:
  154. srclen = 56
  155. address = dns.rdata.Rdata._as_ipv6_address(address)
  156. srclen = dns.rdata.Rdata._as_int(srclen, 0, 128)
  157. scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 128)
  158. elif af == socket.AF_INET:
  159. self.family = 1
  160. if srclen is None:
  161. srclen = 24
  162. address = dns.rdata.Rdata._as_ipv4_address(address)
  163. srclen = dns.rdata.Rdata._as_int(srclen, 0, 32)
  164. scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 32)
  165. else: # pragma: no cover (this will never happen)
  166. raise ValueError("Bad address family")
  167. assert srclen is not None
  168. self.address = address
  169. self.srclen = srclen
  170. self.scopelen = scopelen
  171. addrdata = dns.inet.inet_pton(af, address)
  172. nbytes = int(math.ceil(srclen / 8.0))
  173. # Truncate to srclen and pad to the end of the last octet needed
  174. # See RFC section 6
  175. self.addrdata = addrdata[:nbytes]
  176. nbits = srclen % 8
  177. if nbits != 0:
  178. last = struct.pack("B", ord(self.addrdata[-1:]) & (0xFF << (8 - nbits)))
  179. self.addrdata = self.addrdata[:-1] + last
  180. def to_text(self) -> str:
  181. return f"ECS {self.address}/{self.srclen} scope/{self.scopelen}"
  182. @staticmethod
  183. def from_text(text: str) -> Option:
  184. """Convert a string into a `dns.edns.ECSOption`
  185. *text*, a `str`, the text form of the option.
  186. Returns a `dns.edns.ECSOption`.
  187. Examples:
  188. >>> import dns.edns
  189. >>>
  190. >>> # basic example
  191. >>> dns.edns.ECSOption.from_text('1.2.3.4/24')
  192. >>>
  193. >>> # also understands scope
  194. >>> dns.edns.ECSOption.from_text('1.2.3.4/24/32')
  195. >>>
  196. >>> # IPv6
  197. >>> dns.edns.ECSOption.from_text('2001:4b98::1/64/64')
  198. >>>
  199. >>> # it understands results from `dns.edns.ECSOption.to_text()`
  200. >>> dns.edns.ECSOption.from_text('ECS 1.2.3.4/24/32')
  201. """
  202. optional_prefix = "ECS"
  203. tokens = text.split()
  204. ecs_text = None
  205. if len(tokens) == 1:
  206. ecs_text = tokens[0]
  207. elif len(tokens) == 2:
  208. if tokens[0] != optional_prefix:
  209. raise ValueError(f'could not parse ECS from "{text}"')
  210. ecs_text = tokens[1]
  211. else:
  212. raise ValueError(f'could not parse ECS from "{text}"')
  213. n_slashes = ecs_text.count("/")
  214. if n_slashes == 1:
  215. address, tsrclen = ecs_text.split("/")
  216. tscope = "0"
  217. elif n_slashes == 2:
  218. address, tsrclen, tscope = ecs_text.split("/")
  219. else:
  220. raise ValueError(f'could not parse ECS from "{text}"')
  221. try:
  222. scope = int(tscope)
  223. except ValueError:
  224. raise ValueError("invalid scope " + f'"{tscope}": scope must be an integer')
  225. try:
  226. srclen = int(tsrclen)
  227. except ValueError:
  228. raise ValueError(
  229. "invalid srclen " + f'"{tsrclen}": srclen must be an integer'
  230. )
  231. return ECSOption(address, srclen, scope)
  232. def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]:
  233. value = (
  234. struct.pack("!HBB", self.family, self.srclen, self.scopelen) + self.addrdata
  235. )
  236. if file:
  237. file.write(value)
  238. return None
  239. else:
  240. return value
  241. @classmethod
  242. def from_wire_parser(
  243. cls, otype: Union[OptionType, str], parser: "dns.wire.Parser"
  244. ) -> Option:
  245. family, src, scope = parser.get_struct("!HBB")
  246. addrlen = int(math.ceil(src / 8.0))
  247. prefix = parser.get_bytes(addrlen)
  248. if family == 1:
  249. pad = 4 - addrlen
  250. addr = dns.ipv4.inet_ntoa(prefix + b"\x00" * pad)
  251. elif family == 2:
  252. pad = 16 - addrlen
  253. addr = dns.ipv6.inet_ntoa(prefix + b"\x00" * pad)
  254. else:
  255. raise ValueError("unsupported family")
  256. return cls(addr, src, scope)
  257. class EDECode(dns.enum.IntEnum):
  258. OTHER = 0
  259. UNSUPPORTED_DNSKEY_ALGORITHM = 1
  260. UNSUPPORTED_DS_DIGEST_TYPE = 2
  261. STALE_ANSWER = 3
  262. FORGED_ANSWER = 4
  263. DNSSEC_INDETERMINATE = 5
  264. DNSSEC_BOGUS = 6
  265. SIGNATURE_EXPIRED = 7
  266. SIGNATURE_NOT_YET_VALID = 8
  267. DNSKEY_MISSING = 9
  268. RRSIGS_MISSING = 10
  269. NO_ZONE_KEY_BIT_SET = 11
  270. NSEC_MISSING = 12
  271. CACHED_ERROR = 13
  272. NOT_READY = 14
  273. BLOCKED = 15
  274. CENSORED = 16
  275. FILTERED = 17
  276. PROHIBITED = 18
  277. STALE_NXDOMAIN_ANSWER = 19
  278. NOT_AUTHORITATIVE = 20
  279. NOT_SUPPORTED = 21
  280. NO_REACHABLE_AUTHORITY = 22
  281. NETWORK_ERROR = 23
  282. INVALID_DATA = 24
  283. @classmethod
  284. def _maximum(cls):
  285. return 65535
  286. class EDEOption(Option): # lgtm[py/missing-equals]
  287. """Extended DNS Error (EDE, RFC8914)"""
  288. _preserve_case = {"DNSKEY", "DS", "DNSSEC", "RRSIGs", "NSEC", "NXDOMAIN"}
  289. def __init__(self, code: Union[EDECode, str], text: Optional[str] = None):
  290. """*code*, a ``dns.edns.EDECode`` or ``str``, the info code of the
  291. extended error.
  292. *text*, a ``str`` or ``None``, specifying additional information about
  293. the error.
  294. """
  295. super().__init__(OptionType.EDE)
  296. self.code = EDECode.make(code)
  297. if text is not None and not isinstance(text, str):
  298. raise ValueError("text must be string or None")
  299. self.text = text
  300. def to_text(self) -> str:
  301. output = f"EDE {self.code}"
  302. if self.code in EDECode:
  303. desc = EDECode.to_text(self.code)
  304. desc = " ".join(
  305. word if word in self._preserve_case else word.title()
  306. for word in desc.split("_")
  307. )
  308. output += f" ({desc})"
  309. if self.text is not None:
  310. output += f": {self.text}"
  311. return output
  312. def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]:
  313. value = struct.pack("!H", self.code)
  314. if self.text is not None:
  315. value += self.text.encode("utf8")
  316. if file:
  317. file.write(value)
  318. return None
  319. else:
  320. return value
  321. @classmethod
  322. def from_wire_parser(
  323. cls, otype: Union[OptionType, str], parser: "dns.wire.Parser"
  324. ) -> Option:
  325. code = EDECode.make(parser.get_uint16())
  326. text = parser.get_remaining()
  327. if text:
  328. if text[-1] == 0: # text MAY be null-terminated
  329. text = text[:-1]
  330. btext = text.decode("utf8")
  331. else:
  332. btext = None
  333. return cls(code, btext)
  334. class NSIDOption(Option):
  335. def __init__(self, nsid: bytes):
  336. super().__init__(OptionType.NSID)
  337. self.nsid = nsid
  338. def to_wire(self, file: Any = None) -> Optional[bytes]:
  339. if file:
  340. file.write(self.nsid)
  341. return None
  342. else:
  343. return self.nsid
  344. def to_text(self) -> str:
  345. if all(c >= 0x20 and c <= 0x7E for c in self.nsid):
  346. # All ASCII printable, so it's probably a string.
  347. value = self.nsid.decode()
  348. else:
  349. value = binascii.hexlify(self.nsid).decode()
  350. return f"NSID {value}"
  351. @classmethod
  352. def from_wire_parser(
  353. cls, otype: Union[OptionType, str], parser: dns.wire.Parser
  354. ) -> Option:
  355. return cls(parser.get_remaining())
  356. class CookieOption(Option):
  357. def __init__(self, client: bytes, server: bytes):
  358. super().__init__(dns.edns.OptionType.COOKIE)
  359. self.client = client
  360. self.server = server
  361. if len(client) != 8:
  362. raise ValueError("client cookie must be 8 bytes")
  363. if len(server) != 0 and (len(server) < 8 or len(server) > 32):
  364. raise ValueError("server cookie must be empty or between 8 and 32 bytes")
  365. def to_wire(self, file: Any = None) -> Optional[bytes]:
  366. if file:
  367. file.write(self.client)
  368. if len(self.server) > 0:
  369. file.write(self.server)
  370. return None
  371. else:
  372. return self.client + self.server
  373. def to_text(self) -> str:
  374. client = binascii.hexlify(self.client).decode()
  375. if len(self.server) > 0:
  376. server = binascii.hexlify(self.server).decode()
  377. else:
  378. server = ""
  379. return f"COOKIE {client}{server}"
  380. @classmethod
  381. def from_wire_parser(
  382. cls, otype: Union[OptionType, str], parser: dns.wire.Parser
  383. ) -> Option:
  384. return cls(parser.get_bytes(8), parser.get_remaining())
  385. class ReportChannelOption(Option):
  386. # RFC 9567
  387. def __init__(self, agent_domain: dns.name.Name):
  388. super().__init__(OptionType.REPORTCHANNEL)
  389. self.agent_domain = agent_domain
  390. def to_wire(self, file: Any = None) -> Optional[bytes]:
  391. return self.agent_domain.to_wire(file)
  392. def to_text(self) -> str:
  393. return "REPORTCHANNEL " + self.agent_domain.to_text()
  394. @classmethod
  395. def from_wire_parser(
  396. cls, otype: Union[OptionType, str], parser: dns.wire.Parser
  397. ) -> Option:
  398. return cls(parser.get_name())
  399. _type_to_class: Dict[OptionType, Any] = {
  400. OptionType.ECS: ECSOption,
  401. OptionType.EDE: EDEOption,
  402. OptionType.NSID: NSIDOption,
  403. OptionType.COOKIE: CookieOption,
  404. OptionType.REPORTCHANNEL: ReportChannelOption,
  405. }
  406. def get_option_class(otype: OptionType) -> Any:
  407. """Return the class for the specified option type.
  408. The GenericOption class is used if a more specific class is not
  409. known.
  410. """
  411. cls = _type_to_class.get(otype)
  412. if cls is None:
  413. cls = GenericOption
  414. return cls
  415. def option_from_wire_parser(
  416. otype: Union[OptionType, str], parser: "dns.wire.Parser"
  417. ) -> Option:
  418. """Build an EDNS option object from wire format.
  419. *otype*, an ``int``, is the option type.
  420. *parser*, a ``dns.wire.Parser``, the parser, which should be
  421. restricted to the option length.
  422. Returns an instance of a subclass of ``dns.edns.Option``.
  423. """
  424. otype = OptionType.make(otype)
  425. cls = get_option_class(otype)
  426. return cls.from_wire_parser(otype, parser)
  427. def option_from_wire(
  428. otype: Union[OptionType, str], wire: bytes, current: int, olen: int
  429. ) -> Option:
  430. """Build an EDNS option object from wire format.
  431. *otype*, an ``int``, is the option type.
  432. *wire*, a ``bytes``, is the wire-format message.
  433. *current*, an ``int``, is the offset in *wire* of the beginning
  434. of the rdata.
  435. *olen*, an ``int``, is the length of the wire-format option data
  436. Returns an instance of a subclass of ``dns.edns.Option``.
  437. """
  438. parser = dns.wire.Parser(wire, current)
  439. with parser.restrict_to(olen):
  440. return option_from_wire_parser(otype, parser)
  441. def register_type(implementation: Any, otype: OptionType) -> None:
  442. """Register the implementation of an option type.
  443. *implementation*, a ``class``, is a subclass of ``dns.edns.Option``.
  444. *otype*, an ``int``, is the option type.
  445. """
  446. _type_to_class[otype] = implementation
  447. ### BEGIN generated OptionType constants
  448. NSID = OptionType.NSID
  449. DAU = OptionType.DAU
  450. DHU = OptionType.DHU
  451. N3U = OptionType.N3U
  452. ECS = OptionType.ECS
  453. EXPIRE = OptionType.EXPIRE
  454. COOKIE = OptionType.COOKIE
  455. KEEPALIVE = OptionType.KEEPALIVE
  456. PADDING = OptionType.PADDING
  457. CHAIN = OptionType.CHAIN
  458. EDE = OptionType.EDE
  459. REPORTCHANNEL = OptionType.REPORTCHANNEL
  460. ### END generated OptionType constants