edns.py 17 KB

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