mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 05:01:14 -07:00
Bump dnspython from 2.0.0 to 2.2.0 (#1618)
* Bump dnspython from 2.0.0 to 2.2.0 Bumps [dnspython]() from 2.0.0 to 2.2.0. --- updated-dependencies: - dependency-name: dnspython dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Update dnspython==2.2.0 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci]
This commit is contained in:
parent
515a5d42d3
commit
3c93b5600f
143 changed files with 7498 additions and 2054 deletions
|
@ -27,6 +27,7 @@ __all__ = [
|
|||
'entropy',
|
||||
'exception',
|
||||
'flags',
|
||||
'immutable',
|
||||
'inet',
|
||||
'ipv4',
|
||||
'ipv6',
|
||||
|
@ -48,14 +49,18 @@ __all__ = [
|
|||
'serial',
|
||||
'set',
|
||||
'tokenizer',
|
||||
'transaction',
|
||||
'tsig',
|
||||
'tsigkeyring',
|
||||
'ttl',
|
||||
'rdtypes',
|
||||
'update',
|
||||
'version',
|
||||
'versioned',
|
||||
'wire',
|
||||
'xfr',
|
||||
'zone',
|
||||
'zonefile',
|
||||
]
|
||||
|
||||
from dns.version import version as __version__ # noqa
|
||||
|
|
|
@ -27,6 +27,12 @@ class Socket: # pragma: no cover
|
|||
async def close(self):
|
||||
pass
|
||||
|
||||
async def getpeername(self):
|
||||
raise NotImplementedError
|
||||
|
||||
async def getsockname(self):
|
||||
raise NotImplementedError
|
||||
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
|
@ -36,18 +42,18 @@ class Socket: # pragma: no cover
|
|||
|
||||
class DatagramSocket(Socket): # pragma: no cover
|
||||
async def sendto(self, what, destination, timeout):
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
async def recvfrom(self, size, timeout):
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class StreamSocket(Socket): # pragma: no cover
|
||||
async def sendall(self, what, destination, timeout):
|
||||
pass
|
||||
async def sendall(self, what, timeout):
|
||||
raise NotImplementedError
|
||||
|
||||
async def recv(self, size, timeout):
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Backend: # pragma: no cover
|
||||
|
@ -58,3 +64,6 @@ class Backend: # pragma: no cover
|
|||
source=None, destination=None, timeout=None,
|
||||
ssl_context=None, server_hostname=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def datagram_connection_required(self):
|
||||
return False
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
|
||||
import socket
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
import dns._asyncbackend
|
||||
import dns.exception
|
||||
|
||||
|
||||
_is_win32 = sys.platform == 'win32'
|
||||
|
||||
def _get_running_loop():
|
||||
try:
|
||||
return asyncio.get_running_loop()
|
||||
|
@ -25,16 +28,16 @@ class _DatagramProtocol:
|
|||
self.transport = transport
|
||||
|
||||
def datagram_received(self, data, addr):
|
||||
if self.recvfrom:
|
||||
if self.recvfrom and not self.recvfrom.done():
|
||||
self.recvfrom.set_result((data, addr))
|
||||
self.recvfrom = None
|
||||
|
||||
def error_received(self, exc): # pragma: no cover
|
||||
if self.recvfrom:
|
||||
if self.recvfrom and not self.recvfrom.done():
|
||||
self.recvfrom.set_exception(exc)
|
||||
|
||||
def connection_lost(self, exc):
|
||||
if self.recvfrom:
|
||||
if self.recvfrom and not self.recvfrom.done():
|
||||
self.recvfrom.set_exception(exc)
|
||||
|
||||
def close(self):
|
||||
|
@ -79,21 +82,19 @@ class DatagramSocket(dns._asyncbackend.DatagramSocket):
|
|||
return self.transport.get_extra_info('sockname')
|
||||
|
||||
|
||||
class StreamSocket(dns._asyncbackend.DatagramSocket):
|
||||
class StreamSocket(dns._asyncbackend.StreamSocket):
|
||||
def __init__(self, af, reader, writer):
|
||||
self.family = af
|
||||
self.reader = reader
|
||||
self.writer = writer
|
||||
|
||||
async def sendall(self, what, timeout):
|
||||
self.writer.write(what),
|
||||
self.writer.write(what)
|
||||
return await _maybe_wait_for(self.writer.drain(), timeout)
|
||||
raise dns.exception.Timeout(timeout=timeout)
|
||||
|
||||
async def recv(self, count, timeout):
|
||||
return await _maybe_wait_for(self.reader.read(count),
|
||||
async def recv(self, size, timeout):
|
||||
return await _maybe_wait_for(self.reader.read(size),
|
||||
timeout)
|
||||
raise dns.exception.Timeout(timeout=timeout)
|
||||
|
||||
async def close(self):
|
||||
self.writer.close()
|
||||
|
@ -116,11 +117,16 @@ class Backend(dns._asyncbackend.Backend):
|
|||
async def make_socket(self, af, socktype, proto=0,
|
||||
source=None, destination=None, timeout=None,
|
||||
ssl_context=None, server_hostname=None):
|
||||
if destination is None and socktype == socket.SOCK_DGRAM and \
|
||||
_is_win32:
|
||||
raise NotImplementedError('destinationless datagram sockets '
|
||||
'are not supported by asyncio '
|
||||
'on Windows')
|
||||
loop = _get_running_loop()
|
||||
if socktype == socket.SOCK_DGRAM:
|
||||
transport, protocol = await loop.create_datagram_endpoint(
|
||||
_DatagramProtocol, source, family=af,
|
||||
proto=proto)
|
||||
proto=proto, remote_addr=destination)
|
||||
return DatagramSocket(af, transport, protocol)
|
||||
elif socktype == socket.SOCK_STREAM:
|
||||
(r, w) = await _maybe_wait_for(
|
||||
|
@ -138,3 +144,6 @@ class Backend(dns._asyncbackend.Backend):
|
|||
|
||||
async def sleep(self, interval):
|
||||
await asyncio.sleep(interval)
|
||||
|
||||
def datagram_connection_required(self):
|
||||
return _is_win32
|
||||
|
|
|
@ -21,6 +21,8 @@ def _maybe_timeout(timeout):
|
|||
# for brevity
|
||||
_lltuple = dns.inet.low_level_address_tuple
|
||||
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
class DatagramSocket(dns._asyncbackend.DatagramSocket):
|
||||
def __init__(self, socket):
|
||||
|
@ -47,7 +49,7 @@ class DatagramSocket(dns._asyncbackend.DatagramSocket):
|
|||
return self.socket.getsockname()
|
||||
|
||||
|
||||
class StreamSocket(dns._asyncbackend.DatagramSocket):
|
||||
class StreamSocket(dns._asyncbackend.StreamSocket):
|
||||
def __init__(self, socket):
|
||||
self.socket = socket
|
||||
self.family = socket.family
|
||||
|
|
84
lib/dns/_immutable_attr.py
Normal file
84
lib/dns/_immutable_attr.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# This implementation of the immutable decorator is for python 3.6,
|
||||
# which doesn't have Context Variables. This implementation is somewhat
|
||||
# costly for classes with slots, as it adds a __dict__ to them.
|
||||
|
||||
|
||||
import inspect
|
||||
|
||||
|
||||
class _Immutable:
|
||||
"""Immutable mixin class"""
|
||||
|
||||
# Note we MUST NOT have __slots__ as that causes
|
||||
#
|
||||
# TypeError: multiple bases have instance lay-out conflict
|
||||
#
|
||||
# when we get mixed in with another class with slots. When we
|
||||
# get mixed into something with slots, it effectively adds __dict__ to
|
||||
# the slots of the other class, which allows attribute setting to work,
|
||||
# albeit at the cost of the dictionary.
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if not hasattr(self, '_immutable_init') or \
|
||||
self._immutable_init is not self:
|
||||
raise TypeError("object doesn't support attribute assignment")
|
||||
else:
|
||||
super().__setattr__(name, value)
|
||||
|
||||
def __delattr__(self, name):
|
||||
if not hasattr(self, '_immutable_init') or \
|
||||
self._immutable_init is not self:
|
||||
raise TypeError("object doesn't support attribute assignment")
|
||||
else:
|
||||
super().__delattr__(name)
|
||||
|
||||
|
||||
def _immutable_init(f):
|
||||
def nf(*args, **kwargs):
|
||||
try:
|
||||
# Are we already initializing an immutable class?
|
||||
previous = args[0]._immutable_init
|
||||
except AttributeError:
|
||||
# We are the first!
|
||||
previous = None
|
||||
object.__setattr__(args[0], '_immutable_init', args[0])
|
||||
try:
|
||||
# call the actual __init__
|
||||
f(*args, **kwargs)
|
||||
finally:
|
||||
if not previous:
|
||||
# If we started the initialzation, establish immutability
|
||||
# by removing the attribute that allows mutation
|
||||
object.__delattr__(args[0], '_immutable_init')
|
||||
nf.__signature__ = inspect.signature(f)
|
||||
return nf
|
||||
|
||||
|
||||
def immutable(cls):
|
||||
if _Immutable in cls.__mro__:
|
||||
# Some ancestor already has the mixin, so just make sure we keep
|
||||
# following the __init__ protocol.
|
||||
cls.__init__ = _immutable_init(cls.__init__)
|
||||
if hasattr(cls, '__setstate__'):
|
||||
cls.__setstate__ = _immutable_init(cls.__setstate__)
|
||||
ncls = cls
|
||||
else:
|
||||
# Mixin the Immutable class and follow the __init__ protocol.
|
||||
class ncls(_Immutable, cls):
|
||||
|
||||
@_immutable_init
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if hasattr(cls, '__setstate__'):
|
||||
@_immutable_init
|
||||
def __setstate__(self, *args, **kwargs):
|
||||
super().__setstate__(*args, **kwargs)
|
||||
|
||||
# make ncls have the same name and module as cls
|
||||
ncls.__name__ = cls.__name__
|
||||
ncls.__qualname__ = cls.__qualname__
|
||||
ncls.__module__ = cls.__module__
|
||||
return ncls
|
75
lib/dns/_immutable_ctx.py
Normal file
75
lib/dns/_immutable_ctx.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# This implementation of the immutable decorator requires python >=
|
||||
# 3.7, and is significantly more storage efficient when making classes
|
||||
# with slots immutable. It's also faster.
|
||||
|
||||
import contextvars
|
||||
import inspect
|
||||
|
||||
|
||||
_in__init__ = contextvars.ContextVar('_immutable_in__init__', default=False)
|
||||
|
||||
|
||||
class _Immutable:
|
||||
"""Immutable mixin class"""
|
||||
|
||||
# We set slots to the empty list to say "we don't have any attributes".
|
||||
# We do this so that if we're mixed in with a class with __slots__, we
|
||||
# don't cause a __dict__ to be added which would waste space.
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if _in__init__.get() is not self:
|
||||
raise TypeError("object doesn't support attribute assignment")
|
||||
else:
|
||||
super().__setattr__(name, value)
|
||||
|
||||
def __delattr__(self, name):
|
||||
if _in__init__.get() is not self:
|
||||
raise TypeError("object doesn't support attribute assignment")
|
||||
else:
|
||||
super().__delattr__(name)
|
||||
|
||||
|
||||
def _immutable_init(f):
|
||||
def nf(*args, **kwargs):
|
||||
previous = _in__init__.set(args[0])
|
||||
try:
|
||||
# call the actual __init__
|
||||
f(*args, **kwargs)
|
||||
finally:
|
||||
_in__init__.reset(previous)
|
||||
nf.__signature__ = inspect.signature(f)
|
||||
return nf
|
||||
|
||||
|
||||
def immutable(cls):
|
||||
if _Immutable in cls.__mro__:
|
||||
# Some ancestor already has the mixin, so just make sure we keep
|
||||
# following the __init__ protocol.
|
||||
cls.__init__ = _immutable_init(cls.__init__)
|
||||
if hasattr(cls, '__setstate__'):
|
||||
cls.__setstate__ = _immutable_init(cls.__setstate__)
|
||||
ncls = cls
|
||||
else:
|
||||
# Mixin the Immutable class and follow the __init__ protocol.
|
||||
class ncls(_Immutable, cls):
|
||||
# We have to do the __slots__ declaration here too!
|
||||
__slots__ = ()
|
||||
|
||||
@_immutable_init
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if hasattr(cls, '__setstate__'):
|
||||
@_immutable_init
|
||||
def __setstate__(self, *args, **kwargs):
|
||||
super().__setstate__(*args, **kwargs)
|
||||
|
||||
# make ncls have the same name and module as cls
|
||||
ncls.__name__ = cls.__name__
|
||||
ncls.__qualname__ = cls.__qualname__
|
||||
ncls.__module__ = cls.__module__
|
||||
return ncls
|
|
@ -21,6 +21,8 @@ def _maybe_timeout(timeout):
|
|||
# for brevity
|
||||
_lltuple = dns.inet.low_level_address_tuple
|
||||
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
class DatagramSocket(dns._asyncbackend.DatagramSocket):
|
||||
def __init__(self, socket):
|
||||
|
@ -47,7 +49,7 @@ class DatagramSocket(dns._asyncbackend.DatagramSocket):
|
|||
return self.socket.getsockname()
|
||||
|
||||
|
||||
class StreamSocket(dns._asyncbackend.DatagramSocket):
|
||||
class StreamSocket(dns._asyncbackend.StreamSocket):
|
||||
def __init__(self, family, stream, tls=False):
|
||||
self.family = family
|
||||
self.stream = stream
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
|
||||
import dns.exception
|
||||
|
||||
# pylint: disable=unused-import
|
||||
|
||||
from dns._asyncbackend import Socket, DatagramSocket, \
|
||||
StreamSocket, Backend # noqa:
|
||||
|
||||
# pylint: enable=unused-import
|
||||
|
||||
_default_backend = None
|
||||
|
||||
|
@ -18,13 +21,14 @@ class AsyncLibraryNotFoundError(dns.exception.DNSException):
|
|||
|
||||
|
||||
def get_backend(name):
|
||||
"""Get the specified asychronous backend.
|
||||
"""Get the specified asynchronous backend.
|
||||
|
||||
*name*, a ``str``, the name of the backend. Currently the "trio",
|
||||
"curio", and "asyncio" backends are available.
|
||||
|
||||
Raises NotImplementError if an unknown backend name is specified.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel,redefined-outer-name
|
||||
backend = _backends.get(name)
|
||||
if backend:
|
||||
return backend
|
||||
|
@ -50,6 +54,7 @@ def sniff():
|
|||
Returns the name of the library, or raises AsyncLibraryNotFoundError
|
||||
if the library cannot be determined.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
try:
|
||||
if _no_sniffio:
|
||||
raise ImportError
|
||||
|
|
13
lib/dns/asyncbackend.pyi
Normal file
13
lib/dns/asyncbackend.pyi
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
class Backend:
|
||||
...
|
||||
|
||||
def get_backend(name: str) -> Backend:
|
||||
...
|
||||
def sniff() -> str:
|
||||
...
|
||||
def get_default_backend() -> Backend:
|
||||
...
|
||||
def set_default_backend(name: str) -> Backend:
|
||||
...
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
"""Talk to a DNS server."""
|
||||
|
||||
import base64
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
|
@ -30,8 +31,11 @@ import dns.rcode
|
|||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
|
||||
from dns.query import _compute_times, _matches_destination, BadResponse, ssl
|
||||
from dns.query import _compute_times, _matches_destination, BadResponse, ssl, \
|
||||
UDPMode, _have_httpx, _have_http2, NoDOH
|
||||
|
||||
if _have_httpx:
|
||||
import httpx
|
||||
|
||||
# for brevity
|
||||
_lltuple = dns.inet.low_level_address_tuple
|
||||
|
@ -94,36 +98,8 @@ async def receive_udp(sock, destination=None, expiration=None,
|
|||
|
||||
*sock*, a ``dns.asyncbackend.DatagramSocket``.
|
||||
|
||||
*destination*, a destination tuple appropriate for the address family
|
||||
of the socket, specifying where the message is expected to arrive from.
|
||||
When receiving a response, this would be where the associated query was
|
||||
sent.
|
||||
|
||||
*expiration*, a ``float`` or ``None``, the absolute time at which
|
||||
a timeout exception should be raised. If ``None``, no timeout will
|
||||
occur.
|
||||
|
||||
*ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
|
||||
unexpected sources.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*keyring*, a ``dict``, the keyring to use for TSIG.
|
||||
|
||||
*request_mac*, a ``bytes``, the MAC of the request (for TSIG).
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
*raise_on_truncation*, a ``bool``. If ``True``, raise an exception if
|
||||
the TC bit is set.
|
||||
|
||||
Raises if the message is malformed, if network errors occur, of if
|
||||
there is a timeout.
|
||||
|
||||
Returns a ``(dns.message.Message, float, tuple)`` tuple of the received
|
||||
message, the received time, and the address where the message arrived from.
|
||||
See :py:func:`dns.query.receive_udp()` for the documentation of the other
|
||||
parameters, exceptions, and return type of this method.
|
||||
"""
|
||||
|
||||
wire = b''
|
||||
|
@ -145,34 +121,6 @@ async def udp(q, where, timeout=None, port=53, source=None, source_port=0,
|
|||
backend=None):
|
||||
"""Return the response obtained after sending a query via UDP.
|
||||
|
||||
*q*, a ``dns.message.Message``, the query to send
|
||||
|
||||
*where*, a ``str`` containing an IPv4 or IPv6 address, where
|
||||
to send the message.
|
||||
|
||||
*timeout*, a ``float`` or ``None``, the number of seconds to wait before the
|
||||
query times out. If ``None``, the default, wait forever.
|
||||
|
||||
*port*, an ``int``, the port send the message to. The default is 53.
|
||||
|
||||
*source*, a ``str`` containing an IPv4 or IPv6 address, specifying
|
||||
the source address. The default is the wildcard address.
|
||||
|
||||
*source_port*, an ``int``, the port from which to send the message.
|
||||
The default is 0.
|
||||
|
||||
*ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
|
||||
unexpected sources.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
*raise_on_truncation*, a ``bool``. If ``True``, raise an exception if
|
||||
the TC bit is set.
|
||||
|
||||
*sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``,
|
||||
the socket to use for the query. If ``None``, the default, a
|
||||
socket is created. Note that if a socket is provided, the
|
||||
|
@ -181,7 +129,8 @@ async def udp(q, where, timeout=None, port=53, source=None, source_port=0,
|
|||
*backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
|
||||
the default, then dnspython will use the default backend.
|
||||
|
||||
Returns a ``dns.message.Message``.
|
||||
See :py:func:`dns.query.udp()` for the documentation of the other
|
||||
parameters, exceptions, and return type of this method.
|
||||
"""
|
||||
wire = q.to_wire()
|
||||
(begin_time, expiration) = _compute_times(timeout)
|
||||
|
@ -196,7 +145,12 @@ async def udp(q, where, timeout=None, port=53, source=None, source_port=0,
|
|||
if not backend:
|
||||
backend = dns.asyncbackend.get_default_backend()
|
||||
stuple = _source_tuple(af, source, source_port)
|
||||
s = await backend.make_socket(af, socket.SOCK_DGRAM, 0, stuple)
|
||||
if backend.datagram_connection_required():
|
||||
dtuple = (where, port)
|
||||
else:
|
||||
dtuple = None
|
||||
s = await backend.make_socket(af, socket.SOCK_DGRAM, 0, stuple,
|
||||
dtuple)
|
||||
await send_udp(s, wire, destination, expiration)
|
||||
(r, received_time, _) = await receive_udp(s, destination, expiration,
|
||||
ignore_unexpected,
|
||||
|
@ -219,31 +173,6 @@ async def udp_with_fallback(q, where, timeout=None, port=53, source=None,
|
|||
"""Return the response to the query, trying UDP first and falling back
|
||||
to TCP if UDP results in a truncated response.
|
||||
|
||||
*q*, a ``dns.message.Message``, the query to send
|
||||
|
||||
*where*, a ``str`` containing an IPv4 or IPv6 address, where
|
||||
to send the message.
|
||||
|
||||
*timeout*, a ``float`` or ``None``, the number of seconds to wait before the
|
||||
query times out. If ``None``, the default, wait forever.
|
||||
|
||||
*port*, an ``int``, the port send the message to. The default is 53.
|
||||
|
||||
*source*, a ``str`` containing an IPv4 or IPv6 address, specifying
|
||||
the source address. The default is the wildcard address.
|
||||
|
||||
*source_port*, an ``int``, the port from which to send the message.
|
||||
The default is 0.
|
||||
|
||||
*ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
|
||||
unexpected sources.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
*udp_sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``,
|
||||
the socket to use for the UDP query. If ``None``, the default, a
|
||||
socket is created. Note that if a socket is provided the *source*,
|
||||
|
@ -257,8 +186,9 @@ async def udp_with_fallback(q, where, timeout=None, port=53, source=None,
|
|||
*backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
|
||||
the default, then dnspython will use the default backend.
|
||||
|
||||
Returns a (``dns.message.Message``, tcp) tuple where tcp is ``True``
|
||||
if and only if TCP was used.
|
||||
See :py:func:`dns.query.udp_with_fallback()` for the documentation
|
||||
of the other parameters, exceptions, and return type of this
|
||||
method.
|
||||
"""
|
||||
try:
|
||||
response = await udp(q, where, timeout, port, source, source_port,
|
||||
|
@ -275,15 +205,10 @@ async def udp_with_fallback(q, where, timeout=None, port=53, source=None,
|
|||
async def send_tcp(sock, what, expiration=None):
|
||||
"""Send a DNS message to the specified TCP socket.
|
||||
|
||||
*sock*, a ``socket``.
|
||||
*sock*, a ``dns.asyncbackend.StreamSocket``.
|
||||
|
||||
*what*, a ``bytes`` or ``dns.message.Message``, the message to send.
|
||||
|
||||
*expiration*, a ``float`` or ``None``, the absolute time at which
|
||||
a timeout exception should be raised. If ``None``, no timeout will
|
||||
occur.
|
||||
|
||||
Returns an ``(int, float)`` tuple of bytes sent and the sent time.
|
||||
See :py:func:`dns.query.send_tcp()` for the documentation of the other
|
||||
parameters, exceptions, and return type of this method.
|
||||
"""
|
||||
|
||||
if isinstance(what, dns.message.Message):
|
||||
|
@ -294,7 +219,7 @@ async def send_tcp(sock, what, expiration=None):
|
|||
# onto the net
|
||||
tcpmsg = struct.pack("!H", l) + what
|
||||
sent_time = time.time()
|
||||
await sock.sendall(tcpmsg, expiration)
|
||||
await sock.sendall(tcpmsg, _timeout(expiration, sent_time))
|
||||
return (len(tcpmsg), sent_time)
|
||||
|
||||
|
||||
|
@ -316,27 +241,10 @@ async def receive_tcp(sock, expiration=None, one_rr_per_rrset=False,
|
|||
keyring=None, request_mac=b'', ignore_trailing=False):
|
||||
"""Read a DNS message from a TCP socket.
|
||||
|
||||
*sock*, a ``socket``.
|
||||
*sock*, a ``dns.asyncbackend.StreamSocket``.
|
||||
|
||||
*expiration*, a ``float`` or ``None``, the absolute time at which
|
||||
a timeout exception should be raised. If ``None``, no timeout will
|
||||
occur.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*keyring*, a ``dict``, the keyring to use for TSIG.
|
||||
|
||||
*request_mac*, a ``bytes``, the MAC of the request (for TSIG).
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
Raises if the message is malformed, if network errors occur, of if
|
||||
there is a timeout.
|
||||
|
||||
Returns a ``(dns.message.Message, float)`` tuple of the received message
|
||||
and the received time.
|
||||
See :py:func:`dns.query.receive_tcp()` for the documentation of the other
|
||||
parameters, exceptions, and return type of this method.
|
||||
"""
|
||||
|
||||
ldata = await _read_exactly(sock, 2, expiration)
|
||||
|
@ -354,28 +262,6 @@ async def tcp(q, where, timeout=None, port=53, source=None, source_port=0,
|
|||
backend=None):
|
||||
"""Return the response obtained after sending a query via TCP.
|
||||
|
||||
*q*, a ``dns.message.Message``, the query to send
|
||||
|
||||
*where*, a ``str`` containing an IPv4 or IPv6 address, where
|
||||
to send the message.
|
||||
|
||||
*timeout*, a ``float`` or ``None``, the number of seconds to wait before the
|
||||
query times out. If ``None``, the default, wait forever.
|
||||
|
||||
*port*, an ``int``, the port send the message to. The default is 53.
|
||||
|
||||
*source*, a ``str`` containing an IPv4 or IPv6 address, specifying
|
||||
the source address. The default is the wildcard address.
|
||||
|
||||
*source_port*, an ``int``, the port from which to send the message.
|
||||
The default is 0.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
*sock*, a ``dns.asyncbacket.StreamSocket``, or ``None``, the
|
||||
socket to use for the query. If ``None``, the default, a socket
|
||||
is created. Note that if a socket is provided
|
||||
|
@ -384,7 +270,8 @@ async def tcp(q, where, timeout=None, port=53, source=None, source_port=0,
|
|||
*backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
|
||||
the default, then dnspython will use the default backend.
|
||||
|
||||
Returns a ``dns.message.Message``.
|
||||
See :py:func:`dns.query.tcp()` for the documentation of the other
|
||||
parameters, exceptions, and return type of this method.
|
||||
"""
|
||||
|
||||
wire = q.to_wire()
|
||||
|
@ -426,28 +313,6 @@ async def tls(q, where, timeout=None, port=853, source=None, source_port=0,
|
|||
backend=None, ssl_context=None, server_hostname=None):
|
||||
"""Return the response obtained after sending a query via TLS.
|
||||
|
||||
*q*, a ``dns.message.Message``, the query to send
|
||||
|
||||
*where*, a ``str`` containing an IPv4 or IPv6 address, where
|
||||
to send the message.
|
||||
|
||||
*timeout*, a ``float`` or ``None``, the number of seconds to wait before the
|
||||
query times out. If ``None``, the default, wait forever.
|
||||
|
||||
*port*, an ``int``, the port send the message to. The default is 853.
|
||||
|
||||
*source*, a ``str`` containing an IPv4 or IPv6 address, specifying
|
||||
the source address. The default is the wildcard address.
|
||||
|
||||
*source_port*, an ``int``, the port from which to send the message.
|
||||
The default is 0.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
*sock*, an ``asyncbackend.StreamSocket``, or ``None``, the socket
|
||||
to use for the query. If ``None``, the default, a socket is
|
||||
created. Note that if a socket is provided, it must be a
|
||||
|
@ -458,15 +323,8 @@ async def tls(q, where, timeout=None, port=853, source=None, source_port=0,
|
|||
*backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
|
||||
the default, then dnspython will use the default backend.
|
||||
|
||||
*ssl_context*, an ``ssl.SSLContext``, the context to use when establishing
|
||||
a TLS connection. If ``None``, the default, creates one with the default
|
||||
configuration.
|
||||
|
||||
*server_hostname*, a ``str`` containing the server's hostname. The
|
||||
default is ``None``, which means that no hostname is known, and if an
|
||||
SSL context is created, hostname checking will be disabled.
|
||||
|
||||
Returns a ``dns.message.Message``.
|
||||
See :py:func:`dns.query.tls()` for the documentation of the other
|
||||
parameters, exceptions, and return type of this method.
|
||||
"""
|
||||
# After 3.6 is no longer supported, this can use an AsyncExitStack.
|
||||
(begin_time, expiration) = _compute_times(timeout)
|
||||
|
@ -498,3 +356,168 @@ async def tls(q, where, timeout=None, port=853, source=None, source_port=0,
|
|||
finally:
|
||||
if not sock and s:
|
||||
await s.close()
|
||||
|
||||
async def https(q, where, timeout=None, port=443, source=None, source_port=0,
|
||||
one_rr_per_rrset=False, ignore_trailing=False, client=None,
|
||||
path='/dns-query', post=True, verify=True):
|
||||
"""Return the response obtained after sending a query via DNS-over-HTTPS.
|
||||
|
||||
*client*, a ``httpx.AsyncClient``. If provided, the client to use for
|
||||
the query.
|
||||
|
||||
Unlike the other dnspython async functions, a backend cannot be provided
|
||||
in this function because httpx always auto-detects the async backend.
|
||||
|
||||
See :py:func:`dns.query.https()` for the documentation of the other
|
||||
parameters, exceptions, and return type of this method.
|
||||
"""
|
||||
|
||||
if not _have_httpx:
|
||||
raise NoDOH('httpx is not available.') # pragma: no cover
|
||||
|
||||
wire = q.to_wire()
|
||||
try:
|
||||
af = dns.inet.af_for_address(where)
|
||||
except ValueError:
|
||||
af = None
|
||||
transport = None
|
||||
headers = {
|
||||
"accept": "application/dns-message"
|
||||
}
|
||||
if af is not None:
|
||||
if af == socket.AF_INET:
|
||||
url = 'https://{}:{}{}'.format(where, port, path)
|
||||
elif af == socket.AF_INET6:
|
||||
url = 'https://[{}]:{}{}'.format(where, port, path)
|
||||
else:
|
||||
url = where
|
||||
if source is not None:
|
||||
transport = httpx.AsyncHTTPTransport(local_address=source[0])
|
||||
|
||||
# After 3.6 is no longer supported, this can use an AsyncExitStack
|
||||
client_to_close = None
|
||||
try:
|
||||
if not client:
|
||||
client = httpx.AsyncClient(http1=True, http2=_have_http2,
|
||||
verify=verify, transport=transport)
|
||||
client_to_close = client
|
||||
|
||||
# see https://tools.ietf.org/html/rfc8484#section-4.1.1 for DoH
|
||||
# GET and POST examples
|
||||
if post:
|
||||
headers.update({
|
||||
"content-type": "application/dns-message",
|
||||
"content-length": str(len(wire))
|
||||
})
|
||||
response = await client.post(url, headers=headers, content=wire,
|
||||
timeout=timeout)
|
||||
else:
|
||||
wire = base64.urlsafe_b64encode(wire).rstrip(b"=")
|
||||
wire = wire.decode() # httpx does a repr() if we give it bytes
|
||||
response = await client.get(url, headers=headers, timeout=timeout,
|
||||
params={"dns": wire})
|
||||
finally:
|
||||
if client_to_close:
|
||||
await client.aclose()
|
||||
|
||||
# see https://tools.ietf.org/html/rfc8484#section-4.2.1 for info about DoH
|
||||
# status codes
|
||||
if response.status_code < 200 or response.status_code > 299:
|
||||
raise ValueError('{} responded with status code {}'
|
||||
'\nResponse body: {}'.format(where,
|
||||
response.status_code,
|
||||
response.content))
|
||||
r = dns.message.from_wire(response.content,
|
||||
keyring=q.keyring,
|
||||
request_mac=q.request_mac,
|
||||
one_rr_per_rrset=one_rr_per_rrset,
|
||||
ignore_trailing=ignore_trailing)
|
||||
r.time = response.elapsed
|
||||
if not q.is_response(r):
|
||||
raise BadResponse
|
||||
return r
|
||||
|
||||
async def inbound_xfr(where, txn_manager, query=None,
|
||||
port=53, timeout=None, lifetime=None, source=None,
|
||||
source_port=0, udp_mode=UDPMode.NEVER, backend=None):
|
||||
"""Conduct an inbound transfer and apply it via a transaction from the
|
||||
txn_manager.
|
||||
|
||||
*backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
|
||||
the default, then dnspython will use the default backend.
|
||||
|
||||
See :py:func:`dns.query.inbound_xfr()` for the documentation of
|
||||
the other parameters, exceptions, and return type of this method.
|
||||
"""
|
||||
if query is None:
|
||||
(query, serial) = dns.xfr.make_query(txn_manager)
|
||||
else:
|
||||
serial = dns.xfr.extract_serial_from_query(query)
|
||||
rdtype = query.question[0].rdtype
|
||||
is_ixfr = rdtype == dns.rdatatype.IXFR
|
||||
origin = txn_manager.from_wire_origin()
|
||||
wire = query.to_wire()
|
||||
af = dns.inet.af_for_address(where)
|
||||
stuple = _source_tuple(af, source, source_port)
|
||||
dtuple = (where, port)
|
||||
(_, expiration) = _compute_times(lifetime)
|
||||
retry = True
|
||||
while retry:
|
||||
retry = False
|
||||
if is_ixfr and udp_mode != UDPMode.NEVER:
|
||||
sock_type = socket.SOCK_DGRAM
|
||||
is_udp = True
|
||||
else:
|
||||
sock_type = socket.SOCK_STREAM
|
||||
is_udp = False
|
||||
if not backend:
|
||||
backend = dns.asyncbackend.get_default_backend()
|
||||
s = await backend.make_socket(af, sock_type, 0, stuple, dtuple,
|
||||
_timeout(expiration))
|
||||
async with s:
|
||||
if is_udp:
|
||||
await s.sendto(wire, dtuple, _timeout(expiration))
|
||||
else:
|
||||
tcpmsg = struct.pack("!H", len(wire)) + wire
|
||||
await s.sendall(tcpmsg, expiration)
|
||||
with dns.xfr.Inbound(txn_manager, rdtype, serial,
|
||||
is_udp) as inbound:
|
||||
done = False
|
||||
tsig_ctx = None
|
||||
while not done:
|
||||
(_, mexpiration) = _compute_times(timeout)
|
||||
if mexpiration is None or \
|
||||
(expiration is not None and mexpiration > expiration):
|
||||
mexpiration = expiration
|
||||
if is_udp:
|
||||
destination = _lltuple((where, port), af)
|
||||
while True:
|
||||
timeout = _timeout(mexpiration)
|
||||
(rwire, from_address) = await s.recvfrom(65535,
|
||||
timeout)
|
||||
if _matches_destination(af, from_address,
|
||||
destination, True):
|
||||
break
|
||||
else:
|
||||
ldata = await _read_exactly(s, 2, mexpiration)
|
||||
(l,) = struct.unpack("!H", ldata)
|
||||
rwire = await _read_exactly(s, l, mexpiration)
|
||||
is_ixfr = (rdtype == dns.rdatatype.IXFR)
|
||||
r = dns.message.from_wire(rwire, keyring=query.keyring,
|
||||
request_mac=query.mac, xfr=True,
|
||||
origin=origin, tsig_ctx=tsig_ctx,
|
||||
multi=(not is_udp),
|
||||
one_rr_per_rrset=is_ixfr)
|
||||
try:
|
||||
done = inbound.process_message(r)
|
||||
except dns.xfr.UseTCP:
|
||||
assert is_udp # should not happen if we used TCP!
|
||||
if udp_mode == UDPMode.ONLY:
|
||||
raise
|
||||
done = True
|
||||
retry = True
|
||||
udp_mode = UDPMode.NEVER
|
||||
continue
|
||||
tsig_ctx = r.tsig_ctx
|
||||
if not retry and query.keyring and not r.had_tsig:
|
||||
raise dns.exception.FormError("missing TSIG")
|
||||
|
|
43
lib/dns/asyncquery.pyi
Normal file
43
lib/dns/asyncquery.pyi
Normal file
|
@ -0,0 +1,43 @@
|
|||
from typing import Optional, Union, Dict, Generator, Any
|
||||
from . import tsig, rdatatype, rdataclass, name, message, asyncbackend
|
||||
|
||||
# If the ssl import works, then
|
||||
#
|
||||
# error: Name 'ssl' already defined (by an import)
|
||||
#
|
||||
# is expected and can be ignored.
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
class ssl: # type: ignore
|
||||
SSLContext : Dict = {}
|
||||
|
||||
async def udp(q : message.Message, where : str,
|
||||
timeout : Optional[float] = None, port=53,
|
||||
source : Optional[str] = None, source_port : Optional[int] = 0,
|
||||
ignore_unexpected : Optional[bool] = False,
|
||||
one_rr_per_rrset : Optional[bool] = False,
|
||||
ignore_trailing : Optional[bool] = False,
|
||||
sock : Optional[asyncbackend.DatagramSocket] = None,
|
||||
backend : Optional[asyncbackend.Backend]) -> message.Message:
|
||||
pass
|
||||
|
||||
async def tcp(q : message.Message, where : str, timeout : float = None, port=53,
|
||||
af : Optional[int] = None, source : Optional[str] = None,
|
||||
source_port : Optional[int] = 0,
|
||||
one_rr_per_rrset : Optional[bool] = False,
|
||||
ignore_trailing : Optional[bool] = False,
|
||||
sock : Optional[asyncbackend.StreamSocket] = None,
|
||||
backend : Optional[asyncbackend.Backend]) -> message.Message:
|
||||
pass
|
||||
|
||||
async def tls(q : message.Message, where : str,
|
||||
timeout : Optional[float] = None, port=53,
|
||||
source : Optional[str] = None, source_port : Optional[int] = 0,
|
||||
one_rr_per_rrset : Optional[bool] = False,
|
||||
ignore_trailing : Optional[bool] = False,
|
||||
sock : Optional[asyncbackend.StreamSocket] = None,
|
||||
backend : Optional[asyncbackend.Backend],
|
||||
ssl_context: Optional[ssl.SSLContext] = None,
|
||||
server_hostname: Optional[str] = None) -> message.Message:
|
||||
pass
|
|
@ -34,7 +34,8 @@ _udp = dns.asyncquery.udp
|
|||
_tcp = dns.asyncquery.tcp
|
||||
|
||||
|
||||
class Resolver(dns.resolver.Resolver):
|
||||
class Resolver(dns.resolver.BaseResolver):
|
||||
"""Asynchronous DNS stub resolver."""
|
||||
|
||||
async def resolve(self, qname, rdtype=dns.rdatatype.A,
|
||||
rdclass=dns.rdataclass.IN,
|
||||
|
@ -43,53 +44,12 @@ class Resolver(dns.resolver.Resolver):
|
|||
backend=None):
|
||||
"""Query nameservers asynchronously to find the answer to the question.
|
||||
|
||||
The *qname*, *rdtype*, and *rdclass* parameters may be objects
|
||||
of the appropriate type, or strings that can be converted into objects
|
||||
of the appropriate type.
|
||||
|
||||
*qname*, a ``dns.name.Name`` or ``str``, the query name.
|
||||
|
||||
*rdtype*, an ``int`` or ``str``, the query type.
|
||||
|
||||
*rdclass*, an ``int`` or ``str``, the query class.
|
||||
|
||||
*tcp*, a ``bool``. If ``True``, use TCP to make the query.
|
||||
|
||||
*source*, a ``str`` or ``None``. If not ``None``, bind to this IP
|
||||
address when making queries.
|
||||
|
||||
*raise_on_no_answer*, a ``bool``. If ``True``, raise
|
||||
``dns.resolver.NoAnswer`` if there's no answer to the question.
|
||||
|
||||
*source_port*, an ``int``, the port from which to send the message.
|
||||
|
||||
*lifetime*, a ``float``, how many seconds a query should run
|
||||
before timing out.
|
||||
|
||||
*search*, a ``bool`` or ``None``, determines whether the
|
||||
search list configured in the system's resolver configuration
|
||||
are used for relative names, and whether the resolver's domain
|
||||
may be added to relative names. The default is ``None``,
|
||||
which causes the value of the resolver's
|
||||
``use_search_by_default`` attribute to be used.
|
||||
|
||||
*backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
|
||||
the default, then dnspython will use the default backend.
|
||||
|
||||
Raises ``dns.resolver.NXDOMAIN`` if the query name does not exist.
|
||||
|
||||
Raises ``dns.resolver.YXDOMAIN`` if the query name is too long after
|
||||
DNAME substitution.
|
||||
|
||||
Raises ``dns.resolver.NoAnswer`` if *raise_on_no_answer* is
|
||||
``True`` and the query name exists but has no RRset of the
|
||||
desired type and class.
|
||||
|
||||
Raises ``dns.resolver.NoNameservers`` if no non-broken
|
||||
nameservers are available to answer the question.
|
||||
|
||||
Returns a ``dns.resolver.Answer`` instance.
|
||||
|
||||
See :py:func:`dns.resolver.Resolver.resolve()` for the
|
||||
documentation of the other parameters, exceptions, and return
|
||||
type of this method.
|
||||
"""
|
||||
|
||||
resolution = dns.resolver._Resolution(self, qname, rdtype, rdclass, tcp,
|
||||
|
@ -111,7 +71,8 @@ class Resolver(dns.resolver.Resolver):
|
|||
(nameserver, port, tcp, backoff) = resolution.next_nameserver()
|
||||
if backoff:
|
||||
await backend.sleep(backoff)
|
||||
timeout = self._compute_timeout(start, lifetime)
|
||||
timeout = self._compute_timeout(start, lifetime,
|
||||
resolution.errors)
|
||||
try:
|
||||
if dns.inet.is_address(nameserver):
|
||||
if tcp:
|
||||
|
@ -126,8 +87,9 @@ class Resolver(dns.resolver.Resolver):
|
|||
raise_on_truncation=True,
|
||||
backend=backend)
|
||||
else:
|
||||
# We don't do DoH yet.
|
||||
raise NotImplementedError
|
||||
response = await dns.asyncquery.https(request,
|
||||
nameserver,
|
||||
timeout=timeout)
|
||||
except Exception as ex:
|
||||
(_, done) = resolution.query_result(None, ex)
|
||||
continue
|
||||
|
@ -139,11 +101,6 @@ class Resolver(dns.resolver.Resolver):
|
|||
if answer is not None:
|
||||
return answer
|
||||
|
||||
async def query(self, *args, **kwargs):
|
||||
# We have to define something here as we don't want to inherit the
|
||||
# parent's query().
|
||||
raise NotImplementedError
|
||||
|
||||
async def resolve_address(self, ipaddr, *args, **kwargs):
|
||||
"""Use an asynchronous resolver to run a reverse query for PTR
|
||||
records.
|
||||
|
@ -165,6 +122,30 @@ class Resolver(dns.resolver.Resolver):
|
|||
rdclass=dns.rdataclass.IN,
|
||||
*args, **kwargs)
|
||||
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
async def canonical_name(self, name):
|
||||
"""Determine the canonical name of *name*.
|
||||
|
||||
The canonical name is the name the resolver uses for queries
|
||||
after all CNAME and DNAME renamings have been applied.
|
||||
|
||||
*name*, a ``dns.name.Name`` or ``str``, the query name.
|
||||
|
||||
This method can raise any exception that ``resolve()`` can
|
||||
raise, other than ``dns.resolver.NoAnswer`` and
|
||||
``dns.resolver.NXDOMAIN``.
|
||||
|
||||
Returns a ``dns.name.Name``.
|
||||
"""
|
||||
try:
|
||||
answer = await self.resolve(name, raise_on_no_answer=False)
|
||||
canonical_name = answer.canonical_name
|
||||
except dns.resolver.NXDOMAIN as e:
|
||||
canonical_name = e.canonical_name
|
||||
return canonical_name
|
||||
|
||||
|
||||
default_resolver = None
|
||||
|
||||
|
||||
|
@ -188,52 +169,46 @@ def reset_default_resolver():
|
|||
|
||||
async def resolve(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
|
||||
tcp=False, source=None, raise_on_no_answer=True,
|
||||
source_port=0, search=None, backend=None):
|
||||
source_port=0, lifetime=None, search=None, backend=None):
|
||||
"""Query nameservers asynchronously to find the answer to the question.
|
||||
|
||||
This is a convenience function that uses the default resolver
|
||||
object to make the query.
|
||||
|
||||
See ``dns.asyncresolver.Resolver.resolve`` for more information on the
|
||||
parameters.
|
||||
See :py:func:`dns.asyncresolver.Resolver.resolve` for more
|
||||
information on the parameters.
|
||||
"""
|
||||
|
||||
return await get_default_resolver().resolve(qname, rdtype, rdclass, tcp,
|
||||
source, raise_on_no_answer,
|
||||
source_port, search, backend)
|
||||
source_port, lifetime, search,
|
||||
backend)
|
||||
|
||||
|
||||
async def resolve_address(ipaddr, *args, **kwargs):
|
||||
"""Use a resolver to run a reverse query for PTR records.
|
||||
|
||||
See ``dns.asyncresolver.Resolver.resolve_address`` for more
|
||||
See :py:func:`dns.asyncresolver.Resolver.resolve_address` for more
|
||||
information on the parameters.
|
||||
"""
|
||||
|
||||
return await get_default_resolver().resolve_address(ipaddr, *args, **kwargs)
|
||||
|
||||
async def canonical_name(name):
|
||||
"""Determine the canonical name of *name*.
|
||||
|
||||
See :py:func:`dns.resolver.Resolver.canonical_name` for more
|
||||
information on the parameters and possible exceptions.
|
||||
"""
|
||||
|
||||
return await get_default_resolver().canonical_name(name)
|
||||
|
||||
async def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False,
|
||||
resolver=None, backend=None):
|
||||
"""Find the name of the zone which contains the specified name.
|
||||
|
||||
*name*, an absolute ``dns.name.Name`` or ``str``, the query name.
|
||||
|
||||
*rdclass*, an ``int``, the query class.
|
||||
|
||||
*tcp*, a ``bool``. If ``True``, use TCP to make the query.
|
||||
|
||||
*resolver*, a ``dns.asyncresolver.Resolver`` or ``None``, the
|
||||
resolver to use. If ``None``, the default resolver is used.
|
||||
|
||||
*backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
|
||||
the default, then dnspython will use the default backend.
|
||||
|
||||
Raises ``dns.resolver.NoRootSOA`` if there is no SOA RR at the DNS
|
||||
root. (This is only likely to happen if you're using non-default
|
||||
root servers in your network and they are misconfigured.)
|
||||
|
||||
Returns a ``dns.name.Name``.
|
||||
See :py:func:`dns.resolver.Resolver.zone_for_name` for more
|
||||
information on the parameters and possible exceptions.
|
||||
"""
|
||||
|
||||
if isinstance(name, str):
|
||||
|
|
26
lib/dns/asyncresolver.pyi
Normal file
26
lib/dns/asyncresolver.pyi
Normal file
|
@ -0,0 +1,26 @@
|
|||
from typing import Union, Optional, List, Any, Dict
|
||||
from . import exception, rdataclass, name, rdatatype, asyncbackend
|
||||
|
||||
async def resolve(qname : str, rdtype : Union[int,str] = 0,
|
||||
rdclass : Union[int,str] = 0,
|
||||
tcp=False, source=None, raise_on_no_answer=True,
|
||||
source_port=0, lifetime : Optional[float]=None,
|
||||
search : Optional[bool]=None,
|
||||
backend : Optional[asyncbackend.Backend]=None):
|
||||
...
|
||||
async def resolve_address(self, ipaddr: str,
|
||||
*args: Any, **kwargs: Optional[Dict]):
|
||||
...
|
||||
|
||||
class Resolver:
|
||||
def __init__(self, filename : Optional[str] = '/etc/resolv.conf',
|
||||
configure : Optional[bool] = True):
|
||||
self.nameservers : List[str]
|
||||
async def resolve(self, qname : str, rdtype : Union[int,str] = rdatatype.A,
|
||||
rdclass : Union[int,str] = rdataclass.IN,
|
||||
tcp : bool = False, source : Optional[str] = None,
|
||||
raise_on_no_answer=True, source_port : int = 0,
|
||||
lifetime : Optional[float]=None,
|
||||
search : Optional[bool]=None,
|
||||
backend : Optional[asyncbackend.Backend]=None):
|
||||
...
|
|
@ -64,9 +64,6 @@ class Algorithm(dns.enum.IntEnum):
|
|||
return 255
|
||||
|
||||
|
||||
globals().update(Algorithm.__members__)
|
||||
|
||||
|
||||
def algorithm_from_text(text):
|
||||
"""Convert text into a DNSSEC algorithm value.
|
||||
|
||||
|
@ -169,23 +166,15 @@ def make_ds(name, key, algorithm, origin=None):
|
|||
|
||||
|
||||
def _find_candidate_keys(keys, rrsig):
|
||||
candidate_keys = []
|
||||
value = keys.get(rrsig.signer)
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, dns.node.Node):
|
||||
try:
|
||||
rdataset = value.find_rdataset(dns.rdataclass.IN,
|
||||
dns.rdatatype.DNSKEY)
|
||||
except KeyError:
|
||||
return None
|
||||
rdataset = value.get_rdataset(dns.rdataclass.IN, dns.rdatatype.DNSKEY)
|
||||
else:
|
||||
rdataset = value
|
||||
for rdata in rdataset:
|
||||
if rdata.algorithm == rrsig.algorithm and \
|
||||
key_id(rdata) == rrsig.key_tag:
|
||||
candidate_keys.append(rdata)
|
||||
return candidate_keys
|
||||
if rdataset is None:
|
||||
return None
|
||||
return [rd for rd in rdataset if
|
||||
rd.algorithm == rrsig.algorithm and key_id(rd) == rrsig.key_tag]
|
||||
|
||||
|
||||
def _is_rsa(algorithm):
|
||||
|
@ -254,6 +243,82 @@ def _bytes_to_long(b):
|
|||
return int.from_bytes(b, 'big')
|
||||
|
||||
|
||||
def _validate_signature(sig, data, key, chosen_hash):
|
||||
if _is_rsa(key.algorithm):
|
||||
keyptr = key.key
|
||||
(bytes_,) = struct.unpack('!B', keyptr[0:1])
|
||||
keyptr = keyptr[1:]
|
||||
if bytes_ == 0:
|
||||
(bytes_,) = struct.unpack('!H', keyptr[0:2])
|
||||
keyptr = keyptr[2:]
|
||||
rsa_e = keyptr[0:bytes_]
|
||||
rsa_n = keyptr[bytes_:]
|
||||
try:
|
||||
public_key = rsa.RSAPublicNumbers(
|
||||
_bytes_to_long(rsa_e),
|
||||
_bytes_to_long(rsa_n)).public_key(default_backend())
|
||||
except ValueError:
|
||||
raise ValidationFailure('invalid public key')
|
||||
public_key.verify(sig, data, padding.PKCS1v15(), chosen_hash)
|
||||
elif _is_dsa(key.algorithm):
|
||||
keyptr = key.key
|
||||
(t,) = struct.unpack('!B', keyptr[0:1])
|
||||
keyptr = keyptr[1:]
|
||||
octets = 64 + t * 8
|
||||
dsa_q = keyptr[0:20]
|
||||
keyptr = keyptr[20:]
|
||||
dsa_p = keyptr[0:octets]
|
||||
keyptr = keyptr[octets:]
|
||||
dsa_g = keyptr[0:octets]
|
||||
keyptr = keyptr[octets:]
|
||||
dsa_y = keyptr[0:octets]
|
||||
try:
|
||||
public_key = dsa.DSAPublicNumbers(
|
||||
_bytes_to_long(dsa_y),
|
||||
dsa.DSAParameterNumbers(
|
||||
_bytes_to_long(dsa_p),
|
||||
_bytes_to_long(dsa_q),
|
||||
_bytes_to_long(dsa_g))).public_key(default_backend())
|
||||
except ValueError:
|
||||
raise ValidationFailure('invalid public key')
|
||||
public_key.verify(sig, data, chosen_hash)
|
||||
elif _is_ecdsa(key.algorithm):
|
||||
keyptr = key.key
|
||||
if key.algorithm == Algorithm.ECDSAP256SHA256:
|
||||
curve = ec.SECP256R1()
|
||||
octets = 32
|
||||
else:
|
||||
curve = ec.SECP384R1()
|
||||
octets = 48
|
||||
ecdsa_x = keyptr[0:octets]
|
||||
ecdsa_y = keyptr[octets:octets * 2]
|
||||
try:
|
||||
public_key = ec.EllipticCurvePublicNumbers(
|
||||
curve=curve,
|
||||
x=_bytes_to_long(ecdsa_x),
|
||||
y=_bytes_to_long(ecdsa_y)).public_key(default_backend())
|
||||
except ValueError:
|
||||
raise ValidationFailure('invalid public key')
|
||||
public_key.verify(sig, data, ec.ECDSA(chosen_hash))
|
||||
elif _is_eddsa(key.algorithm):
|
||||
keyptr = key.key
|
||||
if key.algorithm == Algorithm.ED25519:
|
||||
loader = ed25519.Ed25519PublicKey
|
||||
else:
|
||||
loader = ed448.Ed448PublicKey
|
||||
try:
|
||||
public_key = loader.from_public_bytes(keyptr)
|
||||
except ValueError:
|
||||
raise ValidationFailure('invalid public key')
|
||||
public_key.verify(sig, data)
|
||||
elif _is_gost(key.algorithm):
|
||||
raise UnsupportedAlgorithm(
|
||||
'algorithm "%s" not supported by dnspython' %
|
||||
algorithm_to_text(key.algorithm))
|
||||
else:
|
||||
raise ValidationFailure('unknown algorithm %u' % key.algorithm)
|
||||
|
||||
|
||||
def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
|
||||
"""Validate an RRset against a single signature rdata, throwing an
|
||||
exception if validation is not successful.
|
||||
|
@ -291,143 +356,69 @@ def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
|
|||
if candidate_keys is None:
|
||||
raise ValidationFailure('unknown key')
|
||||
|
||||
# For convenience, allow the rrset to be specified as a (name,
|
||||
# rdataset) tuple as well as a proper rrset
|
||||
if isinstance(rrset, tuple):
|
||||
rrname = rrset[0]
|
||||
rdataset = rrset[1]
|
||||
else:
|
||||
rrname = rrset.name
|
||||
rdataset = rrset
|
||||
|
||||
if now is None:
|
||||
now = time.time()
|
||||
if rrsig.expiration < now:
|
||||
raise ValidationFailure('expired')
|
||||
if rrsig.inception > now:
|
||||
raise ValidationFailure('not yet valid')
|
||||
|
||||
if _is_dsa(rrsig.algorithm):
|
||||
sig_r = rrsig.signature[1:21]
|
||||
sig_s = rrsig.signature[21:]
|
||||
sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
|
||||
_bytes_to_long(sig_s))
|
||||
elif _is_ecdsa(rrsig.algorithm):
|
||||
if rrsig.algorithm == Algorithm.ECDSAP256SHA256:
|
||||
octets = 32
|
||||
else:
|
||||
octets = 48
|
||||
sig_r = rrsig.signature[0:octets]
|
||||
sig_s = rrsig.signature[octets:]
|
||||
sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
|
||||
_bytes_to_long(sig_s))
|
||||
else:
|
||||
sig = rrsig.signature
|
||||
|
||||
data = b''
|
||||
data += rrsig.to_wire(origin=origin)[:18]
|
||||
data += rrsig.signer.to_digestable(origin)
|
||||
|
||||
# Derelativize the name before considering labels.
|
||||
rrname = rrname.derelativize(origin)
|
||||
|
||||
if len(rrname) - 1 < rrsig.labels:
|
||||
raise ValidationFailure('owner name longer than RRSIG labels')
|
||||
elif rrsig.labels < len(rrname) - 1:
|
||||
suffix = rrname.split(rrsig.labels + 1)[1]
|
||||
rrname = dns.name.from_text('*', suffix)
|
||||
rrnamebuf = rrname.to_digestable()
|
||||
rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
|
||||
rrsig.original_ttl)
|
||||
rdatas = [rdata.to_digestable(origin) for rdata in rdataset]
|
||||
for rdata in sorted(rdatas):
|
||||
data += rrnamebuf
|
||||
data += rrfixed
|
||||
rrlen = struct.pack('!H', len(rdata))
|
||||
data += rrlen
|
||||
data += rdata
|
||||
|
||||
chosen_hash = _make_hash(rrsig.algorithm)
|
||||
|
||||
for candidate_key in candidate_keys:
|
||||
# For convenience, allow the rrset to be specified as a (name,
|
||||
# rdataset) tuple as well as a proper rrset
|
||||
if isinstance(rrset, tuple):
|
||||
rrname = rrset[0]
|
||||
rdataset = rrset[1]
|
||||
else:
|
||||
rrname = rrset.name
|
||||
rdataset = rrset
|
||||
|
||||
if now is None:
|
||||
now = time.time()
|
||||
if rrsig.expiration < now:
|
||||
raise ValidationFailure('expired')
|
||||
if rrsig.inception > now:
|
||||
raise ValidationFailure('not yet valid')
|
||||
|
||||
if _is_rsa(rrsig.algorithm):
|
||||
keyptr = candidate_key.key
|
||||
(bytes_,) = struct.unpack('!B', keyptr[0:1])
|
||||
keyptr = keyptr[1:]
|
||||
if bytes_ == 0:
|
||||
(bytes_,) = struct.unpack('!H', keyptr[0:2])
|
||||
keyptr = keyptr[2:]
|
||||
rsa_e = keyptr[0:bytes_]
|
||||
rsa_n = keyptr[bytes_:]
|
||||
try:
|
||||
public_key = rsa.RSAPublicNumbers(
|
||||
_bytes_to_long(rsa_e),
|
||||
_bytes_to_long(rsa_n)).public_key(default_backend())
|
||||
except ValueError:
|
||||
raise ValidationFailure('invalid public key')
|
||||
sig = rrsig.signature
|
||||
elif _is_dsa(rrsig.algorithm):
|
||||
keyptr = candidate_key.key
|
||||
(t,) = struct.unpack('!B', keyptr[0:1])
|
||||
keyptr = keyptr[1:]
|
||||
octets = 64 + t * 8
|
||||
dsa_q = keyptr[0:20]
|
||||
keyptr = keyptr[20:]
|
||||
dsa_p = keyptr[0:octets]
|
||||
keyptr = keyptr[octets:]
|
||||
dsa_g = keyptr[0:octets]
|
||||
keyptr = keyptr[octets:]
|
||||
dsa_y = keyptr[0:octets]
|
||||
try:
|
||||
public_key = dsa.DSAPublicNumbers(
|
||||
_bytes_to_long(dsa_y),
|
||||
dsa.DSAParameterNumbers(
|
||||
_bytes_to_long(dsa_p),
|
||||
_bytes_to_long(dsa_q),
|
||||
_bytes_to_long(dsa_g))).public_key(default_backend())
|
||||
except ValueError:
|
||||
raise ValidationFailure('invalid public key')
|
||||
sig_r = rrsig.signature[1:21]
|
||||
sig_s = rrsig.signature[21:]
|
||||
sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
|
||||
_bytes_to_long(sig_s))
|
||||
elif _is_ecdsa(rrsig.algorithm):
|
||||
keyptr = candidate_key.key
|
||||
if rrsig.algorithm == Algorithm.ECDSAP256SHA256:
|
||||
curve = ec.SECP256R1()
|
||||
octets = 32
|
||||
else:
|
||||
curve = ec.SECP384R1()
|
||||
octets = 48
|
||||
ecdsa_x = keyptr[0:octets]
|
||||
ecdsa_y = keyptr[octets:octets * 2]
|
||||
try:
|
||||
public_key = ec.EllipticCurvePublicNumbers(
|
||||
curve=curve,
|
||||
x=_bytes_to_long(ecdsa_x),
|
||||
y=_bytes_to_long(ecdsa_y)).public_key(default_backend())
|
||||
except ValueError:
|
||||
raise ValidationFailure('invalid public key')
|
||||
sig_r = rrsig.signature[0:octets]
|
||||
sig_s = rrsig.signature[octets:]
|
||||
sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
|
||||
_bytes_to_long(sig_s))
|
||||
|
||||
elif _is_eddsa(rrsig.algorithm):
|
||||
keyptr = candidate_key.key
|
||||
if rrsig.algorithm == Algorithm.ED25519:
|
||||
loader = ed25519.Ed25519PublicKey
|
||||
else:
|
||||
loader = ed448.Ed448PublicKey
|
||||
try:
|
||||
public_key = loader.from_public_bytes(keyptr)
|
||||
except ValueError:
|
||||
raise ValidationFailure('invalid public key')
|
||||
sig = rrsig.signature
|
||||
elif _is_gost(rrsig.algorithm):
|
||||
raise UnsupportedAlgorithm(
|
||||
'algorithm "%s" not supported by dnspython' %
|
||||
algorithm_to_text(rrsig.algorithm))
|
||||
else:
|
||||
raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
|
||||
|
||||
data = b''
|
||||
data += rrsig.to_wire(origin=origin)[:18]
|
||||
data += rrsig.signer.to_digestable(origin)
|
||||
|
||||
if rrsig.labels < len(rrname) - 1:
|
||||
suffix = rrname.split(rrsig.labels + 1)[1]
|
||||
rrname = dns.name.from_text('*', suffix)
|
||||
rrnamebuf = rrname.to_digestable(origin)
|
||||
rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
|
||||
rrsig.original_ttl)
|
||||
rrlist = sorted(rdataset)
|
||||
for rr in rrlist:
|
||||
data += rrnamebuf
|
||||
data += rrfixed
|
||||
rrdata = rr.to_digestable(origin)
|
||||
rrlen = struct.pack('!H', len(rrdata))
|
||||
data += rrlen
|
||||
data += rrdata
|
||||
|
||||
chosen_hash = _make_hash(rrsig.algorithm)
|
||||
try:
|
||||
if _is_rsa(rrsig.algorithm):
|
||||
public_key.verify(sig, data, padding.PKCS1v15(), chosen_hash)
|
||||
elif _is_dsa(rrsig.algorithm):
|
||||
public_key.verify(sig, data, chosen_hash)
|
||||
elif _is_ecdsa(rrsig.algorithm):
|
||||
public_key.verify(sig, data, ec.ECDSA(chosen_hash))
|
||||
elif _is_eddsa(rrsig.algorithm):
|
||||
public_key.verify(sig, data)
|
||||
else:
|
||||
# Raise here for code clarity; this won't actually ever happen
|
||||
# since if the algorithm is really unknown we'd already have
|
||||
# raised an exception above
|
||||
raise ValidationFailure('unknown algorithm %u' %
|
||||
rrsig.algorithm) # pragma: no cover
|
||||
# If we got here, we successfully verified so we can return
|
||||
# without error
|
||||
_validate_signature(sig, data, candidate_key, chosen_hash)
|
||||
return
|
||||
except InvalidSignature:
|
||||
except (InvalidSignature, ValidationFailure):
|
||||
# this happens on an individual validation failure
|
||||
continue
|
||||
# nothing verified -- raise failure:
|
||||
|
@ -546,7 +537,7 @@ def nsec3_hash(domain, salt, iterations, algorithm):
|
|||
domain_encoded = domain.canonicalize().to_wire()
|
||||
|
||||
digest = hashlib.sha1(domain_encoded + salt_encoded).digest()
|
||||
for i in range(iterations):
|
||||
for _ in range(iterations):
|
||||
digest = hashlib.sha1(digest + salt_encoded).digest()
|
||||
|
||||
output = base64.b32encode(digest).decode("utf-8")
|
||||
|
@ -579,3 +570,25 @@ else:
|
|||
validate = _validate # type: ignore
|
||||
validate_rrsig = _validate_rrsig # type: ignore
|
||||
_have_pyca = True
|
||||
|
||||
### BEGIN generated Algorithm constants
|
||||
|
||||
RSAMD5 = Algorithm.RSAMD5
|
||||
DH = Algorithm.DH
|
||||
DSA = Algorithm.DSA
|
||||
ECC = Algorithm.ECC
|
||||
RSASHA1 = Algorithm.RSASHA1
|
||||
DSANSEC3SHA1 = Algorithm.DSANSEC3SHA1
|
||||
RSASHA1NSEC3SHA1 = Algorithm.RSASHA1NSEC3SHA1
|
||||
RSASHA256 = Algorithm.RSASHA256
|
||||
RSASHA512 = Algorithm.RSASHA512
|
||||
ECCGOST = Algorithm.ECCGOST
|
||||
ECDSAP256SHA256 = Algorithm.ECDSAP256SHA256
|
||||
ECDSAP384SHA384 = Algorithm.ECDSAP384SHA384
|
||||
ED25519 = Algorithm.ED25519
|
||||
ED448 = Algorithm.ED448
|
||||
INDIRECT = Algorithm.INDIRECT
|
||||
PRIVATEDNS = Algorithm.PRIVATEDNS
|
||||
PRIVATEOID = Algorithm.PRIVATEOID
|
||||
|
||||
### END generated Algorithm constants
|
||||
|
|
21
lib/dns/dnssec.pyi
Normal file
21
lib/dns/dnssec.pyi
Normal file
|
@ -0,0 +1,21 @@
|
|||
from typing import Union, Dict, Tuple, Optional
|
||||
from . import rdataset, rrset, exception, name, rdtypes, rdata, node
|
||||
import dns.rdtypes.ANY.DS as DS
|
||||
import dns.rdtypes.ANY.DNSKEY as DNSKEY
|
||||
|
||||
_have_pyca : bool
|
||||
|
||||
def validate_rrsig(rrset : Union[Tuple[name.Name, rdataset.Rdataset], rrset.RRset], rrsig : rdata.Rdata, keys : Dict[name.Name, Union[node.Node, rdataset.Rdataset]], origin : Optional[name.Name] = None, now : Optional[int] = None) -> None:
|
||||
...
|
||||
|
||||
def validate(rrset: Union[Tuple[name.Name, rdataset.Rdataset], rrset.RRset], rrsigset : Union[Tuple[name.Name, rdataset.Rdataset], rrset.RRset], keys : Dict[name.Name, Union[node.Node, rdataset.Rdataset]], origin=None, now=None) -> None:
|
||||
...
|
||||
|
||||
class ValidationFailure(exception.DNSException):
|
||||
...
|
||||
|
||||
def make_ds(name : name.Name, key : DNSKEY.DNSKEY, algorithm : str, origin : Optional[name.Name] = None) -> DS.DS:
|
||||
...
|
||||
|
||||
def nsec3_hash(domain: str, salt: Optional[Union[str, bytes]], iterations: int, algo: int) -> str:
|
||||
...
|
10
lib/dns/e164.pyi
Normal file
10
lib/dns/e164.pyi
Normal file
|
@ -0,0 +1,10 @@
|
|||
from typing import Optional, Iterable
|
||||
from . import name, resolver
|
||||
def from_e164(text : str, origin=name.Name(".")) -> name.Name:
|
||||
...
|
||||
|
||||
def to_e164(name : name.Name, origin : Optional[name.Name] = None, want_plus_prefix=True) -> str:
|
||||
...
|
||||
|
||||
def query(number : str, domains : Iterable[str], resolver : Optional[resolver.Resolver] = None) -> resolver.Answer:
|
||||
...
|
132
lib/dns/edns.py
132
lib/dns/edns.py
|
@ -23,6 +23,8 @@ import struct
|
|||
|
||||
import dns.enum
|
||||
import dns.inet
|
||||
import dns.rdata
|
||||
|
||||
|
||||
class OptionType(dns.enum.IntEnum):
|
||||
#: NSID
|
||||
|
@ -45,12 +47,13 @@ class OptionType(dns.enum.IntEnum):
|
|||
PADDING = 12
|
||||
#: CHAIN
|
||||
CHAIN = 13
|
||||
#: EDE (extended-dns-error)
|
||||
EDE = 15
|
||||
|
||||
@classmethod
|
||||
def _maximum(cls):
|
||||
return 65535
|
||||
|
||||
globals().update(OptionType.__members__)
|
||||
|
||||
class Option:
|
||||
|
||||
|
@ -61,7 +64,7 @@ class Option:
|
|||
|
||||
*otype*, an ``int``, is the option type.
|
||||
"""
|
||||
self.otype = otype
|
||||
self.otype = OptionType.make(otype)
|
||||
|
||||
def to_wire(self, file=None):
|
||||
"""Convert an option to wire format.
|
||||
|
@ -149,7 +152,7 @@ class GenericOption(Option):
|
|||
|
||||
def __init__(self, otype, data):
|
||||
super().__init__(otype)
|
||||
self.data = data
|
||||
self.data = dns.rdata.Rdata._as_bytes(data, True)
|
||||
|
||||
def to_wire(self, file=None):
|
||||
if file:
|
||||
|
@ -186,12 +189,18 @@ class ECSOption(Option):
|
|||
self.family = 2
|
||||
if srclen is None:
|
||||
srclen = 56
|
||||
address = dns.rdata.Rdata._as_ipv6_address(address)
|
||||
srclen = dns.rdata.Rdata._as_int(srclen, 0, 128)
|
||||
scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 128)
|
||||
elif af == socket.AF_INET:
|
||||
self.family = 1
|
||||
if srclen is None:
|
||||
srclen = 24
|
||||
else:
|
||||
raise ValueError('Bad ip family')
|
||||
address = dns.rdata.Rdata._as_ipv4_address(address)
|
||||
srclen = dns.rdata.Rdata._as_int(srclen, 0, 32)
|
||||
scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 32)
|
||||
else: # pragma: no cover (this will never happen)
|
||||
raise ValueError('Bad address family')
|
||||
|
||||
self.address = address
|
||||
self.srclen = srclen
|
||||
|
@ -293,10 +302,95 @@ class ECSOption(Option):
|
|||
return cls(addr, src, scope)
|
||||
|
||||
|
||||
class EDECode(dns.enum.IntEnum):
|
||||
OTHER = 0
|
||||
UNSUPPORTED_DNSKEY_ALGORITHM = 1
|
||||
UNSUPPORTED_DS_DIGEST_TYPE = 2
|
||||
STALE_ANSWER = 3
|
||||
FORGED_ANSWER = 4
|
||||
DNSSEC_INDETERMINATE = 5
|
||||
DNSSEC_BOGUS = 6
|
||||
SIGNATURE_EXPIRED = 7
|
||||
SIGNATURE_NOT_YET_VALID = 8
|
||||
DNSKEY_MISSING = 9
|
||||
RRSIGS_MISSING = 10
|
||||
NO_ZONE_KEY_BIT_SET = 11
|
||||
NSEC_MISSING = 12
|
||||
CACHED_ERROR = 13
|
||||
NOT_READY = 14
|
||||
BLOCKED = 15
|
||||
CENSORED = 16
|
||||
FILTERED = 17
|
||||
PROHIBITED = 18
|
||||
STALE_NXDOMAIN_ANSWER = 19
|
||||
NOT_AUTHORITATIVE = 20
|
||||
NOT_SUPPORTED = 21
|
||||
NO_REACHABLE_AUTHORITY = 22
|
||||
NETWORK_ERROR = 23
|
||||
INVALID_DATA = 24
|
||||
|
||||
@classmethod
|
||||
def _maximum(cls):
|
||||
return 65535
|
||||
|
||||
|
||||
class EDEOption(Option):
|
||||
"""Extended DNS Error (EDE, RFC8914)"""
|
||||
|
||||
def __init__(self, code, text=None):
|
||||
"""*code*, a ``dns.edns.EDECode`` or ``str``, the info code of the
|
||||
extended error.
|
||||
|
||||
*text*, a ``str`` or ``None``, specifying additional information about
|
||||
the error.
|
||||
"""
|
||||
|
||||
super().__init__(OptionType.EDE)
|
||||
|
||||
self.code = EDECode.make(code)
|
||||
if text is not None and not isinstance(text, str):
|
||||
raise ValueError('text must be string or None')
|
||||
|
||||
self.code = code
|
||||
self.text = text
|
||||
|
||||
def to_text(self):
|
||||
output = f'EDE {self.code}'
|
||||
if self.text is not None:
|
||||
output += f': {self.text}'
|
||||
return output
|
||||
|
||||
def to_wire(self, file=None):
|
||||
value = struct.pack('!H', self.code)
|
||||
if self.text is not None:
|
||||
value += self.text.encode('utf8')
|
||||
|
||||
if file:
|
||||
file.write(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, otype, parser):
|
||||
code = parser.get_uint16()
|
||||
text = parser.get_remaining()
|
||||
|
||||
if text:
|
||||
if text[-1] == 0: # text MAY be null-terminated
|
||||
text = text[:-1]
|
||||
text = text.decode('utf8')
|
||||
else:
|
||||
text = None
|
||||
|
||||
return cls(code, text)
|
||||
|
||||
|
||||
_type_to_class = {
|
||||
OptionType.ECS: ECSOption
|
||||
OptionType.ECS: ECSOption,
|
||||
OptionType.EDE: EDEOption,
|
||||
}
|
||||
|
||||
|
||||
def get_option_class(otype):
|
||||
"""Return the class for the specified option type.
|
||||
|
||||
|
@ -342,3 +436,29 @@ def option_from_wire(otype, wire, current, olen):
|
|||
parser = dns.wire.Parser(wire, current)
|
||||
with parser.restrict_to(olen):
|
||||
return option_from_wire_parser(otype, parser)
|
||||
|
||||
def register_type(implementation, otype):
|
||||
"""Register the implementation of an option type.
|
||||
|
||||
*implementation*, a ``class``, is a subclass of ``dns.edns.Option``.
|
||||
|
||||
*otype*, an ``int``, is the option type.
|
||||
"""
|
||||
|
||||
_type_to_class[otype] = implementation
|
||||
|
||||
### BEGIN generated OptionType constants
|
||||
|
||||
NSID = OptionType.NSID
|
||||
DAU = OptionType.DAU
|
||||
DHU = OptionType.DHU
|
||||
N3U = OptionType.N3U
|
||||
ECS = OptionType.ECS
|
||||
EXPIRE = OptionType.EXPIRE
|
||||
COOKIE = OptionType.COOKIE
|
||||
KEEPALIVE = OptionType.KEEPALIVE
|
||||
PADDING = OptionType.PADDING
|
||||
CHAIN = OptionType.CHAIN
|
||||
EDE = OptionType.EDE
|
||||
|
||||
### END generated OptionType constants
|
||||
|
|
10
lib/dns/entropy.pyi
Normal file
10
lib/dns/entropy.pyi
Normal file
|
@ -0,0 +1,10 @@
|
|||
from typing import Optional
|
||||
from random import SystemRandom
|
||||
|
||||
system_random : Optional[SystemRandom]
|
||||
|
||||
def random_16() -> int:
|
||||
pass
|
||||
|
||||
def between(first: int, last: int) -> int:
|
||||
pass
|
|
@ -75,7 +75,7 @@ class IntEnum(enum.IntEnum):
|
|||
|
||||
@classmethod
|
||||
def _maximum(cls):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
@classmethod
|
||||
def _short_name(cls):
|
||||
|
|
|
@ -126,3 +126,17 @@ class Timeout(DNSException):
|
|||
"""The DNS operation timed out."""
|
||||
supp_kwargs = {'timeout'}
|
||||
fmt = "The DNS operation timed out after {timeout} seconds"
|
||||
|
||||
|
||||
class ExceptionWrapper:
|
||||
def __init__(self, exception_class):
|
||||
self.exception_class = exception_class
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if exc_type is not None and not isinstance(exc_val,
|
||||
self.exception_class):
|
||||
raise self.exception_class(str(exc_val)) from exc_val
|
||||
return False
|
||||
|
|
10
lib/dns/exception.pyi
Normal file
10
lib/dns/exception.pyi
Normal file
|
@ -0,0 +1,10 @@
|
|||
from typing import Set, Optional, Dict
|
||||
|
||||
class DNSException(Exception):
|
||||
supp_kwargs : Set[str]
|
||||
kwargs : Optional[Dict]
|
||||
fmt : Optional[str]
|
||||
|
||||
class SyntaxError(DNSException): ...
|
||||
class FormError(DNSException): ...
|
||||
class Timeout(DNSException): ...
|
|
@ -37,8 +37,6 @@ class Flag(enum.IntFlag):
|
|||
#: Checking Disabled
|
||||
CD = 0x0010
|
||||
|
||||
globals().update(Flag.__members__)
|
||||
|
||||
|
||||
# EDNS flags
|
||||
|
||||
|
@ -47,9 +45,6 @@ class EDNSFlag(enum.IntFlag):
|
|||
DO = 0x8000
|
||||
|
||||
|
||||
globals().update(EDNSFlag.__members__)
|
||||
|
||||
|
||||
def _from_text(text, enum_class):
|
||||
flags = 0
|
||||
tokens = text.split()
|
||||
|
@ -104,3 +99,21 @@ def edns_to_text(flags):
|
|||
"""
|
||||
|
||||
return _to_text(flags, EDNSFlag)
|
||||
|
||||
### BEGIN generated Flag constants
|
||||
|
||||
QR = Flag.QR
|
||||
AA = Flag.AA
|
||||
TC = Flag.TC
|
||||
RD = Flag.RD
|
||||
RA = Flag.RA
|
||||
AD = Flag.AD
|
||||
CD = Flag.CD
|
||||
|
||||
### END generated Flag constants
|
||||
|
||||
### BEGIN generated EDNSFlag constants
|
||||
|
||||
DO = EDNSFlag.DO
|
||||
|
||||
### END generated EDNSFlag constants
|
||||
|
|
|
@ -28,11 +28,12 @@ def from_text(text):
|
|||
Returns a tuple of three ``int`` values ``(start, stop, step)``.
|
||||
"""
|
||||
|
||||
# TODO, figure out the bounds on start, stop and step.
|
||||
start = -1
|
||||
stop = -1
|
||||
step = 1
|
||||
cur = ''
|
||||
state = 0
|
||||
# state 0 1 2 3 4
|
||||
# state 0 1 2
|
||||
# x - y / z
|
||||
|
||||
if text and text[0] == '-':
|
||||
|
@ -42,28 +43,27 @@ def from_text(text):
|
|||
if c == '-' and state == 0:
|
||||
start = int(cur)
|
||||
cur = ''
|
||||
state = 2
|
||||
state = 1
|
||||
elif c == '/':
|
||||
stop = int(cur)
|
||||
cur = ''
|
||||
state = 4
|
||||
state = 2
|
||||
elif c.isdigit():
|
||||
cur += c
|
||||
else:
|
||||
raise dns.exception.SyntaxError("Could not parse %s" % (c))
|
||||
|
||||
if state in (1, 3):
|
||||
raise dns.exception.SyntaxError()
|
||||
|
||||
if state == 2:
|
||||
if state == 0:
|
||||
raise dns.exception.SyntaxError("no stop value specified")
|
||||
elif state == 1:
|
||||
stop = int(cur)
|
||||
|
||||
if state == 4:
|
||||
else:
|
||||
assert state == 2
|
||||
step = int(cur)
|
||||
|
||||
assert step >= 1
|
||||
assert start >= 0
|
||||
assert start <= stop
|
||||
# TODO, can start == stop?
|
||||
if start > stop:
|
||||
raise dns.exception.SyntaxError('start must be <= stop')
|
||||
|
||||
return (start, stop, step)
|
||||
|
|
70
lib/dns/immutable.py
Normal file
70
lib/dns/immutable.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
import collections.abc
|
||||
import sys
|
||||
|
||||
# pylint: disable=unused-import
|
||||
if sys.version_info >= (3, 7):
|
||||
odict = dict
|
||||
from dns._immutable_ctx import immutable
|
||||
else:
|
||||
# pragma: no cover
|
||||
from collections import OrderedDict as odict
|
||||
from dns._immutable_attr import immutable # noqa
|
||||
# pylint: enable=unused-import
|
||||
|
||||
|
||||
@immutable
|
||||
class Dict(collections.abc.Mapping):
|
||||
def __init__(self, dictionary, no_copy=False):
|
||||
"""Make an immutable dictionary from the specified dictionary.
|
||||
|
||||
If *no_copy* is `True`, then *dictionary* will be wrapped instead
|
||||
of copied. Only set this if you are sure there will be no external
|
||||
references to the dictionary.
|
||||
"""
|
||||
if no_copy and isinstance(dictionary, odict):
|
||||
self._odict = dictionary
|
||||
else:
|
||||
self._odict = odict(dictionary)
|
||||
self._hash = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._odict.__getitem__(key)
|
||||
|
||||
def __hash__(self): # pylint: disable=invalid-hash-returned
|
||||
if self._hash is None:
|
||||
h = 0
|
||||
for key in sorted(self._odict.keys()):
|
||||
h ^= hash(key)
|
||||
object.__setattr__(self, '_hash', h)
|
||||
# this does return an int, but pylint doesn't figure that out
|
||||
return self._hash
|
||||
|
||||
def __len__(self):
|
||||
return len(self._odict)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._odict)
|
||||
|
||||
|
||||
def constify(o):
|
||||
"""
|
||||
Convert mutable types to immutable types.
|
||||
"""
|
||||
if isinstance(o, bytearray):
|
||||
return bytes(o)
|
||||
if isinstance(o, tuple):
|
||||
try:
|
||||
hash(o)
|
||||
return o
|
||||
except Exception:
|
||||
return tuple(constify(elt) for elt in o)
|
||||
if isinstance(o, list):
|
||||
return tuple(constify(elt) for elt in o)
|
||||
if isinstance(o, dict):
|
||||
cdict = odict()
|
||||
for k, v in o.items():
|
||||
cdict[k] = constify(v)
|
||||
return Dict(cdict, True)
|
||||
return o
|
|
@ -162,7 +162,7 @@ def low_level_address_tuple(high_tuple, af=None):
|
|||
return (addrpart, port, 0, int(scope))
|
||||
try:
|
||||
return (addrpart, port, 0, socket.if_nametoindex(scope))
|
||||
except AttributeError:
|
||||
except AttributeError: # pragma: no cover (we can't really test this)
|
||||
ai_flags = socket.AI_NUMERICHOST
|
||||
((*_, tup), *_) = socket.getaddrinfo(address, port, flags=ai_flags)
|
||||
return tup
|
||||
|
|
4
lib/dns/inet.pyi
Normal file
4
lib/dns/inet.pyi
Normal file
|
@ -0,0 +1,4 @@
|
|||
from typing import Union
|
||||
from socket import AddressFamily
|
||||
|
||||
AF_INET6 : Union[int, AddressFamily]
|
|
@ -121,7 +121,13 @@ def inet_aton(text, ignore_scope=False):
|
|||
elif l > 2:
|
||||
raise dns.exception.SyntaxError
|
||||
|
||||
if text == b'::':
|
||||
if text == b'':
|
||||
raise dns.exception.SyntaxError
|
||||
elif text.endswith(b':') and not text.endswith(b'::'):
|
||||
raise dns.exception.SyntaxError
|
||||
elif text.startswith(b':') and not text.startswith(b'::'):
|
||||
raise dns.exception.SyntaxError
|
||||
elif text == b'::':
|
||||
text = b'0::'
|
||||
#
|
||||
# Get rid of the icky dot-quad syntax if we have it.
|
||||
|
@ -129,9 +135,9 @@ def inet_aton(text, ignore_scope=False):
|
|||
m = _v4_ending.match(text)
|
||||
if m is not None:
|
||||
b = dns.ipv4.inet_aton(m.group(2))
|
||||
text = (u"{}:{:02x}{:02x}:{:02x}{:02x}".format(m.group(1).decode(),
|
||||
b[0], b[1], b[2],
|
||||
b[3])).encode()
|
||||
text = ("{}:{:02x}{:02x}:{:02x}{:02x}".format(m.group(1).decode(),
|
||||
b[0], b[1], b[2],
|
||||
b[3])).encode()
|
||||
#
|
||||
# Try to turn '::<whatever>' into ':<whatever>'; if no match try to
|
||||
# turn '<whatever>::' into '<whatever>:'
|
||||
|
@ -157,7 +163,7 @@ def inet_aton(text, ignore_scope=False):
|
|||
if seen_empty:
|
||||
raise dns.exception.SyntaxError
|
||||
seen_empty = True
|
||||
for i in range(0, 8 - l + 1):
|
||||
for _ in range(0, 8 - l + 1):
|
||||
canonical.append(b'0000')
|
||||
else:
|
||||
lc = len(c)
|
||||
|
|
|
@ -35,6 +35,7 @@ import dns.rdataclass
|
|||
import dns.rdatatype
|
||||
import dns.rrset
|
||||
import dns.renderer
|
||||
import dns.ttl
|
||||
import dns.tsig
|
||||
import dns.rdtypes.ANY.OPT
|
||||
import dns.rdtypes.ANY.TSIG
|
||||
|
@ -80,6 +81,21 @@ class Truncated(dns.exception.DNSException):
|
|||
return self.kwargs['message']
|
||||
|
||||
|
||||
class NotQueryResponse(dns.exception.DNSException):
|
||||
"""Message is not a response to a query."""
|
||||
|
||||
|
||||
class ChainTooLong(dns.exception.DNSException):
|
||||
"""The CNAME chain is too long."""
|
||||
|
||||
|
||||
class AnswerForNXDOMAIN(dns.exception.DNSException):
|
||||
"""The rcode is NXDOMAIN but an answer was found."""
|
||||
|
||||
class NoPreviousName(dns.exception.SyntaxError):
|
||||
"""No previous name was known."""
|
||||
|
||||
|
||||
class MessageSection(dns.enum.IntEnum):
|
||||
"""Message sections"""
|
||||
QUESTION = 0
|
||||
|
@ -91,8 +107,15 @@ class MessageSection(dns.enum.IntEnum):
|
|||
def _maximum(cls):
|
||||
return 3
|
||||
|
||||
globals().update(MessageSection.__members__)
|
||||
|
||||
class MessageError:
|
||||
def __init__(self, exception, offset):
|
||||
self.exception = exception
|
||||
self.offset = offset
|
||||
|
||||
|
||||
DEFAULT_EDNS_PAYLOAD = 1232
|
||||
MAX_CHAIN = 16
|
||||
|
||||
class Message:
|
||||
"""A DNS message."""
|
||||
|
@ -115,6 +138,7 @@ class Message:
|
|||
self.origin = None
|
||||
self.tsig_ctx = None
|
||||
self.index = {}
|
||||
self.errors = []
|
||||
|
||||
@property
|
||||
def question(self):
|
||||
|
@ -169,10 +193,8 @@ class Message:
|
|||
|
||||
s = io.StringIO()
|
||||
s.write('id %d\n' % self.id)
|
||||
s.write('opcode %s\n' %
|
||||
dns.opcode.to_text(dns.opcode.from_flags(self.flags)))
|
||||
rc = dns.rcode.from_flags(self.flags, self.ednsflags)
|
||||
s.write('rcode %s\n' % dns.rcode.to_text(rc))
|
||||
s.write('opcode %s\n' % dns.opcode.to_text(self.opcode()))
|
||||
s.write('rcode %s\n' % dns.rcode.to_text(self.rcode()))
|
||||
s.write('flags %s\n' % dns.flags.to_text(self.flags))
|
||||
if self.edns >= 0:
|
||||
s.write('edns %s\n' % self.edns)
|
||||
|
@ -221,7 +243,8 @@ class Message:
|
|||
return not self.__eq__(other)
|
||||
|
||||
def is_response(self, other):
|
||||
"""Is *other* a response this message?
|
||||
"""Is *other*, also a ``dns.message.Message``, a response to this
|
||||
message?
|
||||
|
||||
Returns a ``bool``.
|
||||
"""
|
||||
|
@ -231,9 +254,13 @@ class Message:
|
|||
dns.opcode.from_flags(self.flags) != \
|
||||
dns.opcode.from_flags(other.flags):
|
||||
return False
|
||||
if dns.rcode.from_flags(other.flags, other.ednsflags) != \
|
||||
dns.rcode.NOERROR:
|
||||
return True
|
||||
if other.rcode() in {dns.rcode.FORMERR, dns.rcode.SERVFAIL,
|
||||
dns.rcode.NOTIMP, dns.rcode.REFUSED}:
|
||||
# We don't check the question section in these cases if
|
||||
# the other question section is empty, even though they
|
||||
# still really ought to have a question section.
|
||||
if len(other.question) == 0:
|
||||
return True
|
||||
if dns.opcode.is_update(self.flags):
|
||||
# This is assuming the "sender doesn't include anything
|
||||
# from the update", but we don't care to check the other
|
||||
|
@ -330,7 +357,8 @@ class Message:
|
|||
return rrset
|
||||
else:
|
||||
for rrset in section:
|
||||
if rrset.match(name, rdclass, rdtype, covers, deleting):
|
||||
if rrset.full_match(name, rdclass, rdtype, covers,
|
||||
deleting):
|
||||
return rrset
|
||||
if not create:
|
||||
raise KeyError
|
||||
|
@ -403,8 +431,8 @@ class Message:
|
|||
*multi*, a ``bool``, should be set to ``True`` if this message is
|
||||
part of a multiple message sequence.
|
||||
|
||||
*tsig_ctx*, a ``hmac.HMAC`` object, the ongoing TSIG context, used
|
||||
when signing zone transfers.
|
||||
*tsig_ctx*, a ``dns.tsig.HMACTSig`` or ``dns.tsig.GSSTSig`` object, the
|
||||
ongoing TSIG context, used when signing zone transfers.
|
||||
|
||||
Raises ``dns.exception.TooBig`` if *max_size* was exceeded.
|
||||
|
||||
|
@ -467,8 +495,8 @@ class Message:
|
|||
*key*, a ``dns.tsig.Key`` is the key to use. If a key is specified,
|
||||
the *keyring* and *algorithm* fields are not used.
|
||||
|
||||
*keyring*, a ``dict`` or ``dns.tsig.Key``, is either the TSIG
|
||||
keyring or key to use.
|
||||
*keyring*, a ``dict``, ``callable`` or ``dns.tsig.Key``, is either
|
||||
the TSIG keyring or key to use.
|
||||
|
||||
The format of a keyring dict is a mapping from TSIG key name, as
|
||||
``dns.name.Name`` to ``dns.tsig.Key`` or a TSIG secret, a ``bytes``.
|
||||
|
@ -476,7 +504,9 @@ class Message:
|
|||
used will be the first key in the *keyring*. Note that the order of
|
||||
keys in a dictionary is not defined, so applications should supply a
|
||||
keyname when a ``dict`` keyring is used, unless they know the keyring
|
||||
contains only one key.
|
||||
contains only one key. If a ``callable`` keyring is specified, the
|
||||
callable will be called with the message and the keyname, and is
|
||||
expected to return a key.
|
||||
|
||||
*keyname*, a ``dns.name.Name``, ``str`` or ``None``, the name of
|
||||
thes TSIG key to use; defaults to ``None``. If *keyring* is a
|
||||
|
@ -497,7 +527,10 @@ class Message:
|
|||
"""
|
||||
|
||||
if isinstance(keyring, dns.tsig.Key):
|
||||
self.keyring = keyring
|
||||
key = keyring
|
||||
keyname = key.name
|
||||
elif callable(keyring):
|
||||
key = keyring(self, keyname)
|
||||
else:
|
||||
if isinstance(keyname, str):
|
||||
keyname = dns.name.from_text(keyname)
|
||||
|
@ -506,7 +539,7 @@ class Message:
|
|||
key = keyring[keyname]
|
||||
if isinstance(key, bytes):
|
||||
key = dns.tsig.Key(keyname, key, algorithm)
|
||||
self.keyring = key
|
||||
self.keyring = key
|
||||
if original_id is None:
|
||||
original_id = self.id
|
||||
self.tsig = self._make_tsig(keyname, self.keyring.algorithm, 0, fudge,
|
||||
|
@ -545,13 +578,13 @@ class Message:
|
|||
return bool(self.tsig)
|
||||
|
||||
@staticmethod
|
||||
def _make_opt(flags=0, payload=1280, options=None):
|
||||
def _make_opt(flags=0, payload=DEFAULT_EDNS_PAYLOAD, options=None):
|
||||
opt = dns.rdtypes.ANY.OPT.OPT(payload, dns.rdatatype.OPT,
|
||||
options or ())
|
||||
return dns.rrset.from_rdata(dns.name.root, int(flags), opt)
|
||||
|
||||
def use_edns(self, edns=0, ednsflags=0, payload=1280, request_payload=None,
|
||||
options=None):
|
||||
def use_edns(self, edns=0, ednsflags=0, payload=DEFAULT_EDNS_PAYLOAD,
|
||||
request_payload=None, options=None):
|
||||
"""Configure EDNS behavior.
|
||||
|
||||
*edns*, an ``int``, is the EDNS level to use. Specifying
|
||||
|
@ -575,26 +608,21 @@ class Message:
|
|||
|
||||
if edns is None or edns is False:
|
||||
edns = -1
|
||||
if edns is True:
|
||||
elif edns is True:
|
||||
edns = 0
|
||||
if request_payload is None:
|
||||
request_payload = payload
|
||||
if edns < 0:
|
||||
ednsflags = 0
|
||||
payload = 0
|
||||
request_payload = 0
|
||||
options = []
|
||||
self.opt = None
|
||||
self.request_payload = 0
|
||||
else:
|
||||
# make sure the EDNS version in ednsflags agrees with edns
|
||||
ednsflags &= 0xFF00FFFF
|
||||
ednsflags |= (edns << 16)
|
||||
if options is None:
|
||||
options = []
|
||||
if edns >= 0:
|
||||
self.opt = self._make_opt(ednsflags, payload, options)
|
||||
else:
|
||||
self.opt = None
|
||||
self.request_payload = request_payload
|
||||
if request_payload is None:
|
||||
request_payload = payload
|
||||
self.request_payload = request_payload
|
||||
|
||||
@property
|
||||
def edns(self):
|
||||
|
@ -650,7 +678,7 @@ class Message:
|
|||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
return dns.rcode.from_flags(self.flags, self.ednsflags)
|
||||
return dns.rcode.from_flags(int(self.flags), int(self.ednsflags))
|
||||
|
||||
def set_rcode(self, rcode):
|
||||
"""Set the rcode.
|
||||
|
@ -668,7 +696,7 @@ class Message:
|
|||
|
||||
Returns an ``int``.
|
||||
"""
|
||||
return dns.opcode.from_flags(self.flags)
|
||||
return dns.opcode.from_flags(int(self.flags))
|
||||
|
||||
def set_opcode(self, opcode):
|
||||
"""Set the opcode.
|
||||
|
@ -682,9 +710,13 @@ class Message:
|
|||
# What the caller picked is fine.
|
||||
return value
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
def _parse_rr_header(self, section, name, rdclass, rdtype):
|
||||
return (rdclass, rdtype, None, False)
|
||||
|
||||
# pylint: enable=unused-argument
|
||||
|
||||
def _parse_special_rr_header(self, section, count, position,
|
||||
name, rdclass, rdtype):
|
||||
if rdtype == dns.rdatatype.OPT:
|
||||
|
@ -699,14 +731,129 @@ class Message:
|
|||
return (rdclass, rdtype, None, False)
|
||||
|
||||
|
||||
class ChainingResult:
|
||||
"""The result of a call to dns.message.QueryMessage.resolve_chaining().
|
||||
|
||||
The ``answer`` attribute is the answer RRSet, or ``None`` if it doesn't
|
||||
exist.
|
||||
|
||||
The ``canonical_name`` attribute is the canonical name after all
|
||||
chaining has been applied (this is the name as ``rrset.name`` in cases
|
||||
where rrset is not ``None``).
|
||||
|
||||
The ``minimum_ttl`` attribute is the minimum TTL, i.e. the TTL to
|
||||
use if caching the data. It is the smallest of all the CNAME TTLs
|
||||
and either the answer TTL if it exists or the SOA TTL and SOA
|
||||
minimum values for negative answers.
|
||||
|
||||
The ``cnames`` attribute is a list of all the CNAME RRSets followed to
|
||||
get to the canonical name.
|
||||
"""
|
||||
def __init__(self, canonical_name, answer, minimum_ttl, cnames):
|
||||
self.canonical_name = canonical_name
|
||||
self.answer = answer
|
||||
self.minimum_ttl = minimum_ttl
|
||||
self.cnames = cnames
|
||||
|
||||
|
||||
class QueryMessage(Message):
|
||||
pass
|
||||
def resolve_chaining(self):
|
||||
"""Follow the CNAME chain in the response to determine the answer
|
||||
RRset.
|
||||
|
||||
Raises ``dns.message.NotQueryResponse`` if the message is not
|
||||
a response.
|
||||
|
||||
Raises ``dns.message.ChainTooLong`` if the CNAME chain is too long.
|
||||
|
||||
Raises ``dns.message.AnswerForNXDOMAIN`` if the rcode is NXDOMAIN
|
||||
but an answer was found.
|
||||
|
||||
Raises ``dns.exception.FormError`` if the question count is not 1.
|
||||
|
||||
Returns a ChainingResult object.
|
||||
"""
|
||||
if self.flags & dns.flags.QR == 0:
|
||||
raise NotQueryResponse
|
||||
if len(self.question) != 1:
|
||||
raise dns.exception.FormError
|
||||
question = self.question[0]
|
||||
qname = question.name
|
||||
min_ttl = dns.ttl.MAX_TTL
|
||||
answer = None
|
||||
count = 0
|
||||
cnames = []
|
||||
while count < MAX_CHAIN:
|
||||
try:
|
||||
answer = self.find_rrset(self.answer, qname, question.rdclass,
|
||||
question.rdtype)
|
||||
min_ttl = min(min_ttl, answer.ttl)
|
||||
break
|
||||
except KeyError:
|
||||
if question.rdtype != dns.rdatatype.CNAME:
|
||||
try:
|
||||
crrset = self.find_rrset(self.answer, qname,
|
||||
question.rdclass,
|
||||
dns.rdatatype.CNAME)
|
||||
cnames.append(crrset)
|
||||
min_ttl = min(min_ttl, crrset.ttl)
|
||||
for rd in crrset:
|
||||
qname = rd.target
|
||||
break
|
||||
count += 1
|
||||
continue
|
||||
except KeyError:
|
||||
# Exit the chaining loop
|
||||
break
|
||||
else:
|
||||
# Exit the chaining loop
|
||||
break
|
||||
if count >= MAX_CHAIN:
|
||||
raise ChainTooLong
|
||||
if self.rcode() == dns.rcode.NXDOMAIN and answer is not None:
|
||||
raise AnswerForNXDOMAIN
|
||||
if answer is None:
|
||||
# Further minimize the TTL with NCACHE.
|
||||
auname = qname
|
||||
while True:
|
||||
# Look for an SOA RR whose owner name is a superdomain
|
||||
# of qname.
|
||||
try:
|
||||
srrset = self.find_rrset(self.authority, auname,
|
||||
question.rdclass,
|
||||
dns.rdatatype.SOA)
|
||||
min_ttl = min(min_ttl, srrset.ttl, srrset[0].minimum)
|
||||
break
|
||||
except KeyError:
|
||||
try:
|
||||
auname = auname.parent()
|
||||
except dns.name.NoParent:
|
||||
break
|
||||
return ChainingResult(qname, answer, min_ttl, cnames)
|
||||
|
||||
def canonical_name(self):
|
||||
"""Return the canonical name of the first name in the question
|
||||
section.
|
||||
|
||||
Raises ``dns.message.NotQueryResponse`` if the message is not
|
||||
a response.
|
||||
|
||||
Raises ``dns.message.ChainTooLong`` if the CNAME chain is too long.
|
||||
|
||||
Raises ``dns.message.AnswerForNXDOMAIN`` if the rcode is NXDOMAIN
|
||||
but an answer was found.
|
||||
|
||||
Raises ``dns.exception.FormError`` if the question count is not 1.
|
||||
"""
|
||||
return self.resolve_chaining().canonical_name
|
||||
|
||||
|
||||
def _maybe_import_update():
|
||||
# We avoid circular imports by doing this here. We do it in another
|
||||
# function as doing it in _message_factory_from_opcode() makes "dns"
|
||||
# a local symbol, and the first line fails :)
|
||||
|
||||
# pylint: disable=redefined-outer-name,import-outside-toplevel,unused-import
|
||||
import dns.update # noqa: F401
|
||||
|
||||
|
||||
|
@ -733,11 +880,14 @@ class _WireReader:
|
|||
ignore_trailing: Ignore trailing junk at end of request?
|
||||
multi: Is this message part of a multi-message sequence?
|
||||
DNS dynamic updates.
|
||||
continue_on_error: try to extract as much information as possible from
|
||||
the message, accumulating MessageErrors in the *errors* attribute instead of
|
||||
raising them.
|
||||
"""
|
||||
|
||||
def __init__(self, wire, initialize_message, question_only=False,
|
||||
one_rr_per_rrset=False, ignore_trailing=False,
|
||||
keyring=None, multi=False):
|
||||
keyring=None, multi=False, continue_on_error=False):
|
||||
self.parser = dns.wire.Parser(wire)
|
||||
self.message = None
|
||||
self.initialize_message = initialize_message
|
||||
|
@ -746,6 +896,8 @@ class _WireReader:
|
|||
self.ignore_trailing = ignore_trailing
|
||||
self.keyring = keyring
|
||||
self.multi = multi
|
||||
self.continue_on_error = continue_on_error
|
||||
self.errors = []
|
||||
|
||||
def _get_question(self, section_number, qcount):
|
||||
"""Read the next *qcount* records from the wire data and add them to
|
||||
|
@ -753,7 +905,7 @@ class _WireReader:
|
|||
"""
|
||||
|
||||
section = self.message.sections[section_number]
|
||||
for i in range(qcount):
|
||||
for _ in range(qcount):
|
||||
qname = self.parser.get_name(self.message.origin)
|
||||
(rdtype, rdclass) = self.parser.get_struct('!HH')
|
||||
(rdclass, rdtype, _, _) = \
|
||||
|
@ -762,11 +914,14 @@ class _WireReader:
|
|||
self.message.find_rrset(section, qname, rdclass, rdtype,
|
||||
create=True, force_unique=True)
|
||||
|
||||
def _add_error(self, e):
|
||||
self.errors.append(MessageError(e, self.parser.current))
|
||||
|
||||
def _get_section(self, section_number, count):
|
||||
"""Read the next I{count} records from the wire data and add them to
|
||||
the specified section.
|
||||
|
||||
section: the section of the message to which to add records
|
||||
section_number: the section of the message to which to add records
|
||||
count: the number of records to read
|
||||
"""
|
||||
|
||||
|
@ -789,53 +944,65 @@ class _WireReader:
|
|||
(rdclass, rdtype, deleting, empty) = \
|
||||
self.message._parse_rr_header(section_number,
|
||||
name, rdclass, rdtype)
|
||||
if empty:
|
||||
if rdlen > 0:
|
||||
raise dns.exception.FormError
|
||||
rd = None
|
||||
covers = dns.rdatatype.NONE
|
||||
else:
|
||||
with self.parser.restrict_to(rdlen):
|
||||
rd = dns.rdata.from_wire_parser(rdclass, rdtype,
|
||||
self.parser,
|
||||
self.message.origin)
|
||||
covers = rd.covers()
|
||||
if self.message.xfr and rdtype == dns.rdatatype.SOA:
|
||||
force_unique = True
|
||||
if rdtype == dns.rdatatype.OPT:
|
||||
self.message.opt = dns.rrset.from_rdata(name, ttl, rd)
|
||||
elif rdtype == dns.rdatatype.TSIG:
|
||||
if self.keyring is None:
|
||||
raise UnknownTSIGKey('got signed message without keyring')
|
||||
if isinstance(self.keyring, dict):
|
||||
key = self.keyring.get(absolute_name)
|
||||
if isinstance(key, bytes):
|
||||
key = dns.tsig.Key(absolute_name, key, rd.algorithm)
|
||||
try:
|
||||
rdata_start = self.parser.current
|
||||
if empty:
|
||||
if rdlen > 0:
|
||||
raise dns.exception.FormError
|
||||
rd = None
|
||||
covers = dns.rdatatype.NONE
|
||||
else:
|
||||
key = self.keyring
|
||||
if key is None:
|
||||
raise UnknownTSIGKey("key '%s' unknown" % name)
|
||||
self.message.keyring = key
|
||||
self.message.tsig_ctx = \
|
||||
dns.tsig.validate(self.parser.wire,
|
||||
key,
|
||||
absolute_name,
|
||||
rd,
|
||||
int(time.time()),
|
||||
self.message.request_mac,
|
||||
rr_start,
|
||||
self.message.tsig_ctx,
|
||||
self.multi)
|
||||
self.message.tsig = dns.rrset.from_rdata(absolute_name, 0, rd)
|
||||
else:
|
||||
rrset = self.message.find_rrset(section, name,
|
||||
rdclass, rdtype, covers,
|
||||
deleting, True,
|
||||
force_unique)
|
||||
if rd is not None:
|
||||
if ttl > 0x7fffffff:
|
||||
ttl = 0
|
||||
rrset.add(rd, ttl)
|
||||
with self.parser.restrict_to(rdlen):
|
||||
rd = dns.rdata.from_wire_parser(rdclass, rdtype,
|
||||
self.parser,
|
||||
self.message.origin)
|
||||
covers = rd.covers()
|
||||
if self.message.xfr and rdtype == dns.rdatatype.SOA:
|
||||
force_unique = True
|
||||
if rdtype == dns.rdatatype.OPT:
|
||||
self.message.opt = dns.rrset.from_rdata(name, ttl, rd)
|
||||
elif rdtype == dns.rdatatype.TSIG:
|
||||
if self.keyring is None:
|
||||
raise UnknownTSIGKey('got signed message without '
|
||||
'keyring')
|
||||
if isinstance(self.keyring, dict):
|
||||
key = self.keyring.get(absolute_name)
|
||||
if isinstance(key, bytes):
|
||||
key = dns.tsig.Key(absolute_name, key, rd.algorithm)
|
||||
elif callable(self.keyring):
|
||||
key = self.keyring(self.message, absolute_name)
|
||||
else:
|
||||
key = self.keyring
|
||||
if key is None:
|
||||
raise UnknownTSIGKey("key '%s' unknown" % name)
|
||||
self.message.keyring = key
|
||||
self.message.tsig_ctx = \
|
||||
dns.tsig.validate(self.parser.wire,
|
||||
key,
|
||||
absolute_name,
|
||||
rd,
|
||||
int(time.time()),
|
||||
self.message.request_mac,
|
||||
rr_start,
|
||||
self.message.tsig_ctx,
|
||||
self.multi)
|
||||
self.message.tsig = dns.rrset.from_rdata(absolute_name, 0,
|
||||
rd)
|
||||
else:
|
||||
rrset = self.message.find_rrset(section, name,
|
||||
rdclass, rdtype, covers,
|
||||
deleting, True,
|
||||
force_unique)
|
||||
if rd is not None:
|
||||
if ttl > 0x7fffffff:
|
||||
ttl = 0
|
||||
rrset.add(rd, ttl)
|
||||
except Exception as e:
|
||||
if self.continue_on_error:
|
||||
self._add_error(e)
|
||||
self.parser.seek(rdata_start + rdlen)
|
||||
else:
|
||||
raise
|
||||
|
||||
def read(self):
|
||||
"""Read a wire format DNS message and build a dns.message.Message
|
||||
|
@ -847,73 +1014,86 @@ class _WireReader:
|
|||
self.parser.get_struct('!HHHHHH')
|
||||
factory = _message_factory_from_opcode(dns.opcode.from_flags(flags))
|
||||
self.message = factory(id=id)
|
||||
self.message.flags = flags
|
||||
self.message.flags = dns.flags.Flag(flags)
|
||||
self.initialize_message(self.message)
|
||||
self.one_rr_per_rrset = \
|
||||
self.message._get_one_rr_per_rrset(self.one_rr_per_rrset)
|
||||
self._get_question(MessageSection.QUESTION, qcount)
|
||||
if self.question_only:
|
||||
return
|
||||
self._get_section(MessageSection.ANSWER, ancount)
|
||||
self._get_section(MessageSection.AUTHORITY, aucount)
|
||||
self._get_section(MessageSection.ADDITIONAL, adcount)
|
||||
if not self.ignore_trailing and self.parser.remaining() != 0:
|
||||
raise TrailingJunk
|
||||
if self.multi and self.message.tsig_ctx and not self.message.had_tsig:
|
||||
self.message.tsig_ctx.update(self.parser.wire)
|
||||
try:
|
||||
self._get_question(MessageSection.QUESTION, qcount)
|
||||
if self.question_only:
|
||||
return self.message
|
||||
self._get_section(MessageSection.ANSWER, ancount)
|
||||
self._get_section(MessageSection.AUTHORITY, aucount)
|
||||
self._get_section(MessageSection.ADDITIONAL, adcount)
|
||||
if not self.ignore_trailing and self.parser.remaining() != 0:
|
||||
raise TrailingJunk
|
||||
if self.multi and self.message.tsig_ctx and \
|
||||
not self.message.had_tsig:
|
||||
self.message.tsig_ctx.update(self.parser.wire)
|
||||
except Exception as e:
|
||||
if self.continue_on_error:
|
||||
self._add_error(e)
|
||||
else:
|
||||
raise
|
||||
return self.message
|
||||
|
||||
|
||||
def from_wire(wire, keyring=None, request_mac=b'', xfr=False, origin=None,
|
||||
tsig_ctx=None, multi=False,
|
||||
question_only=False, one_rr_per_rrset=False,
|
||||
ignore_trailing=False, raise_on_truncation=False):
|
||||
"""Convert a DNS wire format message into a message
|
||||
object.
|
||||
ignore_trailing=False, raise_on_truncation=False,
|
||||
continue_on_error=False):
|
||||
"""Convert a DNS wire format message into a message object.
|
||||
|
||||
*keyring*, a ``dns.tsig.Key`` or ``dict``, the key or keyring to use
|
||||
if the message is signed.
|
||||
*keyring*, a ``dns.tsig.Key`` or ``dict``, the key or keyring to use if the
|
||||
message is signed.
|
||||
|
||||
*request_mac*, a ``bytes``. If the message is a response to a
|
||||
TSIG-signed request, *request_mac* should be set to the MAC of
|
||||
that request.
|
||||
*request_mac*, a ``bytes``. If the message is a response to a TSIG-signed
|
||||
request, *request_mac* should be set to the MAC of that request.
|
||||
|
||||
*xfr*, a ``bool``, should be set to ``True`` if this message is part of
|
||||
a zone transfer.
|
||||
*xfr*, a ``bool``, should be set to ``True`` if this message is part of a
|
||||
zone transfer.
|
||||
|
||||
*origin*, a ``dns.name.Name`` or ``None``. If the message is part
|
||||
of a zone transfer, *origin* should be the origin name of the
|
||||
zone. If not ``None``, names will be relativized to the origin.
|
||||
*origin*, a ``dns.name.Name`` or ``None``. If the message is part of a zone
|
||||
transfer, *origin* should be the origin name of the zone. If not ``None``,
|
||||
names will be relativized to the origin.
|
||||
|
||||
*tsig_ctx*, a ``hmac.HMAC`` object, the ongoing TSIG context, used
|
||||
when validating zone transfers.
|
||||
*tsig_ctx*, a ``dns.tsig.HMACTSig`` or ``dns.tsig.GSSTSig`` object, the
|
||||
ongoing TSIG context, used when validating zone transfers.
|
||||
|
||||
*multi*, a ``bool``, should be set to ``True`` if this message is
|
||||
part of a multiple message sequence.
|
||||
*multi*, a ``bool``, should be set to ``True`` if this message is part of a
|
||||
multiple message sequence.
|
||||
|
||||
*question_only*, a ``bool``. If ``True``, read only up to
|
||||
the end of the question section.
|
||||
*question_only*, a ``bool``. If ``True``, read only up to the end of the
|
||||
question section.
|
||||
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its
|
||||
own RRset.
|
||||
*one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
|
||||
RRset.
|
||||
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the message.
|
||||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing junk at end of
|
||||
the message.
|
||||
|
||||
*raise_on_truncation*, a ``bool``. If ``True``, raise an exception if
|
||||
the TC bit is set.
|
||||
*raise_on_truncation*, a ``bool``. If ``True``, raise an exception if the
|
||||
TC bit is set.
|
||||
|
||||
*continue_on_error*, a ``bool``. If ``True``, try to continue parsing even
|
||||
if errors occur. Erroneous rdata will be ignored. Errors will be
|
||||
accumulated as a list of MessageError objects in the message's ``errors``
|
||||
attribute. This option is recommended only for DNS analysis tools, or for
|
||||
use in a server as part of an error handling path. The default is
|
||||
``False``.
|
||||
|
||||
Raises ``dns.message.ShortHeader`` if the message is less than 12 octets
|
||||
long.
|
||||
|
||||
Raises ``dns.message.TrailingJunk`` if there were octets in the message
|
||||
past the end of the proper DNS message, and *ignore_trailing* is ``False``.
|
||||
Raises ``dns.message.TrailingJunk`` if there were octets in the message past
|
||||
the end of the proper DNS message, and *ignore_trailing* is ``False``.
|
||||
|
||||
Raises ``dns.message.BadEDNS`` if an OPT record was in the
|
||||
wrong section, or occurred more than once.
|
||||
Raises ``dns.message.BadEDNS`` if an OPT record was in the wrong section, or
|
||||
occurred more than once.
|
||||
|
||||
Raises ``dns.message.BadTSIG`` if a TSIG record was not the last
|
||||
record of the additional data section.
|
||||
Raises ``dns.message.BadTSIG`` if a TSIG record was not the last record of
|
||||
the additional data section.
|
||||
|
||||
Raises ``dns.message.Truncated`` if the TC flag is set and
|
||||
*raise_on_truncation* is ``True``.
|
||||
|
@ -928,7 +1108,8 @@ def from_wire(wire, keyring=None, request_mac=b'', xfr=False, origin=None,
|
|||
message.tsig_ctx = tsig_ctx
|
||||
|
||||
reader = _WireReader(wire, initialize_message, question_only,
|
||||
one_rr_per_rrset, ignore_trailing, keyring, multi)
|
||||
one_rr_per_rrset, ignore_trailing, keyring, multi,
|
||||
continue_on_error)
|
||||
try:
|
||||
m = reader.read()
|
||||
except dns.exception.FormError:
|
||||
|
@ -941,6 +1122,8 @@ def from_wire(wire, keyring=None, request_mac=b'', xfr=False, origin=None,
|
|||
# have to do this check here too.
|
||||
if m.flags & dns.flags.TC and raise_on_truncation:
|
||||
raise Truncated(message=m)
|
||||
if continue_on_error:
|
||||
m.errors = reader.errors
|
||||
|
||||
return m
|
||||
|
||||
|
@ -971,12 +1154,12 @@ class _TextReader:
|
|||
self.id = None
|
||||
self.edns = -1
|
||||
self.ednsflags = 0
|
||||
self.payload = None
|
||||
self.payload = DEFAULT_EDNS_PAYLOAD
|
||||
self.rcode = None
|
||||
self.opcode = dns.opcode.QUERY
|
||||
self.flags = 0
|
||||
|
||||
def _header_line(self, section):
|
||||
def _header_line(self, _):
|
||||
"""Process one line from the text format header section."""
|
||||
|
||||
token = self.tok.get()
|
||||
|
@ -1028,6 +1211,8 @@ class _TextReader:
|
|||
self.relativize,
|
||||
self.relativize_to)
|
||||
name = self.last_name
|
||||
if name is None:
|
||||
raise NoPreviousName
|
||||
token = self.tok.get()
|
||||
if not token.is_identifier():
|
||||
raise dns.exception.SyntaxError
|
||||
|
@ -1062,6 +1247,8 @@ class _TextReader:
|
|||
self.relativize,
|
||||
self.relativize_to)
|
||||
name = self.last_name
|
||||
if name is None:
|
||||
raise NoPreviousName
|
||||
token = self.tok.get()
|
||||
if not token.is_identifier():
|
||||
raise dns.exception.SyntaxError
|
||||
|
@ -1092,6 +1279,8 @@ class _TextReader:
|
|||
token = self.tok.get()
|
||||
if empty and not token.is_eol_or_eof():
|
||||
raise dns.exception.SyntaxError
|
||||
if not empty and token.is_eol_or_eof():
|
||||
raise dns.exception.UnexpectedEnd
|
||||
if not token.is_eol_or_eof():
|
||||
self.tok.unget(token)
|
||||
rd = dns.rdata.from_text(rdclass, rdtype, self.tok,
|
||||
|
@ -1235,7 +1424,8 @@ def from_file(f, idna_codec=None, one_rr_per_rrset=False):
|
|||
|
||||
def make_query(qname, rdtype, rdclass=dns.rdataclass.IN, use_edns=None,
|
||||
want_dnssec=False, ednsflags=None, payload=None,
|
||||
request_payload=None, options=None, idna_codec=None):
|
||||
request_payload=None, options=None, idna_codec=None,
|
||||
id=None, flags=dns.flags.RD):
|
||||
"""Make a query message.
|
||||
|
||||
The query name, type, and class may all be specified either
|
||||
|
@ -1252,7 +1442,9 @@ def make_query(qname, rdtype, rdclass=dns.rdataclass.IN, use_edns=None,
|
|||
is class IN.
|
||||
|
||||
*use_edns*, an ``int``, ``bool`` or ``None``. The EDNS level to use; the
|
||||
default is None (no EDNS).
|
||||
default is ``None``. If ``None``, EDNS will be enabled only if other
|
||||
parameters (*ednsflags*, *payload*, *request_payload*, or *options*) are
|
||||
set.
|
||||
See the description of dns.message.Message.use_edns() for the possible
|
||||
values for use_edns and their meanings.
|
||||
|
||||
|
@ -1275,6 +1467,12 @@ def make_query(qname, rdtype, rdclass=dns.rdataclass.IN, use_edns=None,
|
|||
encoder/decoder. If ``None``, the default IDNA 2003 encoder/decoder
|
||||
is used.
|
||||
|
||||
*id*, an ``int`` or ``None``, the desired query id. The default is
|
||||
``None``, which generates a random query id.
|
||||
|
||||
*flags*, an ``int``, the desired query flags. The default is
|
||||
``dns.flags.RD``.
|
||||
|
||||
Returns a ``dns.message.QueryMessage``
|
||||
"""
|
||||
|
||||
|
@ -1282,8 +1480,8 @@ def make_query(qname, rdtype, rdclass=dns.rdataclass.IN, use_edns=None,
|
|||
qname = dns.name.from_text(qname, idna_codec=idna_codec)
|
||||
rdtype = dns.rdatatype.RdataType.make(rdtype)
|
||||
rdclass = dns.rdataclass.RdataClass.make(rdclass)
|
||||
m = QueryMessage()
|
||||
m.flags |= dns.flags.RD
|
||||
m = QueryMessage(id=id)
|
||||
m.flags = dns.flags.Flag(flags)
|
||||
m.find_rrset(m.question, qname, rdclass, rdtype, create=True,
|
||||
force_unique=True)
|
||||
# only pass keywords on to use_edns if they have been set to a
|
||||
|
@ -1292,20 +1490,14 @@ def make_query(qname, rdtype, rdclass=dns.rdataclass.IN, use_edns=None,
|
|||
kwargs = {}
|
||||
if ednsflags is not None:
|
||||
kwargs['ednsflags'] = ednsflags
|
||||
if use_edns is None:
|
||||
use_edns = 0
|
||||
if payload is not None:
|
||||
kwargs['payload'] = payload
|
||||
if use_edns is None:
|
||||
use_edns = 0
|
||||
if request_payload is not None:
|
||||
kwargs['request_payload'] = request_payload
|
||||
if use_edns is None:
|
||||
use_edns = 0
|
||||
if options is not None:
|
||||
kwargs['options'] = options
|
||||
if use_edns is None:
|
||||
use_edns = 0
|
||||
if kwargs and use_edns is None:
|
||||
use_edns = 0
|
||||
kwargs['edns'] = use_edns
|
||||
m.use_edns(**kwargs)
|
||||
m.want_dnssec(want_dnssec)
|
||||
|
@ -1355,3 +1547,12 @@ def make_response(query, recursion_available=False, our_payload=8192,
|
|||
tsig_error, b'', query.keyalgorithm)
|
||||
response.request_mac = query.mac
|
||||
return response
|
||||
|
||||
### BEGIN generated MessageSection constants
|
||||
|
||||
QUESTION = MessageSection.QUESTION
|
||||
ANSWER = MessageSection.ANSWER
|
||||
AUTHORITY = MessageSection.AUTHORITY
|
||||
ADDITIONAL = MessageSection.ADDITIONAL
|
||||
|
||||
### END generated MessageSection constants
|
||||
|
|
47
lib/dns/message.pyi
Normal file
47
lib/dns/message.pyi
Normal file
|
@ -0,0 +1,47 @@
|
|||
from typing import Optional, Dict, List, Tuple, Union
|
||||
from . import name, rrset, tsig, rdatatype, entropy, edns, rdataclass, rcode
|
||||
import hmac
|
||||
|
||||
class Message:
|
||||
def to_wire(self, origin : Optional[name.Name]=None, max_size=0, **kw) -> bytes:
|
||||
...
|
||||
def find_rrset(self, section : List[rrset.RRset], name : name.Name, rdclass : int, rdtype : int,
|
||||
covers=rdatatype.NONE, deleting : Optional[int]=None, create=False,
|
||||
force_unique=False) -> rrset.RRset:
|
||||
...
|
||||
def __init__(self, id : Optional[int] =None) -> None:
|
||||
self.id : int
|
||||
self.flags = 0
|
||||
self.sections : List[List[rrset.RRset]] = [[], [], [], []]
|
||||
self.opt : rrset.RRset = None
|
||||
self.request_payload = 0
|
||||
self.keyring = None
|
||||
self.tsig : rrset.RRset = None
|
||||
self.request_mac = b''
|
||||
self.xfr = False
|
||||
self.origin = None
|
||||
self.tsig_ctx = None
|
||||
self.index : Dict[Tuple[rrset.RRset, name.Name, int, int, Union[int,str], int], rrset.RRset] = {}
|
||||
|
||||
def is_response(self, other : Message) -> bool:
|
||||
...
|
||||
|
||||
def set_rcode(self, rcode : rcode.Rcode):
|
||||
...
|
||||
|
||||
def from_text(a : str, idna_codec : Optional[name.IDNACodec] = None) -> Message:
|
||||
...
|
||||
|
||||
def from_wire(wire, keyring : Optional[Dict[name.Name,bytes]] = None, request_mac = b'', xfr=False, origin=None,
|
||||
tsig_ctx : Optional[Union[dns.tsig.HMACTSig, dns.tsig.GSSTSig]] = None, multi=False,
|
||||
question_only=False, one_rr_per_rrset=False,
|
||||
ignore_trailing=False) -> Message:
|
||||
...
|
||||
def make_response(query : Message, recursion_available=False, our_payload=8192,
|
||||
fudge=300) -> Message:
|
||||
...
|
||||
|
||||
def make_query(qname : Union[name.Name,str], rdtype : Union[str,int], rdclass : Union[int,str] =rdataclass.IN, use_edns : Optional[bool] = None,
|
||||
want_dnssec=False, ednsflags : Optional[int] = None, payload : Optional[int] = None,
|
||||
request_payload : Optional[int] = None, options : Optional[List[edns.Option]] = None) -> Message:
|
||||
...
|
|
@ -30,6 +30,7 @@ except ImportError: # pragma: no cover
|
|||
|
||||
import dns.wire
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
|
||||
# fullcompare() result values
|
||||
|
||||
|
@ -215,9 +216,10 @@ class IDNA2008Codec(IDNACodec):
|
|||
if not have_idna_2008:
|
||||
raise NoIDNA2008
|
||||
try:
|
||||
ulabel = idna.ulabel(label)
|
||||
if self.uts_46:
|
||||
label = idna.uts46_remap(label, False, False)
|
||||
return _escapify(idna.ulabel(label))
|
||||
ulabel = idna.uts46_remap(ulabel, False, self.transitional)
|
||||
return _escapify(ulabel)
|
||||
except (idna.IDNAError, UnicodeError) as e:
|
||||
raise IDNAException(idna_exception=e)
|
||||
|
||||
|
@ -304,6 +306,7 @@ def _maybe_convert_to_binary(label):
|
|||
raise ValueError # pragma: no cover
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class Name:
|
||||
|
||||
"""A DNS name.
|
||||
|
@ -320,17 +323,9 @@ class Name:
|
|||
"""
|
||||
|
||||
labels = [_maybe_convert_to_binary(x) for x in labels]
|
||||
super().__setattr__('labels', tuple(labels))
|
||||
self.labels = tuple(labels)
|
||||
_validate_labels(self.labels)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# Names are immutable
|
||||
raise TypeError("object doesn't support attribute assignment")
|
||||
|
||||
def __delattr__(self, name):
|
||||
# Names are immutable
|
||||
raise TypeError("object doesn't support attribute deletion")
|
||||
|
||||
def __copy__(self):
|
||||
return Name(self.labels)
|
||||
|
||||
|
@ -458,7 +453,7 @@ class Name:
|
|||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
(nr, o, nl) = self.fullcompare(other)
|
||||
(nr, _, _) = self.fullcompare(other)
|
||||
if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL:
|
||||
return True
|
||||
return False
|
||||
|
@ -472,7 +467,7 @@ class Name:
|
|||
Returns a ``bool``.
|
||||
"""
|
||||
|
||||
(nr, o, nl) = self.fullcompare(other)
|
||||
(nr, _, _) = self.fullcompare(other)
|
||||
if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL:
|
||||
return True
|
||||
return False
|
||||
|
|
40
lib/dns/name.pyi
Normal file
40
lib/dns/name.pyi
Normal file
|
@ -0,0 +1,40 @@
|
|||
from typing import Optional, Union, Tuple, Iterable, List
|
||||
|
||||
have_idna_2008: bool
|
||||
|
||||
class Name:
|
||||
def is_subdomain(self, o : Name) -> bool: ...
|
||||
def is_superdomain(self, o : Name) -> bool: ...
|
||||
def __init__(self, labels : Iterable[Union[bytes,str]]) -> None:
|
||||
self.labels : List[bytes]
|
||||
def is_absolute(self) -> bool: ...
|
||||
def is_wild(self) -> bool: ...
|
||||
def fullcompare(self, other) -> Tuple[int,int,int]: ...
|
||||
def canonicalize(self) -> Name: ...
|
||||
def __eq__(self, other) -> bool: ...
|
||||
def __ne__(self, other) -> bool: ...
|
||||
def __lt__(self, other : Name) -> bool: ...
|
||||
def __le__(self, other : Name) -> bool: ...
|
||||
def __ge__(self, other : Name) -> bool: ...
|
||||
def __gt__(self, other : Name) -> bool: ...
|
||||
def to_text(self, omit_final_dot=False) -> str: ...
|
||||
def to_unicode(self, omit_final_dot=False, idna_codec=None) -> str: ...
|
||||
def to_digestable(self, origin=None) -> bytes: ...
|
||||
def to_wire(self, file=None, compress=None, origin=None,
|
||||
canonicalize=False) -> Optional[bytes]: ...
|
||||
def __add__(self, other : Name) -> Name: ...
|
||||
def __sub__(self, other : Name) -> Name: ...
|
||||
def split(self, depth) -> List[Tuple[str,str]]: ...
|
||||
def concatenate(self, other : Name) -> Name: ...
|
||||
def relativize(self, origin) -> Name: ...
|
||||
def derelativize(self, origin) -> Name: ...
|
||||
def choose_relativity(self, origin : Optional[Name] = None, relativize=True) -> Name: ...
|
||||
def parent(self) -> Name: ...
|
||||
|
||||
class IDNACodec:
|
||||
pass
|
||||
|
||||
def from_text(text, origin : Optional[Name] = Name('.'), idna_codec : Optional[IDNACodec] = None) -> Name:
|
||||
...
|
||||
|
||||
empty : Name
|
|
@ -85,7 +85,7 @@ class NameDict(MutableMapping):
|
|||
return key in self.__store
|
||||
|
||||
def get_deepest_match(self, name):
|
||||
"""Find the deepest match to *fname* in the dictionary.
|
||||
"""Find the deepest match to *name* in the dictionary.
|
||||
|
||||
The deepest match is the longest name in the dictionary which is
|
||||
a superdomain of *name*. Note that *superdomain* includes matching
|
||||
|
|
143
lib/dns/node.py
143
lib/dns/node.py
|
@ -17,16 +17,69 @@
|
|||
|
||||
"""DNS nodes. A node is a set of rdatasets."""
|
||||
|
||||
import enum
|
||||
import io
|
||||
|
||||
import dns.immutable
|
||||
import dns.rdataset
|
||||
import dns.rdatatype
|
||||
import dns.renderer
|
||||
|
||||
|
||||
_cname_types = {
|
||||
dns.rdatatype.CNAME,
|
||||
}
|
||||
|
||||
# "neutral" types can coexist with a CNAME and thus are not "other data"
|
||||
_neutral_types = {
|
||||
dns.rdatatype.NSEC, # RFC 4035 section 2.5
|
||||
dns.rdatatype.NSEC3, # This is not likely to happen, but not impossible!
|
||||
dns.rdatatype.KEY, # RFC 4035 section 2.5, RFC 3007
|
||||
}
|
||||
|
||||
def _matches_type_or_its_signature(rdtypes, rdtype, covers):
|
||||
return rdtype in rdtypes or \
|
||||
(rdtype == dns.rdatatype.RRSIG and covers in rdtypes)
|
||||
|
||||
|
||||
@enum.unique
|
||||
class NodeKind(enum.Enum):
|
||||
"""Rdatasets in nodes
|
||||
"""
|
||||
REGULAR = 0 # a.k.a "other data"
|
||||
NEUTRAL = 1
|
||||
CNAME = 2
|
||||
|
||||
@classmethod
|
||||
def classify(cls, rdtype, covers):
|
||||
if _matches_type_or_its_signature(_cname_types, rdtype, covers):
|
||||
return NodeKind.CNAME
|
||||
elif _matches_type_or_its_signature(_neutral_types, rdtype, covers):
|
||||
return NodeKind.NEUTRAL
|
||||
else:
|
||||
return NodeKind.REGULAR
|
||||
|
||||
@classmethod
|
||||
def classify_rdataset(cls, rdataset):
|
||||
return cls.classify(rdataset.rdtype, rdataset.covers)
|
||||
|
||||
|
||||
class Node:
|
||||
|
||||
"""A Node is a set of rdatasets."""
|
||||
"""A Node is a set of rdatasets.
|
||||
|
||||
A node is either a CNAME node or an "other data" node. A CNAME
|
||||
node contains only CNAME, KEY, NSEC, and NSEC3 rdatasets along with their
|
||||
covering RRSIG rdatasets. An "other data" node contains any
|
||||
rdataset other than a CNAME or RRSIG(CNAME) rdataset. When
|
||||
changes are made to a node, the CNAME or "other data" state is
|
||||
always consistent with the update, i.e. the most recent change
|
||||
wins. For example, if you have a node which contains a CNAME
|
||||
rdataset, and then add an MX rdataset to it, then the CNAME
|
||||
rdataset will be deleted. Likewise if you have a node containing
|
||||
an MX rdataset and add a CNAME rdataset, the MX rdataset will be
|
||||
deleted.
|
||||
"""
|
||||
|
||||
__slots__ = ['rdatasets']
|
||||
|
||||
|
@ -78,6 +131,30 @@ class Node:
|
|||
def __iter__(self):
|
||||
return iter(self.rdatasets)
|
||||
|
||||
def _append_rdataset(self, rdataset):
|
||||
"""Append rdataset to the node with special handling for CNAME and
|
||||
other data conditions.
|
||||
|
||||
Specifically, if the rdataset being appended has ``NodeKind.CNAME``,
|
||||
then all rdatasets other than KEY, NSEC, NSEC3, and their covering
|
||||
RRSIGs are deleted. If the rdataset being appended has
|
||||
``NodeKind.REGULAR`` then CNAME and RRSIG(CNAME) are deleted.
|
||||
"""
|
||||
# Make having just one rdataset at the node fast.
|
||||
if len(self.rdatasets) > 0:
|
||||
kind = NodeKind.classify_rdataset(rdataset)
|
||||
if kind == NodeKind.CNAME:
|
||||
self.rdatasets = [rds for rds in self.rdatasets if
|
||||
NodeKind.classify_rdataset(rds) !=
|
||||
NodeKind.REGULAR]
|
||||
elif kind == NodeKind.REGULAR:
|
||||
self.rdatasets = [rds for rds in self.rdatasets if
|
||||
NodeKind.classify_rdataset(rds) !=
|
||||
NodeKind.CNAME]
|
||||
# Otherwise the rdataset is NodeKind.NEUTRAL and we do not need to
|
||||
# edit self.rdatasets.
|
||||
self.rdatasets.append(rdataset)
|
||||
|
||||
def find_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
|
||||
create=False):
|
||||
"""Find an rdataset matching the specified properties in the
|
||||
|
@ -110,8 +187,8 @@ class Node:
|
|||
return rds
|
||||
if not create:
|
||||
raise KeyError
|
||||
rds = dns.rdataset.Rdataset(rdclass, rdtype)
|
||||
self.rdatasets.append(rds)
|
||||
rds = dns.rdataset.Rdataset(rdclass, rdtype, covers)
|
||||
self._append_rdataset(rds)
|
||||
return rds
|
||||
|
||||
def get_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
|
||||
|
@ -180,6 +257,64 @@ class Node:
|
|||
|
||||
if not isinstance(replacement, dns.rdataset.Rdataset):
|
||||
raise ValueError('replacement is not an rdataset')
|
||||
if isinstance(replacement, dns.rrset.RRset):
|
||||
# RRsets are not good replacements as the match() method
|
||||
# is not compatible.
|
||||
replacement = replacement.to_rdataset()
|
||||
self.delete_rdataset(replacement.rdclass, replacement.rdtype,
|
||||
replacement.covers)
|
||||
self.rdatasets.append(replacement)
|
||||
self._append_rdataset(replacement)
|
||||
|
||||
def classify(self):
|
||||
"""Classify a node.
|
||||
|
||||
A node which contains a CNAME or RRSIG(CNAME) is a
|
||||
``NodeKind.CNAME`` node.
|
||||
|
||||
A node which contains only "neutral" types, i.e. types allowed to
|
||||
co-exist with a CNAME, is a ``NodeKind.NEUTRAL`` node. The neutral
|
||||
types are NSEC, NSEC3, KEY, and their associated RRSIGS. An empty node
|
||||
is also considered neutral.
|
||||
|
||||
A node which contains some rdataset which is not a CNAME, RRSIG(CNAME),
|
||||
or a neutral type is a a ``NodeKind.REGULAR`` node. Regular nodes are
|
||||
also commonly referred to as "other data".
|
||||
"""
|
||||
for rdataset in self.rdatasets:
|
||||
kind = NodeKind.classify(rdataset.rdtype, rdataset.covers)
|
||||
if kind != NodeKind.NEUTRAL:
|
||||
return kind
|
||||
return NodeKind.NEUTRAL
|
||||
|
||||
def is_immutable(self):
|
||||
return False
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class ImmutableNode(Node):
|
||||
def __init__(self, node):
|
||||
super().__init__()
|
||||
self.rdatasets = tuple(
|
||||
[dns.rdataset.ImmutableRdataset(rds) for rds in node.rdatasets]
|
||||
)
|
||||
|
||||
def find_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
|
||||
create=False):
|
||||
if create:
|
||||
raise TypeError("immutable")
|
||||
return super().find_rdataset(rdclass, rdtype, covers, False)
|
||||
|
||||
def get_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
|
||||
create=False):
|
||||
if create:
|
||||
raise TypeError("immutable")
|
||||
return super().get_rdataset(rdclass, rdtype, covers, False)
|
||||
|
||||
def delete_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE):
|
||||
raise TypeError("immutable")
|
||||
|
||||
def replace_rdataset(self, replacement):
|
||||
raise TypeError("immutable")
|
||||
|
||||
def is_immutable(self):
|
||||
return True
|
||||
|
|
17
lib/dns/node.pyi
Normal file
17
lib/dns/node.pyi
Normal file
|
@ -0,0 +1,17 @@
|
|||
from typing import List, Optional, Union
|
||||
from . import rdataset, rdatatype, name
|
||||
class Node:
|
||||
def __init__(self):
|
||||
self.rdatasets : List[rdataset.Rdataset]
|
||||
def to_text(self, name : Union[str,name.Name], **kw) -> str:
|
||||
...
|
||||
def find_rdataset(self, rdclass : int, rdtype : int, covers=rdatatype.NONE,
|
||||
create=False) -> rdataset.Rdataset:
|
||||
...
|
||||
def get_rdataset(self, rdclass : int, rdtype : int, covers=rdatatype.NONE,
|
||||
create=False) -> Optional[rdataset.Rdataset]:
|
||||
...
|
||||
def delete_rdataset(self, rdclass : int, rdtype : int, covers=rdatatype.NONE):
|
||||
...
|
||||
def replace_rdataset(self, replacement : rdataset.Rdataset) -> None:
|
||||
...
|
|
@ -40,8 +40,6 @@ class Opcode(dns.enum.IntEnum):
|
|||
def _unknown_exception_class(cls):
|
||||
return UnknownOpcode
|
||||
|
||||
globals().update(Opcode.__members__)
|
||||
|
||||
|
||||
class UnknownOpcode(dns.exception.DNSException):
|
||||
"""An DNS opcode is unknown."""
|
||||
|
@ -105,3 +103,13 @@ def is_update(flags):
|
|||
"""
|
||||
|
||||
return from_flags(flags) == Opcode.UPDATE
|
||||
|
||||
### BEGIN generated Opcode constants
|
||||
|
||||
QUERY = Opcode.QUERY
|
||||
IQUERY = Opcode.IQUERY
|
||||
STATUS = Opcode.STATUS
|
||||
NOTIFY = Opcode.NOTIFY
|
||||
UPDATE = Opcode.UPDATE
|
||||
|
||||
### END generated Opcode constants
|
||||
|
|
408
lib/dns/query.py
408
lib/dns/query.py
|
@ -18,9 +18,10 @@
|
|||
"""Talk to a DNS server."""
|
||||
|
||||
import contextlib
|
||||
import enum
|
||||
import errno
|
||||
import os
|
||||
import select
|
||||
import selectors
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
|
@ -35,14 +36,31 @@ import dns.rcode
|
|||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.serial
|
||||
import dns.xfr
|
||||
|
||||
try:
|
||||
import requests
|
||||
from requests_toolbelt.adapters.source import SourceAddressAdapter
|
||||
from requests_toolbelt.adapters.host_header_ssl import HostHeaderSSLAdapter
|
||||
have_doh = True
|
||||
_have_requests = True
|
||||
except ImportError: # pragma: no cover
|
||||
have_doh = False
|
||||
_have_requests = False
|
||||
|
||||
_have_httpx = False
|
||||
_have_http2 = False
|
||||
try:
|
||||
import httpx
|
||||
_have_httpx = True
|
||||
try:
|
||||
# See if http2 support is available.
|
||||
with httpx.Client(http2=True):
|
||||
_have_http2 = True
|
||||
except Exception:
|
||||
pass
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
have_doh = _have_requests or _have_httpx
|
||||
|
||||
try:
|
||||
import ssl
|
||||
|
@ -73,20 +91,15 @@ class BadResponse(dns.exception.FormError):
|
|||
"""A DNS query response does not respond to the question asked."""
|
||||
|
||||
|
||||
class TransferError(dns.exception.DNSException):
|
||||
"""A zone transfer response got a non-zero rcode."""
|
||||
|
||||
def __init__(self, rcode):
|
||||
message = 'Zone transfer error: %s' % dns.rcode.to_text(rcode)
|
||||
super().__init__(message)
|
||||
self.rcode = rcode
|
||||
|
||||
|
||||
class NoDOH(dns.exception.DNSException):
|
||||
"""DNS over HTTPS (DOH) was requested but the requests module is not
|
||||
available."""
|
||||
|
||||
|
||||
# for backwards compatibility
|
||||
TransferError = dns.xfr.TransferError
|
||||
|
||||
|
||||
def _compute_times(timeout):
|
||||
now = time.time()
|
||||
if timeout is None:
|
||||
|
@ -94,91 +107,49 @@ def _compute_times(timeout):
|
|||
else:
|
||||
return (now, now + timeout)
|
||||
|
||||
# This module can use either poll() or select() as the "polling backend".
|
||||
#
|
||||
# A backend function takes an fd, bools for readability, writablity, and
|
||||
# error detection, and a timeout.
|
||||
|
||||
def _poll_for(fd, readable, writable, error, timeout):
|
||||
"""Poll polling backend."""
|
||||
|
||||
event_mask = 0
|
||||
if readable:
|
||||
event_mask |= select.POLLIN
|
||||
if writable:
|
||||
event_mask |= select.POLLOUT
|
||||
if error:
|
||||
event_mask |= select.POLLERR
|
||||
|
||||
pollable = select.poll()
|
||||
pollable.register(fd, event_mask)
|
||||
|
||||
if timeout:
|
||||
event_list = pollable.poll(timeout * 1000)
|
||||
else:
|
||||
event_list = pollable.poll()
|
||||
|
||||
return bool(event_list)
|
||||
|
||||
|
||||
def _select_for(fd, readable, writable, error, timeout):
|
||||
"""Select polling backend."""
|
||||
|
||||
rset, wset, xset = [], [], []
|
||||
|
||||
if readable:
|
||||
rset = [fd]
|
||||
if writable:
|
||||
wset = [fd]
|
||||
if error:
|
||||
xset = [fd]
|
||||
|
||||
if timeout is None:
|
||||
(rcount, wcount, xcount) = select.select(rset, wset, xset)
|
||||
else:
|
||||
(rcount, wcount, xcount) = select.select(rset, wset, xset, timeout)
|
||||
|
||||
return bool((rcount or wcount or xcount))
|
||||
|
||||
|
||||
def _wait_for(fd, readable, writable, error, expiration):
|
||||
# Use the selected polling backend to wait for any of the specified
|
||||
def _wait_for(fd, readable, writable, _, expiration):
|
||||
# Use the selected selector class to wait for any of the specified
|
||||
# events. An "expiration" absolute time is converted into a relative
|
||||
# timeout.
|
||||
#
|
||||
# The unused parameter is 'error', which is always set when
|
||||
# selecting for read or write, and we have no error-only selects.
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
if expiration is None:
|
||||
timeout = None
|
||||
else:
|
||||
timeout = expiration - time.time()
|
||||
if timeout <= 0.0:
|
||||
raise dns.exception.Timeout
|
||||
try:
|
||||
if isinstance(fd, ssl.SSLSocket) and readable and fd.pending() > 0:
|
||||
return True
|
||||
if not _polling_backend(fd, readable, writable, error, timeout):
|
||||
raise dns.exception.Timeout
|
||||
except OSError as e: # pragma: no cover
|
||||
if e.args[0] != errno.EINTR:
|
||||
raise e
|
||||
done = True
|
||||
if readable and isinstance(fd, ssl.SSLSocket) and fd.pending() > 0:
|
||||
return True
|
||||
sel = _selector_class()
|
||||
events = 0
|
||||
if readable:
|
||||
events |= selectors.EVENT_READ
|
||||
if writable:
|
||||
events |= selectors.EVENT_WRITE
|
||||
if events:
|
||||
sel.register(fd, events)
|
||||
if expiration is None:
|
||||
timeout = None
|
||||
else:
|
||||
timeout = expiration - time.time()
|
||||
if timeout <= 0.0:
|
||||
raise dns.exception.Timeout
|
||||
if not sel.select(timeout):
|
||||
raise dns.exception.Timeout
|
||||
|
||||
|
||||
def _set_polling_backend(fn):
|
||||
def _set_selector_class(selector_class):
|
||||
# Internal API. Do not use.
|
||||
|
||||
global _polling_backend
|
||||
global _selector_class
|
||||
|
||||
_polling_backend = fn
|
||||
_selector_class = selector_class
|
||||
|
||||
if hasattr(select, 'poll'):
|
||||
if hasattr(selectors, 'PollSelector'):
|
||||
# Prefer poll() on platforms that support it because it has no
|
||||
# limits on the maximum value of a file descriptor (plus it will
|
||||
# be more efficient for high values).
|
||||
_polling_backend = _poll_for
|
||||
_selector_class = selectors.PollSelector
|
||||
else:
|
||||
_polling_backend = _select_for # pragma: no cover
|
||||
_selector_class = selectors.SelectSelector # pragma: no cover
|
||||
|
||||
|
||||
def _wait_for_readable(s, expiration):
|
||||
|
@ -303,8 +274,8 @@ def https(q, where, timeout=None, port=443, source=None, source_port=0,
|
|||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
*session*, a ``requests.session.Session``. If provided, the session to use
|
||||
to send the queries.
|
||||
*session*, an ``httpx.Client`` or ``requests.session.Session``. If
|
||||
provided, the client/session to use to send the queries.
|
||||
|
||||
*path*, a ``str``. If *where* is an IP address, then *path* will be used to
|
||||
construct the URL to send the DNS query to.
|
||||
|
@ -320,37 +291,66 @@ def https(q, where, timeout=None, port=443, source=None, source_port=0,
|
|||
"""
|
||||
|
||||
if not have_doh:
|
||||
raise NoDOH # pragma: no cover
|
||||
raise NoDOH('Neither httpx nor requests is available.') # pragma: no cover
|
||||
|
||||
_httpx_ok = _have_httpx
|
||||
|
||||
wire = q.to_wire()
|
||||
(af, destination, source) = _destination_and_source(where, port,
|
||||
source, source_port,
|
||||
False)
|
||||
(af, _, source) = _destination_and_source(where, port, source, source_port,
|
||||
False)
|
||||
transport_adapter = None
|
||||
transport = None
|
||||
headers = {
|
||||
"accept": "application/dns-message"
|
||||
}
|
||||
try:
|
||||
where_af = dns.inet.af_for_address(where)
|
||||
if where_af == socket.AF_INET:
|
||||
if af is not None:
|
||||
if af == socket.AF_INET:
|
||||
url = 'https://{}:{}{}'.format(where, port, path)
|
||||
elif where_af == socket.AF_INET6:
|
||||
elif af == socket.AF_INET6:
|
||||
url = 'https://[{}]:{}{}'.format(where, port, path)
|
||||
except ValueError:
|
||||
if bootstrap_address is not None:
|
||||
split_url = urllib.parse.urlsplit(where)
|
||||
headers['Host'] = split_url.hostname
|
||||
url = where.replace(split_url.hostname, bootstrap_address)
|
||||
elif bootstrap_address is not None:
|
||||
_httpx_ok = False
|
||||
split_url = urllib.parse.urlsplit(where)
|
||||
headers['Host'] = split_url.hostname
|
||||
url = where.replace(split_url.hostname, bootstrap_address)
|
||||
if _have_requests:
|
||||
transport_adapter = HostHeaderSSLAdapter()
|
||||
else:
|
||||
url = where
|
||||
else:
|
||||
url = where
|
||||
if source is not None:
|
||||
# set source port and source address
|
||||
transport_adapter = SourceAddressAdapter(source)
|
||||
if _have_httpx:
|
||||
if source_port == 0:
|
||||
transport = httpx.HTTPTransport(local_address=source[0])
|
||||
else:
|
||||
_httpx_ok = False
|
||||
if _have_requests:
|
||||
transport_adapter = SourceAddressAdapter(source)
|
||||
|
||||
if session:
|
||||
if _have_httpx:
|
||||
_is_httpx = isinstance(session, httpx.Client)
|
||||
else:
|
||||
_is_httpx = False
|
||||
if _is_httpx and not _httpx_ok:
|
||||
raise NoDOH('Session is httpx, but httpx cannot be used for '
|
||||
'the requested operation.')
|
||||
else:
|
||||
_is_httpx = _httpx_ok
|
||||
|
||||
if not _httpx_ok and not _have_requests:
|
||||
raise NoDOH('Cannot use httpx for this operation, and '
|
||||
'requests is not available.')
|
||||
|
||||
with contextlib.ExitStack() as stack:
|
||||
if not session:
|
||||
session = stack.enter_context(requests.sessions.Session())
|
||||
if _is_httpx:
|
||||
session = stack.enter_context(httpx.Client(http1=True,
|
||||
http2=_have_http2,
|
||||
verify=verify,
|
||||
transport=transport))
|
||||
else:
|
||||
session = stack.enter_context(requests.sessions.Session())
|
||||
|
||||
if transport_adapter:
|
||||
session.mount(url, transport_adapter)
|
||||
|
@ -362,13 +362,23 @@ def https(q, where, timeout=None, port=443, source=None, source_port=0,
|
|||
"content-type": "application/dns-message",
|
||||
"content-length": str(len(wire))
|
||||
})
|
||||
response = session.post(url, headers=headers, data=wire,
|
||||
timeout=timeout, verify=verify)
|
||||
if _is_httpx:
|
||||
response = session.post(url, headers=headers, content=wire,
|
||||
timeout=timeout)
|
||||
else:
|
||||
response = session.post(url, headers=headers, data=wire,
|
||||
timeout=timeout, verify=verify)
|
||||
else:
|
||||
wire = base64.urlsafe_b64encode(wire).rstrip(b"=")
|
||||
response = session.get(url, headers=headers,
|
||||
timeout=timeout, verify=verify,
|
||||
params={"dns": wire})
|
||||
if _is_httpx:
|
||||
wire = wire.decode() # httpx does a repr() if we give it bytes
|
||||
response = session.get(url, headers=headers,
|
||||
timeout=timeout,
|
||||
params={"dns": wire})
|
||||
else:
|
||||
response = session.get(url, headers=headers,
|
||||
timeout=timeout, verify=verify,
|
||||
params={"dns": wire})
|
||||
|
||||
# see https://tools.ietf.org/html/rfc8484#section-4.2.1 for info about DoH
|
||||
# status codes
|
||||
|
@ -387,6 +397,33 @@ def https(q, where, timeout=None, port=443, source=None, source_port=0,
|
|||
raise BadResponse
|
||||
return r
|
||||
|
||||
def _udp_recv(sock, max_size, expiration):
|
||||
"""Reads a datagram from the socket.
|
||||
A Timeout exception will be raised if the operation is not completed
|
||||
by the expiration time.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
return sock.recvfrom(max_size)
|
||||
except BlockingIOError:
|
||||
_wait_for_readable(sock, expiration)
|
||||
|
||||
|
||||
def _udp_send(sock, data, destination, expiration):
|
||||
"""Sends the specified datagram to destination over the socket.
|
||||
A Timeout exception will be raised if the operation is not completed
|
||||
by the expiration time.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
if destination:
|
||||
return sock.sendto(data, destination)
|
||||
else:
|
||||
return sock.send(data)
|
||||
except BlockingIOError: # pragma: no cover
|
||||
_wait_for_writable(sock, expiration)
|
||||
|
||||
|
||||
def send_udp(sock, what, destination, expiration=None):
|
||||
"""Send a DNS message to the specified UDP socket.
|
||||
|
||||
|
@ -406,9 +443,8 @@ def send_udp(sock, what, destination, expiration=None):
|
|||
|
||||
if isinstance(what, dns.message.Message):
|
||||
what = what.to_wire()
|
||||
_wait_for_writable(sock, expiration)
|
||||
sent_time = time.time()
|
||||
n = sock.sendto(what, destination)
|
||||
n = _udp_send(sock, what, destination, expiration)
|
||||
return (n, sent_time)
|
||||
|
||||
|
||||
|
@ -458,9 +494,8 @@ def receive_udp(sock, destination=None, expiration=None,
|
|||
"""
|
||||
|
||||
wire = b''
|
||||
while 1:
|
||||
_wait_for_readable(sock, expiration)
|
||||
(wire, from_address) = sock.recvfrom(65535)
|
||||
while True:
|
||||
(wire, from_address) = _udp_recv(sock, 65535, expiration)
|
||||
if _matches_destination(sock.family, from_address, destination,
|
||||
ignore_unexpected):
|
||||
break
|
||||
|
@ -571,7 +606,7 @@ def udp_with_fallback(q, where, timeout=None, port=53, source=None,
|
|||
if a socket is provided, it must be a nonblocking datagram socket,
|
||||
and the *source* and *source_port* are ignored for the UDP query.
|
||||
|
||||
*tcp_sock*, a ``socket.socket``, or ``None``, the socket to use for the
|
||||
*tcp_sock*, a ``socket.socket``, or ``None``, the connected socket to use for the
|
||||
TCP query. If ``None``, the default, a socket is created. Note that
|
||||
if a socket is provided, it must be a nonblocking connected stream
|
||||
socket, and *where*, *source* and *source_port* are ignored for the TCP
|
||||
|
@ -598,18 +633,16 @@ def _net_read(sock, count, expiration):
|
|||
"""
|
||||
s = b''
|
||||
while count > 0:
|
||||
_wait_for_readable(sock, expiration)
|
||||
try:
|
||||
n = sock.recv(count)
|
||||
except ssl.SSLWantReadError: # pragma: no cover
|
||||
continue
|
||||
if n == b'':
|
||||
raise EOFError
|
||||
count -= len(n)
|
||||
s += n
|
||||
except (BlockingIOError, ssl.SSLWantReadError):
|
||||
_wait_for_readable(sock, expiration)
|
||||
except ssl.SSLWantWriteError: # pragma: no cover
|
||||
_wait_for_writable(sock, expiration)
|
||||
continue
|
||||
if n == b'':
|
||||
raise EOFError
|
||||
count = count - len(n)
|
||||
s = s + n
|
||||
return s
|
||||
|
||||
|
||||
|
@ -621,14 +654,12 @@ def _net_write(sock, data, expiration):
|
|||
current = 0
|
||||
l = len(data)
|
||||
while current < l:
|
||||
_wait_for_writable(sock, expiration)
|
||||
try:
|
||||
current += sock.send(data[current:])
|
||||
except (BlockingIOError, ssl.SSLWantWriteError):
|
||||
_wait_for_writable(sock, expiration)
|
||||
except ssl.SSLWantReadError: # pragma: no cover
|
||||
_wait_for_readable(sock, expiration)
|
||||
continue
|
||||
except ssl.SSLWantWriteError: # pragma: no cover
|
||||
continue
|
||||
|
||||
|
||||
def send_tcp(sock, what, expiration=None):
|
||||
|
@ -652,7 +683,6 @@ def send_tcp(sock, what, expiration=None):
|
|||
# avoid writev() or doing a short write that would get pushed
|
||||
# onto the net
|
||||
tcpmsg = struct.pack("!H", l) + what
|
||||
_wait_for_writable(sock, expiration)
|
||||
sent_time = time.time()
|
||||
_net_write(sock, tcpmsg, expiration)
|
||||
return (len(tcpmsg), sent_time)
|
||||
|
@ -730,7 +760,7 @@ def tcp(q, where, timeout=None, port=53, source=None, source_port=0,
|
|||
*ignore_trailing*, a ``bool``. If ``True``, ignore trailing
|
||||
junk at end of the received message.
|
||||
|
||||
*sock*, a ``socket.socket``, or ``None``, the socket to use for the
|
||||
*sock*, a ``socket.socket``, or ``None``, the connected socket to use for the
|
||||
query. If ``None``, the default, a socket is created. Note that
|
||||
if a socket is provided, it must be a nonblocking connected stream
|
||||
socket, and *where*, *port*, *source* and *source_port* are ignored.
|
||||
|
@ -742,11 +772,6 @@ def tcp(q, where, timeout=None, port=53, source=None, source_port=0,
|
|||
(begin_time, expiration) = _compute_times(timeout)
|
||||
with contextlib.ExitStack() as stack:
|
||||
if sock:
|
||||
#
|
||||
# Verify that the socket is connected, as if it's not connected,
|
||||
# it's not writable, and the polling in send_tcp() will time out or
|
||||
# hang forever.
|
||||
sock.getpeername()
|
||||
s = sock
|
||||
else:
|
||||
(af, destination, source) = _destination_and_source(where, port,
|
||||
|
@ -926,8 +951,7 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
|
|||
_connect(s, destination, expiration)
|
||||
l = len(wire)
|
||||
if use_udp:
|
||||
_wait_for_writable(s, expiration)
|
||||
s.send(wire)
|
||||
_udp_send(s, wire, None, expiration)
|
||||
else:
|
||||
tcpmsg = struct.pack("!H", l) + wire
|
||||
_net_write(s, tcpmsg, expiration)
|
||||
|
@ -948,8 +972,7 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
|
|||
(expiration is not None and mexpiration > expiration):
|
||||
mexpiration = expiration
|
||||
if use_udp:
|
||||
_wait_for_readable(s, expiration)
|
||||
(wire, from_address) = s.recvfrom(65535)
|
||||
(wire, _) = _udp_recv(s, 65535, mexpiration)
|
||||
else:
|
||||
ldata = _net_read(s, 2, mexpiration)
|
||||
(l,) = struct.unpack("!H", ldata)
|
||||
|
@ -1016,3 +1039,116 @@ def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN,
|
|||
if done and q.keyring and not r.had_tsig:
|
||||
raise dns.exception.FormError("missing TSIG")
|
||||
yield r
|
||||
|
||||
|
||||
class UDPMode(enum.IntEnum):
|
||||
"""How should UDP be used in an IXFR from :py:func:`inbound_xfr()`?
|
||||
|
||||
NEVER means "never use UDP; always use TCP"
|
||||
TRY_FIRST means "try to use UDP but fall back to TCP if needed"
|
||||
ONLY means "raise ``dns.xfr.UseTCP`` if trying UDP does not succeed"
|
||||
"""
|
||||
NEVER = 0
|
||||
TRY_FIRST = 1
|
||||
ONLY = 2
|
||||
|
||||
|
||||
def inbound_xfr(where, txn_manager, query=None,
|
||||
port=53, timeout=None, lifetime=None, source=None,
|
||||
source_port=0, udp_mode=UDPMode.NEVER):
|
||||
"""Conduct an inbound transfer and apply it via a transaction from the
|
||||
txn_manager.
|
||||
|
||||
*where*, a ``str`` containing an IPv4 or IPv6 address, where
|
||||
to send the message.
|
||||
|
||||
*txn_manager*, a ``dns.transaction.TransactionManager``, the txn_manager
|
||||
for this transfer (typically a ``dns.zone.Zone``).
|
||||
|
||||
*query*, the query to send. If not supplied, a default query is
|
||||
constructed using information from the *txn_manager*.
|
||||
|
||||
*port*, an ``int``, the port send the message to. The default is 53.
|
||||
|
||||
*timeout*, a ``float``, the number of seconds to wait for each
|
||||
response message. If None, the default, wait forever.
|
||||
|
||||
*lifetime*, a ``float``, the total number of seconds to spend
|
||||
doing the transfer. If ``None``, the default, then there is no
|
||||
limit on the time the transfer may take.
|
||||
|
||||
*source*, a ``str`` containing an IPv4 or IPv6 address, specifying
|
||||
the source address. The default is the wildcard address.
|
||||
|
||||
*source_port*, an ``int``, the port from which to send the message.
|
||||
The default is 0.
|
||||
|
||||
*udp_mode*, a ``dns.query.UDPMode``, determines how UDP is used
|
||||
for IXFRs. The default is ``dns.UDPMode.NEVER``, i.e. only use
|
||||
TCP. Other possibilites are ``dns.UDPMode.TRY_FIRST``, which
|
||||
means "try UDP but fallback to TCP if needed", and
|
||||
``dns.UDPMode.ONLY``, which means "try UDP and raise
|
||||
``dns.xfr.UseTCP`` if it does not succeeed.
|
||||
|
||||
Raises on errors.
|
||||
"""
|
||||
if query is None:
|
||||
(query, serial) = dns.xfr.make_query(txn_manager)
|
||||
else:
|
||||
serial = dns.xfr.extract_serial_from_query(query)
|
||||
rdtype = query.question[0].rdtype
|
||||
is_ixfr = rdtype == dns.rdatatype.IXFR
|
||||
origin = txn_manager.from_wire_origin()
|
||||
wire = query.to_wire()
|
||||
(af, destination, source) = _destination_and_source(where, port,
|
||||
source, source_port)
|
||||
(_, expiration) = _compute_times(lifetime)
|
||||
retry = True
|
||||
while retry:
|
||||
retry = False
|
||||
if is_ixfr and udp_mode != UDPMode.NEVER:
|
||||
sock_type = socket.SOCK_DGRAM
|
||||
is_udp = True
|
||||
else:
|
||||
sock_type = socket.SOCK_STREAM
|
||||
is_udp = False
|
||||
with _make_socket(af, sock_type, source) as s:
|
||||
_connect(s, destination, expiration)
|
||||
if is_udp:
|
||||
_udp_send(s, wire, None, expiration)
|
||||
else:
|
||||
tcpmsg = struct.pack("!H", len(wire)) + wire
|
||||
_net_write(s, tcpmsg, expiration)
|
||||
with dns.xfr.Inbound(txn_manager, rdtype, serial,
|
||||
is_udp) as inbound:
|
||||
done = False
|
||||
tsig_ctx = None
|
||||
while not done:
|
||||
(_, mexpiration) = _compute_times(timeout)
|
||||
if mexpiration is None or \
|
||||
(expiration is not None and mexpiration > expiration):
|
||||
mexpiration = expiration
|
||||
if is_udp:
|
||||
(rwire, _) = _udp_recv(s, 65535, mexpiration)
|
||||
else:
|
||||
ldata = _net_read(s, 2, mexpiration)
|
||||
(l,) = struct.unpack("!H", ldata)
|
||||
rwire = _net_read(s, l, mexpiration)
|
||||
r = dns.message.from_wire(rwire, keyring=query.keyring,
|
||||
request_mac=query.mac, xfr=True,
|
||||
origin=origin, tsig_ctx=tsig_ctx,
|
||||
multi=(not is_udp),
|
||||
one_rr_per_rrset=is_ixfr)
|
||||
try:
|
||||
done = inbound.process_message(r)
|
||||
except dns.xfr.UseTCP:
|
||||
assert is_udp # should not happen if we used TCP!
|
||||
if udp_mode == UDPMode.ONLY:
|
||||
raise
|
||||
done = True
|
||||
retry = True
|
||||
udp_mode = UDPMode.NEVER
|
||||
continue
|
||||
tsig_ctx = r.tsig_ctx
|
||||
if not retry and query.keyring and not r.had_tsig:
|
||||
raise dns.exception.FormError("missing TSIG")
|
||||
|
|
64
lib/dns/query.pyi
Normal file
64
lib/dns/query.pyi
Normal file
|
@ -0,0 +1,64 @@
|
|||
from typing import Optional, Union, Dict, Generator, Any
|
||||
from . import tsig, rdatatype, rdataclass, name, message
|
||||
from requests.sessions import Session
|
||||
|
||||
import socket
|
||||
|
||||
# If the ssl import works, then
|
||||
#
|
||||
# error: Name 'ssl' already defined (by an import)
|
||||
#
|
||||
# is expected and can be ignored.
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
class ssl: # type: ignore
|
||||
SSLContext : Dict = {}
|
||||
|
||||
have_doh: bool
|
||||
|
||||
def https(q : message.Message, where: str, timeout : Optional[float] = None,
|
||||
port : Optional[int] = 443, source : Optional[str] = None,
|
||||
source_port : Optional[int] = 0,
|
||||
session: Optional[Session] = None,
|
||||
path : Optional[str] = '/dns-query', post : Optional[bool] = True,
|
||||
bootstrap_address : Optional[str] = None,
|
||||
verify : Optional[bool] = True) -> message.Message:
|
||||
pass
|
||||
|
||||
def tcp(q : message.Message, where : str, timeout : float = None, port=53,
|
||||
af : Optional[int] = None, source : Optional[str] = None,
|
||||
source_port : Optional[int] = 0,
|
||||
one_rr_per_rrset : Optional[bool] = False,
|
||||
ignore_trailing : Optional[bool] = False,
|
||||
sock : Optional[socket.socket] = None) -> message.Message:
|
||||
pass
|
||||
|
||||
def xfr(where : None, zone : Union[name.Name,str], rdtype=rdatatype.AXFR,
|
||||
rdclass=rdataclass.IN,
|
||||
timeout : Optional[float] = None, port=53,
|
||||
keyring : Optional[Dict[name.Name, bytes]] = None,
|
||||
keyname : Union[str,name.Name]= None, relativize=True,
|
||||
lifetime : Optional[float] = None,
|
||||
source : Optional[str] = None, source_port=0, serial=0,
|
||||
use_udp : Optional[bool] = False,
|
||||
keyalgorithm=tsig.default_algorithm) \
|
||||
-> Generator[Any,Any,message.Message]:
|
||||
pass
|
||||
|
||||
def udp(q : message.Message, where : str, timeout : Optional[float] = None,
|
||||
port=53, source : Optional[str] = None, source_port : Optional[int] = 0,
|
||||
ignore_unexpected : Optional[bool] = False,
|
||||
one_rr_per_rrset : Optional[bool] = False,
|
||||
ignore_trailing : Optional[bool] = False,
|
||||
sock : Optional[socket.socket] = None) -> message.Message:
|
||||
pass
|
||||
|
||||
def tls(q : message.Message, where : str, timeout : Optional[float] = None,
|
||||
port=53, source : Optional[str] = None, source_port : Optional[int] = 0,
|
||||
one_rr_per_rrset : Optional[bool] = False,
|
||||
ignore_trailing : Optional[bool] = False,
|
||||
sock : Optional[socket.socket] = None,
|
||||
ssl_context: Optional[ssl.SSLContext] = None,
|
||||
server_hostname: Optional[str] = None) -> message.Message:
|
||||
pass
|
|
@ -72,7 +72,6 @@ class Rcode(dns.enum.IntEnum):
|
|||
def _unknown_exception_class(cls):
|
||||
return UnknownRcode
|
||||
|
||||
globals().update(Rcode.__members__)
|
||||
|
||||
class UnknownRcode(dns.exception.DNSException):
|
||||
"""A DNS rcode is unknown."""
|
||||
|
@ -104,8 +103,6 @@ def from_flags(flags, ednsflags):
|
|||
"""
|
||||
|
||||
value = (flags & 0x000f) | ((ednsflags >> 20) & 0xff0)
|
||||
if value < 0 or value > 4095:
|
||||
raise ValueError('rcode must be >= 0 and <= 4095')
|
||||
return value
|
||||
|
||||
|
||||
|
@ -139,3 +136,29 @@ def to_text(value, tsig=False):
|
|||
if tsig and value == Rcode.BADVERS:
|
||||
return 'BADSIG'
|
||||
return Rcode.to_text(value)
|
||||
|
||||
### BEGIN generated Rcode constants
|
||||
|
||||
NOERROR = Rcode.NOERROR
|
||||
FORMERR = Rcode.FORMERR
|
||||
SERVFAIL = Rcode.SERVFAIL
|
||||
NXDOMAIN = Rcode.NXDOMAIN
|
||||
NOTIMP = Rcode.NOTIMP
|
||||
REFUSED = Rcode.REFUSED
|
||||
YXDOMAIN = Rcode.YXDOMAIN
|
||||
YXRRSET = Rcode.YXRRSET
|
||||
NXRRSET = Rcode.NXRRSET
|
||||
NOTAUTH = Rcode.NOTAUTH
|
||||
NOTZONE = Rcode.NOTZONE
|
||||
DSOTYPENI = Rcode.DSOTYPENI
|
||||
BADVERS = Rcode.BADVERS
|
||||
BADSIG = Rcode.BADSIG
|
||||
BADKEY = Rcode.BADKEY
|
||||
BADTIME = Rcode.BADTIME
|
||||
BADMODE = Rcode.BADMODE
|
||||
BADNAME = Rcode.BADNAME
|
||||
BADALG = Rcode.BADALG
|
||||
BADTRUNC = Rcode.BADTRUNC
|
||||
BADCOOKIE = Rcode.BADCOOKIE
|
||||
|
||||
### END generated Rcode constants
|
||||
|
|
364
lib/dns/rdata.py
364
lib/dns/rdata.py
|
@ -23,43 +23,68 @@ import binascii
|
|||
import io
|
||||
import inspect
|
||||
import itertools
|
||||
import random
|
||||
|
||||
import dns.wire
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.ipv4
|
||||
import dns.ipv6
|
||||
import dns.name
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.tokenizer
|
||||
import dns.ttl
|
||||
|
||||
_chunksize = 32
|
||||
|
||||
# We currently allow comparisons for rdata with relative names for backwards
|
||||
# compatibility, but in the future we will not, as these kinds of comparisons
|
||||
# can lead to subtle bugs if code is not carefully written.
|
||||
#
|
||||
# This switch allows the future behavior to be turned on so code can be
|
||||
# tested with it.
|
||||
_allow_relative_comparisons = True
|
||||
|
||||
def _wordbreak(data, chunksize=_chunksize):
|
||||
|
||||
class NoRelativeRdataOrdering(dns.exception.DNSException):
|
||||
"""An attempt was made to do an ordered comparison of one or more
|
||||
rdata with relative names. The only reliable way of sorting rdata
|
||||
is to use non-relativized rdata.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def _wordbreak(data, chunksize=_chunksize, separator=b' '):
|
||||
"""Break a binary string into chunks of chunksize characters separated by
|
||||
a space.
|
||||
"""
|
||||
|
||||
if not chunksize:
|
||||
return data.decode()
|
||||
return b' '.join([data[i:i + chunksize]
|
||||
for i
|
||||
in range(0, len(data), chunksize)]).decode()
|
||||
return separator.join([data[i:i + chunksize]
|
||||
for i
|
||||
in range(0, len(data), chunksize)]).decode()
|
||||
|
||||
|
||||
def _hexify(data, chunksize=_chunksize):
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
def _hexify(data, chunksize=_chunksize, separator=b' ', **kw):
|
||||
"""Convert a binary string into its hex encoding, broken up into chunks
|
||||
of chunksize characters separated by a space.
|
||||
of chunksize characters separated by a separator.
|
||||
"""
|
||||
|
||||
return _wordbreak(binascii.hexlify(data), chunksize)
|
||||
return _wordbreak(binascii.hexlify(data), chunksize, separator)
|
||||
|
||||
|
||||
def _base64ify(data, chunksize=_chunksize):
|
||||
def _base64ify(data, chunksize=_chunksize, separator=b' ', **kw):
|
||||
"""Convert a binary string into its base64 encoding, broken up into chunks
|
||||
of chunksize characters separated by a space.
|
||||
of chunksize characters separated by a separator.
|
||||
"""
|
||||
|
||||
return _wordbreak(base64.b64encode(data), chunksize)
|
||||
return _wordbreak(base64.b64encode(data), chunksize, separator)
|
||||
|
||||
# pylint: enable=unused-argument
|
||||
|
||||
__escaped = b'"\\'
|
||||
|
||||
|
@ -92,26 +117,15 @@ def _truncate_bitmap(what):
|
|||
return what[0: i + 1]
|
||||
return what[0:1]
|
||||
|
||||
def _constify(o):
|
||||
"""
|
||||
Convert mutable types to immutable types.
|
||||
"""
|
||||
if isinstance(o, bytearray):
|
||||
return bytes(o)
|
||||
if isinstance(o, tuple):
|
||||
try:
|
||||
hash(o)
|
||||
return o
|
||||
except Exception:
|
||||
return tuple(_constify(elt) for elt in o)
|
||||
if isinstance(o, list):
|
||||
return tuple(_constify(elt) for elt in o)
|
||||
return o
|
||||
# So we don't have to edit all the rdata classes...
|
||||
_constify = dns.immutable.constify
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class Rdata:
|
||||
"""Base class for all DNS rdata types."""
|
||||
|
||||
__slots__ = ['rdclass', 'rdtype']
|
||||
__slots__ = ['rdclass', 'rdtype', 'rdcomment']
|
||||
|
||||
def __init__(self, rdclass, rdtype):
|
||||
"""Initialize an rdata.
|
||||
|
@ -121,16 +135,9 @@ class Rdata:
|
|||
*rdtype*, an ``int`` is the rdatatype of the Rdata.
|
||||
"""
|
||||
|
||||
object.__setattr__(self, 'rdclass', rdclass)
|
||||
object.__setattr__(self, 'rdtype', rdtype)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# Rdatas are immutable
|
||||
raise TypeError("object doesn't support attribute assignment")
|
||||
|
||||
def __delattr__(self, name):
|
||||
# Rdatas are immutable
|
||||
raise TypeError("object doesn't support attribute deletion")
|
||||
self.rdclass = self._as_rdataclass(rdclass)
|
||||
self.rdtype = self._as_rdatatype(rdtype)
|
||||
self.rdcomment = None
|
||||
|
||||
def _get_all_slots(self):
|
||||
return itertools.chain.from_iterable(getattr(cls, '__slots__', [])
|
||||
|
@ -153,6 +160,10 @@ class Rdata:
|
|||
def __setstate__(self, state):
|
||||
for slot, val in state.items():
|
||||
object.__setattr__(self, slot, val)
|
||||
if not hasattr(self, 'rdcomment'):
|
||||
# Pickled rdata from 2.0.x might not have a rdcomment, so add
|
||||
# it if needed.
|
||||
object.__setattr__(self, 'rdcomment', None)
|
||||
|
||||
def covers(self):
|
||||
"""Return the type a Rdata covers.
|
||||
|
@ -184,10 +195,10 @@ class Rdata:
|
|||
Returns a ``str``.
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
def to_wire(self, file=None, compress=None, origin=None,
|
||||
canonicalize=False):
|
||||
|
@ -237,12 +248,42 @@ class Rdata:
|
|||
"""Compare an rdata with another rdata of the same rdtype and
|
||||
rdclass.
|
||||
|
||||
Return < 0 if self < other in the DNSSEC ordering, 0 if self
|
||||
== other, and > 0 if self > other.
|
||||
|
||||
For rdata with only absolute names:
|
||||
Return < 0 if self < other in the DNSSEC ordering, 0 if self
|
||||
== other, and > 0 if self > other.
|
||||
For rdata with at least one relative names:
|
||||
The rdata sorts before any rdata with only absolute names.
|
||||
When compared with another relative rdata, all names are
|
||||
made absolute as if they were relative to the root, as the
|
||||
proper origin is not available. While this creates a stable
|
||||
ordering, it is NOT guaranteed to be the DNSSEC ordering.
|
||||
In the future, all ordering comparisons for rdata with
|
||||
relative names will be disallowed.
|
||||
"""
|
||||
our = self.to_digestable(dns.name.root)
|
||||
their = other.to_digestable(dns.name.root)
|
||||
try:
|
||||
our = self.to_digestable()
|
||||
our_relative = False
|
||||
except dns.name.NeedAbsoluteNameOrOrigin:
|
||||
if _allow_relative_comparisons:
|
||||
our = self.to_digestable(dns.name.root)
|
||||
our_relative = True
|
||||
try:
|
||||
their = other.to_digestable()
|
||||
their_relative = False
|
||||
except dns.name.NeedAbsoluteNameOrOrigin:
|
||||
if _allow_relative_comparisons:
|
||||
their = other.to_digestable(dns.name.root)
|
||||
their_relative = True
|
||||
if _allow_relative_comparisons:
|
||||
if our_relative != their_relative:
|
||||
# For the purpose of comparison, all rdata with at least one
|
||||
# relative name is less than an rdata with only absolute names.
|
||||
if our_relative:
|
||||
return -1
|
||||
else:
|
||||
return 1
|
||||
elif our_relative or their_relative:
|
||||
raise NoRelativeRdataOrdering
|
||||
if our == their:
|
||||
return 0
|
||||
elif our > their:
|
||||
|
@ -255,14 +296,28 @@ class Rdata:
|
|||
return False
|
||||
if self.rdclass != other.rdclass or self.rdtype != other.rdtype:
|
||||
return False
|
||||
return self._cmp(other) == 0
|
||||
our_relative = False
|
||||
their_relative = False
|
||||
try:
|
||||
our = self.to_digestable()
|
||||
except dns.name.NeedAbsoluteNameOrOrigin:
|
||||
our = self.to_digestable(dns.name.root)
|
||||
our_relative = True
|
||||
try:
|
||||
their = other.to_digestable()
|
||||
except dns.name.NeedAbsoluteNameOrOrigin:
|
||||
their = other.to_digestable(dns.name.root)
|
||||
their_relative = True
|
||||
if our_relative != their_relative:
|
||||
return False
|
||||
return our == their
|
||||
|
||||
def __ne__(self, other):
|
||||
if not isinstance(other, Rdata):
|
||||
return True
|
||||
if self.rdclass != other.rdclass or self.rdtype != other.rdtype:
|
||||
return True
|
||||
return self._cmp(other) != 0
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, Rdata) or \
|
||||
|
@ -295,11 +350,11 @@ class Rdata:
|
|||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
@classmethod
|
||||
def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
|
||||
raise NotImplementedError
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
def replace(self, **kwargs):
|
||||
"""
|
||||
|
@ -319,6 +374,8 @@ class Rdata:
|
|||
# Ensure that all of the arguments correspond to valid fields.
|
||||
# Don't allow rdclass or rdtype to be changed, though.
|
||||
for key in kwargs:
|
||||
if key == 'rdcomment':
|
||||
continue
|
||||
if key not in parameters:
|
||||
raise AttributeError("'{}' object has no attribute '{}'"
|
||||
.format(self.__class__.__name__, key))
|
||||
|
@ -331,13 +388,149 @@ class Rdata:
|
|||
args = (kwargs.get(key, getattr(self, key)) for key in parameters)
|
||||
|
||||
# Create, validate, and return the new object.
|
||||
#
|
||||
# Note that if we make constructors do validation in the future,
|
||||
# this validation can go away.
|
||||
rd = self.__class__(*args)
|
||||
dns.rdata.from_text(rd.rdclass, rd.rdtype, rd.to_text())
|
||||
# The comment is not set in the constructor, so give it special
|
||||
# handling.
|
||||
rdcomment = kwargs.get('rdcomment', self.rdcomment)
|
||||
if rdcomment is not None:
|
||||
object.__setattr__(rd, 'rdcomment', rdcomment)
|
||||
return rd
|
||||
|
||||
# Type checking and conversion helpers. These are class methods as
|
||||
# they don't touch object state and may be useful to others.
|
||||
|
||||
@classmethod
|
||||
def _as_rdataclass(cls, value):
|
||||
return dns.rdataclass.RdataClass.make(value)
|
||||
|
||||
@classmethod
|
||||
def _as_rdatatype(cls, value):
|
||||
return dns.rdatatype.RdataType.make(value)
|
||||
|
||||
@classmethod
|
||||
def _as_bytes(cls, value, encode=False, max_length=None, empty_ok=True):
|
||||
if encode and isinstance(value, str):
|
||||
value = value.encode()
|
||||
elif isinstance(value, bytearray):
|
||||
value = bytes(value)
|
||||
elif not isinstance(value, bytes):
|
||||
raise ValueError('not bytes')
|
||||
if max_length is not None and len(value) > max_length:
|
||||
raise ValueError('too long')
|
||||
if not empty_ok and len(value) == 0:
|
||||
raise ValueError('empty bytes not allowed')
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _as_name(cls, value):
|
||||
# Note that proper name conversion (e.g. with origin and IDNA
|
||||
# awareness) is expected to be done via from_text. This is just
|
||||
# a simple thing for people invoking the constructor directly.
|
||||
if isinstance(value, str):
|
||||
return dns.name.from_text(value)
|
||||
elif not isinstance(value, dns.name.Name):
|
||||
raise ValueError('not a name')
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _as_uint8(cls, value):
|
||||
if not isinstance(value, int):
|
||||
raise ValueError('not an integer')
|
||||
if value < 0 or value > 255:
|
||||
raise ValueError('not a uint8')
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _as_uint16(cls, value):
|
||||
if not isinstance(value, int):
|
||||
raise ValueError('not an integer')
|
||||
if value < 0 or value > 65535:
|
||||
raise ValueError('not a uint16')
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _as_uint32(cls, value):
|
||||
if not isinstance(value, int):
|
||||
raise ValueError('not an integer')
|
||||
if value < 0 or value > 4294967295:
|
||||
raise ValueError('not a uint32')
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _as_uint48(cls, value):
|
||||
if not isinstance(value, int):
|
||||
raise ValueError('not an integer')
|
||||
if value < 0 or value > 281474976710655:
|
||||
raise ValueError('not a uint48')
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _as_int(cls, value, low=None, high=None):
|
||||
if not isinstance(value, int):
|
||||
raise ValueError('not an integer')
|
||||
if low is not None and value < low:
|
||||
raise ValueError('value too small')
|
||||
if high is not None and value > high:
|
||||
raise ValueError('value too large')
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _as_ipv4_address(cls, value):
|
||||
if isinstance(value, str):
|
||||
# call to check validity
|
||||
dns.ipv4.inet_aton(value)
|
||||
return value
|
||||
elif isinstance(value, bytes):
|
||||
return dns.ipv4.inet_ntoa(value)
|
||||
else:
|
||||
raise ValueError('not an IPv4 address')
|
||||
|
||||
@classmethod
|
||||
def _as_ipv6_address(cls, value):
|
||||
if isinstance(value, str):
|
||||
# call to check validity
|
||||
dns.ipv6.inet_aton(value)
|
||||
return value
|
||||
elif isinstance(value, bytes):
|
||||
return dns.ipv6.inet_ntoa(value)
|
||||
else:
|
||||
raise ValueError('not an IPv6 address')
|
||||
|
||||
@classmethod
|
||||
def _as_bool(cls, value):
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
else:
|
||||
raise ValueError('not a boolean')
|
||||
|
||||
@classmethod
|
||||
def _as_ttl(cls, value):
|
||||
if isinstance(value, int):
|
||||
return cls._as_int(value, 0, dns.ttl.MAX_TTL)
|
||||
elif isinstance(value, str):
|
||||
return dns.ttl.from_text(value)
|
||||
else:
|
||||
raise ValueError('not a TTL')
|
||||
|
||||
@classmethod
|
||||
def _as_tuple(cls, value, as_value):
|
||||
try:
|
||||
# For user convenience, if value is a singleton of the list
|
||||
# element type, wrap it in a tuple.
|
||||
return (as_value(value),)
|
||||
except Exception:
|
||||
# Otherwise, check each element of the iterable *value*
|
||||
# against *as_value*.
|
||||
return tuple(as_value(v) for v in value)
|
||||
|
||||
# Processing order
|
||||
|
||||
@classmethod
|
||||
def _processing_order(cls, iterable):
|
||||
items = list(iterable)
|
||||
random.shuffle(items)
|
||||
return items
|
||||
|
||||
|
||||
class GenericRdata(Rdata):
|
||||
|
||||
|
@ -354,7 +547,7 @@ class GenericRdata(Rdata):
|
|||
object.__setattr__(self, 'data', data)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return r'\# %d ' % len(self.data) + _hexify(self.data)
|
||||
return r'\# %d ' % len(self.data) + _hexify(self.data, **kw)
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
|
@ -364,13 +557,7 @@ class GenericRdata(Rdata):
|
|||
raise dns.exception.SyntaxError(
|
||||
r'generic rdata does not start with \#')
|
||||
length = tok.get_int()
|
||||
chunks = []
|
||||
while 1:
|
||||
token = tok.get()
|
||||
if token.is_eol_or_eof():
|
||||
break
|
||||
chunks.append(token.value.encode())
|
||||
hex = b''.join(chunks)
|
||||
hex = tok.concatenate_remaining_identifiers().encode()
|
||||
data = binascii.unhexlify(hex)
|
||||
if len(data) != length:
|
||||
raise dns.exception.SyntaxError(
|
||||
|
@ -453,29 +640,45 @@ def from_text(rdclass, rdtype, tok, origin=None, relativize=True,
|
|||
Returns an instance of the chosen Rdata subclass.
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(tok, str):
|
||||
tok = dns.tokenizer.Tokenizer(tok, idna_codec=idna_codec)
|
||||
rdclass = dns.rdataclass.RdataClass.make(rdclass)
|
||||
rdtype = dns.rdatatype.RdataType.make(rdtype)
|
||||
cls = get_rdata_class(rdclass, rdtype)
|
||||
if cls != GenericRdata:
|
||||
# peek at first token
|
||||
token = tok.get()
|
||||
tok.unget(token)
|
||||
if token.is_identifier() and \
|
||||
token.value == r'\#':
|
||||
#
|
||||
# Known type using the generic syntax. Extract the
|
||||
# wire form from the generic syntax, and then run
|
||||
# from_wire on it.
|
||||
#
|
||||
rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
|
||||
relativize, relativize_to)
|
||||
return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
|
||||
origin)
|
||||
return cls.from_text(rdclass, rdtype, tok, origin, relativize,
|
||||
relativize_to)
|
||||
with dns.exception.ExceptionWrapper(dns.exception.SyntaxError):
|
||||
rdata = None
|
||||
if cls != GenericRdata:
|
||||
# peek at first token
|
||||
token = tok.get()
|
||||
tok.unget(token)
|
||||
if token.is_identifier() and \
|
||||
token.value == r'\#':
|
||||
#
|
||||
# Known type using the generic syntax. Extract the
|
||||
# wire form from the generic syntax, and then run
|
||||
# from_wire on it.
|
||||
#
|
||||
grdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
|
||||
relativize, relativize_to)
|
||||
rdata = from_wire(rdclass, rdtype, grdata.data, 0,
|
||||
len(grdata.data), origin)
|
||||
#
|
||||
# If this comparison isn't equal, then there must have been
|
||||
# compressed names in the wire format, which is an error,
|
||||
# there being no reasonable context to decompress with.
|
||||
#
|
||||
rwire = rdata.to_wire()
|
||||
if rwire != grdata.data:
|
||||
raise dns.exception.SyntaxError('compressed data in '
|
||||
'generic syntax form '
|
||||
'of known rdatatype')
|
||||
if rdata is None:
|
||||
rdata = cls.from_text(rdclass, rdtype, tok, origin, relativize,
|
||||
relativize_to)
|
||||
token = tok.get_eol_as_token()
|
||||
if token.comment is not None:
|
||||
object.__setattr__(rdata, 'rdcomment', token.comment)
|
||||
return rdata
|
||||
|
||||
|
||||
def from_wire_parser(rdclass, rdtype, parser, origin=None):
|
||||
|
@ -505,7 +708,8 @@ def from_wire_parser(rdclass, rdtype, parser, origin=None):
|
|||
rdclass = dns.rdataclass.RdataClass.make(rdclass)
|
||||
rdtype = dns.rdatatype.RdataType.make(rdtype)
|
||||
cls = get_rdata_class(rdclass, rdtype)
|
||||
return cls.from_wire_parser(rdclass, rdtype, parser, origin)
|
||||
with dns.exception.ExceptionWrapper(dns.exception.FormError):
|
||||
return cls.from_wire_parser(rdclass, rdtype, parser, origin)
|
||||
|
||||
|
||||
def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None):
|
||||
|
@ -543,7 +747,7 @@ def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None):
|
|||
class RdatatypeExists(dns.exception.DNSException):
|
||||
"""DNS rdatatype already exists."""
|
||||
supp_kwargs = {'rdclass', 'rdtype'}
|
||||
fmt = "The rdata type with class {rdclass} and rdtype {rdtype} " + \
|
||||
fmt = "The rdata type with class {rdclass:d} and rdtype {rdtype:d} " + \
|
||||
"already exists."
|
||||
|
||||
|
||||
|
|
19
lib/dns/rdata.pyi
Normal file
19
lib/dns/rdata.pyi
Normal file
|
@ -0,0 +1,19 @@
|
|||
from typing import Dict, Tuple, Any, Optional, BinaryIO
|
||||
from .name import Name, IDNACodec
|
||||
class Rdata:
|
||||
def __init__(self):
|
||||
self.address : str
|
||||
def to_wire(self, file : Optional[BinaryIO], compress : Optional[Dict[Name,int]], origin : Optional[Name], canonicalize : Optional[bool]) -> Optional[bytes]:
|
||||
...
|
||||
@classmethod
|
||||
def from_text(cls, rdclass : int, rdtype : int, tok, origin=None, relativize=True):
|
||||
...
|
||||
_rdata_modules : Dict[Tuple[Any,Rdata],Any]
|
||||
|
||||
def from_text(rdclass : int, rdtype : int, tok : Optional[str], origin : Optional[Name] = None,
|
||||
relativize : bool = True, relativize_to : Optional[Name] = None,
|
||||
idna_codec : Optional[IDNACodec] = None):
|
||||
...
|
||||
|
||||
def from_wire(rdclass : int, rdtype : int, wire : bytes, current : int, rdlen : int, origin : Optional[Name] = None):
|
||||
...
|
|
@ -48,7 +48,6 @@ class RdataClass(dns.enum.IntEnum):
|
|||
def _unknown_exception_class(cls):
|
||||
return UnknownRdataclass
|
||||
|
||||
globals().update(RdataClass.__members__)
|
||||
|
||||
_metaclasses = {RdataClass.NONE, RdataClass.ANY}
|
||||
|
||||
|
@ -100,3 +99,17 @@ def is_metaclass(rdclass):
|
|||
if rdclass in _metaclasses:
|
||||
return True
|
||||
return False
|
||||
|
||||
### BEGIN generated RdataClass constants
|
||||
|
||||
RESERVED0 = RdataClass.RESERVED0
|
||||
IN = RdataClass.IN
|
||||
INTERNET = RdataClass.INTERNET
|
||||
CH = RdataClass.CH
|
||||
CHAOS = RdataClass.CHAOS
|
||||
HS = RdataClass.HS
|
||||
HESIOD = RdataClass.HESIOD
|
||||
NONE = RdataClass.NONE
|
||||
ANY = RdataClass.ANY
|
||||
|
||||
### END generated RdataClass constants
|
||||
|
|
|
@ -22,6 +22,7 @@ import random
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdatatype
|
||||
import dns.rdataclass
|
||||
import dns.rdata
|
||||
|
@ -79,15 +80,15 @@ class Rdataset(dns.set.Set):
|
|||
TTL or the specified TTL. If the set contains no rdatas, set the TTL
|
||||
to the specified TTL.
|
||||
|
||||
*ttl*, an ``int``.
|
||||
*ttl*, an ``int`` or ``str``.
|
||||
"""
|
||||
|
||||
ttl = dns.ttl.make(ttl)
|
||||
if len(self) == 0:
|
||||
self.ttl = ttl
|
||||
elif ttl < self.ttl:
|
||||
self.ttl = ttl
|
||||
|
||||
def add(self, rd, ttl=None):
|
||||
def add(self, rd, ttl=None): # pylint: disable=arguments-differ
|
||||
"""Add the specified rdata to the rdataset.
|
||||
|
||||
If the optional *ttl* parameter is supplied, then
|
||||
|
@ -176,8 +177,8 @@ class Rdataset(dns.set.Set):
|
|||
return not self.__eq__(other)
|
||||
|
||||
def to_text(self, name=None, origin=None, relativize=True,
|
||||
override_rdclass=None, **kw):
|
||||
"""Convert the rdataset into DNS master file format.
|
||||
override_rdclass=None, want_comments=False, **kw):
|
||||
"""Convert the rdataset into DNS zone file format.
|
||||
|
||||
See ``dns.name.Name.choose_relativity`` for more information
|
||||
on how *origin* and *relativize* determine the way names
|
||||
|
@ -194,6 +195,12 @@ class Rdataset(dns.set.Set):
|
|||
|
||||
*relativize*, a ``bool``. If ``True``, names will be relativized
|
||||
to *origin*.
|
||||
|
||||
*override_rdclass*, a ``dns.rdataclass.RdataClass`` or ``None``.
|
||||
If not ``None``, use this class instead of the Rdataset's class.
|
||||
|
||||
*want_comments*, a ``bool``. If ``True``, emit comments for rdata
|
||||
which have them. The default is ``False``.
|
||||
"""
|
||||
|
||||
if name is not None:
|
||||
|
@ -219,11 +226,16 @@ class Rdataset(dns.set.Set):
|
|||
dns.rdatatype.to_text(self.rdtype)))
|
||||
else:
|
||||
for rd in self:
|
||||
s.write('%s%s%d %s %s %s\n' %
|
||||
extra = ''
|
||||
if want_comments:
|
||||
if rd.rdcomment:
|
||||
extra = f' ;{rd.rdcomment}'
|
||||
s.write('%s%s%d %s %s %s%s\n' %
|
||||
(ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass),
|
||||
dns.rdatatype.to_text(self.rdtype),
|
||||
rd.to_text(origin=origin, relativize=relativize,
|
||||
**kw)))
|
||||
**kw),
|
||||
extra))
|
||||
#
|
||||
# We strip off the final \n for the caller's convenience in printing
|
||||
#
|
||||
|
@ -260,7 +272,7 @@ class Rdataset(dns.set.Set):
|
|||
want_shuffle = False
|
||||
else:
|
||||
rdclass = self.rdclass
|
||||
file.seek(0, 2)
|
||||
file.seek(0, io.SEEK_END)
|
||||
if len(self) == 0:
|
||||
name.to_wire(file, compress, origin)
|
||||
stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)
|
||||
|
@ -284,7 +296,7 @@ class Rdataset(dns.set.Set):
|
|||
file.seek(start - 2)
|
||||
stuff = struct.pack("!H", end - start)
|
||||
file.write(stuff)
|
||||
file.seek(0, 2)
|
||||
file.seek(0, io.SEEK_END)
|
||||
return len(self)
|
||||
|
||||
def match(self, rdclass, rdtype, covers):
|
||||
|
@ -297,8 +309,86 @@ class Rdataset(dns.set.Set):
|
|||
return True
|
||||
return False
|
||||
|
||||
def processing_order(self):
|
||||
"""Return rdatas in a valid processing order according to the type's
|
||||
specification. For example, MX records are in preference order from
|
||||
lowest to highest preferences, with items of the same perference
|
||||
shuffled.
|
||||
|
||||
def from_text_list(rdclass, rdtype, ttl, text_rdatas, idna_codec=None):
|
||||
For types that do not define a processing order, the rdatas are
|
||||
simply shuffled.
|
||||
"""
|
||||
if len(self) == 0:
|
||||
return []
|
||||
else:
|
||||
return self[0]._processing_order(iter(self))
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class ImmutableRdataset(Rdataset):
|
||||
|
||||
"""An immutable DNS rdataset."""
|
||||
|
||||
_clone_class = Rdataset
|
||||
|
||||
def __init__(self, rdataset):
|
||||
"""Create an immutable rdataset from the specified rdataset."""
|
||||
|
||||
super().__init__(rdataset.rdclass, rdataset.rdtype, rdataset.covers,
|
||||
rdataset.ttl)
|
||||
self.items = dns.immutable.Dict(rdataset.items)
|
||||
|
||||
def update_ttl(self, ttl):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def add(self, rd, ttl=None):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def union_update(self, other):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def intersection_update(self, other):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def update(self, other):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def __delitem__(self, i):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def __ior__(self, other):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def __iand__(self, other):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def __iadd__(self, other):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def __isub__(self, other):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def clear(self):
|
||||
raise TypeError('immutable')
|
||||
|
||||
def __copy__(self):
|
||||
return ImmutableRdataset(super().copy())
|
||||
|
||||
def copy(self):
|
||||
return ImmutableRdataset(super().copy())
|
||||
|
||||
def union(self, other):
|
||||
return ImmutableRdataset(super().union(other))
|
||||
|
||||
def intersection(self, other):
|
||||
return ImmutableRdataset(super().intersection(other))
|
||||
|
||||
def difference(self, other):
|
||||
return ImmutableRdataset(super().difference(other))
|
||||
|
||||
|
||||
def from_text_list(rdclass, rdtype, ttl, text_rdatas, idna_codec=None,
|
||||
origin=None, relativize=True, relativize_to=None):
|
||||
"""Create an rdataset with the specified class, type, and TTL, and with
|
||||
the specified list of rdatas in text format.
|
||||
|
||||
|
@ -306,6 +396,14 @@ def from_text_list(rdclass, rdtype, ttl, text_rdatas, idna_codec=None):
|
|||
encoder/decoder to use; if ``None``, the default IDNA 2003
|
||||
encoder/decoder is used.
|
||||
|
||||
*origin*, a ``dns.name.Name`` (or ``None``), the
|
||||
origin to use for relative names.
|
||||
|
||||
*relativize*, a ``bool``. If true, name will be relativized.
|
||||
|
||||
*relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use
|
||||
when relativizing names. If not set, the *origin* value will be used.
|
||||
|
||||
Returns a ``dns.rdataset.Rdataset`` object.
|
||||
"""
|
||||
|
||||
|
@ -314,7 +412,8 @@ def from_text_list(rdclass, rdtype, ttl, text_rdatas, idna_codec=None):
|
|||
r = Rdataset(rdclass, rdtype)
|
||||
r.update_ttl(ttl)
|
||||
for t in text_rdatas:
|
||||
rd = dns.rdata.from_text(r.rdclass, r.rdtype, t, idna_codec=idna_codec)
|
||||
rd = dns.rdata.from_text(r.rdclass, r.rdtype, t, origin, relativize,
|
||||
relativize_to, idna_codec)
|
||||
r.add(rd)
|
||||
return r
|
||||
|
||||
|
|
58
lib/dns/rdataset.pyi
Normal file
58
lib/dns/rdataset.pyi
Normal file
|
@ -0,0 +1,58 @@
|
|||
from typing import Optional, Dict, List, Union
|
||||
from io import BytesIO
|
||||
from . import exception, name, set, rdatatype, rdata, rdataset
|
||||
|
||||
class DifferingCovers(exception.DNSException):
|
||||
"""An attempt was made to add a DNS SIG/RRSIG whose covered type
|
||||
is not the same as that of the other rdatas in the rdataset."""
|
||||
|
||||
|
||||
class IncompatibleTypes(exception.DNSException):
|
||||
"""An attempt was made to add DNS RR data of an incompatible type."""
|
||||
|
||||
|
||||
class Rdataset(set.Set):
|
||||
def __init__(self, rdclass, rdtype, covers=rdatatype.NONE, ttl=0):
|
||||
self.rdclass : int = rdclass
|
||||
self.rdtype : int = rdtype
|
||||
self.covers : int = covers
|
||||
self.ttl : int = ttl
|
||||
|
||||
def update_ttl(self, ttl : int) -> None:
|
||||
...
|
||||
|
||||
def add(self, rd : rdata.Rdata, ttl : Optional[int] =None):
|
||||
...
|
||||
|
||||
def union_update(self, other : Rdataset):
|
||||
...
|
||||
|
||||
def intersection_update(self, other : Rdataset):
|
||||
...
|
||||
|
||||
def update(self, other : Rdataset):
|
||||
...
|
||||
|
||||
def to_text(self, name : Optional[name.Name] =None, origin : Optional[name.Name] =None, relativize=True,
|
||||
override_rdclass : Optional[int] =None, **kw) -> bytes:
|
||||
...
|
||||
|
||||
def to_wire(self, name : Optional[name.Name], file : BytesIO, compress : Optional[Dict[name.Name, int]] = None, origin : Optional[name.Name] = None,
|
||||
override_rdclass : Optional[int] = None, want_shuffle=True) -> int:
|
||||
...
|
||||
|
||||
def match(self, rdclass : int, rdtype : int, covers : int) -> bool:
|
||||
...
|
||||
|
||||
|
||||
def from_text_list(rdclass : Union[int,str], rdtype : Union[int,str], ttl : int, text_rdatas : str, idna_codec : Optional[name.IDNACodec] = None) -> rdataset.Rdataset:
|
||||
...
|
||||
|
||||
def from_text(rdclass : Union[int,str], rdtype : Union[int,str], ttl : int, *text_rdatas : str) -> rdataset.Rdataset:
|
||||
...
|
||||
|
||||
def from_rdata_list(ttl : int, rdatas : List[rdata.Rdata]) -> rdataset.Rdataset:
|
||||
...
|
||||
|
||||
def from_rdata(ttl : int, *rdatas : List[rdata.Rdata]) -> rdataset.Rdataset:
|
||||
...
|
|
@ -72,14 +72,22 @@ class RdataType(dns.enum.IntEnum):
|
|||
NSEC3 = 50
|
||||
NSEC3PARAM = 51
|
||||
TLSA = 52
|
||||
SMIMEA = 53
|
||||
HIP = 55
|
||||
NINFO = 56
|
||||
CDS = 59
|
||||
CDNSKEY = 60
|
||||
OPENPGPKEY = 61
|
||||
CSYNC = 62
|
||||
ZONEMD = 63
|
||||
SVCB = 64
|
||||
HTTPS = 65
|
||||
SPF = 99
|
||||
UNSPEC = 103
|
||||
NID = 104
|
||||
L32 = 105
|
||||
L64 = 106
|
||||
LP = 107
|
||||
EUI48 = 108
|
||||
EUI64 = 109
|
||||
TKEY = 249
|
||||
|
@ -92,7 +100,7 @@ class RdataType(dns.enum.IntEnum):
|
|||
URI = 256
|
||||
CAA = 257
|
||||
AVC = 258
|
||||
AMTRELAY = 259
|
||||
AMTRELAY = 260
|
||||
TA = 32768
|
||||
DLV = 32769
|
||||
|
||||
|
@ -115,8 +123,6 @@ class RdataType(dns.enum.IntEnum):
|
|||
_registered_by_text = {}
|
||||
_registered_by_value = {}
|
||||
|
||||
globals().update(RdataType.__members__)
|
||||
|
||||
_metatypes = {RdataType.OPT}
|
||||
|
||||
_singletons = {RdataType.SOA, RdataType.NXT, RdataType.DNAME,
|
||||
|
@ -219,3 +225,89 @@ def register_type(rdtype, rdtype_text, is_singleton=False):
|
|||
_registered_by_value[rdtype] = rdtype_text
|
||||
if is_singleton:
|
||||
_singletons.add(rdtype)
|
||||
|
||||
### BEGIN generated RdataType constants
|
||||
|
||||
TYPE0 = RdataType.TYPE0
|
||||
NONE = RdataType.NONE
|
||||
A = RdataType.A
|
||||
NS = RdataType.NS
|
||||
MD = RdataType.MD
|
||||
MF = RdataType.MF
|
||||
CNAME = RdataType.CNAME
|
||||
SOA = RdataType.SOA
|
||||
MB = RdataType.MB
|
||||
MG = RdataType.MG
|
||||
MR = RdataType.MR
|
||||
NULL = RdataType.NULL
|
||||
WKS = RdataType.WKS
|
||||
PTR = RdataType.PTR
|
||||
HINFO = RdataType.HINFO
|
||||
MINFO = RdataType.MINFO
|
||||
MX = RdataType.MX
|
||||
TXT = RdataType.TXT
|
||||
RP = RdataType.RP
|
||||
AFSDB = RdataType.AFSDB
|
||||
X25 = RdataType.X25
|
||||
ISDN = RdataType.ISDN
|
||||
RT = RdataType.RT
|
||||
NSAP = RdataType.NSAP
|
||||
NSAP_PTR = RdataType.NSAP_PTR
|
||||
SIG = RdataType.SIG
|
||||
KEY = RdataType.KEY
|
||||
PX = RdataType.PX
|
||||
GPOS = RdataType.GPOS
|
||||
AAAA = RdataType.AAAA
|
||||
LOC = RdataType.LOC
|
||||
NXT = RdataType.NXT
|
||||
SRV = RdataType.SRV
|
||||
NAPTR = RdataType.NAPTR
|
||||
KX = RdataType.KX
|
||||
CERT = RdataType.CERT
|
||||
A6 = RdataType.A6
|
||||
DNAME = RdataType.DNAME
|
||||
OPT = RdataType.OPT
|
||||
APL = RdataType.APL
|
||||
DS = RdataType.DS
|
||||
SSHFP = RdataType.SSHFP
|
||||
IPSECKEY = RdataType.IPSECKEY
|
||||
RRSIG = RdataType.RRSIG
|
||||
NSEC = RdataType.NSEC
|
||||
DNSKEY = RdataType.DNSKEY
|
||||
DHCID = RdataType.DHCID
|
||||
NSEC3 = RdataType.NSEC3
|
||||
NSEC3PARAM = RdataType.NSEC3PARAM
|
||||
TLSA = RdataType.TLSA
|
||||
SMIMEA = RdataType.SMIMEA
|
||||
HIP = RdataType.HIP
|
||||
NINFO = RdataType.NINFO
|
||||
CDS = RdataType.CDS
|
||||
CDNSKEY = RdataType.CDNSKEY
|
||||
OPENPGPKEY = RdataType.OPENPGPKEY
|
||||
CSYNC = RdataType.CSYNC
|
||||
ZONEMD = RdataType.ZONEMD
|
||||
SVCB = RdataType.SVCB
|
||||
HTTPS = RdataType.HTTPS
|
||||
SPF = RdataType.SPF
|
||||
UNSPEC = RdataType.UNSPEC
|
||||
NID = RdataType.NID
|
||||
L32 = RdataType.L32
|
||||
L64 = RdataType.L64
|
||||
LP = RdataType.LP
|
||||
EUI48 = RdataType.EUI48
|
||||
EUI64 = RdataType.EUI64
|
||||
TKEY = RdataType.TKEY
|
||||
TSIG = RdataType.TSIG
|
||||
IXFR = RdataType.IXFR
|
||||
AXFR = RdataType.AXFR
|
||||
MAILB = RdataType.MAILB
|
||||
MAILA = RdataType.MAILA
|
||||
ANY = RdataType.ANY
|
||||
URI = RdataType.URI
|
||||
CAA = RdataType.CAA
|
||||
AVC = RdataType.AVC
|
||||
AMTRELAY = RdataType.AMTRELAY
|
||||
TA = RdataType.TA
|
||||
DLV = RdataType.DLV
|
||||
|
||||
### END generated RdataType constants
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.mxbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class AFSDB(dns.rdtypes.mxbase.UncompressedDowncasingMX):
|
||||
|
||||
"""AFSDB record"""
|
||||
|
|
|
@ -18,12 +18,19 @@
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdtypes.util
|
||||
|
||||
|
||||
class Relay(dns.rdtypes.util.Gateway):
|
||||
name = 'AMTRELAY relay'
|
||||
|
||||
@property
|
||||
def relay(self):
|
||||
return self.gateway
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class AMTRELAY(dns.rdata.Rdata):
|
||||
|
||||
"""AMTRELAY record"""
|
||||
|
@ -35,11 +42,11 @@ class AMTRELAY(dns.rdata.Rdata):
|
|||
def __init__(self, rdclass, rdtype, precedence, discovery_optional,
|
||||
relay_type, relay):
|
||||
super().__init__(rdclass, rdtype)
|
||||
Relay(relay_type, relay).check()
|
||||
object.__setattr__(self, 'precedence', precedence)
|
||||
object.__setattr__(self, 'discovery_optional', discovery_optional)
|
||||
object.__setattr__(self, 'relay_type', relay_type)
|
||||
object.__setattr__(self, 'relay', relay)
|
||||
relay = Relay(relay_type, relay)
|
||||
self.precedence = self._as_uint8(precedence)
|
||||
self.discovery_optional = self._as_bool(discovery_optional)
|
||||
self.relay_type = relay.type
|
||||
self.relay = relay.relay
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
relay = Relay(self.relay_type, self.relay).to_text(origin, relativize)
|
||||
|
@ -57,10 +64,10 @@ class AMTRELAY(dns.rdata.Rdata):
|
|||
relay_type = tok.get_uint8()
|
||||
if relay_type > 0x7f:
|
||||
raise dns.exception.SyntaxError('expecting an integer <= 127')
|
||||
relay = Relay(relay_type).from_text(tok, origin, relativize,
|
||||
relativize_to)
|
||||
relay = Relay.from_text(relay_type, tok, origin, relativize,
|
||||
relativize_to)
|
||||
return cls(rdclass, rdtype, precedence, discovery_optional, relay_type,
|
||||
relay)
|
||||
relay.relay)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
relay_type = self.relay_type | (self.discovery_optional << 7)
|
||||
|
@ -74,6 +81,6 @@ class AMTRELAY(dns.rdata.Rdata):
|
|||
(precedence, relay_type) = parser.get_struct('!BB')
|
||||
discovery_optional = bool(relay_type >> 7)
|
||||
relay_type &= 0x7f
|
||||
relay = Relay(relay_type).from_wire_parser(parser, origin)
|
||||
relay = Relay.from_wire_parser(relay_type, parser, origin)
|
||||
return cls(rdclass, rdtype, precedence, discovery_optional, relay_type,
|
||||
relay)
|
||||
relay.relay)
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.txtbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class AVC(dns.rdtypes.txtbase.TXTBase):
|
||||
|
||||
"""AVC record"""
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class CAA(dns.rdata.Rdata):
|
||||
|
||||
"""CAA (Certification Authority Authorization) record"""
|
||||
|
@ -32,9 +34,11 @@ class CAA(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, flags, tag, value):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'flags', flags)
|
||||
object.__setattr__(self, 'tag', tag)
|
||||
object.__setattr__(self, 'value', value)
|
||||
self.flags = self._as_uint8(flags)
|
||||
self.tag = self._as_bytes(tag, True, 255)
|
||||
if not tag.isalnum():
|
||||
raise ValueError("tag is not alphanumeric")
|
||||
self.value = self._as_bytes(value)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return '%u %s "%s"' % (self.flags,
|
||||
|
@ -46,10 +50,6 @@ class CAA(dns.rdata.Rdata):
|
|||
relativize_to=None):
|
||||
flags = tok.get_uint8()
|
||||
tag = tok.get_string().encode()
|
||||
if len(tag) > 255:
|
||||
raise dns.exception.SyntaxError("tag too long")
|
||||
if not tag.isalnum():
|
||||
raise dns.exception.SyntaxError("tag is not alphanumeric")
|
||||
value = tok.get_string().encode()
|
||||
return cls(rdclass, rdtype, flags, tag, value)
|
||||
|
||||
|
|
|
@ -16,9 +16,13 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.dnskeybase
|
||||
import dns.immutable
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from dns.rdtypes.dnskeybase import SEP, REVOKE, ZONE # noqa: F401
|
||||
# pylint: enable=unused-import
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class CDNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase):
|
||||
|
||||
"""CDNSKEY record"""
|
||||
|
|
|
@ -16,8 +16,15 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.dsbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class CDS(dns.rdtypes.dsbase.DSBase):
|
||||
|
||||
"""CDS record"""
|
||||
|
||||
_digest_length_by_type = {
|
||||
**dns.rdtypes.dsbase.DSBase._digest_length_by_type,
|
||||
0: 1, # delete, RFC 8078 Sec. 4 (including Errata ID 5049)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import struct
|
|||
import base64
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.dnssec
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
@ -27,6 +28,11 @@ _ctype_by_value = {
|
|||
1: 'PKIX',
|
||||
2: 'SPKI',
|
||||
3: 'PGP',
|
||||
4: 'IPKIX',
|
||||
5: 'ISPKI',
|
||||
6: 'IPGP',
|
||||
7: 'ACPKIX',
|
||||
8: 'IACPKIX',
|
||||
253: 'URI',
|
||||
254: 'OID',
|
||||
}
|
||||
|
@ -35,6 +41,11 @@ _ctype_by_name = {
|
|||
'PKIX': 1,
|
||||
'SPKI': 2,
|
||||
'PGP': 3,
|
||||
'IPKIX': 4,
|
||||
'ISPKI': 5,
|
||||
'IPGP': 6,
|
||||
'ACPKIX': 7,
|
||||
'IACPKIX': 8,
|
||||
'URI': 253,
|
||||
'OID': 254,
|
||||
}
|
||||
|
@ -54,27 +65,28 @@ def _ctype_to_text(what):
|
|||
return str(what)
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class CERT(dns.rdata.Rdata):
|
||||
|
||||
"""CERT record"""
|
||||
|
||||
# see RFC 2538
|
||||
# see RFC 4398
|
||||
|
||||
__slots__ = ['certificate_type', 'key_tag', 'algorithm', 'certificate']
|
||||
|
||||
def __init__(self, rdclass, rdtype, certificate_type, key_tag, algorithm,
|
||||
certificate):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'certificate_type', certificate_type)
|
||||
object.__setattr__(self, 'key_tag', key_tag)
|
||||
object.__setattr__(self, 'algorithm', algorithm)
|
||||
object.__setattr__(self, 'certificate', certificate)
|
||||
self.certificate_type = self._as_uint16(certificate_type)
|
||||
self.key_tag = self._as_uint16(key_tag)
|
||||
self.algorithm = self._as_uint8(algorithm)
|
||||
self.certificate = self._as_bytes(certificate)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
certificate_type = _ctype_to_text(self.certificate_type)
|
||||
return "%s %d %s %s" % (certificate_type, self.key_tag,
|
||||
dns.dnssec.algorithm_to_text(self.algorithm),
|
||||
dns.rdata._base64ify(self.certificate))
|
||||
dns.rdata._base64ify(self.certificate, **kw))
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
|
@ -82,8 +94,6 @@ class CERT(dns.rdata.Rdata):
|
|||
certificate_type = _ctype_from_text(tok.get_string())
|
||||
key_tag = tok.get_uint16()
|
||||
algorithm = dns.dnssec.algorithm_from_text(tok.get_string())
|
||||
if algorithm < 0 or algorithm > 255:
|
||||
raise dns.exception.SyntaxError("bad algorithm type")
|
||||
b64 = tok.concatenate_remaining_identifiers().encode()
|
||||
certificate = base64.b64decode(b64)
|
||||
return cls(rdclass, rdtype, certificate_type, key_tag,
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.nsbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class CNAME(dns.rdtypes.nsbase.NSBase):
|
||||
|
||||
"""CNAME record
|
||||
|
|
|
@ -18,16 +18,19 @@
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.rdatatype
|
||||
import dns.name
|
||||
import dns.rdtypes.util
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class Bitmap(dns.rdtypes.util.Bitmap):
|
||||
type_name = 'CSYNC'
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class CSYNC(dns.rdata.Rdata):
|
||||
|
||||
"""CSYNC record"""
|
||||
|
@ -36,9 +39,11 @@ class CSYNC(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, serial, flags, windows):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'serial', serial)
|
||||
object.__setattr__(self, 'flags', flags)
|
||||
object.__setattr__(self, 'windows', dns.rdata._constify(windows))
|
||||
self.serial = self._as_uint32(serial)
|
||||
self.flags = self._as_uint16(flags)
|
||||
if not isinstance(windows, Bitmap):
|
||||
windows = Bitmap(windows)
|
||||
self.windows = tuple(windows.windows)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
text = Bitmap(self.windows).to_text()
|
||||
|
@ -49,8 +54,8 @@ class CSYNC(dns.rdata.Rdata):
|
|||
relativize_to=None):
|
||||
serial = tok.get_uint32()
|
||||
flags = tok.get_uint16()
|
||||
windows = Bitmap().from_text(tok)
|
||||
return cls(rdclass, rdtype, serial, flags, windows)
|
||||
bitmap = Bitmap.from_text(tok)
|
||||
return cls(rdclass, rdtype, serial, flags, bitmap)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
file.write(struct.pack('!IH', self.serial, self.flags))
|
||||
|
@ -59,5 +64,5 @@ class CSYNC(dns.rdata.Rdata):
|
|||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
(serial, flags) = parser.get_struct("!IH")
|
||||
windows = Bitmap().from_wire_parser(parser)
|
||||
return cls(rdclass, rdtype, serial, flags, windows)
|
||||
bitmap = Bitmap.from_wire_parser(parser)
|
||||
return cls(rdclass, rdtype, serial, flags, bitmap)
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.dsbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class DLV(dns.rdtypes.dsbase.DSBase):
|
||||
|
||||
"""DLV record"""
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.nsbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class DNAME(dns.rdtypes.nsbase.UncompressedNS):
|
||||
|
||||
"""DNAME record"""
|
||||
|
|
|
@ -16,9 +16,13 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.dnskeybase
|
||||
import dns.immutable
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from dns.rdtypes.dnskeybase import SEP, REVOKE, ZONE # noqa: F401
|
||||
# pylint: enable=unused-import
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class DNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase):
|
||||
|
||||
"""DNSKEY record"""
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.dsbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class DS(dns.rdtypes.dsbase.DSBase):
|
||||
|
||||
"""DS record"""
|
||||
|
|
|
@ -17,8 +17,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.euibase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class EUI48(dns.rdtypes.euibase.EUIBase):
|
||||
|
||||
"""EUI48 record"""
|
||||
|
|
|
@ -17,8 +17,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.euibase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class EUI64(dns.rdtypes.euibase.EUIBase):
|
||||
|
||||
"""EUI64 record"""
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
||||
|
@ -41,12 +42,7 @@ def _validate_float_string(what):
|
|||
raise dns.exception.FormError
|
||||
|
||||
|
||||
def _sanitize(value):
|
||||
if isinstance(value, str):
|
||||
return value.encode()
|
||||
return value
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class GPOS(dns.rdata.Rdata):
|
||||
|
||||
"""GPOS record"""
|
||||
|
@ -66,15 +62,15 @@ class GPOS(dns.rdata.Rdata):
|
|||
if isinstance(altitude, float) or \
|
||||
isinstance(altitude, int):
|
||||
altitude = str(altitude)
|
||||
latitude = _sanitize(latitude)
|
||||
longitude = _sanitize(longitude)
|
||||
altitude = _sanitize(altitude)
|
||||
latitude = self._as_bytes(latitude, True, 255)
|
||||
longitude = self._as_bytes(longitude, True, 255)
|
||||
altitude = self._as_bytes(altitude, True, 255)
|
||||
_validate_float_string(latitude)
|
||||
_validate_float_string(longitude)
|
||||
_validate_float_string(altitude)
|
||||
object.__setattr__(self, 'latitude', latitude)
|
||||
object.__setattr__(self, 'longitude', longitude)
|
||||
object.__setattr__(self, 'altitude', altitude)
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.altitude = altitude
|
||||
flat = self.float_latitude
|
||||
if flat < -90.0 or flat > 90.0:
|
||||
raise dns.exception.FormError('bad latitude')
|
||||
|
@ -93,7 +89,6 @@ class GPOS(dns.rdata.Rdata):
|
|||
latitude = tok.get_string()
|
||||
longitude = tok.get_string()
|
||||
altitude = tok.get_string()
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, latitude, longitude, altitude)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class HINFO(dns.rdata.Rdata):
|
||||
|
||||
"""HINFO record"""
|
||||
|
@ -32,14 +34,8 @@ class HINFO(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, cpu, os):
|
||||
super().__init__(rdclass, rdtype)
|
||||
if isinstance(cpu, str):
|
||||
object.__setattr__(self, 'cpu', cpu.encode())
|
||||
else:
|
||||
object.__setattr__(self, 'cpu', cpu)
|
||||
if isinstance(os, str):
|
||||
object.__setattr__(self, 'os', os.encode())
|
||||
else:
|
||||
object.__setattr__(self, 'os', os)
|
||||
self.cpu = self._as_bytes(cpu, True, 255)
|
||||
self.os = self._as_bytes(os, True, 255)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return '"{}" "{}"'.format(dns.rdata._escapify(self.cpu),
|
||||
|
@ -50,7 +46,6 @@ class HINFO(dns.rdata.Rdata):
|
|||
relativize_to=None):
|
||||
cpu = tok.get_string(max_length=255)
|
||||
os = tok.get_string(max_length=255)
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, cpu, os)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
|
|
|
@ -20,10 +20,12 @@ import base64
|
|||
import binascii
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.rdatatype
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class HIP(dns.rdata.Rdata):
|
||||
|
||||
"""HIP record"""
|
||||
|
@ -34,10 +36,10 @@ class HIP(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, hit, algorithm, key, servers):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'hit', hit)
|
||||
object.__setattr__(self, 'algorithm', algorithm)
|
||||
object.__setattr__(self, 'key', key)
|
||||
object.__setattr__(self, 'servers', dns.rdata._constify(servers))
|
||||
self.hit = self._as_bytes(hit, True, 255)
|
||||
self.algorithm = self._as_uint8(algorithm)
|
||||
self.key = self._as_bytes(key, True)
|
||||
self.servers = self._as_tuple(servers, self._as_name)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
hit = binascii.hexlify(self.hit).decode()
|
||||
|
@ -55,14 +57,9 @@ class HIP(dns.rdata.Rdata):
|
|||
relativize_to=None):
|
||||
algorithm = tok.get_uint8()
|
||||
hit = binascii.unhexlify(tok.get_string().encode())
|
||||
if len(hit) > 255:
|
||||
raise dns.exception.SyntaxError("HIT too long")
|
||||
key = base64.b64decode(tok.get_string().encode())
|
||||
servers = []
|
||||
while 1:
|
||||
token = tok.get()
|
||||
if token.is_eol_or_eof():
|
||||
break
|
||||
for token in tok.get_remaining():
|
||||
server = tok.as_name(token, origin, relativize, relativize_to)
|
||||
servers.append(server)
|
||||
return cls(rdclass, rdtype, hit, algorithm, key, servers)
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class ISDN(dns.rdata.Rdata):
|
||||
|
||||
"""ISDN record"""
|
||||
|
@ -32,14 +34,8 @@ class ISDN(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, address, subaddress):
|
||||
super().__init__(rdclass, rdtype)
|
||||
if isinstance(address, str):
|
||||
object.__setattr__(self, 'address', address.encode())
|
||||
else:
|
||||
object.__setattr__(self, 'address', address)
|
||||
if isinstance(address, str):
|
||||
object.__setattr__(self, 'subaddress', subaddress.encode())
|
||||
else:
|
||||
object.__setattr__(self, 'subaddress', subaddress)
|
||||
self.address = self._as_bytes(address, True, 255)
|
||||
self.subaddress = self._as_bytes(subaddress, True, 255)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
if self.subaddress:
|
||||
|
@ -52,14 +48,11 @@ class ISDN(dns.rdata.Rdata):
|
|||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
address = tok.get_string()
|
||||
t = tok.get()
|
||||
if not t.is_eol_or_eof():
|
||||
tok.unget(t)
|
||||
subaddress = tok.get_string()
|
||||
tokens = tok.get_remaining(max_tokens=1)
|
||||
if len(tokens) >= 1:
|
||||
subaddress = tokens[0].unescape().value
|
||||
else:
|
||||
tok.unget(t)
|
||||
subaddress = ''
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, address, subaddress)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
|
|
40
lib/dns/rdtypes/ANY/L32.py
Normal file
40
lib/dns/rdtypes/ANY/L32.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
import struct
|
||||
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class L32(dns.rdata.Rdata):
|
||||
|
||||
"""L32 record"""
|
||||
|
||||
# see: rfc6742.txt
|
||||
|
||||
__slots__ = ['preference', 'locator32']
|
||||
|
||||
def __init__(self, rdclass, rdtype, preference, locator32):
|
||||
super().__init__(rdclass, rdtype)
|
||||
self.preference = self._as_uint16(preference)
|
||||
self.locator32 = self._as_ipv4_address(locator32)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return f'{self.preference} {self.locator32}'
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
preference = tok.get_uint16()
|
||||
nodeid = tok.get_identifier()
|
||||
return cls(rdclass, rdtype, preference, nodeid)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
file.write(struct.pack('!H', self.preference))
|
||||
file.write(dns.ipv4.inet_aton(self.locator32))
|
||||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
preference = parser.get_uint16()
|
||||
locator32 = parser.get_remaining()
|
||||
return cls(rdclass, rdtype, preference, locator32)
|
48
lib/dns/rdtypes/ANY/L64.py
Normal file
48
lib/dns/rdtypes/ANY/L64.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
import struct
|
||||
|
||||
import dns.immutable
|
||||
import dns.rdtypes.util
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class L64(dns.rdata.Rdata):
|
||||
|
||||
"""L64 record"""
|
||||
|
||||
# see: rfc6742.txt
|
||||
|
||||
__slots__ = ['preference', 'locator64']
|
||||
|
||||
def __init__(self, rdclass, rdtype, preference, locator64):
|
||||
super().__init__(rdclass, rdtype)
|
||||
self.preference = self._as_uint16(preference)
|
||||
if isinstance(locator64, bytes):
|
||||
if len(locator64) != 8:
|
||||
raise ValueError('invalid locator64')
|
||||
self.locator64 = dns.rdata._hexify(locator64, 4, b':')
|
||||
else:
|
||||
dns.rdtypes.util.parse_formatted_hex(locator64, 4, 4, ':')
|
||||
self.locator64 = locator64
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return f'{self.preference} {self.locator64}'
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
preference = tok.get_uint16()
|
||||
locator64 = tok.get_identifier()
|
||||
return cls(rdclass, rdtype, preference, locator64)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
file.write(struct.pack('!H', self.preference))
|
||||
file.write(dns.rdtypes.util.parse_formatted_hex(self.locator64,
|
||||
4, 4, ':'))
|
||||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
preference = parser.get_uint16()
|
||||
locator64 = parser.get_remaining()
|
||||
return cls(rdclass, rdtype, preference, locator64)
|
|
@ -18,6 +18,7 @@
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
|
||||
|
||||
|
@ -34,17 +35,13 @@ _MIN_LATITUDE = 0x80000000 - 90 * 3600000
|
|||
_MAX_LONGITUDE = 0x80000000 + 180 * 3600000
|
||||
_MIN_LONGITUDE = 0x80000000 - 180 * 3600000
|
||||
|
||||
# pylint complains about division since we don't have a from __future__ for
|
||||
# it, but we don't care about python 2 warnings, so turn them off.
|
||||
#
|
||||
# pylint: disable=old-division
|
||||
|
||||
def _exponent_of(what, desc):
|
||||
if what == 0:
|
||||
return 0
|
||||
exp = None
|
||||
for (i, pow) in enumerate(_pows):
|
||||
if what // pow == 0:
|
||||
if what < pow:
|
||||
exp = i - 1
|
||||
break
|
||||
if exp is None or exp < 0:
|
||||
|
@ -58,7 +55,7 @@ def _float_to_tuple(what):
|
|||
what *= -1
|
||||
else:
|
||||
sign = 1
|
||||
what = round(what * 3600000) # pylint: disable=round-builtin
|
||||
what = round(what * 3600000)
|
||||
degrees = int(what // 3600000)
|
||||
what -= degrees * 3600000
|
||||
minutes = int(what // 60000)
|
||||
|
@ -94,6 +91,20 @@ def _decode_size(what, desc):
|
|||
return base * pow(10, exponent)
|
||||
|
||||
|
||||
def _check_coordinate_list(value, low, high):
|
||||
if value[0] < low or value[0] > high:
|
||||
raise ValueError(f'not in range [{low}, {high}]')
|
||||
if value[1] < 0 or value[1] > 59:
|
||||
raise ValueError('bad minutes value')
|
||||
if value[2] < 0 or value[2] > 59:
|
||||
raise ValueError('bad seconds value')
|
||||
if value[3] < 0 or value[3] > 999:
|
||||
raise ValueError('bad milliseconds value')
|
||||
if value[4] != 1 and value[4] != -1:
|
||||
raise ValueError('bad hemisphere value')
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class LOC(dns.rdata.Rdata):
|
||||
|
||||
"""LOC record"""
|
||||
|
@ -119,16 +130,18 @@ class LOC(dns.rdata.Rdata):
|
|||
latitude = float(latitude)
|
||||
if isinstance(latitude, float):
|
||||
latitude = _float_to_tuple(latitude)
|
||||
object.__setattr__(self, 'latitude', dns.rdata._constify(latitude))
|
||||
_check_coordinate_list(latitude, -90, 90)
|
||||
self.latitude = tuple(latitude)
|
||||
if isinstance(longitude, int):
|
||||
longitude = float(longitude)
|
||||
if isinstance(longitude, float):
|
||||
longitude = _float_to_tuple(longitude)
|
||||
object.__setattr__(self, 'longitude', dns.rdata._constify(longitude))
|
||||
object.__setattr__(self, 'altitude', float(altitude))
|
||||
object.__setattr__(self, 'size', float(size))
|
||||
object.__setattr__(self, 'horizontal_precision', float(hprec))
|
||||
object.__setattr__(self, 'vertical_precision', float(vprec))
|
||||
_check_coordinate_list(longitude, -180, 180)
|
||||
self.longitude = tuple(longitude)
|
||||
self.altitude = float(altitude)
|
||||
self.size = float(size)
|
||||
self.horizontal_precision = float(hprec)
|
||||
self.vertical_precision = float(vprec)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
if self.latitude[4] > 0:
|
||||
|
@ -167,13 +180,9 @@ class LOC(dns.rdata.Rdata):
|
|||
vprec = _default_vprec
|
||||
|
||||
latitude[0] = tok.get_int()
|
||||
if latitude[0] > 90:
|
||||
raise dns.exception.SyntaxError('latitude >= 90')
|
||||
t = tok.get_string()
|
||||
if t.isdigit():
|
||||
latitude[1] = int(t)
|
||||
if latitude[1] >= 60:
|
||||
raise dns.exception.SyntaxError('latitude minutes >= 60')
|
||||
t = tok.get_string()
|
||||
if '.' in t:
|
||||
(seconds, milliseconds) = t.split('.')
|
||||
|
@ -181,8 +190,6 @@ class LOC(dns.rdata.Rdata):
|
|||
raise dns.exception.SyntaxError(
|
||||
'bad latitude seconds value')
|
||||
latitude[2] = int(seconds)
|
||||
if latitude[2] >= 60:
|
||||
raise dns.exception.SyntaxError('latitude seconds >= 60')
|
||||
l = len(milliseconds)
|
||||
if l == 0 or l > 3 or not milliseconds.isdigit():
|
||||
raise dns.exception.SyntaxError(
|
||||
|
@ -204,13 +211,9 @@ class LOC(dns.rdata.Rdata):
|
|||
raise dns.exception.SyntaxError('bad latitude hemisphere value')
|
||||
|
||||
longitude[0] = tok.get_int()
|
||||
if longitude[0] > 180:
|
||||
raise dns.exception.SyntaxError('longitude > 180')
|
||||
t = tok.get_string()
|
||||
if t.isdigit():
|
||||
longitude[1] = int(t)
|
||||
if longitude[1] >= 60:
|
||||
raise dns.exception.SyntaxError('longitude minutes >= 60')
|
||||
t = tok.get_string()
|
||||
if '.' in t:
|
||||
(seconds, milliseconds) = t.split('.')
|
||||
|
@ -218,8 +221,6 @@ class LOC(dns.rdata.Rdata):
|
|||
raise dns.exception.SyntaxError(
|
||||
'bad longitude seconds value')
|
||||
longitude[2] = int(seconds)
|
||||
if longitude[2] >= 60:
|
||||
raise dns.exception.SyntaxError('longitude seconds >= 60')
|
||||
l = len(milliseconds)
|
||||
if l == 0 or l > 3 or not milliseconds.isdigit():
|
||||
raise dns.exception.SyntaxError(
|
||||
|
@ -245,25 +246,22 @@ class LOC(dns.rdata.Rdata):
|
|||
t = t[0: -1]
|
||||
altitude = float(t) * 100.0 # m -> cm
|
||||
|
||||
token = tok.get().unescape()
|
||||
if not token.is_eol_or_eof():
|
||||
value = token.value
|
||||
tokens = tok.get_remaining(max_tokens=3)
|
||||
if len(tokens) >= 1:
|
||||
value = tokens[0].unescape().value
|
||||
if value[-1] == 'm':
|
||||
value = value[0: -1]
|
||||
size = float(value) * 100.0 # m -> cm
|
||||
token = tok.get().unescape()
|
||||
if not token.is_eol_or_eof():
|
||||
value = token.value
|
||||
if len(tokens) >= 2:
|
||||
value = tokens[1].unescape().value
|
||||
if value[-1] == 'm':
|
||||
value = value[0: -1]
|
||||
hprec = float(value) * 100.0 # m -> cm
|
||||
token = tok.get().unescape()
|
||||
if not token.is_eol_or_eof():
|
||||
value = token.value
|
||||
if len(tokens) >= 3:
|
||||
value = tokens[2].unescape().value
|
||||
if value[-1] == 'm':
|
||||
value = value[0: -1]
|
||||
vprec = float(value) * 100.0 # m -> cm
|
||||
tok.get_eol()
|
||||
|
||||
# Try encoding these now so we raise if they are bad
|
||||
_encode_size(size, "size")
|
||||
|
@ -296,6 +294,8 @@ class LOC(dns.rdata.Rdata):
|
|||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
(version, size, hprec, vprec, latitude, longitude, altitude) = \
|
||||
parser.get_struct("!BBBBIII")
|
||||
if version != 0:
|
||||
raise dns.exception.FormError("LOC version not zero")
|
||||
if latitude < _MIN_LATITUDE or latitude > _MAX_LATITUDE:
|
||||
raise dns.exception.FormError("bad latitude")
|
||||
if latitude > 0x80000000:
|
||||
|
|
41
lib/dns/rdtypes/ANY/LP.py
Normal file
41
lib/dns/rdtypes/ANY/LP.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
import struct
|
||||
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class LP(dns.rdata.Rdata):
|
||||
|
||||
"""LP record"""
|
||||
|
||||
# see: rfc6742.txt
|
||||
|
||||
__slots__ = ['preference', 'fqdn']
|
||||
|
||||
def __init__(self, rdclass, rdtype, preference, fqdn):
|
||||
super().__init__(rdclass, rdtype)
|
||||
self.preference = self._as_uint16(preference)
|
||||
self.fqdn = self._as_name(fqdn)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
fqdn = self.fqdn.choose_relativity(origin, relativize)
|
||||
return '%d %s' % (self.preference, fqdn)
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
preference = tok.get_uint16()
|
||||
fqdn = tok.get_name(origin, relativize, relativize_to)
|
||||
return cls(rdclass, rdtype, preference, fqdn)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
file.write(struct.pack('!H', self.preference))
|
||||
self.fqdn.to_wire(file, compress, origin, canonicalize)
|
||||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
preference = parser.get_uint16()
|
||||
fqdn = parser.get_name(origin)
|
||||
return cls(rdclass, rdtype, preference, fqdn)
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.mxbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class MX(dns.rdtypes.mxbase.MXBase):
|
||||
|
||||
"""MX record"""
|
||||
|
|
47
lib/dns/rdtypes/ANY/NID.py
Normal file
47
lib/dns/rdtypes/ANY/NID.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
import struct
|
||||
|
||||
import dns.immutable
|
||||
import dns.rdtypes.util
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class NID(dns.rdata.Rdata):
|
||||
|
||||
"""NID record"""
|
||||
|
||||
# see: rfc6742.txt
|
||||
|
||||
__slots__ = ['preference', 'nodeid']
|
||||
|
||||
def __init__(self, rdclass, rdtype, preference, nodeid):
|
||||
super().__init__(rdclass, rdtype)
|
||||
self.preference = self._as_uint16(preference)
|
||||
if isinstance(nodeid, bytes):
|
||||
if len(nodeid) != 8:
|
||||
raise ValueError('invalid nodeid')
|
||||
self.nodeid = dns.rdata._hexify(nodeid, 4, b':')
|
||||
else:
|
||||
dns.rdtypes.util.parse_formatted_hex(nodeid, 4, 4, ':')
|
||||
self.nodeid = nodeid
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return f'{self.preference} {self.nodeid}'
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
preference = tok.get_uint16()
|
||||
nodeid = tok.get_identifier()
|
||||
return cls(rdclass, rdtype, preference, nodeid)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
file.write(struct.pack('!H', self.preference))
|
||||
file.write(dns.rdtypes.util.parse_formatted_hex(self.nodeid, 4, 4, ':'))
|
||||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
preference = parser.get_uint16()
|
||||
nodeid = parser.get_remaining()
|
||||
return cls(rdclass, rdtype, preference, nodeid)
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.txtbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class NINFO(dns.rdtypes.txtbase.TXTBase):
|
||||
|
||||
"""NINFO record"""
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.nsbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class NS(dns.rdtypes.nsbase.NSBase):
|
||||
|
||||
"""NS record"""
|
||||
|
|
|
@ -16,16 +16,19 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.rdatatype
|
||||
import dns.name
|
||||
import dns.rdtypes.util
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class Bitmap(dns.rdtypes.util.Bitmap):
|
||||
type_name = 'NSEC'
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class NSEC(dns.rdata.Rdata):
|
||||
|
||||
"""NSEC record"""
|
||||
|
@ -34,8 +37,10 @@ class NSEC(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, next, windows):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'next', next)
|
||||
object.__setattr__(self, 'windows', dns.rdata._constify(windows))
|
||||
self.next = self._as_name(next)
|
||||
if not isinstance(windows, Bitmap):
|
||||
windows = Bitmap(windows)
|
||||
self.windows = tuple(windows.windows)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
next = self.next.choose_relativity(origin, relativize)
|
||||
|
@ -46,15 +51,17 @@ class NSEC(dns.rdata.Rdata):
|
|||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
next = tok.get_name(origin, relativize, relativize_to)
|
||||
windows = Bitmap().from_text(tok)
|
||||
windows = Bitmap.from_text(tok)
|
||||
return cls(rdclass, rdtype, next, windows)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
# Note that NSEC downcasing, originally mandated by RFC 4034
|
||||
# section 6.2 was removed by RFC 6840 section 5.1.
|
||||
self.next.to_wire(file, None, origin, False)
|
||||
Bitmap(self.windows).to_wire(file)
|
||||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
next = parser.get_name(origin)
|
||||
windows = Bitmap().from_wire_parser(parser)
|
||||
return cls(rdclass, rdtype, next, windows)
|
||||
bitmap = Bitmap.from_wire_parser(parser)
|
||||
return cls(rdclass, rdtype, next, bitmap)
|
||||
|
|
|
@ -20,6 +20,7 @@ import binascii
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.rdatatype
|
||||
import dns.rdtypes.util
|
||||
|
@ -37,10 +38,12 @@ SHA1 = 1
|
|||
OPTOUT = 1
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class Bitmap(dns.rdtypes.util.Bitmap):
|
||||
type_name = 'NSEC3'
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class NSEC3(dns.rdata.Rdata):
|
||||
|
||||
"""NSEC3 record"""
|
||||
|
@ -50,15 +53,14 @@ class NSEC3(dns.rdata.Rdata):
|
|||
def __init__(self, rdclass, rdtype, algorithm, flags, iterations, salt,
|
||||
next, windows):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'algorithm', algorithm)
|
||||
object.__setattr__(self, 'flags', flags)
|
||||
object.__setattr__(self, 'iterations', iterations)
|
||||
if isinstance(salt, str):
|
||||
object.__setattr__(self, 'salt', salt.encode())
|
||||
else:
|
||||
object.__setattr__(self, 'salt', salt)
|
||||
object.__setattr__(self, 'next', next)
|
||||
object.__setattr__(self, 'windows', dns.rdata._constify(windows))
|
||||
self.algorithm = self._as_uint8(algorithm)
|
||||
self.flags = self._as_uint8(flags)
|
||||
self.iterations = self._as_uint16(iterations)
|
||||
self.salt = self._as_bytes(salt, True, 255)
|
||||
self.next = self._as_bytes(next, True, 255)
|
||||
if not isinstance(windows, Bitmap):
|
||||
windows = Bitmap(windows)
|
||||
self.windows = tuple(windows.windows)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
next = base64.b32encode(self.next).translate(
|
||||
|
@ -85,9 +87,9 @@ class NSEC3(dns.rdata.Rdata):
|
|||
next = tok.get_string().encode(
|
||||
'ascii').upper().translate(b32_hex_to_normal)
|
||||
next = base64.b32decode(next)
|
||||
windows = Bitmap().from_text(tok)
|
||||
bitmap = Bitmap.from_text(tok)
|
||||
return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next,
|
||||
windows)
|
||||
bitmap)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
l = len(self.salt)
|
||||
|
@ -104,6 +106,6 @@ class NSEC3(dns.rdata.Rdata):
|
|||
(algorithm, flags, iterations) = parser.get_struct('!BBH')
|
||||
salt = parser.get_counted_bytes()
|
||||
next = parser.get_counted_bytes()
|
||||
windows = Bitmap().from_wire_parser(parser)
|
||||
bitmap = Bitmap.from_wire_parser(parser)
|
||||
return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next,
|
||||
windows)
|
||||
bitmap)
|
||||
|
|
|
@ -19,9 +19,11 @@ import struct
|
|||
import binascii
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class NSEC3PARAM(dns.rdata.Rdata):
|
||||
|
||||
"""NSEC3PARAM record"""
|
||||
|
@ -30,13 +32,10 @@ class NSEC3PARAM(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, algorithm, flags, iterations, salt):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'algorithm', algorithm)
|
||||
object.__setattr__(self, 'flags', flags)
|
||||
object.__setattr__(self, 'iterations', iterations)
|
||||
if isinstance(salt, str):
|
||||
object.__setattr__(self, 'salt', salt.encode())
|
||||
else:
|
||||
object.__setattr__(self, 'salt', salt)
|
||||
self.algorithm = self._as_uint8(algorithm)
|
||||
self.flags = self._as_uint8(flags)
|
||||
self.iterations = self._as_uint16(iterations)
|
||||
self.salt = self._as_bytes(salt, True, 255)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
if self.salt == b'':
|
||||
|
@ -57,7 +56,6 @@ class NSEC3PARAM(dns.rdata.Rdata):
|
|||
salt = ''
|
||||
else:
|
||||
salt = binascii.unhexlify(salt.encode())
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, algorithm, flags, iterations, salt)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
|
|
|
@ -18,9 +18,11 @@
|
|||
import base64
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
||||
@dns.immutable.immutable
|
||||
class OPENPGPKEY(dns.rdata.Rdata):
|
||||
|
||||
"""OPENPGPKEY record"""
|
||||
|
@ -29,10 +31,10 @@ class OPENPGPKEY(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, key):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'key', key)
|
||||
self.key = self._as_bytes(key)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return dns.rdata._base64ify(self.key)
|
||||
return dns.rdata._base64ify(self.key, chunksize=None, **kw)
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
|
|
|
@ -18,10 +18,15 @@
|
|||
import struct
|
||||
|
||||
import dns.edns
|
||||
import dns.immutable
|
||||
import dns.exception
|
||||
import dns.rdata
|
||||
|
||||
|
||||
# We don't implement from_text, and that's ok.
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
@dns.immutable.immutable
|
||||
class OPT(dns.rdata.Rdata):
|
||||
|
||||
"""OPT record"""
|
||||
|
@ -40,7 +45,11 @@ class OPT(dns.rdata.Rdata):
|
|||
"""
|
||||
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'options', dns.rdata._constify(options))
|
||||
def as_option(option):
|
||||
if not isinstance(option, dns.edns.Option):
|
||||
raise ValueError('option is not a dns.edns.option')
|
||||
return option
|
||||
self.options = self._as_tuple(options, as_option)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
for opt in self.options:
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.nsbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class PTR(dns.rdtypes.nsbase.NSBase):
|
||||
|
||||
"""PTR record"""
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.name
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class RP(dns.rdata.Rdata):
|
||||
|
||||
"""RP record"""
|
||||
|
@ -30,8 +32,8 @@ class RP(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, mbox, txt):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'mbox', mbox)
|
||||
object.__setattr__(self, 'txt', txt)
|
||||
self.mbox = self._as_name(mbox)
|
||||
self.txt = self._as_name(txt)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
mbox = self.mbox.choose_relativity(origin, relativize)
|
||||
|
@ -43,7 +45,6 @@ class RP(dns.rdata.Rdata):
|
|||
relativize_to=None):
|
||||
mbox = tok.get_name(origin, relativize, relativize_to)
|
||||
txt = tok.get_name(origin, relativize, relativize_to)
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, mbox, txt)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
|
|
|
@ -21,6 +21,7 @@ import struct
|
|||
import time
|
||||
|
||||
import dns.dnssec
|
||||
import dns.immutable
|
||||
import dns.exception
|
||||
import dns.rdata
|
||||
import dns.rdatatype
|
||||
|
@ -50,6 +51,7 @@ def posixtime_to_sigtime(what):
|
|||
return time.strftime('%Y%m%d%H%M%S', time.gmtime(what))
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class RRSIG(dns.rdata.Rdata):
|
||||
|
||||
"""RRSIG record"""
|
||||
|
@ -62,15 +64,15 @@ class RRSIG(dns.rdata.Rdata):
|
|||
original_ttl, expiration, inception, key_tag, signer,
|
||||
signature):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'type_covered', type_covered)
|
||||
object.__setattr__(self, 'algorithm', algorithm)
|
||||
object.__setattr__(self, 'labels', labels)
|
||||
object.__setattr__(self, 'original_ttl', original_ttl)
|
||||
object.__setattr__(self, 'expiration', expiration)
|
||||
object.__setattr__(self, 'inception', inception)
|
||||
object.__setattr__(self, 'key_tag', key_tag)
|
||||
object.__setattr__(self, 'signer', signer)
|
||||
object.__setattr__(self, 'signature', signature)
|
||||
self.type_covered = self._as_rdatatype(type_covered)
|
||||
self.algorithm = dns.dnssec.Algorithm.make(algorithm)
|
||||
self.labels = self._as_uint8(labels)
|
||||
self.original_ttl = self._as_ttl(original_ttl)
|
||||
self.expiration = self._as_uint32(expiration)
|
||||
self.inception = self._as_uint32(inception)
|
||||
self.key_tag = self._as_uint16(key_tag)
|
||||
self.signer = self._as_name(signer)
|
||||
self.signature = self._as_bytes(signature)
|
||||
|
||||
def covers(self):
|
||||
return self.type_covered
|
||||
|
@ -85,7 +87,7 @@ class RRSIG(dns.rdata.Rdata):
|
|||
posixtime_to_sigtime(self.inception),
|
||||
self.key_tag,
|
||||
self.signer.choose_relativity(origin, relativize),
|
||||
dns.rdata._base64ify(self.signature)
|
||||
dns.rdata._base64ify(self.signature, **kw)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.mxbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class RT(dns.rdtypes.mxbase.UncompressedDowncasingMX):
|
||||
|
||||
"""RT record"""
|
||||
|
|
9
lib/dns/rdtypes/ANY/SMIMEA.py
Normal file
9
lib/dns/rdtypes/ANY/SMIMEA.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
import dns.immutable
|
||||
import dns.rdtypes.tlsabase
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class SMIMEA(dns.rdtypes.tlsabase.TLSABase):
|
||||
"""SMIMEA record"""
|
|
@ -18,10 +18,12 @@
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.name
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class SOA(dns.rdata.Rdata):
|
||||
|
||||
"""SOA record"""
|
||||
|
@ -34,13 +36,13 @@ class SOA(dns.rdata.Rdata):
|
|||
def __init__(self, rdclass, rdtype, mname, rname, serial, refresh, retry,
|
||||
expire, minimum):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'mname', mname)
|
||||
object.__setattr__(self, 'rname', rname)
|
||||
object.__setattr__(self, 'serial', serial)
|
||||
object.__setattr__(self, 'refresh', refresh)
|
||||
object.__setattr__(self, 'retry', retry)
|
||||
object.__setattr__(self, 'expire', expire)
|
||||
object.__setattr__(self, 'minimum', minimum)
|
||||
self.mname = self._as_name(mname)
|
||||
self.rname = self._as_name(rname)
|
||||
self.serial = self._as_uint32(serial)
|
||||
self.refresh = self._as_ttl(refresh)
|
||||
self.retry = self._as_ttl(retry)
|
||||
self.expire = self._as_ttl(expire)
|
||||
self.minimum = self._as_ttl(minimum)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
mname = self.mname.choose_relativity(origin, relativize)
|
||||
|
@ -59,7 +61,6 @@ class SOA(dns.rdata.Rdata):
|
|||
retry = tok.get_ttl()
|
||||
expire = tok.get_ttl()
|
||||
minimum = tok.get_ttl()
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, mname, rname, serial, refresh, retry,
|
||||
expire, minimum)
|
||||
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.txtbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class SPF(dns.rdtypes.txtbase.TXTBase):
|
||||
|
||||
"""SPF record"""
|
||||
|
|
|
@ -19,9 +19,11 @@ import struct
|
|||
import binascii
|
||||
|
||||
import dns.rdata
|
||||
import dns.immutable
|
||||
import dns.rdatatype
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class SSHFP(dns.rdata.Rdata):
|
||||
|
||||
"""SSHFP record"""
|
||||
|
@ -33,15 +35,18 @@ class SSHFP(dns.rdata.Rdata):
|
|||
def __init__(self, rdclass, rdtype, algorithm, fp_type,
|
||||
fingerprint):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'algorithm', algorithm)
|
||||
object.__setattr__(self, 'fp_type', fp_type)
|
||||
object.__setattr__(self, 'fingerprint', fingerprint)
|
||||
self.algorithm = self._as_uint8(algorithm)
|
||||
self.fp_type = self._as_uint8(fp_type)
|
||||
self.fingerprint = self._as_bytes(fingerprint, True)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
kw = kw.copy()
|
||||
chunksize = kw.pop('chunksize', 128)
|
||||
return '%d %d %s' % (self.algorithm,
|
||||
self.fp_type,
|
||||
dns.rdata._hexify(self.fingerprint,
|
||||
chunksize=128))
|
||||
chunksize=chunksize,
|
||||
**kw))
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
|
|
118
lib/dns/rdtypes/ANY/TKEY.py
Normal file
118
lib/dns/rdtypes/ANY/TKEY.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2004-2007, 2009-2011 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.
|
||||
|
||||
import base64
|
||||
import struct
|
||||
|
||||
import dns.dnssec
|
||||
import dns.immutable
|
||||
import dns.exception
|
||||
import dns.rdata
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class TKEY(dns.rdata.Rdata):
|
||||
|
||||
"""TKEY Record"""
|
||||
|
||||
__slots__ = ['algorithm', 'inception', 'expiration', 'mode', 'error',
|
||||
'key', 'other']
|
||||
|
||||
def __init__(self, rdclass, rdtype, algorithm, inception, expiration,
|
||||
mode, error, key, other=b''):
|
||||
super().__init__(rdclass, rdtype)
|
||||
self.algorithm = self._as_name(algorithm)
|
||||
self.inception = self._as_uint32(inception)
|
||||
self.expiration = self._as_uint32(expiration)
|
||||
self.mode = self._as_uint16(mode)
|
||||
self.error = self._as_uint16(error)
|
||||
self.key = self._as_bytes(key)
|
||||
self.other = self._as_bytes(other)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
_algorithm = self.algorithm.choose_relativity(origin, relativize)
|
||||
text = '%s %u %u %u %u %s' % (str(_algorithm), self.inception,
|
||||
self.expiration, self.mode, self.error,
|
||||
dns.rdata._base64ify(self.key, 0))
|
||||
if len(self.other) > 0:
|
||||
text += ' %s' % (dns.rdata._base64ify(self.other, 0))
|
||||
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
algorithm = tok.get_name(relativize=False)
|
||||
inception = tok.get_uint32()
|
||||
expiration = tok.get_uint32()
|
||||
mode = tok.get_uint16()
|
||||
error = tok.get_uint16()
|
||||
key_b64 = tok.get_string().encode()
|
||||
key = base64.b64decode(key_b64)
|
||||
other_b64 = tok.concatenate_remaining_identifiers().encode()
|
||||
other = base64.b64decode(other_b64)
|
||||
|
||||
return cls(rdclass, rdtype, algorithm, inception, expiration, mode,
|
||||
error, key, other)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
self.algorithm.to_wire(file, compress, origin)
|
||||
file.write(struct.pack("!IIHH", self.inception, self.expiration,
|
||||
self.mode, self.error))
|
||||
file.write(struct.pack("!H", len(self.key)))
|
||||
file.write(self.key)
|
||||
file.write(struct.pack("!H", len(self.other)))
|
||||
if len(self.other) > 0:
|
||||
file.write(self.other)
|
||||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
algorithm = parser.get_name(origin)
|
||||
inception, expiration, mode, error = parser.get_struct("!IIHH")
|
||||
key = parser.get_counted_bytes(2)
|
||||
other = parser.get_counted_bytes(2)
|
||||
|
||||
return cls(rdclass, rdtype, algorithm, inception, expiration, mode,
|
||||
error, key, other)
|
||||
|
||||
# Constants for the mode field - from RFC 2930:
|
||||
# 2.5 The Mode Field
|
||||
#
|
||||
# The mode field specifies the general scheme for key agreement or
|
||||
# the purpose of the TKEY DNS message. Servers and resolvers
|
||||
# supporting this specification MUST implement the Diffie-Hellman key
|
||||
# agreement mode and the key deletion mode for queries. All other
|
||||
# modes are OPTIONAL. A server supporting TKEY that receives a TKEY
|
||||
# request with a mode it does not support returns the BADMODE error.
|
||||
# The following values of the Mode octet are defined, available, or
|
||||
# reserved:
|
||||
#
|
||||
# Value Description
|
||||
# ----- -----------
|
||||
# 0 - reserved, see section 7
|
||||
# 1 server assignment
|
||||
# 2 Diffie-Hellman exchange
|
||||
# 3 GSS-API negotiation
|
||||
# 4 resolver assignment
|
||||
# 5 key deletion
|
||||
# 6-65534 - available, see section 7
|
||||
# 65535 - reserved, see section 7
|
||||
SERVER_ASSIGNMENT = 1
|
||||
DIFFIE_HELLMAN_EXCHANGE = 2
|
||||
GSSAPI_NEGOTIATION = 3
|
||||
RESOLVER_ASSIGNMENT = 4
|
||||
KEY_DELETION = 5
|
|
@ -1,67 +1,10 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
# Copyright (C) 2005-2007, 2009-2011 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.
|
||||
|
||||
import struct
|
||||
import binascii
|
||||
|
||||
import dns.rdata
|
||||
import dns.rdatatype
|
||||
import dns.immutable
|
||||
import dns.rdtypes.tlsabase
|
||||
|
||||
|
||||
class TLSA(dns.rdata.Rdata):
|
||||
@dns.immutable.immutable
|
||||
class TLSA(dns.rdtypes.tlsabase.TLSABase):
|
||||
|
||||
"""TLSA record"""
|
||||
|
||||
# see: RFC 6698
|
||||
|
||||
__slots__ = ['usage', 'selector', 'mtype', 'cert']
|
||||
|
||||
def __init__(self, rdclass, rdtype, usage, selector,
|
||||
mtype, cert):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'usage', usage)
|
||||
object.__setattr__(self, 'selector', selector)
|
||||
object.__setattr__(self, 'mtype', mtype)
|
||||
object.__setattr__(self, 'cert', cert)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return '%d %d %d %s' % (self.usage,
|
||||
self.selector,
|
||||
self.mtype,
|
||||
dns.rdata._hexify(self.cert,
|
||||
chunksize=128))
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
usage = tok.get_uint8()
|
||||
selector = tok.get_uint8()
|
||||
mtype = tok.get_uint8()
|
||||
cert = tok.concatenate_remaining_identifiers().encode()
|
||||
cert = binascii.unhexlify(cert)
|
||||
return cls(rdclass, rdtype, usage, selector, mtype, cert)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
header = struct.pack("!BBB", self.usage, self.selector, self.mtype)
|
||||
file.write(header)
|
||||
file.write(self.cert)
|
||||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
header = parser.get_struct("BBB")
|
||||
cert = parser.get_remaining()
|
||||
return cls(rdclass, rdtype, header[0], header[1], header[2], cert)
|
||||
|
|
|
@ -15,12 +15,16 @@
|
|||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import base64
|
||||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rcode
|
||||
import dns.rdata
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class TSIG(dns.rdata.Rdata):
|
||||
|
||||
"""TSIG record"""
|
||||
|
@ -52,20 +56,45 @@ class TSIG(dns.rdata.Rdata):
|
|||
"""
|
||||
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'algorithm', algorithm)
|
||||
object.__setattr__(self, 'time_signed', time_signed)
|
||||
object.__setattr__(self, 'fudge', fudge)
|
||||
object.__setattr__(self, 'mac', dns.rdata._constify(mac))
|
||||
object.__setattr__(self, 'original_id', original_id)
|
||||
object.__setattr__(self, 'error', error)
|
||||
object.__setattr__(self, 'other', dns.rdata._constify(other))
|
||||
self.algorithm = self._as_name(algorithm)
|
||||
self.time_signed = self._as_uint48(time_signed)
|
||||
self.fudge = self._as_uint16(fudge)
|
||||
self.mac = self._as_bytes(mac)
|
||||
self.original_id = self._as_uint16(original_id)
|
||||
self.error = dns.rcode.Rcode.make(error)
|
||||
self.other = self._as_bytes(other)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
algorithm = self.algorithm.choose_relativity(origin, relativize)
|
||||
return f"{algorithm} {self.fudge} {self.time_signed} " + \
|
||||
error = dns.rcode.to_text(self.error, True)
|
||||
text = f"{algorithm} {self.time_signed} {self.fudge} " + \
|
||||
f"{len(self.mac)} {dns.rdata._base64ify(self.mac, 0)} " + \
|
||||
f"{self.original_id} {self.error} " + \
|
||||
f"{len(self.other)} {dns.rdata._base64ify(self.other, 0)}"
|
||||
f"{self.original_id} {error} {len(self.other)}"
|
||||
if self.other:
|
||||
text += f" {dns.rdata._base64ify(self.other, 0)}"
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
algorithm = tok.get_name(relativize=False)
|
||||
time_signed = tok.get_uint48()
|
||||
fudge = tok.get_uint16()
|
||||
mac_len = tok.get_uint16()
|
||||
mac = base64.b64decode(tok.get_string())
|
||||
if len(mac) != mac_len:
|
||||
raise SyntaxError('invalid MAC')
|
||||
original_id = tok.get_uint16()
|
||||
error = dns.rcode.from_text(tok.get_string())
|
||||
other_len = tok.get_uint16()
|
||||
if other_len > 0:
|
||||
other = base64.b64decode(tok.get_string())
|
||||
if len(other) != other_len:
|
||||
raise SyntaxError('invalid other data')
|
||||
else:
|
||||
other = b''
|
||||
return cls(rdclass, rdtype, algorithm, time_signed, fudge, mac,
|
||||
original_id, error, other)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
self.algorithm.to_wire(file, None, origin, False)
|
||||
|
@ -81,9 +110,9 @@ class TSIG(dns.rdata.Rdata):
|
|||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
algorithm = parser.get_name(origin)
|
||||
(time_hi, time_lo, fudge) = parser.get_struct('!HIH')
|
||||
time_signed = (time_hi << 32) + time_lo
|
||||
algorithm = parser.get_name()
|
||||
time_signed = parser.get_uint48()
|
||||
fudge = parser.get_uint16()
|
||||
mac = parser.get_counted_bytes(2)
|
||||
(original_id, error) = parser.get_struct('!HH')
|
||||
other = parser.get_counted_bytes(2)
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.txtbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class TXT(dns.rdtypes.txtbase.TXTBase):
|
||||
|
||||
"""TXT record"""
|
||||
|
|
|
@ -19,10 +19,13 @@
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.rdtypes.util
|
||||
import dns.name
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class URI(dns.rdata.Rdata):
|
||||
|
||||
"""URI record"""
|
||||
|
@ -33,14 +36,11 @@ class URI(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, priority, weight, target):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'priority', priority)
|
||||
object.__setattr__(self, 'weight', weight)
|
||||
if len(target) < 1:
|
||||
self.priority = self._as_uint16(priority)
|
||||
self.weight = self._as_uint16(weight)
|
||||
self.target = self._as_bytes(target, True)
|
||||
if len(self.target) == 0:
|
||||
raise dns.exception.SyntaxError("URI target cannot be empty")
|
||||
if isinstance(target, str):
|
||||
object.__setattr__(self, 'target', target.encode())
|
||||
else:
|
||||
object.__setattr__(self, 'target', target)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return '%d %d "%s"' % (self.priority, self.weight,
|
||||
|
@ -54,7 +54,6 @@ class URI(dns.rdata.Rdata):
|
|||
target = tok.get().unescape()
|
||||
if not (target.is_quoted_string() or target.is_identifier()):
|
||||
raise dns.exception.SyntaxError("URI target must be a string")
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, priority, weight, target.value)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
|
@ -69,3 +68,13 @@ class URI(dns.rdata.Rdata):
|
|||
if len(target) == 0:
|
||||
raise dns.exception.FormError('URI target may not be empty')
|
||||
return cls(rdclass, rdtype, priority, weight, target)
|
||||
|
||||
def _processing_priority(self):
|
||||
return self.priority
|
||||
|
||||
def _processing_weight(self):
|
||||
return self.weight
|
||||
|
||||
@classmethod
|
||||
def _processing_order(cls, iterable):
|
||||
return dns.rdtypes.util.weighted_processing_order(iterable)
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class X25(dns.rdata.Rdata):
|
||||
|
||||
"""X25 record"""
|
||||
|
@ -32,10 +34,7 @@ class X25(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, address):
|
||||
super().__init__(rdclass, rdtype)
|
||||
if isinstance(address, str):
|
||||
object.__setattr__(self, 'address', address.encode())
|
||||
else:
|
||||
object.__setattr__(self, 'address', address)
|
||||
self.address = self._as_bytes(address, True, 255)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return '"%s"' % dns.rdata._escapify(self.address)
|
||||
|
@ -44,7 +43,6 @@ class X25(dns.rdata.Rdata):
|
|||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
address = tok.get_string()
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, address)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
|
|
65
lib/dns/rdtypes/ANY/ZONEMD.py
Normal file
65
lib/dns/rdtypes/ANY/ZONEMD.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
import struct
|
||||
import binascii
|
||||
|
||||
import dns.immutable
|
||||
import dns.rdata
|
||||
import dns.rdatatype
|
||||
import dns.zone
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class ZONEMD(dns.rdata.Rdata):
|
||||
|
||||
"""ZONEMD record"""
|
||||
|
||||
# See RFC 8976
|
||||
|
||||
__slots__ = ['serial', 'scheme', 'hash_algorithm', 'digest']
|
||||
|
||||
def __init__(self, rdclass, rdtype, serial, scheme, hash_algorithm, digest):
|
||||
super().__init__(rdclass, rdtype)
|
||||
self.serial = self._as_uint32(serial)
|
||||
self.scheme = dns.zone.DigestScheme.make(scheme)
|
||||
self.hash_algorithm = dns.zone.DigestHashAlgorithm.make(hash_algorithm)
|
||||
self.digest = self._as_bytes(digest)
|
||||
|
||||
if self.scheme == 0: # reserved, RFC 8976 Sec. 5.2
|
||||
raise ValueError('scheme 0 is reserved')
|
||||
if self.hash_algorithm == 0: # reserved, RFC 8976 Sec. 5.3
|
||||
raise ValueError('hash_algorithm 0 is reserved')
|
||||
|
||||
hasher = dns.zone._digest_hashers.get(self.hash_algorithm)
|
||||
if hasher and hasher().digest_size != len(self.digest):
|
||||
raise ValueError('digest length inconsistent with hash algorithm')
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
kw = kw.copy()
|
||||
chunksize = kw.pop('chunksize', 128)
|
||||
return '%d %d %d %s' % (self.serial, self.scheme, self.hash_algorithm,
|
||||
dns.rdata._hexify(self.digest,
|
||||
chunksize=chunksize,
|
||||
**kw))
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
serial = tok.get_uint32()
|
||||
scheme = tok.get_uint8()
|
||||
hash_algorithm = tok.get_uint8()
|
||||
digest = tok.concatenate_remaining_identifiers().encode()
|
||||
digest = binascii.unhexlify(digest)
|
||||
return cls(rdclass, rdtype, serial, scheme, hash_algorithm, digest)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
header = struct.pack("!IBB", self.serial, self.scheme,
|
||||
self.hash_algorithm)
|
||||
file.write(header)
|
||||
file.write(self.digest)
|
||||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
header = parser.get_struct("!IBB")
|
||||
digest = parser.get_remaining()
|
||||
return cls(rdclass, rdtype, header[0], header[1], header[2], digest)
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
__all__ = [
|
||||
'AFSDB',
|
||||
'AMTRELAY',
|
||||
'AVC',
|
||||
'CAA',
|
||||
'CDNSKEY',
|
||||
|
@ -38,6 +39,7 @@ __all__ = [
|
|||
'ISDN',
|
||||
'LOC',
|
||||
'MX',
|
||||
'NINFO',
|
||||
'NS',
|
||||
'NSEC',
|
||||
'NSEC3',
|
||||
|
@ -48,12 +50,15 @@ __all__ = [
|
|||
'RP',
|
||||
'RRSIG',
|
||||
'RT',
|
||||
'SMIMEA',
|
||||
'SOA',
|
||||
'SPF',
|
||||
'SSHFP',
|
||||
'TKEY',
|
||||
'TLSA',
|
||||
'TSIG',
|
||||
'TXT',
|
||||
'URI',
|
||||
'X25',
|
||||
'ZONEMD',
|
||||
]
|
||||
|
|
|
@ -15,9 +15,12 @@
|
|||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.mxbase
|
||||
import struct
|
||||
|
||||
import dns.rdtypes.mxbase
|
||||
import dns.immutable
|
||||
|
||||
@dns.immutable.immutable
|
||||
class A(dns.rdata.Rdata):
|
||||
|
||||
"""A record for Chaosnet"""
|
||||
|
@ -29,8 +32,8 @@ class A(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, domain, address):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'domain', domain)
|
||||
object.__setattr__(self, 'address', address)
|
||||
self.domain = self._as_name(domain)
|
||||
self.address = self._as_uint16(address)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
domain = self.domain.choose_relativity(origin, relativize)
|
||||
|
@ -41,7 +44,6 @@ class A(dns.rdata.Rdata):
|
|||
relativize_to=None):
|
||||
domain = tok.get_name(origin, relativize, relativize_to)
|
||||
address = tok.get_uint16(base=8)
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, domain, address)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.ipv4
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class A(dns.rdata.Rdata):
|
||||
|
||||
"""A record."""
|
||||
|
@ -29,9 +31,7 @@ class A(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, address):
|
||||
super().__init__(rdclass, rdtype)
|
||||
# check that it's OK
|
||||
dns.ipv4.inet_aton(address)
|
||||
object.__setattr__(self, 'address', address)
|
||||
self.address = self._as_ipv4_address(address)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return self.address
|
||||
|
@ -40,7 +40,6 @@ class A(dns.rdata.Rdata):
|
|||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
address = tok.get_identifier()
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, address)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
|
@ -48,5 +47,5 @@ class A(dns.rdata.Rdata):
|
|||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
address = dns.ipv4.inet_ntoa(parser.get_remaining())
|
||||
address = parser.get_remaining()
|
||||
return cls(rdclass, rdtype, address)
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.ipv6
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class AAAA(dns.rdata.Rdata):
|
||||
|
||||
"""AAAA record."""
|
||||
|
@ -29,9 +31,7 @@ class AAAA(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, address):
|
||||
super().__init__(rdclass, rdtype)
|
||||
# check that it's OK
|
||||
dns.ipv6.inet_aton(address)
|
||||
object.__setattr__(self, 'address', address)
|
||||
self.address = self._as_ipv6_address(address)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return self.address
|
||||
|
@ -40,7 +40,6 @@ class AAAA(dns.rdata.Rdata):
|
|||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
address = tok.get_identifier()
|
||||
tok.get_eol()
|
||||
return cls(rdclass, rdtype, address)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
|
@ -48,5 +47,5 @@ class AAAA(dns.rdata.Rdata):
|
|||
|
||||
@classmethod
|
||||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
address = dns.ipv6.inet_ntoa(parser.get_remaining())
|
||||
address = parser.get_remaining()
|
||||
return cls(rdclass, rdtype, address)
|
||||
|
|
|
@ -20,11 +20,13 @@ import codecs
|
|||
import struct
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.ipv4
|
||||
import dns.ipv6
|
||||
import dns.rdata
|
||||
import dns.tokenizer
|
||||
|
||||
@dns.immutable.immutable
|
||||
class APLItem:
|
||||
|
||||
"""An APL list item."""
|
||||
|
@ -32,10 +34,17 @@ class APLItem:
|
|||
__slots__ = ['family', 'negation', 'address', 'prefix']
|
||||
|
||||
def __init__(self, family, negation, address, prefix):
|
||||
self.family = family
|
||||
self.negation = negation
|
||||
self.address = address
|
||||
self.prefix = prefix
|
||||
self.family = dns.rdata.Rdata._as_uint16(family)
|
||||
self.negation = dns.rdata.Rdata._as_bool(negation)
|
||||
if self.family == 1:
|
||||
self.address = dns.rdata.Rdata._as_ipv4_address(address)
|
||||
self.prefix = dns.rdata.Rdata._as_int(prefix, 0, 32)
|
||||
elif self.family == 2:
|
||||
self.address = dns.rdata.Rdata._as_ipv6_address(address)
|
||||
self.prefix = dns.rdata.Rdata._as_int(prefix, 0, 128)
|
||||
else:
|
||||
self.address = dns.rdata.Rdata._as_bytes(address, max_length=127)
|
||||
self.prefix = dns.rdata.Rdata._as_uint8(prefix)
|
||||
|
||||
def __str__(self):
|
||||
if self.negation:
|
||||
|
@ -68,6 +77,7 @@ class APLItem:
|
|||
file.write(address)
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class APL(dns.rdata.Rdata):
|
||||
|
||||
"""APL record."""
|
||||
|
@ -78,7 +88,10 @@ class APL(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, items):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'items', dns.rdata._constify(items))
|
||||
for item in items:
|
||||
if not isinstance(item, APLItem):
|
||||
raise ValueError('item not an APLItem')
|
||||
self.items = tuple(items)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return ' '.join(map(str, self.items))
|
||||
|
@ -87,11 +100,8 @@ class APL(dns.rdata.Rdata):
|
|||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
relativize_to=None):
|
||||
items = []
|
||||
while True:
|
||||
token = tok.get().unescape()
|
||||
if token.is_eol_or_eof():
|
||||
break
|
||||
item = token.value
|
||||
for token in tok.get_remaining():
|
||||
item = token.unescape().value
|
||||
if item[0] == '!':
|
||||
negation = True
|
||||
item = item[1:]
|
||||
|
@ -127,11 +137,9 @@ class APL(dns.rdata.Rdata):
|
|||
if header[0] == 1:
|
||||
if l < 4:
|
||||
address += b'\x00' * (4 - l)
|
||||
address = dns.ipv4.inet_ntoa(address)
|
||||
elif header[0] == 2:
|
||||
if l < 16:
|
||||
address += b'\x00' * (16 - l)
|
||||
address = dns.ipv6.inet_ntoa(address)
|
||||
else:
|
||||
#
|
||||
# This isn't really right according to the RFC, but it
|
||||
|
|
|
@ -18,8 +18,10 @@
|
|||
import base64
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class DHCID(dns.rdata.Rdata):
|
||||
|
||||
"""DHCID record"""
|
||||
|
@ -30,10 +32,10 @@ class DHCID(dns.rdata.Rdata):
|
|||
|
||||
def __init__(self, rdclass, rdtype, data):
|
||||
super().__init__(rdclass, rdtype)
|
||||
object.__setattr__(self, 'data', data)
|
||||
self.data = self._as_bytes(data)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
return dns.rdata._base64ify(self.data)
|
||||
return dns.rdata._base64ify(self.data, **kw)
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
|
|
8
lib/dns/rdtypes/IN/HTTPS.py
Normal file
8
lib/dns/rdtypes/IN/HTTPS.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
|
||||
import dns.rdtypes.svcbbase
|
||||
import dns.immutable
|
||||
|
||||
@dns.immutable.immutable
|
||||
class HTTPS(dns.rdtypes.svcbbase.SVCBBase):
|
||||
"""HTTPS record"""
|
|
@ -19,12 +19,14 @@ import struct
|
|||
import base64
|
||||
|
||||
import dns.exception
|
||||
import dns.immutable
|
||||
import dns.rdtypes.util
|
||||
|
||||
|
||||
class Gateway(dns.rdtypes.util.Gateway):
|
||||
name = 'IPSECKEY gateway'
|
||||
|
||||
@dns.immutable.immutable
|
||||
class IPSECKEY(dns.rdata.Rdata):
|
||||
|
||||
"""IPSECKEY record"""
|
||||
|
@ -36,19 +38,19 @@ class IPSECKEY(dns.rdata.Rdata):
|
|||
def __init__(self, rdclass, rdtype, precedence, gateway_type, algorithm,
|
||||
gateway, key):
|
||||
super().__init__(rdclass, rdtype)
|
||||
Gateway(gateway_type, gateway).check()
|
||||
object.__setattr__(self, 'precedence', precedence)
|
||||
object.__setattr__(self, 'gateway_type', gateway_type)
|
||||
object.__setattr__(self, 'algorithm', algorithm)
|
||||
object.__setattr__(self, 'gateway', gateway)
|
||||
object.__setattr__(self, 'key', key)
|
||||
gateway = Gateway(gateway_type, gateway)
|
||||
self.precedence = self._as_uint8(precedence)
|
||||
self.gateway_type = gateway.type
|
||||
self.algorithm = self._as_uint8(algorithm)
|
||||
self.gateway = gateway.gateway
|
||||
self.key = self._as_bytes(key)
|
||||
|
||||
def to_text(self, origin=None, relativize=True, **kw):
|
||||
gateway = Gateway(self.gateway_type, self.gateway).to_text(origin,
|
||||
relativize)
|
||||
return '%d %d %d %s %s' % (self.precedence, self.gateway_type,
|
||||
self.algorithm, gateway,
|
||||
dns.rdata._base64ify(self.key))
|
||||
dns.rdata._base64ify(self.key, **kw))
|
||||
|
||||
@classmethod
|
||||
def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
|
||||
|
@ -56,12 +58,12 @@ class IPSECKEY(dns.rdata.Rdata):
|
|||
precedence = tok.get_uint8()
|
||||
gateway_type = tok.get_uint8()
|
||||
algorithm = tok.get_uint8()
|
||||
gateway = Gateway(gateway_type).from_text(tok, origin, relativize,
|
||||
relativize_to)
|
||||
gateway = Gateway.from_text(gateway_type, tok, origin, relativize,
|
||||
relativize_to)
|
||||
b64 = tok.concatenate_remaining_identifiers().encode()
|
||||
key = base64.b64decode(b64)
|
||||
return cls(rdclass, rdtype, precedence, gateway_type, algorithm,
|
||||
gateway, key)
|
||||
gateway.gateway, key)
|
||||
|
||||
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
|
||||
header = struct.pack("!BBB", self.precedence, self.gateway_type,
|
||||
|
@ -75,7 +77,7 @@ class IPSECKEY(dns.rdata.Rdata):
|
|||
def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
|
||||
header = parser.get_struct('!BBB')
|
||||
gateway_type = header[1]
|
||||
gateway = Gateway(gateway_type).from_wire_parser(parser, origin)
|
||||
gateway = Gateway.from_wire_parser(gateway_type, parser, origin)
|
||||
key = parser.get_remaining()
|
||||
return cls(rdclass, rdtype, header[0], gateway_type, header[2],
|
||||
gateway, key)
|
||||
gateway.gateway, key)
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import dns.rdtypes.mxbase
|
||||
import dns.immutable
|
||||
|
||||
|
||||
@dns.immutable.immutable
|
||||
class KX(dns.rdtypes.mxbase.UncompressedDowncasingMX):
|
||||
|
||||
"""KX record"""
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue