Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
edns.py517 linesDownload Raw Back to dns
1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license2 3# Copyright (C) 2009-2017 Nominum, Inc.4#5# Permission to use, copy, modify, and distribute this software and its6# documentation for any purpose with or without fee is hereby granted,7# provided that the above copyright notice and this permission notice8# appear in all copies.9#10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.17 18"""EDNS Options"""19 20import binascii21import math22import socket23import struct24from typing import Any, Dict, Optional, Union25 26import dns.enum27import dns.inet28import dns.rdata29import dns.wire30 31 32class OptionType(dns.enum.IntEnum):33    #: NSID34    NSID = 335    #: DAU36    DAU = 537    #: DHU38    DHU = 639    #: N3U40    N3U = 741    #: ECS (client-subnet)42    ECS = 843    #: EXPIRE44    EXPIRE = 945    #: COOKIE46    COOKIE = 1047    #: KEEPALIVE48    KEEPALIVE = 1149    #: PADDING50    PADDING = 1251    #: CHAIN52    CHAIN = 1353    #: EDE (extended-dns-error)54    EDE = 1555 56    @classmethod57    def _maximum(cls):58        return 6553559 60 61class Option:62    """Base class for all EDNS option types."""63 64    def __init__(self, otype: Union[OptionType, str]):65        """Initialize an option.66 67        *otype*, a ``dns.edns.OptionType``, is the option type.68        """69        self.otype = OptionType.make(otype)70 71    def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]:72        """Convert an option to wire format.73 74        Returns a ``bytes`` or ``None``.75 76        """77        raise NotImplementedError  # pragma: no cover78 79    def to_text(self) -> str:80        raise NotImplementedError  # pragma: no cover81 82    @classmethod83    def from_wire_parser(cls, otype: OptionType, parser: "dns.wire.Parser") -> "Option":84        """Build an EDNS option object from wire format.85 86        *otype*, a ``dns.edns.OptionType``, is the option type.87 88        *parser*, a ``dns.wire.Parser``, the parser, which should be89        restructed to the option length.90 91        Returns a ``dns.edns.Option``.92        """93        raise NotImplementedError  # pragma: no cover94 95    def _cmp(self, other):96        """Compare an EDNS option with another option of the same type.97 98        Returns < 0 if < *other*, 0 if == *other*, and > 0 if > *other*.99        """100        wire = self.to_wire()101        owire = other.to_wire()102        if wire == owire:103            return 0104        if wire > owire:105            return 1106        return -1107 108    def __eq__(self, other):109        if not isinstance(other, Option):110            return False111        if self.otype != other.otype:112            return False113        return self._cmp(other) == 0114 115    def __ne__(self, other):116        if not isinstance(other, Option):117            return True118        if self.otype != other.otype:119            return True120        return self._cmp(other) != 0121 122    def __lt__(self, other):123        if not isinstance(other, Option) or self.otype != other.otype:124            return NotImplemented125        return self._cmp(other) < 0126 127    def __le__(self, other):128        if not isinstance(other, Option) or self.otype != other.otype:129            return NotImplemented130        return self._cmp(other) <= 0131 132    def __ge__(self, other):133        if not isinstance(other, Option) or self.otype != other.otype:134            return NotImplemented135        return self._cmp(other) >= 0136 137    def __gt__(self, other):138        if not isinstance(other, Option) or self.otype != other.otype:139            return NotImplemented140        return self._cmp(other) > 0141 142    def __str__(self):143        return self.to_text()144 145 146class GenericOption(Option):  # lgtm[py/missing-equals]147    """Generic Option Class148 149    This class is used for EDNS option types for which we have no better150    implementation.151    """152 153    def __init__(self, otype: Union[OptionType, str], data: Union[bytes, str]):154        super().__init__(otype)155        self.data = dns.rdata.Rdata._as_bytes(data, True)156 157    def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]:158        if file:159            file.write(self.data)160            return None161        else:162            return self.data163 164    def to_text(self) -> str:165        return "Generic %d" % self.otype166 167    @classmethod168    def from_wire_parser(169        cls, otype: Union[OptionType, str], parser: "dns.wire.Parser"170    ) -> Option:171        return cls(otype, parser.get_remaining())172 173 174class ECSOption(Option):  # lgtm[py/missing-equals]175    """EDNS Client Subnet (ECS, RFC7871)"""176 177    def __init__(self, address: str, srclen: Optional[int] = None, scopelen: int = 0):178        """*address*, a ``str``, is the client address information.179 180        *srclen*, an ``int``, the source prefix length, which is the181        leftmost number of bits of the address to be used for the182        lookup.  The default is 24 for IPv4 and 56 for IPv6.183 184        *scopelen*, an ``int``, the scope prefix length.  This value185        must be 0 in queries, and should be set in responses.186        """187 188        super().__init__(OptionType.ECS)189        af = dns.inet.af_for_address(address)190 191        if af == socket.AF_INET6:192            self.family = 2193            if srclen is None:194                srclen = 56195            address = dns.rdata.Rdata._as_ipv6_address(address)196            srclen = dns.rdata.Rdata._as_int(srclen, 0, 128)197            scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 128)198        elif af == socket.AF_INET:199            self.family = 1200            if srclen is None:201                srclen = 24202            address = dns.rdata.Rdata._as_ipv4_address(address)203            srclen = dns.rdata.Rdata._as_int(srclen, 0, 32)204            scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 32)205        else:  # pragma: no cover   (this will never happen)206            raise ValueError("Bad address family")207 208        assert srclen is not None209        self.address = address210        self.srclen = srclen211        self.scopelen = scopelen212 213        addrdata = dns.inet.inet_pton(af, address)214        nbytes = int(math.ceil(srclen / 8.0))215 216        # Truncate to srclen and pad to the end of the last octet needed217        # See RFC section 6218        self.addrdata = addrdata[:nbytes]219        nbits = srclen % 8220        if nbits != 0:221            last = struct.pack("B", ord(self.addrdata[-1:]) & (0xFF << (8 - nbits)))222            self.addrdata = self.addrdata[:-1] + last223 224    def to_text(self) -> str:225        return "ECS {}/{} scope/{}".format(self.address, self.srclen, self.scopelen)226 227    @staticmethod228    def from_text(text: str) -> Option:229        """Convert a string into a `dns.edns.ECSOption`230 231        *text*, a `str`, the text form of the option.232 233        Returns a `dns.edns.ECSOption`.234 235        Examples:236 237        >>> import dns.edns238        >>>239        >>> # basic example240        >>> dns.edns.ECSOption.from_text('1.2.3.4/24')241        >>>242        >>> # also understands scope243        >>> dns.edns.ECSOption.from_text('1.2.3.4/24/32')244        >>>245        >>> # IPv6246        >>> dns.edns.ECSOption.from_text('2001:4b98::1/64/64')247        >>>248        >>> # it understands results from `dns.edns.ECSOption.to_text()`249        >>> dns.edns.ECSOption.from_text('ECS 1.2.3.4/24/32')250        """251        optional_prefix = "ECS"252        tokens = text.split()253        ecs_text = None254        if len(tokens) == 1:255            ecs_text = tokens[0]256        elif len(tokens) == 2:257            if tokens[0] != optional_prefix:258                raise ValueError('could not parse ECS from "{}"'.format(text))259            ecs_text = tokens[1]260        else:261            raise ValueError('could not parse ECS from "{}"'.format(text))262        n_slashes = ecs_text.count("/")263        if n_slashes == 1:264            address, tsrclen = ecs_text.split("/")265            tscope = "0"266        elif n_slashes == 2:267            address, tsrclen, tscope = ecs_text.split("/")268        else:269            raise ValueError('could not parse ECS from "{}"'.format(text))270        try:271            scope = int(tscope)272        except ValueError:273            raise ValueError(274                "invalid scope " + '"{}": scope must be an integer'.format(tscope)275            )276        try:277            srclen = int(tsrclen)278        except ValueError:279            raise ValueError(280                "invalid srclen " + '"{}": srclen must be an integer'.format(tsrclen)281            )282        return ECSOption(address, srclen, scope)283 284    def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]:285        value = (286            struct.pack("!HBB", self.family, self.srclen, self.scopelen) + self.addrdata287        )288        if file:289            file.write(value)290            return None291        else:292            return value293 294    @classmethod295    def from_wire_parser(296        cls, otype: Union[OptionType, str], parser: "dns.wire.Parser"297    ) -> Option:298        family, src, scope = parser.get_struct("!HBB")299        addrlen = int(math.ceil(src / 8.0))300        prefix = parser.get_bytes(addrlen)301        if family == 1:302            pad = 4 - addrlen303            addr = dns.ipv4.inet_ntoa(prefix + b"\x00" * pad)304        elif family == 2:305            pad = 16 - addrlen306            addr = dns.ipv6.inet_ntoa(prefix + b"\x00" * pad)307        else:308            raise ValueError("unsupported family")309 310        return cls(addr, src, scope)311 312 313class EDECode(dns.enum.IntEnum):314    OTHER = 0315    UNSUPPORTED_DNSKEY_ALGORITHM = 1316    UNSUPPORTED_DS_DIGEST_TYPE = 2317    STALE_ANSWER = 3318    FORGED_ANSWER = 4319    DNSSEC_INDETERMINATE = 5320    DNSSEC_BOGUS = 6321    SIGNATURE_EXPIRED = 7322    SIGNATURE_NOT_YET_VALID = 8323    DNSKEY_MISSING = 9324    RRSIGS_MISSING = 10325    NO_ZONE_KEY_BIT_SET = 11326    NSEC_MISSING = 12327    CACHED_ERROR = 13328    NOT_READY = 14329    BLOCKED = 15330    CENSORED = 16331    FILTERED = 17332    PROHIBITED = 18333    STALE_NXDOMAIN_ANSWER = 19334    NOT_AUTHORITATIVE = 20335    NOT_SUPPORTED = 21336    NO_REACHABLE_AUTHORITY = 22337    NETWORK_ERROR = 23338    INVALID_DATA = 24339 340    @classmethod341    def _maximum(cls):342        return 65535343 344 345class EDEOption(Option):  # lgtm[py/missing-equals]346    """Extended DNS Error (EDE, RFC8914)"""347 348    _preserve_case = {"DNSKEY", "DS", "DNSSEC", "RRSIGs", "NSEC", "NXDOMAIN"}349 350    def __init__(self, code: Union[EDECode, str], text: Optional[str] = None):351        """*code*, a ``dns.edns.EDECode`` or ``str``, the info code of the352        extended error.353 354        *text*, a ``str`` or ``None``, specifying additional information about355        the error.356        """357 358        super().__init__(OptionType.EDE)359 360        self.code = EDECode.make(code)361        if text is not None and not isinstance(text, str):362            raise ValueError("text must be string or None")363        self.text = text364 365    def to_text(self) -> str:366        output = f"EDE {self.code}"367        if self.code in EDECode:368            desc = EDECode.to_text(self.code)369            desc = " ".join(370                word if word in self._preserve_case else word.title()371                for word in desc.split("_")372            )373            output += f" ({desc})"374        if self.text is not None:375            output += f": {self.text}"376        return output377 378    def to_wire(self, file: Optional[Any] = None) -> Optional[bytes]:379        value = struct.pack("!H", self.code)380        if self.text is not None:381            value += self.text.encode("utf8")382 383        if file:384            file.write(value)385            return None386        else:387            return value388 389    @classmethod390    def from_wire_parser(391        cls, otype: Union[OptionType, str], parser: "dns.wire.Parser"392    ) -> Option:393        code = EDECode.make(parser.get_uint16())394        text = parser.get_remaining()395 396        if text:397            if text[-1] == 0:  # text MAY be null-terminated398                text = text[:-1]399            btext = text.decode("utf8")400        else:401            btext = None402 403        return cls(code, btext)404 405 406class NSIDOption(Option):407    def __init__(self, nsid: bytes):408        super().__init__(OptionType.NSID)409        self.nsid = nsid410 411    def to_wire(self, file: Any = None) -> Optional[bytes]:412        if file:413            file.write(self.nsid)414            return None415        else:416            return self.nsid417 418    def to_text(self) -> str:419        if all(c >= 0x20 and c <= 0x7E for c in self.nsid):420            # All ASCII printable, so it's probably a string.421            value = self.nsid.decode()422        else:423            value = binascii.hexlify(self.nsid).decode()424        return f"NSID {value}"425 426    @classmethod427    def from_wire_parser(428        cls, otype: Union[OptionType, str], parser: dns.wire.Parser429    ) -> Option:430        return cls(parser.get_remaining())431 432 433_type_to_class: Dict[OptionType, Any] = {434    OptionType.ECS: ECSOption,435    OptionType.EDE: EDEOption,436    OptionType.NSID: NSIDOption,437}438 439 440def get_option_class(otype: OptionType) -> Any:441    """Return the class for the specified option type.442 443    The GenericOption class is used if a more specific class is not444    known.445    """446 447    cls = _type_to_class.get(otype)448    if cls is None:449        cls = GenericOption450    return cls451 452 453def option_from_wire_parser(454    otype: Union[OptionType, str], parser: "dns.wire.Parser"455) -> Option:456    """Build an EDNS option object from wire format.457 458    *otype*, an ``int``, is the option type.459 460    *parser*, a ``dns.wire.Parser``, the parser, which should be461    restricted to the option length.462 463    Returns an instance of a subclass of ``dns.edns.Option``.464    """465    otype = OptionType.make(otype)466    cls = get_option_class(otype)467    return cls.from_wire_parser(otype, parser)468 469 470def option_from_wire(471    otype: Union[OptionType, str], wire: bytes, current: int, olen: int472) -> Option:473    """Build an EDNS option object from wire format.474 475    *otype*, an ``int``, is the option type.476 477    *wire*, a ``bytes``, is the wire-format message.478 479    *current*, an ``int``, is the offset in *wire* of the beginning480    of the rdata.481 482    *olen*, an ``int``, is the length of the wire-format option data483 484    Returns an instance of a subclass of ``dns.edns.Option``.485    """486    parser = dns.wire.Parser(wire, current)487    with parser.restrict_to(olen):488        return option_from_wire_parser(otype, parser)489 490 491def register_type(implementation: Any, otype: OptionType) -> None:492    """Register the implementation of an option type.493 494    *implementation*, a ``class``, is a subclass of ``dns.edns.Option``.495 496    *otype*, an ``int``, is the option type.497    """498 499    _type_to_class[otype] = implementation500 501 502### BEGIN generated OptionType constants503 504NSID = OptionType.NSID505DAU = OptionType.DAU506DHU = OptionType.DHU507N3U = OptionType.N3U508ECS = OptionType.ECS509EXPIRE = OptionType.EXPIRE510COOKIE = OptionType.COOKIE511KEEPALIVE = OptionType.KEEPALIVE512PADDING = OptionType.PADDING513CHAIN = OptionType.CHAIN514EDE = OptionType.EDE515 516### END generated OptionType constants517 
codekingpro/portable-devtools · Team Ai