|
|
- # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
-
- # Copyright (C) 2009-2017 Nominum, Inc.
- #
- # Permission to use, copy, modify, and distribute this software and its
- # documentation for any purpose with or without fee is hereby granted,
- # provided that the above copyright notice and this permission notice
- # appear in all copies.
- #
- # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
- # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
- # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
- # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
- """EDNS Options"""
-
- from __future__ import absolute_import
-
- import math
- import struct
-
- import dns.inet
-
- #: NSID
- NSID = 3
- #: DAU
- DAU = 5
- #: DHU
- DHU = 6
- #: N3U
- N3U = 7
- #: ECS (client-subnet)
- ECS = 8
- #: EXPIRE
- EXPIRE = 9
- #: COOKIE
- COOKIE = 10
- #: KEEPALIVE
- KEEPALIVE = 11
- #: PADDING
- PADDING = 12
- #: CHAIN
- CHAIN = 13
-
- class Option(object):
-
- """Base class for all EDNS option types."""
-
- def __init__(self, otype):
- """Initialize an option.
-
- *otype*, an ``int``, is the option type.
- """
- self.otype = otype
-
- def to_wire(self, file):
- """Convert an option to wire format.
- """
- raise NotImplementedError
-
- @classmethod
- def from_wire(cls, otype, wire, current, olen):
- """Build an EDNS option object from wire format.
-
- *otype*, an ``int``, is the option type.
-
- *wire*, a ``binary``, is the wire-format message.
-
- *current*, an ``int``, is the offset in *wire* of the beginning
- of the rdata.
-
- *olen*, an ``int``, is the length of the wire-format option data
-
- Returns a ``dns.edns.Option``.
- """
-
- raise NotImplementedError
-
- def _cmp(self, other):
- """Compare an EDNS option with another option of the same type.
-
- Returns < 0 if < *other*, 0 if == *other*, and > 0 if > *other*.
- """
- raise NotImplementedError
-
- def __eq__(self, other):
- if not isinstance(other, Option):
- return False
- if self.otype != other.otype:
- return False
- return self._cmp(other) == 0
-
- def __ne__(self, other):
- if not isinstance(other, Option):
- return False
- if self.otype != other.otype:
- return False
- return self._cmp(other) != 0
-
- def __lt__(self, other):
- if not isinstance(other, Option) or \
- self.otype != other.otype:
- return NotImplemented
- return self._cmp(other) < 0
-
- def __le__(self, other):
- if not isinstance(other, Option) or \
- self.otype != other.otype:
- return NotImplemented
- return self._cmp(other) <= 0
-
- def __ge__(self, other):
- if not isinstance(other, Option) or \
- self.otype != other.otype:
- return NotImplemented
- return self._cmp(other) >= 0
-
- def __gt__(self, other):
- if not isinstance(other, Option) or \
- self.otype != other.otype:
- return NotImplemented
- return self._cmp(other) > 0
-
-
- class GenericOption(Option):
-
- """Generic Option Class
-
- This class is used for EDNS option types for which we have no better
- implementation.
- """
-
- def __init__(self, otype, data):
- super(GenericOption, self).__init__(otype)
- self.data = data
-
- def to_wire(self, file):
- file.write(self.data)
-
- def to_text(self):
- return "Generic %d" % self.otype
-
- @classmethod
- def from_wire(cls, otype, wire, current, olen):
- return cls(otype, wire[current: current + olen])
-
- def _cmp(self, other):
- if self.data == other.data:
- return 0
- if self.data > other.data:
- return 1
- return -1
-
-
- class ECSOption(Option):
- """EDNS Client Subnet (ECS, RFC7871)"""
-
- def __init__(self, address, srclen=None, scopelen=0):
- """*address*, a ``text``, is the client address information.
-
- *srclen*, an ``int``, the source prefix length, which is the
- leftmost number of bits of the address to be used for the
- lookup. The default is 24 for IPv4 and 56 for IPv6.
-
- *scopelen*, an ``int``, the scope prefix length. This value
- must be 0 in queries, and should be set in responses.
- """
-
- super(ECSOption, self).__init__(ECS)
- af = dns.inet.af_for_address(address)
-
- if af == dns.inet.AF_INET6:
- self.family = 2
- if srclen is None:
- srclen = 56
- elif af == dns.inet.AF_INET:
- self.family = 1
- if srclen is None:
- srclen = 24
- else:
- raise ValueError('Bad ip family')
-
- self.address = address
- self.srclen = srclen
- self.scopelen = scopelen
-
- addrdata = dns.inet.inet_pton(af, address)
- nbytes = int(math.ceil(srclen/8.0))
-
- # Truncate to srclen and pad to the end of the last octet needed
- # See RFC section 6
- self.addrdata = addrdata[:nbytes]
- nbits = srclen % 8
- if nbits != 0:
- last = struct.pack('B', ord(self.addrdata[-1:]) & (0xff << nbits))
- self.addrdata = self.addrdata[:-1] + last
-
- def to_text(self):
- return "ECS {}/{} scope/{}".format(self.address, self.srclen,
- self.scopelen)
-
- def to_wire(self, file):
- file.write(struct.pack('!H', self.family))
- file.write(struct.pack('!BB', self.srclen, self.scopelen))
- file.write(self.addrdata)
-
- @classmethod
- def from_wire(cls, otype, wire, cur, olen):
- family, src, scope = struct.unpack('!HBB', wire[cur:cur+4])
- cur += 4
-
- addrlen = int(math.ceil(src/8.0))
-
- if family == 1:
- af = dns.inet.AF_INET
- pad = 4 - addrlen
- elif family == 2:
- af = dns.inet.AF_INET6
- pad = 16 - addrlen
- else:
- raise ValueError('unsupported family')
-
- addr = dns.inet.inet_ntop(af, wire[cur:cur+addrlen] + b'\x00' * pad)
- return cls(addr, src, scope)
-
- def _cmp(self, other):
- if self.addrdata == other.addrdata:
- return 0
- if self.addrdata > other.addrdata:
- return 1
- return -1
-
- _type_to_class = {
- ECS: ECSOption
- }
-
- def get_option_class(otype):
- """Return the class for the specified option type.
-
- The GenericOption class is used if a more specific class is not
- known.
- """
-
- cls = _type_to_class.get(otype)
- if cls is None:
- cls = GenericOption
- return cls
-
-
- def option_from_wire(otype, wire, current, olen):
- """Build an EDNS option object from wire format.
-
- *otype*, an ``int``, is the option type.
-
- *wire*, a ``binary``, is the wire-format message.
-
- *current*, an ``int``, is the offset in *wire* of the beginning
- of the rdata.
-
- *olen*, an ``int``, is the length of the wire-format option data
-
- Returns an instance of a subclass of ``dns.edns.Option``.
- """
-
- cls = get_option_class(otype)
- return cls.from_wire(otype, wire, current, olen)
|