Codebase list dnspython / ed72b5c
Import upstream version 2.1.0rc1+git20210523.1.cf73187 Debian Janitor 2 years ago
203 changed file(s) with 11982 addition(s) and 2317 deletion(s). Raw diff Collapse all Expand all
55 E129,
66 # Whitespace round parameter '=' can be excessive
77 E252,
8 # Multiple # in a comment is OK
9 E266,
810 # Not excited by the "two blank lines" rule
911 E302,
1012 E305,
1515 htmlcov
1616 coverage.xml
1717 .dir-locals.el
18 .vscode/
22 - "3.6"
33 - "3.7"
44 - "3.8"
5 - "3.9"
56 branches:
67 except:
78 - python3
1011 - ~/.poetry/bin/poetry install -E dnssec -E doh -E idna -E trio -E curio
1112 script:
1213 - ~/.poetry/bin/poetry run pytest --cov=. --cov-report=xml:coverage.xml
13 after_success:
14 - bash <(curl -s https://codecov.io/bash)
00 include LICENSE ChangeLog README.md
11 recursive-include examples *.txt *.py
2 recursive-include tests *.txt *.py Makefile *.good example query
2 recursive-include tests *.txt *.py Makefile *.good example query *.pickle *.text *.generic
5757 potype:
5858 poetry run python -m mypy examples tests dns/*.py
5959
60 polint:
61 poetry run pylint dns
62
6063 poflake:
6164 poetry run flake8 dns
6265
6366 pocov:
64 poetry run coverage run -m pytest
67 poetry run coverage run --branch -m pytest
6568 poetry run coverage html --include 'dns*'
6669 poetry run coverage report --include 'dns*'
6770
0 Metadata-Version: 2.1
1 Name: dnspython
2 Version: 1.15.1.dev1222+gcf73187
3 Summary: DNS toolkit
4 Home-page: http://www.dnspython.org
5 Author: Bob Halley
6 Author-email: halley@dnspython.org
7 License: ISC
8 Description: dnspython is a DNS toolkit for Python. It supports almost all
9 record types. It can be used for queries, zone transfers, and dynamic
10 updates. It supports TSIG authenticated messages and EDNS0.
11
12 dnspython provides both high and low level access to DNS. The high
13 level classes perform queries for data of a given name, type, and
14 class, and return an answer set. The low level classes allow
15 direct manipulation of DNS zones, messages, names, and records.
16 Platform: UNKNOWN
17 Classifier: Development Status :: 5 - Production/Stable
18 Classifier: Intended Audience :: Developers
19 Classifier: Intended Audience :: System Administrators
20 Classifier: License :: OSI Approved :: ISC License (ISCL)
21 Classifier: Operating System :: POSIX
22 Classifier: Operating System :: Microsoft :: Windows
23 Classifier: Programming Language :: Python
24 Classifier: Topic :: Internet :: Name Service (DNS)
25 Classifier: Topic :: Software Development :: Libraries :: Python Modules
26 Classifier: Programming Language :: Python :: 3
27 Classifier: Programming Language :: Python :: 3.6
28 Classifier: Programming Language :: Python :: 3.7
29 Classifier: Programming Language :: Python :: 3.8
30 Classifier: Programming Language :: Python :: 3.9
31 Provides: dns
32 Requires-Python: >=3.6
33 Description-Content-Type: text/plain
34 Provides-Extra: curio
35 Provides-Extra: dnssec
36 Provides-Extra: doh
37 Provides-Extra: idna
38 Provides-Extra: trio
22 [![Build Status](https://travis-ci.org/rthalley/dnspython.svg?branch=master)](https://travis-ci.org/rthalley/dnspython)
33 [![Documentation Status](https://readthedocs.org/projects/dnspython/badge/?version=latest)](https://dnspython.readthedocs.io/en/latest/?badge=latest)
44 [![PyPI version](https://badge.fury.io/py/dnspython.svg)](https://badge.fury.io/py/dnspython)
5 [![PyPI Statistics](https://img.shields.io/pypi/dm/dnspython.svg)](https://pypistats.org/packages/dnspython)
6 [![Build Status](https://dev.azure.com/halley0415/halley/_apis/build/status/rthalley.dnspython?branchName=master)](https://dev.azure.com/halley0415/halley/_build/latest?definitionId=1&branchName=master)
7 [![Coverage](https://codecov.io/github/rthalley/dnspython/coverage.svg?branch=master)](https://codecov.io/gh/rthalley/dnspython)
85 [![License: ISC](https://img.shields.io/badge/License-ISC-brightgreen.svg)](https://opensource.org/licenses/ISC)
96
107 ## INTRODUCTION
3027
3128 ## ABOUT THIS RELEASE
3229
33 This is dnspython 2.0.0.
30 This is the development version of dnspython 2.2.0.
3431 Please read
35 [What's New](https://dnspython.readthedocs.io/en/latest/whatsnew.html) for
32 [What's New](https://dnspython.readthedocs.io/en/stable/whatsnew.html) for
3633 information about the changes in this release.
3734
3835 ## INSTALLATION
1111 vmImage: 'vs2017-win2016'
1212 strategy:
1313 matrix:
14 Python37:
15 python.version: '3.7'
14 Python38:
15 python.version: '3.8'
1616 steps:
1717 - task: UsePythonVersion@0
1818 inputs:
1919 versionSpec: '$(python.version)'
2020 displayName: 'Use Python $(python.version)'
2121
22 # - script: |
23 # python -m pip install --upgrade pip wheel setuptools
24 # displayName: 'Install pip and wheel'
25
26 - powershell:
27 (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python -
28 displayName: 'Install Poetry'
29
2230 - script: |
23 python -m pip install --upgrade pip
24 pip install -e .[dnssec,idna,doh,trio,curio]
31 %USERPROFILE%\.poetry\bin\poetry install -E dnssec -E doh -E idna -E trio -E curio
2532 displayName: 'Install python dependencies'
33
34 # - script: |
35 # python -m pip install requests requests-toolbelt idna cryptography
36 # python -m pip install trio sniffio curio
37 # displayName: 'Install python dependencies'
2638
2739 - script: |
2840 dotnet tool install --global Codecov.Tool
2941 displayName: 'Install Codecov.Tool'
3042
3143 - script: |
32 pip install pytest pytest-cov pytest-azurepipelines
33 pytest --junitxml=junit/test-results.xml --cov=. --cov-report=xml --cov-report=html
44 %USERPROFILE%\.poetry\bin\poetry run python -m pip install pytest-azurepipelines
45 %USERPROFILE%\.poetry\bin\poetry run pytest --junitxml=junit/test-results.xml --cov=. --cov-report=xml --cov-report=html
3446 displayName: 'pytest'
3547
3648 - task: PublishTestResults@2
4557 # summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml'
4658
4759 - script: |
48 codecov -f coverage.xml
60 %USERPROFILE%\.dotnet\tools\codecov -f coverage.xml
4961 displayName: 'Upload to codecov'
2626 'entropy',
2727 'exception',
2828 'flags',
29 'immutable',
2930 'inet',
3031 'ipv4',
3132 'ipv6',
4748 'serial',
4849 'set',
4950 'tokenizer',
51 'transaction',
5052 'tsig',
5153 'tsigkeyring',
5254 'ttl',
5355 'rdtypes',
5456 'update',
5557 'version',
58 'versioned',
5659 'wire',
60 'xfr',
5761 'zone',
62 'zonefile',
5863 ]
5964
6065 from dns.version import version as __version__ # noqa
2626 async def close(self):
2727 pass
2828
29 async def getpeername(self):
30 raise NotImplementedError
31
32 async def getsockname(self):
33 raise NotImplementedError
34
2935 async def __aenter__(self):
3036 return self
3137
3541
3642 class DatagramSocket(Socket): # pragma: no cover
3743 async def sendto(self, what, destination, timeout):
38 pass
44 raise NotImplementedError
3945
4046 async def recvfrom(self, size, timeout):
41 pass
47 raise NotImplementedError
4248
4349
4450 class StreamSocket(Socket): # pragma: no cover
45 async def sendall(self, what, destination, timeout):
46 pass
51 async def sendall(self, what, timeout):
52 raise NotImplementedError
4753
4854 async def recv(self, size, timeout):
49 pass
55 raise NotImplementedError
5056
5157
5258 class Backend: # pragma: no cover
5763 source=None, destination=None, timeout=None,
5864 ssl_context=None, server_hostname=None):
5965 raise NotImplementedError
66
67 def datagram_connection_required(self):
68 return False
33
44 import socket
55 import asyncio
6 import sys
67
78 import dns._asyncbackend
89 import dns.exception
910
11
12 _is_win32 = sys.platform == 'win32'
1013
1114 def _get_running_loop():
1215 try:
2932 self.recvfrom = None
3033
3134 def error_received(self, exc): # pragma: no cover
32 if self.recvfrom:
35 if self.recvfrom and not self.recvfrom.done():
3336 self.recvfrom.set_exception(exc)
3437
3538 def connection_lost(self, exc):
36 if self.recvfrom:
39 if self.recvfrom and not self.recvfrom.done():
3740 self.recvfrom.set_exception(exc)
3841
3942 def close(self):
7881 return self.transport.get_extra_info('sockname')
7982
8083
81 class StreamSocket(dns._asyncbackend.DatagramSocket):
84 class StreamSocket(dns._asyncbackend.StreamSocket):
8285 def __init__(self, af, reader, writer):
8386 self.family = af
8487 self.reader = reader
8588 self.writer = writer
8689
8790 async def sendall(self, what, timeout):
88 self.writer.write(what),
91 self.writer.write(what)
8992 return await _maybe_wait_for(self.writer.drain(), timeout)
90 raise dns.exception.Timeout(timeout=timeout)
9193
92 async def recv(self, count, timeout):
93 return await _maybe_wait_for(self.reader.read(count),
94 async def recv(self, size, timeout):
95 return await _maybe_wait_for(self.reader.read(size),
9496 timeout)
95 raise dns.exception.Timeout(timeout=timeout)
9697
9798 async def close(self):
9899 self.writer.close()
115116 async def make_socket(self, af, socktype, proto=0,
116117 source=None, destination=None, timeout=None,
117118 ssl_context=None, server_hostname=None):
119 if destination is None and socktype == socket.SOCK_DGRAM and \
120 _is_win32:
121 raise NotImplementedError('destinationless datagram sockets '
122 'are not supported by asyncio '
123 'on Windows')
118124 loop = _get_running_loop()
119125 if socktype == socket.SOCK_DGRAM:
120126 transport, protocol = await loop.create_datagram_endpoint(
121127 _DatagramProtocol, source, family=af,
122 proto=proto)
128 proto=proto, remote_addr=destination)
123129 return DatagramSocket(af, transport, protocol)
124130 elif socktype == socket.SOCK_STREAM:
125131 (r, w) = await _maybe_wait_for(
137143
138144 async def sleep(self, interval):
139145 await asyncio.sleep(interval)
146
147 def datagram_connection_required(self):
148 return _is_win32
1919
2020 # for brevity
2121 _lltuple = dns.inet.low_level_address_tuple
22
23 # pylint: disable=redefined-outer-name
2224
2325
2426 class DatagramSocket(dns._asyncbackend.DatagramSocket):
4648 return self.socket.getsockname()
4749
4850
49 class StreamSocket(dns._asyncbackend.DatagramSocket):
51 class StreamSocket(dns._asyncbackend.StreamSocket):
5052 def __init__(self, socket):
5153 self.socket = socket
5254 self.family = socket.family
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 # This implementation of the immutable decorator is for python 3.6,
3 # which doesn't have Context Variables. This implementation is somewhat
4 # costly for classes with slots, as it adds a __dict__ to them.
5
6
7 import inspect
8
9
10 class _Immutable:
11 """Immutable mixin class"""
12
13 # Note we MUST NOT have __slots__ as that causes
14 #
15 # TypeError: multiple bases have instance lay-out conflict
16 #
17 # when we get mixed in with another class with slots. When we
18 # get mixed into something with slots, it effectively adds __dict__ to
19 # the slots of the other class, which allows attribute setting to work,
20 # albeit at the cost of the dictionary.
21
22 def __setattr__(self, name, value):
23 if not hasattr(self, '_immutable_init') or \
24 self._immutable_init is not self:
25 raise TypeError("object doesn't support attribute assignment")
26 else:
27 super().__setattr__(name, value)
28
29 def __delattr__(self, name):
30 if not hasattr(self, '_immutable_init') or \
31 self._immutable_init is not self:
32 raise TypeError("object doesn't support attribute assignment")
33 else:
34 super().__delattr__(name)
35
36
37 def _immutable_init(f):
38 def nf(*args, **kwargs):
39 try:
40 # Are we already initializing an immutable class?
41 previous = args[0]._immutable_init
42 except AttributeError:
43 # We are the first!
44 previous = None
45 object.__setattr__(args[0], '_immutable_init', args[0])
46 try:
47 # call the actual __init__
48 f(*args, **kwargs)
49 finally:
50 if not previous:
51 # If we started the initialzation, establish immutability
52 # by removing the attribute that allows mutation
53 object.__delattr__(args[0], '_immutable_init')
54 nf.__signature__ = inspect.signature(f)
55 return nf
56
57
58 def immutable(cls):
59 if _Immutable in cls.__mro__:
60 # Some ancestor already has the mixin, so just make sure we keep
61 # following the __init__ protocol.
62 cls.__init__ = _immutable_init(cls.__init__)
63 if hasattr(cls, '__setstate__'):
64 cls.__setstate__ = _immutable_init(cls.__setstate__)
65 ncls = cls
66 else:
67 # Mixin the Immutable class and follow the __init__ protocol.
68 class ncls(_Immutable, cls):
69
70 @_immutable_init
71 def __init__(self, *args, **kwargs):
72 super().__init__(*args, **kwargs)
73
74 if hasattr(cls, '__setstate__'):
75 @_immutable_init
76 def __setstate__(self, *args, **kwargs):
77 super().__setstate__(*args, **kwargs)
78
79 # make ncls have the same name and module as cls
80 ncls.__name__ = cls.__name__
81 ncls.__qualname__ = cls.__qualname__
82 ncls.__module__ = cls.__module__
83 return ncls
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 # This implementation of the immutable decorator requires python >=
3 # 3.7, and is significantly more storage efficient when making classes
4 # with slots immutable. It's also faster.
5
6 import contextvars
7 import inspect
8
9
10 _in__init__ = contextvars.ContextVar('_immutable_in__init__', default=False)
11
12
13 class _Immutable:
14 """Immutable mixin class"""
15
16 # We set slots to the empty list to say "we don't have any attributes".
17 # We do this so that if we're mixed in with a class with __slots__, we
18 # don't cause a __dict__ to be added which would waste space.
19
20 __slots__ = ()
21
22 def __setattr__(self, name, value):
23 if _in__init__.get() is not self:
24 raise TypeError("object doesn't support attribute assignment")
25 else:
26 super().__setattr__(name, value)
27
28 def __delattr__(self, name):
29 if _in__init__.get() is not self:
30 raise TypeError("object doesn't support attribute assignment")
31 else:
32 super().__delattr__(name)
33
34
35 def _immutable_init(f):
36 def nf(*args, **kwargs):
37 previous = _in__init__.set(args[0])
38 try:
39 # call the actual __init__
40 f(*args, **kwargs)
41 finally:
42 _in__init__.reset(previous)
43 nf.__signature__ = inspect.signature(f)
44 return nf
45
46
47 def immutable(cls):
48 if _Immutable in cls.__mro__:
49 # Some ancestor already has the mixin, so just make sure we keep
50 # following the __init__ protocol.
51 cls.__init__ = _immutable_init(cls.__init__)
52 if hasattr(cls, '__setstate__'):
53 cls.__setstate__ = _immutable_init(cls.__setstate__)
54 ncls = cls
55 else:
56 # Mixin the Immutable class and follow the __init__ protocol.
57 class ncls(_Immutable, cls):
58 # We have to do the __slots__ declaration here too!
59 __slots__ = ()
60
61 @_immutable_init
62 def __init__(self, *args, **kwargs):
63 super().__init__(*args, **kwargs)
64
65 if hasattr(cls, '__setstate__'):
66 @_immutable_init
67 def __setstate__(self, *args, **kwargs):
68 super().__setstate__(*args, **kwargs)
69
70 # make ncls have the same name and module as cls
71 ncls.__name__ = cls.__name__
72 ncls.__qualname__ = cls.__qualname__
73 ncls.__module__ = cls.__module__
74 return ncls
1919
2020 # for brevity
2121 _lltuple = dns.inet.low_level_address_tuple
22
23 # pylint: disable=redefined-outer-name
2224
2325
2426 class DatagramSocket(dns._asyncbackend.DatagramSocket):
4648 return self.socket.getsockname()
4749
4850
49 class StreamSocket(dns._asyncbackend.DatagramSocket):
51 class StreamSocket(dns._asyncbackend.StreamSocket):
5052 def __init__(self, family, stream, tls=False):
5153 self.family = family
5254 self.stream = stream
11
22 import dns.exception
33
4 # pylint: disable=unused-import
5
46 from dns._asyncbackend import Socket, DatagramSocket, \
57 StreamSocket, Backend # noqa:
68
9 # pylint: enable=unused-import
710
811 _default_backend = None
912
2427
2528 Raises NotImplementError if an unknown backend name is specified.
2629 """
30 # pylint: disable=import-outside-toplevel,redefined-outer-name
2731 backend = _backends.get(name)
2832 if backend:
2933 return backend
4953 Returns the name of the library, or raises AsyncLibraryNotFoundError
5054 if the library cannot be determined.
5155 """
56 # pylint: disable=import-outside-toplevel
5257 try:
5358 if _no_sniffio:
5459 raise ImportError
2929 import dns.rdataclass
3030 import dns.rdatatype
3131
32 from dns.query import _compute_times, _matches_destination, BadResponse, ssl
32 from dns.query import _compute_times, _matches_destination, BadResponse, ssl, \
33 UDPMode
3334
3435
3536 # for brevity
9394
9495 *sock*, a ``dns.asyncbackend.DatagramSocket``.
9596
96 *destination*, a destination tuple appropriate for the address family
97 of the socket, specifying where the message is expected to arrive from.
98 When receiving a response, this would be where the associated query was
99 sent.
100
101 *expiration*, a ``float`` or ``None``, the absolute time at which
102 a timeout exception should be raised. If ``None``, no timeout will
103 occur.
104
105 *ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
106 unexpected sources.
107
108 *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
109 RRset.
110
111 *keyring*, a ``dict``, the keyring to use for TSIG.
112
113 *request_mac*, a ``bytes``, the MAC of the request (for TSIG).
114
115 *ignore_trailing*, a ``bool``. If ``True``, ignore trailing
116 junk at end of the received message.
117
118 *raise_on_truncation*, a ``bool``. If ``True``, raise an exception if
119 the TC bit is set.
120
121 Raises if the message is malformed, if network errors occur, of if
122 there is a timeout.
123
124 Returns a ``(dns.message.Message, float, tuple)`` tuple of the received
125 message, the received time, and the address where the message arrived from.
97 See :py:func:`dns.query.receive_udp()` for the documentation of the other
98 parameters, exceptions, and return type of this method.
12699 """
127100
128101 wire = b''
144117 backend=None):
145118 """Return the response obtained after sending a query via UDP.
146119
147 *q*, a ``dns.message.Message``, the query to send
148
149 *where*, a ``str`` containing an IPv4 or IPv6 address, where
150 to send the message.
151
152 *timeout*, a ``float`` or ``None``, the number of seconds to wait before the
153 query times out. If ``None``, the default, wait forever.
154
155 *port*, an ``int``, the port send the message to. The default is 53.
156
157 *source*, a ``str`` containing an IPv4 or IPv6 address, specifying
158 the source address. The default is the wildcard address.
159
160 *source_port*, an ``int``, the port from which to send the message.
161 The default is 0.
162
163 *ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
164 unexpected sources.
165
166 *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
167 RRset.
168
169 *ignore_trailing*, a ``bool``. If ``True``, ignore trailing
170 junk at end of the received message.
171
172 *raise_on_truncation*, a ``bool``. If ``True``, raise an exception if
173 the TC bit is set.
174
175120 *sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``,
176121 the socket to use for the query. If ``None``, the default, a
177122 socket is created. Note that if a socket is provided, the
180125 *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
181126 the default, then dnspython will use the default backend.
182127
183 Returns a ``dns.message.Message``.
128 See :py:func:`dns.query.udp()` for the documentation of the other
129 parameters, exceptions, and return type of this method.
184130 """
185131 wire = q.to_wire()
186132 (begin_time, expiration) = _compute_times(timeout)
195141 if not backend:
196142 backend = dns.asyncbackend.get_default_backend()
197143 stuple = _source_tuple(af, source, source_port)
198 s = await backend.make_socket(af, socket.SOCK_DGRAM, 0, stuple)
144 if backend.datagram_connection_required():
145 dtuple = (where, port)
146 else:
147 dtuple = None
148 s = await backend.make_socket(af, socket.SOCK_DGRAM, 0, stuple,
149 dtuple)
199150 await send_udp(s, wire, destination, expiration)
200151 (r, received_time, _) = await receive_udp(s, destination, expiration,
201152 ignore_unexpected,
218169 """Return the response to the query, trying UDP first and falling back
219170 to TCP if UDP results in a truncated response.
220171
221 *q*, a ``dns.message.Message``, the query to send
222
223 *where*, a ``str`` containing an IPv4 or IPv6 address, where
224 to send the message.
225
226 *timeout*, a ``float`` or ``None``, the number of seconds to wait before the
227 query times out. If ``None``, the default, wait forever.
228
229 *port*, an ``int``, the port send the message to. The default is 53.
230
231 *source*, a ``str`` containing an IPv4 or IPv6 address, specifying
232 the source address. The default is the wildcard address.
233
234 *source_port*, an ``int``, the port from which to send the message.
235 The default is 0.
236
237 *ignore_unexpected*, a ``bool``. If ``True``, ignore responses from
238 unexpected sources.
239
240 *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
241 RRset.
242
243 *ignore_trailing*, a ``bool``. If ``True``, ignore trailing
244 junk at end of the received message.
245
246172 *udp_sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``,
247173 the socket to use for the UDP query. If ``None``, the default, a
248174 socket is created. Note that if a socket is provided the *source*,
256182 *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
257183 the default, then dnspython will use the default backend.
258184
259 Returns a (``dns.message.Message``, tcp) tuple where tcp is ``True``
260 if and only if TCP was used.
185 See :py:func:`dns.query.udp_with_fallback()` for the documentation
186 of the other parameters, exceptions, and return type of this
187 method.
261188 """
262189 try:
263190 response = await udp(q, where, timeout, port, source, source_port,
274201 async def send_tcp(sock, what, expiration=None):
275202 """Send a DNS message to the specified TCP socket.
276203
277 *sock*, a ``socket``.
278
279 *what*, a ``bytes`` or ``dns.message.Message``, the message to send.
280
281 *expiration*, a ``float`` or ``None``, the absolute time at which
282 a timeout exception should be raised. If ``None``, no timeout will
283 occur.
284
285 Returns an ``(int, float)`` tuple of bytes sent and the sent time.
204 *sock*, a ``dns.asyncbackend.StreamSocket``.
205
206 See :py:func:`dns.query.send_tcp()` for the documentation of the other
207 parameters, exceptions, and return type of this method.
286208 """
287209
288210 if isinstance(what, dns.message.Message):
293215 # onto the net
294216 tcpmsg = struct.pack("!H", l) + what
295217 sent_time = time.time()
296 await sock.sendall(tcpmsg, expiration)
218 await sock.sendall(tcpmsg, _timeout(expiration, sent_time))
297219 return (len(tcpmsg), sent_time)
298220
299221
315237 keyring=None, request_mac=b'', ignore_trailing=False):
316238 """Read a DNS message from a TCP socket.
317239
318 *sock*, a ``socket``.
319
320 *expiration*, a ``float`` or ``None``, the absolute time at which
321 a timeout exception should be raised. If ``None``, no timeout will
322 occur.
323
324 *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
325 RRset.
326
327 *keyring*, a ``dict``, the keyring to use for TSIG.
328
329 *request_mac*, a ``bytes``, the MAC of the request (for TSIG).
330
331 *ignore_trailing*, a ``bool``. If ``True``, ignore trailing
332 junk at end of the received message.
333
334 Raises if the message is malformed, if network errors occur, of if
335 there is a timeout.
336
337 Returns a ``(dns.message.Message, float)`` tuple of the received message
338 and the received time.
240 *sock*, a ``dns.asyncbackend.StreamSocket``.
241
242 See :py:func:`dns.query.receive_tcp()` for the documentation of the other
243 parameters, exceptions, and return type of this method.
339244 """
340245
341246 ldata = await _read_exactly(sock, 2, expiration)
353258 backend=None):
354259 """Return the response obtained after sending a query via TCP.
355260
356 *q*, a ``dns.message.Message``, the query to send
357
358 *where*, a ``str`` containing an IPv4 or IPv6 address, where
359 to send the message.
360
361 *timeout*, a ``float`` or ``None``, the number of seconds to wait before the
362 query times out. If ``None``, the default, wait forever.
363
364 *port*, an ``int``, the port send the message to. The default is 53.
365
366 *source*, a ``str`` containing an IPv4 or IPv6 address, specifying
367 the source address. The default is the wildcard address.
368
369 *source_port*, an ``int``, the port from which to send the message.
370 The default is 0.
371
372 *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
373 RRset.
374
375 *ignore_trailing*, a ``bool``. If ``True``, ignore trailing
376 junk at end of the received message.
377
378261 *sock*, a ``dns.asyncbacket.StreamSocket``, or ``None``, the
379262 socket to use for the query. If ``None``, the default, a socket
380263 is created. Note that if a socket is provided
383266 *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
384267 the default, then dnspython will use the default backend.
385268
386 Returns a ``dns.message.Message``.
269 See :py:func:`dns.query.tcp()` for the documentation of the other
270 parameters, exceptions, and return type of this method.
387271 """
388272
389273 wire = q.to_wire()
425309 backend=None, ssl_context=None, server_hostname=None):
426310 """Return the response obtained after sending a query via TLS.
427311
428 *q*, a ``dns.message.Message``, the query to send
429
430 *where*, a ``str`` containing an IPv4 or IPv6 address, where
431 to send the message.
432
433 *timeout*, a ``float`` or ``None``, the number of seconds to wait before the
434 query times out. If ``None``, the default, wait forever.
435
436 *port*, an ``int``, the port send the message to. The default is 853.
437
438 *source*, a ``str`` containing an IPv4 or IPv6 address, specifying
439 the source address. The default is the wildcard address.
440
441 *source_port*, an ``int``, the port from which to send the message.
442 The default is 0.
443
444 *one_rr_per_rrset*, a ``bool``. If ``True``, put each RR into its own
445 RRset.
446
447 *ignore_trailing*, a ``bool``. If ``True``, ignore trailing
448 junk at end of the received message.
449
450312 *sock*, an ``asyncbackend.StreamSocket``, or ``None``, the socket
451313 to use for the query. If ``None``, the default, a socket is
452314 created. Note that if a socket is provided, it must be a
457319 *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
458320 the default, then dnspython will use the default backend.
459321
460 *ssl_context*, an ``ssl.SSLContext``, the context to use when establishing
461 a TLS connection. If ``None``, the default, creates one with the default
462 configuration.
463
464 *server_hostname*, a ``str`` containing the server's hostname. The
465 default is ``None``, which means that no hostname is known, and if an
466 SSL context is created, hostname checking will be disabled.
467
468 Returns a ``dns.message.Message``.
322 See :py:func:`dns.query.tls()` for the documentation of the other
323 parameters, exceptions, and return type of this method.
469324 """
470325 # After 3.6 is no longer supported, this can use an AsyncExitStack.
471326 (begin_time, expiration) = _compute_times(timeout)
497352 finally:
498353 if not sock and s:
499354 await s.close()
355
356 async def inbound_xfr(where, txn_manager, query=None,
357 port=53, timeout=None, lifetime=None, source=None,
358 source_port=0, udp_mode=UDPMode.NEVER, backend=None):
359 """Conduct an inbound transfer and apply it via a transaction from the
360 txn_manager.
361
362 *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
363 the default, then dnspython will use the default backend.
364
365 See :py:func:`dns.query.inbound_xfr()` for the documentation of
366 the other parameters, exceptions, and return type of this method.
367 """
368 if query is None:
369 (query, serial) = dns.xfr.make_query(txn_manager)
370 else:
371 serial = dns.xfr.extract_serial_from_query(query)
372 rdtype = query.question[0].rdtype
373 is_ixfr = rdtype == dns.rdatatype.IXFR
374 origin = txn_manager.from_wire_origin()
375 wire = query.to_wire()
376 af = dns.inet.af_for_address(where)
377 stuple = _source_tuple(af, source, source_port)
378 dtuple = (where, port)
379 (_, expiration) = _compute_times(lifetime)
380 retry = True
381 while retry:
382 retry = False
383 if is_ixfr and udp_mode != UDPMode.NEVER:
384 sock_type = socket.SOCK_DGRAM
385 is_udp = True
386 else:
387 sock_type = socket.SOCK_STREAM
388 is_udp = False
389 if not backend:
390 backend = dns.asyncbackend.get_default_backend()
391 s = await backend.make_socket(af, sock_type, 0, stuple, dtuple,
392 _timeout(expiration))
393 async with s:
394 if is_udp:
395 await s.sendto(wire, dtuple, _timeout(expiration))
396 else:
397 tcpmsg = struct.pack("!H", len(wire)) + wire
398 await s.sendall(tcpmsg, expiration)
399 with dns.xfr.Inbound(txn_manager, rdtype, serial,
400 is_udp) as inbound:
401 done = False
402 tsig_ctx = None
403 while not done:
404 (_, mexpiration) = _compute_times(timeout)
405 if mexpiration is None or \
406 (expiration is not None and mexpiration > expiration):
407 mexpiration = expiration
408 if is_udp:
409 destination = _lltuple((where, port), af)
410 while True:
411 timeout = _timeout(mexpiration)
412 (rwire, from_address) = await s.recvfrom(65535,
413 timeout)
414 if _matches_destination(af, from_address,
415 destination, True):
416 break
417 else:
418 ldata = await _read_exactly(s, 2, mexpiration)
419 (l,) = struct.unpack("!H", ldata)
420 rwire = await _read_exactly(s, l, mexpiration)
421 is_ixfr = (rdtype == dns.rdatatype.IXFR)
422 r = dns.message.from_wire(rwire, keyring=query.keyring,
423 request_mac=query.mac, xfr=True,
424 origin=origin, tsig_ctx=tsig_ctx,
425 multi=(not is_udp),
426 one_rr_per_rrset=is_ixfr)
427 try:
428 done = inbound.process_message(r)
429 except dns.xfr.UseTCP:
430 assert is_udp # should not happen if we used TCP!
431 if udp_mode == UDPMode.ONLY:
432 raise
433 done = True
434 retry = True
435 udp_mode = UDPMode.NEVER
436 continue
437 tsig_ctx = r.tsig_ctx
438 if not retry and query.keyring and not r.had_tsig:
439 raise dns.exception.FormError("missing TSIG")
3333 _tcp = dns.asyncquery.tcp
3434
3535
36 class Resolver(dns.resolver.Resolver):
36 class Resolver(dns.resolver.BaseResolver):
37 """Asynchronous DNS stub resolver."""
3738
3839 async def resolve(self, qname, rdtype=dns.rdatatype.A,
3940 rdclass=dns.rdataclass.IN,
4243 backend=None):
4344 """Query nameservers asynchronously to find the answer to the question.
4445
45 The *qname*, *rdtype*, and *rdclass* parameters may be objects
46 of the appropriate type, or strings that can be converted into objects
47 of the appropriate type.
48
49 *qname*, a ``dns.name.Name`` or ``str``, the query name.
50
51 *rdtype*, an ``int`` or ``str``, the query type.
52
53 *rdclass*, an ``int`` or ``str``, the query class.
54
55 *tcp*, a ``bool``. If ``True``, use TCP to make the query.
56
57 *source*, a ``str`` or ``None``. If not ``None``, bind to this IP
58 address when making queries.
59
60 *raise_on_no_answer*, a ``bool``. If ``True``, raise
61 ``dns.resolver.NoAnswer`` if there's no answer to the question.
62
63 *source_port*, an ``int``, the port from which to send the message.
64
65 *lifetime*, a ``float``, how many seconds a query should run
66 before timing out.
67
68 *search*, a ``bool`` or ``None``, determines whether the
69 search list configured in the system's resolver configuration
70 are used for relative names, and whether the resolver's domain
71 may be added to relative names. The default is ``None``,
72 which causes the value of the resolver's
73 ``use_search_by_default`` attribute to be used.
74
7546 *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
7647 the default, then dnspython will use the default backend.
7748
78 Raises ``dns.resolver.NXDOMAIN`` if the query name does not exist.
79
80 Raises ``dns.resolver.YXDOMAIN`` if the query name is too long after
81 DNAME substitution.
82
83 Raises ``dns.resolver.NoAnswer`` if *raise_on_no_answer* is
84 ``True`` and the query name exists but has no RRset of the
85 desired type and class.
86
87 Raises ``dns.resolver.NoNameservers`` if no non-broken
88 nameservers are available to answer the question.
89
90 Returns a ``dns.resolver.Answer`` instance.
91
49 See :py:func:`dns.resolver.Resolver.resolve()` for the
50 documentation of the other parameters, exceptions, and return
51 type of this method.
9252 """
9353
9454 resolution = dns.resolver._Resolution(self, qname, rdtype, rdclass, tcp,
11070 (nameserver, port, tcp, backoff) = resolution.next_nameserver()
11171 if backoff:
11272 await backend.sleep(backoff)
113 timeout = self._compute_timeout(start, lifetime)
73 timeout = self._compute_timeout(start, lifetime,
74 resolution.errors)
11475 try:
11576 if dns.inet.is_address(nameserver):
11677 if tcp:
13899 if answer is not None:
139100 return answer
140101
141 async def query(self, *args, **kwargs):
142 # We have to define something here as we don't want to inherit the
143 # parent's query().
144 raise NotImplementedError
145
146102 async def resolve_address(self, ipaddr, *args, **kwargs):
147103 """Use an asynchronous resolver to run a reverse query for PTR
148104 records.
163119 rdtype=dns.rdatatype.PTR,
164120 rdclass=dns.rdataclass.IN,
165121 *args, **kwargs)
122
123 # pylint: disable=redefined-outer-name
124
125 async def canonical_name(self, name):
126 """Determine the canonical name of *name*.
127
128 The canonical name is the name the resolver uses for queries
129 after all CNAME and DNAME renamings have been applied.
130
131 *name*, a ``dns.name.Name`` or ``str``, the query name.
132
133 This method can raise any exception that ``resolve()`` can
134 raise, other than ``dns.resolver.NoAnswer`` and
135 ``dns.resolver.NXDOMAIN``.
136
137 Returns a ``dns.name.Name``.
138 """
139 try:
140 answer = await self.resolve(name, raise_on_no_answer=False)
141 canonical_name = answer.canonical_name
142 except dns.resolver.NXDOMAIN as e:
143 canonical_name = e.canonical_name
144 return canonical_name
145
166146
167147 default_resolver = None
168148
187167
188168 async def resolve(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
189169 tcp=False, source=None, raise_on_no_answer=True,
190 source_port=0, search=None, backend=None):
170 source_port=0, lifetime=None, search=None, backend=None):
191171 """Query nameservers asynchronously to find the answer to the question.
192172
193173 This is a convenience function that uses the default resolver
194174 object to make the query.
195175
196 See ``dns.asyncresolver.Resolver.resolve`` for more information on the
197 parameters.
176 See :py:func:`dns.asyncresolver.Resolver.resolve` for more
177 information on the parameters.
198178 """
199179
200180 return await get_default_resolver().resolve(qname, rdtype, rdclass, tcp,
201181 source, raise_on_no_answer,
202 source_port, search, backend)
182 source_port, lifetime, search,
183 backend)
203184
204185
205186 async def resolve_address(ipaddr, *args, **kwargs):
206187 """Use a resolver to run a reverse query for PTR records.
207188
208 See ``dns.asyncresolver.Resolver.resolve_address`` for more
189 See :py:func:`dns.asyncresolver.Resolver.resolve_address` for more
209190 information on the parameters.
210191 """
211192
212193 return await get_default_resolver().resolve_address(ipaddr, *args, **kwargs)
213194
195 async def canonical_name(name):
196 """Determine the canonical name of *name*.
197
198 See :py:func:`dns.resolver.Resolver.canonical_name` for more
199 information on the parameters and possible exceptions.
200 """
201
202 return await get_default_resolver().canonical_name(name)
214203
215204 async def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False,
216205 resolver=None, backend=None):
217206 """Find the name of the zone which contains the specified name.
218207
219 *name*, an absolute ``dns.name.Name`` or ``str``, the query name.
220
221 *rdclass*, an ``int``, the query class.
222
223 *tcp*, a ``bool``. If ``True``, use TCP to make the query.
224
225 *resolver*, a ``dns.asyncresolver.Resolver`` or ``None``, the
226 resolver to use. If ``None``, the default resolver is used.
227
228 *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
229 the default, then dnspython will use the default backend.
230
231 Raises ``dns.resolver.NoRootSOA`` if there is no SOA RR at the DNS
232 root. (This is only likely to happen if you're using non-default
233 root servers in your network and they are misconfigured.)
234
235 Returns a ``dns.name.Name``.
208 See :py:func:`dns.resolver.Resolver.zone_for_name` for more
209 information on the parameters and possible exceptions.
236210 """
237211
238212 if isinstance(name, str):
6363 return 255
6464
6565
66 globals().update(Algorithm.__members__)
67
68
6966 def algorithm_from_text(text):
7067 """Convert text into a DNSSEC algorithm value.
7168
168165
169166
170167 def _find_candidate_keys(keys, rrsig):
171 candidate_keys = []
172168 value = keys.get(rrsig.signer)
173 if value is None:
169 if isinstance(value, dns.node.Node):
170 rdataset = value.get_rdataset(dns.rdataclass.IN, dns.rdatatype.DNSKEY)
171 else:
172 rdataset = value
173 if rdataset is None:
174174 return None
175 if isinstance(value, dns.node.Node):
176 try:
177 rdataset = value.find_rdataset(dns.rdataclass.IN,
178 dns.rdatatype.DNSKEY)
179 except KeyError:
180 return None
181 else:
182 rdataset = value
183 for rdata in rdataset:
184 if rdata.algorithm == rrsig.algorithm and \
185 key_id(rdata) == rrsig.key_tag:
186 candidate_keys.append(rdata)
187 return candidate_keys
175 return [rd for rd in rdataset if
176 rd.algorithm == rrsig.algorithm and key_id(rd) == rrsig.key_tag]
188177
189178
190179 def _is_rsa(algorithm):
253242 return int.from_bytes(b, 'big')
254243
255244
245 def _validate_signature(sig, data, key, chosen_hash):
246 if _is_rsa(key.algorithm):
247 keyptr = key.key
248 (bytes_,) = struct.unpack('!B', keyptr[0:1])
249 keyptr = keyptr[1:]
250 if bytes_ == 0:
251 (bytes_,) = struct.unpack('!H', keyptr[0:2])
252 keyptr = keyptr[2:]
253 rsa_e = keyptr[0:bytes_]
254 rsa_n = keyptr[bytes_:]
255 try:
256 public_key = rsa.RSAPublicNumbers(
257 _bytes_to_long(rsa_e),
258 _bytes_to_long(rsa_n)).public_key(default_backend())
259 except ValueError:
260 raise ValidationFailure('invalid public key')
261 public_key.verify(sig, data, padding.PKCS1v15(), chosen_hash)
262 elif _is_dsa(key.algorithm):
263 keyptr = key.key
264 (t,) = struct.unpack('!B', keyptr[0:1])
265 keyptr = keyptr[1:]
266 octets = 64 + t * 8
267 dsa_q = keyptr[0:20]
268 keyptr = keyptr[20:]
269 dsa_p = keyptr[0:octets]
270 keyptr = keyptr[octets:]
271 dsa_g = keyptr[0:octets]
272 keyptr = keyptr[octets:]
273 dsa_y = keyptr[0:octets]
274 try:
275 public_key = dsa.DSAPublicNumbers(
276 _bytes_to_long(dsa_y),
277 dsa.DSAParameterNumbers(
278 _bytes_to_long(dsa_p),
279 _bytes_to_long(dsa_q),
280 _bytes_to_long(dsa_g))).public_key(default_backend())
281 except ValueError:
282 raise ValidationFailure('invalid public key')
283 public_key.verify(sig, data, chosen_hash)
284 elif _is_ecdsa(key.algorithm):
285 keyptr = key.key
286 if key.algorithm == Algorithm.ECDSAP256SHA256:
287 curve = ec.SECP256R1()
288 octets = 32
289 else:
290 curve = ec.SECP384R1()
291 octets = 48
292 ecdsa_x = keyptr[0:octets]
293 ecdsa_y = keyptr[octets:octets * 2]
294 try:
295 public_key = ec.EllipticCurvePublicNumbers(
296 curve=curve,
297 x=_bytes_to_long(ecdsa_x),
298 y=_bytes_to_long(ecdsa_y)).public_key(default_backend())
299 except ValueError:
300 raise ValidationFailure('invalid public key')
301 public_key.verify(sig, data, ec.ECDSA(chosen_hash))
302 elif _is_eddsa(key.algorithm):
303 keyptr = key.key
304 if key.algorithm == Algorithm.ED25519:
305 loader = ed25519.Ed25519PublicKey
306 else:
307 loader = ed448.Ed448PublicKey
308 try:
309 public_key = loader.from_public_bytes(keyptr)
310 except ValueError:
311 raise ValidationFailure('invalid public key')
312 public_key.verify(sig, data)
313 elif _is_gost(key.algorithm):
314 raise UnsupportedAlgorithm(
315 'algorithm "%s" not supported by dnspython' %
316 algorithm_to_text(key.algorithm))
317 else:
318 raise ValidationFailure('unknown algorithm %u' % key.algorithm)
319
320
256321 def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None):
257322 """Validate an RRset against a single signature rdata, throwing an
258323 exception if validation is not successful.
290355 if candidate_keys is None:
291356 raise ValidationFailure('unknown key')
292357
358 # For convenience, allow the rrset to be specified as a (name,
359 # rdataset) tuple as well as a proper rrset
360 if isinstance(rrset, tuple):
361 rrname = rrset[0]
362 rdataset = rrset[1]
363 else:
364 rrname = rrset.name
365 rdataset = rrset
366
367 if now is None:
368 now = time.time()
369 if rrsig.expiration < now:
370 raise ValidationFailure('expired')
371 if rrsig.inception > now:
372 raise ValidationFailure('not yet valid')
373
374 if _is_dsa(rrsig.algorithm):
375 sig_r = rrsig.signature[1:21]
376 sig_s = rrsig.signature[21:]
377 sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
378 _bytes_to_long(sig_s))
379 elif _is_ecdsa(rrsig.algorithm):
380 if rrsig.algorithm == Algorithm.ECDSAP256SHA256:
381 octets = 32
382 else:
383 octets = 48
384 sig_r = rrsig.signature[0:octets]
385 sig_s = rrsig.signature[octets:]
386 sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
387 _bytes_to_long(sig_s))
388 else:
389 sig = rrsig.signature
390
391 data = b''
392 data += rrsig.to_wire(origin=origin)[:18]
393 data += rrsig.signer.to_digestable(origin)
394
395 # Derelativize the name before considering labels.
396 rrname = rrname.derelativize(origin)
397
398 if len(rrname) - 1 < rrsig.labels:
399 raise ValidationFailure('owner name longer than RRSIG labels')
400 elif rrsig.labels < len(rrname) - 1:
401 suffix = rrname.split(rrsig.labels + 1)[1]
402 rrname = dns.name.from_text('*', suffix)
403 rrnamebuf = rrname.to_digestable()
404 rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
405 rrsig.original_ttl)
406 for rr in sorted(rdataset):
407 data += rrnamebuf
408 data += rrfixed
409 rrdata = rr.to_digestable(origin)
410 rrlen = struct.pack('!H', len(rrdata))
411 data += rrlen
412 data += rrdata
413
414 chosen_hash = _make_hash(rrsig.algorithm)
415
293416 for candidate_key in candidate_keys:
294 # For convenience, allow the rrset to be specified as a (name,
295 # rdataset) tuple as well as a proper rrset
296 if isinstance(rrset, tuple):
297 rrname = rrset[0]
298 rdataset = rrset[1]
299 else:
300 rrname = rrset.name
301 rdataset = rrset
302
303 if now is None:
304 now = time.time()
305 if rrsig.expiration < now:
306 raise ValidationFailure('expired')
307 if rrsig.inception > now:
308 raise ValidationFailure('not yet valid')
309
310 if _is_rsa(rrsig.algorithm):
311 keyptr = candidate_key.key
312 (bytes_,) = struct.unpack('!B', keyptr[0:1])
313 keyptr = keyptr[1:]
314 if bytes_ == 0:
315 (bytes_,) = struct.unpack('!H', keyptr[0:2])
316 keyptr = keyptr[2:]
317 rsa_e = keyptr[0:bytes_]
318 rsa_n = keyptr[bytes_:]
319 try:
320 public_key = rsa.RSAPublicNumbers(
321 _bytes_to_long(rsa_e),
322 _bytes_to_long(rsa_n)).public_key(default_backend())
323 except ValueError:
324 raise ValidationFailure('invalid public key')
325 sig = rrsig.signature
326 elif _is_dsa(rrsig.algorithm):
327 keyptr = candidate_key.key
328 (t,) = struct.unpack('!B', keyptr[0:1])
329 keyptr = keyptr[1:]
330 octets = 64 + t * 8
331 dsa_q = keyptr[0:20]
332 keyptr = keyptr[20:]
333 dsa_p = keyptr[0:octets]
334 keyptr = keyptr[octets:]
335 dsa_g = keyptr[0:octets]
336 keyptr = keyptr[octets:]
337 dsa_y = keyptr[0:octets]
338 try:
339 public_key = dsa.DSAPublicNumbers(
340 _bytes_to_long(dsa_y),
341 dsa.DSAParameterNumbers(
342 _bytes_to_long(dsa_p),
343 _bytes_to_long(dsa_q),
344 _bytes_to_long(dsa_g))).public_key(default_backend())
345 except ValueError:
346 raise ValidationFailure('invalid public key')
347 sig_r = rrsig.signature[1:21]
348 sig_s = rrsig.signature[21:]
349 sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
350 _bytes_to_long(sig_s))
351 elif _is_ecdsa(rrsig.algorithm):
352 keyptr = candidate_key.key
353 if rrsig.algorithm == Algorithm.ECDSAP256SHA256:
354 curve = ec.SECP256R1()
355 octets = 32
356 else:
357 curve = ec.SECP384R1()
358 octets = 48
359 ecdsa_x = keyptr[0:octets]
360 ecdsa_y = keyptr[octets:octets * 2]
361 try:
362 public_key = ec.EllipticCurvePublicNumbers(
363 curve=curve,
364 x=_bytes_to_long(ecdsa_x),
365 y=_bytes_to_long(ecdsa_y)).public_key(default_backend())
366 except ValueError:
367 raise ValidationFailure('invalid public key')
368 sig_r = rrsig.signature[0:octets]
369 sig_s = rrsig.signature[octets:]
370 sig = utils.encode_dss_signature(_bytes_to_long(sig_r),
371 _bytes_to_long(sig_s))
372
373 elif _is_eddsa(rrsig.algorithm):
374 keyptr = candidate_key.key
375 if rrsig.algorithm == Algorithm.ED25519:
376 loader = ed25519.Ed25519PublicKey
377 else:
378 loader = ed448.Ed448PublicKey
379 try:
380 public_key = loader.from_public_bytes(keyptr)
381 except ValueError:
382 raise ValidationFailure('invalid public key')
383 sig = rrsig.signature
384 elif _is_gost(rrsig.algorithm):
385 raise UnsupportedAlgorithm(
386 'algorithm "%s" not supported by dnspython' %
387 algorithm_to_text(rrsig.algorithm))
388 else:
389 raise ValidationFailure('unknown algorithm %u' % rrsig.algorithm)
390
391 data = b''
392 data += rrsig.to_wire(origin=origin)[:18]
393 data += rrsig.signer.to_digestable(origin)
394
395 if rrsig.labels < len(rrname) - 1:
396 suffix = rrname.split(rrsig.labels + 1)[1]
397 rrname = dns.name.from_text('*', suffix)
398 rrnamebuf = rrname.to_digestable(origin)
399 rrfixed = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
400 rrsig.original_ttl)
401 rrlist = sorted(rdataset)
402 for rr in rrlist:
403 data += rrnamebuf
404 data += rrfixed
405 rrdata = rr.to_digestable(origin)
406 rrlen = struct.pack('!H', len(rrdata))
407 data += rrlen
408 data += rrdata
409
410 chosen_hash = _make_hash(rrsig.algorithm)
411417 try:
412 if _is_rsa(rrsig.algorithm):
413 public_key.verify(sig, data, padding.PKCS1v15(), chosen_hash)
414 elif _is_dsa(rrsig.algorithm):
415 public_key.verify(sig, data, chosen_hash)
416 elif _is_ecdsa(rrsig.algorithm):
417 public_key.verify(sig, data, ec.ECDSA(chosen_hash))
418 elif _is_eddsa(rrsig.algorithm):
419 public_key.verify(sig, data)
420 else:
421 # Raise here for code clarity; this won't actually ever happen
422 # since if the algorithm is really unknown we'd already have
423 # raised an exception above
424 raise ValidationFailure('unknown algorithm %u' %
425 rrsig.algorithm) # pragma: no cover
426 # If we got here, we successfully verified so we can return
427 # without error
418 _validate_signature(sig, data, candidate_key, chosen_hash)
428419 return
429 except InvalidSignature:
420 except (InvalidSignature, ValidationFailure):
430421 # this happens on an individual validation failure
431422 continue
432423 # nothing verified -- raise failure:
545536 domain_encoded = domain.canonicalize().to_wire()
546537
547538 digest = hashlib.sha1(domain_encoded + salt_encoded).digest()
548 for i in range(iterations):
539 for _ in range(iterations):
549540 digest = hashlib.sha1(digest + salt_encoded).digest()
550541
551542 output = base64.b32encode(digest).decode("utf-8")
578569 validate = _validate # type: ignore
579570 validate_rrsig = _validate_rrsig # type: ignore
580571 _have_pyca = True
572
573 ### BEGIN generated Algorithm constants
574
575 RSAMD5 = Algorithm.RSAMD5
576 DH = Algorithm.DH
577 DSA = Algorithm.DSA
578 ECC = Algorithm.ECC
579 RSASHA1 = Algorithm.RSASHA1
580 DSANSEC3SHA1 = Algorithm.DSANSEC3SHA1
581 RSASHA1NSEC3SHA1 = Algorithm.RSASHA1NSEC3SHA1
582 RSASHA256 = Algorithm.RSASHA256
583 RSASHA512 = Algorithm.RSASHA512
584 ECCGOST = Algorithm.ECCGOST
585 ECDSAP256SHA256 = Algorithm.ECDSAP256SHA256
586 ECDSAP384SHA384 = Algorithm.ECDSAP384SHA384
587 ED25519 = Algorithm.ED25519
588 ED448 = Algorithm.ED448
589 INDIRECT = Algorithm.INDIRECT
590 PRIVATEDNS = Algorithm.PRIVATEDNS
591 PRIVATEOID = Algorithm.PRIVATEOID
592
593 ### END generated Algorithm constants
2222
2323 import dns.enum
2424 import dns.inet
25 import dns.rdata
26
2527
2628 class OptionType(dns.enum.IntEnum):
2729 #: NSID
4951 def _maximum(cls):
5052 return 65535
5153
52 globals().update(OptionType.__members__)
5354
5455 class Option:
5556
6061
6162 *otype*, an ``int``, is the option type.
6263 """
63 self.otype = otype
64 self.otype = OptionType.make(otype)
6465
6566 def to_wire(self, file=None):
6667 """Convert an option to wire format.
148149
149150 def __init__(self, otype, data):
150151 super().__init__(otype)
151 self.data = data
152 self.data = dns.rdata.Rdata._as_bytes(data, True)
152153
153154 def to_wire(self, file=None):
154155 if file:
185186 self.family = 2
186187 if srclen is None:
187188 srclen = 56
189 address = dns.rdata.Rdata._as_ipv6_address(address)
190 srclen = dns.rdata.Rdata._as_int(srclen, 0, 128)
191 scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 128)
188192 elif af == socket.AF_INET:
189193 self.family = 1
190194 if srclen is None:
191195 srclen = 24
192 else:
193 raise ValueError('Bad ip family')
196 address = dns.rdata.Rdata._as_ipv4_address(address)
197 srclen = dns.rdata.Rdata._as_int(srclen, 0, 32)
198 scopelen = dns.rdata.Rdata._as_int(scopelen, 0, 32)
199 else: # pragma: no cover (this will never happen)
200 raise ValueError('Bad address family')
194201
195202 self.address = address
196203 self.srclen = srclen
341348 parser = dns.wire.Parser(wire, current)
342349 with parser.restrict_to(olen):
343350 return option_from_wire_parser(otype, parser)
351
352 def register_type(implementation, otype):
353 """Register the implementation of an option type.
354
355 *implementation*, a ``class``, is a subclass of ``dns.edns.Option``.
356
357 *otype*, an ``int``, is the option type.
358 """
359
360 _type_to_class[otype] = implementation
361
362 ### BEGIN generated OptionType constants
363
364 NSID = OptionType.NSID
365 DAU = OptionType.DAU
366 DHU = OptionType.DHU
367 N3U = OptionType.N3U
368 ECS = OptionType.ECS
369 EXPIRE = OptionType.EXPIRE
370 COOKIE = OptionType.COOKIE
371 KEEPALIVE = OptionType.KEEPALIVE
372 PADDING = OptionType.PADDING
373 CHAIN = OptionType.CHAIN
374
375 ### END generated OptionType constants
7474
7575 @classmethod
7676 def _maximum(cls):
77 raise NotImplementedError
77 raise NotImplementedError # pragma: no cover
7878
7979 @classmethod
8080 def _short_name(cls):
125125 """The DNS operation timed out."""
126126 supp_kwargs = {'timeout'}
127127 fmt = "The DNS operation timed out after {timeout} seconds"
128
129
130 class ExceptionWrapper:
131 def __init__(self, exception_class):
132 self.exception_class = exception_class
133
134 def __enter__(self):
135 return self
136
137 def __exit__(self, exc_type, exc_val, exc_tb):
138 if exc_type is not None and not isinstance(exc_val,
139 self.exception_class):
140 raise self.exception_class(str(exc_val)) from exc_val
141 return False
3636 #: Checking Disabled
3737 CD = 0x0010
3838
39 globals().update(Flag.__members__)
40
4139
4240 # EDNS flags
4341
4442 class EDNSFlag(enum.IntFlag):
4543 #: DNSSEC answer OK
4644 DO = 0x8000
47
48
49 globals().update(EDNSFlag.__members__)
5045
5146
5247 def _from_text(text, enum_class):
10398 """
10499
105100 return _to_text(flags, EDNSFlag)
101
102 ### BEGIN generated Flag constants
103
104 QR = Flag.QR
105 AA = Flag.AA
106 TC = Flag.TC
107 RD = Flag.RD
108 RA = Flag.RA
109 AD = Flag.AD
110 CD = Flag.CD
111
112 ### END generated Flag constants
113
114 ### BEGIN generated EDNSFlag constants
115
116 DO = EDNSFlag.DO
117
118 ### END generated EDNSFlag constants
2727 Returns a tuple of three ``int`` values ``(start, stop, step)``.
2828 """
2929
30 # TODO, figure out the bounds on start, stop and step.
30 start = -1
31 stop = -1
3132 step = 1
3233 cur = ''
3334 state = 0
34 # state 0 1 2 3 4
35 # state 0 1 2
3536 # x - y / z
3637
3738 if text and text[0] == '-':
4142 if c == '-' and state == 0:
4243 start = int(cur)
4344 cur = ''
44 state = 2
45 state = 1
4546 elif c == '/':
4647 stop = int(cur)
4748 cur = ''
48 state = 4
49 state = 2
4950 elif c.isdigit():
5051 cur += c
5152 else:
5253 raise dns.exception.SyntaxError("Could not parse %s" % (c))
5354
54 if state in (1, 3):
55 raise dns.exception.SyntaxError()
56
57 if state == 2:
55 if state == 0:
56 raise dns.exception.SyntaxError("no stop value specified")
57 elif state == 1:
5858 stop = int(cur)
59
60 if state == 4:
59 else:
60 assert state == 2
6161 step = int(cur)
6262
6363 assert step >= 1
6464 assert start >= 0
65 assert start <= stop
66 # TODO, can start == stop?
65 if start > stop:
66 raise dns.exception.SyntaxError('start must be <= stop')
6767
6868 return (start, stop, step)
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import collections.abc
3 import sys
4
5 # pylint: disable=unused-import
6 if sys.version_info >= (3, 7):
7 odict = dict
8 from dns._immutable_ctx import immutable
9 else:
10 # pragma: no cover
11 from collections import OrderedDict as odict
12 from dns._immutable_attr import immutable # noqa
13 # pylint: enable=unused-import
14
15
16 @immutable
17 class Dict(collections.abc.Mapping):
18 def __init__(self, dictionary, no_copy=False):
19 """Make an immutable dictionary from the specified dictionary.
20
21 If *no_copy* is `True`, then *dictionary* will be wrapped instead
22 of copied. Only set this if you are sure there will be no external
23 references to the dictionary.
24 """
25 if no_copy and isinstance(dictionary, odict):
26 self._odict = dictionary
27 else:
28 self._odict = odict(dictionary)
29 self._hash = None
30
31 def __getitem__(self, key):
32 return self._odict.__getitem__(key)
33
34 def __hash__(self): # pylint: disable=invalid-hash-returned
35 if self._hash is None:
36 h = 0
37 for key in sorted(self._odict.keys()):
38 h ^= hash(key)
39 object.__setattr__(self, '_hash', h)
40 # this does return an int, but pylint doesn't figure that out
41 return self._hash
42
43 def __len__(self):
44 return len(self._odict)
45
46 def __iter__(self):
47 return iter(self._odict)
48
49
50 def constify(o):
51 """
52 Convert mutable types to immutable types.
53 """
54 if isinstance(o, bytearray):
55 return bytes(o)
56 if isinstance(o, tuple):
57 try:
58 hash(o)
59 return o
60 except Exception:
61 return tuple(constify(elt) for elt in o)
62 if isinstance(o, list):
63 return tuple(constify(elt) for elt in o)
64 if isinstance(o, dict):
65 cdict = odict()
66 for k, v in o.items():
67 cdict[k] = constify(v)
68 return Dict(cdict, True)
69 return o
161161 return (addrpart, port, 0, int(scope))
162162 try:
163163 return (addrpart, port, 0, socket.if_nametoindex(scope))
164 except AttributeError:
164 except AttributeError: # pragma: no cover (we can't really test this)
165165 ai_flags = socket.AI_NUMERICHOST
166166 ((*_, tup), *_) = socket.getaddrinfo(address, port, flags=ai_flags)
167167 return tup
120120 elif l > 2:
121121 raise dns.exception.SyntaxError
122122
123 if text == b'::':
123 if text == b'':
124 raise dns.exception.SyntaxError
125 elif text.endswith(b':') and not text.endswith(b'::'):
126 raise dns.exception.SyntaxError
127 elif text.startswith(b':') and not text.startswith(b'::'):
128 raise dns.exception.SyntaxError
129 elif text == b'::':
124130 text = b'0::'
125131 #
126132 # Get rid of the icky dot-quad syntax if we have it.
156162 if seen_empty:
157163 raise dns.exception.SyntaxError
158164 seen_empty = True
159 for i in range(0, 8 - l + 1):
165 for _ in range(0, 8 - l + 1):
160166 canonical.append(b'0000')
161167 else:
162168 lc = len(c)
3434 import dns.rdatatype
3535 import dns.rrset
3636 import dns.renderer
37 import dns.ttl
3738 import dns.tsig
3839 import dns.rdtypes.ANY.OPT
3940 import dns.rdtypes.ANY.TSIG
7778 Returns a ``dns.message.Message``.
7879 """
7980 return self.kwargs['message']
81
82
83 class NotQueryResponse(dns.exception.DNSException):
84 """Message is not a response to a query."""
85
86
87 class ChainTooLong(dns.exception.DNSException):
88 """The CNAME chain is too long."""
89
90
91 class AnswerForNXDOMAIN(dns.exception.DNSException):
92 """The rcode is NXDOMAIN but an answer was found."""
93
94 class NoPreviousName(dns.exception.SyntaxError):
95 """No previous name was known."""
8096
8197
8298 class MessageSection(dns.enum.IntEnum):
90106 def _maximum(cls):
91107 return 3
92108
93 globals().update(MessageSection.__members__)
94
109
110 DEFAULT_EDNS_PAYLOAD = 1232
111 MAX_CHAIN = 16
95112
96113 class Message:
97114 """A DNS message."""
168185
169186 s = io.StringIO()
170187 s.write('id %d\n' % self.id)
171 s.write('opcode %s\n' %
172 dns.opcode.to_text(dns.opcode.from_flags(self.flags)))
173 rc = dns.rcode.from_flags(self.flags, self.ednsflags)
174 s.write('rcode %s\n' % dns.rcode.to_text(rc))
188 s.write('opcode %s\n' % dns.opcode.to_text(self.opcode()))
189 s.write('rcode %s\n' % dns.rcode.to_text(self.rcode()))
175190 s.write('flags %s\n' % dns.flags.to_text(self.flags))
176191 if self.edns >= 0:
177192 s.write('edns %s\n' % self.edns)
230245 dns.opcode.from_flags(self.flags) != \
231246 dns.opcode.from_flags(other.flags):
232247 return False
233 if dns.rcode.from_flags(other.flags, other.ednsflags) != \
234 dns.rcode.NOERROR:
235 return True
248 if other.rcode() in {dns.rcode.FORMERR, dns.rcode.SERVFAIL,
249 dns.rcode.NOTIMP, dns.rcode.REFUSED}:
250 # We don't check the question section in these cases if
251 # the other question section is empty, even though they
252 # still really ought to have a question section.
253 if len(other.question) == 0:
254 return True
236255 if dns.opcode.is_update(self.flags):
237256 # This is assuming the "sender doesn't include anything
238257 # from the update", but we don't care to check the other
329348 return rrset
330349 else:
331350 for rrset in section:
332 if rrset.match(name, rdclass, rdtype, covers, deleting):
351 if rrset.full_match(name, rdclass, rdtype, covers,
352 deleting):
333353 return rrset
334354 if not create:
335355 raise KeyError
402422 *multi*, a ``bool``, should be set to ``True`` if this message is
403423 part of a multiple message sequence.
404424
405 *tsig_ctx*, a ``hmac.HMAC`` object, the ongoing TSIG context, used
406 when signing zone transfers.
425 *tsig_ctx*, a ``dns.tsig.HMACTSig`` or ``dns.tsig.GSSTSig`` object, the
426 ongoing TSIG context, used when signing zone transfers.
407427
408428 Raises ``dns.exception.TooBig`` if *max_size* was exceeded.
409429
466486 *key*, a ``dns.tsig.Key`` is the key to use. If a key is specified,
467487 the *keyring* and *algorithm* fields are not used.
468488
469 *keyring*, a ``dict`` or ``dns.tsig.Key``, is either the TSIG
470 keyring or key to use.
489 *keyring*, a ``dict``, ``callable`` or ``dns.tsig.Key``, is either
490 the TSIG keyring or key to use.
471491
472492 The format of a keyring dict is a mapping from TSIG key name, as
473493 ``dns.name.Name`` to ``dns.tsig.Key`` or a TSIG secret, a ``bytes``.
475495 used will be the first key in the *keyring*. Note that the order of
476496 keys in a dictionary is not defined, so applications should supply a
477497 keyname when a ``dict`` keyring is used, unless they know the keyring
478 contains only one key.
498 contains only one key. If a ``callable`` keyring is specified, the
499 callable will be called with the message and the keyname, and is
500 expected to return a key.
479501
480502 *keyname*, a ``dns.name.Name``, ``str`` or ``None``, the name of
481503 thes TSIG key to use; defaults to ``None``. If *keyring* is a
496518 """
497519
498520 if isinstance(keyring, dns.tsig.Key):
499 self.keyring = keyring
521 key = keyring
522 keyname = key.name
523 elif callable(keyring):
524 key = keyring(self, keyname)
500525 else:
501526 if isinstance(keyname, str):
502527 keyname = dns.name.from_text(keyname)
505530 key = keyring[keyname]
506531 if isinstance(key, bytes):
507532 key = dns.tsig.Key(keyname, key, algorithm)
508 self.keyring = key
533 self.keyring = key
509534 if original_id is None:
510535 original_id = self.id
511536 self.tsig = self._make_tsig(keyname, self.keyring.algorithm, 0, fudge,
544569 return bool(self.tsig)
545570
546571 @staticmethod
547 def _make_opt(flags=0, payload=1280, options=None):
572 def _make_opt(flags=0, payload=DEFAULT_EDNS_PAYLOAD, options=None):
548573 opt = dns.rdtypes.ANY.OPT.OPT(payload, dns.rdatatype.OPT,
549574 options or ())
550575 return dns.rrset.from_rdata(dns.name.root, int(flags), opt)
551576
552 def use_edns(self, edns=0, ednsflags=0, payload=1280, request_payload=None,
553 options=None):
577 def use_edns(self, edns=0, ednsflags=0, payload=DEFAULT_EDNS_PAYLOAD,
578 request_payload=None, options=None):
554579 """Configure EDNS behavior.
555580
556581 *edns*, an ``int``, is the EDNS level to use. Specifying
574599
575600 if edns is None or edns is False:
576601 edns = -1
577 if edns is True:
602 elif edns is True:
578603 edns = 0
579 if request_payload is None:
580 request_payload = payload
581604 if edns < 0:
582 ednsflags = 0
583 payload = 0
584 request_payload = 0
585 options = []
605 self.opt = None
606 self.request_payload = 0
586607 else:
587608 # make sure the EDNS version in ednsflags agrees with edns
588609 ednsflags &= 0xFF00FFFF
589610 ednsflags |= (edns << 16)
590611 if options is None:
591612 options = []
592 if edns >= 0:
593613 self.opt = self._make_opt(ednsflags, payload, options)
594 else:
595 self.opt = None
596 self.request_payload = request_payload
614 if request_payload is None:
615 request_payload = payload
616 self.request_payload = request_payload
597617
598618 @property
599619 def edns(self):
649669
650670 Returns an ``int``.
651671 """
652 return dns.rcode.from_flags(self.flags, self.ednsflags)
672 return dns.rcode.from_flags(int(self.flags), int(self.ednsflags))
653673
654674 def set_rcode(self, rcode):
655675 """Set the rcode.
667687
668688 Returns an ``int``.
669689 """
670 return dns.opcode.from_flags(self.flags)
690 return dns.opcode.from_flags(int(self.flags))
671691
672692 def set_opcode(self, opcode):
673693 """Set the opcode.
681701 # What the caller picked is fine.
682702 return value
683703
704 # pylint: disable=unused-argument
705
684706 def _parse_rr_header(self, section, name, rdclass, rdtype):
685707 return (rdclass, rdtype, None, False)
708
709 # pylint: enable=unused-argument
686710
687711 def _parse_special_rr_header(self, section, count, position,
688712 name, rdclass, rdtype):
698722 return (rdclass, rdtype, None, False)
699723
700724
725 class ChainingResult:
726 """The result of a call to dns.message.QueryMessage.resolve_chaining().
727
728 The ``answer`` attribute is the answer RRSet, or ``None`` if it doesn't
729 exist.
730
731 The ``canonical_name`` attribute is the canonical name after all
732 chaining has been applied (this is the name as ``rrset.name`` in cases
733 where rrset is not ``None``).
734
735 The ``minimum_ttl`` attribute is the minimum TTL, i.e. the TTL to
736 use if caching the data. It is the smallest of all the CNAME TTLs
737 and either the answer TTL if it exists or the SOA TTL and SOA
738 minimum values for negative answers.
739
740 The ``cnames`` attribute is a list of all the CNAME RRSets followed to
741 get to the canonical name.
742 """
743 def __init__(self, canonical_name, answer, minimum_ttl, cnames):
744 self.canonical_name = canonical_name
745 self.answer = answer
746 self.minimum_ttl = minimum_ttl
747 self.cnames = cnames
748
749
701750 class QueryMessage(Message):
702 pass
751 def resolve_chaining(self):
752 """Follow the CNAME chain in the response to determine the answer
753 RRset.
754
755 Raises ``dns.message.NotQueryResponse`` if the message is not
756 a response.
757
758 Raises ``dns.message.ChainTooLong`` if the CNAME chain is too long.
759
760 Raises ``dns.message.AnswerForNXDOMAIN`` if the rcode is NXDOMAIN
761 but an answer was found.
762
763 Raises ``dns.exception.FormError`` if the question count is not 1.
764
765 Returns a ChainingResult object.
766 """
767 if self.flags & dns.flags.QR == 0:
768 raise NotQueryResponse
769 if len(self.question) != 1:
770 raise dns.exception.FormError
771 question = self.question[0]
772 qname = question.name
773 min_ttl = dns.ttl.MAX_TTL
774 answer = None
775 count = 0
776 cnames = []
777 while count < MAX_CHAIN:
778 try:
779 answer = self.find_rrset(self.answer, qname, question.rdclass,
780 question.rdtype)
781 min_ttl = min(min_ttl, answer.ttl)
782 break
783 except KeyError:
784 if question.rdtype != dns.rdatatype.CNAME:
785 try:
786 crrset = self.find_rrset(self.answer, qname,
787 question.rdclass,
788 dns.rdatatype.CNAME)
789 cnames.append(crrset)
790 min_ttl = min(min_ttl, crrset.ttl)
791 for rd in crrset:
792 qname = rd.target
793 break
794 count += 1
795 continue
796 except KeyError:
797 # Exit the chaining loop
798 break
799 else:
800 # Exit the chaining loop
801 break
802 if count >= MAX_CHAIN:
803 raise ChainTooLong
804 if self.rcode() == dns.rcode.NXDOMAIN and answer is not None:
805 raise AnswerForNXDOMAIN
806 if answer is None:
807 # Further minimize the TTL with NCACHE.
808 auname = qname
809 while True:
810 # Look for an SOA RR whose owner name is a superdomain
811 # of qname.
812 try:
813 srrset = self.find_rrset(self.authority, auname,
814 question.rdclass,
815 dns.rdatatype.SOA)
816 min_ttl = min(min_ttl, srrset.ttl, srrset[0].minimum)
817 break
818 except KeyError:
819 try:
820 auname = auname.parent()
821 except dns.name.NoParent:
822 break
823 return ChainingResult(qname, answer, min_ttl, cnames)
824
825 def canonical_name(self):
826 """Return the canonical name of the first name in the question
827 section.
828
829 Raises ``dns.message.NotQueryResponse`` if the message is not
830 a response.
831
832 Raises ``dns.message.ChainTooLong`` if the CNAME chain is too long.
833
834 Raises ``dns.message.AnswerForNXDOMAIN`` if the rcode is NXDOMAIN
835 but an answer was found.
836
837 Raises ``dns.exception.FormError`` if the question count is not 1.
838 """
839 return self.resolve_chaining().canonical_name
703840
704841
705842 def _maybe_import_update():
706843 # We avoid circular imports by doing this here. We do it in another
707844 # function as doing it in _message_factory_from_opcode() makes "dns"
708845 # a local symbol, and the first line fails :)
846
847 # pylint: disable=redefined-outer-name,import-outside-toplevel,unused-import
709848 import dns.update # noqa: F401
710849
711850
752891 """
753892
754893 section = self.message.sections[section_number]
755 for i in range(qcount):
894 for _ in range(qcount):
756895 qname = self.parser.get_name(self.message.origin)
757896 (rdtype, rdclass) = self.parser.get_struct('!HH')
758897 (rdclass, rdtype, _, _) = \
810949 key = self.keyring.get(absolute_name)
811950 if isinstance(key, bytes):
812951 key = dns.tsig.Key(absolute_name, key, rd.algorithm)
952 elif callable(self.keyring):
953 key = self.keyring(self.message, absolute_name)
813954 else:
814955 key = self.keyring
815956 if key is None:
846987 self.parser.get_struct('!HHHHHH')
847988 factory = _message_factory_from_opcode(dns.opcode.from_flags(flags))
848989 self.message = factory(id=id)
849 self.message.flags = flags
990 self.message.flags = dns.flags.Flag(flags)
850991 self.initialize_message(self.message)
851992 self.one_rr_per_rrset = \
852993 self.message._get_one_rr_per_rrset(self.one_rr_per_rrset)
853994 self._get_question(MessageSection.QUESTION, qcount)
854995 if self.question_only:
855 return
996 return self.message
856997 self._get_section(MessageSection.ANSWER, ancount)
857998 self._get_section(MessageSection.AUTHORITY, aucount)
858999 self._get_section(MessageSection.ADDITIONAL, adcount)
8841025 of a zone transfer, *origin* should be the origin name of the
8851026 zone. If not ``None``, names will be relativized to the origin.
8861027
887 *tsig_ctx*, a ``hmac.HMAC`` object, the ongoing TSIG context, used
888 when validating zone transfers.
1028 *tsig_ctx*, a ``dns.tsig.HMACTSig`` or ``dns.tsig.GSSTSig`` object, the
1029 ongoing TSIG context, used when validating zone transfers.
8891030
8901031 *multi*, a ``bool``, should be set to ``True`` if this message is
8911032 part of a multiple message sequence.
9701111 self.id = None
9711112 self.edns = -1
9721113 self.ednsflags = 0
973 self.payload = None
1114 self.payload = DEFAULT_EDNS_PAYLOAD
9741115 self.rcode = None
9751116 self.opcode = dns.opcode.QUERY
9761117 self.flags = 0
9771118
978 def _header_line(self, section):
1119 def _header_line(self, _):
9791120 """Process one line from the text format header section."""
9801121
9811122 token = self.tok.get()
10271168 self.relativize,
10281169 self.relativize_to)
10291170 name = self.last_name
1171 if name is None:
1172 raise NoPreviousName
10301173 token = self.tok.get()
10311174 if not token.is_identifier():
10321175 raise dns.exception.SyntaxError
10611204 self.relativize,
10621205 self.relativize_to)
10631206 name = self.last_name
1207 if name is None:
1208 raise NoPreviousName
10641209 token = self.tok.get()
10651210 if not token.is_identifier():
10661211 raise dns.exception.SyntaxError
10911236 token = self.tok.get()
10921237 if empty and not token.is_eol_or_eof():
10931238 raise dns.exception.SyntaxError
1239 if not empty and token.is_eol_or_eof():
1240 raise dns.exception.UnexpectedEnd
10941241 if not token.is_eol_or_eof():
10951242 self.tok.unget(token)
10961243 rd = dns.rdata.from_text(rdclass, rdtype, self.tok,
12911438 kwargs = {}
12921439 if ednsflags is not None:
12931440 kwargs['ednsflags'] = ednsflags
1294 if use_edns is None:
1295 use_edns = 0
12961441 if payload is not None:
12971442 kwargs['payload'] = payload
1298 if use_edns is None:
1299 use_edns = 0
13001443 if request_payload is not None:
13011444 kwargs['request_payload'] = request_payload
1302 if use_edns is None:
1303 use_edns = 0
13041445 if options is not None:
13051446 kwargs['options'] = options
1306 if use_edns is None:
1307 use_edns = 0
1447 if kwargs and use_edns is None:
1448 use_edns = 0
13081449 kwargs['edns'] = use_edns
13091450 m.use_edns(**kwargs)
13101451 m.want_dnssec(want_dnssec)
13541495 tsig_error, b'', query.keyalgorithm)
13551496 response.request_mac = query.mac
13561497 return response
1498
1499 ### BEGIN generated MessageSection constants
1500
1501 QUESTION = MessageSection.QUESTION
1502 ANSWER = MessageSection.ANSWER
1503 AUTHORITY = MessageSection.AUTHORITY
1504 ADDITIONAL = MessageSection.ADDITIONAL
1505
1506 ### END generated MessageSection constants
00 from typing import Optional, Dict, List, Tuple, Union
1 from . import name, rrset, tsig, rdatatype, entropy, edns, rdataclass
1 from . import name, rrset, tsig, rdatatype, entropy, edns, rdataclass, rcode
22 import hmac
33
44 class Message:
2525 def is_response(self, other : Message) -> bool:
2626 ...
2727
28 def set_rcode(self, rcode : rcode.Rcode):
29 ...
30
2831 def from_text(a : str, idna_codec : Optional[name.IDNACodec] = None) -> Message:
2932 ...
3033
3134 def from_wire(wire, keyring : Optional[Dict[name.Name,bytes]] = None, request_mac = b'', xfr=False, origin=None,
32 tsig_ctx : Optional[hmac.HMAC] = None, multi=False,
35 tsig_ctx : Optional[Union[dns.tsig.HMACTSig, dns.tsig.GSSTSig]] = None, multi=False,
3336 question_only=False, one_rr_per_rrset=False,
3437 ignore_trailing=False) -> Message:
3538 ...
2929
3030 import dns.wire
3131 import dns.exception
32 import dns.immutable
3233
3334 # fullcompare() result values
3435
214215 if not have_idna_2008:
215216 raise NoIDNA2008
216217 try:
218 ulabel = idna.ulabel(label)
217219 if self.uts_46:
218 label = idna.uts46_remap(label, False, False)
219 return _escapify(idna.ulabel(label))
220 ulabel = idna.uts46_remap(ulabel, False, self.transitional)
221 return _escapify(ulabel)
220222 except (idna.IDNAError, UnicodeError) as e:
221223 raise IDNAException(idna_exception=e)
222224
303305 raise ValueError # pragma: no cover
304306
305307
308 @dns.immutable.immutable
306309 class Name:
307310
308311 """A DNS name.
319322 """
320323
321324 labels = [_maybe_convert_to_binary(x) for x in labels]
322 super().__setattr__('labels', tuple(labels))
325 self.labels = tuple(labels)
323326 _validate_labels(self.labels)
324
325 def __setattr__(self, name, value):
326 # Names are immutable
327 raise TypeError("object doesn't support attribute assignment")
328
329 def __delattr__(self, name):
330 # Names are immutable
331 raise TypeError("object doesn't support attribute deletion")
332327
333328 def __copy__(self):
334329 return Name(self.labels)
457452 Returns a ``bool``.
458453 """
459454
460 (nr, o, nl) = self.fullcompare(other)
455 (nr, _, _) = self.fullcompare(other)
461456 if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL:
462457 return True
463458 return False
471466 Returns a ``bool``.
472467 """
473468
474 (nr, o, nl) = self.fullcompare(other)
469 (nr, _, _) = self.fullcompare(other)
475470 if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL:
476471 return True
477472 return False
1010 def is_wild(self) -> bool: ...
1111 def fullcompare(self, other) -> Tuple[int,int,int]: ...
1212 def canonicalize(self) -> Name: ...
13 def __lt__(self, other : Name): ...
14 def __le__(self, other : Name): ...
15 def __ge__(self, other : Name): ...
16 def __gt__(self, other : Name): ...
13 def __eq__(self, other) -> bool: ...
14 def __ne__(self, other) -> bool: ...
15 def __lt__(self, other : Name) -> bool: ...
16 def __le__(self, other : Name) -> bool: ...
17 def __ge__(self, other : Name) -> bool: ...
18 def __gt__(self, other : Name) -> bool: ...
1719 def to_text(self, omit_final_dot=False) -> str: ...
1820 def to_unicode(self, omit_final_dot=False, idna_codec=None) -> str: ...
1921 def to_digestable(self, origin=None) -> bytes: ...
2022 def to_wire(self, file=None, compress=None, origin=None,
2123 canonicalize=False) -> Optional[bytes]: ...
22 def __add__(self, other : Name): ...
23 def __sub__(self, other : Name): ...
24 def __add__(self, other : Name) -> Name: ...
25 def __sub__(self, other : Name) -> Name: ...
2426 def split(self, depth) -> List[Tuple[str,str]]: ...
2527 def concatenate(self, other : Name) -> Name: ...
26 def relativize(self, origin): ...
27 def derelativize(self, origin): ...
28 def choose_relativity(self, origin : Optional[Name] = None, relativize=True): ...
28 def relativize(self, origin) -> Name: ...
29 def derelativize(self, origin) -> Name: ...
30 def choose_relativity(self, origin : Optional[Name] = None, relativize=True) -> Name: ...
2931 def parent(self) -> Name: ...
3032
3133 class IDNACodec:
8484 return key in self.__store
8585
8686 def get_deepest_match(self, name):
87 """Find the deepest match to *fname* in the dictionary.
87 """Find the deepest match to *name* in the dictionary.
8888
8989 The deepest match is the longest name in the dictionary which is
9090 a superdomain of *name*. Note that *superdomain* includes matching
179179
180180 if not isinstance(replacement, dns.rdataset.Rdataset):
181181 raise ValueError('replacement is not an rdataset')
182 if isinstance(replacement, dns.rrset.RRset):
183 # RRsets are not good replacements as the match() method
184 # is not compatible.
185 replacement = replacement.to_rdataset()
182186 self.delete_rdataset(replacement.rdclass, replacement.rdtype,
183187 replacement.covers)
184188 self.rdatasets.append(replacement)
3838 @classmethod
3939 def _unknown_exception_class(cls):
4040 return UnknownOpcode
41
42 globals().update(Opcode.__members__)
4341
4442
4543 class UnknownOpcode(dns.exception.DNSException):
104102 """
105103
106104 return from_flags(flags) == Opcode.UPDATE
105
106 ### BEGIN generated Opcode constants
107
108 QUERY = Opcode.QUERY
109 IQUERY = Opcode.IQUERY
110 STATUS = Opcode.STATUS
111 NOTIFY = Opcode.NOTIFY
112 UPDATE = Opcode.UPDATE
113
114 ### END generated Opcode constants
1717 """Talk to a DNS server."""
1818
1919 import contextlib
20 import enum
2021 import errno
2122 import os
22 import select
23 import selectors
2324 import socket
2425 import struct
2526 import time
3435 import dns.rdataclass
3536 import dns.rdatatype
3637 import dns.serial
38 import dns.xfr
3739
3840 try:
3941 import requests
7274 """A DNS query response does not respond to the question asked."""
7375
7476
75 class TransferError(dns.exception.DNSException):
76 """A zone transfer response got a non-zero rcode."""
77
78 def __init__(self, rcode):
79 message = 'Zone transfer error: %s' % dns.rcode.to_text(rcode)
80 super().__init__(message)
81 self.rcode = rcode
82
83
8477 class NoDOH(dns.exception.DNSException):
8578 """DNS over HTTPS (DOH) was requested but the requests module is not
8679 available."""
80
81
82 # for backwards compatibility
83 TransferError = dns.xfr.TransferError
8784
8885
8986 def _compute_times(timeout):
9390 else:
9491 return (now, now + timeout)
9592
96 # This module can use either poll() or select() as the "polling backend".
97 #
98 # A backend function takes an fd, bools for readability, writablity, and
99 # error detection, and a timeout.
100
101 def _poll_for(fd, readable, writable, error, timeout):
102 """Poll polling backend."""
103
104 event_mask = 0
105 if readable:
106 event_mask |= select.POLLIN
107 if writable:
108 event_mask |= select.POLLOUT
109 if error:
110 event_mask |= select.POLLERR
111
112 pollable = select.poll()
113 pollable.register(fd, event_mask)
114
115 if timeout:
116 event_list = pollable.poll(timeout * 1000)
117 else:
118 event_list = pollable.poll()
119
120 return bool(event_list)
121
122
123 def _select_for(fd, readable, writable, error, timeout):
124 """Select polling backend."""
125
126 rset, wset, xset = [], [], []
127
128 if readable:
129 rset = [fd]
130 if writable:
131 wset = [fd]
132 if error:
133 xset = [fd]
134
135 if timeout is None:
136 (rcount, wcount, xcount) = select.select(rset, wset, xset)
137 else:
138 (rcount, wcount, xcount) = select.select(rset, wset, xset, timeout)
139
140 return bool((rcount or wcount or xcount))
141
142
143 def _wait_for(fd, readable, writable, error, expiration):
144 # Use the selected polling backend to wait for any of the specified
93
94 def _wait_for(fd, readable, writable, _, expiration):
95 # Use the selected selector class to wait for any of the specified
14596 # events. An "expiration" absolute time is converted into a relative
14697 # timeout.
147
148 done = False
149 while not done:
150 if expiration is None:
151 timeout = None
152 else:
153 timeout = expiration - time.time()
154 if timeout <= 0.0:
155 raise dns.exception.Timeout
156 try:
157 if isinstance(fd, ssl.SSLSocket) and readable and fd.pending() > 0:
158 return True
159 if not _polling_backend(fd, readable, writable, error, timeout):
160 raise dns.exception.Timeout
161 except OSError as e: # pragma: no cover
162 if e.args[0] != errno.EINTR:
163 raise e
164 done = True
165
166
167 def _set_polling_backend(fn):
98 #
99 # The unused parameter is 'error', which is always set when
100 # selecting for read or write, and we have no error-only selects.
101
102 if readable and isinstance(fd, ssl.SSLSocket) and fd.pending() > 0:
103 return True
104 sel = _selector_class()
105 events = 0
106 if readable:
107 events |= selectors.EVENT_READ
108 if writable:
109 events |= selectors.EVENT_WRITE
110 if events:
111 sel.register(fd, events)
112 if expiration is None:
113 timeout = None
114 else:
115 timeout = expiration - time.time()
116 if timeout <= 0.0:
117 raise dns.exception.Timeout
118 if not sel.select(timeout):
119 raise dns.exception.Timeout
120
121
122 def _set_selector_class(selector_class):
168123 # Internal API. Do not use.
169124
170 global _polling_backend
171
172 _polling_backend = fn
173
174 if hasattr(select, 'poll'):
125 global _selector_class
126
127 _selector_class = selector_class
128
129 if hasattr(selectors, 'PollSelector'):
175130 # Prefer poll() on platforms that support it because it has no
176131 # limits on the maximum value of a file descriptor (plus it will
177132 # be more efficient for high values).
178 _polling_backend = _poll_for
133 _selector_class = selectors.PollSelector
179134 else:
180 _polling_backend = _select_for # pragma: no cover
135 _selector_class = selectors.SelectSelector # pragma: no cover
181136
182137
183138 def _wait_for_readable(s, expiration):
322277 raise NoDOH # pragma: no cover
323278
324279 wire = q.to_wire()
325 (af, destination, source) = _destination_and_source(where, port,
326 source, source_port,
327 False)
280 (af, _, source) = _destination_and_source(where, port, source, source_port,
281 False)
328282 transport_adapter = None
329283 headers = {
330284 "accept": "application/dns-message"
331285 }
332 try:
333 where_af = dns.inet.af_for_address(where)
334 if where_af == socket.AF_INET:
286 if af is not None:
287 if af == socket.AF_INET:
335288 url = 'https://{}:{}{}'.format(where, port, path)
336 elif where_af == socket.AF_INET6:
289 elif af == socket.AF_INET6:
337290 url = 'https://[{}]:{}{}'.format(where, port, path)
338 except ValueError:
339 if bootstrap_address is not None:
340 split_url = urllib.parse.urlsplit(where)
341 headers['Host'] = split_url.hostname
342 url = where.replace(split_url.hostname, bootstrap_address)
343 transport_adapter = HostHeaderSSLAdapter()
344 else:
345 url = where
291 elif bootstrap_address is not None:
292 split_url = urllib.parse.urlsplit(where)
293 headers['Host'] = split_url.hostname
294 url = where.replace(split_url.hostname, bootstrap_address)
295 transport_adapter = HostHeaderSSLAdapter()
296 else:
297 url = where
346298 if source is not None:
347299 # set source port and source address
348300 transport_adapter = SourceAddressAdapter(source)
386338 raise BadResponse
387339 return r
388340
341 def _udp_recv(sock, max_size, expiration):
342 """Reads a datagram from the socket.
343 A Timeout exception will be raised if the operation is not completed
344 by the expiration time.
345 """
346 while True:
347 try:
348 return sock.recvfrom(max_size)
349 except BlockingIOError:
350 _wait_for_readable(sock, expiration)
351
352
353 def _udp_send(sock, data, destination, expiration):
354 """Sends the specified datagram to destination over the socket.
355 A Timeout exception will be raised if the operation is not completed
356 by the expiration time.
357 """
358 while True:
359 try:
360 if destination:
361 return sock.sendto(data, destination)
362 else:
363 return sock.send(data)
364 except BlockingIOError: # pragma: no cover
365 _wait_for_writable(sock, expiration)
366
367
389368 def send_udp(sock, what, destination, expiration=None):
390369 """Send a DNS message to the specified UDP socket.
391370
405384
406385 if isinstance(what, dns.message.Message):
407386 what = what.to_wire()
408 _wait_for_writable(sock, expiration)
409387 sent_time = time.time()
410 n = sock.sendto(what, destination)
388 n = _udp_send(sock, what, destination, expiration)
411389 return (n, sent_time)
412390
413391
457435 """
458436
459437 wire = b''
460 while 1:
461 _wait_for_readable(sock, expiration)
462 (wire, from_address) = sock.recvfrom(65535)
438 while True:
439 (wire, from_address) = _udp_recv(sock, 65535, expiration)
463440 if _matches_destination(sock.family, from_address, destination,
464441 ignore_unexpected):
465442 break
597574 """
598575 s = b''
599576 while count > 0:
600 _wait_for_readable(sock, expiration)
601577 try:
602578 n = sock.recv(count)
603 except ssl.SSLWantReadError: # pragma: no cover
604 continue
579 if n == b'':
580 raise EOFError
581 count -= len(n)
582 s += n
583 except (BlockingIOError, ssl.SSLWantReadError):
584 _wait_for_readable(sock, expiration)
605585 except ssl.SSLWantWriteError: # pragma: no cover
606586 _wait_for_writable(sock, expiration)
607 continue
608 if n == b'':
609 raise EOFError
610 count = count - len(n)
611 s = s + n
612587 return s
613588
614589
620595 current = 0
621596 l = len(data)
622597 while current < l:
623 _wait_for_writable(sock, expiration)
624598 try:
625599 current += sock.send(data[current:])
600 except (BlockingIOError, ssl.SSLWantWriteError):
601 _wait_for_writable(sock, expiration)
626602 except ssl.SSLWantReadError: # pragma: no cover
627603 _wait_for_readable(sock, expiration)
628 continue
629 except ssl.SSLWantWriteError: # pragma: no cover
630 continue
631604
632605
633606 def send_tcp(sock, what, expiration=None):
651624 # avoid writev() or doing a short write that would get pushed
652625 # onto the net
653626 tcpmsg = struct.pack("!H", l) + what
654 _wait_for_writable(sock, expiration)
655627 sent_time = time.time()
656628 _net_write(sock, tcpmsg, expiration)
657629 return (len(tcpmsg), sent_time)
741713 (begin_time, expiration) = _compute_times(timeout)
742714 with contextlib.ExitStack() as stack:
743715 if sock:
744 #
745 # Verify that the socket is connected, as if it's not connected,
746 # it's not writable, and the polling in send_tcp() will time out or
747 # hang forever.
748 sock.getpeername()
749716 s = sock
750717 else:
751718 (af, destination, source) = _destination_and_source(where, port,
925892 _connect(s, destination, expiration)
926893 l = len(wire)
927894 if use_udp:
928 _wait_for_writable(s, expiration)
929 s.send(wire)
895 _udp_send(s, wire, None, expiration)
930896 else:
931897 tcpmsg = struct.pack("!H", l) + wire
932898 _net_write(s, tcpmsg, expiration)
947913 (expiration is not None and mexpiration > expiration):
948914 mexpiration = expiration
949915 if use_udp:
950 _wait_for_readable(s, expiration)
951 (wire, from_address) = s.recvfrom(65535)
916 (wire, _) = _udp_recv(s, 65535, mexpiration)
952917 else:
953918 ldata = _net_read(s, 2, mexpiration)
954919 (l,) = struct.unpack("!H", ldata)
1015980 if done and q.keyring and not r.had_tsig:
1016981 raise dns.exception.FormError("missing TSIG")
1017982 yield r
983
984
985 class UDPMode(enum.IntEnum):
986 """How should UDP be used in an IXFR from :py:func:`inbound_xfr()`?
987
988 NEVER means "never use UDP; always use TCP"
989 TRY_FIRST means "try to use UDP but fall back to TCP if needed"
990 ONLY means "raise ``dns.xfr.UseTCP`` if trying UDP does not succeed"
991 """
992 NEVER = 0
993 TRY_FIRST = 1
994 ONLY = 2
995
996
997 def inbound_xfr(where, txn_manager, query=None,
998 port=53, timeout=None, lifetime=None, source=None,
999 source_port=0, udp_mode=UDPMode.NEVER):
1000 """Conduct an inbound transfer and apply it via a transaction from the
1001 txn_manager.
1002
1003 *where*, a ``str`` containing an IPv4 or IPv6 address, where
1004 to send the message.
1005
1006 *txn_manager*, a ``dns.transaction.TransactionManager``, the txn_manager
1007 for this transfer (typically a ``dns.zone.Zone``).
1008
1009 *query*, the query to send. If not supplied, a default query is
1010 constructed using information from the *txn_manager*.
1011
1012 *port*, an ``int``, the port send the message to. The default is 53.
1013
1014 *timeout*, a ``float``, the number of seconds to wait for each
1015 response message. If None, the default, wait forever.
1016
1017 *lifetime*, a ``float``, the total number of seconds to spend
1018 doing the transfer. If ``None``, the default, then there is no
1019 limit on the time the transfer may take.
1020
1021 *source*, a ``str`` containing an IPv4 or IPv6 address, specifying
1022 the source address. The default is the wildcard address.
1023
1024 *source_port*, an ``int``, the port from which to send the message.
1025 The default is 0.
1026
1027 *udp_mode*, a ``dns.query.UDPMode``, determines how UDP is used
1028 for IXFRs. The default is ``dns.UDPMode.NEVER``, i.e. only use
1029 TCP. Other possibilites are ``dns.UDPMode.TRY_FIRST``, which
1030 means "try UDP but fallback to TCP if needed", and
1031 ``dns.UDPMode.ONLY``, which means "try UDP and raise
1032 ``dns.xfr.UseTCP`` if it does not succeeed.
1033
1034 Raises on errors.
1035 """
1036 if query is None:
1037 (query, serial) = dns.xfr.make_query(txn_manager)
1038 else:
1039 serial = dns.xfr.extract_serial_from_query(query)
1040 rdtype = query.question[0].rdtype
1041 is_ixfr = rdtype == dns.rdatatype.IXFR
1042 origin = txn_manager.from_wire_origin()
1043 wire = query.to_wire()
1044 (af, destination, source) = _destination_and_source(where, port,
1045 source, source_port)
1046 (_, expiration) = _compute_times(lifetime)
1047 retry = True
1048 while retry:
1049 retry = False
1050 if is_ixfr and udp_mode != UDPMode.NEVER:
1051 sock_type = socket.SOCK_DGRAM
1052 is_udp = True
1053 else:
1054 sock_type = socket.SOCK_STREAM
1055 is_udp = False
1056 with _make_socket(af, sock_type, source) as s:
1057 _connect(s, destination, expiration)
1058 if is_udp:
1059 _udp_send(s, wire, None, expiration)
1060 else:
1061 tcpmsg = struct.pack("!H", len(wire)) + wire
1062 _net_write(s, tcpmsg, expiration)
1063 with dns.xfr.Inbound(txn_manager, rdtype, serial,
1064 is_udp) as inbound:
1065 done = False
1066 tsig_ctx = None
1067 while not done:
1068 (_, mexpiration) = _compute_times(timeout)
1069 if mexpiration is None or \
1070 (expiration is not None and mexpiration > expiration):
1071 mexpiration = expiration
1072 if is_udp:
1073 (rwire, _) = _udp_recv(s, 65535, mexpiration)
1074 else:
1075 ldata = _net_read(s, 2, mexpiration)
1076 (l,) = struct.unpack("!H", ldata)
1077 rwire = _net_read(s, l, mexpiration)
1078 r = dns.message.from_wire(rwire, keyring=query.keyring,
1079 request_mac=query.mac, xfr=True,
1080 origin=origin, tsig_ctx=tsig_ctx,
1081 multi=(not is_udp),
1082 one_rr_per_rrset=is_ixfr)
1083 try:
1084 done = inbound.process_message(r)
1085 except dns.xfr.UseTCP:
1086 assert is_udp # should not happen if we used TCP!
1087 if udp_mode == UDPMode.ONLY:
1088 raise
1089 done = True
1090 retry = True
1091 udp_mode = UDPMode.NEVER
1092 continue
1093 tsig_ctx = r.tsig_ctx
1094 if not retry and query.keyring and not r.had_tsig:
1095 raise dns.exception.FormError("missing TSIG")
7171 def _unknown_exception_class(cls):
7272 return UnknownRcode
7373
74 globals().update(Rcode.__members__)
7574
7675 class UnknownRcode(dns.exception.DNSException):
7776 """A DNS rcode is unknown."""
103102 """
104103
105104 value = (flags & 0x000f) | ((ednsflags >> 20) & 0xff0)
106 if value < 0 or value > 4095:
107 raise ValueError('rcode must be >= 0 and <= 4095')
108105 return value
109106
110107
138135 if tsig and value == Rcode.BADVERS:
139136 return 'BADSIG'
140137 return Rcode.to_text(value)
138
139 ### BEGIN generated Rcode constants
140
141 NOERROR = Rcode.NOERROR
142 FORMERR = Rcode.FORMERR
143 SERVFAIL = Rcode.SERVFAIL
144 NXDOMAIN = Rcode.NXDOMAIN
145 NOTIMP = Rcode.NOTIMP
146 REFUSED = Rcode.REFUSED
147 YXDOMAIN = Rcode.YXDOMAIN
148 YXRRSET = Rcode.YXRRSET
149 NXRRSET = Rcode.NXRRSET
150 NOTAUTH = Rcode.NOTAUTH
151 NOTZONE = Rcode.NOTZONE
152 DSOTYPENI = Rcode.DSOTYPENI
153 BADVERS = Rcode.BADVERS
154 BADSIG = Rcode.BADSIG
155 BADKEY = Rcode.BADKEY
156 BADTIME = Rcode.BADTIME
157 BADMODE = Rcode.BADMODE
158 BADNAME = Rcode.BADNAME
159 BADALG = Rcode.BADALG
160 BADTRUNC = Rcode.BADTRUNC
161 BADCOOKIE = Rcode.BADCOOKIE
162
163 ### END generated Rcode constants
2222 import io
2323 import inspect
2424 import itertools
25 import random
2526
2627 import dns.wire
2728 import dns.exception
29 import dns.immutable
30 import dns.ipv4
31 import dns.ipv6
2832 import dns.name
2933 import dns.rdataclass
3034 import dns.rdatatype
3135 import dns.tokenizer
36 import dns.ttl
3237
3338 _chunksize = 32
3439
4550 in range(0, len(data), chunksize)]).decode()
4651
4752
48 def _hexify(data, chunksize=_chunksize):
53 # pylint: disable=unused-argument
54
55 def _hexify(data, chunksize=_chunksize, **kw):
4956 """Convert a binary string into its hex encoding, broken up into chunks
5057 of chunksize characters separated by a space.
5158 """
5360 return _wordbreak(binascii.hexlify(data), chunksize)
5461
5562
56 def _base64ify(data, chunksize=_chunksize):
63 def _base64ify(data, chunksize=_chunksize, **kw):
5764 """Convert a binary string into its base64 encoding, broken up into chunks
5865 of chunksize characters separated by a space.
5966 """
6067
6168 return _wordbreak(base64.b64encode(data), chunksize)
69
70 # pylint: enable=unused-argument
6271
6372 __escaped = b'"\\'
6473
91100 return what[0: i + 1]
92101 return what[0:1]
93102
94 def _constify(o):
95 """
96 Convert mutable types to immutable types.
97 """
98 if isinstance(o, bytearray):
99 return bytes(o)
100 if isinstance(o, tuple):
101 try:
102 hash(o)
103 return o
104 except Exception:
105 return tuple(_constify(elt) for elt in o)
106 if isinstance(o, list):
107 return tuple(_constify(elt) for elt in o)
108 return o
109
103 # So we don't have to edit all the rdata classes...
104 _constify = dns.immutable.constify
105
106
107 @dns.immutable.immutable
110108 class Rdata:
111109 """Base class for all DNS rdata types."""
112110
113 __slots__ = ['rdclass', 'rdtype']
111 __slots__ = ['rdclass', 'rdtype', 'rdcomment']
114112
115113 def __init__(self, rdclass, rdtype):
116114 """Initialize an rdata.
120118 *rdtype*, an ``int`` is the rdatatype of the Rdata.
121119 """
122120
123 object.__setattr__(self, 'rdclass', rdclass)
124 object.__setattr__(self, 'rdtype', rdtype)
125
126 def __setattr__(self, name, value):
127 # Rdatas are immutable
128 raise TypeError("object doesn't support attribute assignment")
129
130 def __delattr__(self, name):
131 # Rdatas are immutable
132 raise TypeError("object doesn't support attribute deletion")
121 self.rdclass = self._as_rdataclass(rdclass)
122 self.rdtype = self._as_rdatatype(rdtype)
123 self.rdcomment = None
133124
134125 def _get_all_slots(self):
135126 return itertools.chain.from_iterable(getattr(cls, '__slots__', [])
152143 def __setstate__(self, state):
153144 for slot, val in state.items():
154145 object.__setattr__(self, slot, val)
146 if not hasattr(self, 'rdcomment'):
147 # Pickled rdata from 2.0.x might not have a rdcomment, so add
148 # it if needed.
149 object.__setattr__(self, 'rdcomment', None)
155150
156151 def covers(self):
157152 """Return the type a Rdata covers.
183178 Returns a ``str``.
184179 """
185180
186 raise NotImplementedError
181 raise NotImplementedError # pragma: no cover
187182
188183 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
189 raise NotImplementedError
184 raise NotImplementedError # pragma: no cover
190185
191186 def to_wire(self, file=None, compress=None, origin=None,
192187 canonicalize=False):
294289 @classmethod
295290 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
296291 relativize_to=None):
297 raise NotImplementedError
298
299 @classmethod
300 def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin=None):
301 raise NotImplementedError
292 raise NotImplementedError # pragma: no cover
293
294 @classmethod
295 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
296 raise NotImplementedError # pragma: no cover
302297
303298 def replace(self, **kwargs):
304299 """
318313 # Ensure that all of the arguments correspond to valid fields.
319314 # Don't allow rdclass or rdtype to be changed, though.
320315 for key in kwargs:
316 if key == 'rdcomment':
317 continue
321318 if key not in parameters:
322319 raise AttributeError("'{}' object has no attribute '{}'"
323320 .format(self.__class__.__name__, key))
330327 args = (kwargs.get(key, getattr(self, key)) for key in parameters)
331328
332329 # Create, validate, and return the new object.
333 #
334 # Note that if we make constructors do validation in the future,
335 # this validation can go away.
336330 rd = self.__class__(*args)
337 dns.rdata.from_text(rd.rdclass, rd.rdtype, rd.to_text())
331 # The comment is not set in the constructor, so give it special
332 # handling.
333 rdcomment = kwargs.get('rdcomment', self.rdcomment)
334 if rdcomment is not None:
335 object.__setattr__(rd, 'rdcomment', rdcomment)
338336 return rd
337
338 # Type checking and conversion helpers. These are class methods as
339 # they don't touch object state and may be useful to others.
340
341 @classmethod
342 def _as_rdataclass(cls, value):
343 return dns.rdataclass.RdataClass.make(value)
344
345 @classmethod
346 def _as_rdatatype(cls, value):
347 return dns.rdatatype.RdataType.make(value)
348
349 @classmethod
350 def _as_bytes(cls, value, encode=False, max_length=None, empty_ok=True):
351 if encode and isinstance(value, str):
352 value = value.encode()
353 elif isinstance(value, bytearray):
354 value = bytes(value)
355 elif not isinstance(value, bytes):
356 raise ValueError('not bytes')
357 if max_length is not None and len(value) > max_length:
358 raise ValueError('too long')
359 if not empty_ok and len(value) == 0:
360 raise ValueError('empty bytes not allowed')
361 return value
362
363 @classmethod
364 def _as_name(cls, value):
365 # Note that proper name conversion (e.g. with origin and IDNA
366 # awareness) is expected to be done via from_text. This is just
367 # a simple thing for people invoking the constructor directly.
368 if isinstance(value, str):
369 return dns.name.from_text(value)
370 elif not isinstance(value, dns.name.Name):
371 raise ValueError('not a name')
372 return value
373
374 @classmethod
375 def _as_uint8(cls, value):
376 if not isinstance(value, int):
377 raise ValueError('not an integer')
378 if value < 0 or value > 255:
379 raise ValueError('not a uint8')
380 return value
381
382 @classmethod
383 def _as_uint16(cls, value):
384 if not isinstance(value, int):
385 raise ValueError('not an integer')
386 if value < 0 or value > 65535:
387 raise ValueError('not a uint16')
388 return value
389
390 @classmethod
391 def _as_uint32(cls, value):
392 if not isinstance(value, int):
393 raise ValueError('not an integer')
394 if value < 0 or value > 4294967295:
395 raise ValueError('not a uint32')
396 return value
397
398 @classmethod
399 def _as_uint48(cls, value):
400 if not isinstance(value, int):
401 raise ValueError('not an integer')
402 if value < 0 or value > 281474976710655:
403 raise ValueError('not a uint48')
404 return value
405
406 @classmethod
407 def _as_int(cls, value, low=None, high=None):
408 if not isinstance(value, int):
409 raise ValueError('not an integer')
410 if low is not None and value < low:
411 raise ValueError('value too small')
412 if high is not None and value > high:
413 raise ValueError('value too large')
414 return value
415
416 @classmethod
417 def _as_ipv4_address(cls, value):
418 if isinstance(value, str):
419 # call to check validity
420 dns.ipv4.inet_aton(value)
421 return value
422 elif isinstance(value, bytes):
423 return dns.ipv4.inet_ntoa(value)
424 else:
425 raise ValueError('not an IPv4 address')
426
427 @classmethod
428 def _as_ipv6_address(cls, value):
429 if isinstance(value, str):
430 # call to check validity
431 dns.ipv6.inet_aton(value)
432 return value
433 elif isinstance(value, bytes):
434 return dns.ipv6.inet_ntoa(value)
435 else:
436 raise ValueError('not an IPv6 address')
437
438 @classmethod
439 def _as_bool(cls, value):
440 if isinstance(value, bool):
441 return value
442 else:
443 raise ValueError('not a boolean')
444
445 @classmethod
446 def _as_ttl(cls, value):
447 if isinstance(value, int):
448 return cls._as_int(value, 0, dns.ttl.MAX_TTL)
449 elif isinstance(value, str):
450 return dns.ttl.from_text(value)
451 else:
452 raise ValueError('not a TTL')
453
454 @classmethod
455 def _as_tuple(cls, value, as_value):
456 try:
457 # For user convenience, if value is a singleton of the list
458 # element type, wrap it in a tuple.
459 return (as_value(value),)
460 except Exception:
461 # Otherwise, check each element of the iterable *value*
462 # against *as_value*.
463 return tuple(as_value(v) for v in value)
464
465 # Processing order
466
467 @classmethod
468 def _processing_order(cls, iterable):
469 items = list(iterable)
470 random.shuffle(items)
471 return items
339472
340473
341474 class GenericRdata(Rdata):
353486 object.__setattr__(self, 'data', data)
354487
355488 def to_text(self, origin=None, relativize=True, **kw):
356 return r'\# %d ' % len(self.data) + _hexify(self.data)
489 return r'\# %d ' % len(self.data) + _hexify(self.data, **kw)
357490
358491 @classmethod
359492 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
363496 raise dns.exception.SyntaxError(
364497 r'generic rdata does not start with \#')
365498 length = tok.get_int()
366 chunks = []
367 while 1:
368 token = tok.get()
369 if token.is_eol_or_eof():
370 break
371 chunks.append(token.value.encode())
372 hex = b''.join(chunks)
499 hex = tok.concatenate_remaining_identifiers().encode()
373500 data = binascii.unhexlify(hex)
374501 if len(data) != length:
375502 raise dns.exception.SyntaxError(
452579 Returns an instance of the chosen Rdata subclass.
453580
454581 """
455
456582 if isinstance(tok, str):
457583 tok = dns.tokenizer.Tokenizer(tok, idna_codec=idna_codec)
458584 rdclass = dns.rdataclass.RdataClass.make(rdclass)
459585 rdtype = dns.rdatatype.RdataType.make(rdtype)
460586 cls = get_rdata_class(rdclass, rdtype)
461 if cls != GenericRdata:
462 # peek at first token
463 token = tok.get()
464 tok.unget(token)
465 if token.is_identifier() and \
466 token.value == r'\#':
467 #
468 # Known type using the generic syntax. Extract the
469 # wire form from the generic syntax, and then run
470 # from_wire on it.
471 #
472 rdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
473 relativize, relativize_to)
474 return from_wire(rdclass, rdtype, rdata.data, 0, len(rdata.data),
475 origin)
476 return cls.from_text(rdclass, rdtype, tok, origin, relativize,
477 relativize_to)
587 with dns.exception.ExceptionWrapper(dns.exception.SyntaxError):
588 rdata = None
589 if cls != GenericRdata:
590 # peek at first token
591 token = tok.get()
592 tok.unget(token)
593 if token.is_identifier() and \
594 token.value == r'\#':
595 #
596 # Known type using the generic syntax. Extract the
597 # wire form from the generic syntax, and then run
598 # from_wire on it.
599 #
600 grdata = GenericRdata.from_text(rdclass, rdtype, tok, origin,
601 relativize, relativize_to)
602 rdata = from_wire(rdclass, rdtype, grdata.data, 0,
603 len(grdata.data), origin)
604 #
605 # If this comparison isn't equal, then there must have been
606 # compressed names in the wire format, which is an error,
607 # there being no reasonable context to decompress with.
608 #
609 rwire = rdata.to_wire()
610 if rwire != grdata.data:
611 raise dns.exception.SyntaxError('compressed data in '
612 'generic syntax form '
613 'of known rdatatype')
614 if rdata is None:
615 rdata = cls.from_text(rdclass, rdtype, tok, origin, relativize,
616 relativize_to)
617 token = tok.get_eol_as_token()
618 if token.comment is not None:
619 object.__setattr__(rdata, 'rdcomment', token.comment)
620 return rdata
478621
479622
480623 def from_wire_parser(rdclass, rdtype, parser, origin=None):
504647 rdclass = dns.rdataclass.RdataClass.make(rdclass)
505648 rdtype = dns.rdatatype.RdataType.make(rdtype)
506649 cls = get_rdata_class(rdclass, rdtype)
507 return cls.from_wire_parser(rdclass, rdtype, parser, origin)
650 with dns.exception.ExceptionWrapper(dns.exception.FormError):
651 return cls.from_wire_parser(rdclass, rdtype, parser, origin)
508652
509653
510654 def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None):
4747 def _unknown_exception_class(cls):
4848 return UnknownRdataclass
4949
50 globals().update(RdataClass.__members__)
5150
5251 _metaclasses = {RdataClass.NONE, RdataClass.ANY}
5352
9998 if rdclass in _metaclasses:
10099 return True
101100 return False
101
102 ### BEGIN generated RdataClass constants
103
104 RESERVED0 = RdataClass.RESERVED0
105 IN = RdataClass.IN
106 INTERNET = RdataClass.INTERNET
107 CH = RdataClass.CH
108 CHAOS = RdataClass.CHAOS
109 HS = RdataClass.HS
110 HESIOD = RdataClass.HESIOD
111 NONE = RdataClass.NONE
112 ANY = RdataClass.ANY
113
114 ### END generated RdataClass constants
2121 import struct
2222
2323 import dns.exception
24 import dns.immutable
2425 import dns.rdatatype
2526 import dns.rdataclass
2627 import dns.rdata
7879 TTL or the specified TTL. If the set contains no rdatas, set the TTL
7980 to the specified TTL.
8081
81 *ttl*, an ``int``.
82 """
83
82 *ttl*, an ``int`` or ``str``.
83 """
84 ttl = dns.ttl.make(ttl)
8485 if len(self) == 0:
8586 self.ttl = ttl
8687 elif ttl < self.ttl:
8788 self.ttl = ttl
8889
89 def add(self, rd, ttl=None):
90 def add(self, rd, ttl=None): # pylint: disable=arguments-differ
9091 """Add the specified rdata to the rdataset.
9192
9293 If the optional *ttl* parameter is supplied, then
175176 return not self.__eq__(other)
176177
177178 def to_text(self, name=None, origin=None, relativize=True,
178 override_rdclass=None, **kw):
179 """Convert the rdataset into DNS master file format.
179 override_rdclass=None, want_comments=False, **kw):
180 """Convert the rdataset into DNS zone file format.
180181
181182 See ``dns.name.Name.choose_relativity`` for more information
182183 on how *origin* and *relativize* determine the way names
193194
194195 *relativize*, a ``bool``. If ``True``, names will be relativized
195196 to *origin*.
197
198 *override_rdclass*, a ``dns.rdataclass.RdataClass`` or ``None``.
199 If not ``None``, use this class instead of the Rdataset's class.
200
201 *want_comments*, a ``bool``. If ``True``, emit comments for rdata
202 which have them. The default is ``False``.
196203 """
197204
198205 if name is not None:
218225 dns.rdatatype.to_text(self.rdtype)))
219226 else:
220227 for rd in self:
221 s.write('%s%s%d %s %s %s\n' %
228 extra = ''
229 if want_comments:
230 if rd.rdcomment:
231 extra = f' ;{rd.rdcomment}'
232 s.write('%s%s%d %s %s %s%s\n' %
222233 (ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass),
223234 dns.rdatatype.to_text(self.rdtype),
224235 rd.to_text(origin=origin, relativize=relativize,
225 **kw)))
236 **kw),
237 extra))
226238 #
227239 # We strip off the final \n for the caller's convenience in printing
228240 #
259271 want_shuffle = False
260272 else:
261273 rdclass = self.rdclass
262 file.seek(0, 2)
274 file.seek(0, io.SEEK_END)
263275 if len(self) == 0:
264276 name.to_wire(file, compress, origin)
265277 stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0)
283295 file.seek(start - 2)
284296 stuff = struct.pack("!H", end - start)
285297 file.write(stuff)
286 file.seek(0, 2)
298 file.seek(0, io.SEEK_END)
287299 return len(self)
288300
289301 def match(self, rdclass, rdtype, covers):
296308 return True
297309 return False
298310
299
300 def from_text_list(rdclass, rdtype, ttl, text_rdatas, idna_codec=None):
311 def processing_order(self):
312 """Return rdatas in a valid processing order according to the type's
313 specification. For example, MX records are in preference order from
314 lowest to highest preferences, with items of the same perference
315 shuffled.
316
317 For types that do not define a processing order, the rdatas are
318 simply shuffled.
319 """
320 if len(self) == 0:
321 return []
322 else:
323 return self[0]._processing_order(iter(self))
324
325
326 @dns.immutable.immutable
327 class ImmutableRdataset(Rdataset):
328
329 """An immutable DNS rdataset."""
330
331 _clone_class = Rdataset
332
333 def __init__(self, rdataset):
334 """Create an immutable rdataset from the specified rdataset."""
335
336 super().__init__(rdataset.rdclass, rdataset.rdtype, rdataset.covers,
337 rdataset.ttl)
338 self.items = dns.immutable.Dict(rdataset.items)
339
340 def update_ttl(self, ttl):
341 raise TypeError('immutable')
342
343 def add(self, rd, ttl=None):
344 raise TypeError('immutable')
345
346 def union_update(self, other):
347 raise TypeError('immutable')
348
349 def intersection_update(self, other):
350 raise TypeError('immutable')
351
352 def update(self, other):
353 raise TypeError('immutable')
354
355 def __delitem__(self, i):
356 raise TypeError('immutable')
357
358 def __ior__(self, other):
359 raise TypeError('immutable')
360
361 def __iand__(self, other):
362 raise TypeError('immutable')
363
364 def __iadd__(self, other):
365 raise TypeError('immutable')
366
367 def __isub__(self, other):
368 raise TypeError('immutable')
369
370 def clear(self):
371 raise TypeError('immutable')
372
373 def __copy__(self):
374 return ImmutableRdataset(super().copy())
375
376 def copy(self):
377 return ImmutableRdataset(super().copy())
378
379 def union(self, other):
380 return ImmutableRdataset(super().union(other))
381
382 def intersection(self, other):
383 return ImmutableRdataset(super().intersection(other))
384
385 def difference(self, other):
386 return ImmutableRdataset(super().difference(other))
387
388
389 def from_text_list(rdclass, rdtype, ttl, text_rdatas, idna_codec=None,
390 origin=None, relativize=True, relativize_to=None):
301391 """Create an rdataset with the specified class, type, and TTL, and with
302392 the specified list of rdatas in text format.
303393
304394 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
305395 encoder/decoder to use; if ``None``, the default IDNA 2003
306396 encoder/decoder is used.
397
398 *origin*, a ``dns.name.Name`` (or ``None``), the
399 origin to use for relative names.
400
401 *relativize*, a ``bool``. If true, name will be relativized.
402
403 *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use
404 when relativizing names. If not set, the *origin* value will be used.
307405
308406 Returns a ``dns.rdataset.Rdataset`` object.
309407 """
313411 r = Rdataset(rdclass, rdtype)
314412 r.update_ttl(ttl)
315413 for t in text_rdatas:
316 rd = dns.rdata.from_text(r.rdclass, r.rdtype, t, idna_codec=idna_codec)
414 rd = dns.rdata.from_text(r.rdclass, r.rdtype, t, origin, relativize,
415 relativize_to, idna_codec)
317416 r.add(rd)
318417 return r
319418
7171 NSEC3 = 50
7272 NSEC3PARAM = 51
7373 TLSA = 52
74 SMIMEA = 53
7475 HIP = 55
7576 NINFO = 56
7677 CDS = 59
7778 CDNSKEY = 60
7879 OPENPGPKEY = 61
7980 CSYNC = 62
81 ZONEMD = 63
82 SVCB = 64
83 HTTPS = 65
8084 SPF = 99
8185 UNSPEC = 103
8286 EUI48 = 108
9195 URI = 256
9296 CAA = 257
9397 AVC = 258
94 AMTRELAY = 259
98 AMTRELAY = 260
9599 TA = 32768
96100 DLV = 32769
97101
113117
114118 _registered_by_text = {}
115119 _registered_by_value = {}
116
117 globals().update(RdataType.__members__)
118120
119121 _metatypes = {RdataType.OPT}
120122
218220 _registered_by_value[rdtype] = rdtype_text
219221 if is_singleton:
220222 _singletons.add(rdtype)
223
224 ### BEGIN generated RdataType constants
225
226 TYPE0 = RdataType.TYPE0
227 NONE = RdataType.NONE
228 A = RdataType.A
229 NS = RdataType.NS
230 MD = RdataType.MD
231 MF = RdataType.MF
232 CNAME = RdataType.CNAME
233 SOA = RdataType.SOA
234 MB = RdataType.MB
235 MG = RdataType.MG
236 MR = RdataType.MR
237 NULL = RdataType.NULL
238 WKS = RdataType.WKS
239 PTR = RdataType.PTR
240 HINFO = RdataType.HINFO
241 MINFO = RdataType.MINFO
242 MX = RdataType.MX
243 TXT = RdataType.TXT
244 RP = RdataType.RP
245 AFSDB = RdataType.AFSDB
246 X25 = RdataType.X25
247 ISDN = RdataType.ISDN
248 RT = RdataType.RT
249 NSAP = RdataType.NSAP
250 NSAP_PTR = RdataType.NSAP_PTR
251 SIG = RdataType.SIG
252 KEY = RdataType.KEY
253 PX = RdataType.PX
254 GPOS = RdataType.GPOS
255 AAAA = RdataType.AAAA
256 LOC = RdataType.LOC
257 NXT = RdataType.NXT
258 SRV = RdataType.SRV
259 NAPTR = RdataType.NAPTR
260 KX = RdataType.KX
261 CERT = RdataType.CERT
262 A6 = RdataType.A6
263 DNAME = RdataType.DNAME
264 OPT = RdataType.OPT
265 APL = RdataType.APL
266 DS = RdataType.DS
267 SSHFP = RdataType.SSHFP
268 IPSECKEY = RdataType.IPSECKEY
269 RRSIG = RdataType.RRSIG
270 NSEC = RdataType.NSEC
271 DNSKEY = RdataType.DNSKEY
272 DHCID = RdataType.DHCID
273 NSEC3 = RdataType.NSEC3
274 NSEC3PARAM = RdataType.NSEC3PARAM
275 TLSA = RdataType.TLSA
276 SMIMEA = RdataType.SMIMEA
277 HIP = RdataType.HIP
278 NINFO = RdataType.NINFO
279 CDS = RdataType.CDS
280 CDNSKEY = RdataType.CDNSKEY
281 OPENPGPKEY = RdataType.OPENPGPKEY
282 CSYNC = RdataType.CSYNC
283 ZONEMD = RdataType.ZONEMD
284 SVCB = RdataType.SVCB
285 HTTPS = RdataType.HTTPS
286 SPF = RdataType.SPF
287 UNSPEC = RdataType.UNSPEC
288 EUI48 = RdataType.EUI48
289 EUI64 = RdataType.EUI64
290 TKEY = RdataType.TKEY
291 TSIG = RdataType.TSIG
292 IXFR = RdataType.IXFR
293 AXFR = RdataType.AXFR
294 MAILB = RdataType.MAILB
295 MAILA = RdataType.MAILA
296 ANY = RdataType.ANY
297 URI = RdataType.URI
298 CAA = RdataType.CAA
299 AVC = RdataType.AVC
300 AMTRELAY = RdataType.AMTRELAY
301 TA = RdataType.TA
302 DLV = RdataType.DLV
303
304 ### END generated RdataType constants
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.mxbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class AFSDB(dns.rdtypes.mxbase.UncompressedDowncasingMX):
2123
2224 """AFSDB record"""
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdtypes.util
2122
2223
2324 class Relay(dns.rdtypes.util.Gateway):
2425 name = 'AMTRELAY relay'
2526
27 @property
28 def relay(self):
29 return self.gateway
30
31
32 @dns.immutable.immutable
2633 class AMTRELAY(dns.rdata.Rdata):
2734
2835 """AMTRELAY record"""
3441 def __init__(self, rdclass, rdtype, precedence, discovery_optional,
3542 relay_type, relay):
3643 super().__init__(rdclass, rdtype)
37 Relay(relay_type, relay).check()
38 object.__setattr__(self, 'precedence', precedence)
39 object.__setattr__(self, 'discovery_optional', discovery_optional)
40 object.__setattr__(self, 'relay_type', relay_type)
41 object.__setattr__(self, 'relay', relay)
44 relay = Relay(relay_type, relay)
45 self.precedence = self._as_uint8(precedence)
46 self.discovery_optional = self._as_bool(discovery_optional)
47 self.relay_type = relay.type
48 self.relay = relay.relay
4249
4350 def to_text(self, origin=None, relativize=True, **kw):
4451 relay = Relay(self.relay_type, self.relay).to_text(origin, relativize)
5663 relay_type = tok.get_uint8()
5764 if relay_type > 0x7f:
5865 raise dns.exception.SyntaxError('expecting an integer <= 127')
59 relay = Relay(relay_type).from_text(tok, origin, relativize,
60 relativize_to)
66 relay = Relay.from_text(relay_type, tok, origin, relativize,
67 relativize_to)
6168 return cls(rdclass, rdtype, precedence, discovery_optional, relay_type,
62 relay)
69 relay.relay)
6370
6471 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
6572 relay_type = self.relay_type | (self.discovery_optional << 7)
7380 (precedence, relay_type) = parser.get_struct('!BB')
7481 discovery_optional = bool(relay_type >> 7)
7582 relay_type &= 0x7f
76 relay = Relay(relay_type).from_wire_parser(parser, origin)
83 relay = Relay.from_wire_parser(relay_type, parser, origin)
7784 return cls(rdclass, rdtype, precedence, discovery_optional, relay_type,
78 relay)
85 relay.relay)
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.txtbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class AVC(dns.rdtypes.txtbase.TXTBase):
2123
2224 """AVC record"""
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122 import dns.tokenizer
2223
2324
25 @dns.immutable.immutable
2426 class CAA(dns.rdata.Rdata):
2527
2628 """CAA (Certification Authority Authorization) record"""
3133
3234 def __init__(self, rdclass, rdtype, flags, tag, value):
3335 super().__init__(rdclass, rdtype)
34 object.__setattr__(self, 'flags', flags)
35 object.__setattr__(self, 'tag', tag)
36 object.__setattr__(self, 'value', value)
36 self.flags = self._as_uint8(flags)
37 self.tag = self._as_bytes(tag, True, 255)
38 if not tag.isalnum():
39 raise ValueError("tag is not alphanumeric")
40 self.value = self._as_bytes(value)
3741
3842 def to_text(self, origin=None, relativize=True, **kw):
3943 return '%u %s "%s"' % (self.flags,
4549 relativize_to=None):
4650 flags = tok.get_uint8()
4751 tag = tok.get_string().encode()
48 if len(tag) > 255:
49 raise dns.exception.SyntaxError("tag too long")
50 if not tag.isalnum():
51 raise dns.exception.SyntaxError("tag is not alphanumeric")
5252 value = tok.get_string().encode()
5353 return cls(rdclass, rdtype, flags, tag, value)
5454
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.dnskeybase
18 import dns.immutable
19
20 # pylint: disable=unused-import
1821 from dns.rdtypes.dnskeybase import SEP, REVOKE, ZONE # noqa: F401
22 # pylint: enable=unused-import
1923
20
24 @dns.immutable.immutable
2125 class CDNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase):
2226
2327 """CDNSKEY record"""
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.dsbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class CDS(dns.rdtypes.dsbase.DSBase):
2123
2224 """CDS record"""
1818 import base64
1919
2020 import dns.exception
21 import dns.immutable
2122 import dns.dnssec
2223 import dns.rdata
2324 import dns.tokenizer
5354 return str(what)
5455
5556
57 @dns.immutable.immutable
5658 class CERT(dns.rdata.Rdata):
5759
5860 """CERT record"""
6466 def __init__(self, rdclass, rdtype, certificate_type, key_tag, algorithm,
6567 certificate):
6668 super().__init__(rdclass, rdtype)
67 object.__setattr__(self, 'certificate_type', certificate_type)
68 object.__setattr__(self, 'key_tag', key_tag)
69 object.__setattr__(self, 'algorithm', algorithm)
70 object.__setattr__(self, 'certificate', certificate)
69 self.certificate_type = self._as_uint16(certificate_type)
70 self.key_tag = self._as_uint16(key_tag)
71 self.algorithm = self._as_uint8(algorithm)
72 self.certificate = self._as_bytes(certificate)
7173
7274 def to_text(self, origin=None, relativize=True, **kw):
7375 certificate_type = _ctype_to_text(self.certificate_type)
7476 return "%s %d %s %s" % (certificate_type, self.key_tag,
7577 dns.dnssec.algorithm_to_text(self.algorithm),
76 dns.rdata._base64ify(self.certificate))
78 dns.rdata._base64ify(self.certificate, **kw))
7779
7880 @classmethod
7981 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
8183 certificate_type = _ctype_from_text(tok.get_string())
8284 key_tag = tok.get_uint16()
8385 algorithm = dns.dnssec.algorithm_from_text(tok.get_string())
84 if algorithm < 0 or algorithm > 255:
85 raise dns.exception.SyntaxError("bad algorithm type")
8686 b64 = tok.concatenate_remaining_identifiers().encode()
8787 certificate = base64.b64decode(b64)
8888 return cls(rdclass, rdtype, certificate_type, key_tag,
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.nsbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class CNAME(dns.rdtypes.nsbase.NSBase):
2123
2224 """CNAME record
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122 import dns.rdatatype
2223 import dns.name
2324 import dns.rdtypes.util
2425
2526
27 @dns.immutable.immutable
2628 class Bitmap(dns.rdtypes.util.Bitmap):
2729 type_name = 'CSYNC'
2830
2931
32 @dns.immutable.immutable
3033 class CSYNC(dns.rdata.Rdata):
3134
3235 """CSYNC record"""
3538
3639 def __init__(self, rdclass, rdtype, serial, flags, windows):
3740 super().__init__(rdclass, rdtype)
38 object.__setattr__(self, 'serial', serial)
39 object.__setattr__(self, 'flags', flags)
40 object.__setattr__(self, 'windows', dns.rdata._constify(windows))
41 self.serial = self._as_uint32(serial)
42 self.flags = self._as_uint16(flags)
43 if not isinstance(windows, Bitmap):
44 windows = Bitmap(windows)
45 self.windows = tuple(windows.windows)
4146
4247 def to_text(self, origin=None, relativize=True, **kw):
4348 text = Bitmap(self.windows).to_text()
4853 relativize_to=None):
4954 serial = tok.get_uint32()
5055 flags = tok.get_uint16()
51 windows = Bitmap().from_text(tok)
52 return cls(rdclass, rdtype, serial, flags, windows)
56 bitmap = Bitmap.from_text(tok)
57 return cls(rdclass, rdtype, serial, flags, bitmap)
5358
5459 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
5560 file.write(struct.pack('!IH', self.serial, self.flags))
5863 @classmethod
5964 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
6065 (serial, flags) = parser.get_struct("!IH")
61 windows = Bitmap().from_wire_parser(parser)
62 return cls(rdclass, rdtype, serial, flags, windows)
66 bitmap = Bitmap.from_wire_parser(parser)
67 return cls(rdclass, rdtype, serial, flags, bitmap)
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.dsbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class DLV(dns.rdtypes.dsbase.DSBase):
2123
2224 """DLV record"""
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.nsbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class DNAME(dns.rdtypes.nsbase.UncompressedNS):
2123
2224 """DNAME record"""
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.dnskeybase
18 import dns.immutable
19
20 # pylint: disable=unused-import
1821 from dns.rdtypes.dnskeybase import SEP, REVOKE, ZONE # noqa: F401
22 # pylint: enable=unused-import
1923
20
24 @dns.immutable.immutable
2125 class DNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase):
2226
2327 """DNSKEY record"""
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.dsbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class DS(dns.rdtypes.dsbase.DSBase):
2123
2224 """DS record"""
1616 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1717
1818 import dns.rdtypes.euibase
19 import dns.immutable
1920
2021
22 @dns.immutable.immutable
2123 class EUI48(dns.rdtypes.euibase.EUIBase):
2224
2325 """EUI48 record"""
1616 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1717
1818 import dns.rdtypes.euibase
19 import dns.immutable
1920
2021
22 @dns.immutable.immutable
2123 class EUI64(dns.rdtypes.euibase.EUIBase):
2224
2325 """EUI64 record"""
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122 import dns.tokenizer
2223
4041 raise dns.exception.FormError
4142
4243
43 def _sanitize(value):
44 if isinstance(value, str):
45 return value.encode()
46 return value
47
48
44 @dns.immutable.immutable
4945 class GPOS(dns.rdata.Rdata):
5046
5147 """GPOS record"""
6561 if isinstance(altitude, float) or \
6662 isinstance(altitude, int):
6763 altitude = str(altitude)
68 latitude = _sanitize(latitude)
69 longitude = _sanitize(longitude)
70 altitude = _sanitize(altitude)
64 latitude = self._as_bytes(latitude, True, 255)
65 longitude = self._as_bytes(longitude, True, 255)
66 altitude = self._as_bytes(altitude, True, 255)
7167 _validate_float_string(latitude)
7268 _validate_float_string(longitude)
7369 _validate_float_string(altitude)
74 object.__setattr__(self, 'latitude', latitude)
75 object.__setattr__(self, 'longitude', longitude)
76 object.__setattr__(self, 'altitude', altitude)
70 self.latitude = latitude
71 self.longitude = longitude
72 self.altitude = altitude
7773 flat = self.float_latitude
7874 if flat < -90.0 or flat > 90.0:
7975 raise dns.exception.FormError('bad latitude')
9288 latitude = tok.get_string()
9389 longitude = tok.get_string()
9490 altitude = tok.get_string()
95 tok.get_eol()
9691 return cls(rdclass, rdtype, latitude, longitude, altitude)
9792
9893 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122 import dns.tokenizer
2223
2324
25 @dns.immutable.immutable
2426 class HINFO(dns.rdata.Rdata):
2527
2628 """HINFO record"""
3133
3234 def __init__(self, rdclass, rdtype, cpu, os):
3335 super().__init__(rdclass, rdtype)
34 if isinstance(cpu, str):
35 object.__setattr__(self, 'cpu', cpu.encode())
36 else:
37 object.__setattr__(self, 'cpu', cpu)
38 if isinstance(os, str):
39 object.__setattr__(self, 'os', os.encode())
40 else:
41 object.__setattr__(self, 'os', os)
36 self.cpu = self._as_bytes(cpu, True, 255)
37 self.os = self._as_bytes(os, True, 255)
4238
4339 def to_text(self, origin=None, relativize=True, **kw):
4440 return '"{}" "{}"'.format(dns.rdata._escapify(self.cpu),
4945 relativize_to=None):
5046 cpu = tok.get_string(max_length=255)
5147 os = tok.get_string(max_length=255)
52 tok.get_eol()
5348 return cls(rdclass, rdtype, cpu, os)
5449
5550 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
1919 import binascii
2020
2121 import dns.exception
22 import dns.immutable
2223 import dns.rdata
2324 import dns.rdatatype
2425
2526
27 @dns.immutable.immutable
2628 class HIP(dns.rdata.Rdata):
2729
2830 """HIP record"""
3335
3436 def __init__(self, rdclass, rdtype, hit, algorithm, key, servers):
3537 super().__init__(rdclass, rdtype)
36 object.__setattr__(self, 'hit', hit)
37 object.__setattr__(self, 'algorithm', algorithm)
38 object.__setattr__(self, 'key', key)
39 object.__setattr__(self, 'servers', dns.rdata._constify(servers))
38 self.hit = self._as_bytes(hit, True, 255)
39 self.algorithm = self._as_uint8(algorithm)
40 self.key = self._as_bytes(key, True)
41 self.servers = self._as_tuple(servers, self._as_name)
4042
4143 def to_text(self, origin=None, relativize=True, **kw):
4244 hit = binascii.hexlify(self.hit).decode()
5456 relativize_to=None):
5557 algorithm = tok.get_uint8()
5658 hit = binascii.unhexlify(tok.get_string().encode())
57 if len(hit) > 255:
58 raise dns.exception.SyntaxError("HIT too long")
5959 key = base64.b64decode(tok.get_string().encode())
6060 servers = []
61 while 1:
62 token = tok.get()
63 if token.is_eol_or_eof():
64 break
61 for token in tok.get_remaining():
6562 server = tok.as_name(token, origin, relativize, relativize_to)
6663 servers.append(server)
6764 return cls(rdclass, rdtype, hit, algorithm, key, servers)
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122 import dns.tokenizer
2223
2324
25 @dns.immutable.immutable
2426 class ISDN(dns.rdata.Rdata):
2527
2628 """ISDN record"""
3133
3234 def __init__(self, rdclass, rdtype, address, subaddress):
3335 super().__init__(rdclass, rdtype)
34 if isinstance(address, str):
35 object.__setattr__(self, 'address', address.encode())
36 else:
37 object.__setattr__(self, 'address', address)
38 if isinstance(address, str):
39 object.__setattr__(self, 'subaddress', subaddress.encode())
40 else:
41 object.__setattr__(self, 'subaddress', subaddress)
36 self.address = self._as_bytes(address, True, 255)
37 self.subaddress = self._as_bytes(subaddress, True, 255)
4238
4339 def to_text(self, origin=None, relativize=True, **kw):
4440 if self.subaddress:
5147 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
5248 relativize_to=None):
5349 address = tok.get_string()
54 t = tok.get()
55 if not t.is_eol_or_eof():
56 tok.unget(t)
57 subaddress = tok.get_string()
50 tokens = tok.get_remaining(max_tokens=1)
51 if len(tokens) >= 1:
52 subaddress = tokens[0].unescape().value
5853 else:
59 tok.unget(t)
6054 subaddress = ''
61 tok.get_eol()
6255 return cls(rdclass, rdtype, address, subaddress)
6356
6457 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122
2223
3334 _MAX_LONGITUDE = 0x80000000 + 180 * 3600000
3435 _MIN_LONGITUDE = 0x80000000 - 180 * 3600000
3536
36 # pylint complains about division since we don't have a from __future__ for
37 # it, but we don't care about python 2 warnings, so turn them off.
38 #
39 # pylint: disable=old-division
4037
4138 def _exponent_of(what, desc):
4239 if what == 0:
4340 return 0
4441 exp = None
4542 for (i, pow) in enumerate(_pows):
46 if what // pow == 0:
43 if what < pow:
4744 exp = i - 1
4845 break
4946 if exp is None or exp < 0:
9390 return base * pow(10, exponent)
9491
9592
93 def _check_coordinate_list(value, low, high):
94 if value[0] < low or value[0] > high:
95 raise ValueError(f'not in range [{low}, {high}]')
96 if value[1] < 0 or value[1] > 59:
97 raise ValueError('bad minutes value')
98 if value[2] < 0 or value[2] > 59:
99 raise ValueError('bad seconds value')
100 if value[3] < 0 or value[3] > 999:
101 raise ValueError('bad milliseconds value')
102 if value[4] != 1 and value[4] != -1:
103 raise ValueError('bad hemisphere value')
104
105
106 @dns.immutable.immutable
96107 class LOC(dns.rdata.Rdata):
97108
98109 """LOC record"""
118129 latitude = float(latitude)
119130 if isinstance(latitude, float):
120131 latitude = _float_to_tuple(latitude)
121 object.__setattr__(self, 'latitude', dns.rdata._constify(latitude))
132 _check_coordinate_list(latitude, -90, 90)
133 self.latitude = tuple(latitude)
122134 if isinstance(longitude, int):
123135 longitude = float(longitude)
124136 if isinstance(longitude, float):
125137 longitude = _float_to_tuple(longitude)
126 object.__setattr__(self, 'longitude', dns.rdata._constify(longitude))
127 object.__setattr__(self, 'altitude', float(altitude))
128 object.__setattr__(self, 'size', float(size))
129 object.__setattr__(self, 'horizontal_precision', float(hprec))
130 object.__setattr__(self, 'vertical_precision', float(vprec))
138 _check_coordinate_list(longitude, -180, 180)
139 self.longitude = tuple(longitude)
140 self.altitude = float(altitude)
141 self.size = float(size)
142 self.horizontal_precision = float(hprec)
143 self.vertical_precision = float(vprec)
131144
132145 def to_text(self, origin=None, relativize=True, **kw):
133146 if self.latitude[4] > 0:
166179 vprec = _default_vprec
167180
168181 latitude[0] = tok.get_int()
169 if latitude[0] > 90:
170 raise dns.exception.SyntaxError('latitude >= 90')
171182 t = tok.get_string()
172183 if t.isdigit():
173184 latitude[1] = int(t)
174 if latitude[1] >= 60:
175 raise dns.exception.SyntaxError('latitude minutes >= 60')
176185 t = tok.get_string()
177186 if '.' in t:
178187 (seconds, milliseconds) = t.split('.')
180189 raise dns.exception.SyntaxError(
181190 'bad latitude seconds value')
182191 latitude[2] = int(seconds)
183 if latitude[2] >= 60:
184 raise dns.exception.SyntaxError('latitude seconds >= 60')
185192 l = len(milliseconds)
186193 if l == 0 or l > 3 or not milliseconds.isdigit():
187194 raise dns.exception.SyntaxError(
203210 raise dns.exception.SyntaxError('bad latitude hemisphere value')
204211
205212 longitude[0] = tok.get_int()
206 if longitude[0] > 180:
207 raise dns.exception.SyntaxError('longitude > 180')
208213 t = tok.get_string()
209214 if t.isdigit():
210215 longitude[1] = int(t)
211 if longitude[1] >= 60:
212 raise dns.exception.SyntaxError('longitude minutes >= 60')
213216 t = tok.get_string()
214217 if '.' in t:
215218 (seconds, milliseconds) = t.split('.')
217220 raise dns.exception.SyntaxError(
218221 'bad longitude seconds value')
219222 longitude[2] = int(seconds)
220 if longitude[2] >= 60:
221 raise dns.exception.SyntaxError('longitude seconds >= 60')
222223 l = len(milliseconds)
223224 if l == 0 or l > 3 or not milliseconds.isdigit():
224225 raise dns.exception.SyntaxError(
244245 t = t[0: -1]
245246 altitude = float(t) * 100.0 # m -> cm
246247
247 token = tok.get().unescape()
248 if not token.is_eol_or_eof():
249 value = token.value
248 tokens = tok.get_remaining(max_tokens=3)
249 if len(tokens) >= 1:
250 value = tokens[0].unescape().value
250251 if value[-1] == 'm':
251252 value = value[0: -1]
252253 size = float(value) * 100.0 # m -> cm
253 token = tok.get().unescape()
254 if not token.is_eol_or_eof():
255 value = token.value
254 if len(tokens) >= 2:
255 value = tokens[1].unescape().value
256256 if value[-1] == 'm':
257257 value = value[0: -1]
258258 hprec = float(value) * 100.0 # m -> cm
259 token = tok.get().unescape()
260 if not token.is_eol_or_eof():
261 value = token.value
259 if len(tokens) >= 3:
260 value = tokens[2].unescape().value
262261 if value[-1] == 'm':
263262 value = value[0: -1]
264263 vprec = float(value) * 100.0 # m -> cm
265 tok.get_eol()
266264
267265 # Try encoding these now so we raise if they are bad
268266 _encode_size(size, "size")
295293 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
296294 (version, size, hprec, vprec, latitude, longitude, altitude) = \
297295 parser.get_struct("!BBBBIII")
296 if version != 0:
297 raise dns.exception.FormError("LOC version not zero")
298298 if latitude < _MIN_LATITUDE or latitude > _MAX_LATITUDE:
299299 raise dns.exception.FormError("bad latitude")
300300 if latitude > 0x80000000:
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.mxbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class MX(dns.rdtypes.mxbase.MXBase):
2123
2224 """MX record"""
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.txtbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class NINFO(dns.rdtypes.txtbase.TXTBase):
2123
2224 """NINFO record"""
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.nsbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class NS(dns.rdtypes.nsbase.NSBase):
2123
2224 """NS record"""
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.exception
18 import dns.immutable
1819 import dns.rdata
1920 import dns.rdatatype
2021 import dns.name
2122 import dns.rdtypes.util
2223
2324
25 @dns.immutable.immutable
2426 class Bitmap(dns.rdtypes.util.Bitmap):
2527 type_name = 'NSEC'
2628
2729
30 @dns.immutable.immutable
2831 class NSEC(dns.rdata.Rdata):
2932
3033 """NSEC record"""
3336
3437 def __init__(self, rdclass, rdtype, next, windows):
3538 super().__init__(rdclass, rdtype)
36 object.__setattr__(self, 'next', next)
37 object.__setattr__(self, 'windows', dns.rdata._constify(windows))
39 self.next = self._as_name(next)
40 if not isinstance(windows, Bitmap):
41 windows = Bitmap(windows)
42 self.windows = tuple(windows.windows)
3843
3944 def to_text(self, origin=None, relativize=True, **kw):
4045 next = self.next.choose_relativity(origin, relativize)
4550 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
4651 relativize_to=None):
4752 next = tok.get_name(origin, relativize, relativize_to)
48 windows = Bitmap().from_text(tok)
53 windows = Bitmap.from_text(tok)
4954 return cls(rdclass, rdtype, next, windows)
5055
5156 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
57 # Note that NSEC downcasing, originally mandated by RFC 4034
58 # section 6.2 was removed by RFC 6840 section 5.1.
5259 self.next.to_wire(file, None, origin, False)
5360 Bitmap(self.windows).to_wire(file)
5461
5562 @classmethod
5663 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
5764 next = parser.get_name(origin)
58 windows = Bitmap().from_wire_parser(parser)
59 return cls(rdclass, rdtype, next, windows)
65 bitmap = Bitmap.from_wire_parser(parser)
66 return cls(rdclass, rdtype, next, bitmap)
1919 import struct
2020
2121 import dns.exception
22 import dns.immutable
2223 import dns.rdata
2324 import dns.rdatatype
2425 import dns.rdtypes.util
3637 OPTOUT = 1
3738
3839
40 @dns.immutable.immutable
3941 class Bitmap(dns.rdtypes.util.Bitmap):
4042 type_name = 'NSEC3'
4143
4244
45 @dns.immutable.immutable
4346 class NSEC3(dns.rdata.Rdata):
4447
4548 """NSEC3 record"""
4952 def __init__(self, rdclass, rdtype, algorithm, flags, iterations, salt,
5053 next, windows):
5154 super().__init__(rdclass, rdtype)
52 object.__setattr__(self, 'algorithm', algorithm)
53 object.__setattr__(self, 'flags', flags)
54 object.__setattr__(self, 'iterations', iterations)
55 if isinstance(salt, str):
56 object.__setattr__(self, 'salt', salt.encode())
57 else:
58 object.__setattr__(self, 'salt', salt)
59 object.__setattr__(self, 'next', next)
60 object.__setattr__(self, 'windows', dns.rdata._constify(windows))
55 self.algorithm = self._as_uint8(algorithm)
56 self.flags = self._as_uint8(flags)
57 self.iterations = self._as_uint16(iterations)
58 self.salt = self._as_bytes(salt, True, 255)
59 self.next = self._as_bytes(next, True, 255)
60 if not isinstance(windows, Bitmap):
61 windows = Bitmap(windows)
62 self.windows = tuple(windows.windows)
6163
6264 def to_text(self, origin=None, relativize=True, **kw):
6365 next = base64.b32encode(self.next).translate(
8486 next = tok.get_string().encode(
8587 'ascii').upper().translate(b32_hex_to_normal)
8688 next = base64.b32decode(next)
87 windows = Bitmap().from_text(tok)
89 bitmap = Bitmap.from_text(tok)
8890 return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next,
89 windows)
91 bitmap)
9092
9193 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
9294 l = len(self.salt)
103105 (algorithm, flags, iterations) = parser.get_struct('!BBH')
104106 salt = parser.get_counted_bytes()
105107 next = parser.get_counted_bytes()
106 windows = Bitmap().from_wire_parser(parser)
108 bitmap = Bitmap.from_wire_parser(parser)
107109 return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next,
108 windows)
110 bitmap)
1818 import binascii
1919
2020 import dns.exception
21 import dns.immutable
2122 import dns.rdata
2223
2324
25 @dns.immutable.immutable
2426 class NSEC3PARAM(dns.rdata.Rdata):
2527
2628 """NSEC3PARAM record"""
2931
3032 def __init__(self, rdclass, rdtype, algorithm, flags, iterations, salt):
3133 super().__init__(rdclass, rdtype)
32 object.__setattr__(self, 'algorithm', algorithm)
33 object.__setattr__(self, 'flags', flags)
34 object.__setattr__(self, 'iterations', iterations)
35 if isinstance(salt, str):
36 object.__setattr__(self, 'salt', salt.encode())
37 else:
38 object.__setattr__(self, 'salt', salt)
34 self.algorithm = self._as_uint8(algorithm)
35 self.flags = self._as_uint8(flags)
36 self.iterations = self._as_uint16(iterations)
37 self.salt = self._as_bytes(salt, True, 255)
3938
4039 def to_text(self, origin=None, relativize=True, **kw):
4140 if self.salt == b'':
5655 salt = ''
5756 else:
5857 salt = binascii.unhexlify(salt.encode())
59 tok.get_eol()
6058 return cls(rdclass, rdtype, algorithm, flags, iterations, salt)
6159
6260 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
1717 import base64
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122 import dns.tokenizer
2223
24 @dns.immutable.immutable
2325 class OPENPGPKEY(dns.rdata.Rdata):
2426
2527 """OPENPGPKEY record"""
2830
2931 def __init__(self, rdclass, rdtype, key):
3032 super().__init__(rdclass, rdtype)
31 object.__setattr__(self, 'key', key)
33 self.key = self._as_bytes(key)
3234
3335 def to_text(self, origin=None, relativize=True, **kw):
34 return dns.rdata._base64ify(self.key)
36 return dns.rdata._base64ify(self.key, chunksize=None, **kw)
3537
3638 @classmethod
3739 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
1717 import struct
1818
1919 import dns.edns
20 import dns.immutable
2021 import dns.exception
2122 import dns.rdata
2223
2324
25 # We don't implement from_text, and that's ok.
26 # pylint: disable=abstract-method
27
28 @dns.immutable.immutable
2429 class OPT(dns.rdata.Rdata):
2530
2631 """OPT record"""
3944 """
4045
4146 super().__init__(rdclass, rdtype)
42 object.__setattr__(self, 'options', dns.rdata._constify(options))
47 def as_option(option):
48 if not isinstance(option, dns.edns.Option):
49 raise ValueError('option is not a dns.edns.option')
50 return option
51 self.options = self._as_tuple(options, as_option)
4352
4453 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
4554 for opt in self.options:
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.nsbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class PTR(dns.rdtypes.nsbase.NSBase):
2123
2224 """PTR record"""
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.exception
18 import dns.immutable
1819 import dns.rdata
1920 import dns.name
2021
2122
23 @dns.immutable.immutable
2224 class RP(dns.rdata.Rdata):
2325
2426 """RP record"""
2931
3032 def __init__(self, rdclass, rdtype, mbox, txt):
3133 super().__init__(rdclass, rdtype)
32 object.__setattr__(self, 'mbox', mbox)
33 object.__setattr__(self, 'txt', txt)
34 self.mbox = self._as_name(mbox)
35 self.txt = self._as_name(txt)
3436
3537 def to_text(self, origin=None, relativize=True, **kw):
3638 mbox = self.mbox.choose_relativity(origin, relativize)
4244 relativize_to=None):
4345 mbox = tok.get_name(origin, relativize, relativize_to)
4446 txt = tok.get_name(origin, relativize, relativize_to)
45 tok.get_eol()
4647 return cls(rdclass, rdtype, mbox, txt)
4748
4849 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
2020 import time
2121
2222 import dns.dnssec
23 import dns.immutable
2324 import dns.exception
2425 import dns.rdata
2526 import dns.rdatatype
4950 return time.strftime('%Y%m%d%H%M%S', time.gmtime(what))
5051
5152
53 @dns.immutable.immutable
5254 class RRSIG(dns.rdata.Rdata):
5355
5456 """RRSIG record"""
6163 original_ttl, expiration, inception, key_tag, signer,
6264 signature):
6365 super().__init__(rdclass, rdtype)
64 object.__setattr__(self, 'type_covered', type_covered)
65 object.__setattr__(self, 'algorithm', algorithm)
66 object.__setattr__(self, 'labels', labels)
67 object.__setattr__(self, 'original_ttl', original_ttl)
68 object.__setattr__(self, 'expiration', expiration)
69 object.__setattr__(self, 'inception', inception)
70 object.__setattr__(self, 'key_tag', key_tag)
71 object.__setattr__(self, 'signer', signer)
72 object.__setattr__(self, 'signature', signature)
66 self.type_covered = self._as_rdatatype(type_covered)
67 self.algorithm = dns.dnssec.Algorithm.make(algorithm)
68 self.labels = self._as_uint8(labels)
69 self.original_ttl = self._as_ttl(original_ttl)
70 self.expiration = self._as_uint32(expiration)
71 self.inception = self._as_uint32(inception)
72 self.key_tag = self._as_uint16(key_tag)
73 self.signer = self._as_name(signer)
74 self.signature = self._as_bytes(signature)
7375
7476 def covers(self):
7577 return self.type_covered
8486 posixtime_to_sigtime(self.inception),
8587 self.key_tag,
8688 self.signer.choose_relativity(origin, relativize),
87 dns.rdata._base64ify(self.signature)
89 dns.rdata._base64ify(self.signature, **kw)
8890 )
8991
9092 @classmethod
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.mxbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class RT(dns.rdtypes.mxbase.UncompressedDowncasingMX):
2123
2224 """RT record"""
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import dns.immutable
3 import dns.rdtypes.tlsabase
4
5
6 @dns.immutable.immutable
7 class SMIMEA(dns.rdtypes.tlsabase.TLSABase):
8 """SMIMEA record"""
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122 import dns.name
2223
2324
25 @dns.immutable.immutable
2426 class SOA(dns.rdata.Rdata):
2527
2628 """SOA record"""
3335 def __init__(self, rdclass, rdtype, mname, rname, serial, refresh, retry,
3436 expire, minimum):
3537 super().__init__(rdclass, rdtype)
36 object.__setattr__(self, 'mname', mname)
37 object.__setattr__(self, 'rname', rname)
38 object.__setattr__(self, 'serial', serial)
39 object.__setattr__(self, 'refresh', refresh)
40 object.__setattr__(self, 'retry', retry)
41 object.__setattr__(self, 'expire', expire)
42 object.__setattr__(self, 'minimum', minimum)
38 self.mname = self._as_name(mname)
39 self.rname = self._as_name(rname)
40 self.serial = self._as_uint32(serial)
41 self.refresh = self._as_ttl(refresh)
42 self.retry = self._as_ttl(retry)
43 self.expire = self._as_ttl(expire)
44 self.minimum = self._as_ttl(minimum)
4345
4446 def to_text(self, origin=None, relativize=True, **kw):
4547 mname = self.mname.choose_relativity(origin, relativize)
5860 retry = tok.get_ttl()
5961 expire = tok.get_ttl()
6062 minimum = tok.get_ttl()
61 tok.get_eol()
6263 return cls(rdclass, rdtype, mname, rname, serial, refresh, retry,
6364 expire, minimum)
6465
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.txtbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class SPF(dns.rdtypes.txtbase.TXTBase):
2123
2224 """SPF record"""
1818 import binascii
1919
2020 import dns.rdata
21 import dns.immutable
2122 import dns.rdatatype
2223
2324
25 @dns.immutable.immutable
2426 class SSHFP(dns.rdata.Rdata):
2527
2628 """SSHFP record"""
3234 def __init__(self, rdclass, rdtype, algorithm, fp_type,
3335 fingerprint):
3436 super().__init__(rdclass, rdtype)
35 object.__setattr__(self, 'algorithm', algorithm)
36 object.__setattr__(self, 'fp_type', fp_type)
37 object.__setattr__(self, 'fingerprint', fingerprint)
37 self.algorithm = self._as_uint8(algorithm)
38 self.fp_type = self._as_uint8(fp_type)
39 self.fingerprint = self._as_bytes(fingerprint, True)
3840
3941 def to_text(self, origin=None, relativize=True, **kw):
42 kw = kw.copy()
43 chunksize = kw.pop('chunksize', 128)
4044 return '%d %d %s' % (self.algorithm,
4145 self.fp_type,
4246 dns.rdata._hexify(self.fingerprint,
43 chunksize=128))
47 chunksize=chunksize,
48 **kw))
4449
4550 @classmethod
4651 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 # Copyright (C) 2004-2007, 2009-2011 Nominum, Inc.
3 #
4 # Permission to use, copy, modify, and distribute this software and its
5 # documentation for any purpose with or without fee is hereby granted,
6 # provided that the above copyright notice and this permission notice
7 # appear in all copies.
8 #
9 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
12 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
15 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17 import base64
18 import struct
19
20 import dns.dnssec
21 import dns.immutable
22 import dns.exception
23 import dns.rdata
24
25
26 @dns.immutable.immutable
27 class TKEY(dns.rdata.Rdata):
28
29 """TKEY Record"""
30
31 __slots__ = ['algorithm', 'inception', 'expiration', 'mode', 'error',
32 'key', 'other']
33
34 def __init__(self, rdclass, rdtype, algorithm, inception, expiration,
35 mode, error, key, other=b''):
36 super().__init__(rdclass, rdtype)
37 self.algorithm = self._as_name(algorithm)
38 self.inception = self._as_uint32(inception)
39 self.expiration = self._as_uint32(expiration)
40 self.mode = self._as_uint16(mode)
41 self.error = self._as_uint16(error)
42 self.key = self._as_bytes(key)
43 self.other = self._as_bytes(other)
44
45 def to_text(self, origin=None, relativize=True, **kw):
46 _algorithm = self.algorithm.choose_relativity(origin, relativize)
47 text = '%s %u %u %u %u %s' % (str(_algorithm), self.inception,
48 self.expiration, self.mode, self.error,
49 dns.rdata._base64ify(self.key, 0))
50 if len(self.other) > 0:
51 text += ' %s' % (dns.rdata._base64ify(self.other, 0))
52
53 return text
54
55 @classmethod
56 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
57 relativize_to=None):
58 algorithm = tok.get_name(relativize=False)
59 inception = tok.get_uint32()
60 expiration = tok.get_uint32()
61 mode = tok.get_uint16()
62 error = tok.get_uint16()
63 key_b64 = tok.get_string().encode()
64 key = base64.b64decode(key_b64)
65 other_b64 = tok.concatenate_remaining_identifiers().encode()
66 other = base64.b64decode(other_b64)
67
68 return cls(rdclass, rdtype, algorithm, inception, expiration, mode,
69 error, key, other)
70
71 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
72 self.algorithm.to_wire(file, compress, origin)
73 file.write(struct.pack("!IIHH", self.inception, self.expiration,
74 self.mode, self.error))
75 file.write(struct.pack("!H", len(self.key)))
76 file.write(self.key)
77 file.write(struct.pack("!H", len(self.other)))
78 if len(self.other) > 0:
79 file.write(self.other)
80
81 @classmethod
82 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
83 algorithm = parser.get_name(origin)
84 inception, expiration, mode, error = parser.get_struct("!IIHH")
85 key = parser.get_counted_bytes(2)
86 other = parser.get_counted_bytes(2)
87
88 return cls(rdclass, rdtype, algorithm, inception, expiration, mode,
89 error, key, other)
90
91 # Constants for the mode field - from RFC 2930:
92 # 2.5 The Mode Field
93 #
94 # The mode field specifies the general scheme for key agreement or
95 # the purpose of the TKEY DNS message. Servers and resolvers
96 # supporting this specification MUST implement the Diffie-Hellman key
97 # agreement mode and the key deletion mode for queries. All other
98 # modes are OPTIONAL. A server supporting TKEY that receives a TKEY
99 # request with a mode it does not support returns the BADMODE error.
100 # The following values of the Mode octet are defined, available, or
101 # reserved:
102 #
103 # Value Description
104 # ----- -----------
105 # 0 - reserved, see section 7
106 # 1 server assignment
107 # 2 Diffie-Hellman exchange
108 # 3 GSS-API negotiation
109 # 4 resolver assignment
110 # 5 key deletion
111 # 6-65534 - available, see section 7
112 # 65535 - reserved, see section 7
113 SERVER_ASSIGNMENT = 1
114 DIFFIE_HELLMAN_EXCHANGE = 2
115 GSSAPI_NEGOTIATION = 3
116 RESOLVER_ASSIGNMENT = 4
117 KEY_DELETION = 5
00 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
11
2 # Copyright (C) 2005-2007, 2009-2011 Nominum, Inc.
3 #
4 # Permission to use, copy, modify, and distribute this software and its
5 # documentation for any purpose with or without fee is hereby granted,
6 # provided that the above copyright notice and this permission notice
7 # appear in all copies.
8 #
9 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
12 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
15 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17 import struct
18 import binascii
19
20 import dns.rdata
21 import dns.rdatatype
2 import dns.immutable
3 import dns.rdtypes.tlsabase
224
235
24 class TLSA(dns.rdata.Rdata):
6 @dns.immutable.immutable
7 class TLSA(dns.rdtypes.tlsabase.TLSABase):
258
269 """TLSA record"""
27
28 # see: RFC 6698
29
30 __slots__ = ['usage', 'selector', 'mtype', 'cert']
31
32 def __init__(self, rdclass, rdtype, usage, selector,
33 mtype, cert):
34 super().__init__(rdclass, rdtype)
35 object.__setattr__(self, 'usage', usage)
36 object.__setattr__(self, 'selector', selector)
37 object.__setattr__(self, 'mtype', mtype)
38 object.__setattr__(self, 'cert', cert)
39
40 def to_text(self, origin=None, relativize=True, **kw):
41 return '%d %d %d %s' % (self.usage,
42 self.selector,
43 self.mtype,
44 dns.rdata._hexify(self.cert,
45 chunksize=128))
46
47 @classmethod
48 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
49 relativize_to=None):
50 usage = tok.get_uint8()
51 selector = tok.get_uint8()
52 mtype = tok.get_uint8()
53 cert = tok.concatenate_remaining_identifiers().encode()
54 cert = binascii.unhexlify(cert)
55 return cls(rdclass, rdtype, usage, selector, mtype, cert)
56
57 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
58 header = struct.pack("!BBB", self.usage, self.selector, self.mtype)
59 file.write(header)
60 file.write(self.cert)
61
62 @classmethod
63 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
64 header = parser.get_struct("BBB")
65 cert = parser.get_remaining()
66 return cls(rdclass, rdtype, header[0], header[1], header[2], cert)
1414 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
17 import base64
1718 import struct
1819
1920 import dns.exception
21 import dns.immutable
22 import dns.rcode
2023 import dns.rdata
2124
2225
26 @dns.immutable.immutable
2327 class TSIG(dns.rdata.Rdata):
2428
2529 """TSIG record"""
5155 """
5256
5357 super().__init__(rdclass, rdtype)
54 object.__setattr__(self, 'algorithm', algorithm)
55 object.__setattr__(self, 'time_signed', time_signed)
56 object.__setattr__(self, 'fudge', fudge)
57 object.__setattr__(self, 'mac', dns.rdata._constify(mac))
58 object.__setattr__(self, 'original_id', original_id)
59 object.__setattr__(self, 'error', error)
60 object.__setattr__(self, 'other', dns.rdata._constify(other))
58 self.algorithm = self._as_name(algorithm)
59 self.time_signed = self._as_uint48(time_signed)
60 self.fudge = self._as_uint16(fudge)
61 self.mac = self._as_bytes(mac)
62 self.original_id = self._as_uint16(original_id)
63 self.error = dns.rcode.Rcode.make(error)
64 self.other = self._as_bytes(other)
6165
6266 def to_text(self, origin=None, relativize=True, **kw):
6367 algorithm = self.algorithm.choose_relativity(origin, relativize)
64 return f"{algorithm} {self.fudge} {self.time_signed} " + \
68 error = dns.rcode.to_text(self.error, True)
69 text = f"{algorithm} {self.time_signed} {self.fudge} " + \
6570 f"{len(self.mac)} {dns.rdata._base64ify(self.mac, 0)} " + \
66 f"{self.original_id} {self.error} " + \
67 f"{len(self.other)} {dns.rdata._base64ify(self.other, 0)}"
71 f"{self.original_id} {error} {len(self.other)}"
72 if self.other:
73 text += f" {dns.rdata._base64ify(self.other, 0)}"
74 return text
75
76 @classmethod
77 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
78 relativize_to=None):
79 algorithm = tok.get_name(relativize=False)
80 time_signed = tok.get_uint48()
81 fudge = tok.get_uint16()
82 mac_len = tok.get_uint16()
83 mac = base64.b64decode(tok.get_string())
84 if len(mac) != mac_len:
85 raise SyntaxError('invalid MAC')
86 original_id = tok.get_uint16()
87 error = dns.rcode.from_text(tok.get_string())
88 other_len = tok.get_uint16()
89 if other_len > 0:
90 other = base64.b64decode(tok.get_string())
91 if len(other) != other_len:
92 raise SyntaxError('invalid other data')
93 else:
94 other = b''
95 return cls(rdclass, rdtype, algorithm, time_signed, fudge, mac,
96 original_id, error, other)
6897
6998 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
7099 self.algorithm.to_wire(file, None, origin, False)
80109
81110 @classmethod
82111 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
83 algorithm = parser.get_name(origin)
84 (time_hi, time_lo, fudge) = parser.get_struct('!HIH')
85 time_signed = (time_hi << 32) + time_lo
112 algorithm = parser.get_name()
113 time_signed = parser.get_uint48()
114 fudge = parser.get_uint16()
86115 mac = parser.get_counted_bytes(2)
87116 (original_id, error) = parser.get_struct('!HH')
88117 other = parser.get_counted_bytes(2)
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.txtbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class TXT(dns.rdtypes.txtbase.TXTBase):
2123
2224 """TXT record"""
1818 import struct
1919
2020 import dns.exception
21 import dns.immutable
2122 import dns.rdata
23 import dns.rdtypes.util
2224 import dns.name
2325
2426
27 @dns.immutable.immutable
2528 class URI(dns.rdata.Rdata):
2629
2730 """URI record"""
3235
3336 def __init__(self, rdclass, rdtype, priority, weight, target):
3437 super().__init__(rdclass, rdtype)
35 object.__setattr__(self, 'priority', priority)
36 object.__setattr__(self, 'weight', weight)
37 if len(target) < 1:
38 self.priority = self._as_uint16(priority)
39 self.weight = self._as_uint16(weight)
40 self.target = self._as_bytes(target, True)
41 if len(self.target) == 0:
3842 raise dns.exception.SyntaxError("URI target cannot be empty")
39 if isinstance(target, str):
40 object.__setattr__(self, 'target', target.encode())
41 else:
42 object.__setattr__(self, 'target', target)
4343
4444 def to_text(self, origin=None, relativize=True, **kw):
4545 return '%d %d "%s"' % (self.priority, self.weight,
5353 target = tok.get().unescape()
5454 if not (target.is_quoted_string() or target.is_identifier()):
5555 raise dns.exception.SyntaxError("URI target must be a string")
56 tok.get_eol()
5756 return cls(rdclass, rdtype, priority, weight, target.value)
5857
5958 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
6867 if len(target) == 0:
6968 raise dns.exception.FormError('URI target may not be empty')
7069 return cls(rdclass, rdtype, priority, weight, target)
70
71 def _processing_priority(self):
72 return self.priority
73
74 def _processing_weight(self):
75 return self.weight
76
77 @classmethod
78 def _processing_order(cls, iterable):
79 return dns.rdtypes.util.weighted_processing_order(iterable)
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122 import dns.tokenizer
2223
2324
25 @dns.immutable.immutable
2426 class X25(dns.rdata.Rdata):
2527
2628 """X25 record"""
3133
3234 def __init__(self, rdclass, rdtype, address):
3335 super().__init__(rdclass, rdtype)
34 if isinstance(address, str):
35 object.__setattr__(self, 'address', address.encode())
36 else:
37 object.__setattr__(self, 'address', address)
36 self.address = self._as_bytes(address, True, 255)
3837
3938 def to_text(self, origin=None, relativize=True, **kw):
4039 return '"%s"' % dns.rdata._escapify(self.address)
4342 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
4443 relativize_to=None):
4544 address = tok.get_string()
46 tok.get_eol()
4745 return cls(rdclass, rdtype, address)
4846
4947 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import struct
3 import binascii
4
5 import dns.immutable
6 import dns.rdata
7 import dns.rdatatype
8 import dns.zone
9
10
11 @dns.immutable.immutable
12 class ZONEMD(dns.rdata.Rdata):
13
14 """ZONEMD record"""
15
16 # See RFC 8976
17
18 __slots__ = ['serial', 'scheme', 'hash_algorithm', 'digest']
19
20 def __init__(self, rdclass, rdtype, serial, scheme, hash_algorithm, digest):
21 super().__init__(rdclass, rdtype)
22 self.serial = self._as_uint32(serial)
23 self.scheme = dns.zone.DigestScheme.make(scheme)
24 self.hash_algorithm = dns.zone.DigestHashAlgorithm.make(hash_algorithm)
25 self.digest = self._as_bytes(digest)
26
27 if self.scheme == 0: # reserved, RFC 8976 Sec. 5.2
28 raise ValueError('scheme 0 is reserved')
29 if self.hash_algorithm == 0: # reserved, RFC 8976 Sec. 5.3
30 raise ValueError('hash_algorithm 0 is reserved')
31
32 hasher = dns.zone._digest_hashers.get(self.hash_algorithm)
33 if hasher and hasher().digest_size != len(self.digest):
34 raise ValueError('digest length inconsistent with hash algorithm')
35
36 def to_text(self, origin=None, relativize=True, **kw):
37 kw = kw.copy()
38 chunksize = kw.pop('chunksize', 128)
39 return '%d %d %d %s' % (self.serial, self.scheme, self.hash_algorithm,
40 dns.rdata._hexify(self.digest,
41 chunksize=chunksize,
42 **kw))
43
44 @classmethod
45 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
46 relativize_to=None):
47 serial = tok.get_uint32()
48 scheme = tok.get_uint8()
49 hash_algorithm = tok.get_uint8()
50 digest = tok.concatenate_remaining_identifiers().encode()
51 digest = binascii.unhexlify(digest)
52 return cls(rdclass, rdtype, serial, scheme, hash_algorithm, digest)
53
54 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
55 header = struct.pack("!IBB", self.serial, self.scheme,
56 self.hash_algorithm)
57 file.write(header)
58 file.write(self.digest)
59
60 @classmethod
61 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
62 header = parser.get_struct("!IBB")
63 digest = parser.get_remaining()
64 return cls(rdclass, rdtype, header[0], header[1], header[2], digest)
1818
1919 __all__ = [
2020 'AFSDB',
21 'AMTRELAY',
2122 'AVC',
2223 'CAA',
2324 'CDNSKEY',
3738 'ISDN',
3839 'LOC',
3940 'MX',
41 'NINFO',
4042 'NS',
4143 'NSEC',
4244 'NSEC3',
4749 'RP',
4850 'RRSIG',
4951 'RT',
52 'SMIMEA',
5053 'SOA',
5154 'SPF',
5255 'SSHFP',
56 'TKEY',
5357 'TLSA',
5458 'TSIG',
5559 'TXT',
5660 'URI',
5761 'X25',
62 'ZONEMD',
5863 ]
1414 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
17 import dns.rdtypes.mxbase
1817 import struct
1918
19 import dns.rdtypes.mxbase
20 import dns.immutable
21
22 @dns.immutable.immutable
2023 class A(dns.rdata.Rdata):
2124
2225 """A record for Chaosnet"""
2831
2932 def __init__(self, rdclass, rdtype, domain, address):
3033 super().__init__(rdclass, rdtype)
31 object.__setattr__(self, 'domain', domain)
32 object.__setattr__(self, 'address', address)
34 self.domain = self._as_name(domain)
35 self.address = self._as_uint16(address)
3336
3437 def to_text(self, origin=None, relativize=True, **kw):
3538 domain = self.domain.choose_relativity(origin, relativize)
4043 relativize_to=None):
4144 domain = tok.get_name(origin, relativize, relativize_to)
4245 address = tok.get_uint16(base=8)
43 tok.get_eol()
4446 return cls(rdclass, rdtype, domain, address)
4547
4648 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.exception
18 import dns.immutable
1819 import dns.ipv4
1920 import dns.rdata
2021 import dns.tokenizer
2122
2223
24 @dns.immutable.immutable
2325 class A(dns.rdata.Rdata):
2426
2527 """A record."""
2830
2931 def __init__(self, rdclass, rdtype, address):
3032 super().__init__(rdclass, rdtype)
31 # check that it's OK
32 dns.ipv4.inet_aton(address)
33 object.__setattr__(self, 'address', address)
33 self.address = self._as_ipv4_address(address)
3434
3535 def to_text(self, origin=None, relativize=True, **kw):
3636 return self.address
3939 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
4040 relativize_to=None):
4141 address = tok.get_identifier()
42 tok.get_eol()
4342 return cls(rdclass, rdtype, address)
4443
4544 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
4746
4847 @classmethod
4948 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
50 address = dns.ipv4.inet_ntoa(parser.get_remaining())
49 address = parser.get_remaining()
5150 return cls(rdclass, rdtype, address)
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.exception
18 import dns.immutable
1819 import dns.ipv6
1920 import dns.rdata
2021 import dns.tokenizer
2122
2223
24 @dns.immutable.immutable
2325 class AAAA(dns.rdata.Rdata):
2426
2527 """AAAA record."""
2830
2931 def __init__(self, rdclass, rdtype, address):
3032 super().__init__(rdclass, rdtype)
31 # check that it's OK
32 dns.ipv6.inet_aton(address)
33 object.__setattr__(self, 'address', address)
33 self.address = self._as_ipv6_address(address)
3434
3535 def to_text(self, origin=None, relativize=True, **kw):
3636 return self.address
3939 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
4040 relativize_to=None):
4141 address = tok.get_identifier()
42 tok.get_eol()
4342 return cls(rdclass, rdtype, address)
4443
4544 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
4746
4847 @classmethod
4948 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
50 address = dns.ipv6.inet_ntoa(parser.get_remaining())
49 address = parser.get_remaining()
5150 return cls(rdclass, rdtype, address)
1919 import struct
2020
2121 import dns.exception
22 import dns.immutable
2223 import dns.ipv4
2324 import dns.ipv6
2425 import dns.rdata
2526 import dns.tokenizer
2627
28 @dns.immutable.immutable
2729 class APLItem:
2830
2931 """An APL list item."""
3133 __slots__ = ['family', 'negation', 'address', 'prefix']
3234
3335 def __init__(self, family, negation, address, prefix):
34 self.family = family
35 self.negation = negation
36 self.address = address
37 self.prefix = prefix
36 self.family = dns.rdata.Rdata._as_uint16(family)
37 self.negation = dns.rdata.Rdata._as_bool(negation)
38 if self.family == 1:
39 self.address = dns.rdata.Rdata._as_ipv4_address(address)
40 self.prefix = dns.rdata.Rdata._as_int(prefix, 0, 32)
41 elif self.family == 2:
42 self.address = dns.rdata.Rdata._as_ipv6_address(address)
43 self.prefix = dns.rdata.Rdata._as_int(prefix, 0, 128)
44 else:
45 self.address = dns.rdata.Rdata._as_bytes(address)
46 self.prefix = dns.rdata.Rdata._as_uint8(prefix)
3847
3948 def __str__(self):
4049 if self.negation:
6776 file.write(address)
6877
6978
79 @dns.immutable.immutable
7080 class APL(dns.rdata.Rdata):
7181
7282 """APL record."""
7787
7888 def __init__(self, rdclass, rdtype, items):
7989 super().__init__(rdclass, rdtype)
80 object.__setattr__(self, 'items', dns.rdata._constify(items))
90 for item in items:
91 if not isinstance(item, APLItem):
92 raise ValueError('item not an APLItem')
93 self.items = tuple(items)
8194
8295 def to_text(self, origin=None, relativize=True, **kw):
8396 return ' '.join(map(str, self.items))
8699 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
87100 relativize_to=None):
88101 items = []
89 while True:
90 token = tok.get().unescape()
91 if token.is_eol_or_eof():
92 break
93 item = token.value
102 for token in tok.get_remaining():
103 item = token.unescape().value
94104 if item[0] == '!':
95105 negation = True
96106 item = item[1:]
126136 if header[0] == 1:
127137 if l < 4:
128138 address += b'\x00' * (4 - l)
129 address = dns.ipv4.inet_ntoa(address)
130139 elif header[0] == 2:
131140 if l < 16:
132141 address += b'\x00' * (16 - l)
133 address = dns.ipv6.inet_ntoa(address)
134142 else:
135143 #
136144 # This isn't really right according to the RFC, but it
1717 import base64
1818
1919 import dns.exception
20 import dns.immutable
2021
2122
23 @dns.immutable.immutable
2224 class DHCID(dns.rdata.Rdata):
2325
2426 """DHCID record"""
2931
3032 def __init__(self, rdclass, rdtype, data):
3133 super().__init__(rdclass, rdtype)
32 object.__setattr__(self, 'data', data)
34 self.data = self._as_bytes(data)
3335
3436 def to_text(self, origin=None, relativize=True, **kw):
35 return dns.rdata._base64ify(self.data)
37 return dns.rdata._base64ify(self.data, **kw)
3638
3739 @classmethod
3840 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import dns.rdtypes.svcbbase
3 import dns.immutable
4
5 @dns.immutable.immutable
6 class HTTPS(dns.rdtypes.svcbbase.SVCBBase):
7 """HTTPS record"""
1818 import base64
1919
2020 import dns.exception
21 import dns.immutable
2122 import dns.rdtypes.util
2223
2324
2425 class Gateway(dns.rdtypes.util.Gateway):
2526 name = 'IPSECKEY gateway'
2627
28 @dns.immutable.immutable
2729 class IPSECKEY(dns.rdata.Rdata):
2830
2931 """IPSECKEY record"""
3537 def __init__(self, rdclass, rdtype, precedence, gateway_type, algorithm,
3638 gateway, key):
3739 super().__init__(rdclass, rdtype)
38 Gateway(gateway_type, gateway).check()
39 object.__setattr__(self, 'precedence', precedence)
40 object.__setattr__(self, 'gateway_type', gateway_type)
41 object.__setattr__(self, 'algorithm', algorithm)
42 object.__setattr__(self, 'gateway', gateway)
43 object.__setattr__(self, 'key', key)
40 gateway = Gateway(gateway_type, gateway)
41 self.precedence = self._as_uint8(precedence)
42 self.gateway_type = gateway.type
43 self.algorithm = self._as_uint8(algorithm)
44 self.gateway = gateway.gateway
45 self.key = self._as_bytes(key)
4446
4547 def to_text(self, origin=None, relativize=True, **kw):
4648 gateway = Gateway(self.gateway_type, self.gateway).to_text(origin,
4749 relativize)
4850 return '%d %d %d %s %s' % (self.precedence, self.gateway_type,
4951 self.algorithm, gateway,
50 dns.rdata._base64ify(self.key))
52 dns.rdata._base64ify(self.key, **kw))
5153
5254 @classmethod
5355 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
5557 precedence = tok.get_uint8()
5658 gateway_type = tok.get_uint8()
5759 algorithm = tok.get_uint8()
58 gateway = Gateway(gateway_type).from_text(tok, origin, relativize,
59 relativize_to)
60 gateway = Gateway.from_text(gateway_type, tok, origin, relativize,
61 relativize_to)
6062 b64 = tok.concatenate_remaining_identifiers().encode()
6163 key = base64.b64decode(b64)
6264 return cls(rdclass, rdtype, precedence, gateway_type, algorithm,
63 gateway, key)
65 gateway.gateway, key)
6466
6567 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
6668 header = struct.pack("!BBB", self.precedence, self.gateway_type,
7476 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
7577 header = parser.get_struct('!BBB')
7678 gateway_type = header[1]
77 gateway = Gateway(gateway_type).from_wire_parser(parser, origin)
79 gateway = Gateway.from_wire_parser(gateway_type, parser, origin)
7880 key = parser.get_remaining()
7981 return cls(rdclass, rdtype, header[0], gateway_type, header[2],
80 gateway, key)
82 gateway.gateway, key)
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.mxbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class KX(dns.rdtypes.mxbase.UncompressedDowncasingMX):
2123
2224 """KX record"""
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.name
2122 import dns.rdata
23 import dns.rdtypes.util
2224
2325
2426 def _write_string(file, s):
2830 file.write(s)
2931
3032
31 def _sanitize(value):
32 if isinstance(value, str):
33 return value.encode()
34 return value
35
36
33 @dns.immutable.immutable
3734 class NAPTR(dns.rdata.Rdata):
3835
3936 """NAPTR record"""
4643 def __init__(self, rdclass, rdtype, order, preference, flags, service,
4744 regexp, replacement):
4845 super().__init__(rdclass, rdtype)
49 object.__setattr__(self, 'flags', _sanitize(flags))
50 object.__setattr__(self, 'service', _sanitize(service))
51 object.__setattr__(self, 'regexp', _sanitize(regexp))
52 object.__setattr__(self, 'order', order)
53 object.__setattr__(self, 'preference', preference)
54 object.__setattr__(self, 'replacement', replacement)
46 self.flags = self._as_bytes(flags, True, 255)
47 self.service = self._as_bytes(service, True, 255)
48 self.regexp = self._as_bytes(regexp, True, 255)
49 self.order = self._as_uint16(order)
50 self.preference = self._as_uint16(preference)
51 self.replacement = self._as_name(replacement)
5552
5653 def to_text(self, origin=None, relativize=True, **kw):
5754 replacement = self.replacement.choose_relativity(origin, relativize)
7168 service = tok.get_string()
7269 regexp = tok.get_string()
7370 replacement = tok.get_name(origin, relativize, relativize_to)
74 tok.get_eol()
7571 return cls(rdclass, rdtype, order, preference, flags, service,
7672 regexp, replacement)
7773
8783 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
8884 (order, preference) = parser.get_struct('!HH')
8985 strings = []
90 for i in range(3):
86 for _ in range(3):
9187 s = parser.get_counted_bytes()
9288 strings.append(s)
9389 replacement = parser.get_name(origin)
9490 return cls(rdclass, rdtype, order, preference, strings[0], strings[1],
9591 strings[2], replacement)
92
93 def _processing_priority(self):
94 return (self.order, self.preference)
95
96 @classmethod
97 def _processing_order(cls, iterable):
98 return dns.rdtypes.util.priority_processing_order(iterable)
1717 import binascii
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122 import dns.tokenizer
2223
2324
25 @dns.immutable.immutable
2426 class NSAP(dns.rdata.Rdata):
2527
2628 """NSAP record."""
3133
3234 def __init__(self, rdclass, rdtype, address):
3335 super().__init__(rdclass, rdtype)
34 object.__setattr__(self, 'address', address)
36 self.address = self._as_bytes(address)
3537
3638 def to_text(self, origin=None, relativize=True, **kw):
3739 return "0x%s" % binascii.hexlify(self.address).decode()
4042 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
4143 relativize_to=None):
4244 address = tok.get_string()
43 tok.get_eol()
4445 if address[0:2] != '0x':
4546 raise dns.exception.SyntaxError('string does not start with 0x')
4647 address = address[2:].replace('.', '')
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 import dns.rdtypes.nsbase
18 import dns.immutable
1819
1920
21 @dns.immutable.immutable
2022 class NSAP_PTR(dns.rdtypes.nsbase.UncompressedNS):
2123
2224 """NSAP-PTR record"""
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
22 import dns.rdtypes.util
2123 import dns.name
2224
2325
26 @dns.immutable.immutable
2427 class PX(dns.rdata.Rdata):
2528
2629 """PX record."""
3134
3235 def __init__(self, rdclass, rdtype, preference, map822, mapx400):
3336 super().__init__(rdclass, rdtype)
34 object.__setattr__(self, 'preference', preference)
35 object.__setattr__(self, 'map822', map822)
36 object.__setattr__(self, 'mapx400', mapx400)
37 self.preference = self._as_uint16(preference)
38 self.map822 = self._as_name(map822)
39 self.mapx400 = self._as_name(mapx400)
3740
3841 def to_text(self, origin=None, relativize=True, **kw):
3942 map822 = self.map822.choose_relativity(origin, relativize)
4649 preference = tok.get_uint16()
4750 map822 = tok.get_name(origin, relativize, relativize_to)
4851 mapx400 = tok.get_name(origin, relativize, relativize_to)
49 tok.get_eol()
5052 return cls(rdclass, rdtype, preference, map822, mapx400)
5153
5254 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
6163 map822 = parser.get_name(origin)
6264 mapx400 = parser.get_name(origin)
6365 return cls(rdclass, rdtype, preference, map822, mapx400)
66
67 def _processing_priority(self):
68 return self.preference
69
70 @classmethod
71 def _processing_order(cls, iterable):
72 return dns.rdtypes.util.priority_processing_order(iterable)
1717 import struct
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
22 import dns.rdtypes.util
2123 import dns.name
2224
2325
26 @dns.immutable.immutable
2427 class SRV(dns.rdata.Rdata):
2528
2629 """SRV record"""
3134
3235 def __init__(self, rdclass, rdtype, priority, weight, port, target):
3336 super().__init__(rdclass, rdtype)
34 object.__setattr__(self, 'priority', priority)
35 object.__setattr__(self, 'weight', weight)
36 object.__setattr__(self, 'port', port)
37 object.__setattr__(self, 'target', target)
37 self.priority = self._as_uint16(priority)
38 self.weight = self._as_uint16(weight)
39 self.port = self._as_uint16(port)
40 self.target = self._as_name(target)
3841
3942 def to_text(self, origin=None, relativize=True, **kw):
4043 target = self.target.choose_relativity(origin, relativize)
4851 weight = tok.get_uint16()
4952 port = tok.get_uint16()
5053 target = tok.get_name(origin, relativize, relativize_to)
51 tok.get_eol()
5254 return cls(rdclass, rdtype, priority, weight, port, target)
5355
5456 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
6163 (priority, weight, port) = parser.get_struct('!HHH')
6264 target = parser.get_name(origin)
6365 return cls(rdclass, rdtype, priority, weight, port, target)
66
67 def _processing_priority(self):
68 return self.priority
69
70 def _processing_weight(self):
71 return self.weight
72
73 @classmethod
74 def _processing_order(cls, iterable):
75 return dns.rdtypes.util.weighted_processing_order(iterable)
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import dns.rdtypes.svcbbase
3 import dns.immutable
4
5 @dns.immutable.immutable
6 class SVCB(dns.rdtypes.svcbbase.SVCBBase):
7 """SVCB record"""
1818 import struct
1919
2020 import dns.ipv4
21 import dns.immutable
2122 import dns.rdata
2223
23 _proto_tcp = socket.getprotobyname('tcp')
24 _proto_udp = socket.getprotobyname('udp')
24 try:
25 _proto_tcp = socket.getprotobyname('tcp')
26 _proto_udp = socket.getprotobyname('udp')
27 except OSError:
28 # Fall back to defaults in case /etc/protocols is unavailable.
29 _proto_tcp = 6
30 _proto_udp = 17
2531
26
32 @dns.immutable.immutable
2733 class WKS(dns.rdata.Rdata):
2834
2935 """WKS record"""
3440
3541 def __init__(self, rdclass, rdtype, address, protocol, bitmap):
3642 super().__init__(rdclass, rdtype)
37 object.__setattr__(self, 'address', address)
38 object.__setattr__(self, 'protocol', protocol)
39 object.__setattr__(self, 'bitmap', dns.rdata._constify(bitmap))
43 self.address = self._as_ipv4_address(address)
44 self.protocol = self._as_uint8(protocol)
45 self.bitmap = self._as_bytes(bitmap)
4046
4147 def to_text(self, origin=None, relativize=True, **kw):
4248 bits = []
5864 else:
5965 protocol = socket.getprotobyname(protocol)
6066 bitmap = bytearray()
61 while 1:
62 token = tok.get().unescape()
63 if token.is_eol_or_eof():
64 break
65 if token.value.isdigit():
66 serv = int(token.value)
67 for token in tok.get_remaining():
68 value = token.unescape().value
69 if value.isdigit():
70 serv = int(value)
6771 else:
6872 if protocol != _proto_udp and protocol != _proto_tcp:
6973 raise NotImplementedError("protocol must be TCP or UDP")
7175 protocol_text = "udp"
7276 else:
7377 protocol_text = "tcp"
74 serv = socket.getservbyname(token.value, protocol_text)
78 serv = socket.getservbyname(value, protocol_text)
7579 i = serv // 8
7680 l = len(bitmap)
7781 if l < i + 1:
78 for j in range(l, i + 1):
82 for _ in range(l, i + 1):
7983 bitmap.append(0)
8084 bitmap[i] = bitmap[i] | (0x80 >> (serv % 8))
8185 bitmap = dns.rdata._truncate_bitmap(bitmap)
8993
9094 @classmethod
9195 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
92 address = dns.ipv4.inet_ntoa(parser.get_bytes(4))
96 address = parser.get_bytes(4)
9397 protocol = parser.get_uint8()
9498 bitmap = parser.get_remaining()
9599 return cls(rdclass, rdtype, address, protocol, bitmap)
2121 'AAAA',
2222 'APL',
2323 'DHCID',
24 'HTTPS',
2425 'IPSECKEY',
2526 'KX',
2627 'NAPTR',
2829 'NSAP_PTR',
2930 'PX',
3031 'SRV',
32 'SVCB',
3133 'WKS',
3234 ]
2020 'ANY',
2121 'IN',
2222 'CH',
23 'dnskeybase',
24 'dsbase',
2325 'euibase',
2426 'mxbase',
2527 'nsbase',
28 'svcbbase',
29 'tlsabase',
30 'txtbase',
2631 'util'
2732 ]
1919 import struct
2020
2121 import dns.exception
22 import dns.immutable
2223 import dns.dnssec
2324 import dns.rdata
2425
3031 REVOKE = 0x0080
3132 ZONE = 0x0100
3233
33 globals().update(Flag.__members__)
3434
35
35 @dns.immutable.immutable
3636 class DNSKEYBase(dns.rdata.Rdata):
3737
3838 """Base class for rdata that is like a DNSKEY record"""
4141
4242 def __init__(self, rdclass, rdtype, flags, protocol, algorithm, key):
4343 super().__init__(rdclass, rdtype)
44 object.__setattr__(self, 'flags', flags)
45 object.__setattr__(self, 'protocol', protocol)
46 object.__setattr__(self, 'algorithm', algorithm)
47 object.__setattr__(self, 'key', key)
44 self.flags = self._as_uint16(flags)
45 self.protocol = self._as_uint8(protocol)
46 self.algorithm = dns.dnssec.Algorithm.make(algorithm)
47 self.key = self._as_bytes(key)
4848
4949 def to_text(self, origin=None, relativize=True, **kw):
5050 return '%d %d %d %s' % (self.flags, self.protocol, self.algorithm,
51 dns.rdata._base64ify(self.key))
51 dns.rdata._base64ify(self.key, **kw))
5252
5353 @classmethod
5454 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
5555 relativize_to=None):
5656 flags = tok.get_uint16()
5757 protocol = tok.get_uint8()
58 algorithm = dns.dnssec.algorithm_from_text(tok.get_string())
58 algorithm = tok.get_string()
5959 b64 = tok.concatenate_remaining_identifiers().encode()
6060 key = base64.b64decode(b64)
6161 return cls(rdclass, rdtype, flags, protocol, algorithm, key)
7171 key = parser.get_remaining()
7272 return cls(rdclass, rdtype, header[0], header[1], header[2],
7373 key)
74
75 ### BEGIN generated Flag constants
76
77 SEP = Flag.SEP
78 REVOKE = Flag.REVOKE
79 ZONE = Flag.ZONE
80
81 ### END generated Flag constants
1818 import binascii
1919
2020 import dns.dnssec
21 import dns.immutable
2122 import dns.rdata
2223 import dns.rdatatype
2324
2425
26 # Digest types registry: https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml
27 _digest_length_by_type = {
28 1: 20, # SHA-1, RFC 3658 Sec. 2.4
29 2: 32, # SHA-256, RFC 4509 Sec. 2.2
30 3: 32, # GOST R 34.11-94, RFC 5933 Sec. 4 in conjunction with RFC 4490 Sec. 2.1
31 4: 48, # SHA-384, RFC 6605 Sec. 2
32 }
33
34
35 @dns.immutable.immutable
2536 class DSBase(dns.rdata.Rdata):
2637
2738 """Base class for rdata that is like a DS record"""
3142 def __init__(self, rdclass, rdtype, key_tag, algorithm, digest_type,
3243 digest):
3344 super().__init__(rdclass, rdtype)
34 object.__setattr__(self, 'key_tag', key_tag)
35 object.__setattr__(self, 'algorithm', algorithm)
36 object.__setattr__(self, 'digest_type', digest_type)
37 object.__setattr__(self, 'digest', digest)
45 self.key_tag = self._as_uint16(key_tag)
46 self.algorithm = dns.dnssec.Algorithm.make(algorithm)
47 self.digest_type = self._as_uint8(digest_type)
48 self.digest = self._as_bytes(digest)
49
50 try:
51 if self.digest_type == 0: # reserved, RFC 3658 Sec. 2.4
52 raise ValueError('digest type 0 is reserved')
53 expected_length = _digest_length_by_type[self.digest_type]
54 except KeyError:
55 raise ValueError('unknown digest type')
56 if len(self.digest) != expected_length:
57 raise ValueError('digest length inconsistent with digest type')
3858
3959 def to_text(self, origin=None, relativize=True, **kw):
60 kw = kw.copy()
61 chunksize = kw.pop('chunksize', 128)
4062 return '%d %d %d %s' % (self.key_tag, self.algorithm,
4163 self.digest_type,
4264 dns.rdata._hexify(self.digest,
43 chunksize=128))
65 chunksize=chunksize,
66 **kw))
4467
4568 @classmethod
4669 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
4770 relativize_to=None):
4871 key_tag = tok.get_uint16()
49 algorithm = dns.dnssec.algorithm_from_text(tok.get_string())
72 algorithm = tok.get_string()
5073 digest_type = tok.get_uint8()
5174 digest = tok.concatenate_remaining_identifiers().encode()
5275 digest = binascii.unhexlify(digest)
1616 import binascii
1717
1818 import dns.rdata
19 import dns.immutable
1920
2021
22 @dns.immutable.immutable
2123 class EUIBase(dns.rdata.Rdata):
2224
2325 """EUIxx record"""
3133
3234 def __init__(self, rdclass, rdtype, eui):
3335 super().__init__(rdclass, rdtype)
34 if len(eui) != self.byte_len:
36 self.eui = self._as_bytes(eui)
37 if len(self.eui) != self.byte_len:
3538 raise dns.exception.FormError('EUI%s rdata has to have %s bytes'
3639 % (self.byte_len * 8, self.byte_len))
37 object.__setattr__(self, 'eui', eui)
3840
3941 def to_text(self, origin=None, relativize=True, **kw):
40 return dns.rdata._hexify(self.eui, chunksize=2).replace(' ', '-')
42 return dns.rdata._hexify(self.eui, chunksize=2, **kw).replace(' ', '-')
4143
4244 @classmethod
4345 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
4446 relativize_to=None):
4547 text = tok.get_string()
46 tok.get_eol()
4748 if len(text) != cls.text_len:
4849 raise dns.exception.SyntaxError(
4950 'Input text must have %s characters' % cls.text_len)
1919 import struct
2020
2121 import dns.exception
22 import dns.immutable
2223 import dns.rdata
2324 import dns.name
25 import dns.rdtypes.util
2426
2527
28 @dns.immutable.immutable
2629 class MXBase(dns.rdata.Rdata):
2730
2831 """Base class for rdata that is like an MX record."""
3134
3235 def __init__(self, rdclass, rdtype, preference, exchange):
3336 super().__init__(rdclass, rdtype)
34 object.__setattr__(self, 'preference', preference)
35 object.__setattr__(self, 'exchange', exchange)
37 self.preference = self._as_uint16(preference)
38 self.exchange = self._as_name(exchange)
3639
3740 def to_text(self, origin=None, relativize=True, **kw):
3841 exchange = self.exchange.choose_relativity(origin, relativize)
4346 relativize_to=None):
4447 preference = tok.get_uint16()
4548 exchange = tok.get_name(origin, relativize, relativize_to)
46 tok.get_eol()
4749 return cls(rdclass, rdtype, preference, exchange)
4850
4951 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
5759 exchange = parser.get_name(origin)
5860 return cls(rdclass, rdtype, preference, exchange)
5961
62 def _processing_priority(self):
63 return self.preference
6064
65 @classmethod
66 def _processing_order(cls, iterable):
67 return dns.rdtypes.util.priority_processing_order(iterable)
68
69
70 @dns.immutable.immutable
6171 class UncompressedMX(MXBase):
6272
6373 """Base class for rdata that is like an MX record, but whose name
6878 super()._to_wire(file, None, origin, False)
6979
7080
81 @dns.immutable.immutable
7182 class UncompressedDowncasingMX(MXBase):
7283
7384 """Base class for rdata that is like an MX record, but whose name
1717 """NS-like base classes."""
1818
1919 import dns.exception
20 import dns.immutable
2021 import dns.rdata
2122 import dns.name
2223
2324
25 @dns.immutable.immutable
2426 class NSBase(dns.rdata.Rdata):
2527
2628 """Base class for rdata that is like an NS record."""
2931
3032 def __init__(self, rdclass, rdtype, target):
3133 super().__init__(rdclass, rdtype)
32 object.__setattr__(self, 'target', target)
34 self.target = self._as_name(target)
3335
3436 def to_text(self, origin=None, relativize=True, **kw):
3537 target = self.target.choose_relativity(origin, relativize)
3941 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
4042 relativize_to=None):
4143 target = tok.get_name(origin, relativize, relativize_to)
42 tok.get_eol()
4344 return cls(rdclass, rdtype, target)
4445
4546 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
5152 return cls(rdclass, rdtype, target)
5253
5354
55 @dns.immutable.immutable
5456 class UncompressedNS(NSBase):
5557
5658 """Base class for rdata that is like an NS record, but whose name
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import base64
3 import enum
4 import io
5 import struct
6
7 import dns.enum
8 import dns.exception
9 import dns.immutable
10 import dns.ipv4
11 import dns.ipv6
12 import dns.name
13 import dns.rdata
14 import dns.rdtypes.util
15 import dns.tokenizer
16 import dns.wire
17
18 # Until there is an RFC, this module is experimental and may be changed in
19 # incompatible ways.
20
21
22 class UnknownParamKey(dns.exception.DNSException):
23 """Unknown SVCB ParamKey"""
24
25
26 class ParamKey(dns.enum.IntEnum):
27 """SVCB ParamKey"""
28
29 MANDATORY = 0
30 ALPN = 1
31 NO_DEFAULT_ALPN = 2
32 PORT = 3
33 IPV4HINT = 4
34 ECH = 5
35 IPV6HINT = 6
36
37 @classmethod
38 def _maximum(cls):
39 return 65535
40
41 @classmethod
42 def _short_name(cls):
43 return "SVCBParamKey"
44
45 @classmethod
46 def _prefix(cls):
47 return "KEY"
48
49 @classmethod
50 def _unknown_exception_class(cls):
51 return UnknownParamKey
52
53
54 class Emptiness(enum.IntEnum):
55 NEVER = 0
56 ALWAYS = 1
57 ALLOWED = 2
58
59
60 def _validate_key(key):
61 force_generic = False
62 if isinstance(key, bytes):
63 # We decode to latin-1 so we get 0-255 as valid and do NOT interpret
64 # UTF-8 sequences
65 key = key.decode('latin-1')
66 if isinstance(key, str):
67 if key.lower().startswith('key'):
68 force_generic = True
69 if key[3:].startswith('0') and len(key) != 4:
70 # key has leading zeros
71 raise ValueError('leading zeros in key')
72 key = key.replace('-', '_')
73 return (ParamKey.make(key), force_generic)
74
75 def key_to_text(key):
76 return ParamKey.to_text(key).replace('_', '-').lower()
77
78 # Like rdata escapify, but escapes ',' too.
79
80 _escaped = b'",\\'
81
82 def _escapify(qstring):
83 text = ''
84 for c in qstring:
85 if c in _escaped:
86 text += '\\' + chr(c)
87 elif c >= 0x20 and c < 0x7F:
88 text += chr(c)
89 else:
90 text += '\\%03d' % c
91 return text
92
93 def _unescape(value):
94 if value == '':
95 return value
96 unescaped = b''
97 l = len(value)
98 i = 0
99 while i < l:
100 c = value[i]
101 i += 1
102 if c == '\\':
103 if i >= l: # pragma: no cover (can't happen via tokenizer get())
104 raise dns.exception.UnexpectedEnd
105 c = value[i]
106 i += 1
107 if c.isdigit():
108 if i >= l:
109 raise dns.exception.UnexpectedEnd
110 c2 = value[i]
111 i += 1
112 if i >= l:
113 raise dns.exception.UnexpectedEnd
114 c3 = value[i]
115 i += 1
116 if not (c2.isdigit() and c3.isdigit()):
117 raise dns.exception.SyntaxError
118 codepoint = int(c) * 100 + int(c2) * 10 + int(c3)
119 if codepoint > 255:
120 raise dns.exception.SyntaxError
121 unescaped += b'%c' % (codepoint)
122 continue
123 unescaped += c.encode()
124 return unescaped
125
126
127 def _split(value):
128 l = len(value)
129 i = 0
130 items = []
131 unescaped = b''
132 while i < l:
133 c = value[i]
134 i += 1
135 if c == ord('\\'):
136 if i >= l: # pragma: no cover (can't happen via tokenizer get())
137 raise dns.exception.UnexpectedEnd
138 c = value[i]
139 i += 1
140 unescaped += b'%c' % (c)
141 elif c == ord(','):
142 items.append(unescaped)
143 unescaped = b''
144 else:
145 unescaped += b'%c' % (c)
146 items.append(unescaped)
147 return items
148
149
150 @dns.immutable.immutable
151 class Param:
152 """Abstract base class for SVCB parameters"""
153
154 @classmethod
155 def emptiness(cls):
156 return Emptiness.NEVER
157
158
159 @dns.immutable.immutable
160 class GenericParam(Param):
161 """Generic SVCB parameter
162 """
163 def __init__(self, value):
164 self.value = dns.rdata.Rdata._as_bytes(value, True)
165
166 @classmethod
167 def emptiness(cls):
168 return Emptiness.ALLOWED
169
170 @classmethod
171 def from_value(cls, value):
172 if value is None or len(value) == 0:
173 return None
174 else:
175 return cls(_unescape(value))
176
177 def to_text(self):
178 return '"' + dns.rdata._escapify(self.value) + '"'
179
180 @classmethod
181 def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613
182 value = parser.get_bytes(parser.remaining())
183 if len(value) == 0:
184 return None
185 else:
186 return cls(value)
187
188 def to_wire(self, file, origin=None): # pylint: disable=W0613
189 file.write(self.value)
190
191
192 @dns.immutable.immutable
193 class MandatoryParam(Param):
194 def __init__(self, keys):
195 # check for duplicates
196 keys = sorted([_validate_key(key)[0] for key in keys])
197 prior_k = None
198 for k in keys:
199 if k == prior_k:
200 raise ValueError(f'duplicate key {k}')
201 prior_k = k
202 if k == ParamKey.MANDATORY:
203 raise ValueError('listed the mandatory key as mandatory')
204 self.keys = tuple(keys)
205
206 @classmethod
207 def from_value(cls, value):
208 keys = [k.encode() for k in value.split(',')]
209 return cls(keys)
210
211 def to_text(self):
212 return '"' + ','.join([key_to_text(key) for key in self.keys]) + '"'
213
214 @classmethod
215 def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613
216 keys = []
217 last_key = -1
218 while parser.remaining() > 0:
219 key = parser.get_uint16()
220 if key < last_key:
221 raise dns.exception.FormError('manadatory keys not ascending')
222 last_key = key
223 keys.append(key)
224 return cls(keys)
225
226 def to_wire(self, file, origin=None): # pylint: disable=W0613
227 for key in self.keys:
228 file.write(struct.pack('!H', key))
229
230
231 @dns.immutable.immutable
232 class ALPNParam(Param):
233 def __init__(self, ids):
234 self.ids = dns.rdata.Rdata._as_tuple(
235 ids, lambda x: dns.rdata.Rdata._as_bytes(x, True, 255, False))
236
237 @classmethod
238 def from_value(cls, value):
239 return cls(_split(_unescape(value)))
240
241 def to_text(self):
242 value = ','.join([_escapify(id) for id in self.ids])
243 return '"' + dns.rdata._escapify(value.encode()) + '"'
244
245 @classmethod
246 def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613
247 ids = []
248 while parser.remaining() > 0:
249 id = parser.get_counted_bytes()
250 ids.append(id)
251 return cls(ids)
252
253 def to_wire(self, file, origin=None): # pylint: disable=W0613
254 for id in self.ids:
255 file.write(struct.pack('!B', len(id)))
256 file.write(id)
257
258
259 @dns.immutable.immutable
260 class NoDefaultALPNParam(Param):
261 # We don't ever expect to instantiate this class, but we need
262 # a from_value() and a from_wire_parser(), so we just return None
263 # from the class methods when things are OK.
264
265 @classmethod
266 def emptiness(cls):
267 return Emptiness.ALWAYS
268
269 @classmethod
270 def from_value(cls, value):
271 if value is None or value == '':
272 return None
273 else:
274 raise ValueError('no-default-alpn with non-empty value')
275
276 def to_text(self):
277 raise NotImplementedError # pragma: no cover
278
279 @classmethod
280 def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613
281 if parser.remaining() != 0:
282 raise dns.exception.FormError
283 return None
284
285 def to_wire(self, file, origin=None): # pylint: disable=W0613
286 raise NotImplementedError # pragma: no cover
287
288
289 @dns.immutable.immutable
290 class PortParam(Param):
291 def __init__(self, port):
292 self.port = dns.rdata.Rdata._as_uint16(port)
293
294 @classmethod
295 def from_value(cls, value):
296 value = int(value)
297 return cls(value)
298
299 def to_text(self):
300 return f'"{self.port}"'
301
302 @classmethod
303 def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613
304 port = parser.get_uint16()
305 return cls(port)
306
307 def to_wire(self, file, origin=None): # pylint: disable=W0613
308 file.write(struct.pack('!H', self.port))
309
310
311 @dns.immutable.immutable
312 class IPv4HintParam(Param):
313 def __init__(self, addresses):
314 self.addresses = dns.rdata.Rdata._as_tuple(
315 addresses, dns.rdata.Rdata._as_ipv4_address)
316
317 @classmethod
318 def from_value(cls, value):
319 addresses = value.split(',')
320 return cls(addresses)
321
322 def to_text(self):
323 return '"' + ','.join(self.addresses) + '"'
324
325 @classmethod
326 def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613
327 addresses = []
328 while parser.remaining() > 0:
329 ip = parser.get_bytes(4)
330 addresses.append(dns.ipv4.inet_ntoa(ip))
331 return cls(addresses)
332
333 def to_wire(self, file, origin=None): # pylint: disable=W0613
334 for address in self.addresses:
335 file.write(dns.ipv4.inet_aton(address))
336
337
338 @dns.immutable.immutable
339 class IPv6HintParam(Param):
340 def __init__(self, addresses):
341 self.addresses = dns.rdata.Rdata._as_tuple(
342 addresses, dns.rdata.Rdata._as_ipv6_address)
343
344 @classmethod
345 def from_value(cls, value):
346 addresses = value.split(',')
347 return cls(addresses)
348
349 def to_text(self):
350 return '"' + ','.join(self.addresses) + '"'
351
352 @classmethod
353 def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613
354 addresses = []
355 while parser.remaining() > 0:
356 ip = parser.get_bytes(16)
357 addresses.append(dns.ipv6.inet_ntoa(ip))
358 return cls(addresses)
359
360 def to_wire(self, file, origin=None): # pylint: disable=W0613
361 for address in self.addresses:
362 file.write(dns.ipv6.inet_aton(address))
363
364
365 @dns.immutable.immutable
366 class ECHParam(Param):
367 def __init__(self, ech):
368 self.ech = dns.rdata.Rdata._as_bytes(ech, True)
369
370 @classmethod
371 def from_value(cls, value):
372 if '\\' in value:
373 raise ValueError('escape in ECH value')
374 value = base64.b64decode(value.encode())
375 return cls(value)
376
377 def to_text(self):
378 b64 = base64.b64encode(self.ech).decode('ascii')
379 return f'"{b64}"'
380
381 @classmethod
382 def from_wire_parser(cls, parser, origin=None): # pylint: disable=W0613
383 value = parser.get_bytes(parser.remaining())
384 return cls(value)
385
386 def to_wire(self, file, origin=None): # pylint: disable=W0613
387 file.write(self.ech)
388
389
390 _class_for_key = {
391 ParamKey.MANDATORY: MandatoryParam,
392 ParamKey.ALPN: ALPNParam,
393 ParamKey.NO_DEFAULT_ALPN: NoDefaultALPNParam,
394 ParamKey.PORT: PortParam,
395 ParamKey.IPV4HINT: IPv4HintParam,
396 ParamKey.ECH: ECHParam,
397 ParamKey.IPV6HINT: IPv6HintParam,
398 }
399
400
401 def _validate_and_define(params, key, value):
402 (key, force_generic) = _validate_key(_unescape(key))
403 if key in params:
404 raise SyntaxError(f'duplicate key "{key}"')
405 cls = _class_for_key.get(key, GenericParam)
406 emptiness = cls.emptiness()
407 if value is None:
408 if emptiness == Emptiness.NEVER:
409 raise SyntaxError('value cannot be empty')
410 value = cls.from_value(value)
411 else:
412 if force_generic:
413 value = cls.from_wire_parser(dns.wire.Parser(_unescape(value)))
414 else:
415 value = cls.from_value(value)
416 params[key] = value
417
418
419 @dns.immutable.immutable
420 class SVCBBase(dns.rdata.Rdata):
421
422 """Base class for SVCB-like records"""
423
424 # see: draft-ietf-dnsop-svcb-https-01
425
426 __slots__ = ['priority', 'target', 'params']
427
428 def __init__(self, rdclass, rdtype, priority, target, params):
429 super().__init__(rdclass, rdtype)
430 self.priority = self._as_uint16(priority)
431 self.target = self._as_name(target)
432 for k, v in params.items():
433 k = ParamKey.make(k)
434 if not isinstance(v, Param) and v is not None:
435 raise ValueError("not a Param")
436 self.params = dns.immutable.Dict(params)
437 # Make sure any paramater listed as mandatory is present in the
438 # record.
439 mandatory = params.get(ParamKey.MANDATORY)
440 if mandatory:
441 for key in mandatory.keys:
442 # Note we have to say "not in" as we have None as a value
443 # so a get() and a not None test would be wrong.
444 if key not in params:
445 raise ValueError(f'key {key} declared mandatory but not '
446 'present')
447 # The no-default-alpn parameter requires the alpn parameter.
448 if ParamKey.NO_DEFAULT_ALPN in params:
449 if ParamKey.ALPN not in params:
450 raise ValueError('no-default-alpn present, but alpn missing')
451
452 def to_text(self, origin=None, relativize=True, **kw):
453 target = self.target.choose_relativity(origin, relativize)
454 params = []
455 for key in sorted(self.params.keys()):
456 value = self.params[key]
457 if value is None:
458 params.append(key_to_text(key))
459 else:
460 kv = key_to_text(key) + '=' + value.to_text()
461 params.append(kv)
462 if len(params) > 0:
463 space = ' '
464 else:
465 space = ''
466 return '%d %s%s%s' % (self.priority, target, space, ' '.join(params))
467
468 @classmethod
469 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
470 relativize_to=None):
471 priority = tok.get_uint16()
472 target = tok.get_name(origin, relativize, relativize_to)
473 if priority == 0:
474 token = tok.get()
475 if not token.is_eol_or_eof():
476 raise SyntaxError('parameters in AliasMode')
477 tok.unget(token)
478 params = {}
479 while True:
480 token = tok.get()
481 if token.is_eol_or_eof():
482 tok.unget(token)
483 break
484 if token.ttype != dns.tokenizer.IDENTIFIER:
485 raise SyntaxError('parameter is not an identifier')
486 equals = token.value.find('=')
487 if equals == len(token.value) - 1:
488 # 'key=', so next token should be a quoted string without
489 # any intervening whitespace.
490 key = token.value[:-1]
491 token = tok.get(want_leading=True)
492 if token.ttype != dns.tokenizer.QUOTED_STRING:
493 raise SyntaxError('whitespace after =')
494 value = token.value
495 elif equals > 0:
496 # key=value
497 key = token.value[:equals]
498 value = token.value[equals + 1:]
499 elif equals == 0:
500 # =key
501 raise SyntaxError('parameter cannot start with "="')
502 else:
503 # key
504 key = token.value
505 value = None
506 _validate_and_define(params, key, value)
507 return cls(rdclass, rdtype, priority, target, params)
508
509 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
510 file.write(struct.pack("!H", self.priority))
511 self.target.to_wire(file, None, origin, False)
512 for key in sorted(self.params):
513 file.write(struct.pack("!H", key))
514 value = self.params[key]
515 # placeholder for length (or actual length of empty values)
516 file.write(struct.pack("!H", 0))
517 if value is None:
518 continue
519 else:
520 start = file.tell()
521 value.to_wire(file, origin)
522 end = file.tell()
523 assert end - start < 65536
524 file.seek(start - 2)
525 stuff = struct.pack("!H", end - start)
526 file.write(stuff)
527 file.seek(0, io.SEEK_END)
528
529 @classmethod
530 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
531 priority = parser.get_uint16()
532 target = parser.get_name(origin)
533 if priority == 0 and parser.remaining() != 0:
534 raise dns.exception.FormError('parameters in AliasMode')
535 params = {}
536 prior_key = -1
537 while parser.remaining() > 0:
538 key = parser.get_uint16()
539 if key < prior_key:
540 raise dns.exception.FormError('keys not in order')
541 prior_key = key
542 vlen = parser.get_uint16()
543 pcls = _class_for_key.get(key, GenericParam)
544 with parser.restrict_to(vlen):
545 value = pcls.from_wire_parser(parser, origin)
546 params[key] = value
547 return cls(rdclass, rdtype, priority, target, params)
548
549 def _processing_priority(self):
550 return self.priority
551
552 @classmethod
553 def _processing_order(cls, iterable):
554 return dns.rdtypes.util.priority_processing_order(iterable)
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 # Copyright (C) 2005-2007, 2009-2011 Nominum, Inc.
3 #
4 # Permission to use, copy, modify, and distribute this software and its
5 # documentation for any purpose with or without fee is hereby granted,
6 # provided that the above copyright notice and this permission notice
7 # appear in all copies.
8 #
9 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
12 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
15 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17 import struct
18 import binascii
19
20 import dns.rdata
21 import dns.immutable
22 import dns.rdatatype
23
24
25 @dns.immutable.immutable
26 class TLSABase(dns.rdata.Rdata):
27
28 """Base class for TLSA and SMIMEA records"""
29
30 # see: RFC 6698
31
32 __slots__ = ['usage', 'selector', 'mtype', 'cert']
33
34 def __init__(self, rdclass, rdtype, usage, selector,
35 mtype, cert):
36 super().__init__(rdclass, rdtype)
37 self.usage = self._as_uint8(usage)
38 self.selector = self._as_uint8(selector)
39 self.mtype = self._as_uint8(mtype)
40 self.cert = self._as_bytes(cert)
41
42 def to_text(self, origin=None, relativize=True, **kw):
43 kw = kw.copy()
44 chunksize = kw.pop('chunksize', 128)
45 return '%d %d %d %s' % (self.usage,
46 self.selector,
47 self.mtype,
48 dns.rdata._hexify(self.cert,
49 chunksize=chunksize,
50 **kw))
51
52 @classmethod
53 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
54 relativize_to=None):
55 usage = tok.get_uint8()
56 selector = tok.get_uint8()
57 mtype = tok.get_uint8()
58 cert = tok.concatenate_remaining_identifiers().encode()
59 cert = binascii.unhexlify(cert)
60 return cls(rdclass, rdtype, usage, selector, mtype, cert)
61
62 def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
63 header = struct.pack("!BBB", self.usage, self.selector, self.mtype)
64 file.write(header)
65 file.write(self.cert)
66
67 @classmethod
68 def from_wire_parser(cls, rdclass, rdtype, parser, origin=None):
69 header = parser.get_struct("BBB")
70 cert = parser.get_remaining()
71 return cls(rdclass, rdtype, header[0], header[1], header[2], cert)
1919 import struct
2020
2121 import dns.exception
22 import dns.immutable
2223 import dns.rdata
2324 import dns.tokenizer
2425
2526
27 @dns.immutable.immutable
2628 class TXTBase(dns.rdata.Rdata):
2729
2830 """Base class for rdata that is like a TXT record (see RFC 1035)."""
3941 *strings*, a tuple of ``bytes``
4042 """
4143 super().__init__(rdclass, rdtype)
42 if isinstance(strings, (bytes, str)):
43 strings = (strings,)
44 encoded_strings = []
45 for string in strings:
46 if isinstance(string, str):
47 string = string.encode()
48 else:
49 string = dns.rdata._constify(string)
50 encoded_strings.append(string)
51 object.__setattr__(self, 'strings', tuple(encoded_strings))
44 self.strings = self._as_tuple(strings,
45 lambda x: self._as_bytes(x, True, 255))
5246
5347 def to_text(self, origin=None, relativize=True, **kw):
5448 txt = ''
6256 def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True,
6357 relativize_to=None):
6458 strings = []
65 while 1:
66 token = tok.get().unescape_to_bytes()
67 if token.is_eol_or_eof():
68 break
69 if not (token.is_quoted_string() or token.is_identifier()):
59 for token in tok.get_remaining():
60 token = token.unescape_to_bytes()
61 # The 'if' below is always true in the current code, but we
62 # are leaving this check in in case things change some day.
63 if not (token.is_quoted_string() or
64 token.is_identifier()): # pragma: no cover
7065 raise dns.exception.SyntaxError("expected a string")
7166 if len(token.value) > 255:
7267 raise dns.exception.SyntaxError("string too long")
1414 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
17 import collections
18 import random
1719 import struct
1820
1921 import dns.exception
20 import dns.name
2122 import dns.ipv4
2223 import dns.ipv6
24 import dns.name
25 import dns.rdata
26
2327
2428 class Gateway:
2529 """A helper class for the IPSECKEY gateway and AMTRELAY relay fields"""
2630 name = ""
2731
2832 def __init__(self, type, gateway=None):
29 self.type = type
33 self.type = dns.rdata.Rdata._as_uint8(type)
3034 self.gateway = gateway
31
32 def _invalid_type(self):
33 return f"invalid {self.name} type: {self.type}"
34
35 def check(self):
35 self._check()
36
37 @classmethod
38 def _invalid_type(cls, gateway_type):
39 return f"invalid {cls.name} type: {gateway_type}"
40
41 def _check(self):
3642 if self.type == 0:
3743 if self.gateway not in (".", None):
3844 raise SyntaxError(f"invalid {self.name} for type 0")
4753 if not isinstance(self.gateway, dns.name.Name):
4854 raise SyntaxError(f"invalid {self.name}; not a name")
4955 else:
50 raise SyntaxError(self._invalid_type())
56 raise SyntaxError(self._invalid_type(self.type))
5157
5258 def to_text(self, origin=None, relativize=True):
5359 if self.type == 0:
5763 elif self.type == 3:
5864 return str(self.gateway.choose_relativity(origin, relativize))
5965 else:
60 raise ValueError(self._invalid_type())
61
62 def from_text(self, tok, origin=None, relativize=True, relativize_to=None):
63 if self.type in (0, 1, 2):
64 return tok.get_string()
65 elif self.type == 3:
66 return tok.get_name(origin, relativize, relativize_to)
67 else:
68 raise dns.exception.SyntaxError(self._invalid_type())
69
66 raise ValueError(self._invalid_type(self.type)) # pragma: no cover
67
68 @classmethod
69 def from_text(cls, gateway_type, tok, origin=None, relativize=True,
70 relativize_to=None):
71 if gateway_type in (0, 1, 2):
72 gateway = tok.get_string()
73 elif gateway_type == 3:
74 gateway = tok.get_name(origin, relativize, relativize_to)
75 else:
76 raise dns.exception.SyntaxError(
77 cls._invalid_type(gateway_type)) # pragma: no cover
78 return cls(gateway_type, gateway)
79
80 # pylint: disable=unused-argument
7081 def to_wire(self, file, compress=None, origin=None, canonicalize=False):
7182 if self.type == 0:
7283 pass
7788 elif self.type == 3:
7889 self.gateway.to_wire(file, None, origin, False)
7990 else:
80 raise ValueError(self._invalid_type())
81
82 def from_wire_parser(self, parser, origin=None):
83 if self.type == 0:
84 return None
85 elif self.type == 1:
86 return dns.ipv4.inet_ntoa(parser.get_bytes(4))
87 elif self.type == 2:
88 return dns.ipv6.inet_ntoa(parser.get_bytes(16))
89 elif self.type == 3:
90 return parser.get_name(origin)
91 else:
92 raise dns.exception.FormError(self._invalid_type())
91 raise ValueError(self._invalid_type(self.type)) # pragma: no cover
92 # pylint: enable=unused-argument
93
94 @classmethod
95 def from_wire_parser(cls, gateway_type, parser, origin=None):
96 if gateway_type == 0:
97 gateway = None
98 elif gateway_type == 1:
99 gateway = dns.ipv4.inet_ntoa(parser.get_bytes(4))
100 elif gateway_type == 2:
101 gateway = dns.ipv6.inet_ntoa(parser.get_bytes(16))
102 elif gateway_type == 3:
103 gateway = parser.get_name(origin)
104 else:
105 raise dns.exception.FormError(cls._invalid_type(gateway_type))
106 return cls(gateway_type, gateway)
107
93108
94109 class Bitmap:
95110 """A helper class for the NSEC/NSEC3/CSYNC type bitmaps"""
96111 type_name = ""
97112
98113 def __init__(self, windows=None):
114 last_window = -1
99115 self.windows = windows
116 for (window, bitmap) in self.windows:
117 if not isinstance(window, int):
118 raise ValueError(f"bad {self.type_name} window type")
119 if window <= last_window:
120 raise ValueError(f"bad {self.type_name} window order")
121 if window > 256:
122 raise ValueError(f"bad {self.type_name} window number")
123 last_window = window
124 if not isinstance(bitmap, bytes):
125 raise ValueError(f"bad {self.type_name} octets type")
126 if len(bitmap) == 0 or len(bitmap) > 32:
127 raise ValueError(f"bad {self.type_name} octets")
100128
101129 def to_text(self):
102130 text = ""
110138 text += (' ' + ' '.join(bits))
111139 return text
112140
113 def from_text(self, tok):
141 @classmethod
142 def from_text(cls, tok):
114143 rdtypes = []
115 while True:
116 token = tok.get().unescape()
117 if token.is_eol_or_eof():
118 break
119 rdtype = dns.rdatatype.from_text(token.value)
144 for token in tok.get_remaining():
145 rdtype = dns.rdatatype.from_text(token.unescape().value)
120146 if rdtype == 0:
121 raise dns.exception.SyntaxError(f"{self.type_name} with bit 0")
147 raise dns.exception.SyntaxError(f"{cls.type_name} with bit 0")
122148 rdtypes.append(rdtype)
123149 rdtypes.sort()
124150 window = 0
133159 new_window = rdtype // 256
134160 if new_window != window:
135161 if octets != 0:
136 windows.append((window, bitmap[0:octets]))
162 windows.append((window, bytes(bitmap[0:octets])))
137163 bitmap = bytearray(b'\0' * 32)
138164 window = new_window
139165 offset = rdtype % 256
142168 octets = byte + 1
143169 bitmap[byte] = bitmap[byte] | (0x80 >> bit)
144170 if octets != 0:
145 windows.append((window, bitmap[0:octets]))
146 return windows
171 windows.append((window, bytes(bitmap[0:octets])))
172 return cls(windows)
147173
148174 def to_wire(self, file):
149175 for (window, bitmap) in self.windows:
150176 file.write(struct.pack('!BB', window, len(bitmap)))
151177 file.write(bitmap)
152178
153 def from_wire_parser(self, parser):
179 @classmethod
180 def from_wire_parser(cls, parser):
154181 windows = []
155 last_window = -1
156182 while parser.remaining() > 0:
157183 window = parser.get_uint8()
158 if window <= last_window:
159 raise dns.exception.FormError(f"bad {self.type_name} bitmap")
160184 bitmap = parser.get_counted_bytes()
161 if len(bitmap) == 0 or len(bitmap) > 32:
162 raise dns.exception.FormError(f"bad {self.type_name} octets")
163185 windows.append((window, bitmap))
164 last_window = window
165 return windows
186 return cls(windows)
187
188
189 def _priority_table(items):
190 by_priority = collections.defaultdict(list)
191 for rdata in items:
192 by_priority[rdata._processing_priority()].append(rdata)
193 return by_priority
194
195 def priority_processing_order(iterable):
196 items = list(iterable)
197 if len(items) == 1:
198 return items
199 by_priority = _priority_table(items)
200 ordered = []
201 for k in sorted(by_priority.keys()):
202 rdatas = by_priority[k]
203 random.shuffle(rdatas)
204 ordered.extend(rdatas)
205 return ordered
206
207 _no_weight = 0.1
208
209 def weighted_processing_order(iterable):
210 items = list(iterable)
211 if len(items) == 1:
212 return items
213 by_priority = _priority_table(items)
214 ordered = []
215 for k in sorted(by_priority.keys()):
216 rdatas = by_priority[k]
217 total = sum(rdata._processing_weight() or _no_weight
218 for rdata in rdatas)
219 while len(rdatas) > 1:
220 r = random.uniform(0, total)
221 for (n, rdata) in enumerate(rdatas):
222 weight = rdata._processing_weight() or _no_weight
223 if weight > r:
224 break
225 r -= weight
226 total -= weight
227 ordered.append(rdata) # pylint: disable=undefined-loop-variable
228 del rdatas[n] # pylint: disable=undefined-loop-variable
229 ordered.append(rdatas[0])
230 return ordered
4242 import dns.tsig
4343
4444 if sys.platform == 'win32':
45 import winreg # pragma: no cover
45 # pylint: disable=import-error
46 import winreg
4647
4748 class NXDOMAIN(dns.exception.DNSException):
4849 """The DNS query name does not exist."""
4950 supp_kwargs = {'qnames', 'responses'}
5051 fmt = None # we have our own __str__ implementation
5152
52 def _check_kwargs(self, qnames, responses=None):
53 # pylint: disable=arguments-differ
54
55 def _check_kwargs(self, qnames,
56 responses=None):
5357 if not isinstance(qnames, (list, tuple, set)):
5458 raise AttributeError("qnames must be a list, tuple or set")
5559 if len(qnames) == 0:
7781 """Return the unresolved canonical name."""
7882 if 'qnames' not in self.kwargs:
7983 raise TypeError("parametrized exception required")
80 IN = dns.rdataclass.IN
81 CNAME = dns.rdatatype.CNAME
82 cname = None
8384 for qname in self.kwargs['qnames']:
8485 response = self.kwargs['responses'][qname]
85 for answer in response.answer:
86 if answer.rdtype != CNAME or answer.rdclass != IN:
87 continue
88 cname = answer[0].target.to_text()
89 if cname is not None:
90 return dns.name.from_text(cname)
86 try:
87 cname = response.canonical_name()
88 if cname != qname:
89 return cname
90 except Exception:
91 # We can just eat this exception as it means there was
92 # something wrong with the response.
93 pass
9194 return self.kwargs['qnames'][0]
9295
9396 def __add__(self, e_nx):
128131 class YXDOMAIN(dns.exception.DNSException):
129132 """The DNS query name is too long after DNAME substitution."""
130133
131 # The definition of the Timeout exception has moved from here to the
132 # dns.exception module. We keep dns.resolver.Timeout defined for
133 # backwards compatibility.
134
135 Timeout = dns.exception.Timeout
134
135 def _errors_to_text(errors):
136 """Turn a resolution errors trace into a list of text."""
137 texts = []
138 for err in errors:
139 texts.append('Server {} {} port {} answered {}'.format(err[0],
140 'TCP' if err[1] else 'UDP', err[2], err[3]))
141 return texts
142
143
144 class LifetimeTimeout(dns.exception.Timeout):
145 """The resolution lifetime expired."""
146
147 msg = "The resolution lifetime expired."
148 fmt = "%s after {timeout} seconds: {errors}" % msg[:-1]
149 supp_kwargs = {'timeout', 'errors'}
150
151 def _fmt_kwargs(self, **kwargs):
152 srv_msgs = _errors_to_text(kwargs['errors'])
153 return super()._fmt_kwargs(timeout=kwargs['timeout'],
154 errors='; '.join(srv_msgs))
155
156
157 # We added more detail to resolution timeouts, but they are still
158 # subclasses of dns.exception.Timeout for backwards compatibility. We also
159 # keep dns.resolver.Timeout defined for backwards compatibility.
160 Timeout = LifetimeTimeout
136161
137162
138163 class NoAnswer(dns.exception.DNSException):
159184 supp_kwargs = {'request', 'errors'}
160185
161186 def _fmt_kwargs(self, **kwargs):
162 srv_msgs = []
163 for err in kwargs['errors']:
164 srv_msgs.append('Server {} {} port {} answered {}'.format(err[0],
165 'TCP' if err[1] else 'UDP', err[2], err[3]))
187 srv_msgs = _errors_to_text(kwargs['errors'])
166188 return super()._fmt_kwargs(query=kwargs['request'].question,
167189 errors='; '.join(srv_msgs))
168190
205227 self.response = response
206228 self.nameserver = nameserver
207229 self.port = port
208 min_ttl = -1
209 rrset = None
210 for count in range(0, 15):
211 try:
212 rrset = response.find_rrset(response.answer, qname,
213 rdclass, rdtype)
214 if min_ttl == -1 or rrset.ttl < min_ttl:
215 min_ttl = rrset.ttl
216 break
217 except KeyError:
218 if rdtype != dns.rdatatype.CNAME:
219 try:
220 crrset = response.find_rrset(response.answer,
221 qname,
222 rdclass,
223 dns.rdatatype.CNAME)
224 if min_ttl == -1 or crrset.ttl < min_ttl:
225 min_ttl = crrset.ttl
226 for rd in crrset:
227 qname = rd.target
228 break
229 continue
230 except KeyError:
231 # Exit the chaining loop
232 break
233 self.canonical_name = qname
234 self.rrset = rrset
235 if rrset is None:
236 while 1:
237 # Look for a SOA RR whose owner name is a superdomain
238 # of qname.
239 try:
240 srrset = response.find_rrset(response.authority, qname,
241 rdclass, dns.rdatatype.SOA)
242 if min_ttl == -1 or srrset.ttl < min_ttl:
243 min_ttl = srrset.ttl
244 if srrset[0].minimum < min_ttl:
245 min_ttl = srrset[0].minimum
246 break
247 except KeyError:
248 try:
249 qname = qname.parent()
250 except dns.name.NoParent:
251 break
252 self.expiration = time.time() + min_ttl
230 self.chaining_result = response.resolve_chaining()
231 # Copy some attributes out of chaining_result for backwards
232 # compatibility and convenience.
233 self.canonical_name = self.chaining_result.canonical_name
234 self.rrset = self.chaining_result.answer
235 self.expiration = time.time() + self.chaining_result.minimum_ttl
253236
254237 def __getattr__(self, attr): # pragma: no cover
255238 if attr == 'name':
282265 del self.rrset[i]
283266
284267
285 class Cache:
268 class CacheStatistics:
269 """Cache Statistics
270 """
271
272 def __init__(self, hits=0, misses=0):
273 self.hits = hits
274 self.misses = misses
275
276 def reset(self):
277 self.hits = 0
278 self.misses = 0
279
280 def clone(self):
281 return CacheStatistics(self.hits, self.misses)
282
283
284 class CacheBase:
285 def __init__(self):
286 self.lock = _threading.Lock()
287 self.statistics = CacheStatistics()
288
289 def reset_statistics(self):
290 """Reset all statistics to zero."""
291 with self.lock:
292 self.statistics.reset()
293
294 def hits(self):
295 """How many hits has the cache had?"""
296 with self.lock:
297 return self.statistics.hits
298
299 def misses(self):
300 """How many misses has the cache had?"""
301 with self.lock:
302 return self.statistics.misses
303
304 def get_statistics_snapshot(self):
305 """Return a consistent snapshot of all the statistics.
306
307 If running with multiple threads, it's better to take a
308 snapshot than to call statistics methods such as hits() and
309 misses() individually.
310 """
311 with self.lock:
312 return self.statistics.clone()
313
314
315 class Cache(CacheBase):
286316 """Simple thread-safe DNS answer cache."""
287317
288318 def __init__(self, cleaning_interval=300.0):
290320 periodic cleanings.
291321 """
292322
323 super().__init__()
293324 self.data = {}
294325 self.cleaning_interval = cleaning_interval
295326 self.next_cleaning = time.time() + self.cleaning_interval
296 self.lock = _threading.Lock()
297327
298328 def _maybe_clean(self):
299329 """Clean the cache if it's time to do so."""
324354 self._maybe_clean()
325355 v = self.data.get(key)
326356 if v is None or v.expiration <= time.time():
357 self.statistics.misses += 1
327358 return None
359 self.statistics.hits += 1
328360 return v
329361
330362 def put(self, key, value):
365397 def __init__(self, key, value):
366398 self.key = key
367399 self.value = value
400 self.hits = 0
368401 self.prev = self
369402 self.next = self
370403
379412 self.prev.next = self.next
380413
381414
382 class LRUCache:
415 class LRUCache(CacheBase):
383416 """Thread-safe, bounded, least-recently-used DNS answer cache.
384417
385418 This cache is better than the simple cache (above) if you're
394427 it must be greater than 0.
395428 """
396429
430 super().__init__()
397431 self.data = {}
398432 self.set_max_size(max_size)
399433 self.sentinel = LRUCacheNode(None, None)
400434 self.sentinel.prev = self.sentinel
401435 self.sentinel.next = self.sentinel
402 self.lock = _threading.Lock()
403436
404437 def set_max_size(self, max_size):
405438 if max_size < 1:
420453 with self.lock:
421454 node = self.data.get(key)
422455 if node is None:
456 self.statistics.misses += 1
423457 return None
424458 # Unlink because we're either going to move the node to the front
425459 # of the LRU list or we're going to free it.
426460 node.unlink()
427461 if node.value.expiration <= time.time():
428462 del self.data[node.key]
463 self.statistics.misses += 1
429464 return None
430465 node.link_after(self.sentinel)
466 self.statistics.hits += 1
467 node.hits += 1
431468 return node.value
469
470 def get_hits_for_key(self, key):
471 """Return the number of cache hits associated with the specified key."""
472 with self.lock:
473 node = self.data.get(key)
474 if node is None or node.value.expiration <= time.time():
475 return 0
476 else:
477 return node.hits
432478
433479 def put(self, key, value):
434480 """Associate key and value in the cache.
631677 assert response is not None
632678 rcode = response.rcode()
633679 if rcode == dns.rcode.NOERROR:
634 answer = Answer(self.qname, self.rdtype, self.rdclass, response,
635 self.nameserver, self.port)
680 try:
681 answer = Answer(self.qname, self.rdtype, self.rdclass, response,
682 self.nameserver, self.port)
683 except Exception as e:
684 self.errors.append((self.nameserver, self.tcp_attempt,
685 self.port, e, response))
686 # The nameserver is no good, take it out of the mix.
687 self.nameservers.remove(self.nameserver)
688 return (None, False)
636689 if self.resolver.cache:
637690 self.resolver.cache.put((self.qname, self.rdtype,
638691 self.rdclass), answer)
640693 raise NoAnswer(response=answer.response)
641694 return (answer, True)
642695 elif rcode == dns.rcode.NXDOMAIN:
643 self.nxdomain_responses[self.qname] = response
644 # Make next_nameserver() return None, so caller breaks its
645 # inner loop and calls next_request().
646 if self.resolver.cache:
696 # Further validate the response by making an Answer, even
697 # if we aren't going to cache it.
698 try:
647699 answer = Answer(self.qname, dns.rdatatype.ANY,
648700 dns.rdataclass.IN, response)
701 except Exception as e:
702 self.errors.append((self.nameserver, self.tcp_attempt,
703 self.port, e, response))
704 # The nameserver is no good, take it out of the mix.
705 self.nameservers.remove(self.nameserver)
706 return (None, False)
707 self.nxdomain_responses[self.qname] = response
708 if self.resolver.cache:
649709 self.resolver.cache.put((self.qname,
650710 dns.rdatatype.ANY,
651711 self.rdclass), answer)
652
712 # Make next_nameserver() return None, so caller breaks its
713 # inner loop and calls next_request().
653714 return (None, True)
654715 elif rcode == dns.rcode.YXDOMAIN:
655716 yex = YXDOMAIN()
667728 dns.rcode.to_text(rcode), response))
668729 return (None, False)
669730
670 class Resolver:
731 class BaseResolver:
671732 """DNS stub resolver."""
672733
673734 # We initialize in reset()
689750 self.reset()
690751 if configure:
691752 if sys.platform == 'win32':
692 self.read_registry() # pragma: no cover
753 self.read_registry()
693754 elif filename:
694755 self.read_resolv_conf(filename)
695756
757818 self.nameservers.append(tokens[1])
758819 elif tokens[0] == 'domain':
759820 self.domain = dns.name.from_text(tokens[1])
821 # domain and search are exclusive
822 self.search = []
760823 elif tokens[0] == 'search':
824 # the last search wins
825 self.search = []
761826 for suffix in tokens[1:]:
762827 self.search.append(dns.name.from_text(suffix))
828 # We don't set domain as it is not used if
829 # len(self.search) > 0
763830 elif tokens[0] == 'options':
764831 for opt in tokens[1:]:
765832 if opt == 'rotate':
766833 self.rotate = True
767834 elif opt == 'edns0':
768 self.use_edns(0, 0, 0)
835 self.use_edns()
769836 elif 'timeout' in opt:
770837 try:
771838 self.timeout = int(opt.split(':')[1])
817884 self.search.append(dns.name.from_text(s))
818885
819886 def _config_win32_fromkey(self, key, always_try_domain):
887 # pylint: disable=undefined-variable
888 # (disabled for WindowsError)
820889 try:
821 servers, rtype = winreg.QueryValueEx(key, 'NameServer')
822 except WindowsError: # pylint: disable=undefined-variable
890 servers, _ = winreg.QueryValueEx(key, 'NameServer')
891 except WindowsError: # pragma: no cover
823892 servers = None
824893 if servers:
825894 self._config_win32_nameservers(servers)
826895 if servers or always_try_domain:
827896 try:
828 dom, rtype = winreg.QueryValueEx(key, 'Domain')
897 dom, _ = winreg.QueryValueEx(key, 'Domain')
829898 if dom:
830 self._config_win32_domain(dom)
899 self._config_win32_domain(dom) # pragma: no cover
831900 except WindowsError: # pragma: no cover
832901 pass
833902 else:
834903 try:
835 servers, rtype = winreg.QueryValueEx(key, 'DhcpNameServer')
904 servers, _ = winreg.QueryValueEx(key, 'DhcpNameServer')
836905 except WindowsError: # pragma: no cover
837906 servers = None
838 if servers: # pragma: no cover
907 if servers:
839908 self._config_win32_nameservers(servers)
840909 try:
841 dom, rtype = winreg.QueryValueEx(key, 'DhcpDomain')
842 if dom: # pragma: no cover
910 dom, _ = winreg.QueryValueEx(key, 'DhcpDomain')
911 if dom:
843912 self._config_win32_domain(dom)
844913 except WindowsError: # pragma: no cover
845914 pass
846915 try:
847 search, rtype = winreg.QueryValueEx(key, 'SearchList')
848 except WindowsError: # pylint: disable=undefined-variable
916 search, _ = winreg.QueryValueEx(key, 'SearchList')
917 except WindowsError: # pragma: no cover
849918 search = None
850919 if search: # pragma: no cover
851920 self._config_win32_search(search)
872941 try:
873942 guid = winreg.EnumKey(interfaces, i)
874943 i += 1
944 # XXXRTH why do we get this key and then not use it?
875945 key = winreg.OpenKey(interfaces, guid)
876946 if not self._win32_is_nic_enabled(lm, guid, key):
877947 continue
886956 finally:
887957 lm.Close()
888958
889 def _win32_is_nic_enabled(self, lm, guid,
890 interface_key):
959 def _win32_is_nic_enabled(self, lm, guid, _):
891960 # Look in the Windows Registry to determine whether the network
892961 # interface corresponding to the given guid is enabled.
893962 #
907976 (pnp_id, ttype) = winreg.QueryValueEx(
908977 connection_key, 'PnpInstanceID')
909978
910 if ttype != winreg.REG_SZ: # pragma: no cover
911 raise ValueError
979 if ttype != winreg.REG_SZ:
980 raise ValueError # pragma: no cover
912981
913982 device_key = winreg.OpenKey(
914983 lm, r'SYSTEM\CurrentControlSet\Enum\%s' % pnp_id)
918987 (flags, ttype) = winreg.QueryValueEx(
919988 device_key, 'ConfigFlags')
920989
921 if ttype != winreg.REG_DWORD: # pragma: no cover
922 raise ValueError
990 if ttype != winreg.REG_DWORD:
991 raise ValueError # pragma: no cover
923992
924993 # Based on experimentation, bit 0x1 indicates that the
925994 # device is disabled.
9321001 except Exception: # pragma: no cover
9331002 return False
9341003
935 def _compute_timeout(self, start, lifetime=None):
1004 def _compute_timeout(self, start, lifetime=None, errors=None):
9361005 lifetime = self.lifetime if lifetime is None else lifetime
9371006 now = time.time()
9381007 duration = now - start
1008 if errors is None:
1009 errors = []
9391010 if duration < 0:
9401011 if duration < -1:
9411012 # Time going backwards is bad. Just give up.
942 raise Timeout(timeout=duration)
1013 raise LifetimeTimeout(timeout=duration, errors=errors)
9431014 else:
9441015 # Time went backwards, but only a little. This can
9451016 # happen, e.g. under vmware with older linux kernels.
9461017 # Pretend it didn't happen.
9471018 now = start
9481019 if duration >= lifetime:
949 raise Timeout(timeout=duration)
1020 raise LifetimeTimeout(timeout=duration, errors=errors)
9501021 return min(lifetime - duration, self.timeout)
9511022
9521023 def _get_qnames_to_try(self, qname, search):
9581029 if qname.is_absolute():
9591030 qnames_to_try.append(qname)
9601031 else:
961 if len(qname) > 1 or not search:
962 qnames_to_try.append(qname.concatenate(dns.name.root))
963 if search and self.search:
964 for suffix in self.search:
965 if self.ndots is None or len(qname.labels) >= self.ndots:
966 qnames_to_try.append(qname.concatenate(suffix))
967 elif search:
968 qnames_to_try.append(qname.concatenate(self.domain))
1032 abs_qname = qname.concatenate(dns.name.root)
1033 if search:
1034 if len(self.search) > 0:
1035 # There is a search list, so use it exclusively
1036 search_list = self.search[:]
1037 elif self.domain != dns.name.root and self.domain is not None:
1038 # We have some notion of a domain that isn't the root, so
1039 # use it as the search list.
1040 search_list = [self.domain]
1041 else:
1042 search_list = []
1043 # Figure out the effective ndots (default is 1)
1044 if self.ndots is None:
1045 ndots = 1
1046 else:
1047 ndots = self.ndots
1048 for suffix in search_list:
1049 qnames_to_try.append(qname + suffix)
1050 if len(qname) > ndots:
1051 # The name has at least ndots dots, so we should try an
1052 # absolute query first.
1053 qnames_to_try.insert(0, abs_qname)
1054 else:
1055 # The name has less than ndots dots, so we should search
1056 # first, then try the absolute name.
1057 qnames_to_try.append(abs_qname)
1058 else:
1059 qnames_to_try.append(abs_qname)
9691060 return qnames_to_try
1061
1062 def use_tsig(self, keyring, keyname=None,
1063 algorithm=dns.tsig.default_algorithm):
1064 """Add a TSIG signature to each query.
1065
1066 The parameters are passed to ``dns.message.Message.use_tsig()``;
1067 see its documentation for details.
1068 """
1069
1070 self.keyring = keyring
1071 self.keyname = keyname
1072 self.keyalgorithm = algorithm
1073
1074 def use_edns(self, edns=0, ednsflags=0,
1075 payload=dns.message.DEFAULT_EDNS_PAYLOAD):
1076 """Configure EDNS behavior.
1077
1078 *edns*, an ``int``, is the EDNS level to use. Specifying
1079 ``None``, ``False``, or ``-1`` means "do not use EDNS", and in this case
1080 the other parameters are ignored. Specifying ``True`` is
1081 equivalent to specifying 0, i.e. "use EDNS0".
1082
1083 *ednsflags*, an ``int``, the EDNS flag values.
1084
1085 *payload*, an ``int``, is the EDNS sender's payload field, which is the
1086 maximum size of UDP datagram the sender can handle. I.e. how big
1087 a response to this message can be.
1088 """
1089
1090 if edns is None or edns is False:
1091 edns = -1
1092 elif edns is True:
1093 edns = 0
1094 self.edns = edns
1095 self.ednsflags = ednsflags
1096 self.payload = payload
1097
1098 def set_flags(self, flags):
1099 """Overrides the default flags with your own.
1100
1101 *flags*, an ``int``, the message flags to use.
1102 """
1103
1104 self.flags = flags
1105
1106 @property
1107 def nameservers(self):
1108 return self._nameservers
1109
1110 @nameservers.setter
1111 def nameservers(self, nameservers):
1112 """
1113 *nameservers*, a ``list`` of nameservers.
1114
1115 Raises ``ValueError`` if *nameservers* is anything other than a
1116 ``list``.
1117 """
1118 if isinstance(nameservers, list):
1119 self._nameservers = nameservers
1120 else:
1121 raise ValueError('nameservers must be a list'
1122 ' (not a {})'.format(type(nameservers)))
1123
1124
1125 class Resolver(BaseResolver):
1126 """DNS stub resolver."""
9701127
9711128 def resolve(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
9721129 tcp=False, source=None, raise_on_no_answer=True, source_port=0,
973 lifetime=None, search=None):
1130 lifetime=None, search=None): # pylint: disable=arguments-differ
9741131 """Query nameservers to find the answer to the question.
9751132
9761133 The *qname*, *rdtype*, and *rdclass* parameters may be objects
10031160 which causes the value of the resolver's
10041161 ``use_search_by_default`` attribute to be used.
10051162
1006 Raises ``dns.exception.Timeout`` if no answers could be found
1163 Raises ``dns.resolver.LifetimeTimeout`` if no answers could be found
10071164 in the specified lifetime.
10081165
10091166 Raises ``dns.resolver.NXDOMAIN`` if the query name does not exist.
10391196 (nameserver, port, tcp, backoff) = resolution.next_nameserver()
10401197 if backoff:
10411198 time.sleep(backoff)
1042 timeout = self._compute_timeout(start, lifetime)
1199 timeout = self._compute_timeout(start, lifetime,
1200 resolution.errors)
10431201 try:
10441202 if dns.inet.is_address(nameserver):
10451203 if tcp:
11081266 rdclass=dns.rdataclass.IN,
11091267 *args, **kwargs)
11101268
1111 def use_tsig(self, keyring, keyname=None,
1112 algorithm=dns.tsig.default_algorithm):
1113 """Add a TSIG signature to each query.
1114
1115 The parameters are passed to ``dns.message.Message.use_tsig()``;
1116 see its documentation for details.
1117 """
1118
1119 self.keyring = keyring
1120 self.keyname = keyname
1121 self.keyalgorithm = algorithm
1122
1123 def use_edns(self, edns, ednsflags, payload):
1124 """Configure EDNS behavior.
1125
1126 *edns*, an ``int``, is the EDNS level to use. Specifying
1127 ``None``, ``False``, or ``-1`` means "do not use EDNS", and in this case
1128 the other parameters are ignored. Specifying ``True`` is
1129 equivalent to specifying 0, i.e. "use EDNS0".
1130
1131 *ednsflags*, an ``int``, the EDNS flag values.
1132
1133 *payload*, an ``int``, is the EDNS sender's payload field, which is the
1134 maximum size of UDP datagram the sender can handle. I.e. how big
1135 a response to this message can be.
1136 """
1137
1138 if edns is None:
1139 edns = -1
1140 self.edns = edns
1141 self.ednsflags = ednsflags
1142 self.payload = payload
1143
1144 def set_flags(self, flags):
1145 """Overrides the default flags with your own.
1146
1147 *flags*, an ``int``, the message flags to use.
1148 """
1149
1150 self.flags = flags
1151
1152 @property
1153 def nameservers(self):
1154 return self._nameservers
1155
1156 @nameservers.setter
1157 def nameservers(self, nameservers):
1158 """
1159 *nameservers*, a ``list`` of nameservers.
1160
1161 Raises ``ValueError`` if *nameservers* is anything other than a
1162 ``list``.
1163 """
1164 if isinstance(nameservers, list):
1165 self._nameservers = nameservers
1166 else:
1167 raise ValueError('nameservers must be a list'
1168 ' (not a {})'.format(type(nameservers)))
1269 # pylint: disable=redefined-outer-name
1270
1271 def canonical_name(self, name):
1272 """Determine the canonical name of *name*.
1273
1274 The canonical name is the name the resolver uses for queries
1275 after all CNAME and DNAME renamings have been applied.
1276
1277 *name*, a ``dns.name.Name`` or ``str``, the query name.
1278
1279 This method can raise any exception that ``resolve()`` can
1280 raise, other than ``dns.resolver.NoAnswer`` and
1281 ``dns.resolver.NXDOMAIN``.
1282
1283 Returns a ``dns.name.Name``.
1284 """
1285 try:
1286 answer = self.resolve(name, raise_on_no_answer=False)
1287 canonical_name = answer.canonical_name
1288 except dns.resolver.NXDOMAIN as e:
1289 canonical_name = e.canonical_name
1290 return canonical_name
1291
1292 # pylint: enable=redefined-outer-name
1293
11691294
11701295 #: The default resolver.
11711296 default_resolver = None
12301355 """
12311356
12321357 return get_default_resolver().resolve_address(ipaddr, *args, **kwargs)
1358
1359
1360 def canonical_name(name):
1361 """Determine the canonical name of *name*.
1362
1363 See ``dns.resolver.Resolver.canonical_name`` for more information on the
1364 parameters and possible exceptions.
1365 """
1366
1367 return get_default_resolver().canonical_name(name)
12331368
12341369
12351370 def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None):
13141449 raise socket.gaierror(socket.EAI_NONAME, 'Name or service not known')
13151450 v6addrs = []
13161451 v4addrs = []
1317 canonical_name = None
1452 canonical_name = None # pylint: disable=redefined-outer-name
13181453 # Is host None or an address literal? If so, use the system's
13191454 # getaddrinfo().
13201455 if host is None:
13511486 v4addrs.append(rdata.address)
13521487 except dns.resolver.NXDOMAIN:
13531488 raise socket.gaierror(socket.EAI_NONAME, 'Name or service not known')
1354 except Exception as e:
1355 print(e)
1489 except Exception:
13561490 # We raise EAI_AGAIN here as the failure may be temporary
13571491 # (e.g. a timeout) and EAI_SYSTEM isn't defined on Windows.
13581492 # [Issue #416]
14811615 'Name or service not known')
14821616 sockaddr = (ip, 80)
14831617 family = socket.AF_INET
1484 (name, port) = _getnameinfo(sockaddr, socket.NI_NAMEREQD)
1618 (name, _) = _getnameinfo(sockaddr, socket.NI_NAMEREQD)
14851619 aliases = []
14861620 addresses = []
14871621 tuples = _getaddrinfo(name, 0, family, socket.SOCK_STREAM, socket.SOL_TCP,
6868 return self.to_text()
6969
7070 def __eq__(self, other):
71 if not isinstance(other, RRset):
72 return False
73 if self.name != other.name:
71 if isinstance(other, RRset):
72 if self.name != other.name:
73 return False
74 elif not isinstance(other, dns.rdataset.Rdataset):
7475 return False
7576 return super().__eq__(other)
7677
77 def match(self, name, rdclass, rdtype, covers, deleting=None):
78 """Returns ``True`` if this rrset matches the specified class, type,
79 covers, and deletion state.
80 """
81
78 def match(self, *args, **kwargs):
79 """Does this rrset match the specified attributes?
80
81 Behaves as :py:func:`full_match()` if the first argument is a
82 ``dns.name.Name``, and as :py:func:`dns.rdataset.Rdataset.match()`
83 otherwise.
84
85 (This behavior fixes a design mistake where the signature of this
86 method became incompatible with that of its superclass. The fix
87 makes RRsets matchable as Rdatasets while preserving backwards
88 compatibility.)
89 """
90 if isinstance(args[0], dns.name.Name):
91 return self.full_match(*args, **kwargs)
92 else:
93 return super().match(*args, **kwargs)
94
95 def full_match(self, name, rdclass, rdtype, covers,
96 deleting=None):
97 """Returns ``True`` if this rrset matches the specified name, class,
98 type, covers, and deletion state.
99 """
82100 if not super().match(rdclass, rdtype, covers):
83101 return False
84102 if self.name != name or self.deleting != deleting:
85103 return False
86104 return True
87105
106 # pylint: disable=arguments-differ
107
88108 def to_text(self, origin=None, relativize=True, **kw):
89 """Convert the RRset into DNS master file format.
109 """Convert the RRset into DNS zone file format.
90110
91111 See ``dns.name.Name.choose_relativity`` for more information
92112 on how *origin* and *relativize* determine the way names
105125 return super().to_text(self.name, origin, relativize,
106126 self.deleting, **kw)
107127
108 def to_wire(self, file, compress=None, origin=None, **kw):
128 def to_wire(self, file, compress=None, origin=None,
129 **kw):
109130 """Convert the RRset to wire format.
110131
111132 All keyword arguments are passed to ``dns.rdataset.to_wire()``; see
117138 return super().to_wire(self.name, file, compress, origin,
118139 self.deleting, **kw)
119140
141 # pylint: enable=arguments-differ
142
120143 def to_rdataset(self):
121144 """Convert an RRset into an Rdataset.
122145
126149
127150
128151 def from_text_list(name, ttl, rdclass, rdtype, text_rdatas,
129 idna_codec=None):
152 idna_codec=None, origin=None, relativize=True,
153 relativize_to=None):
130154 """Create an RRset with the specified name, TTL, class, and type, and with
131155 the specified list of rdatas in text format.
132156
133157 *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA
134158 encoder/decoder to use; if ``None``, the default IDNA 2003
135159 encoder/decoder is used.
160
161 *origin*, a ``dns.name.Name`` (or ``None``), the
162 origin to use for relative names.
163
164 *relativize*, a ``bool``. If true, name will be relativized.
165
166 *relativize_to*, a ``dns.name.Name`` (or ``None``), the origin to use
167 when relativizing names. If not set, the *origin* value will be used.
136168
137169 Returns a ``dns.rrset.RRset`` object.
138170 """
144176 r = RRset(name, rdclass, rdtype)
145177 r.update_ttl(ttl)
146178 for t in text_rdatas:
147 rd = dns.rdata.from_text(r.rdclass, r.rdtype, t, idna_codec=idna_codec)
179 rd = dns.rdata.from_text(r.rdclass, r.rdtype, t, origin, relativize,
180 relativize_to, idna_codec)
148181 r.add(rd)
149182 return r
150183
8383 subclasses.
8484 """
8585
86 cls = self.__class__
86 if hasattr(self, '_clone_class'):
87 cls = self._clone_class
88 else:
89 cls = self.__class__
8790 obj = cls.__new__(cls)
88 obj.items = self.items.copy()
91 obj.items = odict()
92 obj.items.update(self.items)
8993 return obj
9094
9195 def __copy__(self):
1414 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
17 """Tokenize DNS master file format"""
17 """Tokenize DNS zone file format"""
1818
1919 import io
2020 import sys
4040
4141
4242 class Token:
43 """A DNS master file format token.
43 """A DNS zone file format token.
4444
4545 ttype: The token type
4646 value: The token value
4747 has_escape: Does the token value contain escapes?
4848 """
4949
50 def __init__(self, ttype, value='', has_escape=False):
50 def __init__(self, ttype, value='', has_escape=False, comment=None):
5151 """Initialize a token instance."""
5252
5353 self.ttype = ttype
5454 self.value = value
5555 self.has_escape = has_escape
56 self.comment = comment
5657
5758 def is_eof(self):
5859 return self.ttype == EOF
103104 c = self.value[i]
104105 i += 1
105106 if c == '\\':
106 if i >= l:
107 if i >= l: # pragma: no cover (can't happen via get())
107108 raise dns.exception.UnexpectedEnd
108109 c = self.value[i]
109110 i += 1
118119 i += 1
119120 if not (c2.isdigit() and c3.isdigit()):
120121 raise dns.exception.SyntaxError
121 c = chr(int(c) * 100 + int(c2) * 10 + int(c3))
122 codepoint = int(c) * 100 + int(c2) * 10 + int(c3)
123 if codepoint > 255:
124 raise dns.exception.SyntaxError
125 c = chr(codepoint)
122126 unescaped += c
123127 return Token(self.ttype, unescaped)
124128
154158 c = self.value[i]
155159 i += 1
156160 if c == '\\':
157 if i >= l:
161 if i >= l: # pragma: no cover (can't happen via get())
158162 raise dns.exception.UnexpectedEnd
159163 c = self.value[i]
160164 i += 1
169173 i += 1
170174 if not (c2.isdigit() and c3.isdigit()):
171175 raise dns.exception.SyntaxError
172 unescaped += b'%c' % (int(c) * 100 + int(c2) * 10 + int(c3))
176 codepoint = int(c) * 100 + int(c2) * 10 + int(c3)
177 if codepoint > 255:
178 raise dns.exception.SyntaxError
179 unescaped += b'%c' % (codepoint)
173180 else:
174181 # Note that as mentioned above, if c is a Unicode
175182 # code point outside of the ASCII range, then this
183190
184191
185192 class Tokenizer:
186 """A DNS master file format tokenizer.
193 """A DNS zone file format tokenizer.
187194
188195 A token object is basically a (type, value) tuple. The valid
189196 types are EOF, EOL, WHITESPACE, IDENTIFIER, QUOTED_STRING,
395402 if self.multiline:
396403 raise dns.exception.SyntaxError(
397404 'unbalanced parentheses')
398 return Token(EOF)
405 return Token(EOF, comment=token)
399406 elif self.multiline:
400407 self.skip_whitespace()
401408 token = ''
402409 continue
403410 else:
404 return Token(EOL, '\n')
411 return Token(EOL, '\n', comment=token)
405412 else:
406413 # This code exists in case we ever want a
407414 # delimiter to be returned. It never produces
421428 token += c
422429 has_escape = True
423430 c = self._get_char()
424 if c == '' or c == '\n':
431 if c == '' or (c == '\n' and not self.quoting):
425432 raise dns.exception.UnexpectedEnd
426433 token += c
427434 if token == '' and ttype != QUOTED_STRING:
528535 '%d is not an unsigned 32-bit integer' % value)
529536 return value
530537
538 def get_uint48(self, base=10):
539 """Read the next token and interpret it as a 48-bit unsigned
540 integer.
541
542 Raises dns.exception.SyntaxError if not a 48-bit unsigned integer.
543
544 Returns an int.
545 """
546
547 value = self.get_int(base=base)
548 if value < 0 or value > 281474976710655:
549 raise dns.exception.SyntaxError(
550 '%d is not an unsigned 48-bit integer' % value)
551 return value
552
531553 def get_string(self, max_length=None):
532554 """Read the next token and interpret it as a string.
533555
558580 raise dns.exception.SyntaxError('expecting an identifier')
559581 return token.value
560582
583 def get_remaining(self, max_tokens=None):
584 """Return the remaining tokens on the line, until an EOL or EOF is seen.
585
586 max_tokens: If not None, stop after this number of tokens.
587
588 Returns a list of tokens.
589 """
590
591 tokens = []
592 while True:
593 token = self.get()
594 if token.is_eol_or_eof():
595 self.unget(token)
596 break
597 tokens.append(token)
598 if len(tokens) == max_tokens:
599 break
600 return tokens
601
561602 def concatenate_remaining_identifiers(self):
562603 """Read the remaining tokens on the line, which should be identifiers.
563604
571612 while True:
572613 token = self.get().unescape()
573614 if token.is_eol_or_eof():
615 self.unget(token)
574616 break
575617 if not token.is_identifier():
576618 raise dns.exception.SyntaxError
600642 token = self.get()
601643 return self.as_name(token, origin, relativize, relativize_to)
602644
603 def get_eol(self):
645 def get_eol_as_token(self):
604646 """Read the next token and raise an exception if it isn't EOL or
605647 EOF.
606648
612654 raise dns.exception.SyntaxError(
613655 'expected EOL or EOF, got %d "%s"' % (token.ttype,
614656 token.value))
615 return token.value
657 return token
658
659 def get_eol(self):
660 return self.get_eol_as_token().value
616661
617662 def get_ttl(self):
618663 """Read the next token and interpret it as a DNS TTL.
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import collections
3
4 import dns.exception
5 import dns.name
6 import dns.rdataclass
7 import dns.rdataset
8 import dns.rdatatype
9 import dns.rrset
10 import dns.serial
11 import dns.ttl
12
13
14 class TransactionManager:
15 def reader(self):
16 """Begin a read-only transaction."""
17 raise NotImplementedError # pragma: no cover
18
19 def writer(self, replacement=False):
20 """Begin a writable transaction.
21
22 *replacement*, a ``bool``. If `True`, the content of the
23 transaction completely replaces any prior content. If False,
24 the default, then the content of the transaction updates the
25 existing content.
26 """
27 raise NotImplementedError # pragma: no cover
28
29 def origin_information(self):
30 """Returns a tuple
31
32 (absolute_origin, relativize, effective_origin)
33
34 giving the absolute name of the default origin for any
35 relative domain names, the "effective origin", and whether
36 names should be relativized. The "effective origin" is the
37 absolute origin if relativize is False, and the empty name if
38 relativize is true. (The effective origin is provided even
39 though it can be computed from the absolute_origin and
40 relativize setting because it avoids a lot of code
41 duplication.)
42
43 If the returned names are `None`, then no origin information is
44 available.
45
46 This information is used by code working with transactions to
47 allow it to coordinate relativization. The transaction code
48 itself takes what it gets (i.e. does not change name
49 relativity).
50
51 """
52 raise NotImplementedError # pragma: no cover
53
54 def get_class(self):
55 """The class of the transaction manager.
56 """
57 raise NotImplementedError # pragma: no cover
58
59 def from_wire_origin(self):
60 """Origin to use in from_wire() calls.
61 """
62 (absolute_origin, relativize, _) = self.origin_information()
63 if relativize:
64 return absolute_origin
65 else:
66 return None
67
68
69 class DeleteNotExact(dns.exception.DNSException):
70 """Existing data did not match data specified by an exact delete."""
71
72
73 class ReadOnly(dns.exception.DNSException):
74 """Tried to write to a read-only transaction."""
75
76
77 class AlreadyEnded(dns.exception.DNSException):
78 """Tried to use an already-ended transaction."""
79
80
81 class Transaction:
82
83 def __init__(self, manager, replacement=False, read_only=False):
84 self.manager = manager
85 self.replacement = replacement
86 self.read_only = read_only
87 self._ended = False
88
89 #
90 # This is the high level API
91 #
92
93 def get(self, name, rdtype, covers=dns.rdatatype.NONE):
94 """Return the rdataset associated with *name*, *rdtype*, and *covers*,
95 or `None` if not found.
96
97 Note that the returned rdataset is immutable.
98 """
99 self._check_ended()
100 if isinstance(name, str):
101 name = dns.name.from_text(name, None)
102 rdtype = dns.rdatatype.RdataType.make(rdtype)
103 rdataset = self._get_rdataset(name, rdtype, covers)
104 if rdataset is not None and \
105 not isinstance(rdataset, dns.rdataset.ImmutableRdataset):
106 rdataset = dns.rdataset.ImmutableRdataset(rdataset)
107 return rdataset
108
109 def _check_read_only(self):
110 if self.read_only:
111 raise ReadOnly
112
113 def add(self, *args):
114 """Add records.
115
116 The arguments may be:
117
118 - rrset
119
120 - name, rdataset...
121
122 - name, ttl, rdata...
123 """
124 self._check_ended()
125 self._check_read_only()
126 return self._add(False, args)
127
128 def replace(self, *args):
129 """Replace the existing rdataset at the name with the specified
130 rdataset, or add the specified rdataset if there was no existing
131 rdataset.
132
133 The arguments may be:
134
135 - rrset
136
137 - name, rdataset...
138
139 - name, ttl, rdata...
140
141 Note that if you want to replace the entire node, you should do
142 a delete of the name followed by one or more calls to add() or
143 replace().
144 """
145 self._check_ended()
146 self._check_read_only()
147 return self._add(True, args)
148
149 def delete(self, *args):
150 """Delete records.
151
152 It is not an error if some of the records are not in the existing
153 set.
154
155 The arguments may be:
156
157 - rrset
158
159 - name
160
161 - name, rdataclass, rdatatype, [covers]
162
163 - name, rdataset...
164
165 - name, rdata...
166 """
167 self._check_ended()
168 self._check_read_only()
169 return self._delete(False, args)
170
171 def delete_exact(self, *args):
172 """Delete records.
173
174 The arguments may be:
175
176 - rrset
177
178 - name
179
180 - name, rdataclass, rdatatype, [covers]
181
182 - name, rdataset...
183
184 - name, rdata...
185
186 Raises dns.transaction.DeleteNotExact if some of the records
187 are not in the existing set.
188
189 """
190 self._check_ended()
191 self._check_read_only()
192 return self._delete(True, args)
193
194 def name_exists(self, name):
195 """Does the specified name exist?"""
196 self._check_ended()
197 if isinstance(name, str):
198 name = dns.name.from_text(name, None)
199 return self._name_exists(name)
200
201 def update_serial(self, value=1, relative=True, name=dns.name.empty):
202 """Update the serial number.
203
204 *value*, an `int`, is an increment if *relative* is `True`, or the
205 actual value to set if *relative* is `False`.
206
207 Raises `KeyError` if there is no SOA rdataset at *name*.
208
209 Raises `ValueError` if *value* is negative or if the increment is
210 so large that it would cause the new serial to be less than the
211 prior value.
212 """
213 self._check_ended()
214 if value < 0:
215 raise ValueError('negative update_serial() value')
216 if isinstance(name, str):
217 name = dns.name.from_text(name, None)
218 rdataset = self._get_rdataset(name, dns.rdatatype.SOA,
219 dns.rdatatype.NONE)
220 if rdataset is None or len(rdataset) == 0:
221 raise KeyError
222 if relative:
223 serial = dns.serial.Serial(rdataset[0].serial) + value
224 else:
225 serial = dns.serial.Serial(value)
226 serial = serial.value # convert back to int
227 if serial == 0:
228 serial = 1
229 rdata = rdataset[0].replace(serial=serial)
230 new_rdataset = dns.rdataset.from_rdata(rdataset.ttl, rdata)
231 self.replace(name, new_rdataset)
232
233 def __iter__(self):
234 self._check_ended()
235 return self._iterate_rdatasets()
236
237 def changed(self):
238 """Has this transaction changed anything?
239
240 For read-only transactions, the result is always `False`.
241
242 For writable transactions, the result is `True` if at some time
243 during the life of the transaction, the content was changed.
244 """
245 self._check_ended()
246 return self._changed()
247
248 def commit(self):
249 """Commit the transaction.
250
251 Normally transactions are used as context managers and commit
252 or rollback automatically, but it may be done explicitly if needed.
253 A ``dns.transaction.Ended`` exception will be raised if you try
254 to use a transaction after it has been committed or rolled back.
255
256 Raises an exception if the commit fails (in which case the transaction
257 is also rolled back.
258 """
259 self._end(True)
260
261 def rollback(self):
262 """Rollback the transaction.
263
264 Normally transactions are used as context managers and commit
265 or rollback automatically, but it may be done explicitly if needed.
266 A ``dns.transaction.AlreadyEnded`` exception will be raised if you try
267 to use a transaction after it has been committed or rolled back.
268
269 Rollback cannot otherwise fail.
270 """
271 self._end(False)
272
273 #
274 # Helper methods
275 #
276
277 def _raise_if_not_empty(self, method, args):
278 if len(args) != 0:
279 raise TypeError(f'extra parameters to {method}')
280
281 def _rdataset_from_args(self, method, deleting, args):
282 try:
283 arg = args.popleft()
284 if isinstance(arg, dns.rrset.RRset):
285 rdataset = arg.to_rdataset()
286 elif isinstance(arg, dns.rdataset.Rdataset):
287 rdataset = arg
288 else:
289 if deleting:
290 ttl = 0
291 else:
292 if isinstance(arg, int):
293 ttl = arg
294 if ttl > dns.ttl.MAX_TTL:
295 raise ValueError(f'{method}: TTL value too big')
296 else:
297 raise TypeError(f'{method}: expected a TTL')
298 arg = args.popleft()
299 if isinstance(arg, dns.rdata.Rdata):
300 rdataset = dns.rdataset.from_rdata(ttl, arg)
301 else:
302 raise TypeError(f'{method}: expected an Rdata')
303 return rdataset
304 except IndexError:
305 if deleting:
306 return None
307 else:
308 # reraise
309 raise TypeError(f'{method}: expected more arguments')
310
311 def _add(self, replace, args):
312 try:
313 args = collections.deque(args)
314 if replace:
315 method = 'replace()'
316 else:
317 method = 'add()'
318 arg = args.popleft()
319 if isinstance(arg, str):
320 arg = dns.name.from_text(arg, None)
321 if isinstance(arg, dns.name.Name):
322 name = arg
323 rdataset = self._rdataset_from_args(method, False, args)
324 elif isinstance(arg, dns.rrset.RRset):
325 rrset = arg
326 name = rrset.name
327 # rrsets are also rdatasets, but they don't print the
328 # same and can't be stored in nodes, so convert.
329 rdataset = rrset.to_rdataset()
330 else:
331 raise TypeError(f'{method} requires a name or RRset ' +
332 'as the first argument')
333 if rdataset.rdclass != self.manager.get_class():
334 raise ValueError(f'{method} has objects of wrong RdataClass')
335 if rdataset.rdtype == dns.rdatatype.SOA:
336 (_, _, origin) = self.manager.origin_information()
337 if name != origin:
338 raise ValueError(f'{method} has non-origin SOA')
339 self._raise_if_not_empty(method, args)
340 if not replace:
341 existing = self._get_rdataset(name, rdataset.rdtype,
342 rdataset.covers)
343 if existing is not None:
344 if isinstance(existing, dns.rdataset.ImmutableRdataset):
345 trds = dns.rdataset.Rdataset(existing.rdclass,
346 existing.rdtype,
347 existing.covers)
348 trds.update(existing)
349 existing = trds
350 rdataset = existing.union(rdataset)
351 self._put_rdataset(name, rdataset)
352 except IndexError:
353 raise TypeError(f'not enough parameters to {method}')
354
355 def _delete(self, exact, args):
356 try:
357 args = collections.deque(args)
358 if exact:
359 method = 'delete_exact()'
360 else:
361 method = 'delete()'
362 arg = args.popleft()
363 if isinstance(arg, str):
364 arg = dns.name.from_text(arg, None)
365 if isinstance(arg, dns.name.Name):
366 name = arg
367 if len(args) > 0 and (isinstance(args[0], int) or
368 isinstance(args[0], str)):
369 # deleting by type and (optionally) covers
370 rdtype = dns.rdatatype.RdataType.make(args.popleft())
371 if len(args) > 0:
372 covers = dns.rdatatype.RdataType.make(args.popleft())
373 else:
374 covers = dns.rdatatype.NONE
375 self._raise_if_not_empty(method, args)
376 existing = self._get_rdataset(name, rdtype, covers)
377 if existing is None:
378 if exact:
379 raise DeleteNotExact(f'{method}: missing rdataset')
380 else:
381 self._delete_rdataset(name, rdtype, covers)
382 return
383 else:
384 rdataset = self._rdataset_from_args(method, True, args)
385 elif isinstance(arg, dns.rrset.RRset):
386 rdataset = arg # rrsets are also rdatasets
387 name = rdataset.name
388 else:
389 raise TypeError(f'{method} requires a name or RRset ' +
390 'as the first argument')
391 self._raise_if_not_empty(method, args)
392 if rdataset:
393 if rdataset.rdclass != self.manager.get_class():
394 raise ValueError(f'{method} has objects of wrong '
395 'RdataClass')
396 existing = self._get_rdataset(name, rdataset.rdtype,
397 rdataset.covers)
398 if existing is not None:
399 if exact:
400 intersection = existing.intersection(rdataset)
401 if intersection != rdataset:
402 raise DeleteNotExact(f'{method}: missing rdatas')
403 rdataset = existing.difference(rdataset)
404 if len(rdataset) == 0:
405 self._delete_rdataset(name, rdataset.rdtype,
406 rdataset.covers)
407 else:
408 self._put_rdataset(name, rdataset)
409 elif exact:
410 raise DeleteNotExact(f'{method}: missing rdataset')
411 else:
412 if exact and not self._name_exists(name):
413 raise DeleteNotExact(f'{method}: name not known')
414 self._delete_name(name)
415 except IndexError:
416 raise TypeError(f'not enough parameters to {method}')
417
418 def _check_ended(self):
419 if self._ended:
420 raise AlreadyEnded
421
422 def _end(self, commit):
423 self._check_ended()
424 if self._ended:
425 raise AlreadyEnded
426 try:
427 self._end_transaction(commit)
428 finally:
429 self._ended = True
430
431 #
432 # Transactions are context managers.
433 #
434
435 def __enter__(self):
436 return self
437
438 def __exit__(self, exc_type, exc_val, exc_tb):
439 if not self._ended:
440 if exc_type is None:
441 self.commit()
442 else:
443 self.rollback()
444 return False
445
446 #
447 # This is the low level API, which must be implemented by subclasses
448 # of Transaction.
449 #
450
451 def _get_rdataset(self, name, rdtype, covers):
452 """Return the rdataset associated with *name*, *rdtype*, and *covers*,
453 or `None` if not found.
454 """
455 raise NotImplementedError # pragma: no cover
456
457 def _put_rdataset(self, name, rdataset):
458 """Store the rdataset."""
459 raise NotImplementedError # pragma: no cover
460
461 def _delete_name(self, name):
462 """Delete all data associated with *name*.
463
464 It is not an error if the rdataset does not exist.
465 """
466 raise NotImplementedError # pragma: no cover
467
468 def _delete_rdataset(self, name, rdtype, covers):
469 """Delete all data associated with *name*, *rdtype*, and *covers*.
470
471 It is not an error if the rdataset does not exist.
472 """
473 raise NotImplementedError # pragma: no cover
474
475 def _name_exists(self, name):
476 """Does name exist?
477
478 Returns a bool.
479 """
480 raise NotImplementedError # pragma: no cover
481
482 def _changed(self):
483 """Has this transaction changed anything?"""
484 raise NotImplementedError # pragma: no cover
485
486 def _end_transaction(self, commit):
487 """End the transaction.
488
489 *commit*, a bool. If ``True``, commit the transaction, otherwise
490 roll it back.
491
492 If committing adn the commit fails, then roll back and raise an
493 exception.
494 """
495 raise NotImplementedError # pragma: no cover
496
497 def _set_origin(self, origin):
498 """Set the origin.
499
500 This method is called when reading a possibly relativized
501 source, and an origin setting operation occurs (e.g. $ORIGIN
502 in a zone file).
503 """
504 raise NotImplementedError # pragma: no cover
505
506 def _iterate_rdatasets(self):
507 """Return an iterator that yields (name, rdataset) tuples.
508
509 Not all Transaction subclasses implement this.
510 """
511 raise NotImplementedError # pragma: no cover
7070
7171 """The peer didn't like amount of truncation in the TSIG we sent"""
7272
73
7374 # TSIG Algorithms
7475
7576 HMAC_MD5 = dns.name.from_text("HMAC-MD5.SIG-ALG.REG.INT")
7677 HMAC_SHA1 = dns.name.from_text("hmac-sha1")
7778 HMAC_SHA224 = dns.name.from_text("hmac-sha224")
7879 HMAC_SHA256 = dns.name.from_text("hmac-sha256")
80 HMAC_SHA256_128 = dns.name.from_text("hmac-sha256-128")
7981 HMAC_SHA384 = dns.name.from_text("hmac-sha384")
82 HMAC_SHA384_192 = dns.name.from_text("hmac-sha384-192")
8083 HMAC_SHA512 = dns.name.from_text("hmac-sha512")
81
82 _hashes = {
83 HMAC_SHA224: hashlib.sha224,
84 HMAC_SHA256: hashlib.sha256,
85 HMAC_SHA384: hashlib.sha384,
86 HMAC_SHA512: hashlib.sha512,
87 HMAC_SHA1: hashlib.sha1,
88 HMAC_MD5: hashlib.md5,
89 }
84 HMAC_SHA512_256 = dns.name.from_text("hmac-sha512-256")
85 GSS_TSIG = dns.name.from_text("gss-tsig")
9086
9187 default_algorithm = HMAC_SHA256
88
89
90 class GSSTSig:
91 """
92 GSS-TSIG TSIG implementation. This uses the GSS-API context established
93 in the TKEY message handshake to sign messages using GSS-API message
94 integrity codes, per the RFC.
95
96 In order to avoid a direct GSSAPI dependency, the keyring holds a ref
97 to the GSSAPI object required, rather than the key itself.
98 """
99 def __init__(self, gssapi_context):
100 self.gssapi_context = gssapi_context
101 self.data = b''
102 self.name = 'gss-tsig'
103
104 def update(self, data):
105 self.data += data
106
107 def sign(self):
108 # defer to the GSSAPI function to sign
109 return self.gssapi_context.get_signature(self.data)
110
111 def verify(self, expected):
112 try:
113 # defer to the GSSAPI function to verify
114 return self.gssapi_context.verify_signature(self.data, expected)
115 except Exception:
116 # note the usage of a bare exception
117 raise BadSignature
118
119
120 class GSSTSigAdapter:
121 def __init__(self, keyring):
122 self.keyring = keyring
123
124 def __call__(self, message, keyname):
125 if keyname in self.keyring:
126 key = self.keyring[keyname]
127 if isinstance(key, Key) and key.algorithm == GSS_TSIG:
128 if message:
129 GSSTSigAdapter.parse_tkey_and_step(key, message, keyname)
130 return key
131 else:
132 return None
133
134 @classmethod
135 def parse_tkey_and_step(cls, key, message, keyname):
136 # if the message is a TKEY type, absorb the key material
137 # into the context using step(); this is used to allow the
138 # client to complete the GSSAPI negotiation before attempting
139 # to verify the signed response to a TKEY message exchange
140 try:
141 rrset = message.find_rrset(message.answer, keyname,
142 dns.rdataclass.ANY,
143 dns.rdatatype.TKEY)
144 if rrset:
145 token = rrset[0].key
146 gssapi_context = key.secret
147 return gssapi_context.step(token)
148 except KeyError:
149 pass
150
151
152 class HMACTSig:
153 """
154 HMAC TSIG implementation. This uses the HMAC python module to handle the
155 sign/verify operations.
156 """
157
158 _hashes = {
159 HMAC_SHA1: hashlib.sha1,
160 HMAC_SHA224: hashlib.sha224,
161 HMAC_SHA256: hashlib.sha256,
162 HMAC_SHA256_128: (hashlib.sha256, 128),
163 HMAC_SHA384: hashlib.sha384,
164 HMAC_SHA384_192: (hashlib.sha384, 192),
165 HMAC_SHA512: hashlib.sha512,
166 HMAC_SHA512_256: (hashlib.sha512, 256),
167 HMAC_MD5: hashlib.md5,
168 }
169
170 def __init__(self, key, algorithm):
171 try:
172 hashinfo = self._hashes[algorithm]
173 except KeyError:
174 raise NotImplementedError(f"TSIG algorithm {algorithm} " +
175 "is not supported")
176
177 # create the HMAC context
178 if isinstance(hashinfo, tuple):
179 self.hmac_context = hmac.new(key, digestmod=hashinfo[0])
180 self.size = hashinfo[1]
181 else:
182 self.hmac_context = hmac.new(key, digestmod=hashinfo)
183 self.size = None
184 self.name = self.hmac_context.name
185 if self.size:
186 self.name += f'-{self.size}'
187
188 def update(self, data):
189 return self.hmac_context.update(data)
190
191 def sign(self):
192 # defer to the HMAC digest() function for that digestmod
193 digest = self.hmac_context.digest()
194 if self.size:
195 digest = digest[: (self.size // 8)]
196 return digest
197
198 def verify(self, expected):
199 # re-digest and compare the results
200 mac = self.sign()
201 if not hmac.compare_digest(mac, expected):
202 raise BadSignature
92203
93204
94205 def _digest(wire, key, rdata, time=None, request_mac=None, ctx=None,
95206 multi=None):
96207 """Return a context containing the TSIG rdata for the input parameters
97 @rtype: hmac.HMAC object
208 @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object
98209 @raises ValueError: I{other_data} is too long
99210 @raises NotImplementedError: I{algorithm} is not supported
100211 """
130241 def _maybe_start_digest(key, mac, multi):
131242 """If this is the first message in a multi-message sequence,
132243 start a new context.
133 @rtype: hmac.HMAC object
244 @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object
134245 """
135246 if multi:
136247 ctx = get_context(key)
145256 """Return a (tsig_rdata, mac, ctx) tuple containing the HMAC TSIG rdata
146257 for the input parameters, the HMAC MAC calculated by applying the
147258 TSIG signature algorithm, and the TSIG digest context.
148 @rtype: (string, hmac.HMAC object)
259 @rtype: (string, dns.tsig.HMACTSig or dns.tsig.GSSTSig object)
149260 @raises ValueError: I{other_data} is too long
150261 @raises NotImplementedError: I{algorithm} is not supported
151262 """
152263
153264 ctx = _digest(wire, key, rdata, time, request_mac, ctx, multi)
154 mac = ctx.digest()
155 tsig = dns.rdtypes.ANY.TSIG.TSIG(dns.rdataclass.ANY, dns.rdatatype.TSIG,
156 key.algorithm, time, rdata.fudge, mac,
157 rdata.original_id, rdata.error,
158 rdata.other)
265 mac = ctx.sign()
266 tsig = rdata.replace(time_signed=time, mac=mac)
159267
160268 return (tsig, _maybe_start_digest(key, mac, multi))
161269
168276 @raises BadTime: There is too much time skew between the client and the
169277 server.
170278 @raises BadSignature: The TSIG signature did not validate
171 @rtype: hmac.HMAC object"""
279 @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object"""
172280
173281 (adcount,) = struct.unpack("!H", wire[10:12])
174282 if adcount == 0:
193301 if key.algorithm != rdata.algorithm:
194302 raise BadAlgorithm
195303 ctx = _digest(new_wire, key, rdata, None, request_mac, ctx, multi)
196 mac = ctx.digest()
197 if not hmac.compare_digest(mac, rdata.mac):
198 raise BadSignature
199 return _maybe_start_digest(key, mac, multi)
304 ctx.verify(rdata.mac)
305 return _maybe_start_digest(key, rdata.mac, multi)
200306
201307
202308 def get_context(key):
203 """Returns an HMAC context foe the specified key.
309 """Returns an HMAC context for the specified key.
204310
205311 @rtype: HMAC context
206312 @raises NotImplementedError: I{algorithm} is not supported
207313 """
208314
209 try:
210 digestmod = _hashes[key.algorithm]
211 except KeyError:
212 raise NotImplementedError(f"TSIG algorithm {key.algorithm} " +
213 "is not supported")
214 return hmac.new(key.secret, digestmod=digestmod)
315 if key.algorithm == GSS_TSIG:
316 return GSSTSig(key.secret)
317 else:
318 return HMACTSig(key.secret, key.algorithm)
215319
216320
217321 class Key:
231335 self.name == other.name and
232336 self.secret == other.secret and
233337 self.algorithm == other.algorithm)
338
339 def __repr__(self):
340 r = f"<DNS key name='{self.name}', " + \
341 f"algorithm='{self.algorithm}'"
342 if self.algorithm != GSS_TSIG:
343 r += f", secret='{base64.b64encode(self.secret).decode()}'"
344 r += ">"
345 return r
5454 if isinstance(key, bytes):
5555 textring[name] = b64encode(key)
5656 else:
57 textring[name] = (key.algorithm.to_text(), b64encode(key.secret))
57 if isinstance(key.secret, bytes):
58 text_secret = b64encode(key.secret)
59 else:
60 text_secret = str(key.secret)
61
62 textring[name] = (key.algorithm.to_text(), text_secret)
5863 return textring
1818
1919 import dns.exception
2020
21 MAX_TTL = 2147483647
2122
2223 class BadTTL(dns.exception.SyntaxError):
2324 """DNS TTL value is not well-formed."""
3738
3839 if text.isdigit():
3940 total = int(text)
41 elif len(text) == 0:
42 raise BadTTL
4043 else:
41 if not text[0].isdigit():
42 raise BadTTL
4344 total = 0
4445 current = 0
46 need_digit = True
4547 for c in text:
4648 if c.isdigit():
4749 current *= 10
4850 current += int(c)
51 need_digit = False
4952 else:
53 if need_digit:
54 raise BadTTL
5055 c = c.lower()
5156 if c == 'w':
5257 total += current * 604800
6166 else:
6267 raise BadTTL("unknown unit '%s'" % c)
6368 current = 0
69 need_digit = True
6470 if not current == 0:
6571 raise BadTTL("trailing integer")
66 if total < 0 or total > 2147483647:
72 if total < 0 or total > MAX_TTL:
6773 raise BadTTL("TTL should be between 0 and 2^31 - 1 (inclusive)")
6874 return total
75
76
77 def make(value):
78 if isinstance(value, int):
79 return value
80 elif isinstance(value, str):
81 return dns.ttl.from_text(value)
82 else:
83 raise ValueError('cannot convert value to TTL')
3636 @classmethod
3737 def _maximum(cls):
3838 return 3
39
40 globals().update(UpdateSection.__members__)
4139
4240
4341 class UpdateMessage(dns.message.Message):
309307
310308 # backwards compatibility
311309 Update = UpdateMessage
310
311 ### BEGIN generated UpdateSection constants
312
313 ZONE = UpdateSection.ZONE
314 PREREQ = UpdateSection.PREREQ
315 UPDATE = UpdateSection.UPDATE
316 ADDITIONAL = UpdateSection.ADDITIONAL
317
318 ### END generated UpdateSection constants
1919 #: MAJOR
2020 MAJOR = 2
2121 #: MINOR
22 MINOR = 0
22 MINOR = 2
2323 #: MICRO
2424 MICRO = 0
2525 #: RELEASELEVEL
26 RELEASELEVEL = 0x0f
26 RELEASELEVEL = 0x00
2727 #: SERIAL
2828 SERIAL = 0
2929
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 """DNS Versioned Zones."""
3
4 import collections
5 try:
6 import threading as _threading
7 except ImportError: # pragma: no cover
8 import dummy_threading as _threading # type: ignore
9
10 import dns.exception
11 import dns.immutable
12 import dns.name
13 import dns.node
14 import dns.rdataclass
15 import dns.rdatatype
16 import dns.rdata
17 import dns.rdtypes.ANY.SOA
18 import dns.transaction
19 import dns.zone
20
21
22 class UseTransaction(dns.exception.DNSException):
23 """To alter a versioned zone, use a transaction."""
24
25
26 class Version:
27 def __init__(self, zone, id):
28 self.zone = zone
29 self.id = id
30 self.nodes = {}
31
32 def _validate_name(self, name):
33 if name.is_absolute():
34 if not name.is_subdomain(self.zone.origin):
35 raise KeyError("name is not a subdomain of the zone origin")
36 if self.zone.relativize:
37 name = name.relativize(self.origin)
38 return name
39
40 def get_node(self, name):
41 name = self._validate_name(name)
42 return self.nodes.get(name)
43
44 def get_rdataset(self, name, rdtype, covers):
45 node = self.get_node(name)
46 if node is None:
47 return None
48 return node.get_rdataset(self.zone.rdclass, rdtype, covers)
49
50 def items(self):
51 return self.nodes.items() # pylint: disable=dict-items-not-iterating
52
53
54 class WritableVersion(Version):
55 def __init__(self, zone, replacement=False):
56 # The zone._versions_lock must be held by our caller.
57 if len(zone._versions) > 0:
58 id = zone._versions[-1].id + 1
59 else:
60 id = 1
61 super().__init__(zone, id)
62 if not replacement:
63 # We copy the map, because that gives us a simple and thread-safe
64 # way of doing versions, and we have a garbage collector to help
65 # us. We only make new node objects if we actually change the
66 # node.
67 self.nodes.update(zone.nodes)
68 # We have to copy the zone origin as it may be None in the first
69 # version, and we don't want to mutate the zone until we commit.
70 self.origin = zone.origin
71 self.changed = set()
72
73 def _maybe_cow(self, name):
74 name = self._validate_name(name)
75 node = self.nodes.get(name)
76 if node is None or node.id != self.id:
77 new_node = self.zone.node_factory()
78 new_node.id = self.id
79 if node is not None:
80 # moo! copy on write!
81 new_node.rdatasets.extend(node.rdatasets)
82 self.nodes[name] = new_node
83 self.changed.add(name)
84 return new_node
85 else:
86 return node
87
88 def delete_node(self, name):
89 name = self._validate_name(name)
90 if name in self.nodes:
91 del self.nodes[name]
92 self.changed.add(name)
93
94 def put_rdataset(self, name, rdataset):
95 node = self._maybe_cow(name)
96 node.replace_rdataset(rdataset)
97
98 def delete_rdataset(self, name, rdtype, covers):
99 node = self._maybe_cow(name)
100 node.delete_rdataset(self.zone.rdclass, rdtype, covers)
101 if len(node) == 0:
102 del self.nodes[name]
103
104
105 @dns.immutable.immutable
106 class ImmutableVersion(Version):
107 def __init__(self, version):
108 # We tell super() that it's a replacement as we don't want it
109 # to copy the nodes, as we're about to do that with an
110 # immutable Dict.
111 super().__init__(version.zone, True)
112 # set the right id!
113 self.id = version.id
114 # Make changed nodes immutable
115 for name in version.changed:
116 node = version.nodes.get(name)
117 # it might not exist if we deleted it in the version
118 if node:
119 version.nodes[name] = ImmutableNode(node)
120 self.nodes = dns.immutable.Dict(version.nodes, True)
121
122
123 # A node with a version id.
124
125 class Node(dns.node.Node):
126 __slots__ = ['id']
127
128 def __init__(self):
129 super().__init__()
130 # A proper id will get set by the Version
131 self.id = 0
132
133
134 @dns.immutable.immutable
135 class ImmutableNode(Node):
136 __slots__ = ['id']
137
138 def __init__(self, node):
139 super().__init__()
140 self.id = node.id
141 self.rdatasets = tuple(
142 [dns.rdataset.ImmutableRdataset(rds) for rds in node.rdatasets]
143 )
144
145 def find_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
146 create=False):
147 if create:
148 raise TypeError("immutable")
149 return super().find_rdataset(rdclass, rdtype, covers, False)
150
151 def get_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE,
152 create=False):
153 if create:
154 raise TypeError("immutable")
155 return super().get_rdataset(rdclass, rdtype, covers, False)
156
157 def delete_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE):
158 raise TypeError("immutable")
159
160 def replace_rdataset(self, replacement):
161 raise TypeError("immutable")
162
163
164 class Zone(dns.zone.Zone):
165
166 __slots__ = ['_versions', '_versions_lock', '_write_txn',
167 '_write_waiters', '_write_event', '_pruning_policy',
168 '_readers']
169
170 node_factory = Node
171
172 def __init__(self, origin, rdclass=dns.rdataclass.IN, relativize=True,
173 pruning_policy=None):
174 """Initialize a versioned zone object.
175
176 *origin* is the origin of the zone. It may be a ``dns.name.Name``,
177 a ``str``, or ``None``. If ``None``, then the zone's origin will
178 be set by the first ``$ORIGIN`` line in a zone file.
179
180 *rdclass*, an ``int``, the zone's rdata class; the default is class IN.
181
182 *relativize*, a ``bool``, determine's whether domain names are
183 relativized to the zone's origin. The default is ``True``.
184
185 *pruning policy*, a function taking a `Version` and returning
186 a `bool`, or `None`. Should the version be pruned? If `None`,
187 the default policy, which retains one version is used.
188 """
189 super().__init__(origin, rdclass, relativize)
190 self._versions = collections.deque()
191 self._version_lock = _threading.Lock()
192 if pruning_policy is None:
193 self._pruning_policy = self._default_pruning_policy
194 else:
195 self._pruning_policy = pruning_policy
196 self._write_txn = None
197 self._write_event = None
198 self._write_waiters = collections.deque()
199 self._readers = set()
200 self._commit_version_unlocked(None, WritableVersion(self), origin)
201
202 def reader(self, id=None, serial=None): # pylint: disable=arguments-differ
203 if id is not None and serial is not None:
204 raise ValueError('cannot specify both id and serial')
205 with self._version_lock:
206 if id is not None:
207 version = None
208 for v in reversed(self._versions):
209 if v.id == id:
210 version = v
211 break
212 if version is None:
213 raise KeyError('version not found')
214 elif serial is not None:
215 if self.relativize:
216 oname = dns.name.empty
217 else:
218 oname = self.origin
219 version = None
220 for v in reversed(self._versions):
221 n = v.nodes.get(oname)
222 if n:
223 rds = n.get_rdataset(self.rdclass, dns.rdatatype.SOA)
224 if rds and rds[0].serial == serial:
225 version = v
226 break
227 if version is None:
228 raise KeyError('serial not found')
229 else:
230 version = self._versions[-1]
231 txn = Transaction(self, False, version)
232 self._readers.add(txn)
233 return txn
234
235 def writer(self, replacement=False):
236 event = None
237 while True:
238 with self._version_lock:
239 # Checking event == self._write_event ensures that either
240 # no one was waiting before we got lucky and found no write
241 # txn, or we were the one who was waiting and got woken up.
242 # This prevents "taking cuts" when creating a write txn.
243 if self._write_txn is None and event == self._write_event:
244 # Creating the transaction defers version setup
245 # (i.e. copying the nodes dictionary) until we
246 # give up the lock, so that we hold the lock as
247 # short a time as possible. This is why we call
248 # _setup_version() below.
249 self._write_txn = Transaction(self, replacement)
250 # give up our exclusive right to make a Transaction
251 self._write_event = None
252 break
253 # Someone else is writing already, so we will have to
254 # wait, but we want to do the actual wait outside the
255 # lock.
256 event = _threading.Event()
257 self._write_waiters.append(event)
258 # wait (note we gave up the lock!)
259 #
260 # We only wake one sleeper at a time, so it's important
261 # that no event waiter can exit this method (e.g. via
262 # cancelation) without returning a transaction or waking
263 # someone else up.
264 #
265 # This is not a problem with Threading module threads as
266 # they cannot be canceled, but could be an issue with trio
267 # or curio tasks when we do the async version of writer().
268 # I.e. we'd need to do something like:
269 #
270 # try:
271 # event.wait()
272 # except trio.Cancelled:
273 # with self._version_lock:
274 # self._maybe_wakeup_one_waiter_unlocked()
275 # raise
276 #
277 event.wait()
278 # Do the deferred version setup.
279 self._write_txn._setup_version()
280 return self._write_txn
281
282 def _maybe_wakeup_one_waiter_unlocked(self):
283 if len(self._write_waiters) > 0:
284 self._write_event = self._write_waiters.popleft()
285 self._write_event.set()
286
287 # pylint: disable=unused-argument
288 def _default_pruning_policy(self, zone, version):
289 return True
290 # pylint: enable=unused-argument
291
292 def _prune_versions_unlocked(self):
293 assert len(self._versions) > 0
294 # Don't ever prune a version greater than or equal to one that
295 # a reader has open. This pins versions in memory while the
296 # reader is open, and importantly lets the reader open a txn on
297 # a successor version (e.g. if generating an IXFR).
298 #
299 # Note our definition of least_kept also ensures we do not try to
300 # delete the greatest version.
301 if len(self._readers) > 0:
302 least_kept = min(txn.version.id for txn in self._readers)
303 else:
304 least_kept = self._versions[-1].id
305 while self._versions[0].id < least_kept and \
306 self._pruning_policy(self, self._versions[0]):
307 self._versions.popleft()
308
309 def set_max_versions(self, max_versions):
310 """Set a pruning policy that retains up to the specified number
311 of versions
312 """
313 if max_versions is not None and max_versions < 1:
314 raise ValueError('max versions must be at least 1')
315 if max_versions is None:
316 def policy(*_):
317 return False
318 else:
319 def policy(zone, _):
320 return len(zone._versions) > max_versions
321 self.set_pruning_policy(policy)
322
323 def set_pruning_policy(self, policy):
324 """Set the pruning policy for the zone.
325
326 The *policy* function takes a `Version` and returns `True` if
327 the version should be pruned, and `False` otherwise. `None`
328 may also be specified for policy, in which case the default policy
329 is used.
330
331 Pruning checking proceeds from the least version and the first
332 time the function returns `False`, the checking stops. I.e. the
333 retained versions are always a consecutive sequence.
334 """
335 if policy is None:
336 policy = self._default_pruning_policy
337 with self._version_lock:
338 self._pruning_policy = policy
339 self._prune_versions_unlocked()
340
341 def _end_read(self, txn):
342 with self._version_lock:
343 self._readers.remove(txn)
344 self._prune_versions_unlocked()
345
346 def _end_write_unlocked(self, txn):
347 assert self._write_txn == txn
348 self._write_txn = None
349 self._maybe_wakeup_one_waiter_unlocked()
350
351 def _end_write(self, txn):
352 with self._version_lock:
353 self._end_write_unlocked(txn)
354
355 def _commit_version_unlocked(self, txn, version, origin):
356 self._versions.append(version)
357 self._prune_versions_unlocked()
358 self.nodes = version.nodes
359 if self.origin is None:
360 self.origin = origin
361 # txn can be None in __init__ when we make the empty version.
362 if txn is not None:
363 self._end_write_unlocked(txn)
364
365 def _commit_version(self, txn, version, origin):
366 with self._version_lock:
367 self._commit_version_unlocked(txn, version, origin)
368
369 def find_node(self, name, create=False):
370 if create:
371 raise UseTransaction
372 return super().find_node(name)
373
374 def delete_node(self, name):
375 raise UseTransaction
376
377 def find_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE,
378 create=False):
379 if create:
380 raise UseTransaction
381 rdataset = super().find_rdataset(name, rdtype, covers)
382 return dns.rdataset.ImmutableRdataset(rdataset)
383
384 def get_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE,
385 create=False):
386 if create:
387 raise UseTransaction
388 rdataset = super().get_rdataset(name, rdtype, covers)
389 return dns.rdataset.ImmutableRdataset(rdataset)
390
391 def delete_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE):
392 raise UseTransaction
393
394 def replace_rdataset(self, name, replacement):
395 raise UseTransaction
396
397
398 class Transaction(dns.transaction.Transaction):
399
400 def __init__(self, zone, replacement, version=None):
401 read_only = version is not None
402 super().__init__(zone, replacement, read_only)
403 self.version = version
404
405 @property
406 def zone(self):
407 return self.manager
408
409 def _setup_version(self):
410 assert self.version is None
411 self.version = WritableVersion(self.zone, self.replacement)
412
413 def _get_rdataset(self, name, rdtype, covers):
414 return self.version.get_rdataset(name, rdtype, covers)
415
416 def _put_rdataset(self, name, rdataset):
417 assert not self.read_only
418 self.version.put_rdataset(name, rdataset)
419
420 def _delete_name(self, name):
421 assert not self.read_only
422 self.version.delete_node(name)
423
424 def _delete_rdataset(self, name, rdtype, covers):
425 assert not self.read_only
426 self.version.delete_rdataset(name, rdtype, covers)
427
428 def _name_exists(self, name):
429 return self.version.get_node(name) is not None
430
431 def _changed(self):
432 if self.read_only:
433 return False
434 else:
435 return len(self.version.changed) > 0
436
437 def _end_transaction(self, commit):
438 if self.read_only:
439 self.zone._end_read(self)
440 elif commit and len(self.version.changed) > 0:
441 self.zone._commit_version(self, ImmutableVersion(self.version),
442 self.version.origin)
443 else:
444 # rollback
445 self.zone._end_write(self)
446
447 def _set_origin(self, origin):
448 if self.version.origin is None:
449 self.version.origin = origin
450
451 def _iterate_rdatasets(self):
452 for (name, node) in self.version.items():
453 for rdataset in node:
454 yield (name, rdataset)
4141 def get_uint32(self):
4242 return struct.unpack('!I', self.get_bytes(4))[0]
4343
44 def get_uint48(self):
45 return int.from_bytes(self.get_bytes(6), 'big')
46
4447 def get_struct(self, format):
4548 return struct.unpack(format, self.get_bytes(struct.calcsize(format)))
4649
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 # Copyright (C) 2003-2017 Nominum, Inc.
3 #
4 # Permission to use, copy, modify, and distribute this software and its
5 # documentation for any purpose with or without fee is hereby granted,
6 # provided that the above copyright notice and this permission notice
7 # appear in all copies.
8 #
9 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
12 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
15 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17 import dns.exception
18 import dns.message
19 import dns.name
20 import dns.rcode
21 import dns.serial
22 import dns.rdatatype
23 import dns.zone
24
25
26 class TransferError(dns.exception.DNSException):
27 """A zone transfer response got a non-zero rcode."""
28
29 def __init__(self, rcode):
30 message = 'Zone transfer error: %s' % dns.rcode.to_text(rcode)
31 super().__init__(message)
32 self.rcode = rcode
33
34
35 class SerialWentBackwards(dns.exception.FormError):
36 """The current serial number is less than the serial we know."""
37
38
39 class UseTCP(dns.exception.DNSException):
40 """This IXFR cannot be completed with UDP."""
41
42
43 class Inbound:
44 """
45 State machine for zone transfers.
46 """
47
48 def __init__(self, txn_manager, rdtype=dns.rdatatype.AXFR,
49 serial=None, is_udp=False):
50 """Initialize an inbound zone transfer.
51
52 *txn_manager* is a :py:class:`dns.transaction.TransactionManager`.
53
54 *rdtype* can be `dns.rdatatype.AXFR` or `dns.rdatatype.IXFR`
55
56 *serial* is the base serial number for IXFRs, and is required in
57 that case.
58
59 *is_udp*, a ``bool`` indidicates if UDP is being used for this
60 XFR.
61 """
62 self.txn_manager = txn_manager
63 self.txn = None
64 self.rdtype = rdtype
65 if rdtype == dns.rdatatype.IXFR:
66 if serial is None:
67 raise ValueError('a starting serial must be supplied for IXFRs')
68 elif is_udp:
69 raise ValueError('is_udp specified for AXFR')
70 self.serial = serial
71 self.is_udp = is_udp
72 (_, _, self.origin) = txn_manager.origin_information()
73 self.soa_rdataset = None
74 self.done = False
75 self.expecting_SOA = False
76 self.delete_mode = False
77
78 def process_message(self, message):
79 """Process one message in the transfer.
80
81 The message should have the same relativization as was specified when
82 the `dns.xfr.Inbound` was created. The message should also have been
83 created with `one_rr_per_rrset=True` because order matters.
84
85 Returns `True` if the transfer is complete, and `False` otherwise.
86 """
87 if self.txn is None:
88 replacement = self.rdtype == dns.rdatatype.AXFR
89 self.txn = self.txn_manager.writer(replacement)
90 rcode = message.rcode()
91 if rcode != dns.rcode.NOERROR:
92 raise TransferError(rcode)
93 #
94 # We don't require a question section, but if it is present is
95 # should be correct.
96 #
97 if len(message.question) > 0:
98 if message.question[0].name != self.origin:
99 raise dns.exception.FormError("wrong question name")
100 if message.question[0].rdtype != self.rdtype:
101 raise dns.exception.FormError("wrong question rdatatype")
102 answer_index = 0
103 if self.soa_rdataset is None:
104 #
105 # This is the first message. We're expecting an SOA at
106 # the origin.
107 #
108 if not message.answer or message.answer[0].name != self.origin:
109 raise dns.exception.FormError("No answer or RRset not "
110 "for zone origin")
111 rrset = message.answer[0]
112 name = rrset.name
113 rdataset = rrset
114 if rdataset.rdtype != dns.rdatatype.SOA:
115 raise dns.exception.FormError("first RRset is not an SOA")
116 answer_index = 1
117 self.soa_rdataset = rdataset.copy()
118 if self.rdtype == dns.rdatatype.IXFR:
119 if self.soa_rdataset[0].serial == self.serial:
120 #
121 # We're already up-to-date.
122 #
123 self.done = True
124 elif dns.serial.Serial(self.soa_rdataset[0].serial) < \
125 self.serial:
126 # It went backwards!
127 raise SerialWentBackwards
128 else:
129 if self.is_udp and len(message.answer[answer_index:]) == 0:
130 #
131 # There are no more records, so this is the
132 # "truncated" response. Say to use TCP
133 #
134 raise UseTCP
135 #
136 # Note we're expecting another SOA so we can detect
137 # if this IXFR response is an AXFR-style response.
138 #
139 self.expecting_SOA = True
140 #
141 # Process the answer section (other than the initial SOA in
142 # the first message).
143 #
144 for rrset in message.answer[answer_index:]:
145 name = rrset.name
146 rdataset = rrset
147 if self.done:
148 raise dns.exception.FormError("answers after final SOA")
149 if rdataset.rdtype == dns.rdatatype.SOA and \
150 name == self.origin:
151 #
152 # Every time we see an origin SOA delete_mode inverts
153 #
154 if self.rdtype == dns.rdatatype.IXFR:
155 self.delete_mode = not self.delete_mode
156 #
157 # If this SOA Rdataset is equal to the first we saw
158 # then we're finished. If this is an IXFR we also
159 # check that we're seeing the record in the expected
160 # part of the response.
161 #
162 if rdataset == self.soa_rdataset and \
163 (self.rdtype == dns.rdatatype.AXFR or
164 (self.rdtype == dns.rdatatype.IXFR and
165 self.delete_mode)):
166 #
167 # This is the final SOA
168 #
169 if self.expecting_SOA:
170 # We got an empty IXFR sequence!
171 raise dns.exception.FormError('empty IXFR sequence')
172 if self.rdtype == dns.rdatatype.IXFR \
173 and self.serial != rdataset[0].serial:
174 raise dns.exception.FormError('unexpected end of IXFR '
175 'sequence')
176 self.txn.replace(name, rdataset)
177 self.txn.commit()
178 self.txn = None
179 self.done = True
180 else:
181 #
182 # This is not the final SOA
183 #
184 self.expecting_SOA = False
185 if self.rdtype == dns.rdatatype.IXFR:
186 if self.delete_mode:
187 # This is the start of an IXFR deletion set
188 if rdataset[0].serial != self.serial:
189 raise dns.exception.FormError(
190 "IXFR base serial mismatch")
191 else:
192 # This is the start of an IXFR addition set
193 self.serial = rdataset[0].serial
194 self.txn.replace(name, rdataset)
195 else:
196 # We saw a non-final SOA for the origin in an AXFR.
197 raise dns.exception.FormError('unexpected origin SOA '
198 'in AXFR')
199 continue
200 if self.expecting_SOA:
201 #
202 # We made an IXFR request and are expecting another
203 # SOA RR, but saw something else, so this must be an
204 # AXFR response.
205 #
206 self.rdtype = dns.rdatatype.AXFR
207 self.expecting_SOA = False
208 self.delete_mode = False
209 self.txn.rollback()
210 self.txn = self.txn_manager.writer(True)
211 #
212 # Note we are falling through into the code below
213 # so whatever rdataset this was gets written.
214 #
215 # Add or remove the data
216 if self.delete_mode:
217 self.txn.delete_exact(name, rdataset)
218 else:
219 self.txn.add(name, rdataset)
220 if self.is_udp and not self.done:
221 #
222 # This is a UDP IXFR and we didn't get to done, and we didn't
223 # get the proper "truncated" response
224 #
225 raise dns.exception.FormError('unexpected end of UDP IXFR')
226 return self.done
227
228 #
229 # Inbounds are context managers.
230 #
231
232 def __enter__(self):
233 return self
234
235 def __exit__(self, exc_type, exc_val, exc_tb):
236 if self.txn:
237 self.txn.rollback()
238 return False
239
240
241 def make_query(txn_manager, serial=0,
242 use_edns=None, ednsflags=None, payload=None,
243 request_payload=None, options=None,
244 keyring=None, keyname=None,
245 keyalgorithm=dns.tsig.default_algorithm):
246 """Make an AXFR or IXFR query.
247
248 *txn_manager* is a ``dns.transaction.TransactionManager``, typically a
249 ``dns.zone.Zone``.
250
251 *serial* is an ``int`` or ``None``. If 0, then IXFR will be
252 attempted using the most recent serial number from the
253 *txn_manager*; it is the caller's responsibility to ensure there
254 are no write transactions active that could invalidate the
255 retrieved serial. If a serial cannot be determined, AXFR will be
256 forced. Other integer values are the starting serial to use.
257 ``None`` forces an AXFR.
258
259 Please see the documentation for :py:func:`dns.message.make_query` and
260 :py:func:`dns.message.Message.use_tsig` for details on the other parameters
261 to this function.
262
263 Returns a `(query, serial)` tuple.
264 """
265 (zone_origin, _, origin) = txn_manager.origin_information()
266 if serial is None:
267 rdtype = dns.rdatatype.AXFR
268 elif not isinstance(serial, int):
269 raise ValueError('serial is not an integer')
270 elif serial == 0:
271 with txn_manager.reader() as txn:
272 rdataset = txn.get(origin, 'SOA')
273 if rdataset:
274 serial = rdataset[0].serial
275 rdtype = dns.rdatatype.IXFR
276 else:
277 serial = None
278 rdtype = dns.rdatatype.AXFR
279 elif serial > 0 and serial < 4294967296:
280 rdtype = dns.rdatatype.IXFR
281 else:
282 raise ValueError('serial out-of-range')
283 rdclass = txn_manager.get_class()
284 q = dns.message.make_query(zone_origin, rdtype, rdclass,
285 use_edns, False, ednsflags, payload,
286 request_payload, options)
287 if serial is not None:
288 rdata = dns.rdata.from_text(rdclass, 'SOA', f'. . {serial} 0 0 0 0')
289 rrset = q.find_rrset(q.authority, zone_origin, rdclass,
290 dns.rdatatype.SOA, create=True)
291 rrset.add(rdata, 0)
292 if keyring is not None:
293 q.use_tsig(keyring, keyname, algorithm=keyalgorithm)
294 return (q, serial)
295
296 def extract_serial_from_query(query):
297 """Extract the SOA serial number from query if it is an IXFR and return
298 it, otherwise return None.
299
300 *query* is a dns.message.QueryMessage that is an IXFR or AXFR request.
301
302 Raises if the query is not an IXFR or AXFR, or if an IXFR doesn't have
303 an appropriate SOA RRset in the authority section."""
304
305 question = query.question[0]
306 if question.rdtype == dns.rdatatype.AXFR:
307 return None
308 elif question.rdtype != dns.rdatatype.IXFR:
309 raise ValueError("query is not an AXFR or IXFR")
310 soa = query.find_rrset(query.authority, question.name, question.rdclass,
311 dns.rdatatype.SOA)
312 return soa[0].serial
1717 """DNS Zones."""
1818
1919 import contextlib
20 import hashlib
2021 import io
2122 import os
22 import re
23 import sys
23 import struct
2424
2525 import dns.exception
2626 import dns.name
2929 import dns.rdatatype
3030 import dns.rdata
3131 import dns.rdtypes.ANY.SOA
32 import dns.rdtypes.ANY.ZONEMD
3233 import dns.rrset
3334 import dns.tokenizer
35 import dns.transaction
3436 import dns.ttl
3537 import dns.grange
38 import dns.zonefile
3639
3740
3841 class BadZone(dns.exception.DNSException):
5558 """The DNS zone's origin is unknown."""
5659
5760
58 class Zone:
61 class UnsupportedDigestScheme(dns.exception.DNSException):
62
63 """The zone digest's scheme is unsupported."""
64
65
66 class UnsupportedDigestHashAlgorithm(dns.exception.DNSException):
67
68 """The zone digest's origin is unsupported."""
69
70
71 class NoDigest(dns.exception.DNSException):
72
73 """The DNS zone has no ZONEMD RRset at its origin."""
74
75
76 class DigestVerificationFailure(dns.exception.DNSException):
77
78 """The ZONEMD digest failed to verify."""
79
80
81 class DigestScheme(dns.enum.IntEnum):
82 """ZONEMD Scheme"""
83
84 SIMPLE = 1
85
86 @classmethod
87 def _maximum(cls):
88 return 255
89
90
91 class DigestHashAlgorithm(dns.enum.IntEnum):
92 """ZONEMD Hash Algorithm"""
93
94 SHA384 = 1
95 SHA512 = 2
96
97 @classmethod
98 def _maximum(cls):
99 return 255
100
101
102 _digest_hashers = {
103 DigestHashAlgorithm.SHA384: hashlib.sha384,
104 DigestHashAlgorithm.SHA512 : hashlib.sha512
105 }
106
107
108 class Zone(dns.transaction.TransactionManager):
59109
60110 """A DNS zone.
61111
76126
77127 *origin* is the origin of the zone. It may be a ``dns.name.Name``,
78128 a ``str``, or ``None``. If ``None``, then the zone's origin will
79 be set by the first ``$ORIGIN`` line in a masterfile.
129 be set by the first ``$ORIGIN`` line in a zone file.
80130
81131 *rdclass*, an ``int``, the zone's rdata class; the default is class IN.
82132
161211 key = self._validate_name(key)
162212 return self.nodes.get(key)
163213
164 def __contains__(self, other):
165 return other in self.nodes
214 def __contains__(self, key):
215 key = self._validate_name(key)
216 return key in self.nodes
166217
167218 def find_node(self, name, create=False):
168219 """Find a node in the zone, possibly creating it.
531582 for rdata in rds:
532583 yield (name, rds.ttl, rdata)
533584
534 def to_file(self, f, sorted=True, relativize=True, nl=None):
585 def to_file(self, f, sorted=True, relativize=True, nl=None,
586 want_comments=False):
535587 """Write a zone to a file.
536588
537589 *f*, a file or `str`. If *f* is a string, it is treated
549601 *nl*, a ``str`` or None. The end of line string. If not
550602 ``None``, the output will use the platform's native
551603 end-of-line marker (i.e. LF on POSIX, CRLF on Windows).
604
605 *want_comments*, a ``bool``. If ``True``, emit end-of-line comments
606 as part of writing the file. If ``False``, the default, do not
607 emit them.
552608 """
553609
554610 with contextlib.ExitStack() as stack:
578634 names = self.keys()
579635 for n in names:
580636 l = self[n].to_text(n, origin=self.origin,
581 relativize=relativize)
582 if isinstance(l, str):
583 l_b = l.encode(file_enc)
584 else:
585 l_b = l
586 l = l.decode()
637 relativize=relativize,
638 want_comments=want_comments)
639 l_b = l.encode(file_enc)
587640
588641 try:
589642 f.write(l_b)
592645 f.write(l)
593646 f.write(nl)
594647
595 def to_text(self, sorted=True, relativize=True, nl=None):
648 def to_text(self, sorted=True, relativize=True, nl=None,
649 want_comments=False):
596650 """Return a zone's text as though it were written to a file.
597651
598652 *sorted*, a ``bool``. If True, the default, then the file
608662 ``None``, the output will use the platform's native
609663 end-of-line marker (i.e. LF on POSIX, CRLF on Windows).
610664
665 *want_comments*, a ``bool``. If ``True``, emit end-of-line comments
666 as part of writing the file. If ``False``, the default, do not
667 emit them.
668
611669 Returns a ``str``.
612670 """
613671 temp_buffer = io.StringIO()
614 self.to_file(temp_buffer, sorted, relativize, nl)
672 self.to_file(temp_buffer, sorted, relativize, nl, want_comments)
615673 return_value = temp_buffer.getvalue()
616674 temp_buffer.close()
617675 return return_value
634692 if self.get_rdataset(name, dns.rdatatype.NS) is None:
635693 raise NoNS
636694
637
638 class _MasterReader:
639
640 """Read a DNS master file
641
642 @ivar tok: The tokenizer
643 @type tok: dns.tokenizer.Tokenizer object
644 @ivar last_ttl: The last seen explicit TTL for an RR
645 @type last_ttl: int
646 @ivar last_ttl_known: Has last TTL been detected
647 @type last_ttl_known: bool
648 @ivar default_ttl: The default TTL from a $TTL directive or SOA RR
649 @type default_ttl: int
650 @ivar default_ttl_known: Has default TTL been detected
651 @type default_ttl_known: bool
652 @ivar last_name: The last name read
653 @type last_name: dns.name.Name object
654 @ivar current_origin: The current origin
655 @type current_origin: dns.name.Name object
656 @ivar relativize: should names in the zone be relativized?
657 @type relativize: bool
658 @ivar zone: the zone
659 @type zone: dns.zone.Zone object
660 @ivar saved_state: saved reader state (used when processing $INCLUDE)
661 @type saved_state: list of (tokenizer, current_origin, last_name, file,
662 last_ttl, last_ttl_known, default_ttl, default_ttl_known) tuples.
663 @ivar current_file: the file object of the $INCLUDed file being parsed
664 (None if no $INCLUDE is active).
665 @ivar allow_include: is $INCLUDE allowed?
666 @type allow_include: bool
667 @ivar check_origin: should sanity checks of the origin node be done?
668 The default is True.
669 @type check_origin: bool
670 """
671
672 def __init__(self, tok, origin, rdclass, relativize, zone_factory=Zone,
673 allow_include=False, check_origin=True):
674 if isinstance(origin, str):
675 origin = dns.name.from_text(origin)
676 self.tok = tok
677 self.current_origin = origin
678 self.relativize = relativize
679 self.last_ttl = 0
680 self.last_ttl_known = False
681 self.default_ttl = 0
682 self.default_ttl_known = False
683 self.last_name = self.current_origin
684 self.zone = zone_factory(origin, rdclass, relativize=relativize)
685 self.saved_state = []
686 self.current_file = None
687 self.allow_include = allow_include
688 self.check_origin = check_origin
689
690 def _eat_line(self):
691 while 1:
692 token = self.tok.get()
693 if token.is_eol_or_eof():
694 break
695
696 def _rr_line(self):
697 """Process one line from a DNS master file."""
698 # Name
699 if self.current_origin is None:
700 raise UnknownOrigin
701 token = self.tok.get(want_leading=True)
702 if not token.is_whitespace():
703 self.last_name = self.tok.as_name(token, self.current_origin)
695 def _compute_digest(self, hash_algorithm, scheme=DigestScheme.SIMPLE):
696 hashinfo = _digest_hashers.get(hash_algorithm)
697 if not hashinfo:
698 raise UnsupportedDigestHashAlgorithm
699 if scheme != DigestScheme.SIMPLE:
700 raise UnsupportedDigestScheme
701
702 if self.relativize:
703 origin_name = dns.name.empty
704704 else:
705 token = self.tok.get()
706 if token.is_eol_or_eof():
707 # treat leading WS followed by EOL/EOF as if they were EOL/EOF.
708 return
709 self.tok.unget(token)
710 name = self.last_name
711 if not name.is_subdomain(self.zone.origin):
712 self._eat_line()
713 return
705 origin_name = self.origin
706 hasher = hashinfo()
707 for (name, node) in sorted(self.items()):
708 rrnamebuf = name.to_digestable(self.origin)
709 for rdataset in sorted(node,
710 key=lambda rds: (rds.rdtype, rds.covers)):
711 if name == origin_name and \
712 dns.rdatatype.ZONEMD in (rdataset.rdtype, rdataset.covers):
713 continue
714 rrfixed = struct.pack('!HHI', rdataset.rdtype,
715 rdataset.rdclass, rdataset.ttl)
716 for rr in sorted(rdataset):
717 rrdata = rr.to_digestable(self.origin)
718 rrlen = struct.pack('!H', len(rrdata))
719 hasher.update(rrnamebuf + rrfixed + rrlen + rrdata)
720 return hasher.digest()
721
722 def compute_digest(self, hash_algorithm, scheme=DigestScheme.SIMPLE):
714723 if self.relativize:
715 name = name.relativize(self.zone.origin)
716 token = self.tok.get()
717 if not token.is_identifier():
718 raise dns.exception.SyntaxError
719
720 # TTL
721 ttl = None
724 origin_name = dns.name.empty
725 else:
726 origin_name = self.origin
727 serial = self.get_rdataset(origin_name, dns.rdatatype.SOA)[0].serial
728 digest = self._compute_digest(hash_algorithm, scheme)
729 return dns.rdtypes.ANY.ZONEMD.ZONEMD(self.rdclass,
730 dns.rdatatype.ZONEMD,
731 serial, scheme, hash_algorithm,
732 digest)
733
734 def verify_digest(self, zonemd=None):
735 if zonemd:
736 digests = [zonemd]
737 else:
738 digests = self.get_rdataset(self.origin, dns.rdatatype.ZONEMD)
739 if digests is None:
740 raise NoDigest
741 for digest in digests:
742 try:
743 computed = self._compute_digest(digest.hash_algorithm,
744 digest.scheme)
745 if computed == digest.digest:
746 return
747 except Exception:
748 pass
749 raise DigestVerificationFailure
750
751 # TransactionManager methods
752
753 def reader(self):
754 return Transaction(self, False, True)
755
756 def writer(self, replacement=False):
757 return Transaction(self, replacement, False)
758
759 def origin_information(self):
760 if self.relativize:
761 effective = dns.name.empty
762 else:
763 effective = self.origin
764 return (self.origin, self.relativize, effective)
765
766 def get_class(self):
767 return self.rdclass
768
769
770 class Transaction(dns.transaction.Transaction):
771
772 _deleted_rdataset = dns.rdataset.Rdataset(dns.rdataclass.ANY,
773 dns.rdatatype.ANY)
774
775 def __init__(self, zone, replacement, read_only):
776 super().__init__(zone, replacement, read_only)
777 self.rdatasets = {}
778
779 @property
780 def zone(self):
781 return self.manager
782
783 def _get_rdataset(self, name, rdtype, covers):
784 rdataset = self.rdatasets.get((name, rdtype, covers))
785 if rdataset is self._deleted_rdataset:
786 return None
787 elif rdataset is None:
788 rdataset = self.zone.get_rdataset(name, rdtype, covers)
789 return rdataset
790
791 def _put_rdataset(self, name, rdataset):
792 assert not self.read_only
793 self.zone._validate_name(name)
794 self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = rdataset
795
796 def _delete_name(self, name):
797 assert not self.read_only
798 # First remove any changes involving the name
799 remove = []
800 for key in self.rdatasets:
801 if key[0] == name:
802 remove.append(key)
803 if len(remove) > 0:
804 for key in remove:
805 del self.rdatasets[key]
806 # Next add deletion records for any rdatasets matching the
807 # name in the zone
808 node = self.zone.get_node(name)
809 if node is not None:
810 for rdataset in node.rdatasets:
811 self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = \
812 self._deleted_rdataset
813
814 def _delete_rdataset(self, name, rdtype, covers):
815 assert not self.read_only
722816 try:
723 ttl = dns.ttl.from_text(token.value)
724 self.last_ttl = ttl
725 self.last_ttl_known = True
726 token = self.tok.get()
727 if not token.is_identifier():
728 raise dns.exception.SyntaxError
729 except dns.ttl.BadTTL:
730 if self.default_ttl_known:
731 ttl = self.default_ttl
732 elif self.last_ttl_known:
733 ttl = self.last_ttl
734
735 # Class
736 try:
737 rdclass = dns.rdataclass.from_text(token.value)
738 token = self.tok.get()
739 if not token.is_identifier():
740 raise dns.exception.SyntaxError
741 except dns.exception.SyntaxError:
742 raise
743 except Exception:
744 rdclass = self.zone.rdclass
745 if rdclass != self.zone.rdclass:
746 raise dns.exception.SyntaxError("RR class is not zone's class")
747 # Type
748 try:
749 rdtype = dns.rdatatype.from_text(token.value)
750 except Exception:
751 raise dns.exception.SyntaxError(
752 "unknown rdatatype '%s'" % token.value)
753 n = self.zone.nodes.get(name)
754 if n is None:
755 n = self.zone.node_factory()
756 self.zone.nodes[name] = n
757 try:
758 rd = dns.rdata.from_text(rdclass, rdtype, self.tok,
759 self.current_origin, self.relativize,
760 self.zone.origin)
761 except dns.exception.SyntaxError:
762 # Catch and reraise.
763 raise
764 except Exception:
765 # All exceptions that occur in the processing of rdata
766 # are treated as syntax errors. This is not strictly
767 # correct, but it is correct almost all of the time.
768 # We convert them to syntax errors so that we can emit
769 # helpful filename:line info.
770 (ty, va) = sys.exc_info()[:2]
771 raise dns.exception.SyntaxError(
772 "caught exception {}: {}".format(str(ty), str(va)))
773
774 if not self.default_ttl_known and rdtype == dns.rdatatype.SOA:
775 # The pre-RFC2308 and pre-BIND9 behavior inherits the zone default
776 # TTL from the SOA minttl if no $TTL statement is present before the
777 # SOA is parsed.
778 self.default_ttl = rd.minimum
779 self.default_ttl_known = True
780 if ttl is None:
781 # if we didn't have a TTL on the SOA, set it!
782 ttl = rd.minimum
783
784 # TTL check. We had to wait until now to do this as the SOA RR's
785 # own TTL can be inferred from its minimum.
786 if ttl is None:
787 raise dns.exception.SyntaxError("Missing default TTL value")
788
789 covers = rd.covers()
790 rds = n.find_rdataset(rdclass, rdtype, covers, True)
791 rds.add(rd, ttl)
792
793 def _parse_modify(self, side):
794 # Here we catch everything in '{' '}' in a group so we can replace it
795 # with ''.
796 is_generate1 = re.compile(r"^.*\$({(\+|-?)(\d+),(\d+),(.)}).*$")
797 is_generate2 = re.compile(r"^.*\$({(\+|-?)(\d+)}).*$")
798 is_generate3 = re.compile(r"^.*\$({(\+|-?)(\d+),(\d+)}).*$")
799 # Sometimes there are modifiers in the hostname. These come after
800 # the dollar sign. They are in the form: ${offset[,width[,base]]}.
801 # Make names
802 g1 = is_generate1.match(side)
803 if g1:
804 mod, sign, offset, width, base = g1.groups()
805 if sign == '':
806 sign = '+'
807 g2 = is_generate2.match(side)
808 if g2:
809 mod, sign, offset = g2.groups()
810 if sign == '':
811 sign = '+'
812 width = 0
813 base = 'd'
814 g3 = is_generate3.match(side)
815 if g3:
816 mod, sign, offset, width = g3.groups()
817 if sign == '':
818 sign = '+'
819 base = 'd'
820
821 if not (g1 or g2 or g3):
822 mod = ''
823 sign = '+'
824 offset = 0
825 width = 0
826 base = 'd'
827
828 if base != 'd':
829 raise NotImplementedError()
830
831 return mod, sign, offset, width, base
832
833 def _generate_line(self):
834 # range lhs [ttl] [class] type rhs [ comment ]
835 """Process one line containing the GENERATE statement from a DNS
836 master file."""
837 if self.current_origin is None:
838 raise UnknownOrigin
839
840 token = self.tok.get()
841 # Range (required)
842 try:
843 start, stop, step = dns.grange.from_text(token.value)
844 token = self.tok.get()
845 if not token.is_identifier():
846 raise dns.exception.SyntaxError
847 except Exception:
848 raise dns.exception.SyntaxError
849
850 # lhs (required)
851 try:
852 lhs = token.value
853 token = self.tok.get()
854 if not token.is_identifier():
855 raise dns.exception.SyntaxError
856 except Exception:
857 raise dns.exception.SyntaxError
858
859 # TTL
860 try:
861 ttl = dns.ttl.from_text(token.value)
862 self.last_ttl = ttl
863 self.last_ttl_known = True
864 token = self.tok.get()
865 if not token.is_identifier():
866 raise dns.exception.SyntaxError
867 except dns.ttl.BadTTL:
868 if not (self.last_ttl_known or self.default_ttl_known):
869 raise dns.exception.SyntaxError("Missing default TTL value")
870 if self.default_ttl_known:
871 ttl = self.default_ttl
872 elif self.last_ttl_known:
873 ttl = self.last_ttl
874 # Class
875 try:
876 rdclass = dns.rdataclass.from_text(token.value)
877 token = self.tok.get()
878 if not token.is_identifier():
879 raise dns.exception.SyntaxError
880 except dns.exception.SyntaxError:
881 raise dns.exception.SyntaxError
882 except Exception:
883 rdclass = self.zone.rdclass
884 if rdclass != self.zone.rdclass:
885 raise dns.exception.SyntaxError("RR class is not zone's class")
886 # Type
887 try:
888 rdtype = dns.rdatatype.from_text(token.value)
889 token = self.tok.get()
890 if not token.is_identifier():
891 raise dns.exception.SyntaxError
892 except Exception:
893 raise dns.exception.SyntaxError("unknown rdatatype '%s'" %
894 token.value)
895
896 # rhs (required)
897 rhs = token.value
898
899 lmod, lsign, loffset, lwidth, lbase = self._parse_modify(lhs)
900 rmod, rsign, roffset, rwidth, rbase = self._parse_modify(rhs)
901 for i in range(start, stop + 1, step):
902 # +1 because bind is inclusive and python is exclusive
903
904 if lsign == '+':
905 lindex = i + int(loffset)
906 elif lsign == '-':
907 lindex = i - int(loffset)
908
909 if rsign == '-':
910 rindex = i - int(roffset)
911 elif rsign == '+':
912 rindex = i + int(roffset)
913
914 lzfindex = str(lindex).zfill(int(lwidth))
915 rzfindex = str(rindex).zfill(int(rwidth))
916
917 name = lhs.replace('$%s' % (lmod), lzfindex)
918 rdata = rhs.replace('$%s' % (rmod), rzfindex)
919
920 self.last_name = dns.name.from_text(name, self.current_origin,
921 self.tok.idna_codec)
922 name = self.last_name
923 if not name.is_subdomain(self.zone.origin):
924 self._eat_line()
925 return
926 if self.relativize:
927 name = name.relativize(self.zone.origin)
928
929 n = self.zone.nodes.get(name)
930 if n is None:
931 n = self.zone.node_factory()
932 self.zone.nodes[name] = n
933 try:
934 rd = dns.rdata.from_text(rdclass, rdtype, rdata,
935 self.current_origin, self.relativize,
936 self.zone.origin)
937 except dns.exception.SyntaxError:
938 # Catch and reraise.
939 raise
940 except Exception:
941 # All exceptions that occur in the processing of rdata
942 # are treated as syntax errors. This is not strictly
943 # correct, but it is correct almost all of the time.
944 # We convert them to syntax errors so that we can emit
945 # helpful filename:line info.
946 (ty, va) = sys.exc_info()[:2]
947 raise dns.exception.SyntaxError("caught exception %s: %s" %
948 (str(ty), str(va)))
949
950 covers = rd.covers()
951 rds = n.find_rdataset(rdclass, rdtype, covers, True)
952 rds.add(rd, ttl)
953
954 def read(self):
955 """Read a DNS master file and build a zone object.
956
957 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
958 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
959 """
960
961 try:
962 while 1:
963 token = self.tok.get(True, True)
964 if token.is_eof():
965 if self.current_file is not None:
966 self.current_file.close()
967 if len(self.saved_state) > 0:
968 (self.tok,
969 self.current_origin,
970 self.last_name,
971 self.current_file,
972 self.last_ttl,
973 self.last_ttl_known,
974 self.default_ttl,
975 self.default_ttl_known) = self.saved_state.pop(-1)
976 continue
977 break
978 elif token.is_eol():
979 continue
980 elif token.is_comment():
981 self.tok.get_eol()
982 continue
983 elif token.value[0] == '$':
984 c = token.value.upper()
985 if c == '$TTL':
986 token = self.tok.get()
987 if not token.is_identifier():
988 raise dns.exception.SyntaxError("bad $TTL")
989 self.default_ttl = dns.ttl.from_text(token.value)
990 self.default_ttl_known = True
991 self.tok.get_eol()
992 elif c == '$ORIGIN':
993 self.current_origin = self.tok.get_name()
994 self.tok.get_eol()
995 if self.zone.origin is None:
996 self.zone.origin = self.current_origin
997 elif c == '$INCLUDE' and self.allow_include:
998 token = self.tok.get()
999 filename = token.value
1000 token = self.tok.get()
1001 if token.is_identifier():
1002 new_origin =\
1003 dns.name.from_text(token.value,
1004 self.current_origin,
1005 self.tok.idna_codec)
1006 self.tok.get_eol()
1007 elif not token.is_eol_or_eof():
1008 raise dns.exception.SyntaxError(
1009 "bad origin in $INCLUDE")
1010 else:
1011 new_origin = self.current_origin
1012 self.saved_state.append((self.tok,
1013 self.current_origin,
1014 self.last_name,
1015 self.current_file,
1016 self.last_ttl,
1017 self.last_ttl_known,
1018 self.default_ttl,
1019 self.default_ttl_known))
1020 self.current_file = open(filename, 'r')
1021 self.tok = dns.tokenizer.Tokenizer(self.current_file,
1022 filename)
1023 self.current_origin = new_origin
1024 elif c == '$GENERATE':
1025 self._generate_line()
1026 else:
1027 raise dns.exception.SyntaxError(
1028 "Unknown master file directive '" + c + "'")
1029 continue
1030 self.tok.unget(token)
1031 self._rr_line()
1032 except dns.exception.SyntaxError as detail:
1033 (filename, line_number) = self.tok.where()
1034 if detail is None:
1035 detail = "syntax error"
1036 ex = dns.exception.SyntaxError(
1037 "%s:%d: %s" % (filename, line_number, detail))
1038 tb = sys.exc_info()[2]
1039 raise ex.with_traceback(tb) from None
1040
1041 # Now that we're done reading, do some basic checking of the zone.
1042 if self.check_origin:
1043 self.zone.check_origin()
817 del self.rdatasets[(name, rdtype, covers)]
818 except KeyError:
819 pass
820 rdataset = self.zone.get_rdataset(name, rdtype, covers)
821 if rdataset is not None:
822 self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = \
823 self._deleted_rdataset
824
825 def _name_exists(self, name):
826 for key, rdataset in self.rdatasets.items():
827 if key[0] == name:
828 if rdataset != self._deleted_rdataset:
829 return True
830 else:
831 return None
832 self.zone._validate_name(name)
833 if self.zone.get_node(name):
834 return True
835 return False
836
837 def _changed(self):
838 if self.read_only:
839 return False
840 else:
841 return len(self.rdatasets) > 0
842
843 def _end_transaction(self, commit):
844 if commit and self._changed():
845 for (name, rdtype, covers), rdataset in \
846 self.rdatasets.items():
847 if rdataset is self._deleted_rdataset:
848 self.zone.delete_rdataset(name, rdtype, covers)
849 else:
850 self.zone.replace_rdataset(name, rdataset)
851
852 def _set_origin(self, origin):
853 if self.zone.origin is None:
854 self.zone.origin = origin
855
856 def _iterate_rdatasets(self):
857 # Expensive but simple! Use a versioned zone for efficient txn
858 # iteration.
859 rdatasets = {}
860 for (name, rdataset) in self.zone.iterate_rdatasets():
861 rdatasets[(name, rdataset.rdtype, rdataset.covers)] = rdataset
862 rdatasets.update(self.rdatasets)
863 for (name, _, _), rdataset in rdatasets.items():
864 yield (name, rdataset)
1044865
1045866
1046867 def from_text(text, origin=None, rdclass=dns.rdataclass.IN,
1047868 relativize=True, zone_factory=Zone, filename=None,
1048869 allow_include=False, check_origin=True, idna_codec=None):
1049 """Build a zone object from a master file format string.
1050
1051 *text*, a ``str``, the master file format input.
870 """Build a zone object from a zone file format string.
871
872 *text*, a ``str``, the zone file format input.
1052873
1053874 *origin*, a ``dns.name.Name``, a ``str``, or ``None``. The origin
1054875 of the zone; if not specified, the first ``$ORIGIN`` statement in the
1055 masterfile will determine the origin of the zone.
876 zone file will determine the origin of the zone.
1056877
1057878 *rdclass*, an ``int``, the zone's rdata class; the default is class IN.
1058879
1093914
1094915 if filename is None:
1095916 filename = '<string>'
1096 tok = dns.tokenizer.Tokenizer(text, filename, idna_codec=idna_codec)
1097 reader = _MasterReader(tok, origin, rdclass, relativize, zone_factory,
1098 allow_include=allow_include,
1099 check_origin=check_origin)
1100 reader.read()
1101 return reader.zone
917 zone = zone_factory(origin, rdclass, relativize=relativize)
918 with zone.writer(True) as txn:
919 tok = dns.tokenizer.Tokenizer(text, filename, idna_codec=idna_codec)
920 reader = dns.zonefile.Reader(tok, rdclass, txn,
921 allow_include=allow_include)
922 try:
923 reader.read()
924 except dns.zonefile.UnknownOrigin:
925 # for backwards compatibility
926 raise dns.zone.UnknownOrigin
927 # Now that we're done reading, do some basic checking of the zone.
928 if check_origin:
929 zone.check_origin()
930 return zone
1102931
1103932
1104933 def from_file(f, origin=None, rdclass=dns.rdataclass.IN,
1105934 relativize=True, zone_factory=Zone, filename=None,
1106935 allow_include=True, check_origin=True):
1107 """Read a master file and build a zone object.
936 """Read a zone file and build a zone object.
1108937
1109938 *f*, a file or ``str``. If *f* is a string, it is treated
1110939 as the name of a file to open.
1111940
1112941 *origin*, a ``dns.name.Name``, a ``str``, or ``None``. The origin
1113942 of the zone; if not specified, the first ``$ORIGIN`` statement in the
1114 masterfile will determine the origin of the zone.
943 zone file will determine the origin of the zone.
1115944
1116945 *rdclass*, an ``int``, the zone's rdata class; the default is class IN.
1117946
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
3 #
4 # Permission to use, copy, modify, and distribute this software and its
5 # documentation for any purpose with or without fee is hereby granted,
6 # provided that the above copyright notice and this permission notice
7 # appear in all copies.
8 #
9 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
12 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
15 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17 """DNS Zones."""
18
19 import re
20 import sys
21
22 import dns.exception
23 import dns.name
24 import dns.node
25 import dns.rdataclass
26 import dns.rdatatype
27 import dns.rdata
28 import dns.rdtypes.ANY.SOA
29 import dns.rrset
30 import dns.tokenizer
31 import dns.transaction
32 import dns.ttl
33 import dns.grange
34
35
36 class UnknownOrigin(dns.exception.DNSException):
37 """Unknown origin"""
38
39
40 class Reader:
41
42 """Read a DNS zone file into a transaction."""
43
44 def __init__(self, tok, rdclass, txn, allow_include=False):
45 self.tok = tok
46 (self.zone_origin, self.relativize, _) = \
47 txn.manager.origin_information()
48 self.current_origin = self.zone_origin
49 self.last_ttl = 0
50 self.last_ttl_known = False
51 self.default_ttl = 0
52 self.default_ttl_known = False
53 self.last_name = self.current_origin
54 self.zone_rdclass = rdclass
55 self.txn = txn
56 self.saved_state = []
57 self.current_file = None
58 self.allow_include = allow_include
59
60 def _eat_line(self):
61 while 1:
62 token = self.tok.get()
63 if token.is_eol_or_eof():
64 break
65
66 def _rr_line(self):
67 """Process one line from a DNS zone file."""
68 # Name
69 if self.current_origin is None:
70 raise UnknownOrigin
71 token = self.tok.get(want_leading=True)
72 if not token.is_whitespace():
73 self.last_name = self.tok.as_name(token, self.current_origin)
74 else:
75 token = self.tok.get()
76 if token.is_eol_or_eof():
77 # treat leading WS followed by EOL/EOF as if they were EOL/EOF.
78 return
79 self.tok.unget(token)
80 name = self.last_name
81 if not name.is_subdomain(self.zone_origin):
82 self._eat_line()
83 return
84 if self.relativize:
85 name = name.relativize(self.zone_origin)
86 token = self.tok.get()
87 if not token.is_identifier():
88 raise dns.exception.SyntaxError
89
90 # TTL
91 ttl = None
92 try:
93 ttl = dns.ttl.from_text(token.value)
94 self.last_ttl = ttl
95 self.last_ttl_known = True
96 token = self.tok.get()
97 if not token.is_identifier():
98 raise dns.exception.SyntaxError
99 except dns.ttl.BadTTL:
100 if self.default_ttl_known:
101 ttl = self.default_ttl
102 elif self.last_ttl_known:
103 ttl = self.last_ttl
104
105 # Class
106 try:
107 rdclass = dns.rdataclass.from_text(token.value)
108 token = self.tok.get()
109 if not token.is_identifier():
110 raise dns.exception.SyntaxError
111 except dns.exception.SyntaxError:
112 raise
113 except Exception:
114 rdclass = self.zone_rdclass
115 if rdclass != self.zone_rdclass:
116 raise dns.exception.SyntaxError("RR class is not zone's class")
117 # Type
118 try:
119 rdtype = dns.rdatatype.from_text(token.value)
120 except Exception:
121 raise dns.exception.SyntaxError(
122 "unknown rdatatype '%s'" % token.value)
123 try:
124 rd = dns.rdata.from_text(rdclass, rdtype, self.tok,
125 self.current_origin, self.relativize,
126 self.zone_origin)
127 except dns.exception.SyntaxError:
128 # Catch and reraise.
129 raise
130 except Exception:
131 # All exceptions that occur in the processing of rdata
132 # are treated as syntax errors. This is not strictly
133 # correct, but it is correct almost all of the time.
134 # We convert them to syntax errors so that we can emit
135 # helpful filename:line info.
136 (ty, va) = sys.exc_info()[:2]
137 raise dns.exception.SyntaxError(
138 "caught exception {}: {}".format(str(ty), str(va)))
139
140 if not self.default_ttl_known and rdtype == dns.rdatatype.SOA:
141 # The pre-RFC2308 and pre-BIND9 behavior inherits the zone default
142 # TTL from the SOA minttl if no $TTL statement is present before the
143 # SOA is parsed.
144 self.default_ttl = rd.minimum
145 self.default_ttl_known = True
146 if ttl is None:
147 # if we didn't have a TTL on the SOA, set it!
148 ttl = rd.minimum
149
150 # TTL check. We had to wait until now to do this as the SOA RR's
151 # own TTL can be inferred from its minimum.
152 if ttl is None:
153 raise dns.exception.SyntaxError("Missing default TTL value")
154
155 self.txn.add(name, ttl, rd)
156
157 def _parse_modify(self, side):
158 # Here we catch everything in '{' '}' in a group so we can replace it
159 # with ''.
160 is_generate1 = re.compile(r"^.*\$({(\+|-?)(\d+),(\d+),(.)}).*$")
161 is_generate2 = re.compile(r"^.*\$({(\+|-?)(\d+)}).*$")
162 is_generate3 = re.compile(r"^.*\$({(\+|-?)(\d+),(\d+)}).*$")
163 # Sometimes there are modifiers in the hostname. These come after
164 # the dollar sign. They are in the form: ${offset[,width[,base]]}.
165 # Make names
166 g1 = is_generate1.match(side)
167 if g1:
168 mod, sign, offset, width, base = g1.groups()
169 if sign == '':
170 sign = '+'
171 g2 = is_generate2.match(side)
172 if g2:
173 mod, sign, offset = g2.groups()
174 if sign == '':
175 sign = '+'
176 width = 0
177 base = 'd'
178 g3 = is_generate3.match(side)
179 if g3:
180 mod, sign, offset, width = g3.groups()
181 if sign == '':
182 sign = '+'
183 base = 'd'
184
185 if not (g1 or g2 or g3):
186 mod = ''
187 sign = '+'
188 offset = 0
189 width = 0
190 base = 'd'
191
192 if base != 'd':
193 raise NotImplementedError()
194
195 return mod, sign, offset, width, base
196
197 def _generate_line(self):
198 # range lhs [ttl] [class] type rhs [ comment ]
199 """Process one line containing the GENERATE statement from a DNS
200 zone file."""
201 if self.current_origin is None:
202 raise UnknownOrigin
203
204 token = self.tok.get()
205 # Range (required)
206 try:
207 start, stop, step = dns.grange.from_text(token.value)
208 token = self.tok.get()
209 if not token.is_identifier():
210 raise dns.exception.SyntaxError
211 except Exception:
212 raise dns.exception.SyntaxError
213
214 # lhs (required)
215 try:
216 lhs = token.value
217 token = self.tok.get()
218 if not token.is_identifier():
219 raise dns.exception.SyntaxError
220 except Exception:
221 raise dns.exception.SyntaxError
222
223 # TTL
224 try:
225 ttl = dns.ttl.from_text(token.value)
226 self.last_ttl = ttl
227 self.last_ttl_known = True
228 token = self.tok.get()
229 if not token.is_identifier():
230 raise dns.exception.SyntaxError
231 except dns.ttl.BadTTL:
232 if not (self.last_ttl_known or self.default_ttl_known):
233 raise dns.exception.SyntaxError("Missing default TTL value")
234 if self.default_ttl_known:
235 ttl = self.default_ttl
236 elif self.last_ttl_known:
237 ttl = self.last_ttl
238 # Class
239 try:
240 rdclass = dns.rdataclass.from_text(token.value)
241 token = self.tok.get()
242 if not token.is_identifier():
243 raise dns.exception.SyntaxError
244 except dns.exception.SyntaxError:
245 raise dns.exception.SyntaxError
246 except Exception:
247 rdclass = self.zone_rdclass
248 if rdclass != self.zone_rdclass:
249 raise dns.exception.SyntaxError("RR class is not zone's class")
250 # Type
251 try:
252 rdtype = dns.rdatatype.from_text(token.value)
253 token = self.tok.get()
254 if not token.is_identifier():
255 raise dns.exception.SyntaxError
256 except Exception:
257 raise dns.exception.SyntaxError("unknown rdatatype '%s'" %
258 token.value)
259
260 # rhs (required)
261 rhs = token.value
262
263 # The code currently only supports base 'd', so the last value
264 # in the tuple _parse_modify returns is ignored
265 lmod, lsign, loffset, lwidth, _ = self._parse_modify(lhs)
266 rmod, rsign, roffset, rwidth, _ = self._parse_modify(rhs)
267 for i in range(start, stop + 1, step):
268 # +1 because bind is inclusive and python is exclusive
269
270 if lsign == '+':
271 lindex = i + int(loffset)
272 elif lsign == '-':
273 lindex = i - int(loffset)
274
275 if rsign == '-':
276 rindex = i - int(roffset)
277 elif rsign == '+':
278 rindex = i + int(roffset)
279
280 lzfindex = str(lindex).zfill(int(lwidth))
281 rzfindex = str(rindex).zfill(int(rwidth))
282
283 name = lhs.replace('$%s' % (lmod), lzfindex)
284 rdata = rhs.replace('$%s' % (rmod), rzfindex)
285
286 self.last_name = dns.name.from_text(name, self.current_origin,
287 self.tok.idna_codec)
288 name = self.last_name
289 if not name.is_subdomain(self.zone_origin):
290 self._eat_line()
291 return
292 if self.relativize:
293 name = name.relativize(self.zone_origin)
294
295 try:
296 rd = dns.rdata.from_text(rdclass, rdtype, rdata,
297 self.current_origin, self.relativize,
298 self.zone_origin)
299 except dns.exception.SyntaxError:
300 # Catch and reraise.
301 raise
302 except Exception:
303 # All exceptions that occur in the processing of rdata
304 # are treated as syntax errors. This is not strictly
305 # correct, but it is correct almost all of the time.
306 # We convert them to syntax errors so that we can emit
307 # helpful filename:line info.
308 (ty, va) = sys.exc_info()[:2]
309 raise dns.exception.SyntaxError("caught exception %s: %s" %
310 (str(ty), str(va)))
311
312 self.txn.add(name, ttl, rd)
313
314 def read(self):
315 """Read a DNS zone file and build a zone object.
316
317 @raises dns.zone.NoSOA: No SOA RR was found at the zone origin
318 @raises dns.zone.NoNS: No NS RRset was found at the zone origin
319 """
320
321 try:
322 while 1:
323 token = self.tok.get(True, True)
324 if token.is_eof():
325 if self.current_file is not None:
326 self.current_file.close()
327 if len(self.saved_state) > 0:
328 (self.tok,
329 self.current_origin,
330 self.last_name,
331 self.current_file,
332 self.last_ttl,
333 self.last_ttl_known,
334 self.default_ttl,
335 self.default_ttl_known) = self.saved_state.pop(-1)
336 continue
337 break
338 elif token.is_eol():
339 continue
340 elif token.is_comment():
341 self.tok.get_eol()
342 continue
343 elif token.value[0] == '$':
344 c = token.value.upper()
345 if c == '$TTL':
346 token = self.tok.get()
347 if not token.is_identifier():
348 raise dns.exception.SyntaxError("bad $TTL")
349 self.default_ttl = dns.ttl.from_text(token.value)
350 self.default_ttl_known = True
351 self.tok.get_eol()
352 elif c == '$ORIGIN':
353 self.current_origin = self.tok.get_name()
354 self.tok.get_eol()
355 if self.zone_origin is None:
356 self.zone_origin = self.current_origin
357 self.txn._set_origin(self.current_origin)
358 elif c == '$INCLUDE' and self.allow_include:
359 token = self.tok.get()
360 filename = token.value
361 token = self.tok.get()
362 if token.is_identifier():
363 new_origin =\
364 dns.name.from_text(token.value,
365 self.current_origin,
366 self.tok.idna_codec)
367 self.tok.get_eol()
368 elif not token.is_eol_or_eof():
369 raise dns.exception.SyntaxError(
370 "bad origin in $INCLUDE")
371 else:
372 new_origin = self.current_origin
373 self.saved_state.append((self.tok,
374 self.current_origin,
375 self.last_name,
376 self.current_file,
377 self.last_ttl,
378 self.last_ttl_known,
379 self.default_ttl,
380 self.default_ttl_known))
381 self.current_file = open(filename, 'r')
382 self.tok = dns.tokenizer.Tokenizer(self.current_file,
383 filename)
384 self.current_origin = new_origin
385 elif c == '$GENERATE':
386 self._generate_line()
387 else:
388 raise dns.exception.SyntaxError(
389 "Unknown zone file directive '" + c + "'")
390 continue
391 self.tok.unget(token)
392 self._rr_line()
393 except dns.exception.SyntaxError as detail:
394 (filename, line_number) = self.tok.where()
395 if detail is None:
396 detail = "syntax error"
397 ex = dns.exception.SyntaxError(
398 "%s:%d: %s" % (filename, line_number, detail))
399 tb = sys.exc_info()[2]
400 raise ex.with_traceback(tb) from None
0 Metadata-Version: 2.1
1 Name: dnspython
2 Version: 1.15.1.dev1222+gcf73187
3 Summary: DNS toolkit
4 Home-page: http://www.dnspython.org
5 Author: Bob Halley
6 Author-email: halley@dnspython.org
7 License: ISC
8 Description: dnspython is a DNS toolkit for Python. It supports almost all
9 record types. It can be used for queries, zone transfers, and dynamic
10 updates. It supports TSIG authenticated messages and EDNS0.
11
12 dnspython provides both high and low level access to DNS. The high
13 level classes perform queries for data of a given name, type, and
14 class, and return an answer set. The low level classes allow
15 direct manipulation of DNS zones, messages, names, and records.
16 Platform: UNKNOWN
17 Classifier: Development Status :: 5 - Production/Stable
18 Classifier: Intended Audience :: Developers
19 Classifier: Intended Audience :: System Administrators
20 Classifier: License :: OSI Approved :: ISC License (ISCL)
21 Classifier: Operating System :: POSIX
22 Classifier: Operating System :: Microsoft :: Windows
23 Classifier: Programming Language :: Python
24 Classifier: Topic :: Internet :: Name Service (DNS)
25 Classifier: Topic :: Software Development :: Libraries :: Python Modules
26 Classifier: Programming Language :: Python :: 3
27 Classifier: Programming Language :: Python :: 3.6
28 Classifier: Programming Language :: Python :: 3.7
29 Classifier: Programming Language :: Python :: 3.8
30 Classifier: Programming Language :: Python :: 3.9
31 Provides: dns
32 Requires-Python: >=3.6
33 Description-Content-Type: text/plain
34 Provides-Extra: curio
35 Provides-Extra: dnssec
36 Provides-Extra: doh
37 Provides-Extra: idna
38 Provides-Extra: trio
0 .flake8
1 .gitignore
2 .readthedocs.yml
3 .travis.yml
4 LICENSE
5 MANIFEST.in
6 Makefile
7 README.md
8 SECURITY.md
9 azure-pipelines.yml
10 codecov.yml
11 mypy.ini
12 pylintrc
13 pyproject.toml
14 pytest.ini
15 setup.cfg
16 setup.py
17 .github/dependabot.yml
18 dns/__init__.py
19 dns/_asyncbackend.py
20 dns/_asyncio_backend.py
21 dns/_curio_backend.py
22 dns/_immutable_attr.py
23 dns/_immutable_ctx.py
24 dns/_trio_backend.py
25 dns/asyncbackend.py
26 dns/asyncbackend.pyi
27 dns/asyncquery.py
28 dns/asyncquery.pyi
29 dns/asyncresolver.py
30 dns/asyncresolver.pyi
31 dns/dnssec.py
32 dns/dnssec.pyi
33 dns/e164.py
34 dns/e164.pyi
35 dns/edns.py
36 dns/entropy.py
37 dns/entropy.pyi
38 dns/enum.py
39 dns/exception.py
40 dns/exception.pyi
41 dns/flags.py
42 dns/grange.py
43 dns/immutable.py
44 dns/inet.py
45 dns/inet.pyi
46 dns/ipv4.py
47 dns/ipv6.py
48 dns/message.py
49 dns/message.pyi
50 dns/name.py
51 dns/name.pyi
52 dns/namedict.py
53 dns/node.py
54 dns/node.pyi
55 dns/opcode.py
56 dns/py.typed
57 dns/query.py
58 dns/query.pyi
59 dns/rcode.py
60 dns/rdata.py
61 dns/rdata.pyi
62 dns/rdataclass.py
63 dns/rdataset.py
64 dns/rdataset.pyi
65 dns/rdatatype.py
66 dns/renderer.py
67 dns/resolver.py
68 dns/resolver.pyi
69 dns/reversename.py
70 dns/reversename.pyi
71 dns/rrset.py
72 dns/rrset.pyi
73 dns/serial.py
74 dns/set.py
75 dns/tokenizer.py
76 dns/transaction.py
77 dns/tsig.py
78 dns/tsigkeyring.py
79 dns/tsigkeyring.pyi
80 dns/ttl.py
81 dns/update.py
82 dns/update.pyi
83 dns/version.py
84 dns/versioned.py
85 dns/wire.py
86 dns/xfr.py
87 dns/zone.py
88 dns/zone.pyi
89 dns/zonefile.py
90 dns/rdtypes/__init__.py
91 dns/rdtypes/dnskeybase.py
92 dns/rdtypes/dnskeybase.pyi
93 dns/rdtypes/dsbase.py
94 dns/rdtypes/euibase.py
95 dns/rdtypes/mxbase.py
96 dns/rdtypes/nsbase.py
97 dns/rdtypes/svcbbase.py
98 dns/rdtypes/tlsabase.py
99 dns/rdtypes/txtbase.py
100 dns/rdtypes/txtbase.pyi
101 dns/rdtypes/util.py
102 dns/rdtypes/ANY/AFSDB.py
103 dns/rdtypes/ANY/AMTRELAY.py
104 dns/rdtypes/ANY/AVC.py
105 dns/rdtypes/ANY/CAA.py
106 dns/rdtypes/ANY/CDNSKEY.py
107 dns/rdtypes/ANY/CDS.py
108 dns/rdtypes/ANY/CERT.py
109 dns/rdtypes/ANY/CNAME.py
110 dns/rdtypes/ANY/CSYNC.py
111 dns/rdtypes/ANY/DLV.py
112 dns/rdtypes/ANY/DNAME.py
113 dns/rdtypes/ANY/DNSKEY.py
114 dns/rdtypes/ANY/DS.py
115 dns/rdtypes/ANY/EUI48.py
116 dns/rdtypes/ANY/EUI64.py
117 dns/rdtypes/ANY/GPOS.py
118 dns/rdtypes/ANY/HINFO.py
119 dns/rdtypes/ANY/HIP.py
120 dns/rdtypes/ANY/ISDN.py
121 dns/rdtypes/ANY/LOC.py
122 dns/rdtypes/ANY/MX.py
123 dns/rdtypes/ANY/NINFO.py
124 dns/rdtypes/ANY/NS.py
125 dns/rdtypes/ANY/NSEC.py
126 dns/rdtypes/ANY/NSEC3.py
127 dns/rdtypes/ANY/NSEC3PARAM.py
128 dns/rdtypes/ANY/OPENPGPKEY.py
129 dns/rdtypes/ANY/OPT.py
130 dns/rdtypes/ANY/PTR.py
131 dns/rdtypes/ANY/RP.py
132 dns/rdtypes/ANY/RRSIG.py
133 dns/rdtypes/ANY/RT.py
134 dns/rdtypes/ANY/SMIMEA.py
135 dns/rdtypes/ANY/SOA.py
136 dns/rdtypes/ANY/SPF.py
137 dns/rdtypes/ANY/SSHFP.py
138 dns/rdtypes/ANY/TKEY.py
139 dns/rdtypes/ANY/TLSA.py
140 dns/rdtypes/ANY/TSIG.py
141 dns/rdtypes/ANY/TXT.py
142 dns/rdtypes/ANY/URI.py
143 dns/rdtypes/ANY/X25.py
144 dns/rdtypes/ANY/ZONEMD.py
145 dns/rdtypes/ANY/__init__.py
146 dns/rdtypes/CH/A.py
147 dns/rdtypes/CH/__init__.py
148 dns/rdtypes/IN/A.py
149 dns/rdtypes/IN/AAAA.py
150 dns/rdtypes/IN/APL.py
151 dns/rdtypes/IN/DHCID.py
152 dns/rdtypes/IN/HTTPS.py
153 dns/rdtypes/IN/IPSECKEY.py
154 dns/rdtypes/IN/KX.py
155 dns/rdtypes/IN/NAPTR.py
156 dns/rdtypes/IN/NSAP.py
157 dns/rdtypes/IN/NSAP_PTR.py
158 dns/rdtypes/IN/PX.py
159 dns/rdtypes/IN/SRV.py
160 dns/rdtypes/IN/SVCB.py
161 dns/rdtypes/IN/WKS.py
162 dns/rdtypes/IN/__init__.py
163 dnspython.egg-info/PKG-INFO
164 dnspython.egg-info/SOURCES.txt
165 dnspython.egg-info/dependency_links.txt
166 dnspython.egg-info/requires.txt
167 dnspython.egg-info/top_level.txt
168 doc/.gitignore
169 doc/Makefile
170 doc/async-backend.rst
171 doc/async-query.rst
172 doc/async-resolver-class.rst
173 doc/async-resolver-functions.rst
174 doc/async-resolver.rst
175 doc/async.rst
176 doc/community.rst
177 doc/conf.py
178 doc/dnssec.rst
179 doc/exceptions.rst
180 doc/inbound-xfr-class.rst
181 doc/index.rst
182 doc/installation.rst
183 doc/license.rst
184 doc/manual.rst
185 doc/message-class.rst
186 doc/message-edns.rst
187 doc/message-flags.rst
188 doc/message-make.rst
189 doc/message-opcode.rst
190 doc/message-query.rst
191 doc/message-rcode.rst
192 doc/message-update.rst
193 doc/message.rst
194 doc/name-class.rst
195 doc/name-codecs.rst
196 doc/name-dict.rst
197 doc/name-helpers.rst
198 doc/name-make.rst
199 doc/name.rst
200 doc/query.rst
201 doc/rdata-class.rst
202 doc/rdata-make.rst
203 doc/rdata-set-classes.rst
204 doc/rdata-set-make.rst
205 doc/rdata-subclasses.rst
206 doc/rdata-types.rst
207 doc/rdata.rst
208 doc/rdataclass-list.rst
209 doc/rdatatype-list.rst
210 doc/resolver-caching.rst
211 doc/resolver-class.rst
212 doc/resolver-functions.rst
213 doc/resolver-override.rst
214 doc/resolver.rst
215 doc/rfc.rst
216 doc/typing.rst
217 doc/utilities.rst
218 doc/whatsnew.rst
219 doc/zone-class.rst
220 doc/zone-make.rst
221 doc/zone.rst
222 doc/util/auto-values.py
223 examples/async_dns.py
224 examples/ddns.py
225 examples/doh-json.py
226 examples/doh.py
227 examples/e164.py
228 examples/ecs.py
229 examples/mx.py
230 examples/name.py
231 examples/query_specific.py
232 examples/receive_notify.py
233 examples/reverse.py
234 examples/reverse_name.py
235 examples/xfr.py
236 examples/zonediff.py
237 tests/Makefile
238 tests/__init__.py
239 tests/example
240 tests/example1.good
241 tests/example2.good
242 tests/example3.good
243 tests/md_module.py
244 tests/mx-2-0.pickle
245 tests/nanonameserver.py
246 tests/query
247 tests/stxt_module.py
248 tests/svcb_test_vectors.generic
249 tests/svcb_test_vectors.text
250 tests/test_address.py
251 tests/test_async.py
252 tests/test_bugs.py
253 tests/test_constants.py
254 tests/test_dnssec.py
255 tests/test_doh.py
256 tests/test_edns.py
257 tests/test_entropy.py
258 tests/test_exceptions.py
259 tests/test_flags.py
260 tests/test_generate.py
261 tests/test_grange.py
262 tests/test_immutable.py
263 tests/test_message.py
264 tests/test_name.py
265 tests/test_namedict.py
266 tests/test_nsec3.py
267 tests/test_nsec3_hash.py
268 tests/test_ntoaaton.py
269 tests/test_processing_order.py
270 tests/test_query.py
271 tests/test_rdata.py
272 tests/test_rdataset.py
273 tests/test_rdtypeandclass.py
274 tests/test_rdtypeanydnskey.py
275 tests/test_rdtypeanyeui.py
276 tests/test_rdtypeanyloc.py
277 tests/test_rdtypeanytkey.py
278 tests/test_renderer.py
279 tests/test_resolution.py
280 tests/test_resolver.py
281 tests/test_resolver_override.py
282 tests/test_rrset.py
283 tests/test_serial.py
284 tests/test_set.py
285 tests/test_svcb.py
286 tests/test_tokenizer.py
287 tests/test_transaction.py
288 tests/test_tsig.py
289 tests/test_tsigkeyring.py
290 tests/test_ttl.py
291 tests/test_update.py
292 tests/test_wire.py
293 tests/test_xfr.py
294 tests/test_zone.py
295 tests/test_zonedigest.py
296 tests/ttxt_module.py
297 tests/utest.py
298 tests/util.py
299 util/constants-tool
300 util/generate-mx-pickle.py
301 util/generate-rdatatype-doc.py
0
1 [curio]
2 curio>=1.2
3 sniffio>=1.1
4
5 [dnssec]
6 cryptography>=2.6
7
8 [doh]
9 requests
10 requests-toolbelt
11
12 [idna]
13 idna>=2.1
14
15 [trio]
16 sniffio>=1.1
17 trio>=0.14.0
88 you should use the higher level ``dns.asyncresolver`` module; see
99 :ref:`async_resolver`.
1010
11 There is currently no support for zone transfers or DNS-over-HTTPS
12 using asynchronous I/O but we hope to offer this in the future.
11 There is currently no support for DNS-over-HTTPS using asynchronous
12 I/O but we hope to offer this in the future.
1313
1414 UDP
1515 ---
3030 ---
3131
3232 .. autofunction:: dns.asyncquery.tls
33
34 Zone Transfers
35 --------------
36
37 .. autofunction:: dns.asyncquery.inbound_xfr
44
55 .. autofunction:: dns.asyncresolver.resolve
66 .. autofunction:: dns.asyncresolver.resolve_address
7 .. autofunction:: dns.asyncresolver.canonical_name
78 .. autofunction:: dns.asyncresolver.zone_for_name
0 .. _inbound-xfr-class:
1
2 The dns.xfr.Inbound Class and make_query() function
3 ---------------------------------------------------
4
5 The ``Inbound`` class provides support for inbound DNS zone transfers,
6 both AXFR and IXFR. It is invoked by I/O code, i.e.
7 :py:func:`dns.query.inbound_xfr` or
8 :py:func:`dns.asyncquery.inbound_xfr`. When a message related to the
9 transfer arrives, the I/O code calls the ``process_message()`` method
10 which adds the content to the pending transaction.
11
12 The ``make_query()`` function is used to making the query message for
13 the query methods to use in more complex situations, e.g. with TSIG or
14 EDNS.
15
16 .. autoclass:: dns.xfr.Inbound
17 :members:
18
19 .. autofunction:: dns.xfr.make_query
1414 async
1515 exceptions
1616 utilities
17 typing
3131 .. autofunction:: dns.edns.get_option_class
3232 .. autofunction:: dns.edns.option_from_wire_parser
3333 .. autofunction:: dns.edns.option_from_wire
34 .. autofunction:: dns.edns.register_type
22 The dns.message.QueryMessage Class
33 ----------------------------------
44
5 The ``dns.update.QueryMessage`` class is used for ordinary DNS query messages.
5 The ``dns.message.QueryMessage`` class is used for ordinary DNS query messages.
66
77 .. autoclass:: dns.message.QueryMessage
88 :members:
9
10 The dns.message.ChainingResult Class
11 ------------------------------------
12
13 Objects of the ``dns.message.ChainingResult`` class are returned by the
14 ``dns.message.QueryMessage.resolve_chaining()`` method.
15
16 .. autoclass:: dns.message.ChainingResult
17 :members:
2828 absolute names.
2929
3030 Names may also be compared according to the DNS tree hierarchy with
31 the ``fullcompare()`` method. For example ```www.dnspython.org.`` is
32 a subdomain of ``dnspython.org.``. See the method description for
33 full details.
31 the :py:func:`dns.name.Name.fullcompare` method. For example
32 ```www.dnspython.org.`` is a subdomain of ``dnspython.org.``. See the
33 method description for full details.
3434
3535 .. toctree::
3636
4040 Zone Transfers
4141 --------------
4242
43 As of dnspython 2.1, :py:func:`dns.query.xfr` is deprecated. Please use
44 :py:func:`dns.query.inbound_xfr` instead.
45
46 .. autoclass:: dns.query.UDPMode
47
48 .. autofunction:: dns.query.inbound_xfr
49
4350 .. autofunction:: dns.query.xfr
432432
433433 A ``dns.name.Name``, the exchange name.
434434
435 .. autoclass:: dns.rdtypes.ANY.SMIMEA.SMIMEA
436 :members:
437
438 .. attribute:: usage
439
440 An ``int``, the certificate usage.
441
442 .. attribute:: selector
443
444 An ``int``, the selector.
445
446 .. attribute:: mtype
447
448 An ``int``, the matching type.
449
450 .. attribute:: cert
451
452 A ``bytes``, the certificate association data.
453
435454 .. autoclass:: dns.rdtypes.ANY.SOA.SOA
436455 :members:
437456
00 Rdatatypes
1 ==========
1 ----------
22
33 .. py:data:: dns.rdatatype.A
44 :annotation: = 1
88 :annotation: = 28
99 .. py:data:: dns.rdatatype.AFSDB
1010 :annotation: = 18
11 .. py:data:: dns.rdatatype.AMTRELAY
12 :annotation: = 259
1113 .. py:data:: dns.rdatatype.ANY
1214 :annotation: = 255
1315 .. py:data:: dns.rdatatype.APL
4850 :annotation: = 13
4951 .. py:data:: dns.rdatatype.HIP
5052 :annotation: = 55
53 .. py:data:: dns.rdatatype.HTTPS
54 :annotation: = 65
5155 .. py:data:: dns.rdatatype.IPSECKEY
5256 :annotation: = 45
5357 .. py:data:: dns.rdatatype.ISDN
8084 :annotation: = 15
8185 .. py:data:: dns.rdatatype.NAPTR
8286 :annotation: = 35
83 .. py:data:: dns.rdatatype.NONE
84 :annotation: = 0
87 .. py:data:: dns.rdatatype.NINFO
88 :annotation: = 56
8589 .. py:data:: dns.rdatatype.NS
8690 :annotation: = 2
8791 .. py:data:: dns.rdatatype.NSAP
8892 :annotation: = 22
89 .. py:data:: dns.rdatatype.NSAP-PTR
93 .. py:data:: dns.rdatatype.NSAP_PTR
9094 :annotation: = 23
9195 .. py:data:: dns.rdatatype.NSEC
9296 :annotation: = 47
98102 :annotation: = 10
99103 .. py:data:: dns.rdatatype.NXT
100104 :annotation: = 30
105 .. py:data:: dns.rdatatype.OPENPGPKEY
106 :annotation: = 61
101107 .. py:data:: dns.rdatatype.OPT
102108 :annotation: = 41
103109 .. py:data:: dns.rdatatype.PTR
112118 :annotation: = 21
113119 .. py:data:: dns.rdatatype.SIG
114120 :annotation: = 24
121 .. py:data:: dns.rdatatype.SMIMEA
122 :annotation: = 53
115123 .. py:data:: dns.rdatatype.SOA
116124 :annotation: = 6
117125 .. py:data:: dns.rdatatype.SPF
120128 :annotation: = 33
121129 .. py:data:: dns.rdatatype.SSHFP
122130 :annotation: = 44
131 .. py:data:: dns.rdatatype.SVCB
132 :annotation: = 64
123133 .. py:data:: dns.rdatatype.TA
124134 :annotation: = 32768
125135 .. py:data:: dns.rdatatype.TKEY
130140 :annotation: = 250
131141 .. py:data:: dns.rdatatype.TXT
132142 :annotation: = 16
143 .. py:data:: dns.rdatatype.TYPE0
144 :annotation: = 0
133145 .. py:data:: dns.rdatatype.UNSPEC
134146 :annotation: = 103
135147 .. py:data:: dns.rdatatype.URI
1010
1111 Two thread-safe cache implementations are provided, a simple
1212 dictionary-based Cache, and an LRUCache which provides cache size
13 control suitable for use in web crawlers.
13 control suitable for use in web crawlers. Both are subclasses of
14 a common base class which provides basic statistics. The LRUCache can
15 also provide a hits count per cache entry.
16
17 .. autoclass:: dns.resolver.CacheBase
18 :members:
1419
1520 .. autoclass:: dns.resolver.Cache
1621 :members:
1823 .. autoclass:: dns.resolver.LRUCache
1924 :members:
2025
26 .. autoclass:: dns.resolver.CacheStatistics
27 :members:
44
55 .. autofunction:: dns.resolver.resolve
66 .. autofunction:: dns.resolver.resolve_address
7 .. autofunction:: dns.resolver.canonical_name
78 .. autofunction:: dns.resolver.zone_for_name
89 .. autofunction:: dns.resolver.query
910 .. autodata:: dns.resolver.default_resolver
4141 Negative Caching.
4242
4343 `RFC 2845 <https://tools.ietf.org/html/rfc2845>`_
44 Transaction Sigatures (TSIG)
44 Transaction Signatures (TSIG)
4545
4646 `RFC 3007 <https://tools.ietf.org/html/rfc3007>`_
4747 Dynamic Updates
140140 `RFC 1035 <https://tools.ietf.org/html/rfc1035>`_
141141 RRSIG
142142 `RFC 4034 <https://tools.ietf.org/html/rfc4034>`_
143 SMIMEA
144 `RFC 8162 <https://tools.ietf.org/html/rfc8162>`_
143145 SOA
144146 `RFC 1035 <https://tools.ietf.org/html/rfc1035>`_
145147 SPF
0 .. _typing:
1
2 A Note on Typing
3 ----------------
4
5 Dnspython has partial support for type annotations in separate .pyi
6 files. Type information will not be integrated into the main files
7 until major LTS versions of various Linux distributions containing 3.6
8 are beyond their support times. Improvements to the .pyi files are
9 welcome during this time.
00 .. _whatsnew:
11
2 What's New in dnspython 2.0.0
3 =============================
2 What's New in dnspython
3 =======================
4
5 2.2.0 (in development)
6 ----------------------
7
8 * SVCB and HTTPS records have been updated to track the evolving draft
9 standard.
10
11 * The ZONEMD type has been added.
12
13 * The resolver now returns a LifetimeTimeout exception which includes
14 an error trace like the NoNameservers exception. This class is a subclass of
15 dns.exception.Timeout for backwards compatibility.
16
17 2.1.0
18 ----------------------
19
20 * End-of-line comments are now associated with rdata when read from text.
21 For backwards compatibility with prior versions of dnspython, they are
22 only emitted in to_text() when requested.
23
24 * Synchronous I/O is a bit more efficient, as we now try the I/O and only
25 use poll() or select() if the I/O would block.
26
27 * The resolver cache classes now offer basic hit and miss statistics, and
28 the LRUCache can also provide hits for every cache key.
29
30 * The resolver has a canonical_name() method.
31
32 * There is now a registration mechanism for EDNS option types.
33
34 * The default EDNS payload size has changed from 1280 to 1232.
35
36 * The SVCB, HTTPS, and SMIMEA RR types are now supported.
37
38 * TSIG has been enhanced with TKEY and GSS-TSIG support. Thanks to
39 Nick Hall for writing this.
40
41 * Zones now can be updated via transactions.
42
43 * A new zone subclass, dns.versioned.Zone is available which has a
44 thread-safe transaction implementation and support for keeping many
45 versions of a zone.
46
47 * The zone file reading code has been adapted to use transactions, and
48 is now a public API.
49
50 * Inbound zone transfer support has been rewritten and is available as
51 dns.query.inbound_xfr() and dns.asyncquery.inbound_xfr(). It uses
52 the transaction mechanism, and fully supports IXFR and AXFR.
53
54 2.0.0
55 -----
456
557 * Python 3.6 or newer is required.
658
11
22 The dns.zone.Zone Class
33 -----------------------
4
5 The ``Zone`` class provides a non-thread-safe implementation of a DNS zone,
6 as well as a lightweight translation mechanism that allows it to be atomically
7 updated. For more complicated transactional needs, or for concurrency, please
8 use the :py:class:`dns.versioned.Zone` class (described below).
49
510 .. autoclass:: dns.zone.Zone
611 :members:
2732 subclassed if a different node factory is desired.
2833 The node factory is a class or callable that returns a subclass of
2934 ``dns.node.Node``.
35
36
37 The dns.versioned.Zone Class
38 ----------------------------
39
40 A versioned Zone is a subclass of ``Zone`` that provides a thread-safe
41 multiversioned transactional API. There can be many concurrent
42 readers, of possibly different versions, and at most one active
43 writer. Others cannot see the changes being made by the writer until
44 it commits. Versions are immutable once committed.
45
46 The read-only parts of the standard zone API continue to be available, and
47 are equivalent to doing a single-query read-only transaction. Note that
48 unless reading is done through a transaction, version stability is not
49 guaranteed between successive calls. Attempts to use zone API methods
50 that directly manipulate the zone, e.g. ``replace_rdataset`` will result
51 in a ``UseTransaction`` exception.
52
53 Transactions are context managers, and are created with ``reader()`` or
54 ``writer()``. For example:
55
56 ::
57
58 # Print the SOA serial number of the most recent version
59 with zone.reader() as txn:
60 rdataset = txn.get('@', 'in', 'soa')
61 print('The most recent serial number is', rdataset[0].serial)
62
63 # Write an A RR and increment the SOA serial number to the next value.
64 with zone.writer() as txn:
65 txn.replace('node1', dns.rdataset.from_text('in', 'a', 300,
66 '10.0.0.1'))
67 txn.set_serial()
68
69 See below for more information on the ``Transaction`` API.
70
71 .. autoclass:: dns.versioned.Zone
72 :exclude-members: delete_node, delete_rdataset, replace_rdataset
73 :members:
74
75 .. attribute:: rdclass
76
77 The zone's rdata class, an ``int``; the default is class IN.
78
79 .. attribute:: origin
80
81 The origin of the zone, a ``dns.name.Name``.
82
83 .. attribute:: nodes
84
85 A dictionary mapping the names of nodes in the zone to the nodes
86 themselves.
87
88 .. attribute:: relativize
89
90 A ``bool``, which is ``True`` if names in the zone should be relativized.
91
92
93 The TransactionManager Class
94 ----------------------------
95
96 This is the abstract base class of all objects that support transactions.
97
98 .. autoclass:: dns.transaction.TransactionManager
99 :members:
100
101
102 The Transaction Class
103 ---------------------
104
105 .. autoclass:: dns.transaction.Transaction
106 :members:
107
77
88 zone-class
99 zone-make
10 inbound-xfr-class
0
1 import sys
2
3 import trio
4
5 import dns.message
6 import dns.asyncquery
7 import dns.asyncresolver
8
9 async def main():
10 if len(sys.argv) > 1:
11 host = sys.argv[0]
12 else:
13 host = 'www.dnspython.org'
14 q = dns.message.make_query(host, 'A')
15 r = await dns.asyncquery.udp(q, '8.8.8.8')
16 print(r)
17 q = dns.message.make_query(host, 'A')
18 r = await dns.asyncquery.tcp(q, '8.8.8.8')
19 print(r)
20 q = dns.message.make_query(host, 'A')
21 r = await dns.asyncquery.tls(q, '8.8.8.8')
22 print(r)
23 a = await dns.asyncresolver.resolve(host, 'A')
24 print(a.response)
25 zn = await dns.asyncresolver.zone_for_name(host)
26 print(zn)
27
28 if __name__ == '__main__':
29 trio.run(main)
22 # This is an example of sending DNS queries over HTTPS (DoH) with dnspython.
33 # Requires use of the requests module's Session object.
44 #
5 # See https://2.python-requests.org//en/latest/user/advanced/#session-objects
5 # See https://2.python-requests.org/en/latest/user/advanced/#session-objects
66 # for more details about Session objects
77 import requests
88
11
22 import dns.resolver
33
4 answers = dns.resolver.query('nominum.com', 'MX')
4 answers = dns.resolver.resolve('nominum.com', 'MX')
55 for rdata in answers:
66 print('Host', rdata.exchange, 'has preference', rdata.preference)
3030
3131 resolver = dns.resolver.Resolver(configure=False)
3232 resolver.nameservers = ['8.8.8.8']
33 answer = resolver.query('amazon.com', 'NS')
33 answer = resolver.resolve('amazon.com', 'NS')
3434 print('The nameservers are:')
3535 for rr in answer:
3636 print(rr.target)
+0
-29
examples/trio.py less more
0
1 import sys
2 import trio
3
4 import dns.message
5 import dns.trio.query
6 import dns.trio.resolver
7
8 async def main():
9 if len(sys.argv) > 1:
10 host = sys.argv[0]
11 else:
12 host = 'www.dnspython.org'
13 q = dns.message.make_query(host, 'A')
14 r = await dns.trio.query.udp(q, '8.8.8.8')
15 print(r)
16 q = dns.message.make_query(host, 'A')
17 r = await dns.trio.query.stream(q, '8.8.8.8')
18 print(r)
19 q = dns.message.make_query(host, 'A')
20 r = await dns.trio.query.stream(q, '8.8.8.8', tls=True)
21 print(r)
22 a = await dns.trio.resolver.resolve(host, 'A')
23 print(a.response)
24 zn = await dns.trio.resolver.zone_for_name(host)
25 print(zn)
26
27 if __name__ == '__main__':
28 trio.run(main)
33 import dns.resolver
44 import dns.zone
55
6 soa_answer = dns.resolver.query('dnspython.org', 'SOA')
7 master_answer = dns.resolver.query(soa_answer[0].mname, 'A')
6 soa_answer = dns.resolver.resolve('dnspython.org', 'SOA')
7 master_answer = dns.resolver.resolve(soa_answer[0].mname, 'A')
88
99 z = dns.zone.from_xfr(dns.query.xfr(master_answer[0].address, 'dnspython.org'))
1010 for n in sorted(z.nodes.keys()):
88
99 enable=
1010 all,
11 python3
1211
1312 # It's worth looking at len-as-condition for optimization, but it's disabled
1413 # here as it is not a correctness thing. Similarly eq-without-hash is
1716 disable=
1817 R,
1918 I,
20 anomalous-backslash-in-string,
21 arguments-differ,
22 assigning-non-slot,
23 bad-builtin,
24 bad-continuation,
2519 broad-except,
26 deprecated-method,
2720 fixme,
2821 global-statement,
2922 invalid-name,
30 missing-docstring,
23 missing-module-docstring,
24 missing-class-docstring,
25 missing-function-docstring,
3126 no-absolute-import,
32 no-member,
27 no-member, # We'd like to use this, but our immutability is too hard for it
3328 protected-access,
3429 redefined-builtin,
3530 too-many-lines,
36 unused-argument,
37 unused-variable,
38 wrong-import-order,
39 wrong-import-position,
40 len-as-condition,
41 eq-without-hash,
42 next-method-defined,
31 raise-missing-from, # we should start doing this, but too noisy for now
4332
4433 [REPORTS]
4534
4635 # Set the output format. Available formats are text, parseable, colorized, msvs
4736 # (visual studio) and html. You can also give a reporter class, eg
4837 # mypackage.mymodule.MyReporterClass.
49 output-format=colorized
38 #output-format=colorized
39 output-format=parseable
5040
5141 # Tells whether to display a full report or only the messages
5242 reports=no
00 [tool.poetry]
11 name = "dnspython"
2 version = "2.0.0"
2 version = "2.2.0"
33 description = "DNS toolkit"
44 authors = ["Bob Halley <halley@dnspython.org>"]
55 license = "ISC"
1212 requests-toolbelt = {version="^0.9.1", optional=true}
1313 requests = {version="^2.23.0", optional=true}
1414 idna = {version="^2.1", optional=true}
15 cryptography = {version="^2.6", optional=true}
16 trio = {version=">=0.14,<0.17", optional=true}
15 cryptography = {version=">=2.6,<4.0", optional=true}
16 trio = {version=">=0.14,<0.19", optional=true}
1717 curio = {version="^1.2", optional=true}
1818 sniffio = {version="^1.1", optional=true}
1919
2020 [tool.poetry.dev-dependencies]
21 mypy = "^0.782"
22 pytest = "^5.4.1"
21 mypy = "^0.812"
22 pytest = ">=5.4.1,<7"
2323 pytest-cov = "^2.10.0"
2424 flake8 = "^3.7.9"
25 sphinx = "^3.0.0"
25 sphinx = "^4.0.0"
2626 coverage = "^5.1"
2727 twine = "^3.1.1"
28 wheel = "^0.34.2"
28 wheel = "^0.35.0"
29 pylint = "^2.7.4"
2930
3031 [tool.poetry.extras]
3132 doh = ['requests', 'requests-toolbelt']
3536 curio = ['curio', 'sniffio']
3637
3738 [build-system]
38 requires = ["poetry>=0.12"]
39 build-backend = "poetry.masonry.api"
39 requires = ["poetry-core"]
40 build-backend = "poetry.core.masonry.api"
41
42 [tool.setuptools_scm]
00 [metadata]
1 name = dnspython
2 author = Bob Halley
3 author_email = halley@dnspython.org
4 license = ISC
15 license_file = LICENSE
6 description = DNS toolkit
7 url = http://www.dnspython.org
8 long_description = dnspython is a DNS toolkit for Python. It supports almost all
9 record types. It can be used for queries, zone transfers, and dynamic
10 updates. It supports TSIG authenticated messages and EDNS0.
11
12 dnspython provides both high and low level access to DNS. The high
13 level classes perform queries for data of a given name, type, and
14 class, and return an answer set. The low level classes allow
15 direct manipulation of DNS zones, messages, names, and records.
16 long_description_content_type = text/plain
17 classifiers =
18 Development Status :: 5 - Production/Stable
19 Intended Audience :: Developers
20 Intended Audience :: System Administrators
21 License :: OSI Approved :: ISC License (ISCL)
22 Operating System :: POSIX
23 Operating System :: Microsoft :: Windows
24 Programming Language :: Python
25 Topic :: Internet :: Name Service (DNS)
26 Topic :: Software Development :: Libraries :: Python Modules
27 Programming Language :: Python :: 3
28 Programming Language :: Python :: 3.6
29 Programming Language :: Python :: 3.7
30 Programming Language :: Python :: 3.8
31 Programming Language :: Python :: 3.9
32 provides = dns
33
34 [options]
35 packages =
36 dns
37 dns.rdtypes
38 dns.rdtypes.IN
39 dns.rdtypes.ANY
40 dns.rdtypes.CH
41 python_requires = >=3.6
42 test_suite = tests
43 setup_requires = setuptools>=44; wheel; setuptools_scm[toml]>=3.4.3
44
45 [options.extras_require]
46 doh = requests; requests-toolbelt
47 idna = idna>=2.1
48 dnssec = cryptography>=2.6
49 trio = trio>=0.14.0; sniffio>=1.1
50 curio = curio>=1.2; sniffio>=1.1
51
52 [options.package_data]
53 dns = py.typed
54
55 [egg_info]
56 tag_build =
57 tag_date = 0
58
1919 import sys
2020 from setuptools import setup
2121
22 version = '2.0.0'
2322
2423 try:
25 sys.argv.remove("--cython-compile")
24 sys.argv.remove("--cython-compile")
2625 except ValueError:
27 compile_cython = False
26 compile_cython = False
2827 else:
2928 compile_cython = True
3029 from Cython.Build import cythonize
3231 language_level='3')
3332
3433 kwargs = {
35 'name' : 'dnspython',
36 'version' : version,
37 'description' : 'DNS toolkit',
38 'long_description' : \
39 """dnspython is a DNS toolkit for Python. It supports almost all
40 record types. It can be used for queries, zone transfers, and dynamic
41 updates. It supports TSIG authenticated messages and EDNS0.
42
43 dnspython provides both high and low level access to DNS. The high
44 level classes perform queries for data of a given name, type, and
45 class, and return an answer set. The low level classes allow
46 direct manipulation of DNS zones, messages, names, and records.""",
47 'author' : 'Bob Halley',
48 'author_email' : 'halley@dnspython.org',
49 'license' : 'ISC',
50 'url' : 'http://www.dnspython.org',
51 'packages' : ['dns', 'dns.rdtypes', 'dns.rdtypes.IN', 'dns.rdtypes.ANY',
52 'dns.rdtypes.CH'],
53 'package_data' : {'dns': ['py.typed']},
54 'download_url' : \
55 'http://www.dnspython.org/kits/{}/dnspython-{}.tar.gz'.format(version, version),
56 'classifiers' : [
57 "Development Status :: 5 - Production/Stable",
58 "Intended Audience :: Developers",
59 "Intended Audience :: System Administrators",
60 "License :: OSI Approved :: ISC License (ISCL)",
61 "Operating System :: POSIX",
62 "Operating System :: Microsoft :: Windows",
63 "Programming Language :: Python",
64 "Topic :: Internet :: Name Service (DNS)",
65 "Topic :: Software Development :: Libraries :: Python Modules",
66 "Programming Language :: Python :: 3",
67 "Programming Language :: Python :: 3.6",
68 "Programming Language :: Python :: 3.7",
69 "Programming Language :: Python :: 3.8",
70 ],
71 'python_requires': '>=3.6',
72 'test_suite': 'tests',
73 'provides': ['dns'],
74 'extras_require': {
75 'DOH': ['requests', 'requests-toolbelt'],
76 'IDNA': ['idna>=2.1'],
77 'DNSSEC': ['cryptography>=2.6'],
78 'trio': ['trio>=0.14.0', 'sniffio>=1.1'],
79 'curio': ['curio>=1.2', 'sniffio>=1.1'],
80 },
8134 'ext_modules': ext_modules if compile_cython else None,
8235 'zip_safe': False if compile_cython else None,
8336 }
7878 hinfo02 HINFO "PC" "NetBSD"
7979 isdn01 ISDN "isdn-address"
8080 isdn02 ISDN "isdn-address" "subaddress"
81 isdn03 ISDN "isdn-address"
82 isdn04 ISDN "isdn-address" "subaddress"
81 isdn03 ISDN isdn-address
82 isdn04 ISDN isdn-address subaddress
8383 ;key01 KEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o jqf0BaqHT+8=
8484 ;key02 KEY HOST|FLAG4 DNSSEC RSAMD5 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o jqf0BaqHT+8=
8585 kx01 KX 10 kdc
8686 kx02 KX 10 .
87 loc01 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m
88 loc02 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m
87 loc01 LOC 60 9 N 24 39 E 10 20 2000 20
88 loc02 LOC 60 09 00.000 N 24 39 00.000 E 10.00m 20.00m 2000.00m 20.00m
8989 loc03 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m
9090 loc04 LOC 60 9 1.5 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m
9191 loc05 LOC 60 9 1.51 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m
9292 loc06 LOC 60 9 1 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m
93 loc07 LOC 0 9 1 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m
93 loc07 LOC 0 9 1 N 24 39 E 10 90000000 2000 20
9494 loc08 LOC 0 9 1 S 24 39 0.000 E 10.00m 90000000.00m 2000m 20m
9595 ;;
9696 ;; XXXRTH These are all obsolete and unused. dnspython doesn't implement
137137 tlsa1 TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
138138 tlsa2 TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
139139 tlsa3 TLSA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94
140 smimea1 SMIMEA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
141 smimea2 SMIMEA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
142 smimea3 SMIMEA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94
140143 txt01 TXT "foo"
141144 txt02 TXT "foo" "bar"
142145 txt03 TXT foo
164167 wks03 WKS 10.0.0.2 6 ( 65535 )
165168 x2501 X25 "123456789"
166169 ds01 DS 12345 3 1 123456789abcdef67890123456789abcdef67890
170 dlv01 DLV 12345 3 1 123456789abcdef67890123456789abcdef67890
167171 apl01 APL 1:192.168.32.0/21 !1:192.168.38.0/28
168172 apl02 APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8
169173 unknown2 TYPE999 \# 8 0a0000010a000001
231235 amtrelay05 AMTRELAY 128 1 3 amtrelays.example.com.
232236 csync0 CSYNC 12345 0 A MX RRSIG NSEC TYPE1234
233237 avc01 AVC "app-name:WOLFGANG|app-class:OAM|business=yes"
238 zonemd01 ZONEMD 2018031900 1 1 62e6cf51b02e54b9 b5f967d547ce4313 6792901f9f88e637 493daaf401c92c27 9dd10f0edb1c56f8 080211f8480ee306
239 zonemd02 ZONEMD 2018031900 1 2 08cfa1115c7b948c 4163a901270395ea 226a930cd2cbcf2f a9a5e6eb85f37c8a 4e114d884e66f176 eab121cb02db7d65 2e0cc4827e7a3204 f166b47e5613fd27
240 zonemd03 ZONEMD 2018031900 1 240 e2d523f654b9422a 96c5a8f44607bbee
241 zonemd04 ZONEMD 2018031900 241 1 e1846540e33a9e41 89792d18d5d131f6 05fc283e aaaaaaaa aaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaa aaaaaaaaaaaaaaa
242 svcb01 SVCB (
243 100 foo.com. mandatory="alpn,port" alpn="h2,h3" no-default-alpn port="12345"
244 ech="abcd" ipv4hint=1.2.3.4,4.3.2.1 ipv6hint=1::2,3::4 key12345="foo"
245 )
246 https01 HTTPS 0 svc
247 https02 HTTPS 1 . port=8002 ech="abcd"
3838 dhcid01 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA=
3939 dhcid02 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No=
4040 dhcid03 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY=
41 dlv01 3600 IN DLV 12345 3 1 123456789abcdef67890123456789abcdef67890
4142 dname01 3600 IN DNAME dname-target.
4243 dname02 3600 IN DNAME dname-target
4344 dname03 3600 IN DNAME .
5960 hip01 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D
6061 hip02 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com.
6162 hip03 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs1.example.com. rvs2.example.com.
63 https01 3600 IN HTTPS 0 svc
64 https02 3600 IN HTTPS 1 . port="8002" ech="abcd"
6265 ipseckey01 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ==
6366 ipseckey02 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ==
6467 ipseckey03 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ==
9396 nsec03 3600 IN NSEC . NSEC TYPE65535
9497 nsec301 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
9598 nsec302 3600 IN NSEC3 1 1 12 - 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
96 openpgpkey 3600 IN OPENPGPKEY mQENBEteQDsBCADYnatn9+5t43AdJlVk 9dZC2RM0idPQcmrrKcjeAWDnISqoJzkv Q8ifX6mefquTBsDZC279uXShyTffYzQt vP2r9ewkK7zmSv52Ar563TSULAMwiLpe 0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+ AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3 ldx2Jz//kuru4YqROLBYyB8D6V2jNUFO daP6j5C5prh9dxfYFp2O/xFeAKLWlWuH 9o96INUoIhgdEyj9PHPT3c821NMZu8tC vsZgUB+QPbHA/QYGa+aollcdGkJpVxXo Hhbu6aMx/B+pXg55WM5pqOxmoVjyViHI UYfPABEBAAG0IUJvYiBIYWxsZXkgPGhh bGxleUBkbnNweXRob24ub3JnPokBPgQT AQIAKAUCS15AOwIbAwUJA8JnAAYLCQgH AwIGFQgCCQoLBBYCAwECHgECF4AACgkQ 6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d 24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7Z qRXAcOATNteQmpzqexx+BRKDWU8ZgYx1 2J4GZmC06jABr2JDWxgvbMX9qjkUUgDG ZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKT B9yNJ8v/WERlFdGaUveEUiFU8g75xp1H j9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE6 7EQdFGgqQFynR53md7cmVhAGopKLwMkp CtToKUlxxlfnDfpKZhhXThmhA0PsUQUk JptfGwYwH3O2N3KzfUw3wXRvLa3hona3 TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1 kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1 SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUD u4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUx poI+A7WWk9ThfjbynoZxRD820Kbqidqx BSgtFF36SRWzmX8DZfKKAskT9ZGU1ode SKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE 6Xd65JO6ufhad+ELhgFt95vRwTiFvVrB RjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu 0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E 7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5V UqRR25ubXLuzx9PaHYiC5GiQIU45pWAd 0IWcTI/MJQARAQABiQElBBgBAgAPBQJL XkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12 HRsIAKrB9E++9X9W6VTXBfdkShCFv0yk ZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkM DMdGVe6bn4A3oIAbf0Tjykq1AetZLVPs Hl/QosTbSQluis/PEvJkTQXHaKHB3bFh wA90c/3HNhrLGugt9AmcfLf9LAynXDgN LV5eYdPYqfKE+27qjEBARf6PYh/8WQ8C PKS8DILFbwCZbRxUogyrZf/7AiHAGdJi 8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQ RaHBhD4pHU1S/IRSlx9/3Ytww32PYD9A yO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg 0qzwVBNGb84v/ex2MouwtAYScwc=
99 openpgpkey 3600 IN OPENPGPKEY mQENBEteQDsBCADYnatn9+5t43AdJlVk9dZC2RM0idPQcmrrKcjeAWDnISqoJzkvQ8ifX6mefquTBsDZC279uXShyTffYzQtvP2r9ewkK7zmSv52Ar563TSULAMwiLpe0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3ldx2Jz//kuru4YqROLBYyB8D6V2jNUFOdaP6j5C5prh9dxfYFp2O/xFeAKLWlWuH9o96INUoIhgdEyj9PHPT3c821NMZu8tCvsZgUB+QPbHA/QYGa+aollcdGkJpVxXoHhbu6aMx/B+pXg55WM5pqOxmoVjyViHIUYfPABEBAAG0IUJvYiBIYWxsZXkgPGhhbGxleUBkbnNweXRob24ub3JnPokBPgQTAQIAKAUCS15AOwIbAwUJA8JnAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7ZqRXAcOATNteQmpzqexx+BRKDWU8ZgYx12J4GZmC06jABr2JDWxgvbMX9qjkUUgDGZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKTB9yNJ8v/WERlFdGaUveEUiFU8g75xp1Hj9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE67EQdFGgqQFynR53md7cmVhAGopKLwMkpCtToKUlxxlfnDfpKZhhXThmhA0PsUQUkJptfGwYwH3O2N3KzfUw3wXRvLa3hona3TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUDu4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUxpoI+A7WWk9ThfjbynoZxRD820KbqidqxBSgtFF36SRWzmX8DZfKKAskT9ZGU1odeSKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE6Xd65JO6ufhad+ELhgFt95vRwTiFvVrBRjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5VUqRR25ubXLuzx9PaHYiC5GiQIU45pWAd0IWcTI/MJQARAQABiQElBBgBAgAPBQJLXkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12HRsIAKrB9E++9X9W6VTXBfdkShCFv0ykZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkMDMdGVe6bn4A3oIAbf0Tjykq1AetZLVPsHl/QosTbSQluis/PEvJkTQXHaKHB3bFhwA90c/3HNhrLGugt9AmcfLf9LAynXDgNLV5eYdPYqfKE+27qjEBARf6PYh/8WQ8CPKS8DILFbwCZbRxUogyrZf/7AiHAGdJi8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQRaHBhD4pHU1S/IRSlx9/3Ytww32PYD9AyO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg0qzwVBNGb84v/ex2MouwtAYScwc=
97100 ptr01 3600 IN PTR @
98101 px01 3600 IN PX 65535 foo. bar.
99102 px02 3600 IN PX 65535 . .
105108 rt02 3600 IN RT 65535 .
106109 s 300 IN NS ns.s
107110 ns.s 300 IN A 73.80.65.49
111 smimea1 3600 IN SMIMEA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
112 smimea2 3600 IN SMIMEA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
113 smimea3 3600 IN SMIMEA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94
108114 spf 3600 IN SPF "v=spf1 mx -all"
109115 srv01 3600 IN SRV 0 0 0 .
110116 srv02 3600 IN SRV 65535 65535 65535 old-slow-box.example.com.
111117 sshfp1 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab
118 svcb01 3600 IN SVCB 100 foo.com. mandatory="alpn,port" alpn="h2,h3" no-default-alpn port="12345" ipv4hint="1.2.3.4,4.3.2.1" ech="abcd" ipv6hint="1::2,3::4" key12345="foo"
112119 t 301 IN A 73.80.65.49
113120 tlsa1 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
114121 tlsa2 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
139146 wks02 3600 IN WKS 10.0.0.1 17 0 1 2 53
140147 wks03 3600 IN WKS 10.0.0.2 6 65535
141148 x2501 3600 IN X25 "123456789"
149 zonemd01 3600 IN ZONEMD 2018031900 1 1 62e6cf51b02e54b9b5f967d547ce43136792901f9f88e637493daaf401c92c279dd10f0edb1c56f8080211f8480ee306
150 zonemd02 3600 IN ZONEMD 2018031900 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27
151 zonemd03 3600 IN ZONEMD 2018031900 1 240 e2d523f654b9422a96c5a8f44607bbee
152 zonemd04 3600 IN ZONEMD 2018031900 241 1 e1846540e33a9e4189792d18d5d131f605fc283eaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
3838 dhcid01.example. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA=
3939 dhcid02.example. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No=
4040 dhcid03.example. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY=
41 dlv01.example. 3600 IN DLV 12345 3 1 123456789abcdef67890123456789abcdef67890
4142 dname01.example. 3600 IN DNAME dname-target.
4243 dname02.example. 3600 IN DNAME dname-target.example.
4344 dname03.example. 3600 IN DNAME .
5960 hip01.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D
6061 hip02.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com.
6162 hip03.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs1.example.com. rvs2.example.com.
63 https01.example. 3600 IN HTTPS 0 svc.example.
64 https02.example. 3600 IN HTTPS 1 . port="8002" ech="abcd"
6265 ipseckey01.example. 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ==
6366 ipseckey02.example. 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ==
6467 ipseckey03.example. 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ==
9396 nsec03.example. 3600 IN NSEC . NSEC TYPE65535
9497 nsec301.example. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
9598 nsec302.example. 3600 IN NSEC3 1 1 12 - 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
96 openpgpkey.example. 3600 IN OPENPGPKEY mQENBEteQDsBCADYnatn9+5t43AdJlVk 9dZC2RM0idPQcmrrKcjeAWDnISqoJzkv Q8ifX6mefquTBsDZC279uXShyTffYzQt vP2r9ewkK7zmSv52Ar563TSULAMwiLpe 0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+ AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3 ldx2Jz//kuru4YqROLBYyB8D6V2jNUFO daP6j5C5prh9dxfYFp2O/xFeAKLWlWuH 9o96INUoIhgdEyj9PHPT3c821NMZu8tC vsZgUB+QPbHA/QYGa+aollcdGkJpVxXo Hhbu6aMx/B+pXg55WM5pqOxmoVjyViHI UYfPABEBAAG0IUJvYiBIYWxsZXkgPGhh bGxleUBkbnNweXRob24ub3JnPokBPgQT AQIAKAUCS15AOwIbAwUJA8JnAAYLCQgH AwIGFQgCCQoLBBYCAwECHgECF4AACgkQ 6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d 24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7Z qRXAcOATNteQmpzqexx+BRKDWU8ZgYx1 2J4GZmC06jABr2JDWxgvbMX9qjkUUgDG ZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKT B9yNJ8v/WERlFdGaUveEUiFU8g75xp1H j9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE6 7EQdFGgqQFynR53md7cmVhAGopKLwMkp CtToKUlxxlfnDfpKZhhXThmhA0PsUQUk JptfGwYwH3O2N3KzfUw3wXRvLa3hona3 TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1 kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1 SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUD u4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUx poI+A7WWk9ThfjbynoZxRD820Kbqidqx BSgtFF36SRWzmX8DZfKKAskT9ZGU1ode SKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE 6Xd65JO6ufhad+ELhgFt95vRwTiFvVrB RjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu 0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E 7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5V UqRR25ubXLuzx9PaHYiC5GiQIU45pWAd 0IWcTI/MJQARAQABiQElBBgBAgAPBQJL XkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12 HRsIAKrB9E++9X9W6VTXBfdkShCFv0yk ZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkM DMdGVe6bn4A3oIAbf0Tjykq1AetZLVPs Hl/QosTbSQluis/PEvJkTQXHaKHB3bFh wA90c/3HNhrLGugt9AmcfLf9LAynXDgN LV5eYdPYqfKE+27qjEBARf6PYh/8WQ8C PKS8DILFbwCZbRxUogyrZf/7AiHAGdJi 8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQ RaHBhD4pHU1S/IRSlx9/3Ytww32PYD9A yO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg 0qzwVBNGb84v/ex2MouwtAYScwc=
99 openpgpkey.example. 3600 IN OPENPGPKEY mQENBEteQDsBCADYnatn9+5t43AdJlVk9dZC2RM0idPQcmrrKcjeAWDnISqoJzkvQ8ifX6mefquTBsDZC279uXShyTffYzQtvP2r9ewkK7zmSv52Ar563TSULAMwiLpe0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3ldx2Jz//kuru4YqROLBYyB8D6V2jNUFOdaP6j5C5prh9dxfYFp2O/xFeAKLWlWuH9o96INUoIhgdEyj9PHPT3c821NMZu8tCvsZgUB+QPbHA/QYGa+aollcdGkJpVxXoHhbu6aMx/B+pXg55WM5pqOxmoVjyViHIUYfPABEBAAG0IUJvYiBIYWxsZXkgPGhhbGxleUBkbnNweXRob24ub3JnPokBPgQTAQIAKAUCS15AOwIbAwUJA8JnAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7ZqRXAcOATNteQmpzqexx+BRKDWU8ZgYx12J4GZmC06jABr2JDWxgvbMX9qjkUUgDGZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKTB9yNJ8v/WERlFdGaUveEUiFU8g75xp1Hj9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE67EQdFGgqQFynR53md7cmVhAGopKLwMkpCtToKUlxxlfnDfpKZhhXThmhA0PsUQUkJptfGwYwH3O2N3KzfUw3wXRvLa3hona3TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUDu4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUxpoI+A7WWk9ThfjbynoZxRD820KbqidqxBSgtFF36SRWzmX8DZfKKAskT9ZGU1odeSKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE6Xd65JO6ufhad+ELhgFt95vRwTiFvVrBRjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5VUqRR25ubXLuzx9PaHYiC5GiQIU45pWAd0IWcTI/MJQARAQABiQElBBgBAgAPBQJLXkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12HRsIAKrB9E++9X9W6VTXBfdkShCFv0ykZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkMDMdGVe6bn4A3oIAbf0Tjykq1AetZLVPsHl/QosTbSQluis/PEvJkTQXHaKHB3bFhwA90c/3HNhrLGugt9AmcfLf9LAynXDgNLV5eYdPYqfKE+27qjEBARf6PYh/8WQ8CPKS8DILFbwCZbRxUogyrZf/7AiHAGdJi8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQRaHBhD4pHU1S/IRSlx9/3Ytww32PYD9AyO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg0qzwVBNGb84v/ex2MouwtAYScwc=
97100 ptr01.example. 3600 IN PTR example.
98101 px01.example. 3600 IN PX 65535 foo. bar.
99102 px02.example. 3600 IN PX 65535 . .
105108 rt02.example. 3600 IN RT 65535 .
106109 s.example. 300 IN NS ns.s.example.
107110 ns.s.example. 300 IN A 73.80.65.49
111 smimea1.example. 3600 IN SMIMEA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
112 smimea2.example. 3600 IN SMIMEA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
113 smimea3.example. 3600 IN SMIMEA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94
108114 spf.example. 3600 IN SPF "v=spf1 mx -all"
109115 srv01.example. 3600 IN SRV 0 0 0 .
110116 srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example.com.
111117 sshfp1.example. 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab
118 svcb01.example. 3600 IN SVCB 100 foo.com. mandatory="alpn,port" alpn="h2,h3" no-default-alpn port="12345" ipv4hint="1.2.3.4,4.3.2.1" ech="abcd" ipv6hint="1::2,3::4" key12345="foo"
112119 t.example. 301 IN A 73.80.65.49
113120 tlsa1.example. 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
114121 tlsa2.example. 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
139146 wks02.example. 3600 IN WKS 10.0.0.1 17 0 1 2 53
140147 wks03.example. 3600 IN WKS 10.0.0.2 6 65535
141148 x2501.example. 3600 IN X25 "123456789"
149 zonemd01.example. 3600 IN ZONEMD 2018031900 1 1 62e6cf51b02e54b9b5f967d547ce43136792901f9f88e637493daaf401c92c279dd10f0edb1c56f8080211f8480ee306
150 zonemd02.example. 3600 IN ZONEMD 2018031900 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27
151 zonemd03.example. 3600 IN ZONEMD 2018031900 1 240 e2d523f654b9422a96c5a8f44607bbee
152 zonemd04.example. 3600 IN ZONEMD 2018031900 241 1 e1846540e33a9e4189792d18d5d131f605fc283eaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
3838 dhcid01 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA=
3939 dhcid02 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No=
4040 dhcid03 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY=
41 dlv01 3600 IN DLV 12345 3 1 123456789abcdef67890123456789abcdef67890
4142 dname01 3600 IN DNAME dname-target.
4243 dname02 3600 IN DNAME dname-target
4344 dname03 3600 IN DNAME .
5960 hip01 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D
6061 hip02 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com.
6162 hip03 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs1.example.com. rvs2.example.com.
63 https01 3600 IN HTTPS 0 svc
64 https02 3600 IN HTTPS 1 . port="8002" ech="abcd"
6265 ipseckey01 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ==
6366 ipseckey02 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ==
6467 ipseckey03 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ==
9396 nsec03 3600 IN NSEC . NSEC TYPE65535
9497 nsec301 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
9598 nsec302 3600 IN NSEC3 1 1 12 - 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
96 openpgpkey 3600 IN OPENPGPKEY mQENBEteQDsBCADYnatn9+5t43AdJlVk 9dZC2RM0idPQcmrrKcjeAWDnISqoJzkv Q8ifX6mefquTBsDZC279uXShyTffYzQt vP2r9ewkK7zmSv52Ar563TSULAMwiLpe 0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+ AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3 ldx2Jz//kuru4YqROLBYyB8D6V2jNUFO daP6j5C5prh9dxfYFp2O/xFeAKLWlWuH 9o96INUoIhgdEyj9PHPT3c821NMZu8tC vsZgUB+QPbHA/QYGa+aollcdGkJpVxXo Hhbu6aMx/B+pXg55WM5pqOxmoVjyViHI UYfPABEBAAG0IUJvYiBIYWxsZXkgPGhh bGxleUBkbnNweXRob24ub3JnPokBPgQT AQIAKAUCS15AOwIbAwUJA8JnAAYLCQgH AwIGFQgCCQoLBBYCAwECHgECF4AACgkQ 6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d 24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7Z qRXAcOATNteQmpzqexx+BRKDWU8ZgYx1 2J4GZmC06jABr2JDWxgvbMX9qjkUUgDG ZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKT B9yNJ8v/WERlFdGaUveEUiFU8g75xp1H j9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE6 7EQdFGgqQFynR53md7cmVhAGopKLwMkp CtToKUlxxlfnDfpKZhhXThmhA0PsUQUk JptfGwYwH3O2N3KzfUw3wXRvLa3hona3 TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1 kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1 SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUD u4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUx poI+A7WWk9ThfjbynoZxRD820Kbqidqx BSgtFF36SRWzmX8DZfKKAskT9ZGU1ode SKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE 6Xd65JO6ufhad+ELhgFt95vRwTiFvVrB RjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu 0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E 7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5V UqRR25ubXLuzx9PaHYiC5GiQIU45pWAd 0IWcTI/MJQARAQABiQElBBgBAgAPBQJL XkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12 HRsIAKrB9E++9X9W6VTXBfdkShCFv0yk ZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkM DMdGVe6bn4A3oIAbf0Tjykq1AetZLVPs Hl/QosTbSQluis/PEvJkTQXHaKHB3bFh wA90c/3HNhrLGugt9AmcfLf9LAynXDgN LV5eYdPYqfKE+27qjEBARf6PYh/8WQ8C PKS8DILFbwCZbRxUogyrZf/7AiHAGdJi 8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQ RaHBhD4pHU1S/IRSlx9/3Ytww32PYD9A yO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg 0qzwVBNGb84v/ex2MouwtAYScwc=
99 openpgpkey 3600 IN OPENPGPKEY mQENBEteQDsBCADYnatn9+5t43AdJlVk9dZC2RM0idPQcmrrKcjeAWDnISqoJzkvQ8ifX6mefquTBsDZC279uXShyTffYzQtvP2r9ewkK7zmSv52Ar563TSULAMwiLpe0gGQE0ex20mX5ggtYn6czdbEtcKpW0t+AfDqRk5YcpgqfZKXapKQ+A3CwWJKP9i3ldx2Jz//kuru4YqROLBYyB8D6V2jNUFOdaP6j5C5prh9dxfYFp2O/xFeAKLWlWuH9o96INUoIhgdEyj9PHPT3c821NMZu8tCvsZgUB+QPbHA/QYGa+aollcdGkJpVxXoHhbu6aMx/B+pXg55WM5pqOxmoVjyViHIUYfPABEBAAG0IUJvYiBIYWxsZXkgPGhhbGxleUBkbnNweXRob24ub3JnPokBPgQTAQIAKAUCS15AOwIbAwUJA8JnAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ6o6Gb8yUnXaflQgAhlhIqZGncRw3LV3d24JmPD+UEcEGiVh2b/Ic/1TMec46Ts7ZqRXAcOATNteQmpzqexx+BRKDWU8ZgYx12J4GZmC06jABr2JDWxgvbMX9qjkUUgDGZZgAS/B2x5AmKgy2ZnCUlaKfePcKmtKTB9yNJ8v/WERlFdGaUveEUiFU8g75xp1Hj9Wp9sXCg9yeG1K2RwQ3RQd5tLudhyE67EQdFGgqQFynR53md7cmVhAGopKLwMkpCtToKUlxxlfnDfpKZhhXThmhA0PsUQUkJptfGwYwH3O2N3KzfUw3wXRvLa3hona3TlHk3kfg7Qyd7oP4AZGbJKp97YHnfqo1kp8rObkBDQRLXkA7AQgA0ePG7g5GgZ/1SdtGZlJJiE2X15vTUc3KGfmx/kI5NaUDu4fXb+XK+yFy9I/X+UJ46JSkyhj6QvUxpoI+A7WWk9ThfjbynoZxRD820KbqidqxBSgtFF36SRWzmX8DZfKKAskT9ZGU1odeSKDXLCJF7qAbZVRTuFRiDFGwtoVIICeE6Xd65JO6ufhad+ELhgFt95vRwTiFvVrBRjwF7ZgN/nOXfYncxZ/2mpFqfwsnB2eu0A2XZBm8IngsSmr/Wrz1RQ7+SNMqt77E7CKwBX7UIAZgyoJxIRxWirJoOt1rIm5VUqRR25ubXLuzx9PaHYiC5GiQIU45pWAd0IWcTI/MJQARAQABiQElBBgBAgAPBQJLXkA7AhsMBQkDwmcAAAoJEOqOhm/MlJ12HRsIAKrB9E++9X9W6VTXBfdkShCFv0ykZVn2eVs6tkqzoub9s4f+Z5ylWw+a5nkMDMdGVe6bn4A3oIAbf0Tjykq1AetZLVPsHl/QosTbSQluis/PEvJkTQXHaKHB3bFhwA90c/3HNhrLGugt9AmcfLf9LAynXDgNLV5eYdPYqfKE+27qjEBARf6PYh/8WQ8CPKS8DILFbwCZbRxUogyrZf/7AiHAGdJi8dmpR1WPQYef2hF3kqGX6NngLBPzZ6CQRaHBhD4pHU1S/IRSlx9/3Ytww32PYD9AyO732NmCUcq3bmvqcOWy4Cc1NkEwU0Vg0qzwVBNGb84v/ex2MouwtAYScwc=
97100 ptr01 3600 IN PTR @
98101 px01 3600 IN PX 65535 foo. bar.
99102 px02 3600 IN PX 65535 . .
105108 rt02 3600 IN RT 65535 .
106109 s 300 IN NS ns.s
107110 ns.s 300 IN A 73.80.65.49
111 smimea1 3600 IN SMIMEA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
112 smimea2 3600 IN SMIMEA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
113 smimea3 3600 IN SMIMEA 1 0 2 81ee7f6c0ecc6b09b7785a9418f54432de630dd54dc6ee9e3c49de547708d236d4c413c3e97e44f969e635958aa410495844127c04883503e5b024cf7a8f6a94
108114 spf 3600 IN SPF "v=spf1 mx -all"
109115 srv01 3600 IN SRV 0 0 0 .
110116 srv02 3600 IN SRV 65535 65535 65535 old-slow-box.example.com.
111117 sshfp1 3600 IN SSHFP 1 1 aa549bfe898489c02d1715d97d79c57ba2fa76ab
118 svcb01 3600 IN SVCB 100 foo.com. mandatory="alpn,port" alpn="h2,h3" no-default-alpn port="12345" ipv4hint="1.2.3.4,4.3.2.1" ech="abcd" ipv6hint="1::2,3::4" key12345="foo"
112119 t 301 IN A 73.80.65.49
113120 tlsa1 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065
114121 tlsa2 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955
139146 wks02 3600 IN WKS 10.0.0.1 17 0 1 2 53
140147 wks03 3600 IN WKS 10.0.0.2 6 65535
141148 x2501 3600 IN X25 "123456789"
149 zonemd01 3600 IN ZONEMD 2018031900 1 1 62e6cf51b02e54b9b5f967d547ce43136792901f9f88e637493daaf401c92c279dd10f0edb1c56f8080211f8480ee306
150 zonemd02 3600 IN ZONEMD 2018031900 1 2 08cfa1115c7b948c4163a901270395ea226a930cd2cbcf2fa9a5e6eb85f37c8a4e114d884e66f176eab121cb02db7d652e0cc4827e7a3204f166b47e5613fd27
151 zonemd03 3600 IN ZONEMD 2018031900 1 240 e2d523f654b9422a96c5a8f44607bbee
152 zonemd04 3600 IN ZONEMD 2018031900 241 1 e1846540e33a9e4189792d18d5d131f605fc283eaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
0 import dns.rdtypes.nsbase
1
2 class MD(dns.rdtypes.nsbase.NSBase):
3 """Test MD record."""
Binary diff not shown
159159 finally:
160160 raise EOFError
161161
162 def handle(self, message, peer, connection_type):
163 #
164 # Handle message 'message'. Override this method to change
162 def handle(self, request):
163 #
164 # Handle request 'request'. Override this method to change
165165 # how the server behaves.
166166 #
167167 # The return value is either a dns.message.Message, a bytes,
172172 # returned, then the output code will run for each returned
173173 # item.
174174 #
175 try:
176 r = dns.message.make_response(message)
177 r.set_rcode(dns.rcode.REFUSED)
178 return r
179 except Exception:
180 return None
175 r = dns.message.make_response(request.message)
176 r.set_rcode(dns.rcode.REFUSED)
177 return r
181178
182179 def maybe_listify(self, thing):
183180 if isinstance(thing, list):
239236 out = thing.to_wire(self.origin, multi=multi, tsig_ctx=tsig_ctx)
240237 tsig_ctx = thing.tsig_ctx
241238 yield out
242 else:
239 elif thing is not None:
243240 yield thing
244241
245242 async def serve_udp(self):
0 ; Alias form
1 \# 19 (
2 00 00 ; priority
3 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target
4 )
5
6 ; Service form
7
8 ; The first form is the simple "use the ownername".
9 \# 3 (
10 00 01 ; priority
11 00 ; target (root label)
12 )
13
14 ; This vector only has a port.
15 \# 25 (
16 00 10 ; priority
17 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target
18 00 03 ; key 3
19 00 02 ; length 2
20 00 35 ; value
21 )
22
23 ; This example has a key that is not registered, its value is unquoted.
24 \# 28 (
25 00 01 ; priority
26 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target
27 02 9b ; key 667
28 00 05 ; length 5
29 68 65 6c 6c 6f ; value
30 )
31
32 ; This example has a key that is not registered, its value is quoted and
33 ; contains a decimal-escaped character.
34 \# 32 (
35 00 01 ; priority
36 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target
37 02 9b ; key 667
38 00 09 ; length 9
39 68 65 6c 6c 6f d2 71 6f 6f ; value
40 )
41
42 ; Here, two IPv6 hints are quoted in the presentation format.
43 \# 55 (
44 00 01 ; priority
45 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target
46 00 06 ; key 6
47 00 20 ; length 32
48 20 01 0d b8 00 00 00 00 00 00 00 00 00 00 00 01 ; first address
49 20 01 0d b8 00 00 00 00 00 00 00 00 00 53 00 01 ; second address
50 )
51
52 ; This example shows a single IPv6 hint in IPv4 mapped IPv6 presentation format.
53 \# 35 (
54 00 01 ; priority
55 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 ; target
56 00 06 ; key 6
57 00 10 ; length 16
58 20 01 0d b8 ff ff ff ff ff ff ff ff c6 33 64 64 ; address
59 )
60
61 ; In the next vector, neither the SvcParamValues nor the mandatory keys are
62 ; sorted in presentation format, but are correctly sorted in the wire-format.
63 \# 48 (
64 00 10 ; priority
65 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 ; target
66 00 00 ; key 0
67 00 04 ; param length 4
68 00 01 ; value: key 1
69 00 04 ; value: key 4
70 00 01 ; key 1
71 00 09 ; param length 9
72 02 ; alpn length 2
73 68 32 ; alpn value
74 05 ; alpn length 5
75 68 33 2d 31 39 ; alpn value
76 00 04 ; key 4
77 00 04 ; param length 4
78 c0 00 02 01 ; param value
79 )
80
81 ; This last vector has an alpn value with an escaped comma and an escaped
82 ; backslash in two presentation formats.
83 \# 35 (
84 00 10 ; priority
85 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 ; target
86 00 01 ; key 1
87 00 0c ; param length 12
88 08 ; alpn length 8
89 66 5c 6f 6f 2c 62 61 72 ; alpn value
90 02 ; alpn length 2
91 68 32 ; alpn value
92 )
93 \# 35 (
94 00 10 ; priority
95 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 ; target
96 00 01 ; key 1
97 00 0c ; param length 12
98 08 ; alpn length 8
99 66 5c 6f 6f 2c 62 61 72 ; alpn value
100 02 ; alpn length 2
101 68 32 ; alpn value
102 )
0 ; Alias form
1 0 foo.example.com.
2
3 ; Service form
4
5 ; The first form is the simple "use the ownername".
6 1 .
7
8 ; This vector only has a port.
9 16 foo.example.com. port=53
10
11 ; This example has a key that is not registered, its value is unquoted.
12 1 foo.example.com. key667=hello
13
14 ; This example has a key that is not registered, its value is quoted and
15 ; contains a decimal-escaped character.
16 1 foo.example.com. key667="hello\210qoo"
17
18 ; Here, two IPv6 hints are quoted in the presentation format.
19 1 foo.example.com. ipv6hint="2001:db8::1,2001:db8::53:1"
20
21 ; This example shows a single IPv6 hint in IPv4 mapped IPv6 presentation format.
22 1 example.com. ipv6hint="2001:db8:ffff:ffff:ffff:ffff:198.51.100.100"
23
24 ; In the next vector, neither the SvcParamValues nor the mandatory keys are
25 ; sorted in presentation format, but are correctly sorted in the wire-format.
26 16 foo.example.org. (alpn=h2,h3-19 mandatory=ipv4hint,alpn
27 ipv4hint=192.0.2.1)
28
29 ; This last vector has an alpn value with an escaped comma and an escaped
30 ; backslash in two presentation formats.
31 16 foo.example.org. alpn="f\\\\oo\\,bar,h2"
32 16 foo.example.org. alpn=f\\\092oo\092,bar,h2
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import socket
3 import sys
4 import unittest
5
6 import dns.exception
7 import dns.ipv4
8 import dns.ipv6
9
10 class IPv4Tests(unittest.TestCase):
11 def test_valid(self):
12 valid = (
13 "1.2.3.4",
14 "11.22.33.44",
15 "254.7.237.98",
16 "192.168.1.26",
17 "192.168.1.1",
18 "13.1.68.3",
19 "129.144.52.38",
20 "254.157.241.86",
21 "12.34.56.78",
22 "192.0.2.128",
23 )
24 for s in valid:
25 self.assertEqual(dns.ipv4.inet_aton(s),
26 socket.inet_pton(socket.AF_INET, s))
27
28 def test_invalid(self):
29 invalid = (
30 "",
31 ".",
32 "..",
33 "400.2.3.4",
34 "260.2.3.4",
35 "256.2.3.4",
36 "1.256.3.4",
37 "1.2.256.4",
38 "1.2.3.256",
39 "300.2.3.4",
40 "1.300.3.4",
41 "1.2.300.4",
42 "1.2.3.300",
43 "900.2.3.4",
44 "1.900.3.4",
45 "1.2.900.4",
46 "1.2.3.900",
47 "300.300.300.300",
48 "3000.30.30.30",
49 "255Z255X255Y255",
50 "192x168.1.26",
51 "2.3.4",
52 "257.1.2.3",
53 "00.00.00.00",
54 "000.000.000.000",
55 "256.256.256.256",
56 "255255.255.255",
57 "255.255255.255",
58 "255.255.255255",
59 "1...",
60 "1.2..",
61 "1.2.3.",
62 ".2..",
63 ".2.3.",
64 ".2.3.4",
65 "..3.",
66 "..3.4",
67 "...4",
68 ".1.2.3.4",
69 "1.2.3.4.",
70 " 1.2.3.4",
71 "1.2.3.4 ",
72 " 1.2.3.4 ",
73 "::",
74 )
75 for s in invalid:
76 with self.assertRaises(dns.exception.SyntaxError,
77 msg=f'invalid IPv4 address: "{s}"'):
78 dns.ipv4.inet_aton(s)
79
80 class IPv6Tests(unittest.TestCase):
81 def test_valid(self):
82 valid = (
83 "::1",
84 "::",
85 "0:0:0:0:0:0:0:1",
86 "0:0:0:0:0:0:0:0",
87 "2001:DB8:0:0:8:800:200C:417A",
88 "FF01:0:0:0:0:0:0:101",
89 "2001:DB8::8:800:200C:417A",
90 "FF01::101",
91 "fe80::217:f2ff:fe07:ed62",
92 "2001:0000:1234:0000:0000:C1C0:ABCD:0876",
93 "3ffe:0b00:0000:0000:0001:0000:0000:000a",
94 "FF02:0000:0000:0000:0000:0000:0000:0001",
95 "0000:0000:0000:0000:0000:0000:0000:0001",
96 "0000:0000:0000:0000:0000:0000:0000:0000",
97 "2::10",
98 "ff02::1",
99 "fe80::",
100 "2002::",
101 "2001:db8::",
102 "2001:0db8:1234::",
103 "::ffff:0:0",
104 "1:2:3:4:5:6:7:8",
105 "1:2:3:4:5:6::8",
106 "1:2:3:4:5::8",
107 "1:2:3:4::8",
108 "1:2:3::8",
109 "1:2::8",
110 "1::8",
111 "1::2:3:4:5:6:7",
112 "1::2:3:4:5:6",
113 "1::2:3:4:5",
114 "1::2:3:4",
115 "1::2:3",
116 "::2:3:4:5:6:7:8",
117 "::2:3:4:5:6:7",
118 "::2:3:4:5:6",
119 "::2:3:4:5",
120 "::2:3:4",
121 "::2:3",
122 "::8",
123 "1:2:3:4:5:6::",
124 "1:2:3:4:5::",
125 "1:2:3:4::",
126 "1:2:3::",
127 "1:2::",
128 "1::",
129 "1:2:3:4:5::7:8",
130 "1:2:3:4::7:8",
131 "1:2:3::7:8",
132 "1:2::7:8",
133 "1::7:8",
134 "1:2:3:4:5:6:1.2.3.4",
135 "1:2:3:4:5::1.2.3.4",
136 "1:2:3:4::1.2.3.4",
137 "1:2:3::1.2.3.4",
138 "1:2::1.2.3.4",
139 "1::1.2.3.4",
140 "1:2:3:4::5:1.2.3.4",
141 "1:2:3::5:1.2.3.4",
142 "1:2:3::5:1.2.3.4",
143 "1:2::5:1.2.3.4",
144 "1::5:1.2.3.4",
145 "1::5:11.22.33.44",
146 "fe80::217:f2ff:254.7.237.98",
147 "::ffff:192.168.1.26",
148 "::ffff:192.168.1.1",
149 "0:0:0:0:0:0:13.1.68.3",
150 "0:0:0:0:0:FFFF:129.144.52.38",
151 "::13.1.68.3",
152 "::FFFF:129.144.52.38",
153 "fe80:0:0:0:204:61ff:254.157.241.86",
154 "fe80::204:61ff:254.157.241.86",
155 "::ffff:12.34.56.78",
156 "::ffff:192.0.2.128",
157 "fe80:0000:0000:0000:0204:61ff:fe9d:f156",
158 "fe80:0:0:0:204:61ff:fe9d:f156",
159 "fe80::204:61ff:fe9d:f156",
160 "fe80::1",
161 "::ffff:c000:280",
162 "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
163 "2001:db8:85a3:0:0:8a2e:370:7334",
164 "2001:db8:85a3::8a2e:370:7334",
165 "2001:0db8:0000:0000:0000:0000:1428:57ab",
166 "2001:0db8:0000:0000:0000::1428:57ab",
167 "2001:0db8:0:0:0:0:1428:57ab",
168 "2001:0db8:0:0::1428:57ab",
169 "2001:0db8::1428:57ab",
170 "2001:db8::1428:57ab",
171 "::ffff:0c22:384e",
172 "2001:0db8:1234:0000:0000:0000:0000:0000",
173 "2001:0db8:1234:ffff:ffff:ffff:ffff:ffff",
174 "2001:db8:a::123",
175 "1111:2222:3333:4444:5555:6666:7777:8888",
176 "1111:2222:3333:4444:5555:6666:7777::",
177 "1111:2222:3333:4444:5555:6666::",
178 "1111:2222:3333:4444:5555::",
179 "1111:2222:3333:4444::",
180 "1111:2222:3333::",
181 "1111:2222::",
182 "1111::",
183 "1111:2222:3333:4444:5555:6666::8888",
184 "1111:2222:3333:4444:5555::8888",
185 "1111:2222:3333:4444::8888",
186 "1111:2222:3333::8888",
187 "1111:2222::8888",
188 "1111::8888",
189 "::8888",
190 "1111:2222:3333:4444:5555::7777:8888",
191 "1111:2222:3333:4444::7777:8888",
192 "1111:2222:3333::7777:8888",
193 "1111:2222::7777:8888",
194 "1111::7777:8888",
195 "::7777:8888",
196 "1111:2222:3333:4444::6666:7777:8888",
197 "1111:2222:3333::6666:7777:8888",
198 "1111:2222::6666:7777:8888",
199 "1111::6666:7777:8888",
200 "::6666:7777:8888",
201 "1111:2222:3333::5555:6666:7777:8888",
202 "1111:2222::5555:6666:7777:8888",
203 "1111::5555:6666:7777:8888",
204 "::5555:6666:7777:8888",
205 "1111:2222::4444:5555:6666:7777:8888",
206 "1111::4444:5555:6666:7777:8888",
207 "::4444:5555:6666:7777:8888",
208 "1111::3333:4444:5555:6666:7777:8888",
209 "::3333:4444:5555:6666:7777:8888",
210 "::2222:3333:4444:5555:6666:7777:8888",
211 "1111:2222:3333:4444:5555:6666:123.123.123.123",
212 "1111:2222:3333:4444:5555::123.123.123.123",
213 "1111:2222:3333:4444::123.123.123.123",
214 "1111:2222:3333::123.123.123.123",
215 "1111:2222::123.123.123.123",
216 "1111::123.123.123.123",
217 "::123.123.123.123",
218 "1111:2222:3333:4444::6666:123.123.123.123",
219 "1111:2222:3333::6666:123.123.123.123",
220 "1111:2222::6666:123.123.123.123",
221 "1111::6666:123.123.123.123",
222 "::6666:123.123.123.123",
223 "1111:2222:3333::5555:6666:123.123.123.123",
224 "1111:2222::5555:6666:123.123.123.123",
225 "1111::5555:6666:123.123.123.123",
226 "::5555:6666:123.123.123.123",
227 "1111:2222::4444:5555:6666:123.123.123.123",
228 "1111::4444:5555:6666:123.123.123.123",
229 "::4444:5555:6666:123.123.123.123",
230 "1111::3333:4444:5555:6666:123.123.123.123",
231 "::2222:3333:4444:5555:6666:123.123.123.123",
232 "::0:0:0:0:0:0:0",
233 "::0:0:0:0:0:0",
234 "::0:0:0:0:0",
235 "::0:0:0:0",
236 "::0:0:0",
237 "::0:0",
238 "::0",
239 "0:0:0:0:0:0:0::",
240 "0:0:0:0:0:0::",
241 "0:0:0:0:0::",
242 "0:0:0:0::",
243 "0:0:0::",
244 "0:0::",
245 "0::",
246 "0:a:b:c:d:e:f::",
247 "::0:a:b:c:d:e:f",
248 "a:b:c:d:e:f:0::",
249 )
250
251 win32_invalid = {
252 "::2:3:4:5:6:7:8",
253 "::2222:3333:4444:5555:6666:7777:8888",
254 "::2222:3333:4444:5555:6666:123.123.123.123",
255 "::0:0:0:0:0:0:0",
256 "::0:a:b:c:d:e:f",
257 }
258
259 for s in valid:
260 if sys.platform == 'win32' and s in win32_invalid:
261 # socket.inet_pton() on win32 rejects some valid (as
262 # far as we can tell) IPv6 addresses. Skip them.
263 continue
264 self.assertEqual(dns.ipv6.inet_aton(s),
265 socket.inet_pton(socket.AF_INET6, s))
266
267 def test_invalid(self):
268 invalid = (
269 "",
270 ":",
271 ":::",
272 "2001:DB8:0:0:8:800:200C:417A:221",
273 "FF01::101::2",
274 "02001:0000:1234:0000:0000:C1C0:ABCD:0876",
275 "2001:0000:1234:0000:00001:C1C0:ABCD:0876",
276 " 2001:0000:1234:0000:0000:C1C0:ABCD:0876",
277 "2001:0000:1234:0000:0000:C1C0:ABCD:0876 ",
278 " 2001:0000:1234:0000:0000:C1C0:ABCD:0876 ",
279 "2001:0000:1234:0000:0000:C1C0:ABCD:0876 0",
280 "2001:0000:1234: 0000:0000:C1C0:ABCD:0876",
281 "3ffe:0b00:0000:0001:0000:0000:000a",
282 "FF02:0000:0000:0000:0000:0000:0000:0000:0001",
283 "3ffe:b00::1::a",
284 "::1111:2222:3333:4444:5555:6666::",
285 "1:2:3::4:5::7:8",
286 "12345::6:7:8",
287 "1::5:400.2.3.4",
288 "1::5:260.2.3.4",
289 "1::5:256.2.3.4",
290 "1::5:1.256.3.4",
291 "1::5:1.2.256.4",
292 "1::5:1.2.3.256",
293 "1::5:300.2.3.4",
294 "1::5:1.300.3.4",
295 "1::5:1.2.300.4",
296 "1::5:1.2.3.300",
297 "1::5:900.2.3.4",
298 "1::5:1.900.3.4",
299 "1::5:1.2.900.4",
300 "1::5:1.2.3.900",
301 "1::5:300.300.300.300",
302 "1::5:3000.30.30.30",
303 "1::400.2.3.4",
304 "1::260.2.3.4",
305 "1::256.2.3.4",
306 "1::1.256.3.4",
307 "1::1.2.256.4",
308 "1::1.2.3.256",
309 "1::300.2.3.4",
310 "1::1.300.3.4",
311 "1::1.2.300.4",
312 "1::1.2.3.300",
313 "1::900.2.3.4",
314 "1::1.900.3.4",
315 "1::1.2.900.4",
316 "1::1.2.3.900",
317 "1::300.300.300.300",
318 "1::3000.30.30.30",
319 "::400.2.3.4",
320 "::260.2.3.4",
321 "::256.2.3.4",
322 "::1.256.3.4",
323 "::1.2.256.4",
324 "::1.2.3.256",
325 "::300.2.3.4",
326 "::1.300.3.4",
327 "::1.2.300.4",
328 "::1.2.3.300",
329 "::900.2.3.4",
330 "::1.900.3.4",
331 "::1.2.900.4",
332 "::1.2.3.900",
333 "::300.300.300.300",
334 "::3000.30.30.30",
335 "::1.2.3.4.",
336 "2001:1:1:1:1:1:255Z255X255Y255",
337 "::ffff:192x168.1.26",
338 "::ffff:2.3.4",
339 "::ffff:257.1.2.3",
340 "1.2.3.4",
341 "1.2.3.4:1111:2222:3333:4444::5555",
342 "1.2.3.4:1111:2222:3333::5555",
343 "1.2.3.4:1111:2222::5555",
344 "1.2.3.4:1111::5555",
345 "1.2.3.4::5555",
346 "1.2.3.4::",
347 "fe80:0000:0000:0000:0204:61ff:254.157.241.086",
348 "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4",
349 "1111:2222:3333:4444:5555:6666:00.00.00.00",
350 "1111:2222:3333:4444:5555:6666:000.000.000.000",
351 "1111:2222:3333:4444:5555:6666:256.256.256.256",
352 "1111:2222:3333:4444::5555:",
353 "1111:2222:3333::5555:",
354 "1111:2222::5555:",
355 "1111::5555:",
356 "::5555:",
357 "1111:",
358 ":1111:2222:3333:4444::5555",
359 ":1111:2222:3333::5555",
360 ":1111:2222::5555",
361 ":1111::5555",
362 ":::5555",
363 "123",
364 "ldkfj",
365 "2001::FFD3::57ab",
366 "2001:db8:85a3::8a2e:37023:7334",
367 "2001:db8:85a3::8a2e:370k:7334",
368 "1:2:3:4:5:6:7:8:9",
369 "1::2::3",
370 "1:::3:4:5",
371 "1:2:3::4:5:6:7:8:9",
372 "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX",
373 "1111:2222:3333:4444:5555:6666:7777:8888:9999",
374 "1111:2222:3333:4444:5555:6666:7777:8888::",
375 "::2222:3333:4444:5555:6666:7777:8888:9999",
376 "1111:2222:3333:4444:5555:6666:7777",
377 "1111:2222:3333:4444:5555:6666",
378 "1111:2222:3333:4444:5555",
379 "1111:2222:3333:4444",
380 "1111:2222:3333",
381 "1111:2222",
382 "1111",
383 "11112222:3333:4444:5555:6666:7777:8888",
384 "1111:22223333:4444:5555:6666:7777:8888",
385 "1111:2222:33334444:5555:6666:7777:8888",
386 "1111:2222:3333:44445555:6666:7777:8888",
387 "1111:2222:3333:4444:55556666:7777:8888",
388 "1111:2222:3333:4444:5555:66667777:8888",
389 "1111:2222:3333:4444:5555:6666:77778888",
390 "1111:2222:3333:4444:5555:6666:7777:8888:",
391 "1111:2222:3333:4444:5555:6666:7777:",
392 "1111:2222:3333:4444:5555:6666:",
393 "1111:2222:3333:4444:5555:",
394 "1111:2222:3333:4444:",
395 "1111:2222:3333:",
396 "1111:2222:",
397 ":8888",
398 ":7777:8888",
399 ":6666:7777:8888",
400 ":5555:6666:7777:8888",
401 ":4444:5555:6666:7777:8888",
402 ":3333:4444:5555:6666:7777:8888",
403 ":2222:3333:4444:5555:6666:7777:8888",
404 ":1111:2222:3333:4444:5555:6666:7777:8888",
405 ":::2222:3333:4444:5555:6666:7777:8888",
406 "1111:::3333:4444:5555:6666:7777:8888",
407 "1111:2222:::4444:5555:6666:7777:8888",
408 "1111:2222:3333:::5555:6666:7777:8888",
409 "1111:2222:3333:4444:::6666:7777:8888",
410 "1111:2222:3333:4444:5555:::7777:8888",
411 "1111:2222:3333:4444:5555:6666:::8888",
412 "::2222::4444:5555:6666:7777:8888",
413 "::2222:3333::5555:6666:7777:8888",
414 "::2222:3333:4444::6666:7777:8888",
415 "::2222:3333:4444:5555::7777:8888",
416 "::2222:3333:4444:5555:7777::8888",
417 "::2222:3333:4444:5555:7777:8888::",
418 "1111::3333::5555:6666:7777:8888",
419 "1111::3333:4444::6666:7777:8888",
420 "1111::3333:4444:5555::7777:8888",
421 "1111::3333:4444:5555:6666::8888",
422 "1111::3333:4444:5555:6666:7777::",
423 "1111:2222::4444::6666:7777:8888",
424 "1111:2222::4444:5555::7777:8888",
425 "1111:2222::4444:5555:6666::8888",
426 "1111:2222::4444:5555:6666:7777::",
427 "1111:2222:3333::5555::7777:8888",
428 "1111:2222:3333::5555:6666::8888",
429 "1111:2222:3333::5555:6666:7777::",
430 "1111:2222:3333:4444::6666::8888",
431 "1111:2222:3333:4444::6666:7777::",
432 "1111:2222:3333:4444:5555::7777::",
433 "1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4",
434 "1111:2222:3333:4444:5555:6666:7777:1.2.3.4",
435 "1111:2222:3333:4444:5555:6666::1.2.3.4",
436 "::2222:3333:4444:5555:6666:7777:1.2.3.4",
437 "1111:2222:3333:4444:5555:6666:1.2.3.4.5",
438 "1111:2222:3333:4444:5555:1.2.3.4",
439 "1111:2222:3333:4444:1.2.3.4",
440 "1111:2222:3333:1.2.3.4",
441 "1111:2222:1.2.3.4",
442 "1111:1.2.3.4",
443 "11112222:3333:4444:5555:6666:1.2.3.4",
444 "1111:22223333:4444:5555:6666:1.2.3.4",
445 "1111:2222:33334444:5555:6666:1.2.3.4",
446 "1111:2222:3333:44445555:6666:1.2.3.4",
447 "1111:2222:3333:4444:55556666:1.2.3.4",
448 "1111:2222:3333:4444:5555:66661.2.3.4",
449 "1111:2222:3333:4444:5555:6666:255255.255.255",
450 "1111:2222:3333:4444:5555:6666:255.255255.255",
451 "1111:2222:3333:4444:5555:6666:255.255.255255",
452 ":1.2.3.4",
453 ":6666:1.2.3.4",
454 ":5555:6666:1.2.3.4",
455 ":4444:5555:6666:1.2.3.4",
456 ":3333:4444:5555:6666:1.2.3.4",
457 ":2222:3333:4444:5555:6666:1.2.3.4",
458 ":1111:2222:3333:4444:5555:6666:1.2.3.4",
459 ":::2222:3333:4444:5555:6666:1.2.3.4",
460 "1111:::3333:4444:5555:6666:1.2.3.4",
461 "1111:2222:::4444:5555:6666:1.2.3.4",
462 "1111:2222:3333:::5555:6666:1.2.3.4",
463 "1111:2222:3333:4444:::6666:1.2.3.4",
464 "1111:2222:3333:4444:5555:::1.2.3.4",
465 "::2222::4444:5555:6666:1.2.3.4",
466 "::2222:3333::5555:6666:1.2.3.4",
467 "::2222:3333:4444::6666:1.2.3.4",
468 "::2222:3333:4444:5555::1.2.3.4",
469 "1111::3333::5555:6666:1.2.3.4",
470 "1111::3333:4444::6666:1.2.3.4",
471 "1111::3333:4444:5555::1.2.3.4",
472 "1111:2222::4444::6666:1.2.3.4",
473 "1111:2222::4444:5555::1.2.3.4",
474 "1111:2222:3333::5555::1.2.3.4",
475 "::.",
476 "::..",
477 "::...",
478 "::1...",
479 "::1.2..",
480 "::1.2.3.",
481 "::.2..",
482 "::.2.3.",
483 "::.2.3.4",
484 "::..3.",
485 "::..3.4",
486 "::...4",
487 ":1111:2222:3333:4444:5555:6666:7777::",
488 ":1111:2222:3333:4444:5555:6666::",
489 ":1111:2222:3333:4444:5555::",
490 ":1111:2222:3333:4444::",
491 ":1111:2222:3333::",
492 ":1111:2222::",
493 ":1111::",
494 ":1111:2222:3333:4444:5555:6666::8888",
495 ":1111:2222:3333:4444:5555::8888",
496 ":1111:2222:3333:4444::8888",
497 ":1111:2222:3333::8888",
498 ":1111:2222::8888",
499 ":1111::8888",
500 ":::8888",
501 ":1111:2222:3333:4444:5555::7777:8888",
502 ":1111:2222:3333:4444::7777:8888",
503 ":1111:2222:3333::7777:8888",
504 ":1111:2222::7777:8888",
505 ":1111::7777:8888",
506 ":::7777:8888",
507 ":1111:2222:3333:4444::6666:7777:8888",
508 ":1111:2222:3333::6666:7777:8888",
509 ":1111:2222::6666:7777:8888",
510 ":1111::6666:7777:8888",
511 ":::6666:7777:8888",
512 ":1111:2222:3333::5555:6666:7777:8888",
513 ":1111:2222::5555:6666:7777:8888",
514 ":1111::5555:6666:7777:8888",
515 ":::5555:6666:7777:8888",
516 ":1111:2222::4444:5555:6666:7777:8888",
517 ":1111::4444:5555:6666:7777:8888",
518 ":::4444:5555:6666:7777:8888",
519 ":1111::3333:4444:5555:6666:7777:8888",
520 ":::3333:4444:5555:6666:7777:8888",
521 ":1111:2222:3333:4444:5555::1.2.3.4",
522 ":1111:2222:3333:4444::1.2.3.4",
523 ":1111:2222:3333::1.2.3.4",
524 ":1111:2222::1.2.3.4",
525 ":1111::1.2.3.4",
526 ":::1.2.3.4",
527 ":1111:2222:3333:4444::6666:1.2.3.4",
528 ":1111:2222:3333::6666:1.2.3.4",
529 ":1111:2222::6666:1.2.3.4",
530 ":1111::6666:1.2.3.4",
531 ":::6666:1.2.3.4",
532 ":1111:2222:3333::5555:6666:1.2.3.4",
533 ":1111:2222::5555:6666:1.2.3.4",
534 ":1111::5555:6666:1.2.3.4",
535 ":::5555:6666:1.2.3.4",
536 ":1111:2222::4444:5555:6666:1.2.3.4",
537 ":1111::4444:5555:6666:1.2.3.4",
538 ":::4444:5555:6666:1.2.3.4",
539 ":1111::3333:4444:5555:6666:1.2.3.4",
540 "1111:2222:3333:4444:5555:6666:7777:::",
541 "1111:2222:3333:4444:5555:6666:::",
542 "1111:2222:3333:4444:5555:::",
543 "1111:2222:3333:4444:::",
544 "1111:2222:3333:::",
545 "1111:2222:::",
546 "1111:::",
547 "1111:2222:3333:4444:5555:6666::8888:",
548 "1111:2222:3333:4444:5555::8888:",
549 "1111:2222:3333:4444::8888:",
550 "1111:2222:3333::8888:",
551 "1111:2222::8888:",
552 "1111::8888:",
553 "::8888:",
554 "1111:2222:3333:4444:5555::7777:8888:",
555 "1111:2222:3333:4444::7777:8888:",
556 "1111:2222:3333::7777:8888:",
557 "1111:2222::7777:8888:",
558 "1111::7777:8888:",
559 "::7777:8888:",
560 "1111:2222:3333:4444::6666:7777:8888:",
561 "1111:2222:3333::6666:7777:8888:",
562 "1111:2222::6666:7777:8888:",
563 "1111::6666:7777:8888:",
564 "::6666:7777:8888:",
565 "1111:2222:3333::5555:6666:7777:8888:",
566 "1111:2222::5555:6666:7777:8888:",
567 "1111::5555:6666:7777:8888:",
568 "::5555:6666:7777:8888:",
569 "1111:2222::4444:5555:6666:7777:8888:",
570 "1111::4444:5555:6666:7777:8888:",
571 "::4444:5555:6666:7777:8888:",
572 "1111::3333:4444:5555:6666:7777:8888:",
573 "::3333:4444:5555:6666:7777:8888:",
574 "::2222:3333:4444:5555:6666:7777:8888:",
575 "':10.0.0.1",
576 )
577 for s in invalid:
578 with self.assertRaises(dns.exception.SyntaxError,
579 msg=f'invalid IPv6 address: "{s}"'):
580 dns.ipv6.inet_aton(s)
1616
1717 import asyncio
1818 import socket
19 import sys
1920 import time
2021 import unittest
2122
151152
152153 @unittest.skipIf(not _network_available, "Internet not reachable")
153154 class AsyncTests(unittest.TestCase):
155 connect_udp = sys.platform == 'win32'
154156
155157 def setUp(self):
156158 self.backend = dns.asyncbackend.set_default_backend('asyncio')
181183 dnsgoogle = dns.name.from_text('dns.google.')
182184 self.assertEqual(answer[0].target, dnsgoogle)
183185
186 def testCanonicalNameNoCNAME(self):
187 cname = dns.name.from_text('www.google.com')
188 async def run():
189 return await dns.asyncresolver.canonical_name('www.google.com')
190 self.assertEqual(self.async_run(run), cname)
191
192 def testCanonicalNameCNAME(self):
193 name = dns.name.from_text('www.dnspython.org')
194 cname = dns.name.from_text('dmfrjf4ips8xa.cloudfront.net')
195 async def run():
196 return await dns.asyncresolver.canonical_name(name)
197 self.assertEqual(self.async_run(run), cname)
198
199 def testCanonicalNameDangling(self):
200 name = dns.name.from_text('dangling-cname.dnspython.org')
201 cname = dns.name.from_text('dangling-target.dnspython.org')
202 async def run():
203 return await dns.asyncresolver.canonical_name(name)
204 self.assertEqual(self.async_run(run), cname)
205
184206 def testResolverBadScheme(self):
185207 res = dns.asyncresolver.Resolver(configure=False)
186208 res.nameservers = ['bogus://dns.google/dns-query']
227249 qname = dns.name.from_text('dns.google.')
228250 async def run():
229251 q = dns.message.make_query(qname, dns.rdatatype.A)
230 return await dns.asyncquery.udp(q, address)
252 return await dns.asyncquery.udp(q, address, timeout=2)
231253 response = self.async_run(run)
232254 rrs = response.get_rrset(response.answer, qname,
233255 dns.rdataclass.IN, dns.rdatatype.A)
240262 for address in query_addresses:
241263 qname = dns.name.from_text('dns.google.')
242264 async def run():
265 if self.connect_udp:
266 dtuple=(address, 53)
267 else:
268 dtuple=None
243269 async with await self.backend.make_socket(
244270 dns.inet.af_for_address(address),
245 socket.SOCK_DGRAM) as s:
271 socket.SOCK_DGRAM, 0, None, dtuple) as s:
246272 q = dns.message.make_query(qname, dns.rdatatype.A)
247 return await dns.asyncquery.udp(q, address, sock=s)
273 return await dns.asyncquery.udp(q, address, sock=s,
274 timeout=2)
248275 response = self.async_run(run)
249276 rrs = response.get_rrset(response.answer, qname,
250277 dns.rdataclass.IN, dns.rdatatype.A)
258285 qname = dns.name.from_text('dns.google.')
259286 async def run():
260287 q = dns.message.make_query(qname, dns.rdatatype.A)
261 return await dns.asyncquery.tcp(q, address)
288 return await dns.asyncquery.tcp(q, address, timeout=2)
262289 response = self.async_run(run)
263290 rrs = response.get_rrset(response.answer, qname,
264291 dns.rdataclass.IN, dns.rdatatype.A)
275302 dns.inet.af_for_address(address),
276303 socket.SOCK_STREAM, 0,
277304 None,
278 (address, 53)) as s:
305 (address, 53), 2) as s:
279306 # for basic coverage
280307 await s.getsockname()
281308 q = dns.message.make_query(qname, dns.rdatatype.A)
282 return await dns.asyncquery.tcp(q, address, sock=s)
309 return await dns.asyncquery.tcp(q, address, sock=s,
310 timeout=2)
283311 response = self.async_run(run)
284312 rrs = response.get_rrset(response.answer, qname,
285313 dns.rdataclass.IN, dns.rdatatype.A)
294322 qname = dns.name.from_text('dns.google.')
295323 async def run():
296324 q = dns.message.make_query(qname, dns.rdatatype.A)
297 return await dns.asyncquery.tls(q, address)
325 return await dns.asyncquery.tls(q, address, timeout=2)
298326 response = self.async_run(run)
299327 rrs = response.get_rrset(response.answer, qname,
300328 dns.rdataclass.IN, dns.rdatatype.A)
314342 dns.inet.af_for_address(address),
315343 socket.SOCK_STREAM, 0,
316344 None,
317 (address, 853), None,
345 (address, 853), 2,
318346 ssl_context, None) as s:
319347 # for basic coverage
320348 await s.getsockname()
321349 q = dns.message.make_query(qname, dns.rdatatype.A)
322 return await dns.asyncquery.tls(q, '8.8.8.8', sock=s)
350 return await dns.asyncquery.tls(q, '8.8.8.8', sock=s,
351 timeout=2)
323352 response = self.async_run(run)
324353 rrs = response.get_rrset(response.answer, qname,
325354 dns.rdataclass.IN, dns.rdatatype.A)
333362 qname = dns.name.from_text('.')
334363 async def run():
335364 q = dns.message.make_query(qname, dns.rdatatype.DNSKEY)
336 return await dns.asyncquery.udp_with_fallback(q, address)
365 return await dns.asyncquery.udp_with_fallback(q, address,
366 timeout=2)
337367 (_, tcp) = self.async_run(run)
338368 self.assertTrue(tcp)
339369
342372 qname = dns.name.from_text('dns.google.')
343373 async def run():
344374 q = dns.message.make_query(qname, dns.rdatatype.A)
345 return await dns.asyncquery.udp_with_fallback(q, address)
375 return await dns.asyncquery.udp_with_fallback(q, address,
376 timeout=2)
346377 (_, tcp) = self.async_run(run)
347378 self.assertFalse(tcp)
348379
349380 def testUDPReceiveQuery(self):
381 if self.connect_udp:
382 self.skipTest('test needs connectionless sockets')
350383 async def run():
351384 async with await self.backend.make_socket(
352385 socket.AF_INET, socket.SOCK_DGRAM,
366399 self.assertEqual(sender_address, recv_address)
367400
368401 def testUDPReceiveTimeout(self):
402 if self.connect_udp:
403 self.skipTest('test needs connectionless sockets')
369404 async def arun():
370405 async with await self.backend.make_socket(socket.AF_INET,
371406 socket.SOCK_DGRAM, 0,
404439 return trio.run(afunc)
405440
406441 class TrioAsyncTests(AsyncTests):
442 connect_udp = False
407443 def setUp(self):
408444 self.backend = dns.asyncbackend.set_default_backend('trio')
409445
427463 return curio.run(afunc)
428464
429465 class CurioAsyncTests(AsyncTests):
466 connect_udp = False
430467 def setUp(self):
431468 self.backend = dns.asyncbackend.set_default_backend('curio')
432469
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import unittest
3
4 import dns.dnssec
5 import dns.rdtypes.dnskeybase
6 import dns.flags
7 import dns.rcode
8 import dns.opcode
9 import dns.message
10 import dns.update
11 import dns.edns
12
13 import tests.util
14
15
16 class ConstantsTestCase(unittest.TestCase):
17
18 def test_dnssec_constants(self):
19 tests.util.check_enum_exports(dns.dnssec, self.assertEqual,
20 only={dns.dnssec.Algorithm})
21 tests.util.check_enum_exports(dns.rdtypes.dnskeybase, self.assertEqual)
22
23 def test_flags_constants(self):
24 tests.util.check_enum_exports(dns.flags, self.assertEqual)
25 tests.util.check_enum_exports(dns.rcode, self.assertEqual)
26 tests.util.check_enum_exports(dns.opcode, self.assertEqual)
27
28 def test_message_constants(self):
29 tests.util.check_enum_exports(dns.message, self.assertEqual)
30 tests.util.check_enum_exports(dns.update, self.assertEqual)
31
32 def test_rdata_constants(self):
33 tests.util.check_enum_exports(dns.rdataclass, self.assertEqual)
34 tests.util.check_enum_exports(dns.rdatatype, self.assertEqual)
35
36 def test_edns_constants(self):
37 tests.util.check_enum_exports(dns.edns, self.assertEqual)
191191 'MX 16 2 3600 1440021600 1438207200 38353 example.com. E1/oLjSGIbmLny/4fcgM1z4oL6aqo+izT3urCyHyvEp4Sp8Syg1eI+lJ57CSnZqjJP41O/9l4m0AsQ4f7qI1gVnML8vWWiyW2KXhT9kuAICUSxv5OWbf81Rq7Yu60npabODB0QFPb/rkW3kUZmQ0YQUA')
192192
193193 when5 = 1440021600
194 when5_start = 1438207200
194195
195196 wildcard_keys = {
196197 abs_example_com : dns.rrset.from_text(
204205
205206 wildcard_when = 1593541048
206207
207 class DNSSECMakeDSTestCase(unittest.TestCase):
208 def testMnemonicParser(self):
209 good_ds_mnemonic = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS,
210 '57349 RSASHA1 2 53A79A3E7488AB44FFC56B2D1109F0699D1796DD977E72108B841F96 E47D7013')
211 self.assertEqual(good_ds, good_ds_mnemonic)
208
209 rsamd5_keys = {
210 abs_example: dns.rrset.from_text(
211 'example', 3600, 'in', 'dnskey',
212 '257 3 1 AwEAAewnoEWe+AVEnQzcZTwpl8K/QKuScYIX 9xHOhejAL1enMjE0j97Gq3XXJJPWF7eQQGHs 1De4Srv2UT0zRCLkH9r36lOR/ggANvthO/Ub Es0hlD3A58LumEPudgIDwEkxGvQAXMFTMw0x 1d/a82UtzmNoPVzFOl2r+OCXx9Jbdh/L; KSK; alg = RSAMD5; key id = 30239',
213 '256 3 1 AwEAAb8OJM5YcqaYG0fenUdRlrhBQ6LuwCvr 5BRlrVbVzadSDBpq+yIiklfdGNBg3WZztDy1 du62NWC/olMfc6uRe/SjqTa7IJ3MdEuZQXQw MedGdNSF73zbokx8wg7zBBr74xHczJcEpQhr ZLzwCDmIPu0yoVi3Yqdl4dm4vNBj9hAD; ZSK; alg = RSAMD5; key id = 62992')
214 }
215
216 rsamd5_ns = dns.rrset.from_text('example.', 3600, 'in', 'ns',
217 'ns1.example.', 'ns2.example.')
218 rsamd5_ns_rrsig = dns.rrset.from_text('example.', 3600, 'in', 'rrsig',
219 'NS 1 1 3600 20200825153103 20200726153103 62992 example. YPv0WVqzQBDH45mFcYGo9psCVoMoeeHeAugh 9RZuO2NmdwfQ3mmiQm7WJ3AYnzYIozFGf7CL nwn3vN8/fjsfcQgEv5xfhFTSd4IoAzJJiZAa vrI4L5590C/+aXQ8tjRmbMTPiqoudaXvsevE jP2lTFg5DCruJyFq5dnAY5b90RY=')
220
221 rsamd5_when = 1595781671
222
223 rsasha512_keys = {
224 abs_example: dns.rrset.from_text(
225 'example', 3600, 'in', 'dnskey',
226 '256 3 10 AwEAAb2JvKjZ6l5qg2ab3qqUQhLGGjsiMIuQ 2zhaXJHdTntS+8LgUXo5yLFn7YF9YL1VX9V4 5ASGxUpz0u0chjWqBNtUO3Ymzas/vck9o21M 2Ce/LrpfYsqvJaLvGf/dozW9uSeMQq1mPKYG xo4uxyhZBhZewX8znXZySrAIozBPH3yp ; ZSK; alg = RSASHA512 ; key id = 5957',
227 '257 3 10 AwEAAc7Lnoe+mHijJ8OOHgyJHKYantQGKx5t rIs267gOePyAL7cUt9HO1Sm3vABSGNsoHL6w 8/542SxGbT21osVISamtq7kUPTgDU9iKqCBq VdXEdzXYbhBKVoQkGPl4PflfbOgg/45xAiTi 7qOUERuRCPdKEkd4FW0tg6VfZmm7QjP1 ; KSK; alg = RSASHA512 ; key id = 53212')
228 }
229
230 rsasha512_ns = dns.rrset.from_text('example.', 3600, 'in', 'ns',
231 'ns1.example.', 'ns2.example.')
232 rsasha512_ns_rrsig = dns.rrset.from_text(
233 'example.', 3600, 'in', 'rrsig',
234 'NS 10 1 3600 20200825161255 20200726161255 5957 example. P9A+1zYke7yIiKEnxFMm+UIW2CIwy2WDvbx6 g8hHiI8qISe6oeKveFW23OSk9+VwFgBiOpeM ygzzFbckY7RkGbOr4TR8ogDRANt6LhV402Hu SXTV9hCLVFWU4PS+/fxxfOHCetsY5tWWSxZi zSHfgpGfsHWzQoAamag4XYDyykc=')
235
236 rsasha512_when = 1595783997
237
238
239 unknown_alg_keys = {
240 abs_example: dns.rrset.from_text(
241 'example', 3600, 'in', 'dnskey',
242 '256 3 100 Ym9ndXM=',
243 '257 3 100 Ym9ndXM=')
244 }
245
246 unknown_alg_ns_rrsig = dns.rrset.from_text(
247 'example.', 3600, 'in', 'rrsig',
248 'NS 100 1 3600 20200825161255 20200726161255 16713 example. P9A+1zYke7yIiKEnxFMm+UIW2CIwy2WDvbx6 g8hHiI8qISe6oeKveFW23OSk9+VwFgBiOpeM ygzzFbckY7RkGbOr4TR8ogDRANt6LhV402Hu SXTV9hCLVFWU4PS+/fxxfOHCetsY5tWWSxZi zSHfgpGfsHWzQoAamag4XYDyykc=')
249
250 fake_gost_keys = {
251 abs_example: dns.rrset.from_text(
252 'example', 3600, 'in', 'dnskey',
253 '256 3 12 Ym9ndXM=',
254 '257 3 12 Ym9ndXM=')
255 }
256
257 fake_gost_ns_rrsig = dns.rrset.from_text(
258 'example.', 3600, 'in', 'rrsig',
259 'NS 12 1 3600 20200825161255 20200726161255 16625 example. P9A+1zYke7yIiKEnxFMm+UIW2CIwy2WDvbx6 g8hHiI8qISe6oeKveFW23OSk9+VwFgBiOpeM ygzzFbckY7RkGbOr4TR8ogDRANt6LhV402Hu SXTV9hCLVFWU4PS+/fxxfOHCetsY5tWWSxZi zSHfgpGfsHWzQoAamag4XYDyykc=')
212260
213261 @unittest.skipUnless(dns.dnssec._have_pyca,
214262 "Python Cryptography cannot be imported")
215263 class DNSSECValidatorTestCase(unittest.TestCase):
264
265 def testAbsoluteRSAMD5Good(self): # type: () -> None
266 dns.dnssec.validate(rsamd5_ns, rsamd5_ns_rrsig, rsamd5_keys, None,
267 rsamd5_when)
268
269 def testRSAMD5Keyid(self):
270 self.assertEqual(dns.dnssec.key_id(rsamd5_keys[abs_example][0]), 30239)
271 self.assertEqual(dns.dnssec.key_id(rsamd5_keys[abs_example][1]), 62992)
216272
217273 def testAbsoluteRSAGood(self): # type: () -> None
218274 dns.dnssec.validate(abs_soa, abs_soa_rrsig, abs_keys, None, when)
229285 def testRelativeRSAGood(self): # type: () -> None
230286 dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys,
231287 abs_dnspython_org, when)
288 # test the text conversion for origin too
289 dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys,
290 'dnspython.org', when)
232291
233292 def testRelativeRSABad(self): # type: () -> None
234293 def bad(): # type: () -> None
294353 dns.dnssec.validate(abs_other_ed448_mx, abs_ed448_mx_rrsig_2,
295354 abs_ed448_keys_2, None, when5)
296355
297 def testWildcardGood(self): # type: () -> None
356 def testAbsoluteRSASHA512Good(self):
357 dns.dnssec.validate(rsasha512_ns, rsasha512_ns_rrsig, rsasha512_keys,
358 None, rsasha512_when)
359
360 def testWildcardGoodAndBad(self):
298361 dns.dnssec.validate(wildcard_txt, wildcard_txt_rrsig,
299362 wildcard_keys, None, wildcard_when)
300363
312375 abc_txt_rrsig = clone_rrset(wildcard_txt_rrsig, abc_name)
313376 dns.dnssec.validate(abc_txt, abc_txt_rrsig, wildcard_keys, None,
314377 wildcard_when)
378
379 com_name = dns.name.from_text('com.')
380 com_txt = clone_rrset(wildcard_txt, com_name)
381 com_txt_rrsig = clone_rrset(wildcard_txt_rrsig, abc_name)
382 with self.assertRaises(dns.dnssec.ValidationFailure):
383 dns.dnssec.validate_rrsig(com_txt, com_txt_rrsig[0], wildcard_keys,
384 None, wildcard_when)
315385
316386 def testAlternateParameterFormats(self): # type: () -> None
317387 # Pass rrset and rrsigset as (name, rdataset) tuples, not rrsets
325395 keys[name] = dns.node.Node()
326396 keys[name].rdatasets.append(key_rrset.to_rdataset())
327397 dns.dnssec.validate(abs_soa, abs_soa_rrsig, keys, None, when)
398 # test key not found.
399 keys = {}
400 for (name, key_rrset) in abs_keys.items():
401 keys[name] = dns.node.Node()
402 with self.assertRaises(dns.dnssec.ValidationFailure):
403 dns.dnssec.validate(abs_soa, abs_soa_rrsig, keys, None, when)
328404
329405 # Pass origin as a string, not a name.
330406 dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys,
331407 'dnspython.org', when)
408 dns.dnssec.validate_rrsig(rel_soa, rel_soa_rrsig[0], rel_keys,
409 'dnspython.org', when)
410
411 def testAbsoluteKeyNotFound(self):
412 with self.assertRaises(dns.dnssec.ValidationFailure):
413 dns.dnssec.validate(abs_ed448_mx, abs_ed448_mx_rrsig_1, {}, None,
414 when5)
415
416 def testTimeBounds(self):
417 # not yet valid
418 with self.assertRaises(dns.dnssec.ValidationFailure):
419 dns.dnssec.validate(abs_ed448_mx, abs_ed448_mx_rrsig_1,
420 abs_ed448_keys_1, None, when5_start - 1)
421 # expired
422 with self.assertRaises(dns.dnssec.ValidationFailure):
423 dns.dnssec.validate(abs_ed448_mx, abs_ed448_mx_rrsig_1,
424 abs_ed448_keys_1, None, when5 + 1)
425 # expired using the current time (to test the "get the time" code
426 # path)
427 with self.assertRaises(dns.dnssec.ValidationFailure):
428 dns.dnssec.validate(abs_ed448_mx, abs_ed448_mx_rrsig_1,
429 abs_ed448_keys_1, None)
430
431 def testOwnerNameMismatch(self):
432 bogus = dns.name.from_text('example.bogus')
433 with self.assertRaises(dns.dnssec.ValidationFailure):
434 dns.dnssec.validate((bogus, abs_ed448_mx), abs_ed448_mx_rrsig_1,
435 abs_ed448_keys_1, None, when5 + 1)
436
437 def testGOSTNotSupported(self):
438 with self.assertRaises(dns.dnssec.ValidationFailure):
439 dns.dnssec.validate(rsasha512_ns, fake_gost_ns_rrsig,
440 fake_gost_keys, None, rsasha512_when)
441
442 def testUnknownAlgorithm(self):
443 with self.assertRaises(dns.dnssec.ValidationFailure):
444 dns.dnssec.validate(rsasha512_ns, unknown_alg_ns_rrsig,
445 unknown_alg_keys, None, rsasha512_when)
446
447
448 class DNSSECMiscTestCase(unittest.TestCase):
449 def testDigestToBig(self):
450 with self.assertRaises(ValueError):
451 dns.dnssec.DSDigest.make(256)
452
453 def testNSEC3HashTooBig(self):
454 with self.assertRaises(ValueError):
455 dns.dnssec.NSEC3Hash.make(256)
456
457 def testIsNotGOST(self):
458 self.assertTrue(dns.dnssec._is_gost(dns.dnssec.Algorithm.ECCGOST))
459
460 def testUnknownHash(self):
461 with self.assertRaises(dns.dnssec.ValidationFailure):
462 dns.dnssec._make_hash(100)
463
332464
333465 class DNSSECMakeDSTestCase(unittest.TestCase):
466
467 def testMnemonicParser(self):
468 good_ds_mnemonic = dns.rdata.from_text(dns.rdataclass.IN,
469 dns.rdatatype.DS,
470 '57349 RSASHA1 2 53A79A3E7488AB44FFC56B2D1109F0699D1796DD977E72108B841F96 E47D7013')
471 self.assertEqual(good_ds, good_ds_mnemonic)
334472
335473 def testMakeExampleSHA1DS(self): # type: () -> None
336474 for algorithm in ('SHA1', 'sha1', dns.dnssec.DSDigest.SHA1):
337475 ds = dns.dnssec.make_ds(abs_example, example_sep_key, algorithm)
338476 self.assertEqual(ds, example_ds_sha1)
477 ds = dns.dnssec.make_ds('example.', example_sep_key, algorithm)
478 self.assertEqual(ds, example_ds_sha1)
339479
340480 def testMakeExampleSHA256DS(self): # type: () -> None
341481 for algorithm in ('SHA256', 'sha256', dns.dnssec.DSDigest.SHA256):
356496 with self.assertRaises(dns.dnssec.UnsupportedAlgorithm):
357497 ds = dns.dnssec.make_ds(abs_example, example_sep_key, algorithm)
358498
499 def testInvalidDigestType(self): # type: () -> None
500 digest_type_errors = {
501 0: 'digest type 0 is reserved',
502 5: 'unknown digest type',
503 }
504 for digest_type, msg in digest_type_errors.items():
505 with self.assertRaises(dns.exception.SyntaxError) as cm:
506 dns.rdata.from_text(dns.rdataclass.IN,
507 dns.rdatatype.DS,
508 f'18673 3 {digest_type} 71b71d4f3e11bbd71b4eff12cde69f7f9215bbe7')
509 self.assertEqual(msg, str(cm.exception))
510
511 def testInvalidDigestLength(self): # type: () -> None
512 test_records = []
513 for rdata in [example_ds_sha1, example_ds_sha256, example_ds_sha384]:
514 flags, digest = rdata.to_text().rsplit(' ', 1)
515
516 # Make sure the construction is working
517 dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS, f'{flags} {digest}')
518
519 test_records.append(f'{flags} {digest[:len(digest)//2]}') # too short digest
520 test_records.append(f'{flags} {digest*2}') # too long digest
521
522 for record in test_records:
523 with self.assertRaises(dns.exception.SyntaxError) as cm:
524 dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS, record)
525
526 self.assertEqual('digest length inconsistent with digest type', str(cm.exception))
527
528
359529 if __name__ == '__main__':
360530 unittest.main()
3131 resolver_v6_addresses = []
3232 try:
3333 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
34 s.settimeout(4)
3435 s.connect(('8.8.8.8', 53))
35 resolver_v4_addresses = ['1.1.1.1', '8.8.8.8', '9.9.9.9']
36 resolver_v4_addresses = [
37 '1.1.1.1',
38 '8.8.8.8',
39 # '9.9.9.9',
40 ]
3641 except Exception:
3742 pass
3843 try:
4247 '2606:4700:4700::1111',
4348 # Google says 404
4449 # '2001:4860:4860::8888',
45 '2620:fe::11'
50 # '2620:fe::fe',
4651 ]
4752 except Exception:
4853 pass
4954
5055 KNOWN_ANYCAST_DOH_RESOLVER_URLS = ['https://cloudflare-dns.com/dns-query',
5156 'https://dns.google/dns-query',
52 'https://dns11.quad9.net/dns-query']
57 # 'https://dns11.quad9.net/dns-query',
58 ]
5359
5460 # Some tests require the internet to be available to run, so let's
5561 # skip those if it's not there.
7177 def test_get_request(self):
7278 nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS)
7379 q = dns.message.make_query('example.com.', dns.rdatatype.A)
74 r = dns.query.https(q, nameserver_url, session=self.session, post=False)
80 r = dns.query.https(q, nameserver_url, session=self.session, post=False,
81 timeout=4)
7582 self.assertTrue(q.is_response(r))
7683
7784 def test_post_request(self):
7885 nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS)
7986 q = dns.message.make_query('example.com.', dns.rdatatype.A)
80 r = dns.query.https(q, nameserver_url, session=self.session, post=True)
87 r = dns.query.https(q, nameserver_url, session=self.session, post=True,
88 timeout=4)
8189 self.assertTrue(q.is_response(r))
8290
8391 def test_build_url_from_ip(self):
8997 # https://8.8.8.8/dns-query
9098 # So we're just going to do GET requests here
9199 r = dns.query.https(q, nameserver_ip, session=self.session,
92 post=False)
100 post=False, timeout=4)
93101
94102 self.assertTrue(q.is_response(r))
95103 if resolver_v6_addresses:
96104 nameserver_ip = random.choice(resolver_v6_addresses)
97105 q = dns.message.make_query('example.com.', dns.rdatatype.A)
98106 r = dns.query.https(q, nameserver_ip, session=self.session,
99 post=False)
107 post=False, timeout=4)
100108 self.assertTrue(q.is_response(r))
101109
102110 def test_bootstrap_address(self):
109117 # make sure CleanBrowsing's IP address will fail TLS certificate
110118 # check
111119 with self.assertRaises(SSLError):
112 dns.query.https(q, invalid_tls_url, session=self.session)
120 dns.query.https(q, invalid_tls_url, session=self.session,
121 timeout=4)
113122 # use host header
114123 r = dns.query.https(q, valid_tls_url, session=self.session,
115 bootstrap_address=ip)
124 bootstrap_address=ip, timeout=4)
116125 self.assertTrue(q.is_response(r))
117126
118127 def test_new_session(self):
119128 nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS)
120129 q = dns.message.make_query('example.com.', dns.rdatatype.A)
121 r = dns.query.https(q, nameserver_url)
130 r = dns.query.https(q, nameserver_url, timeout=4)
122131 self.assertTrue(q.is_response(r))
123132
124133 def test_resolver(self):
1616 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1717
1818 import operator
19 import struct
1920 import unittest
2021
2122 from io import BytesIO
2223
2324 import dns.edns
25 import dns.wire
2426
2527 class OptionTestCase(unittest.TestCase):
2628 def testGenericOption(self):
3032 data = io.getvalue()
3133 self.assertEqual(data, b'data')
3234 self.assertEqual(dns.edns.option_from_wire(3, data, 0, len(data)), opt)
35 self.assertEqual(str(opt), 'Generic 3')
3336
3437 def testECSOption_prefix_length(self):
3538 opt = dns.edns.ECSOption('1.2.255.33', 20)
4447 opt.to_wire(io)
4548 data = io.getvalue()
4649 self.assertEqual(data, b'\x00\x01\x18\x00\x01\x02\x03')
50 # default srclen
51 opt = dns.edns.ECSOption('1.2.3.4')
52 io = BytesIO()
53 opt.to_wire(io)
54 data = io.getvalue()
55 self.assertEqual(data, b'\x00\x01\x18\x00\x01\x02\x03')
56 self.assertEqual(opt.to_text(), 'ECS 1.2.3.4/24 scope/0')
4757
4858 def testECSOption25(self):
4959 opt = dns.edns.ECSOption('1.2.3.255', 25)
104114 dns.edns.ECSOption.from_text('1.2.3.4/twentyfour')
105115
106116 with self.assertRaises(ValueError):
117 dns.edns.ECSOption.from_text('BOGUS 1.2.3.4/5/6/7')
118
119 with self.assertRaises(ValueError):
120 dns.edns.ECSOption.from_text('1.2.3.4/5/6/7')
121
122 with self.assertRaises(ValueError):
107123 dns.edns.ECSOption.from_text('1.2.3.4/24/O') # <-- that's not a zero
108124
109125 with self.assertRaises(ValueError):
111127
112128 with self.assertRaises(ValueError):
113129 dns.edns.ECSOption.from_text('1.2.3.4/2001:4b98::1/24')
130
131 def testECSOption_from_wire_invalid(self):
132 with self.assertRaises(ValueError):
133 opt = dns.edns.option_from_wire(dns.edns.ECS,
134 b'\x00\xff\x18\x00\x01\x02\x03',
135 0, 7)
114136
115137 def test_basic_relations(self):
116138 o1 = dns.edns.ECSOption.from_text('1.2.3.0/24/0')
137159 self.assertTrue(o1 != o2)
138160 self.assertFalse(o1 == 123)
139161 self.assertTrue(o1 != 123)
162
163 def test_option_registration(self):
164 U32OptionType = 9999
165
166 class U32Option(dns.edns.Option):
167 def __init__(self, value=None):
168 super().__init__(U32OptionType)
169 self.value = value
170
171 def to_wire(self, file=None):
172 data = struct.pack('!I', self.value)
173 if file:
174 file.write(data)
175 else:
176 return data
177
178 @classmethod
179 def from_wire_parser(cls, otype, parser):
180 (value,) = parser.get_struct('!I')
181 return cls(value)
182
183 try:
184 dns.edns.register_type(U32Option, U32OptionType)
185 generic = dns.edns.GenericOption(U32OptionType, b'\x00\x00\x00\x01')
186 wire1 = generic.to_wire()
187 u32 = dns.edns.option_from_wire_parser(U32OptionType,
188 dns.wire.Parser(wire1))
189 self.assertEqual(u32.value, 1)
190 wire2 = u32.to_wire()
191 self.assertEqual(wire1, wire2)
192 self.assertEqual(u32, generic)
193 finally:
194 dns.edns._type_to_class.pop(U32OptionType, None)
195
196 opt = dns.edns.option_from_wire_parser(9999, dns.wire.Parser(wire1))
197 self.assertEqual(opt, generic)
1212 self.assertEqual(pool.random_16(), 61532)
1313 self.assertEqual(pool.random_32(), 4226376065)
1414 self.assertEqual(pool.random_between(10, 50), 29)
15 # stir in some not-really-entropy to exercise the stir API
16 pool.stir(b'not-really-entropy')
1517
1618 def test_pool_random(self):
1719 pool = dns.entropy.EntropyPool()
7171 # In TSIG text mode, it should be BADSIG
7272 self.assertEqual(dns.rcode.to_text(rcode, True), 'BADSIG')
7373
74 def test_unknown_rcode(self):
75 with self.assertRaises(dns.rcode.UnknownRcode):
76 dns.rcode.Rcode.make('BOGUS')
77
7478
7579 if __name__ == '__main__':
7680 unittest.main()
6363 self.assertEqual(step, 77)
6464
6565 def testFailFromText1(self):
66 def bad():
66 with self.assertRaises(dns.exception.SyntaxError):
6767 start = 2
6868 stop = 1
6969 step = 1
7070 dns.grange.from_text('%d-%d/%d' % (start, stop, step))
71 self.assertRaises(AssertionError, bad)
71 self.assertTrue(False)
7272
7373 def testFailFromText2(self):
74 def bad():
74 with self.assertRaises(dns.exception.SyntaxError):
7575 start = '-1'
7676 stop = 3
7777 step = 1
7878 dns.grange.from_text('%s-%d/%d' % (start, stop, step))
79 self.assertRaises(dns.exception.SyntaxError, bad)
8079
8180 def testFailFromText3(self):
82 def bad():
81 with self.assertRaises(dns.exception.SyntaxError):
8382 start = 1
8483 stop = 4
8584 step = '-2'
8685 dns.grange.from_text('%d-%d/%s' % (start, stop, step))
87 self.assertRaises(dns.exception.SyntaxError, bad)
86
87 def testFailFromText4(self):
88 with self.assertRaises(dns.exception.SyntaxError):
89 dns.grange.from_text('1')
8890
8991 if __name__ == '__main__':
9092 unittest.main()
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import unittest
3
4 import dns.immutable
5 import dns._immutable_attr
6
7 try:
8 import dns._immutable_ctx as immutable_ctx
9 _have_contextvars = True
10 except ImportError:
11 _have_contextvars = False
12
13 class immutable_ctx:
14 pass
15
16
17 class ImmutableTestCase(unittest.TestCase):
18
19 def test_immutable_dict_hash(self):
20 d1 = dns.immutable.Dict({'a': 1, 'b': 2})
21 d2 = dns.immutable.Dict({'b': 2, 'a': 1})
22 d3 = {'b': 2, 'a': 1}
23 self.assertEqual(d1, d2)
24 self.assertEqual(d2, d3)
25 self.assertEqual(hash(d1), hash(d2))
26
27 def test_immutable_dict_hash_cache(self):
28 d = dns.immutable.Dict({'a': 1, 'b': 2})
29 self.assertEqual(d._hash, None)
30 h1 = hash(d)
31 self.assertEqual(d._hash, h1)
32 h2 = hash(d)
33 self.assertEqual(h1, h2)
34
35 def test_constify(self):
36 items = (
37 (bytearray([1, 2, 3]), b'\x01\x02\x03'),
38 ((1, 2, 3), (1, 2, 3)),
39 ((1, [2], 3), (1, (2,), 3)),
40 ([1, 2, 3], (1, 2, 3)),
41 ([1, {'a': [1, 2]}],
42 (1, dns.immutable.Dict({'a': (1, 2)}))),
43 ('hi', 'hi'),
44 (b'hi', b'hi'),
45 )
46 for input, expected in items:
47 self.assertEqual(dns.immutable.constify(input), expected)
48 self.assertIsInstance(dns.immutable.constify({'a': 1}),
49 dns.immutable.Dict)
50
51
52 class DecoratorTestCase(unittest.TestCase):
53
54 immutable_module = dns._immutable_attr
55
56 def make_classes(self):
57 class A:
58 def __init__(self, a, akw=10):
59 self.a = a
60 self.akw = akw
61
62 class B(A):
63 def __init__(self, a, b):
64 super().__init__(a, akw=20)
65 self.b = b
66 B = self.immutable_module.immutable(B)
67
68 # note C is immutable by inheritance
69 class C(B):
70 def __init__(self, a, b, c):
71 super().__init__(a, b)
72 self.c = c
73 C = self.immutable_module.immutable(C)
74
75 class SA:
76 __slots__ = ('a', 'akw')
77 def __init__(self, a, akw=10):
78 self.a = a
79 self.akw = akw
80
81 class SB(A):
82 __slots__ = ('b')
83 def __init__(self, a, b):
84 super().__init__(a, akw=20)
85 self.b = b
86 SB = self.immutable_module.immutable(SB)
87
88 # note SC is immutable by inheritance and has no slots of its own
89 class SC(SB):
90 def __init__(self, a, b, c):
91 super().__init__(a, b)
92 self.c = c
93 SC = self.immutable_module.immutable(SC)
94
95 return ((A, B, C), (SA, SB, SC))
96
97 def test_basic(self):
98 for A, B, C in self.make_classes():
99 a = A(1)
100 self.assertEqual(a.a, 1)
101 self.assertEqual(a.akw, 10)
102 b = B(11, 21)
103 self.assertEqual(b.a, 11)
104 self.assertEqual(b.akw, 20)
105 self.assertEqual(b.b, 21)
106 c = C(111, 211, 311)
107 self.assertEqual(c.a, 111)
108 self.assertEqual(c.akw, 20)
109 self.assertEqual(c.b, 211)
110 self.assertEqual(c.c, 311)
111 # changing A is ok!
112 a.a = 11
113 self.assertEqual(a.a, 11)
114 # changing B is not!
115 with self.assertRaises(TypeError):
116 b.a = 11
117 with self.assertRaises(TypeError):
118 del b.a
119
120 def test_constructor_deletes_attribute(self):
121 class A:
122 def __init__(self, a):
123 self.a = a
124 self.b = a
125 del self.b
126 A = self.immutable_module.immutable(A)
127 a = A(10)
128 self.assertEqual(a.a, 10)
129 self.assertFalse(hasattr(a, 'b'))
130
131 def test_no_collateral_damage(self):
132
133 # A and B are immutable but not related. The magic that lets
134 # us write to immutable things while initializing B should not let
135 # B mess with A.
136
137 class A:
138 def __init__(self, a):
139 self.a = a
140 A = self.immutable_module.immutable(A)
141
142 class B:
143 def __init__(self, a, b):
144 self.b = a.a + b
145 # rudely attempt to mutate innocent immutable bystander 'a'
146 a.a = 1000
147 B = self.immutable_module.immutable(B)
148
149 a = A(10)
150 self.assertEqual(a.a, 10)
151 with self.assertRaises(TypeError):
152 B(a, 20)
153 self.assertEqual(a.a, 10)
154
155
156 @unittest.skipIf(not _have_contextvars, "contextvars not available")
157 class CtxDecoratorTestCase(DecoratorTestCase):
158
159 immutable_module = immutable_ctx
1515 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
1616 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1717
18 import os
1918 import unittest
2019 import binascii
2120
2625 import dns.rdataclass
2726 import dns.rdatatype
2827 import dns.rrset
28 import dns.tsig
2929 import dns.update
30
31 def here(filename):
32 return os.path.join(os.path.dirname(__file__), filename)
30 import dns.rdtypes.ANY.OPT
31 import dns.rdtypes.ANY.TSIG
32
33 from tests.util import here
3334
3435 query_text = """id 1234
3536 opcode QUERY
336337 m.want_dnssec()
337338 self.assertEqual(m.edns, 0)
338339 self.assertTrue(m.ednsflags & dns.flags.DO)
340
341 def test_EDNS_default_payload_is_1232(self):
342 m = dns.message.make_query('foo', 'A')
343 m.use_edns()
344 self.assertEqual(m.payload, dns.message.DEFAULT_EDNS_PAYLOAD)
339345
340346 def test_from_file(self):
341347 m = dns.message.from_file(here('query'))
363369 self.assertEqual(q.sections[3], [])
364370 q.additional = [rrset]
365371 self.assertEqual(q.sections[3], [rrset])
372
373 def test_is_a_response_empty_question(self):
374 q = dns.message.make_query('www.dnspython.org.', 'a')
375 r = dns.message.make_response(q)
376 r.question = []
377 r.set_rcode(dns.rcode.FORMERR)
378 self.assertTrue(q.is_response(r))
366379
367380 def test_not_a_response(self):
368381 q = dns.message.QueryMessage(id=1)
380393 q2.id = 1
381394 r = dns.message.make_response(q2)
382395 self.assertFalse(q1.is_response(r))
396 # Now set rcode to FORMERR and check again. It should still
397 # not be a response as we check the question section for FORMERR
398 # if it is present.
399 r.set_rcode(dns.rcode.FORMERR)
400 self.assertFalse(q1.is_response(r))
383401 # Test the other case of differing questions, where there is
384402 # something in the response's question section that is not in
385403 # the question's. We have to do multiple questions to test
432450 self.assertFalse(isinstance(q2, dns.update.UpdateMessage))
433451 self.assertEqual(q1, q2)
434452
453 def test_truncated_exception_message(self):
454 q = dns.message.Message(id=1)
455 q.flags |= dns.flags.TC
456 te = dns.message.Truncated(message=q)
457 self.assertEqual(te.message(), q)
458
459 def test_bad_opt(self):
460 # Not in addtional
461 q = dns.message.Message(id=1)
462 opt = dns.rdtypes.ANY.OPT.OPT(1200, dns.rdatatype.OPT, ())
463 rrs = dns.rrset.from_rdata(dns.name.root, 0, opt)
464 q.answer.append(rrs)
465 wire = q.to_wire()
466 with self.assertRaises(dns.message.BadEDNS):
467 dns.message.from_wire(wire)
468 # Owner name not root name
469 q = dns.message.Message(id=1)
470 rrs = dns.rrset.from_rdata('foo.', 0, opt)
471 q.additional.append(rrs)
472 wire = q.to_wire()
473 with self.assertRaises(dns.message.BadEDNS):
474 dns.message.from_wire(wire)
475 # Multiple opts
476 q = dns.message.Message(id=1)
477 rrs = dns.rrset.from_rdata(dns.name.root, 0, opt)
478 q.additional.append(rrs)
479 q.additional.append(rrs)
480 wire = q.to_wire()
481 with self.assertRaises(dns.message.BadEDNS):
482 dns.message.from_wire(wire)
483
484 def test_bad_tsig(self):
485 keyname = dns.name.from_text('key.')
486 # Not in addtional
487 q = dns.message.Message(id=1)
488 tsig = dns.rdtypes.ANY.TSIG.TSIG(dns.rdataclass.ANY, dns.rdatatype.TSIG,
489 dns.tsig.HMAC_SHA256, 0, 300, b'1234',
490 0, 0, b'')
491 rrs = dns.rrset.from_rdata(keyname, 0, tsig)
492 q.answer.append(rrs)
493 wire = q.to_wire()
494 with self.assertRaises(dns.message.BadTSIG):
495 dns.message.from_wire(wire)
496 # Multiple tsigs
497 q = dns.message.Message(id=1)
498 q.additional.append(rrs)
499 q.additional.append(rrs)
500 wire = q.to_wire()
501 with self.assertRaises(dns.message.BadTSIG):
502 dns.message.from_wire(wire)
503 # Class not ANY
504 tsig = dns.rdtypes.ANY.TSIG.TSIG(dns.rdataclass.IN, dns.rdatatype.TSIG,
505 dns.tsig.HMAC_SHA256, 0, 300, b'1234',
506 0, 0, b'')
507 rrs = dns.rrset.from_rdata(keyname, 0, tsig)
508 wire = q.to_wire()
509 with self.assertRaises(dns.message.BadTSIG):
510 dns.message.from_wire(wire)
511
512 def test_read_no_content_message(self):
513 m = dns.message.from_text(';comment')
514 self.assertIsInstance(m, dns.message.QueryMessage)
515
516 def test_eflags_turns_on_edns(self):
517 m = dns.message.from_text('eflags DO')
518 self.assertIsInstance(m, dns.message.QueryMessage)
519 self.assertEqual(m.edns, 0)
520
521 def test_payload_turns_on_edns(self):
522 m = dns.message.from_text('payload 1200')
523 self.assertIsInstance(m, dns.message.QueryMessage)
524 self.assertEqual(m.payload, 1200)
525
526 def test_bogus_header(self):
527 with self.assertRaises(dns.message.UnknownHeaderField):
528 dns.message.from_text('bogus foo')
529
530 def test_question_only(self):
531 m = dns.message.from_text(answer_text)
532 w = m.to_wire()
533 r = dns.message.from_wire(w, question_only=True)
534 self.assertEqual(r.id, m.id)
535 self.assertEqual(r.question[0], m.question[0])
536 self.assertEqual(len(r.answer), 0)
537 self.assertEqual(len(r.authority), 0)
538 self.assertEqual(len(r.additional), 0)
539
540 def test_bad_resolve_chaining(self):
541 r = dns.message.make_query('www.dnspython.org.', 'a')
542 with self.assertRaises(dns.message.NotQueryResponse):
543 r.resolve_chaining()
544 r.flags |= dns.flags.QR
545 r.id = 1
546 r.find_rrset(r.question, dns.name.from_text('example'),
547 dns.rdataclass.IN, dns.rdatatype.A, create=True,
548 force_unique=True)
549 with self.assertRaises(dns.exception.FormError):
550 r.resolve_chaining()
551
552 def test_resolve_chaining_no_infinite_loop(self):
553 r = dns.message.from_text('''id 1
554 flags QR
555 ;QUESTION
556 www.example. IN CNAME
557 ;AUTHORITY
558 example. 300 IN SOA . . 1 2 3 4 5
559 ''')
560 # passing is actuall not going into an infinite loop in this call
561 result = r.resolve_chaining()
562 self.assertEqual(result.canonical_name,
563 dns.name.from_text('www.example.'))
564 self.assertEqual(result.minimum_ttl, 5)
565 self.assertIsNone(result.answer)
566
567 def test_bad_text_questions(self):
568 with self.assertRaises(dns.exception.SyntaxError):
569 dns.message.from_text('''id 1
570 ;QUESTION
571 example.
572 ''')
573 with self.assertRaises(dns.exception.SyntaxError):
574 dns.message.from_text('''id 1
575 ;QUESTION
576 example. IN
577 ''')
578 with self.assertRaises(dns.rdatatype.UnknownRdatatype):
579 dns.message.from_text('''id 1
580 ;QUESTION
581 example. INA
582 ''')
583 with self.assertRaises(dns.rdatatype.UnknownRdatatype):
584 dns.message.from_text('''id 1
585 ;QUESTION
586 example. IN BOGUS
587 ''')
588
589 def test_bad_text_rrs(self):
590 with self.assertRaises(dns.exception.SyntaxError):
591 dns.message.from_text('''id 1
592 flags QR
593 ;QUESTION
594 example. IN A
595 ;ANSWER
596 example.
597 ''')
598 with self.assertRaises(dns.exception.SyntaxError):
599 dns.message.from_text('''id 1
600 flags QR
601 ;QUESTION
602 example. IN A
603 ;ANSWER
604 example. IN
605 ''')
606 with self.assertRaises(dns.exception.SyntaxError):
607 dns.message.from_text('''id 1
608 flags QR
609 ;QUESTION
610 example. IN A
611 ;ANSWER
612 example. 300
613 ''')
614 with self.assertRaises(dns.rdatatype.UnknownRdatatype):
615 dns.message.from_text('''id 1
616 flags QR
617 ;QUESTION
618 example. IN A
619 ;ANSWER
620 example. 30a IN A
621 ''')
622 with self.assertRaises(dns.rdatatype.UnknownRdatatype):
623 dns.message.from_text('''id 1
624 flags QR
625 ;QUESTION
626 example. IN A
627 ;ANSWER
628 example. 300 INA A
629 ''')
630 with self.assertRaises(dns.exception.UnexpectedEnd):
631 dns.message.from_text('''id 1
632 flags QR
633 ;QUESTION
634 example. IN A
635 ;ANSWER
636 example. 300 IN A
637 ''')
638 with self.assertRaises(dns.exception.SyntaxError):
639 dns.message.from_text('''id 1
640 flags QR
641 opcode UPDATE
642 ;ZONE
643 example. IN SOA
644 ;UPDATE
645 example. 300 IN A
646 ''')
647 with self.assertRaises(dns.exception.SyntaxError):
648 dns.message.from_text('''id 1
649 flags QR
650 opcode UPDATE
651 ;ZONE
652 example. IN SOA
653 ;UPDATE
654 example. 300 NONE A
655 ''')
656 with self.assertRaises(dns.exception.SyntaxError):
657 dns.message.from_text('''id 1
658 flags QR
659 opcode UPDATE
660 ;ZONE
661 example. IN SOA
662 ;PREREQ
663 example. 300 NONE A 10.0.0.1
664 ''')
665 with self.assertRaises(dns.exception.SyntaxError):
666 dns.message.from_text('''id 1
667 flags QR
668 ;ANSWER
669 300 IN A 10.0.0.1
670 ''')
671 with self.assertRaises(dns.exception.SyntaxError):
672 dns.message.from_text('''id 1
673 flags QR
674 ;QUESTION
675 IN SOA
676 ''')
677
678 def test_from_wire_makes_Flag(self):
679 m = dns.message.from_wire(goodwire)
680 self.assertIsInstance(m.flags, dns.flags.Flag)
681 self.assertEqual(m.flags, dns.flags.Flag.RD)
682
683
435684 if __name__ == '__main__':
436685 unittest.main()
1818 from typing import Dict # pylint: disable=unused-import
1919 import copy
2020 import operator
21 import pickle
2122 import unittest
2223
2324 from io import BytesIO
464465 n.to_wire(f, compress)
465466 self.assertRaises(dns.name.NeedAbsoluteNameOrOrigin, bad)
466467
468 def testGiantCompressionTable(self):
469 # Only the first 16KiB of a message can have compression pointers.
470 f = BytesIO()
471 compress = {} # type: Dict[dns.name.Name,int]
472 # exactly 16 bytes encoded
473 n = dns.name.from_text('0000000000.com.')
474 n.to_wire(f, compress)
475 # There are now two entries in the compression table (for the full
476 # name, and for the com. suffix.
477 self.assertEqual(len(compress), 2)
478 for i in range(1023):
479 # exactly 16 bytes encoded with compression
480 n = dns.name.from_text(f'{i:013d}.com')
481 n.to_wire(f, compress)
482 # There are now 1025 entries in the compression table with
483 # the last entry at offset 16368.
484 self.assertEqual(len(compress), 1025)
485 self.assertEqual(compress[n], 16368)
486 # Adding another name should not increase the size of the compression
487 # table, as the pointer would be at offset 16384, which is too big.
488 n = dns.name.from_text('toobig.com.')
489 n.to_wire(f, compress)
490 self.assertEqual(len(compress), 1025)
491
467492 def testSplit1(self):
468493 n = dns.name.from_text('foo.bar.')
469494 (prefix, suffix) = n.split(2)
780805 @unittest.skipUnless(dns.name.have_idna_2008,
781806 'Python idna cannot be imported; no IDNA2008')
782807 def testToUnicode4(self):
783 if dns.name.have_idna_2008:
784 n = dns.name.from_text('ドメイン.テスト',
785 idna_codec=dns.name.IDNA_2008)
786 s = n.to_unicode()
787 self.assertEqual(str(n), 'xn--eckwd4c7c.xn--zckzah.')
788 self.assertEqual(s, 'ドメイン.テスト.')
808 n = dns.name.from_text('ドメイン.テスト',
809 idna_codec=dns.name.IDNA_2008)
810 s = n.to_unicode()
811 self.assertEqual(str(n), 'xn--eckwd4c7c.xn--zckzah.')
812 self.assertEqual(s, 'ドメイン.テスト.')
813
814 @unittest.skipUnless(dns.name.have_idna_2008,
815 'Python idna cannot be imported; no IDNA2008')
816 def testToUnicode5(self):
817 # Exercise UTS 46 remapping in decode. This doesn't normally happen
818 # as you can see from us having to instantiate the codec as
819 # transitional with strict decoding, not one of our usual choices.
820 codec = dns.name.IDNA2008Codec(True, True, False, True)
821 n = dns.name.from_text('xn--gro-7ka.com')
822 self.assertEqual(n.to_unicode(idna_codec=codec),
823 'gross.com.')
824
825 @unittest.skipUnless(dns.name.have_idna_2008,
826 'Python idna cannot be imported; no IDNA2008')
827 def testToUnicode6(self):
828 # Test strict 2008 decoding without UTS 46
829 n = dns.name.from_text('xn--gro-7ka.com')
830 self.assertEqual(n.to_unicode(idna_codec=dns.name.IDNA_2008_Strict),
831 'groß.com.')
789832
790833 def testDefaultDecodeIsJustPunycode(self):
791834 # groß.com. in IDNA2008 form, pre-encoded.
890933 origin = dns.name.from_text('foo.bar')
891934 text = dns.reversename.to_address(n, v6_origin=origin)
892935 self.assertEqual(text, e)
936
937 def testUnknownReverseOrigin(self):
938 n = dns.name.from_text('1.2.3.4.unknown.')
939 with self.assertRaises(dns.exception.SyntaxError):
940 dns.reversename.to_address(n)
893941
894942 def testE164ToEnum(self):
895943 text = '+1 650 555 1212'
10311079 n = dns.name.from_unicode('Königsgäßchen;\ttext')
10321080 self.assertEqual(n.to_unicode(), 'königsgässchen\\;\\009text.')
10331081
1082 def test_pickle(self):
1083 n1 = dns.name.from_text('foo.example')
1084 p = pickle.dumps(n1)
1085 n2 = pickle.loads(p)
1086 self.assertEqual(n1, n2)
1087
10341088 if __name__ == '__main__':
10351089 unittest.main()
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
02 import unittest
13
24 from dns import dnssec, name
161161 b = binascii.unhexlify(b'0000000000000000000000000001ffff')
162162 t = ntoa6(b)
163163 self.assertEqual(t, '::0.1.255.255')
164
165 def test_ntoa15(self):
166 # This exercises the current_len > best_len branch in the <= case.
167 b = binascii.unhexlify(b'0000ffff00000000ffff00000000ffff')
168 t = ntoa6(b)
169 self.assertEqual(t, '0:ffff::ffff:0:0:ffff')
164170
165171 def test_bad_ntoa1(self):
166172 def bad():
0
1 import dns.rdata
2 import dns.rdataset
3
4
5 def test_processing_order_shuffle():
6 rds = dns.rdataset.from_text('in', 'a', 300,
7 '10.0.0.1', '10.0.0.2', '10.0.0.3')
8 seen = set()
9 for i in range(100):
10 po = rds.processing_order()
11 assert len(po) == 3
12 for j in range(3):
13 assert rds[j] in po
14 seen.add(tuple(po))
15 assert len(seen) == 6
16
17
18 def test_processing_order_priority_mx():
19 rds = dns.rdataset.from_text('in', 'mx', 300,
20 '10 a', '20 b', '20 c')
21 seen = set()
22 for i in range(100):
23 po = rds.processing_order()
24 assert len(po) == 3
25 for j in range(3):
26 assert rds[j] in po
27 assert rds[0] == po[0]
28 seen.add(tuple(po))
29 assert len(seen) == 2
30
31
32 def test_processing_order_priority_weighted():
33 rds = dns.rdataset.from_text('in', 'srv', 300,
34 '1 10 1234 a', '2 90 1234 b', '2 10 1234 c')
35 seen = set()
36 weight_90_count = 0
37 weight_10_count = 0
38 for i in range(100):
39 po = rds.processing_order()
40 assert len(po) == 3
41 for j in range(3):
42 assert rds[j] in po
43 assert rds[0] == po[0]
44 if po[1].weight == 90:
45 weight_90_count += 1
46 else:
47 assert po[1].weight == 10
48 weight_10_count += 1
49 seen.add(tuple(po))
50 assert len(seen) == 2
51 # We can't assert anything with certainty given these are random
52 # draws, but it's super likely that weight_90_count > weight_10_count,
53 # so we just assert that.
54 assert weight_90_count > weight_10_count
55
56
57 def test_processing_order_priority_naptr():
58 rds = dns.rdataset.from_text('in', 'naptr', 300,
59 '1 10 a b c foo.', '1 20 a b c foo.',
60 '2 10 a b c foo.', '2 10 d e f bar.')
61 seen = set()
62 for i in range(100):
63 po = rds.processing_order()
64 assert len(po) == 4
65 for j in range(4):
66 assert rds[j] in po
67 assert rds[0] == po[0]
68 assert rds[1] == po[1]
69 seen.add(tuple(po))
70 assert len(seen) == 2
71
72
73 def test_processing_order_empty():
74 rds = dns.rdataset.from_text('in', 'naptr', 300)
75 po = rds.processing_order()
76 assert po == []
77
78
79 def test_processing_singleton_priority():
80 rds = dns.rdataset.from_text('in', 'mx', 300, '10 a')
81 po = rds.processing_order()
82 assert po == [rds[0]]
83
84
85 def test_processing_singleton_weighted():
86 rds = dns.rdataset.from_text('in', 'srv', 300, '1 10 1234 a')
87 po = rds.processing_order()
88 assert po == [rds[0]]
89
90
91 def test_processing_all_zero_weight_srv():
92 rds = dns.rdataset.from_text('in', 'srv', 300,
93 '1 0 1234 a', '1 0 1234 b', '1 0 1234 c')
94 seen = set()
95 for i in range(100):
96 po = rds.processing_order()
97 assert len(po) == 3
98 for j in range(3):
99 assert rds[j] in po
100 seen.add(tuple(po))
101 assert len(seen) == 6
102
103
104 def test_processing_order_uri():
105 # We're testing here just to provide coverage for URI methods; the
106 # testing of the weighting algorithm is done above in tests with
107 # SRV.
108 rds = dns.rdataset.from_text('in', 'uri', 300,
109 '1 1 "ftp://ftp1.example.com/public"',
110 '2 2 "ftp://ftp2.example.com/public"',
111 '3 3 "ftp://ftp3.example.com/public"')
112 po = rds.processing_order()
113 assert len(po) == 3
114 for i in range(3):
115 assert po[i] == rds[i]
116
117
118 def test_processing_order_svcb():
119 # We're testing here just to provide coverage for SVCB methods; the
120 # testing of the priority algorithm is done above in tests with
121 # MX and NAPTR.
122 rds = dns.rdataset.from_text('in', 'svcb', 300,
123 "1 . mandatory=alpn alpn=h2",
124 "2 . mandatory=alpn alpn=h2",
125 "3 . mandatory=alpn alpn=h2")
126 po = rds.processing_order()
127 assert len(po) == 3
128 for i in range(3):
129 assert po[i] == rds[i]
6767 except Exception:
6868 pass
6969
70 keyring = dns.tsigkeyring.from_text({'name' : 'tDz6cfXXGtNivRpQ98hr6A=='})
70 keyring = dns.tsigkeyring.from_text({'name': 'tDz6cfXXGtNivRpQ98hr6A=='})
7171
7272 @unittest.skipIf(not _network_available, "Internet not reachable")
7373 class QueryTests(unittest.TestCase):
7676 for address in query_addresses:
7777 qname = dns.name.from_text('dns.google.')
7878 q = dns.message.make_query(qname, dns.rdatatype.A)
79 response = dns.query.udp(q, address)
79 response = dns.query.udp(q, address, timeout=2)
8080 rrs = response.get_rrset(response.answer, qname,
8181 dns.rdataclass.IN, dns.rdatatype.A)
8282 self.assertTrue(rrs is not None)
9191 s.setblocking(0)
9292 qname = dns.name.from_text('dns.google.')
9393 q = dns.message.make_query(qname, dns.rdatatype.A)
94 response = dns.query.udp(q, address, sock=s)
94 response = dns.query.udp(q, address, sock=s, timeout=2)
9595 rrs = response.get_rrset(response.answer, qname,
9696 dns.rdataclass.IN, dns.rdatatype.A)
9797 self.assertTrue(rrs is not None)
103103 for address in query_addresses:
104104 qname = dns.name.from_text('dns.google.')
105105 q = dns.message.make_query(qname, dns.rdatatype.A)
106 response = dns.query.tcp(q, address)
106 response = dns.query.tcp(q, address, timeout=2)
107107 rrs = response.get_rrset(response.answer, qname,
108108 dns.rdataclass.IN, dns.rdatatype.A)
109109 self.assertTrue(rrs is not None)
116116 with socket.socket(dns.inet.af_for_address(address),
117117 socket.SOCK_STREAM) as s:
118118 ll = dns.inet.low_level_address_tuple((address, 53))
119 s.settimeout(2)
119120 s.connect(ll)
120121 s.setblocking(0)
121122 qname = dns.name.from_text('dns.google.')
122123 q = dns.message.make_query(qname, dns.rdatatype.A)
123 response = dns.query.tcp(q, None, sock=s)
124 response = dns.query.tcp(q, None, sock=s, timeout=2)
124125 rrs = response.get_rrset(response.answer, qname,
125126 dns.rdataclass.IN, dns.rdatatype.A)
126127 self.assertTrue(rrs is not None)
132133 for address in query_addresses:
133134 qname = dns.name.from_text('dns.google.')
134135 q = dns.message.make_query(qname, dns.rdatatype.A)
135 response = dns.query.tls(q, address)
136 response = dns.query.tls(q, address, timeout=2)
136137 rrs = response.get_rrset(response.answer, qname,
137138 dns.rdataclass.IN, dns.rdatatype.A)
138139 self.assertTrue(rrs is not None)
146147 with socket.socket(dns.inet.af_for_address(address),
147148 socket.SOCK_STREAM) as base_s:
148149 ll = dns.inet.low_level_address_tuple((address, 853))
150 base_s.settimeout(2)
149151 base_s.connect(ll)
150152 ctx = ssl.create_default_context()
151153 with ctx.wrap_socket(base_s, server_hostname='dns.google') as s:
152154 s.setblocking(0)
153155 qname = dns.name.from_text('dns.google.')
154156 q = dns.message.make_query(qname, dns.rdatatype.A)
155 response = dns.query.tls(q, None, sock=s)
157 response = dns.query.tls(q, None, sock=s, timeout=2)
156158 rrs = response.get_rrset(response.answer, qname,
157159 dns.rdataclass.IN, dns.rdatatype.A)
158160 self.assertTrue(rrs is not None)
164166 for address in query_addresses:
165167 qname = dns.name.from_text('.')
166168 q = dns.message.make_query(qname, dns.rdatatype.DNSKEY)
167 (_, tcp) = dns.query.udp_with_fallback(q, address)
169 (_, tcp) = dns.query.udp_with_fallback(q, address, timeout=2)
168170 self.assertTrue(tcp)
169171
170172 def testQueryUDPFallbackWithSocket(self):
174176 udp_s.setblocking(0)
175177 with socket.socket(af, socket.SOCK_STREAM) as tcp_s:
176178 ll = dns.inet.low_level_address_tuple((address, 53))
179 tcp_s.settimeout(2)
177180 tcp_s.connect(ll)
178181 tcp_s.setblocking(0)
179182 qname = dns.name.from_text('.')
180183 q = dns.message.make_query(qname, dns.rdatatype.DNSKEY)
181184 (_, tcp) = dns.query.udp_with_fallback(q, address,
182 udp_sock=udp_s,
183 tcp_sock=tcp_s)
185 udp_sock=udp_s,
186 tcp_sock=tcp_s,
187 timeout=2)
184188 self.assertTrue(tcp)
185189
186190 def testQueryUDPFallbackNoFallback(self):
187191 for address in query_addresses:
188192 qname = dns.name.from_text('dns.google.')
189193 q = dns.message.make_query(qname, dns.rdatatype.A)
190 (_, tcp) = dns.query.udp_with_fallback(q, address)
194 (_, tcp) = dns.query.udp_with_fallback(q, address, timeout=2)
191195 self.assertFalse(tcp)
192196
193197 def testUDPReceiveQuery(self):
275279
276280
277281 axfr_zone = '''
278 $ORIGIN example.
279282 $TTL 300
280283 @ SOA ns1 root 1 7200 900 1209600 86400
281284 @ NS ns1
287290 class AXFRNanoNameserver(Server):
288291
289292 def handle(self, request):
290 self.zone = dns.zone.from_text(axfr_zone, origin='example')
293 self.zone = dns.zone.from_text(axfr_zone, origin=self.origin)
291294 self.origin = self.zone.origin
292295 items = []
293296 soa = self.zone.find_rrset(dns.name.empty, dns.rdatatype.SOA)
380383
381384 def test_axfr(self):
382385 expected = dns.zone.from_text(axfr_zone, origin='example')
383 with AXFRNanoNameserver() as ns:
386 with AXFRNanoNameserver(origin='example') as ns:
384387 xfr = dns.query.xfr(ns.tcp_address[0], 'example',
385388 port=ns.tcp_address[1])
386389 zone = dns.zone.from_xfr(xfr)
388391
389392 def test_axfr_tsig(self):
390393 expected = dns.zone.from_text(axfr_zone, origin='example')
391 with AXFRNanoNameserver(keyring=keyring) as ns:
394 with AXFRNanoNameserver(origin='example', keyring=keyring) as ns:
392395 xfr = dns.query.xfr(ns.tcp_address[0], 'example',
393396 port=ns.tcp_address[1],
394397 keyring=keyring, keyname='name')
395398 zone = dns.zone.from_xfr(xfr)
396399 self.assertEqual(zone, expected)
397400
401 def test_axfr_root_tsig(self):
402 expected = dns.zone.from_text(axfr_zone, origin='.')
403 with AXFRNanoNameserver(origin='.', keyring=keyring) as ns:
404 xfr = dns.query.xfr(ns.tcp_address[0], '.',
405 port=ns.tcp_address[1],
406 keyring=keyring, keyname='name')
407 zone = dns.zone.from_xfr(xfr)
408 self.assertEqual(zone, expected)
409
398410 def test_axfr_udp(self):
399411 def bad():
400 with AXFRNanoNameserver() as ns:
412 with AXFRNanoNameserver(origin='example') as ns:
401413 xfr = dns.query.xfr(ns.udp_address[0], 'example',
402414 port=ns.udp_address[1], use_udp=True)
403415 l = list(xfr)
540552 l.close()
541553 r.close()
542554
543 def test_select_for(self):
544 # we test this explicitly in case _wait_for didn't test it (i.e.
545 # if the default polling backing is _poll_for)
546 try:
547 (l, r) = socket.socketpair()
548 # simple timeout
549 self.assertFalse(dns.query._select_for(l, False, False, False,
550 0.05))
551 # writable no timeout
552 self.assertTrue(dns.query._select_for(l, False, True, False, None))
553 finally:
554 l.close()
555 r.close()
555
556 class MiscTests(unittest.TestCase):
557 def test_matches_destination(self):
558 self.assertTrue(dns.query._matches_destination(socket.AF_INET,
559 ('10.0.0.1', 1234),
560 ('10.0.0.1', 1234),
561 True))
562 self.assertTrue(dns.query._matches_destination(socket.AF_INET6,
563 ('1::2', 1234),
564 ('0001::2', 1234),
565 True))
566 self.assertTrue(dns.query._matches_destination(socket.AF_INET,
567 ('10.0.0.1', 1234),
568 None,
569 True))
570 self.assertFalse(dns.query._matches_destination(socket.AF_INET,
571 ('10.0.0.1', 1234),
572 ('10.0.0.2', 1234),
573 True))
574 self.assertFalse(dns.query._matches_destination(socket.AF_INET,
575 ('10.0.0.1', 1234),
576 ('10.0.0.1', 1235),
577 True))
578 with self.assertRaises(dns.query.UnexpectedSource):
579 dns.query._matches_destination(socket.AF_INET,
580 ('10.0.0.1', 1234),
581 ('10.0.0.1', 1235),
582 False)
1515 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
1616 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1717
18 import binascii
1918 import io
2019 import operator
2120 import pickle
3029 import dns.rdataset
3130 import dns.rdatatype
3231 from dns.rdtypes.ANY.OPT import OPT
32 from dns.rdtypes.ANY.LOC import LOC
33 from dns.rdtypes.ANY.GPOS import GPOS
34 import dns.rdtypes.ANY.RRSIG
35 import dns.rdtypes.util
36 import dns.tokenizer
37 import dns.ttl
38 import dns.wire
3339
3440 import tests.stxt_module
3541 import tests.ttxt_module
42 import tests.md_module
43 from tests.util import here
3644
3745 class RdataTestCase(unittest.TestCase):
3846
8997
9098 def test_invalid_replace(self):
9199 a1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4")
92 def bad():
100 with self.assertRaises(dns.exception.SyntaxError):
93101 a1.replace(address="bogus")
94 self.assertRaises(dns.exception.SyntaxError, bad)
102
103 def test_replace_comment(self):
104 a1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
105 "1.2.3.4 ;foo")
106 self.assertEqual(a1.rdcomment, "foo")
107 a2 = a1.replace(rdcomment="bar")
108 self.assertEqual(a1, a2)
109 self.assertEqual(a1.rdcomment, "foo")
110 self.assertEqual(a2.rdcomment, "bar")
111
112 def test_no_replace_class_or_type(self):
113 a1 = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4")
114 with self.assertRaises(AttributeError):
115 a1.replace(rdclass=255)
116 with self.assertRaises(AttributeError):
117 a1.replace(rdtype=2)
95118
96119 def test_to_generic(self):
97120 a = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4")
267290 self.assertEqual(rda, rdb)
268291
269292 def test_misc_good_LOC_text(self):
293 # test just degrees
294 self.equal_loc('60 N 24 39 0.000 E 10.00m 20m 2000m 20m',
295 '60 0 0 N 24 39 0.000 E 10.00m 20m 2000m 20m')
296 self.equal_loc('60 0 0 N 24 E 10.00m 20m 2000m 20m',
297 '60 0 0 N 24 0 0 E 10.00m 20m 2000m 20m')
270298 # test variable length latitude
271299 self.equal_loc('60 9 0.510 N 24 39 0.000 E 10.00m 20m 2000m 20m',
272300 '60 9 0.51 N 24 39 0.000 E 10.00m 20m 2000m 20m')
281309 '60 9 0.000 N 24 39 0.5 E 10.00m 20m 2000m 20m')
282310 self.equal_loc('60 9 0.000 N 24 39 1.000 E 10.00m 20m 2000m 20m',
283311 '60 9 0.000 N 24 39 1 E 10.00m 20m 2000m 20m')
312 # test siz, hp, vp defaults
313 self.equal_loc('60 9 0.510 N 24 39 0.000 E 10.00m',
314 '60 9 0.51 N 24 39 0.000 E 10.00m 1m 10000m 10m')
315 self.equal_loc('60 9 0.510 N 24 39 0.000 E 10.00m 2m',
316 '60 9 0.51 N 24 39 0.000 E 10.00m 2m 10000m 10m')
317 self.equal_loc('60 9 0.510 N 24 39 0.000 E 10.00m 2m 2000m',
318 '60 9 0.51 N 24 39 0.000 E 10.00m 2m 2000m 10m')
319 # test siz, hp, vp optional units
320 self.equal_loc('60 9 0.510 N 24 39 0.000 E 1m 20m 2000m 20m',
321 '60 9 0.51 N 24 39 0.000 E 1 20 2000 20')
322
323 def test_LOC_to_text_SW_hemispheres(self):
324 # As an extra, we test int->float conversion in the constructor
325 loc = LOC(dns.rdataclass.IN, dns.rdatatype.LOC, -60, -24, 1)
326 text = '60 0 0.000 S 24 0 0.000 W 0.01m'
327 self.assertEqual(loc.to_text(), text)
328
329 def test_zero_size(self):
330 # This is to exercise the 0 path in _exponent_of.
331 loc = dns.rdata.from_text('in', 'loc', '60 S 24 W 1 0')
332 self.assertEqual(loc.size, 0.0)
284333
285334 def test_bad_LOC_text(self):
286335 bad_locs = ['60 9 a.000 N 24 39 0.000 E 10.00m 20m 2000m 20m',
303352 '60 9 0.000 N 24 39 0.000 E 10.00m 20m 100000000m 20m',
304353 '60 9 0.000 N 24 39 0.000 E 10.00m 20m 20m 100000000m',
305354 ]
306 def bad(text):
307 rd = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.LOC,
308 text)
309355 for loc in bad_locs:
310 self.assertRaises(dns.exception.SyntaxError,
311 lambda: bad(loc))
356 with self.assertRaises(dns.exception.SyntaxError):
357 dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.LOC, loc)
312358
313359 def test_bad_LOC_wire(self):
314360 bad_locs = [(0, 0, 0, 0x934fd901, 0x80000000, 100),
323369 (0, 0, 0x0a, 0x80000000, 0x80000000, 100),
324370 ]
325371 for t in bad_locs:
326 wire = struct.pack('!BBBBIII', 0, t[0], t[1], t[2],
327 t[3], t[4], t[5])
328 self.assertRaises(dns.exception.FormError,
329 lambda: dns.rdata.from_wire(dns.rdataclass.IN,
330 dns.rdatatype.LOC,
331 wire, 0, len(wire)))
372 with self.assertRaises(dns.exception.FormError):
373 wire = struct.pack('!BBBBIII', 0, t[0], t[1], t[2],
374 t[3], t[4], t[5])
375 dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.LOC,
376 wire, 0, len(wire))
377 with self.assertRaises(dns.exception.FormError):
378 wire = struct.pack('!BBBBIII', 1, 0, 0, 0, 0, 0, 0)
379 dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.LOC,
380 wire, 0, len(wire))
332381
333382 def equal_wks(self, a, b):
334383 rda = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.WKS, a)
340389 self.equal_wks('10.0.0.1 udp ( domain )', '10.0.0.1 17 ( 53 )')
341390
342391 def test_misc_bad_WKS_text(self):
343 def bad():
392 try:
344393 dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.WKS,
345394 '10.0.0.1 132 ( domain )')
346 self.assertRaises(NotImplementedError, bad)
395 self.assertTrue(False) # should not happen
396 except dns.exception.SyntaxError as e:
397 self.assertIsInstance(e.__cause__, NotImplementedError)
398
399 def test_GPOS_float_converters(self):
400 rd = dns.rdata.from_text('in', 'gpos', '49 0 0')
401 self.assertEqual(rd.float_latitude, 49.0)
402 self.assertEqual(rd.float_longitude, 0.0)
403 self.assertEqual(rd.float_altitude, 0.0)
404
405 def test_GPOS_constructor_conversion(self):
406 rd = GPOS(dns.rdataclass.IN, dns.rdatatype.GPOS, 49.0, 0.0, 0.0)
407 self.assertEqual(rd.float_latitude, 49.0)
408 self.assertEqual(rd.float_longitude, 0.0)
409 self.assertEqual(rd.float_altitude, 0.0)
410 rd = GPOS(dns.rdataclass.IN, dns.rdatatype.GPOS, 49, 0, 0)
411 self.assertEqual(rd.float_latitude, 49.0)
412 self.assertEqual(rd.float_longitude, 0.0)
413 self.assertEqual(rd.float_altitude, 0.0)
347414
348415 def test_bad_GPOS_text(self):
349416 bad_gpos = ['"-" "116.8652" "250"',
364431 '"0" "180.1" "0"',
365432 '"0" "-180.1" "0"',
366433 ]
367 def bad(text):
368 rd = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.GPOS,
369 text)
370434 for gpos in bad_gpos:
371 self.assertRaises(dns.exception.FormError,
372 lambda: bad(gpos))
435 with self.assertRaises(dns.exception.SyntaxError):
436 dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.GPOS, gpos)
373437
374438 def test_bad_GPOS_wire(self):
375439 bad_gpos = [b'\x01',
400464 self.assertEqual(repr(opt), '<DNS CLASS4096 OPT rdata: >')
401465
402466 def test_opt_short_lengths(self):
403 def bad1():
467 with self.assertRaises(dns.exception.FormError):
404468 parser = dns.wire.Parser(bytes.fromhex('f00102'))
405 opt = OPT.from_wire_parser(4096, dns.rdatatype.OPT, parser)
406 self.assertRaises(dns.exception.FormError, bad1)
407 def bad2():
469 OPT.from_wire_parser(4096, dns.rdatatype.OPT, parser)
470 with self.assertRaises(dns.exception.FormError):
408471 parser = dns.wire.Parser(bytes.fromhex('f00100030000'))
409 opt = OPT.from_wire_parser(4096, dns.rdatatype.OPT, parser)
410 self.assertRaises(dns.exception.FormError, bad2)
472 OPT.from_wire_parser(4096, dns.rdatatype.OPT, parser)
411473
412474 def test_from_wire_parser(self):
413475 wire = bytes.fromhex('01020304')
414476 rdata = dns.rdata.from_wire('in', 'a', wire, 0, 4)
415477 self.assertEqual(rdata, dns.rdata.from_text('in', 'a', '1.2.3.4'))
416478
479 def test_unpickle(self):
480 expected_mx = dns.rdata.from_text('in', 'mx', '10 mx.example.')
481 with open(here('mx-2-0.pickle'), 'rb') as f:
482 mx = pickle.load(f)
483 self.assertEqual(mx, expected_mx)
484 self.assertIsNone(mx.rdcomment)
485
486 def test_escaped_newline_in_quoted_string(self):
487 rd = dns.rdata.from_text('in', 'txt', '"foo\\\nbar"')
488 self.assertEqual(rd.strings, (b'foo\nbar',))
489 self.assertEqual(rd.to_text(), '"foo\\010bar"')
490
491 def test_escaped_newline_in_nonquoted_string(self):
492 with self.assertRaises(dns.exception.UnexpectedEnd):
493 dns.rdata.from_text('in', 'txt', 'foo\\\nbar')
494
495 def test_wordbreak(self):
496 text = b'abcdefgh'
497 self.assertEqual(dns.rdata._wordbreak(text, 4), 'abcd efgh')
498 self.assertEqual(dns.rdata._wordbreak(text, 0), 'abcdefgh')
499
500 def test_escapify(self):
501 self.assertEqual(dns.rdata._escapify('abc'), 'abc')
502 self.assertEqual(dns.rdata._escapify(b'abc'), 'abc')
503 self.assertEqual(dns.rdata._escapify(bytearray(b'abc')), 'abc')
504 self.assertEqual(dns.rdata._escapify(b'ab"c'), 'ab\\"c')
505 self.assertEqual(dns.rdata._escapify(b'ab\\c'), 'ab\\\\c')
506 self.assertEqual(dns.rdata._escapify(b'ab\x01c'), 'ab\\001c')
507
508 def test_truncate_bitmap(self):
509 self.assertEqual(dns.rdata._truncate_bitmap(b'\x00\x01\x00\x00'),
510 b'\x00\x01')
511 self.assertEqual(dns.rdata._truncate_bitmap(b'\x00\x01\x00\x01'),
512 b'\x00\x01\x00\x01')
513 self.assertEqual(dns.rdata._truncate_bitmap(b'\x00\x00\x00\x00'),
514 b'\x00')
515
516 def test_covers_and_extended_rdatatype(self):
517 rd = dns.rdata.from_text('in', 'a', '10.0.0.1')
518 self.assertEqual(rd.covers(), dns.rdatatype.NONE)
519 self.assertEqual(rd.extended_rdatatype(), 0x00000001)
520 rd = dns.rdata.from_text('in', 'rrsig',
521 'NSEC 1 3 3600 ' +
522 '20200101000000 20030101000000 ' +
523 '2143 foo Ym9ndXM=')
524 self.assertEqual(rd.covers(), dns.rdatatype.NSEC)
525 self.assertEqual(rd.extended_rdatatype(), 0x002f002e)
526
527 def test_uncomparable(self):
528 rd = dns.rdata.from_text('in', 'a', '10.0.0.1')
529 self.assertFalse(rd == 'a')
530 self.assertTrue(rd != 'a')
531
532 def test_bad_generic(self):
533 # does not start with \#
534 with self.assertRaises(dns.exception.SyntaxError):
535 dns.rdata.from_text('in', 'type45678', '# 7 000a03666f6f00')
536 # wrong length
537 with self.assertRaises(dns.exception.SyntaxError):
538 dns.rdata.from_text('in', 'type45678', '\\# 6 000a03666f6f00')
539
540 def test_covered_repr(self):
541 text = 'NSEC 1 3 3600 20190101000000 20030101000000 ' + \
542 '2143 foo Ym9ndXM='
543 rd = dns.rdata.from_text('in', 'rrsig', text)
544 self.assertEqual(repr(rd), '<DNS IN RRSIG(NSEC) rdata: ' + text + '>')
545
546 def test_bad_registration_implementing_known_type_with_wrong_name(self):
547 # Try to register an implementation at the MG codepoint that isn't
548 # called "MG"
549 with self.assertRaises(dns.rdata.RdatatypeExists):
550 dns.rdata.register_type(None, dns.rdatatype.MG, 'NOTMG')
551
552 def test_registration_implementing_known_type_with_right_name(self):
553 # Try to register an implementation at the MD codepoint
554 dns.rdata.register_type(tests.md_module, dns.rdatatype.MD, 'MD')
555 rd = dns.rdata.from_text('in', 'md', 'foo.')
556 self.assertEqual(rd.target, dns.name.from_text('foo.'))
557
558 def test_CERT_with_string_type(self):
559 rd = dns.rdata.from_text('in', 'cert', 'SPKI 1 PRIVATEOID Ym9ndXM=')
560 self.assertEqual(rd.to_text(), 'SPKI 1 PRIVATEOID Ym9ndXM=')
561
562 def test_CERT_algorithm(self):
563 rd = dns.rdata.from_text('in', 'cert', 'SPKI 1 0 Ym9ndXM=')
564 self.assertEqual(rd.algorithm, 0)
565 with self.assertRaises(dns.exception.SyntaxError):
566 dns.rdata.from_text('in', 'cert', 'SPKI 1 -1 Ym9ndXM=')
567 with self.assertRaises(dns.exception.SyntaxError):
568 dns.rdata.from_text('in', 'cert', 'SPKI 1 256 Ym9ndXM=')
569 with self.assertRaises(dns.exception.SyntaxError):
570 dns.rdata.from_text('in', 'cert', 'SPKI 1 BOGUS Ym9ndXM=')
571
572 def test_bad_URI_text(self):
573 # empty target
574 with self.assertRaises(dns.exception.SyntaxError):
575 dns.rdata.from_text('in', 'uri', '10 1 ""')
576 # no target
577 with self.assertRaises(dns.exception.SyntaxError):
578 dns.rdata.from_text('in', 'uri', '10 1')
579
580 def test_bad_URI_wire(self):
581 wire = bytes.fromhex('000a0001')
582 with self.assertRaises(dns.exception.FormError):
583 dns.rdata.from_wire('in', 'uri', wire, 0, 4)
584
585 def test_bad_NSAP_text(self):
586 # does not start with 0x
587 with self.assertRaises(dns.exception.SyntaxError):
588 dns.rdata.from_text('in', 'nsap', '0y4700')
589 # odd hex string length
590 with self.assertRaises(dns.exception.SyntaxError):
591 dns.rdata.from_text('in', 'nsap', '0x470')
592
593 def test_bad_CAA_text(self):
594 # tag too long
595 with self.assertRaises(dns.exception.SyntaxError):
596 dns.rdata.from_text('in', 'caa',
597 '0 ' + 'a' * 256 + ' "ca.example.net"')
598 # tag not alphanumeric
599 with self.assertRaises(dns.exception.SyntaxError):
600 dns.rdata.from_text('in', 'caa',
601 '0 a-b "ca.example.net"')
602
603 def test_bad_HIP_text(self):
604 # hit too long
605 with self.assertRaises(dns.exception.SyntaxError):
606 dns.rdata.from_text('in', 'hip',
607 '2 ' +
608 '00' * 256 +
609 ' Ym9ndXM=')
610
611 def test_bad_sigtime(self):
612 try:
613 dns.rdata.from_text('in', 'rrsig',
614 'NSEC 1 3 3600 ' +
615 '202001010000000 20030101000000 ' +
616 '2143 foo Ym9ndXM=')
617 self.assertTrue(False) # should not happen
618 except dns.exception.SyntaxError as e:
619 self.assertIsInstance(e.__cause__,
620 dns.rdtypes.ANY.RRSIG.BadSigTime)
621 try:
622 dns.rdata.from_text('in', 'rrsig',
623 'NSEC 1 3 3600 ' +
624 '20200101000000 2003010100000 ' +
625 '2143 foo Ym9ndXM=')
626 self.assertTrue(False) # should not happen
627 except dns.exception.SyntaxError as e:
628 self.assertIsInstance(e.__cause__,
629 dns.rdtypes.ANY.RRSIG.BadSigTime)
630
631 def test_empty_TXT(self):
632 # hit too long
633 with self.assertRaises(dns.exception.SyntaxError):
634 dns.rdata.from_text('in', 'txt', '')
635
636 def test_too_long_TXT(self):
637 # hit too long
638 with self.assertRaises(dns.exception.SyntaxError):
639 dns.rdata.from_text('in', 'txt', 'a' * 256)
640
641 def equal_smimea(self, a, b):
642 a = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SMIMEA, a)
643 b = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SMIMEA, b)
644 self.assertEqual(a, b)
645
646 def test_good_SMIMEA(self):
647 self.equal_smimea('3 0 1 aabbccddeeff', '3 0 01 AABBCCDDEEFF')
648
649 def test_bad_SMIMEA(self):
650 with self.assertRaises(dns.exception.SyntaxError):
651 dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.SMIMEA, '1 1 1 aGVsbG8gd29ybGQh')
652
653 def test_DNSKEY_chunking(self):
654 inputs = ( # each with chunking as given by dig, unusual chunking, and no chunking
655 # example 1
656 (
657 '257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryq uB78Pyk/NTEoai5bxoipVQQXzHlzyg==',
658 '257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocK mnS1iDSFZNORnQuHKtJ9Wpyz+kNryquB78Pyk/ NTEoai5bxoipVQQXzHlzyg==',
659 '257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iDSFZNORnQuHKtJ9Wpyz+kNryquB78Pyk/NTEoai5bxoipVQQXzHlzyg==',
660 ),
661 # example 2
662 (
663 '257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/ qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGr CHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll 96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPri ec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAst bxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6as lO7jXv16Gws=',
664 '257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeq Hy9mvL5qGQTuaG5TSrNqEA R6b/qvxDx6my4JmEmjUPA1JeEI9Y fTUieMr2UZflu7aIbZFLw0vqiYrywCGrC HXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7 xXiP3U5Ll 96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPriec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAst bxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6as lO7jXv16Gws=',
665 '257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGrCHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPriec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAstbxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6aslO7jXv16Gws=',
666 ),
667 # example 3
668 (
669 '256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+ 1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mx t6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLK l3D0L/cD',
670 '256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5 AgB/2jmdR/+1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mxt6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+ TLKl3D0L/cD',
671 '256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mxt6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLKl3D0L/cD',
672 ),
673 )
674 output_map = {
675 32: (
676 '257 3 13 aCoEWYBBVsP9Fek2oC8yqU8ocKmnS1iD SFZNORnQuHKtJ9Wpyz+kNryquB78Pyk/ NTEoai5bxoipVQQXzHlzyg==',
677 '257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy 9mvL5qGQTuaG5TSrNqEAR6b/qvxDx6my 4JmEmjUPA1JeEI9YfTUieMr2UZflu7aI bZFLw0vqiYrywCGrCHXLalOrEOmrvAxL vq4vHtuTlH7JIszzYBSes8g1vle6KG7x XiP3U5Ll96Qiu6bZ31rlMQSPB20xbqJJ h6psNSrQs41QvdcXAej+K2Hl1Wd8kPri ec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFa W2m7N/Wy4qcFU13roWKDEAstbxH5CHPo BfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lv u9TAiZPc0oysY6aslO7jXv16Gws=',
678 '256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5O fv4akjQGN2zY5AgB/2jmdR/+1PvXFqzK CAGJv4wjABEBNWLLFm7ew1hHMDZEKVL1 7aml0EBKI6Dsz6Mxt6n7ScvLtHaFRKax T4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0 P+2F+TLKl3D0L/cD',
679 ),
680 56: (t[0] for t in inputs),
681 0: (t[0][:12] + t[0][12:].replace(' ', '') for t in inputs)
682 }
683
684 for chunksize, outputs in output_map.items():
685 for input, output in zip(inputs, outputs):
686 for input_variation in input:
687 rr = dns.rdata.from_text('IN', 'DNSKEY', input_variation)
688 new_text = rr.to_text(chunksize=chunksize)
689 self.assertEqual(output, new_text)
690
691
692 class UtilTestCase(unittest.TestCase):
693
694 def test_Gateway_bad_type0(self):
695 with self.assertRaises(SyntaxError):
696 dns.rdtypes.util.Gateway(0, 'bad.')
697
698 def test_Gateway_bad_type3(self):
699 with self.assertRaises(SyntaxError):
700 dns.rdtypes.util.Gateway(3, 'bad.')
701
702 def test_Gateway_type4(self):
703 with self.assertRaises(SyntaxError):
704 dns.rdtypes.util.Gateway(4)
705 with self.assertRaises(dns.exception.FormError):
706 dns.rdtypes.util.Gateway.from_wire_parser(4, None)
707
708 def test_Bitmap(self):
709 b = dns.rdtypes.util.Bitmap
710 tok = dns.tokenizer.Tokenizer('A MX')
711 windows = b.from_text(tok).windows
712 ba = bytearray()
713 ba.append(0x40) # bit 1, for A
714 ba.append(0x01) # bit 15, for MX
715 self.assertEqual(windows, [(0, bytes(ba))])
716
717 def test_Bitmap_with_duplicate_types(self):
718 b = dns.rdtypes.util.Bitmap
719 tok = dns.tokenizer.Tokenizer('A MX A A MX')
720 windows = b.from_text(tok).windows
721 ba = bytearray()
722 ba.append(0x40) # bit 1, for A
723 ba.append(0x01) # bit 15, for MX
724 self.assertEqual(windows, [(0, bytes(ba))])
725
726 def test_Bitmap_with_out_of_order_types(self):
727 b = dns.rdtypes.util.Bitmap
728 tok = dns.tokenizer.Tokenizer('MX A')
729 windows = b.from_text(tok).windows
730 ba = bytearray()
731 ba.append(0x40) # bit 1, for A
732 ba.append(0x01) # bit 15, for MX
733 self.assertEqual(windows, [(0, bytes(ba))])
734
735 def test_Bitmap_zero_padding_works(self):
736 b = dns.rdtypes.util.Bitmap
737 tok = dns.tokenizer.Tokenizer('SRV')
738 windows = b.from_text(tok).windows
739 ba = bytearray()
740 ba.append(0)
741 ba.append(0)
742 ba.append(0)
743 ba.append(0)
744 ba.append(0x40) # bit 33, for SRV
745 self.assertEqual(windows, [(0, bytes(ba))])
746
747 def test_Bitmap_has_type_0_set(self):
748 b = dns.rdtypes.util.Bitmap
749 with self.assertRaises(dns.exception.SyntaxError):
750 tok = dns.tokenizer.Tokenizer('NONE A MX')
751 b.from_text(tok)
752
753 def test_Bitmap_empty_window_not_written(self):
754 b = dns.rdtypes.util.Bitmap
755 tok = dns.tokenizer.Tokenizer('URI CAA') # types 256 and 257
756 windows = b.from_text(tok).windows
757 ba = bytearray()
758 ba.append(0xc0) # bits 0 and 1 in window 1
759 self.assertEqual(windows, [(1, bytes(ba))])
760
761 def test_Bitmap_ok_parse(self):
762 parser = dns.wire.Parser(b'\x00\x01\x40')
763 b = dns.rdtypes.util.Bitmap([])
764 windows = b.from_wire_parser(parser).windows
765 self.assertEqual(windows, [(0, b'@')])
766
767 def test_Bitmap_0_length_window_parse(self):
768 parser = dns.wire.Parser(b'\x00\x00')
769 with self.assertRaises(ValueError):
770 b = dns.rdtypes.util.Bitmap([])
771 b.from_wire_parser(parser)
772
773 def test_Bitmap_too_long_parse(self):
774 parser = dns.wire.Parser(b'\x00\x21' + b'\x01' * 33)
775 with self.assertRaises(ValueError):
776 b = dns.rdtypes.util.Bitmap([])
777 b.from_wire_parser(parser)
778
779 def test_compressed_in_generic_is_bad(self):
780 with self.assertRaises(dns.exception.SyntaxError):
781 dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.MX,
782 r'\# 4 000aC000')
783
784 def test_rdataset_ttl_conversion(self):
785 rds1 = dns.rdataset.from_text('in', 'a', 300, '10.0.0.1')
786 self.assertEqual(rds1.ttl, 300)
787 rds2 = dns.rdataset.from_text('in', 'a', '5m', '10.0.0.1')
788 self.assertEqual(rds2.ttl, 300)
789 with self.assertRaises(ValueError):
790 dns.rdataset.from_text('in', 'a', 1.6, '10.0.0.1')
791 with self.assertRaises(dns.ttl.BadTTL):
792 dns.rdataset.from_text('in', 'a', '10.0.0.1', '10.0.0.2')
793
794
795 Rdata = dns.rdata.Rdata
796
797
798 class RdataConvertersTestCase(unittest.TestCase):
799 def test_as_name(self):
800 n = dns.name.from_text('hi')
801 self.assertEqual(Rdata._as_name(n), n)
802 self.assertEqual(Rdata._as_name('hi'), n)
803 with self.assertRaises(ValueError):
804 Rdata._as_name(100)
805
806 def test_as_uint8(self):
807 self.assertEqual(Rdata._as_uint8(0), 0)
808 with self.assertRaises(ValueError):
809 Rdata._as_uint8('hi')
810 with self.assertRaises(ValueError):
811 Rdata._as_uint8(-1)
812 with self.assertRaises(ValueError):
813 Rdata._as_uint8(256)
814
815 def test_as_uint16(self):
816 self.assertEqual(Rdata._as_uint16(0), 0)
817 with self.assertRaises(ValueError):
818 Rdata._as_uint16('hi')
819 with self.assertRaises(ValueError):
820 Rdata._as_uint16(-1)
821 with self.assertRaises(ValueError):
822 Rdata._as_uint16(65536)
823
824 def test_as_uint32(self):
825 self.assertEqual(Rdata._as_uint32(0), 0)
826 with self.assertRaises(ValueError):
827 Rdata._as_uint32('hi')
828 with self.assertRaises(ValueError):
829 Rdata._as_uint32(-1)
830 with self.assertRaises(ValueError):
831 Rdata._as_uint32(2 ** 32)
832
833 def test_as_uint48(self):
834 self.assertEqual(Rdata._as_uint48(0), 0)
835 with self.assertRaises(ValueError):
836 Rdata._as_uint48('hi')
837 with self.assertRaises(ValueError):
838 Rdata._as_uint48(-1)
839 with self.assertRaises(ValueError):
840 Rdata._as_uint48(2 ** 48)
841
842 def test_as_int(self):
843 self.assertEqual(Rdata._as_int(0, 0, 10), 0)
844 with self.assertRaises(ValueError):
845 Rdata._as_int('hi', 0, 10)
846 with self.assertRaises(ValueError):
847 Rdata._as_int(-1, 0, 10)
848 with self.assertRaises(ValueError):
849 Rdata._as_int(11, 0, 10)
850
851 def test_as_bool(self):
852 self.assertEqual(Rdata._as_bool(True), True)
853 self.assertEqual(Rdata._as_bool(False), False)
854 with self.assertRaises(ValueError):
855 Rdata._as_bool('hi')
856
857 def test_as_ttl(self):
858 self.assertEqual(Rdata._as_ttl(300), 300)
859 self.assertEqual(Rdata._as_ttl('5m'), 300)
860 self.assertEqual(Rdata._as_ttl(dns.ttl.MAX_TTL), dns.ttl.MAX_TTL)
861 with self.assertRaises(dns.ttl.BadTTL):
862 Rdata._as_ttl('hi')
863 with self.assertRaises(ValueError):
864 Rdata._as_ttl(1.9)
865 with self.assertRaises(ValueError):
866 Rdata._as_ttl(dns.ttl.MAX_TTL + 1)
867
417868 if __name__ == '__main__':
418869 unittest.main()
8080 self.assertRaises(ValueError,
8181 lambda: dns.rdataset.from_rdata_list(300, []))
8282
83 def testToTextNoName(self):
84 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.1')
85 text = rds.to_text()
86 self.assertEqual(text, '300 IN A 10.0.0.1')
87
88 def testToTextOverrideClass(self):
89 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.1')
90 text = rds.to_text(override_rdclass=dns.rdataclass.NONE)
91 self.assertEqual(text, '300 NONE A 10.0.0.1')
92
93 def testRepr(self):
94 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.1')
95 self.assertEqual(repr(rds), "<DNS IN A rdataset: [<10.0.0.1>]>")
96
97 def testTruncatedRepr(self):
98 rds = dns.rdataset.from_text('in', 'txt', 300,
99 'a' * 200)
100 # * 99 not * 100 below as the " counts as one of the 100 chars
101 self.assertEqual(repr(rds),
102 '<DNS IN TXT rdataset: [<"' + 'a' * 99 + '...>]>')
103
104 def testStr(self):
105 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.1')
106 self.assertEqual(str(rds), "300 IN A 10.0.0.1")
107
108 def testMultilineToText(self):
109 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.1', '10.0.0.2')
110 self.assertEqual(rds.to_text(), "300 IN A 10.0.0.1\n300 IN A 10.0.0.2")
111
112 def testCoveredRepr(self):
113 rds = dns.rdataset.from_text('in', 'rrsig', 300,
114 'NSEC 1 3 3600 ' +
115 '20190101000000 20030101000000 ' +
116 '2143 foo Ym9ndXM=')
117 # Using startswith as I don't care about the repr of the rdata,
118 # just the covers
119 self.assertTrue(repr(rds).startswith(
120 '<DNS IN RRSIG(NSEC) rdataset:'))
121
122
123 class ImmutableRdatasetTestCase(unittest.TestCase):
124
125 def test_basic(self):
126 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.1', '10.0.0.2')
127 rd = dns.rdata.from_text('in', 'a', '10.0.0.3')
128 irds = dns.rdataset.ImmutableRdataset(rds)
129 with self.assertRaises(TypeError):
130 irds.update_ttl(100)
131 with self.assertRaises(TypeError):
132 irds.add(rd, 300)
133 with self.assertRaises(TypeError):
134 irds.union_update(rds)
135 with self.assertRaises(TypeError):
136 irds.intersection_update(rds)
137 with self.assertRaises(TypeError):
138 irds.update(rds)
139 with self.assertRaises(TypeError):
140 irds += rds
141 with self.assertRaises(TypeError):
142 irds -= rds
143 with self.assertRaises(TypeError):
144 irds &= rds
145 with self.assertRaises(TypeError):
146 irds |= rds
147 with self.assertRaises(TypeError):
148 del irds[0]
149 with self.assertRaises(TypeError):
150 irds.clear()
151
152 def test_cloning(self):
153 rds1 = dns.rdataset.from_text('in', 'a', 300, '10.0.0.1', '10.0.0.2')
154 rds1 = dns.rdataset.ImmutableRdataset(rds1)
155 rds2 = dns.rdataset.from_text('in', 'a', 300, '10.0.0.2', '10.0.0.3')
156 rds2 = dns.rdataset.ImmutableRdataset(rds2)
157 expected = dns.rdataset.from_text('in', 'a', 300, '10.0.0.2')
158 intersection = rds1.intersection(rds2)
159 self.assertEqual(intersection, expected)
160
83161 if __name__ == '__main__':
84162 unittest.main()
0 # -*- coding: utf-8
1 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
2
3 # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
4 #
5 # Permission to use, copy, modify, and distribute this software and its
6 # documentation for any purpose with or without fee is hereby granted,
7 # provided that the above copyright notice and this permission notice
8 # appear in all copies.
9 #
10 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
11 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
13 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
16 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
18 import unittest
19 import base64
20
21 import dns.name
22 import dns.zone
23 import dns.rdtypes.ANY.TKEY
24 from dns.rdataclass import RdataClass
25 from dns.rdatatype import RdataType
26
27
28 class RdtypeAnyTKeyTestCase(unittest.TestCase):
29 tkey_rdata_text = 'gss-tsig. 1594203795 1594206664 3 0 KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY OTHEROTHEROTHEROTHEROTHEROTHEROT'
30 tkey_rdata_text_no_other = 'gss-tsig. 1594203795 1594206664 3 0 KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY'
31
32 def testTextOptionalData(self):
33 # construct the rdata from text and extract the TKEY
34 tkey = dns.rdata.from_text(
35 RdataClass.ANY, RdataType.TKEY,
36 RdtypeAnyTKeyTestCase.tkey_rdata_text, origin='.')
37 self.assertEqual(type(tkey), dns.rdtypes.ANY.TKEY.TKEY)
38
39 # go to text and compare
40 tkey_out_text = tkey.to_text(relativize=False)
41 self.assertEqual(tkey_out_text,
42 RdtypeAnyTKeyTestCase.tkey_rdata_text)
43
44 def testTextNoOptionalData(self):
45 # construct the rdata from text and extract the TKEY
46 tkey = dns.rdata.from_text(
47 RdataClass.ANY, RdataType.TKEY,
48 RdtypeAnyTKeyTestCase.tkey_rdata_text_no_other, origin='.')
49 self.assertEqual(type(tkey), dns.rdtypes.ANY.TKEY.TKEY)
50
51 # go to text and compare
52 tkey_out_text = tkey.to_text(relativize=False)
53 self.assertEqual(tkey_out_text,
54 RdtypeAnyTKeyTestCase.tkey_rdata_text_no_other)
55
56 def testWireOptionalData(self):
57 key = base64.b64decode('KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY')
58 other = base64.b64decode('OTHEROTHEROTHEROTHEROTHEROTHEROT')
59
60 # construct the TKEY and compare the text output
61 tkey = dns.rdtypes.ANY.TKEY.TKEY(dns.rdataclass.ANY,
62 dns.rdatatype.TKEY,
63 dns.name.from_text('gss-tsig.'),
64 1594203795, 1594206664,
65 3, 0, key, other)
66 self.assertEqual(tkey.to_text(relativize=False),
67 RdtypeAnyTKeyTestCase.tkey_rdata_text)
68
69 # go to/from wire and compare the text output
70 wire = tkey.to_wire()
71 tkey_out_wire = dns.rdata.from_wire(dns.rdataclass.ANY,
72 dns.rdatatype.TKEY,
73 wire, 0, len(wire))
74 self.assertEqual(tkey_out_wire.to_text(relativize=False),
75 RdtypeAnyTKeyTestCase.tkey_rdata_text)
76
77 def testWireNoOptionalData(self):
78 key = base64.b64decode('KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY')
79
80 # construct the TKEY with no 'other' data and compare the text output
81 tkey = dns.rdtypes.ANY.TKEY.TKEY(dns.rdataclass.ANY,
82 dns.rdatatype.TKEY,
83 dns.name.from_text('gss-tsig.'),
84 1594203795, 1594206664,
85 3, 0, key)
86 self.assertEqual(tkey.to_text(relativize=False),
87 RdtypeAnyTKeyTestCase.tkey_rdata_text_no_other)
88
89 # go to/from wire and compare the text output
90 wire = tkey.to_wire()
91 tkey_out_wire = dns.rdata.from_wire(dns.rdataclass.ANY,
92 dns.rdatatype.TKEY,
93 wire, 0, len(wire))
94 self.assertEqual(tkey_out_wire.to_text(relativize=False),
95 RdtypeAnyTKeyTestCase.tkey_rdata_text_no_other)
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
02 import unittest
13
24 import dns.flags
8082 '. . 1 2 3 4 300'), 300)
8183 if nxdomain:
8284 r.set_rcode(dns.rcode.NXDOMAIN)
85 return r
86
87 def make_long_chain_response(self, q, count):
88 r = dns.message.make_response(q)
89 name = self.qname
90 for i in range(count):
91 rrs = r.get_rrset(r.answer, name, dns.rdataclass.IN,
92 dns.rdatatype.CNAME, create=True)
93 tname = dns.name.from_text(f'target{i}.')
94 rrs.add(dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.CNAME,
95 str(tname)), 300)
96 name = tname
97 rrs = r.get_rrset(r.answer, name, dns.rdataclass.IN,
98 dns.rdatatype.A, create=True)
99 rrs.add(dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A,
100 '10.0.0.1'), 300)
83101 return r
84102
85103 def test_next_request_cache_hit(self):
352370 self.assertTrue(answer is None)
353371 self.assertTrue(done)
354372
373 def test_query_result_nxdomain_but_has_answer(self):
374 q = dns.message.make_query(self.qname, dns.rdatatype.A)
375 r = self.make_address_response(q)
376 r.set_rcode(dns.rcode.NXDOMAIN)
377 (_, _) = self.resn.next_request()
378 (nameserver, _, _, _) = self.resn.next_nameserver()
379 (answer, done) = self.resn.query_result(r, None)
380 self.assertIsNone(answer)
381 self.assertFalse(done)
382 self.assertTrue(nameserver not in self.resn.nameservers)
383
384 def test_query_result_chain_not_too_long(self):
385 q = dns.message.make_query(self.qname, dns.rdatatype.A)
386 r = self.make_long_chain_response(q, 15)
387 (_, _) = self.resn.next_request()
388 (_, _, _, _) = self.resn.next_nameserver()
389 (answer, done) = self.resn.query_result(r, None)
390 self.assertIsNotNone(answer)
391 self.assertTrue(done)
392
393 def test_query_result_chain_too_long(self):
394 q = dns.message.make_query(self.qname, dns.rdatatype.A)
395 r = self.make_long_chain_response(q, 16)
396 (_, _) = self.resn.next_request()
397 (nameserver, _, _, _) = self.resn.next_nameserver()
398 (answer, done) = self.resn.query_result(r, None)
399 self.assertIsNone(answer)
400 self.assertFalse(done)
401 self.assertTrue(nameserver not in self.resn.nameservers)
402
355403 def test_query_result_nxdomain_cached(self):
356404 self.resolver.cache = dns.resolver.Cache()
357405 q = dns.message.make_query(self.qname, dns.rdatatype.A)
1515 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1616
1717 from io import StringIO
18 import select
18 import selectors
1919 import sys
2020 import socket
2121 import time
2222 import unittest
23
24 import pytest
2325
2426 import dns.e164
2527 import dns.message
2729 import dns.rdataclass
2830 import dns.rdatatype
2931 import dns.resolver
32 import dns.tsig
33 import dns.tsigkeyring
3034
3135 # Some tests require the internet to be available to run, so let's
3236 # skip those if it's not there.
9195 options rotate
9296 """
9397
98 unknown_and_bad_directives = """
99 nameserver 10.0.0.1
100 foo bar
101 bad
102 """
103
104 unknown_option = """
105 nameserver 10.0.0.1
106 option foobar
107 """
108
94109 message_text = """id 1234
95110 opcode QUERY
96111 rcode NOERROR
97112 flags QR AA RD
98113 ;QUESTION
99114 example. IN A
115 ;ANSWER
116 example. 1 IN A 10.0.0.1
117 ;AUTHORITY
118 ;ADDITIONAL
119 """
120
121 message_text_mx = """id 1234
122 opcode QUERY
123 rcode NOERROR
124 flags QR AA RD
125 ;QUESTION
126 example. IN MX
100127 ;ANSWER
101128 example. 1 IN A 10.0.0.1
102129 ;AUTHORITY
147174 self.expiration = expiration
148175
149176
177 class FakeTime:
178 # Mock the clock!
179 def __init__(self, now=None, want_fake=True):
180 if now is None:
181 now = time.time()
182 self.now = now
183 self.saved_time = time.time
184 self.want_fake = want_fake
185
186 def __enter__(self):
187 if self.want_fake:
188 time.time = self.time
189 return self
190
191 def __exit__(self, exc_type, exc_value, traceback):
192 if self.want_fake:
193 time.time = self.saved_time
194 return False
195
196 def time(self):
197 if self.want_fake:
198 return self.now
199 else:
200 return time.time()
201
202 def sleep(self, offset):
203 if self.want_fake:
204 self.now += offset
205 else:
206 time.sleep(offset)
207
208
150209 class BaseResolverTests(unittest.TestCase):
151210
152211 def testRead(self):
165224 self.assertEqual(r.timeout, 1)
166225 self.assertEqual(r.ndots, 2)
167226 self.assertEqual(r.edns, 0)
227 self.assertEqual(r.payload, dns.message.DEFAULT_EDNS_PAYLOAD)
168228
169229 def testReadOptionsBadTimeouts(self):
170230 f = StringIO(bad_timeout_1)
196256 with self.assertRaises(dns.resolver.NoResolverConfiguration):
197257 r.read_resolv_conf(f)
198258
259 def testReadUnknownDirective(self):
260 # The real test here is ignoring the unknown directive and the bad
261 # directive.
262 f = StringIO(unknown_and_bad_directives)
263 r = dns.resolver.Resolver(configure=False)
264 r.read_resolv_conf(f)
265 self.assertEqual(r.nameservers, ['10.0.0.1'])
266
267 def testReadUnknownOption(self):
268 # The real test here is ignoring the unknown option
269 f = StringIO(unknown_option)
270 r = dns.resolver.Resolver(configure=False)
271 r.read_resolv_conf(f)
272 self.assertEqual(r.nameservers, ['10.0.0.1'])
273
199274 def testCacheExpiration(self):
275 with FakeTime() as fake_time:
276 message = dns.message.from_text(message_text)
277 name = dns.name.from_text('example.')
278 answer = dns.resolver.Answer(name, dns.rdatatype.A,
279 dns.rdataclass.IN, message)
280 cache = dns.resolver.Cache()
281 cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer)
282 fake_time.sleep(2)
283 self.assertTrue(cache.get((name, dns.rdatatype.A,
284 dns.rdataclass.IN))
285 is None)
286
287 def testCacheCleaning(self):
288 with FakeTime() as fake_time:
289 message = dns.message.from_text(message_text)
290 name = dns.name.from_text('example.')
291 answer = dns.resolver.Answer(name, dns.rdatatype.A,
292 dns.rdataclass.IN, message)
293 cache = dns.resolver.Cache(cleaning_interval=1.0)
294 cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer)
295 fake_time.sleep(2)
296 cache._maybe_clean()
297 self.assertTrue(cache.data.get((name, dns.rdatatype.A,
298 dns.rdataclass.IN))
299 is None)
300
301 def testCacheNonCleaning(self):
302 with FakeTime() as fake_time:
303 message = dns.message.from_text(message_text)
304 name = dns.name.from_text('example.')
305 answer = dns.resolver.Answer(name, dns.rdatatype.A,
306 dns.rdataclass.IN, message)
307 # override TTL as we're testing non-cleaning
308 answer.expiration = fake_time.time() + 100
309 cache = dns.resolver.Cache(cleaning_interval=1.0)
310 cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer)
311 fake_time.sleep(1.1)
312 self.assertEqual(cache.get((name, dns.rdatatype.A,
313 dns.rdataclass.IN)), answer)
314
315 def testIndexErrorOnEmptyRRsetAccess(self):
316 def bad():
317 message = dns.message.from_text(message_text_mx)
318 name = dns.name.from_text('example.')
319 answer = dns.resolver.Answer(name, dns.rdatatype.MX,
320 dns.rdataclass.IN, message)
321 return answer[0]
322 self.assertRaises(IndexError, bad)
323
324 def testIndexErrorOnEmptyRRsetDelete(self):
325 def bad():
326 message = dns.message.from_text(message_text_mx)
327 name = dns.name.from_text('example.')
328 answer = dns.resolver.Answer(name, dns.rdatatype.MX,
329 dns.rdataclass.IN, message)
330 del answer[0]
331 self.assertRaises(IndexError, bad)
332
333 def testRRsetDelete(self):
200334 message = dns.message.from_text(message_text)
201335 name = dns.name.from_text('example.')
202 answer = dns.resolver.Answer(name, dns.rdatatype.A, dns.rdataclass.IN,
203 message)
204 cache = dns.resolver.Cache()
205 cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer)
206 time.sleep(2)
207 self.assertTrue(cache.get((name, dns.rdatatype.A, dns.rdataclass.IN))
208 is None)
209
210 def testCacheCleaning(self):
211 message = dns.message.from_text(message_text)
212 name = dns.name.from_text('example.')
213 answer = dns.resolver.Answer(name, dns.rdatatype.A, dns.rdataclass.IN,
214 message)
215 cache = dns.resolver.Cache(cleaning_interval=1.0)
216 cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer)
217 time.sleep(2)
218 self.assertTrue(cache.get((name, dns.rdatatype.A, dns.rdataclass.IN))
219 is None)
220
221 def testIndexErrorOnEmptyRRsetAccess(self):
222 def bad():
223 message = dns.message.from_text(message_text)
224 name = dns.name.from_text('example.')
225 answer = dns.resolver.Answer(name, dns.rdatatype.MX,
226 dns.rdataclass.IN, message,
227 False)
228 return answer[0]
229 self.assertRaises(IndexError, bad)
230
231 def testIndexErrorOnEmptyRRsetDelete(self):
232 def bad():
233 message = dns.message.from_text(message_text)
234 name = dns.name.from_text('example.')
235 answer = dns.resolver.Answer(name, dns.rdatatype.MX,
236 dns.rdataclass.IN, message,
237 False)
238 del answer[0]
239 self.assertRaises(IndexError, bad)
336 answer = dns.resolver.Answer(name, dns.rdatatype.A,
337 dns.rdataclass.IN, message)
338 del answer[0]
339 self.assertEqual(len(answer), 0)
240340
241341 def testLRUReplace(self):
242342 cache = dns.resolver.LRUCache(4)
279379 is None)
280380
281381 def testLRUExpiration(self):
282 cache = dns.resolver.LRUCache(4)
283 for i in range(0, 4):
284 name = dns.name.from_text('example%d.' % i)
285 answer = FakeAnswer(time.time() + 1)
286 cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer)
287 time.sleep(2)
288 for i in range(0, 4):
289 name = dns.name.from_text('example%d.' % i)
290 self.assertTrue(cache.get((name, dns.rdatatype.A,
291 dns.rdataclass.IN))
292 is None)
382 with FakeTime() as fake_time:
383 cache = dns.resolver.LRUCache(4)
384 for i in range(0, 4):
385 name = dns.name.from_text('example%d.' % i)
386 answer = FakeAnswer(time.time() + 1)
387 cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer)
388 fake_time.sleep(2)
389 for i in range(0, 4):
390 name = dns.name.from_text('example%d.' % i)
391 self.assertTrue(cache.get((name, dns.rdatatype.A,
392 dns.rdataclass.IN))
393 is None)
293394
294395 def test_cache_flush(self):
295396 name1 = dns.name.from_text('name1')
296397 name2 = dns.name.from_text('name2')
398 name3 = dns.name.from_text('name3')
297399 basic_cache = dns.resolver.Cache()
298400 lru_cache = dns.resolver.LRUCache(100)
299401 for cache in [basic_cache, lru_cache]:
301403 answer2 = FakeAnswer(time.time() + 10)
302404 cache.put((name1, dns.rdatatype.A, dns.rdataclass.IN), answer1)
303405 cache.put((name2, dns.rdatatype.A, dns.rdataclass.IN), answer2)
406 canswer = cache.get((name1, dns.rdatatype.A, dns.rdataclass.IN))
407 self.assertTrue(canswer is answer1)
408 canswer = cache.get((name2, dns.rdatatype.A, dns.rdataclass.IN))
409 self.assertTrue(canswer is answer2)
410 # explicit flush of nonexistent key, just to exercise the branch
411 cache.flush((name3, dns.rdatatype.A, dns.rdataclass.IN))
304412 canswer = cache.get((name1, dns.rdatatype.A, dns.rdataclass.IN))
305413 self.assertTrue(canswer is answer1)
306414 canswer = cache.get((name2, dns.rdatatype.A, dns.rdataclass.IN))
346454 self.assertFalse(on_lru_list(cache, key, answer1))
347455 self.assertTrue(on_lru_list(cache, key, answer2))
348456
457 def test_cache_stats(self):
458 caches = [dns.resolver.Cache(), dns.resolver.LRUCache(4)]
459 key1 = (dns.name.from_text('key1.'), dns.rdatatype.A, dns.rdataclass.IN)
460 key2 = (dns.name.from_text('key2.'), dns.rdatatype.A, dns.rdataclass.IN)
461 for cache in caches:
462 answer1 = FakeAnswer(time.time() + 10)
463 answer2 = FakeAnswer(10) # expired!
464 a = cache.get(key1)
465 self.assertIsNone(a)
466 self.assertEqual(cache.hits(), 0)
467 self.assertEqual(cache.misses(), 1)
468 if isinstance(cache, dns.resolver.LRUCache):
469 self.assertEqual(cache.get_hits_for_key(key1), 0)
470 cache.put(key1, answer1)
471 a = cache.get(key1)
472 self.assertIs(a, answer1)
473 self.assertEqual(cache.hits(), 1)
474 self.assertEqual(cache.misses(), 1)
475 if isinstance(cache, dns.resolver.LRUCache):
476 self.assertEqual(cache.get_hits_for_key(key1), 1)
477 cache.put(key2, answer2)
478 a = cache.get(key2)
479 self.assertIsNone(a)
480 self.assertEqual(cache.hits(), 1)
481 self.assertEqual(cache.misses(), 2)
482 if isinstance(cache, dns.resolver.LRUCache):
483 self.assertEqual(cache.get_hits_for_key(key2), 0)
484 stats = cache.get_statistics_snapshot()
485 self.assertEqual(stats.hits, 1)
486 self.assertEqual(stats.misses, 2)
487 cache.reset_statistics()
488 stats = cache.get_statistics_snapshot()
489 self.assertEqual(stats.hits, 0)
490 self.assertEqual(stats.misses, 0)
491
349492 def testEmptyAnswerSection(self):
350493 # TODO: dangling_cname_0_message_text was the only sample message
351494 # with an empty answer section. Other than that it doesn't
372515 qnames = res._get_qnames_to_try(qname, True)
373516 self.assertEqual(qnames,
374517 [dns.name.from_text(x) for x in
375 ['www.dnspython.org', 'www.dnspython.net']])
518 ['www.dnspython.org', 'www.dnspython.net', 'www.']])
376519 qnames = res._get_qnames_to_try(qname, False)
377520 self.assertEqual(qnames,
378521 [dns.name.from_text('www.')])
386529 qnames = res._get_qnames_to_try(qname, None)
387530 self.assertEqual(qnames,
388531 [dns.name.from_text(x) for x in
389 ['www.dnspython.org', 'www.dnspython.net']])
532 ['www.dnspython.org', 'www.dnspython.net', 'www.']])
533 #
534 # Now test ndots
535 #
536 qname = dns.name.from_text('a.b', None)
537 res.ndots = 1
538 qnames = res._get_qnames_to_try(qname, True)
539 self.assertEqual(qnames,
540 [dns.name.from_text(x) for x in
541 ['a.b', 'a.b.dnspython.org', 'a.b.dnspython.net']])
542 res.ndots = 2
543 qnames = res._get_qnames_to_try(qname, True)
544 self.assertEqual(qnames,
545 [dns.name.from_text(x) for x in
546 ['a.b.dnspython.org', 'a.b.dnspython.net', 'a.b']])
547 qname = dns.name.from_text('a.b.c', None)
548 qnames = res._get_qnames_to_try(qname, True)
549 self.assertEqual(qnames,
550 [dns.name.from_text(x) for x in
551 ['a.b.c', 'a.b.c.dnspython.org',
552 'a.b.c.dnspython.net']])
390553
391554 def testSearchListsAbsolute(self):
392555 res = dns.resolver.Resolver(configure=False)
398561 qnames = res._get_qnames_to_try(qname, None)
399562 self.assertEqual(qnames, [qname])
400563
564 def testUseEDNS(self):
565 r = dns.resolver.Resolver(configure=False)
566 r.use_edns(None)
567 self.assertEqual(r.edns, -1)
568 r.use_edns(False)
569 self.assertEqual(r.edns, -1)
570 r.use_edns(True)
571 self.assertEqual(r.edns, 0)
572
573 def testSetFlags(self):
574 flags = dns.flags.CD | dns.flags.RD
575 r = dns.resolver.Resolver(configure=False)
576 r.set_flags(flags)
577 self.assertEqual(r.flags, flags)
578
579 def testUseTSIG(self):
580 keyring = dns.tsigkeyring.from_text(
581 {
582 'keyname.': 'NjHwPsMKjdN++dOfE5iAiQ=='
583 }
584 )
585 r = dns.resolver.Resolver(configure=False)
586 r.use_tsig(keyring)
587 self.assertEqual(r.keyring, keyring)
588 self.assertEqual(r.keyname, None)
589 self.assertEqual(r.keyalgorithm, dns.tsig.default_algorithm)
590
591 keyname = dns.name.from_text('keyname')
592
593
594
401595 @unittest.skipIf(not _network_available, "Internet not reachable")
402596 class LiveResolverTests(unittest.TestCase):
403597 def testZoneForName1(self):
413607 self.assertEqual(zname, ezname)
414608
415609 def testZoneForName3(self):
416 name = dns.name.from_text('dnspython.org.')
417610 ezname = dns.name.from_text('dnspython.org.')
418 zname = dns.resolver.zone_for_name(name)
611 zname = dns.resolver.zone_for_name('dnspython.org.')
419612 self.assertEqual(zname, ezname)
420613
421614 def testZoneForName4(self):
461654 qtype = dns.rdatatype.from_text('A')
462655 def bad():
463656 answer = dns.resolver.resolve(qname, qtype)
464 self.assertRaises(dns.resolver.NXDOMAIN, bad)
657 try:
658 dns.resolver.resolve(qname, qtype)
659 self.assertTrue(False) # should not happen!
660 except dns.resolver.NXDOMAIN as nx:
661 self.assertIn(qname, nx.qnames())
662 self.assertGreaterEqual(len(nx.responses()), 1)
465663
466664 def testResolveCacheHit(self):
467665 res = dns.resolver.Resolver(configure=False)
474672 answer2 = res.resolve('dns.google.', 'A')
475673 self.assertIs(answer2, answer1)
476674
675 def testCanonicalNameNoCNAME(self):
676 cname = dns.name.from_text('www.google.com')
677 self.assertEqual(dns.resolver.canonical_name('www.google.com'), cname)
678
679 def testCanonicalNameCNAME(self):
680 name = dns.name.from_text('www.dnspython.org')
681 cname = dns.name.from_text('dmfrjf4ips8xa.cloudfront.net')
682 self.assertEqual(dns.resolver.canonical_name(name), cname)
683
684 def testCanonicalNameDangling(self):
685 name = dns.name.from_text('dangling-cname.dnspython.org')
686 cname = dns.name.from_text('dangling-target.dnspython.org')
687 self.assertEqual(dns.resolver.canonical_name(name), cname)
688
477689 class PollingMonkeyPatchMixin(object):
478690 def setUp(self):
479 self.__native_polling_backend = dns.query._polling_backend
480 dns.query._set_polling_backend(self.polling_backend())
691 self.__native_selector_class = dns.query._selector_class
692 dns.query._set_selector_class(self.selector_class())
481693
482694 unittest.TestCase.setUp(self)
483695
484696 def tearDown(self):
485 dns.query._set_polling_backend(self.__native_polling_backend)
697 dns.query._set_selector_class(self.__native_selector_class)
486698
487699 unittest.TestCase.tearDown(self)
488700
489701
490702 class SelectResolverTestCase(PollingMonkeyPatchMixin, LiveResolverTests, unittest.TestCase):
491 def polling_backend(self):
492 return dns.query._select_for
493
494
495 if hasattr(select, 'poll'):
703 def selector_class(self):
704 return selectors.SelectSelector
705
706
707 if hasattr(selectors, 'PollSelector'):
496708 class PollResolverTestCase(PollingMonkeyPatchMixin, LiveResolverTests, unittest.TestCase):
497 def polling_backend(self):
498 return dns.query._poll_for
709 def selector_class(self):
710 return selectors.PollSelector
499711
500712
501713 class NXDOMAINExceptionTestCase(unittest.TestCase):
701913 with NaptrNanoNameserver() as na:
702914 res = dns.resolver.Resolver(configure=False)
703915 res.port = na.udp_address[1]
704 res.nameservers = [ na.udp_address[0] ]
916 res.nameservers = [na.udp_address[0]]
705917 answer = dns.e164.query('1650551212', ['e164.arpa'], res)
706918 self.assertEqual(answer[0].order, 0)
707919 self.assertEqual(answer[0].preference, 0)
709921 self.assertEqual(answer[0].service, b'')
710922 self.assertEqual(answer[0].regexp, b'')
711923 self.assertEqual(answer[0].replacement, dns.name.root)
712 def nxdomain():
713 answer = dns.e164.query('0123456789', ['e164.arpa'], res)
714 self.assertRaises(dns.resolver.NXDOMAIN, nxdomain)
924 with self.assertRaises(dns.resolver.NXDOMAIN):
925 dns.e164.query('0123456789', ['e164.arpa'], res)
926
927
928 class AlwaysType3NXDOMAINNanoNameserver(Server):
929
930 def handle(self, request):
931 response = dns.message.make_response(request.message)
932 response.set_rcode(dns.rcode.NXDOMAIN)
933 response.flags |= dns.flags.RA
934 return response
935
936 @unittest.skipIf(not (_network_available and _nanonameserver_available),
937 "Internet and NanoAuth required")
938 class ZoneForNameNoParentTest(unittest.TestCase):
939
940 def testNoRootSOA(self):
941 with AlwaysType3NXDOMAINNanoNameserver() as na:
942 res = dns.resolver.Resolver(configure=False)
943 res.port = na.udp_address[1]
944 res.nameservers = [na.udp_address[0]]
945 with self.assertRaises(dns.resolver.NoRootSOA):
946 dns.resolver.zone_for_name('www.foo.bar.', resolver=res)
947
948
949 class DroppingNanoNameserver(Server):
950
951 def handle(self, request):
952 return None
953
954
955 class FormErrNanoNameserver(Server):
956
957 def handle(self, request):
958 r = dns.message.make_response(request.message)
959 r.set_rcode(dns.rcode.FORMERR)
960 return r
961
962
963 # we use pytest for these so we can have a "slow" mark later if we want to
964 # (right now it's still fast enough we don't really need it)
965
966 @pytest.mark.skipif(not (_network_available and _nanonameserver_available),
967 reason="Internet and NanoAuth required")
968 def testResolverTimeout():
969 with DroppingNanoNameserver() as na:
970 res = dns.resolver.Resolver(configure=False)
971 res.port = na.udp_address[1]
972 res.nameservers = [na.udp_address[0]]
973 res.timeout = 0.2
974 try:
975 lifetime = 1.0
976 a = res.resolve('www.dnspython.org', lifetime=lifetime)
977 assert False # should never happen
978 except dns.resolver.LifetimeTimeout as e:
979 assert e.kwargs['timeout'] >= lifetime
980 # The length of errors can vary based on how slow things are,
981 # but it ought to be > 1, so we assert that.
982 errors = e.kwargs['errors']
983 assert len(errors) > 1
984 for error in errors:
985 assert error[0] == na.udp_address[0] # address
986 assert not error[1] # not TCP
987 assert error[2] == na.udp_address[1] # port
988 assert isinstance(error[3], dns.exception.Timeout) # exception
989
990 def testResolverNoNameservers():
991 with FormErrNanoNameserver() as na:
992 res = dns.resolver.Resolver(configure=False)
993 res.port = na.udp_address[1]
994 res.nameservers = [na.udp_address[0]]
995 try:
996 a = res.resolve('www.dnspython.org')
997 assert False # should never happen
998 except dns.resolver.NoNameservers as e:
999 errors = e.kwargs['errors']
1000 assert len(errors) == 1
1001 for error in errors:
1002 assert error[0] == na.udp_address[0] # address
1003 assert not error[1] # not TCP
1004 assert error[2] == na.udp_address[1] # port
1005 assert error[3] == 'FORMERR'
1515 socket.gethostbyname('dnspython.org')
1616 except socket.gaierror:
1717 _network_available = False
18
1819
1920 @unittest.skipIf(not _network_available, "Internet not reachable")
2021 class OverrideSystemResolverTestCase(unittest.TestCase):
116117 self.assertTrue(False) # should not happen!
117118 except socket.gaierror as e:
118119 self.assertEqual(e.errno, socket.EAI_NONAME)
120
121 def test_getaddrinfo_only_service(self):
122 infos = socket.getaddrinfo(service=53, family=socket.AF_INET,
123 socktype=socket.SOCK_DGRAM,
124 proto=socket.IPPROTO_UDP)
125 self.assertEqual(len(infos), 1)
126 info = infos[0]
127 self.assertEqual(info[0], socket.AF_INET)
128 self.assertEqual(info[1], socket.SOCK_DGRAM)
129 self.assertEqual(info[2], socket.IPPROTO_UDP)
130 self.assertEqual(info[4], ('127.0.0.1', 53))
131
132 def test_unknown_service_fails(self):
133 with self.assertRaises(socket.gaierror):
134 socket.getaddrinfo('dns.google.', 'bogus-service')
135
136 def test_getnameinfo_tcp(self):
137 info = socket.getnameinfo(('8.8.8.8', 53))
138 self.assertEqual(info, ('dns.google', 'domain'))
139
140 def test_getnameinfo_udp(self):
141 info = socket.getnameinfo(('8.8.8.8', 53), socket.NI_DGRAM)
142 self.assertEqual(info, ('dns.google', 'domain'))
143
119144
120145 # Give up on testing this for now as all of the names I've considered
121146 # using for testing are part of CDNs and there is deep magic in
144169 b = socket.gethostbyaddr('2001:4860:4860::8888')
145170 self.assertEqual(a[0], b[0])
146171 self.assertEqual(a[2], b[2])
172
173
174 class FakeResolver:
175 def resolve(self, *args, **kwargs):
176 raise dns.exception.Timeout
177
178
179 class OverrideSystemResolverUsingFakeResolverTestCase(unittest.TestCase):
180
181 def setUp(self):
182 self.res = FakeResolver()
183 dns.resolver.override_system_resolver(self.res)
184
185 def tearDown(self):
186 dns.resolver.restore_system_resolver()
187 self.res = None
188
189 def test_temporary_failure(self):
190 with self.assertRaises(socket.gaierror):
191 socket.getaddrinfo('dns.google')
192
193 # We don't need the fake resolver for the following tests, but we
194 # don't need the live network either, so we're testing here.
195
196 def test_no_host_or_service_fails(self):
197 with self.assertRaises(socket.gaierror):
198 socket.getaddrinfo()
199
200 def test_AI_ADDRCONFIG_fails(self):
201 with self.assertRaises(socket.gaierror):
202 socket.getaddrinfo('dns.google', flags=socket.AI_ADDRCONFIG)
203
204 def test_gethostbyaddr_of_name_fails(self):
205 with self.assertRaises(socket.gaierror):
206 socket.gethostbyaddr('bogus')
207
208
209 @unittest.skipIf(not _network_available, "Internet not reachable")
210 class OverrideSystemResolverUsingDefaultResolverTestCase(unittest.TestCase):
211
212 def setUp(self):
213 self.res = FakeResolver()
214 dns.resolver.override_system_resolver()
215
216 def tearDown(self):
217 dns.resolver.restore_system_resolver()
218 self.res = None
219
220 def test_override(self):
221 self.assertEqual(dns.resolver._resolver, dns.resolver.default_resolver)
7878 self.assertFalse(r1 is r2)
7979 self.assertTrue(r1 == r2)
8080
81 def testMatch1(self):
81 def testFullMatch1(self):
82 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
83 ['10.0.0.1', '10.0.0.2'])
84 self.assertTrue(r1.full_match(r1.name, dns.rdataclass.IN,
85 dns.rdatatype.A, dns.rdatatype.NONE))
86
87 def testFullMatch2(self):
88 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
89 ['10.0.0.1', '10.0.0.2'])
90 r1.deleting = dns.rdataclass.NONE
91 self.assertTrue(r1.full_match(r1.name, dns.rdataclass.IN,
92 dns.rdatatype.A, dns.rdatatype.NONE,
93 dns.rdataclass.NONE))
94
95 def testNoFullMatch1(self):
96 n = dns.name.from_text('bar', None)
97 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
98 ['10.0.0.1', '10.0.0.2'])
99 self.assertFalse(r1.full_match(n, dns.rdataclass.IN,
100 dns.rdatatype.A, dns.rdatatype.NONE,
101 dns.rdataclass.ANY))
102
103 def testNoFullMatch2(self):
104 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
105 ['10.0.0.1', '10.0.0.2'])
106 r1.deleting = dns.rdataclass.NONE
107 self.assertFalse(r1.full_match(r1.name, dns.rdataclass.IN,
108 dns.rdatatype.A, dns.rdatatype.NONE,
109 dns.rdataclass.ANY))
110
111 def testNoFullMatch3(self):
112 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
113 ['10.0.0.1', '10.0.0.2'])
114 self.assertFalse(r1.full_match(r1.name, dns.rdataclass.IN,
115 dns.rdatatype.MX, dns.rdatatype.NONE,
116 dns.rdataclass.ANY))
117
118 def testMatchCompatibilityWithFullMatch(self):
82119 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
83120 ['10.0.0.1', '10.0.0.2'])
84121 self.assertTrue(r1.match(r1.name, dns.rdataclass.IN,
85122 dns.rdatatype.A, dns.rdatatype.NONE))
86123
87 def testMatch2(self):
124 def testMatchCompatibilityWithRdatasetMatch(self):
88125 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
89126 ['10.0.0.1', '10.0.0.2'])
90 r1.deleting = dns.rdataclass.NONE
91 self.assertTrue(r1.match(r1.name, dns.rdataclass.IN,
92 dns.rdatatype.A, dns.rdatatype.NONE,
93 dns.rdataclass.NONE))
94
95 def testNoMatch1(self):
96 n = dns.name.from_text('bar', None)
97 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
98 ['10.0.0.1', '10.0.0.2'])
99 self.assertFalse(r1.match(n, dns.rdataclass.IN,
100 dns.rdatatype.A, dns.rdatatype.NONE,
101 dns.rdataclass.ANY))
102
103 def testNoMatch2(self):
104 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
105 ['10.0.0.1', '10.0.0.2'])
106 r1.deleting = dns.rdataclass.NONE
107 self.assertFalse(r1.match(r1.name, dns.rdataclass.IN,
108 dns.rdatatype.A, dns.rdatatype.NONE,
109 dns.rdataclass.ANY))
110
111 def testNoMatch3(self):
112 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
113 ['10.0.0.1', '10.0.0.2'])
114 self.assertFalse(r1.match(r1.name, dns.rdataclass.IN,
115 dns.rdatatype.MX, dns.rdatatype.NONE,
116 dns.rdataclass.ANY))
127 self.assertTrue(r1.match(dns.rdataclass.IN, dns.rdatatype.A,
128 dns.rdatatype.NONE))
117129
118130 def testToRdataset(self):
119131 r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a',
6363 def test_sub(self):
6464 self.assertEqual(S8(0) - S8(1), S8(255))
6565
66 def test_sub(self):
67 self.assertEqual(S8(0) - S8(1), S8(255))
68
6966 def test_addition_bounds(self):
7067 self.assertRaises(ValueError, lambda: S8(0) + 128)
7168 self.assertRaises(ValueError, lambda: S8(0) - 128)
9996 self.assertRaises(ValueError, bad2)
10097
10198 def test_uncomparable(self):
99 self.assertFalse(S8(0) == S2(0))
102100 self.assertFalse(S8(0) == 'a')
103101 self.assertTrue(S8(0) != 'a')
104102 self.assertRaises(TypeError, lambda: S8(0) < 'a')
112110
113111 def test_repr(self):
114112 self.assertEqual(repr(S8(1)), 'dns.serial.Serial(1, 8)')
113
114 def test_not_equal(self):
115 self.assertNotEqual(S8(0), S8(1))
116 self.assertNotEqual(S8(0), S2(0))
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import io
3 import unittest
4
5 import dns.rdata
6 import dns.rdtypes.svcbbase
7 import dns.rrset
8 from dns.tokenizer import Tokenizer
9
10 from tests.util import here
11
12 class SVCBTestCase(unittest.TestCase):
13 def check_valid_inputs(self, inputs):
14 expected = inputs[0]
15 for text in inputs:
16 rr = dns.rdata.from_text('IN', 'SVCB', text)
17 new_text = rr.to_text()
18 self.assertEqual(expected, new_text)
19
20 def check_invalid_inputs(self, inputs):
21 for text in inputs:
22 with self.assertRaises((dns.exception.SyntaxError, ValueError)):
23 dns.rdata.from_text('IN', 'SVCB', text)
24
25 def test_svcb_general_invalid(self):
26 invalid_inputs = (
27 # Duplicate keys
28 "1 . alpn=h2 alpn=h3",
29 "1 . alpn=h2 key1=h3",
30 # Quoted keys
31 "1 . \"alpn=h2\"",
32 # Invalid space
33 "1 . alpn= h2",
34 "1 . alpn =h2",
35 "1 . alpn = h2",
36 "1 . alpn= \"h2\"",
37 "1 . =alpn",
38 )
39 self.check_invalid_inputs(invalid_inputs)
40
41 def test_svcb_mandatory(self):
42 valid_inputs = (
43 "1 . mandatory=\"alpn,no-default-alpn\" alpn=\"h2\" no-default-alpn",
44 "1 . mandatory=alpn,no-default-alpn alpn=h2 no-default-alpn",
45 "1 . mandatory=key1,key2 alpn=h2 no-default-alpn",
46 "1 . mandatory=alpn,no-default-alpn key1=\\002h2 key2",
47 "1 . key0=\\000\\001\\000\\002 alpn=h2 no-default-alpn",
48 "1 . alpn=h2 no-default-alpn mandatory=alpn,no-default-alpn",
49 )
50 self.check_valid_inputs(valid_inputs)
51
52 invalid_inputs = (
53 # empty
54 "1 . mandatory=",
55 "1 . mandatory",
56 # unknown key
57 "1 . mandatory=foo",
58 # key 0
59 "1 . mandatory=key0",
60 "1 . mandatory=key0,alpn",
61 # missing key
62 "1 . mandatory=alpn",
63 # duplicate
64 "1 . mandatory=alpn,alpn alpn=h2",
65 # invalid escaping
66 "1 . mandatory=\\alpn alpn=h2",
67 # empty wire format
68 "1 . key0",
69 "1 . key0=",
70 # 0 in wire format
71 "1 . key0=\\000\\000",
72 # invalid length in wire format
73 "1 . key0=\\000",
74 # out of order in wire format
75 "1 . key0=\\000\\002\\000\\001 alpn=h2 no-default-alpn",
76 # leading zeros
77 "1 . mandatory=key1,key002 alpn=h2 no-default-alpn",
78 )
79 self.check_invalid_inputs(invalid_inputs)
80
81 def test_svcb_alpn(self):
82 valid_inputs_two_items = (
83 "1 . alpn=\"h2,h3\"",
84 "1 . alpn=h2,h3",
85 "1 . alpn=h\\050,h3",
86 "1 . alpn=\"h\\050,h3\"",
87 "1 . alpn=\\h2,h3",
88 "1 . alpn=\"h2\\,h3\"",
89 "1 . alpn=h2\\,h3",
90 "1 . alpn=h2\\044h3",
91 "1 . key1=\\002h2\\002h3",
92 )
93 self.check_valid_inputs(valid_inputs_two_items)
94
95 valid_inputs_one_item = (
96 "1 . alpn=\"h2\\\\,h3\"",
97 "1 . alpn=h2\\\\,h3",
98 "1 . alpn=h2\\092\\044h3",
99 "1 . key1=\\005h2,h3",
100 )
101 self.check_valid_inputs(valid_inputs_one_item)
102
103 invalid_inputs = (
104 "1 . alpn",
105 "1 . alpn=",
106 "1 . alpn=h2,,h3",
107 "1 . alpn=01234567890abcdef01234567890abcdef01234567890abcdef"
108 "01234567890abcdef01234567890abcdef01234567890abcdef"
109 "01234567890abcdef01234567890abcdef01234567890abcdef"
110 "01234567890abcdef01234567890abcdef01234567890abcdef"
111 "01234567890abcdef01234567890abcdef01234567890abcdef"
112 "01234567890abcdef",
113 "1 . alpn=\",h2,h3\"",
114 "1 . alpn=\"h2,h3,\"",
115 "1 . key1",
116 "1 . key1=",
117 "1 . key1=\\000",
118 "1 . key1=\\002x",
119 )
120 self.check_invalid_inputs(invalid_inputs)
121
122 def test_svcb_no_default_alpn(self):
123 valid_inputs = (
124 "1 . alpn=\"h2\" no-default-alpn",
125 "1 . alpn=\"h2\" no-default-alpn=\"\"",
126 "1 . alpn=\"h2\" key2",
127 "1 . alpn=\"h2\" key2=\"\"",
128 )
129 self.check_valid_inputs(valid_inputs)
130
131 invalid_inputs = (
132 "1 . no-default-alpn",
133 "1 . no-default-alpn=\"\"",
134 "1 . key2",
135 "1 . key2=\"\"",
136 "1 . alpn=h2 no-default-alpn=foo",
137 "1 . alpn=h2 no-default-alpn=",
138 "1 . alpn=h2 key2=foo",
139 "1 . alpn=h2 key2=",
140 )
141 self.check_invalid_inputs(invalid_inputs)
142
143 def test_svcb_port(self):
144 valid_inputs = (
145 "1 . port=\"53\"",
146 "1 . port=53",
147 "1 . key3=\\000\\053",
148 )
149 self.check_valid_inputs(valid_inputs)
150
151 invalid_inputs = (
152 "1 . port",
153 "1 . port=",
154 "1 . port=53x",
155 "1 . port=x53",
156 "1 . port=53,54",
157 "1 . port=53\\,54",
158 "1 . port=65536",
159 "1 . key3",
160 "1 . key3=",
161 "1 . key3=\\000",
162 )
163 self.check_invalid_inputs(invalid_inputs)
164
165 def test_svcb_ipv4hint(self):
166 valid_inputs = (
167 "1 . ipv4hint=\"0.0.0.0,1.1.1.1\"",
168 "1 . ipv4hint=0.0.0.0,1.1.1.1",
169 "1 . key4=\\000\\000\\000\\000\\001\\001\\001\\001",
170 )
171 self.check_valid_inputs(valid_inputs)
172
173 invalid_inputs = (
174 "1 . ipv4hint",
175 "1 . ipv4hint=",
176 "1 . ipv4hint=1234",
177 "1 . ipv4hint=1\\.2.3.4",
178 "1 . ipv4hint=1.2.3.4\\,2.3.4.5",
179 "1 . key4=",
180 "1 . key4=123",
181 )
182 self.check_invalid_inputs(invalid_inputs)
183
184 def test_svcb_ech(self):
185 valid_inputs = (
186 "1 . ech=\"Zm9vMA==\"",
187 "1 . ech=Zm9vMA==",
188 "1 . key5=foo0",
189 "1 . key5=\\102\\111\\111\\048",
190 )
191 self.check_valid_inputs(valid_inputs)
192
193 invalid_inputs = (
194 "1 . ech",
195 "1 . ech=",
196 "1 . ech=Zm9vMA",
197 "1 . ech=\\090m9vMA==",
198 "1 . key5",
199 "1 . key5=",
200 )
201 self.check_invalid_inputs(invalid_inputs)
202
203 def test_svcb_ipv6hint(self):
204 valid_inputs = (
205 "1 . ipv6hint=\"::4,1::\"",
206 "1 . ipv6hint=::4,1::",
207 "1 . key6=\\000\\000\\000\\000\\000\\000\\000\\000"
208 "\\000\\000\\000\\000\\000\\000\\000\\004"
209 "\\000\\001\\000\\000\\000\\000\\000\\000"
210 "\\000\\000\\000\\000\\000\\000\\000\\000",
211 )
212 self.check_valid_inputs(valid_inputs)
213
214 invalid_inputs = (
215 "1 . ipv6hint",
216 "1 . ipv6hint=",
217 "1 . ipv6hint=1234",
218 "1 . ipv6hint=1\\::2",
219 "1 . ipv6hint=::1\\,::2",
220 "1 . ipv6hint",
221 "1 . key6",
222 "1 . key6=",
223 "1 . key6=123",
224 )
225 self.check_invalid_inputs(invalid_inputs)
226
227 def test_svcb_unknown(self):
228 valid_inputs_one_key = (
229 "1 . key23=\"key45\"",
230 "1 . key23=key45",
231 "1 . key23=key\\052\\053",
232 "1 . key23=\"key\\052\\053\"",
233 "1 . key23=\\107\\101\\121\\052\\053",
234 )
235 self.check_valid_inputs(valid_inputs_one_key)
236
237 valid_inputs_one_key_empty = (
238 "1 . key23",
239 "1 . key23=\"\"",
240 )
241 self.check_valid_inputs(valid_inputs_one_key_empty)
242
243 invalid_inputs_one_key = (
244 "1 . key65536=foo",
245 "1 . key24= key48",
246 )
247 self.check_invalid_inputs(invalid_inputs_one_key)
248
249 valid_inputs_two_keys = (
250 "1 . key24 key48",
251 "1 . key24=\"\" key48",
252 )
253 self.check_valid_inputs(valid_inputs_two_keys)
254
255 def test_svcb_wire(self):
256 valid_inputs = (
257 "1 . mandatory=\"alpn,port\" alpn=\"h2\" port=\"257\"",
258 "\\# 24 0001 00 0000000400010003 00010003026832 000300020101",
259 )
260 self.check_valid_inputs(valid_inputs)
261
262 everything = \
263 "100 foo.com. mandatory=\"alpn,port\" alpn=\"h2,h3\" " \
264 " no-default-alpn port=\"12345\" ech=\"abcd\" " \
265 " ipv4hint=1.2.3.4,4.3.2.1 ipv6hint=1::2,3::4" \
266 " key12345=\"foo\""
267 rr = dns.rdata.from_text('IN', 'SVCB', everything)
268 rr2 = dns.rdata.from_text('IN', 'SVCB', rr.to_generic().to_text())
269 self.assertEqual(rr, rr2)
270
271 invalid_inputs = (
272 # As above, but the keys are out of order.
273 "\\# 24 0001 00 0000000400010003 000300020101 00010003026832",
274 # As above, but the mandatory keys don't match
275 "\\# 24 0001 00 0000000400010002 00010003026832 000300020101",
276 "\\# 24 0001 00 0000000400010004 00010003026832 000300020101",
277 # Alias form shouldn't have parameters.
278 "\\# 08 0000 000300020101",
279 # no-default-alpn requires alpn
280 "\\# 07 0001 00 00020000",
281 )
282 self.check_invalid_inputs(invalid_inputs)
283
284 def test_misc_escape(self):
285 rdata = dns.rdata.from_text('in', 'svcb', '1 . alpn=\\010\\010')
286 expected = '1 . alpn="\\\\010\\\\010"'
287 self.assertEqual(rdata.to_text(), expected)
288 with self.assertRaises(dns.exception.SyntaxError):
289 dns.rdata.from_text('in', 'svcb', '1 . alpn=\\0')
290 with self.assertRaises(dns.exception.SyntaxError):
291 dns.rdata.from_text('in', 'svcb', '1 . alpn=\\00')
292 with self.assertRaises(dns.exception.SyntaxError):
293 dns.rdata.from_text('in', 'svcb', '1 . alpn=\\00q')
294 with self.assertRaises(dns.exception.SyntaxError):
295 dns.rdata.from_text('in', 'svcb', '1 . alpn=\\256')
296 # This doesn't usually get exercised, so we do it directly.
297 gp = dns.rdtypes.svcbbase.GenericParam.from_value('\\001\\002')
298 expected = '"\\001\\002"'
299 self.assertEqual(gp.to_text(), expected)
300
301 def test_svcb_spec_test_vectors(self):
302 text_file = here("svcb_test_vectors.text")
303 text_tokenizer = Tokenizer(open(text_file), filename=text_file)
304 generic_file = here("svcb_test_vectors.generic")
305 generic_tokenizer = Tokenizer(open(generic_file), filename=generic_file)
306
307 while True:
308 while True:
309 text_token = text_tokenizer.get()
310 if text_token.is_eol():
311 continue
312 break
313 while True:
314 generic_token = generic_tokenizer.get()
315 if generic_token.is_eol():
316 continue
317 break
318 self.assertEqual(text_token.ttype, generic_token.ttype)
319 if text_token.is_eof():
320 break
321 self.assertTrue(text_token.is_identifier)
322 text_tokenizer.unget(text_token)
323 generic_tokenizer.unget(generic_token)
324 text_rdata = dns.rdata.from_text('IN', 'SVCB', text_tokenizer)
325 generic_rdata = dns.rdata.from_text('IN', 'SVCB', generic_tokenizer)
326 self.assertEqual(text_rdata, generic_rdata)
327
328 def test_svcb_spec_failure_cases(self):
329 failure_cases = (
330 # This example has multiple instances of the same SvcParamKey
331 "1 foo.example.com. key123=abc key123=def",
332 # In the next examples the SvcParamKeys are missing their values.
333 "1 foo.example.com. mandatory",
334 "1 foo.example.com. alpn",
335 "1 foo.example.com. port",
336 "1 foo.example.com. ipv4hint",
337 "1 foo.example.com. ipv6hint",
338 # The "no-default-alpn" SvcParamKey value MUST be empty (Section 6.1).
339 "1 foo.example.com. no-default-alpn=abc",
340 # In this record a mandatory SvcParam is missing (Section 7).
341 "1 foo.example.com. mandatory=key123",
342 # The "mandatory" SvcParamKey MUST not be included in mandatory list
343 # (Section 7).
344 "1 foo.example.com. mandatory=mandatory",
345 # Here there are multiple instances of the same SvcParamKey in the
346 # mandatory list (Section 7).
347 "1 foo.example.com. mandatory=key123,key123 key123=abc",
348 )
349 self.check_invalid_inputs(failure_cases);
350
351 def test_alias_mode(self):
352 rd = dns.rdata.from_text('in', 'svcb', '0 .')
353 self.assertEqual(len(rd.params), 0)
354 self.assertEqual(rd.target, dns.name.root)
355 self.assertEqual(rd.to_text(), '0 .')
356 rd = dns.rdata.from_text('in', 'svcb', '0 elsewhere.')
357 self.assertEqual(rd.target, dns.name.from_text('elsewhere.'))
358 self.assertEqual(len(rd.params), 0)
359 # provoke 'parameters in AliasMode' from text.
360 with self.assertRaises(dns.exception.SyntaxError):
361 dns.rdata.from_text('in', 'svcb', '0 elsewhere. alpn=h2')
362 # provoke 'parameters in AliasMode' from wire too.
363 wire = bytes.fromhex('0000000000000400010003')
364 with self.assertRaises(dns.exception.FormError):
365 dns.rdata.from_wire('in', 'svcb', wire, 0, len(wire))
366
367 def test_immutability(self):
368 alpn = dns.rdtypes.svcbbase.ALPNParam.from_value(['h2', 'h3'])
369 with self.assertRaises(TypeError):
370 alpn.ids[0] = 'foo'
371 with self.assertRaises(TypeError):
372 del alpn.ids[0]
373 with self.assertRaises(TypeError):
374 alpn.ids = 'foo'
375 with self.assertRaises(TypeError):
376 del alpn.ids
377
378 def test_alias_not_compressed(self):
379 rrs = dns.rrset.from_text('elsewhere.', 300, 'in', 'svcb',
380 '0 elseWhere.')
381 output = io.BytesIO()
382 compress = {}
383 rrs.to_wire(output, compress)
384 wire = output.getvalue()
385 # Just one of these assertions is enough, but we do both to show
386 # the bug we're checking is fixed.
387 assert not wire.endswith(b'\xc0\x00')
388 assert wire.endswith(b'\x09elseWhere\x00')
5050 'foo\\010bar'))
5151
5252 def testQuotedString5(self):
53 def bad():
53 with self.assertRaises(dns.exception.UnexpectedEnd):
5454 tok = dns.tokenizer.Tokenizer(r'"foo')
5555 tok.get()
56 self.assertRaises(dns.exception.UnexpectedEnd, bad)
5756
5857 def testQuotedString6(self):
59 def bad():
58 with self.assertRaises(dns.exception.SyntaxError):
6059 tok = dns.tokenizer.Tokenizer(r'"foo\01')
6160 tok.get()
62 self.assertRaises(dns.exception.SyntaxError, bad)
6361
6462 def testQuotedString7(self):
65 def bad():
63 with self.assertRaises(dns.exception.SyntaxError):
6664 tok = dns.tokenizer.Tokenizer('"foo\nbar"')
6765 tok.get()
68 self.assertRaises(dns.exception.SyntaxError, bad)
6966
7067 def testEmpty1(self):
7168 tok = dns.tokenizer.Tokenizer('')
125122 self.assertEqual(tokens, [Token(dns.tokenizer.IDENTIFIER, 'foo'),
126123 Token(dns.tokenizer.IDENTIFIER, 'bar'),
127124 Token(dns.tokenizer.EOL, '\n')])
125
128126 def testMultiline3(self):
129 def bad():
127 with self.assertRaises(dns.exception.SyntaxError):
130128 tok = dns.tokenizer.Tokenizer('foo)')
131129 list(iter(tok))
132 self.assertRaises(dns.exception.SyntaxError, bad)
133130
134131 def testMultiline4(self):
135 def bad():
132 with self.assertRaises(dns.exception.SyntaxError):
136133 tok = dns.tokenizer.Tokenizer('((foo)')
137134 list(iter(tok))
138 self.assertRaises(dns.exception.SyntaxError, bad)
139135
140136 def testUnget1(self):
141137 tok = dns.tokenizer.Tokenizer('foo')
147143 self.assertEqual(t1.value, 'foo')
148144
149145 def testUnget2(self):
150 def bad():
146 with self.assertRaises(dns.tokenizer.UngetBufferFull):
151147 tok = dns.tokenizer.Tokenizer('foo')
152148 t1 = tok.get()
153149 tok.unget(t1)
154150 tok.unget(t1)
155 self.assertRaises(dns.tokenizer.UngetBufferFull, bad)
156151
157152 def testGetEOL1(self):
158153 tok = dns.tokenizer.Tokenizer('\n')
204199 tok = dns.tokenizer.Tokenizer('1234')
205200 v = tok.get_int()
206201 self.assertEqual(v, 1234)
207 def bad1():
202 with self.assertRaises(dns.exception.SyntaxError):
208203 tok = dns.tokenizer.Tokenizer('"1234"')
209 v = tok.get_int()
210 self.assertRaises(dns.exception.SyntaxError, bad1)
211 def bad2():
204 tok.get_int()
205 with self.assertRaises(dns.exception.SyntaxError):
212206 tok = dns.tokenizer.Tokenizer('q1234')
213 v = tok.get_int()
214 self.assertRaises(dns.exception.SyntaxError, bad2)
215 def bad3():
207 tok.get_int()
208 with self.assertRaises(dns.exception.SyntaxError):
209 tok = dns.tokenizer.Tokenizer('281474976710656')
210 tok.get_uint48()
211 with self.assertRaises(dns.exception.SyntaxError):
216212 tok = dns.tokenizer.Tokenizer('4294967296')
217 v = tok.get_uint32()
218 self.assertRaises(dns.exception.SyntaxError, bad3)
219 def bad4():
213 tok.get_uint32()
214 with self.assertRaises(dns.exception.SyntaxError):
220215 tok = dns.tokenizer.Tokenizer('65536')
221 v = tok.get_uint16()
222 self.assertRaises(dns.exception.SyntaxError, bad4)
223 def bad5():
216 tok.get_uint16()
217 with self.assertRaises(dns.exception.SyntaxError):
224218 tok = dns.tokenizer.Tokenizer('256')
225 v = tok.get_uint8()
226 self.assertRaises(dns.exception.SyntaxError, bad5)
219 tok.get_uint8()
227220 # Even though it is badly named get_int(), it's really get_unit!
228 def bad6():
221 with self.assertRaises(dns.exception.SyntaxError):
229222 tok = dns.tokenizer.Tokenizer('-1234')
230 v = tok.get_int()
231 self.assertRaises(dns.exception.SyntaxError, bad5)
223 tok.get_int()
224 # get_uint16 can do other bases too, and has a custom error
225 # for base 8.
226 tok = dns.tokenizer.Tokenizer('177777')
227 self.assertEqual(tok.get_uint16(base=8), 65535)
228 with self.assertRaises(dns.exception.SyntaxError):
229 tok = dns.tokenizer.Tokenizer('200000')
230 tok.get_uint16(base=8)
232231
233232 def testGetString(self):
234233 tok = dns.tokenizer.Tokenizer('foo')
240239 tok = dns.tokenizer.Tokenizer('abcdefghij')
241240 v = tok.get_string(max_length=10)
242241 self.assertEqual(v, 'abcdefghij')
243 def bad():
242 with self.assertRaises(dns.exception.SyntaxError):
244243 tok = dns.tokenizer.Tokenizer('abcdefghij')
245 v = tok.get_string(max_length=9)
246 self.assertRaises(dns.exception.SyntaxError, bad)
244 tok.get_string(max_length=9)
245 tok = dns.tokenizer.Tokenizer('')
246 with self.assertRaises(dns.exception.SyntaxError):
247 tok.get_string()
247248
248249 def testMultiLineWithComment(self):
249250 tok = dns.tokenizer.Tokenizer('( ; abc\n)')
262263 self.assertTrue(t.is_eof())
263264
264265 def testMultiLineWithEOFAfterComment(self):
265 def bad():
266 with self.assertRaises(dns.exception.SyntaxError):
266267 tok = dns.tokenizer.Tokenizer('( ; abc')
267268 tok.get_eol()
268 self.assertRaises(dns.exception.SyntaxError, bad)
269269
270270 def testEscapeUnexpectedEnd(self):
271 def bad():
271 with self.assertRaises(dns.exception.UnexpectedEnd):
272272 tok = dns.tokenizer.Tokenizer('\\')
273273 tok.get()
274 self.assertRaises(dns.exception.UnexpectedEnd, bad)
274
275 def testEscapeBounds(self):
276 with self.assertRaises(dns.exception.SyntaxError):
277 tok = dns.tokenizer.Tokenizer('\\256')
278 tok.get().unescape()
279 with self.assertRaises(dns.exception.SyntaxError):
280 tok = dns.tokenizer.Tokenizer('\\256')
281 tok.get().unescape_to_bytes()
275282
276283 def testGetUngetRegetComment(self):
277284 tok = dns.tokenizer.Tokenizer(';comment')
281288 self.assertEqual(t1, t2)
282289
283290 def testBadAsName(self):
284 def bad():
291 with self.assertRaises(dns.exception.SyntaxError):
285292 tok = dns.tokenizer.Tokenizer('"not an identifier"')
286293 t = tok.get()
287294 tok.as_name(t)
288 self.assertRaises(dns.exception.SyntaxError, bad)
289295
290296 def testBadGetTTL(self):
291 def bad():
297 with self.assertRaises(dns.exception.SyntaxError):
292298 tok = dns.tokenizer.Tokenizer('"not an identifier"')
293 v = tok.get_ttl()
294 self.assertRaises(dns.exception.SyntaxError, bad)
299 tok.get_ttl()
300
301 def testBadGetEOL(self):
302 with self.assertRaises(dns.exception.SyntaxError):
303 tok = dns.tokenizer.Tokenizer('"not an identifier"')
304 tok.get_eol_as_token()
295305
296306 def testDanglingEscapes(self):
297 def bad1():
298 tok = dns.tokenizer.Tokenizer('"\\"')
299 t = tok.get().unescape()
300 self.assertRaises(dns.exception.SyntaxError, bad1)
301 def bad2():
302 tok = dns.tokenizer.Tokenizer('"\\0"')
303 t = tok.get().unescape()
304 self.assertRaises(dns.exception.SyntaxError, bad2)
305 def bad3():
306 tok = dns.tokenizer.Tokenizer('"\\00"')
307 t = tok.get().unescape()
308 self.assertRaises(dns.exception.SyntaxError, bad3)
309 def bad4():
310 tok = dns.tokenizer.Tokenizer('"\\"')
311 t = tok.get().unescape_to_bytes()
312 self.assertRaises(dns.exception.SyntaxError, bad4)
313 def bad5():
314 tok = dns.tokenizer.Tokenizer('"\\0"')
315 t = tok.get().unescape_to_bytes()
316 self.assertRaises(dns.exception.SyntaxError, bad5)
317 def bad6():
318 tok = dns.tokenizer.Tokenizer('"\\00"')
319 t = tok.get().unescape_to_bytes()
320 self.assertRaises(dns.exception.SyntaxError, bad6)
321 def bad7():
322 tok = dns.tokenizer.Tokenizer('"\\00a"')
323 t = tok.get().unescape()
324 self.assertRaises(dns.exception.SyntaxError, bad7)
325 def bad8():
326 tok = dns.tokenizer.Tokenizer('"\\00a"')
327 t = tok.get().unescape_to_bytes()
328 self.assertRaises(dns.exception.SyntaxError, bad8)
307 for text in ['"\\"', '"\\0"', '"\\00"', '"\\00a"']:
308 with self.assertRaises(dns.exception.SyntaxError):
309 tok = dns.tokenizer.Tokenizer(text)
310 tok.get().unescape()
311 with self.assertRaises(dns.exception.SyntaxError):
312 tok = dns.tokenizer.Tokenizer(text)
313 tok.get().unescape_to_bytes()
314
315 def testTokenMisc(self):
316 t1 = dns.tokenizer.Token(dns.tokenizer.IDENTIFIER, 'hi')
317 t2 = dns.tokenizer.Token(dns.tokenizer.IDENTIFIER, 'hi')
318 t3 = dns.tokenizer.Token(dns.tokenizer.IDENTIFIER, 'there')
319 self.assertEqual(t1, t2)
320 self.assertFalse(t1 == 'hi') # not NotEqual because we want to use ==
321 self.assertNotEqual(t1, 'hi')
322 self.assertNotEqual(t1, t3)
323 self.assertEqual(str(t1), '3 "hi"')
324
325 def testBadConcatenateRemaining(self):
326 with self.assertRaises(dns.exception.SyntaxError):
327 tok = dns.tokenizer.Tokenizer('a b "not an identifer" c')
328 tok.concatenate_remaining_identifiers()
329
330 def testStdinFilename(self):
331 tok = dns.tokenizer.Tokenizer()
332 self.assertEqual(tok.filename, '<stdin>')
333
334 def testBytesLiteral(self):
335 tok = dns.tokenizer.Tokenizer(b'this is input')
336 self.assertEqual(tok.get().value, 'this')
337 self.assertEqual(tok.filename, '<string>')
338 tok = dns.tokenizer.Tokenizer(b'this is input', 'myfilename')
339 self.assertEqual(tok.filename, 'myfilename')
340
341 def testUngetBranches(self):
342 tok = dns.tokenizer.Tokenizer(b' this is input')
343 t = tok.get(want_leading=True)
344 tok.unget(t)
345 t = tok.get(want_leading=True)
346 self.assertEqual(t.ttype, dns.tokenizer.WHITESPACE)
347 tok.unget(t)
348 t = tok.get()
349 self.assertEqual(t.ttype, dns.tokenizer.IDENTIFIER)
350 self.assertEqual(t.value, 'this')
351 tok = dns.tokenizer.Tokenizer(b'; this is input\n')
352 t = tok.get(want_comment=True)
353 tok.unget(t)
354 t = tok.get(want_comment=True)
355 self.assertEqual(t.ttype, dns.tokenizer.COMMENT)
356 tok.unget(t)
357 t = tok.get()
358 self.assertEqual(t.ttype, dns.tokenizer.EOL)
329359
330360 if __name__ == '__main__':
331361 unittest.main()
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import time
3
4 import pytest
5
6 import dns.name
7 import dns.rdataclass
8 import dns.rdatatype
9 import dns.rdataset
10 import dns.rrset
11 import dns.transaction
12 import dns.versioned
13 import dns.zone
14
15
16 class DB(dns.transaction.TransactionManager):
17 def __init__(self):
18 self.rdatasets = {}
19
20 def reader(self):
21 return Transaction(self, False, True)
22
23 def writer(self, replacement=False):
24 return Transaction(self, replacement, False)
25
26 def origin_information(self):
27 return (dns.name.from_text('example'), True, dns.name.empty)
28
29 def get_class(self):
30 return dns.rdataclass.IN
31
32
33 class Transaction(dns.transaction.Transaction):
34 def __init__(self, db, replacement, read_only):
35 super().__init__(db, replacement, read_only)
36 self.rdatasets = {}
37 if not replacement:
38 self.rdatasets.update(db.rdatasets)
39
40 @property
41 def db(self):
42 return self.manager
43
44 def _get_rdataset(self, name, rdtype, covers):
45 return self.rdatasets.get((name, rdtype, covers))
46
47 def _put_rdataset(self, name, rdataset):
48 self.rdatasets[(name, rdataset.rdtype, rdataset.covers)] = rdataset
49
50 def _delete_name(self, name):
51 remove = []
52 for key in self.rdatasets.keys():
53 if key[0] == name:
54 remove.append(key)
55 if len(remove) > 0:
56 for key in remove:
57 del self.rdatasets[key]
58
59 def _delete_rdataset(self, name, rdtype, covers):
60 del self.rdatasets[(name, rdtype, covers)]
61
62 def _name_exists(self, name):
63 for key in self.rdatasets.keys():
64 if key[0] == name:
65 return True
66 return False
67
68 def _changed(self):
69 if self.read_only:
70 return False
71 else:
72 return len(self.rdatasets) > 0
73
74 def _end_transaction(self, commit):
75 if commit:
76 self.db.rdatasets = self.rdatasets
77
78 def _set_origin(self, origin):
79 pass
80
81 @pytest.fixture
82 def db():
83 db = DB()
84 rrset = dns.rrset.from_text('content', 300, 'in', 'txt', 'content')
85 db.rdatasets[(rrset.name, rrset.rdtype, 0)] = rrset
86 return db
87
88 def test_basic(db):
89 # successful txn
90 with db.writer() as txn:
91 rrset = dns.rrset.from_text('foo', 300, 'in', 'a',
92 '10.0.0.1', '10.0.0.2')
93 txn.add(rrset)
94 assert txn.name_exists(rrset.name)
95 assert db.rdatasets[(rrset.name, rrset.rdtype, 0)] == \
96 rrset
97 # rollback
98 with pytest.raises(Exception):
99 with db.writer() as txn:
100 rrset2 = dns.rrset.from_text('foo', 300, 'in', 'a',
101 '10.0.0.3', '10.0.0.4')
102 txn.add(rrset2)
103 raise Exception()
104 assert db.rdatasets[(rrset.name, rrset.rdtype, 0)] == \
105 rrset
106 with db.writer() as txn:
107 txn.delete(rrset.name)
108 assert db.rdatasets.get((rrset.name, rrset.rdtype, 0)) \
109 is None
110
111 def test_get(db):
112 with db.writer() as txn:
113 content = dns.name.from_text('content', None)
114 rdataset = txn.get(content, dns.rdatatype.TXT)
115 assert rdataset is not None
116 assert rdataset[0].strings == (b'content',)
117 assert isinstance(rdataset, dns.rdataset.ImmutableRdataset)
118
119 def test_add(db):
120 with db.writer() as txn:
121 rrset = dns.rrset.from_text('foo', 300, 'in', 'a',
122 '10.0.0.1', '10.0.0.2')
123 txn.add(rrset)
124 rrset2 = dns.rrset.from_text('foo', 300, 'in', 'a',
125 '10.0.0.3', '10.0.0.4')
126 txn.add(rrset2)
127 expected = dns.rrset.from_text('foo', 300, 'in', 'a',
128 '10.0.0.1', '10.0.0.2',
129 '10.0.0.3', '10.0.0.4')
130 assert db.rdatasets[(rrset.name, rrset.rdtype, 0)] == \
131 expected
132
133 def test_replacement(db):
134 with db.writer() as txn:
135 rrset = dns.rrset.from_text('foo', 300, 'in', 'a',
136 '10.0.0.1', '10.0.0.2')
137 txn.add(rrset)
138 rrset2 = dns.rrset.from_text('foo', 300, 'in', 'a',
139 '10.0.0.3', '10.0.0.4')
140 txn.replace(rrset2)
141 assert db.rdatasets[(rrset.name, rrset.rdtype, 0)] == \
142 rrset2
143
144 def test_delete(db):
145 with db.writer() as txn:
146 txn.delete(dns.name.from_text('nonexistent', None))
147 content = dns.name.from_text('content', None)
148 content2 = dns.name.from_text('content2', None)
149 txn.delete(content)
150 assert not txn.name_exists(content)
151 txn.delete(content2, dns.rdatatype.TXT)
152 rrset = dns.rrset.from_text('content', 300, 'in', 'txt', 'new-content')
153 txn.add(rrset)
154 assert txn.name_exists(content)
155 txn.delete(content, dns.rdatatype.TXT)
156 assert not txn.name_exists(content)
157 rrset = dns.rrset.from_text('content2', 300, 'in', 'txt', 'new-content')
158 txn.delete(rrset)
159 content_keys = [k for k in db.rdatasets if k[0] == content]
160 assert len(content_keys) == 0
161
162 def test_delete_exact(db):
163 with db.writer() as txn:
164 rrset = dns.rrset.from_text('content', 300, 'in', 'txt', 'bad-content')
165 with pytest.raises(dns.transaction.DeleteNotExact):
166 txn.delete_exact(rrset)
167 rrset = dns.rrset.from_text('content2', 300, 'in', 'txt', 'bad-content')
168 with pytest.raises(dns.transaction.DeleteNotExact):
169 txn.delete_exact(rrset)
170 with pytest.raises(dns.transaction.DeleteNotExact):
171 txn.delete_exact(rrset.name)
172 with pytest.raises(dns.transaction.DeleteNotExact):
173 txn.delete_exact(rrset.name, dns.rdatatype.TXT)
174 rrset = dns.rrset.from_text('content', 300, 'in', 'txt', 'content')
175 txn.delete_exact(rrset)
176 assert db.rdatasets.get((rrset.name, rrset.rdtype, 0)) \
177 is None
178
179 def test_parameter_forms(db):
180 with db.writer() as txn:
181 foo = dns.name.from_text('foo', None)
182 rdataset = dns.rdataset.from_text('in', 'a', 300,
183 '10.0.0.1', '10.0.0.2')
184 rdata1 = dns.rdata.from_text('in', 'a', '10.0.0.3')
185 rdata2 = dns.rdata.from_text('in', 'a', '10.0.0.4')
186 txn.add(foo, rdataset)
187 txn.add(foo, 100, rdata1)
188 txn.add(foo, 30, rdata2)
189 expected = dns.rrset.from_text('foo', 30, 'in', 'a',
190 '10.0.0.1', '10.0.0.2',
191 '10.0.0.3', '10.0.0.4')
192 assert db.rdatasets[(foo, rdataset.rdtype, 0)] == \
193 expected
194 with db.writer() as txn:
195 txn.delete(foo, rdataset)
196 txn.delete(foo, rdata1)
197 txn.delete(foo, rdata2)
198 assert db.rdatasets.get((foo, rdataset.rdtype, 0)) \
199 is None
200
201 def test_bad_parameters(db):
202 with db.writer() as txn:
203 with pytest.raises(TypeError):
204 txn.add(1)
205 with pytest.raises(TypeError):
206 rrset = dns.rrset.from_text('bar', 300, 'in', 'txt', 'bar')
207 txn.add(rrset, 1)
208 with pytest.raises(ValueError):
209 foo = dns.name.from_text('foo', None)
210 rdata = dns.rdata.from_text('in', 'a', '10.0.0.3')
211 txn.add(foo, 0x80000000, rdata)
212 with pytest.raises(TypeError):
213 txn.add(foo)
214 with pytest.raises(TypeError):
215 txn.add()
216 with pytest.raises(TypeError):
217 txn.add(foo, 300)
218 with pytest.raises(TypeError):
219 txn.add(foo, 300, 'hi')
220 with pytest.raises(TypeError):
221 txn.add(foo, 'hi')
222 with pytest.raises(TypeError):
223 txn.delete()
224 with pytest.raises(TypeError):
225 txn.delete(1)
226
227 def test_cannot_store_non_origin_soa(db):
228 with pytest.raises(ValueError):
229 with db.writer() as txn:
230 rrset = dns.rrset.from_text('foo', 300, 'in', 'SOA',
231 '. . 1 2 3 4 5')
232 txn.add(rrset)
233
234 example_text = """$TTL 3600
235 $ORIGIN example.
236 @ soa foo bar 1 2 3 4 5
237 @ ns ns1
238 @ ns ns2
239 ns1 a 10.0.0.1
240 ns2 a 10.0.0.2
241 $TTL 300
242 $ORIGIN foo.example.
243 bar mx 0 blaz
244 """
245
246 example_text_output = """@ 3600 IN SOA foo bar 1 2 3 4 5
247 @ 3600 IN NS ns1
248 @ 3600 IN NS ns2
249 @ 3600 IN NS ns3
250 ns1 3600 IN A 10.0.0.1
251 ns2 3600 IN A 10.0.0.2
252 ns3 3600 IN A 10.0.0.3
253 """
254
255 @pytest.fixture(params=[dns.zone.Zone, dns.versioned.Zone])
256 def zone(request):
257 return dns.zone.from_text(example_text, zone_factory=request.param)
258
259 def test_zone_basic(zone):
260 with zone.writer() as txn:
261 txn.delete(dns.name.from_text('bar.foo', None))
262 rd = dns.rdata.from_text('in', 'ns', 'ns3')
263 txn.add(dns.name.empty, 3600, rd)
264 rd = dns.rdata.from_text('in', 'a', '10.0.0.3')
265 txn.add(dns.name.from_text('ns3', None), 3600, rd)
266 output = zone.to_text()
267 assert output == example_text_output
268
269 def test_explicit_rollback_and_commit(zone):
270 with zone.writer() as txn:
271 assert not txn.changed()
272 txn.delete(dns.name.from_text('bar.foo', None))
273 txn.rollback()
274 assert zone.get_node('bar.foo') is not None
275 with zone.writer() as txn:
276 assert not txn.changed()
277 txn.delete(dns.name.from_text('bar.foo', None))
278 txn.commit()
279 assert zone.get_node('bar.foo') is None
280 with pytest.raises(dns.transaction.AlreadyEnded):
281 with zone.writer() as txn:
282 txn.rollback()
283 txn.delete(dns.name.from_text('bar.foo', None))
284 with pytest.raises(dns.transaction.AlreadyEnded):
285 with zone.writer() as txn:
286 txn.rollback()
287 txn.add('bar.foo', 300, dns.rdata.from_text('in', 'txt', 'hi'))
288 with pytest.raises(dns.transaction.AlreadyEnded):
289 with zone.writer() as txn:
290 txn.rollback()
291 txn.replace('bar.foo', 300, dns.rdata.from_text('in', 'txt', 'hi'))
292 with pytest.raises(dns.transaction.AlreadyEnded):
293 with zone.reader() as txn:
294 txn.rollback()
295 txn.get('bar.foo', 'in', 'mx')
296 with pytest.raises(dns.transaction.AlreadyEnded):
297 with zone.writer() as txn:
298 txn.rollback()
299 txn.delete_exact('bar.foo')
300 with pytest.raises(dns.transaction.AlreadyEnded):
301 with zone.writer() as txn:
302 txn.rollback()
303 txn.name_exists('bar.foo')
304 with pytest.raises(dns.transaction.AlreadyEnded):
305 with zone.writer() as txn:
306 txn.rollback()
307 txn.update_serial()
308 with pytest.raises(dns.transaction.AlreadyEnded):
309 with zone.writer() as txn:
310 txn.rollback()
311 txn.changed()
312 with pytest.raises(dns.transaction.AlreadyEnded):
313 with zone.writer() as txn:
314 txn.rollback()
315 txn.rollback()
316 with pytest.raises(dns.transaction.AlreadyEnded):
317 with zone.writer() as txn:
318 txn.rollback()
319 txn.commit()
320 with pytest.raises(dns.transaction.AlreadyEnded):
321 with zone.writer() as txn:
322 txn.rollback()
323 for rdataset in txn:
324 pass
325
326 def test_zone_changed(zone):
327 # Read-only is not changed!
328 with zone.reader() as txn:
329 assert not txn.changed()
330 # delete an existing name
331 with zone.writer() as txn:
332 assert not txn.changed()
333 txn.delete(dns.name.from_text('bar.foo', None))
334 assert txn.changed()
335 # delete a nonexistent name
336 with zone.writer() as txn:
337 assert not txn.changed()
338 txn.delete(dns.name.from_text('unknown.bar.foo', None))
339 assert not txn.changed()
340 # delete a nonexistent rdataset from an extant node
341 with zone.writer() as txn:
342 assert not txn.changed()
343 txn.delete(dns.name.from_text('bar.foo', None), 'txt')
344 assert not txn.changed()
345 # add an rdataset to an extant Node
346 with zone.writer() as txn:
347 assert not txn.changed()
348 txn.add('bar.foo', 300, dns.rdata.from_text('in', 'txt', 'hi'))
349 assert txn.changed()
350 # add an rdataset to a nonexistent Node
351 with zone.writer() as txn:
352 assert not txn.changed()
353 txn.add('foo.foo', 300, dns.rdata.from_text('in', 'txt', 'hi'))
354 assert txn.changed()
355
356 def test_zone_base_layer(zone):
357 with zone.writer() as txn:
358 # Get a set from the zone layer
359 rdataset = txn.get(dns.name.empty, dns.rdatatype.NS, dns.rdatatype.NONE)
360 expected = dns.rdataset.from_text('in', 'ns', 300, 'ns1', 'ns2')
361 assert rdataset == expected
362
363 def test_zone_transaction_layer(zone):
364 with zone.writer() as txn:
365 # Make a change
366 rd = dns.rdata.from_text('in', 'ns', 'ns3')
367 txn.add(dns.name.empty, 3600, rd)
368 # Get a set from the transaction layer
369 expected = dns.rdataset.from_text('in', 'ns', 300, 'ns1', 'ns2', 'ns3')
370 rdataset = txn.get(dns.name.empty, dns.rdatatype.NS, dns.rdatatype.NONE)
371 assert rdataset == expected
372 assert txn.name_exists(dns.name.empty)
373 ns1 = dns.name.from_text('ns1', None)
374 assert txn.name_exists(ns1)
375 ns99 = dns.name.from_text('ns99', None)
376 assert not txn.name_exists(ns99)
377
378 def test_zone_add_and_delete(zone):
379 with zone.writer() as txn:
380 a99 = dns.name.from_text('a99', None)
381 a100 = dns.name.from_text('a100', None)
382 a101 = dns.name.from_text('a101', None)
383 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.99')
384 txn.add(a99, rds)
385 txn.delete(a99, dns.rdatatype.A)
386 txn.delete(a100, dns.rdatatype.A)
387 txn.delete(a101)
388 assert not txn.name_exists(a99)
389 assert not txn.name_exists(a100)
390 assert not txn.name_exists(a101)
391 ns1 = dns.name.from_text('ns1', None)
392 txn.delete(ns1, dns.rdatatype.A)
393 assert not txn.name_exists(ns1)
394 with zone.writer() as txn:
395 txn.add(a99, rds)
396 txn.delete(a99)
397 assert not txn.name_exists(a99)
398 with zone.writer() as txn:
399 txn.add(a100, rds)
400 txn.delete(a99)
401 assert not txn.name_exists(a99)
402 assert txn.name_exists(a100)
403
404 def test_write_after_rollback(zone):
405 with pytest.raises(ExpectedException):
406 with zone.writer() as txn:
407 a99 = dns.name.from_text('a99', None)
408 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.99')
409 txn.add(a99, rds)
410 raise ExpectedException
411 with zone.writer() as txn:
412 a99 = dns.name.from_text('a99', None)
413 rds = dns.rdataset.from_text('in', 'a', 300, '10.99.99.99')
414 txn.add(a99, rds)
415 assert zone.get_rdataset('a99', 'a') == rds
416
417 def test_zone_get_deleted(zone):
418 with zone.writer() as txn:
419 ns1 = dns.name.from_text('ns1', None)
420 assert txn.get(ns1, dns.rdatatype.A) is not None
421 txn.delete(ns1)
422 assert txn.get(ns1, dns.rdatatype.A) is None
423 ns2 = dns.name.from_text('ns2', None)
424 txn.delete(ns2, dns.rdatatype.A)
425 assert txn.get(ns2, dns.rdatatype.A) is None
426
427 def test_zone_bad_class(zone):
428 with zone.writer() as txn:
429 rds = dns.rdataset.from_text('ch', 'ns', 300, 'ns1', 'ns2')
430 with pytest.raises(ValueError):
431 txn.add(dns.name.empty, rds)
432 with pytest.raises(ValueError):
433 txn.replace(dns.name.empty, rds)
434 with pytest.raises(ValueError):
435 txn.delete(dns.name.empty, rds)
436
437 def test_update_serial(zone):
438 # basic
439 with zone.writer() as txn:
440 txn.update_serial()
441 rdataset = zone.find_rdataset('@', 'soa')
442 assert rdataset[0].serial == 2
443 # max
444 with zone.writer() as txn:
445 txn.update_serial(0xffffffff, False)
446 rdataset = zone.find_rdataset('@', 'soa')
447 assert rdataset[0].serial == 0xffffffff
448 # wraparound to 1
449 with zone.writer() as txn:
450 txn.update_serial()
451 rdataset = zone.find_rdataset('@', 'soa')
452 assert rdataset[0].serial == 1
453 # trying to set to zero sets to 1
454 with zone.writer() as txn:
455 txn.update_serial(0, False)
456 rdataset = zone.find_rdataset('@', 'soa')
457 assert rdataset[0].serial == 1
458 with pytest.raises(KeyError):
459 with zone.writer() as txn:
460 txn.update_serial(name=dns.name.from_text('unknown', None))
461 with pytest.raises(ValueError):
462 with zone.writer() as txn:
463 txn.update_serial(-1)
464 with pytest.raises(ValueError):
465 with zone.writer() as txn:
466 txn.update_serial(2**31)
467
468 class ExpectedException(Exception):
469 pass
470
471 def test_zone_rollback(zone):
472 a99 = dns.name.from_text('a99.example.')
473 try:
474 with zone.writer() as txn:
475 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.99')
476 txn.add(a99, rds)
477 assert txn.name_exists(a99)
478 raise ExpectedException
479 except ExpectedException:
480 pass
481 assert not zone.get_node(a99)
482
483 def test_zone_ooz_name(zone):
484 with zone.writer() as txn:
485 with pytest.raises(KeyError):
486 a99 = dns.name.from_text('a99.not-example.')
487 assert txn.name_exists(a99)
488
489 def test_zone_iteration(zone):
490 expected = {}
491 for (name, rdataset) in zone.iterate_rdatasets():
492 expected[(name, rdataset.rdtype, rdataset.covers)] = rdataset
493 with zone.writer() as txn:
494 actual = {}
495 for (name, rdataset) in txn:
496 actual[(name, rdataset.rdtype, rdataset.covers)] = rdataset
497 assert actual == expected
498
499 @pytest.fixture
500 def vzone():
501 return dns.zone.from_text(example_text, zone_factory=dns.versioned.Zone)
502
503 def test_vzone_read_only(vzone):
504 with vzone.reader() as txn:
505 rdataset = txn.get(dns.name.empty, dns.rdatatype.NS, dns.rdatatype.NONE)
506 expected = dns.rdataset.from_text('in', 'ns', 300, 'ns1', 'ns2')
507 assert rdataset == expected
508 with pytest.raises(dns.transaction.ReadOnly):
509 txn.replace(dns.name.empty, expected)
510
511 def test_vzone_multiple_versions(vzone):
512 assert len(vzone._versions) == 1
513 vzone.set_max_versions(None) # unlimited!
514 with vzone.writer() as txn:
515 txn.update_serial()
516 with vzone.writer() as txn:
517 txn.update_serial()
518 with vzone.writer() as txn:
519 txn.update_serial(1000, False)
520 rdataset = vzone.find_rdataset('@', 'soa')
521 assert rdataset[0].serial == 1000
522 assert len(vzone._versions) == 4
523 with vzone.reader(id=5) as txn:
524 assert txn.version.id == 5
525 rdataset = txn.get('@', 'soa')
526 assert rdataset[0].serial == 1000
527 with vzone.reader(serial=1000) as txn:
528 assert txn.version.id == 5
529 rdataset = txn.get('@', 'soa')
530 assert rdataset[0].serial == 1000
531 vzone.set_max_versions(2)
532 assert len(vzone._versions) == 2
533 # The ones that survived should be 3 and 1000
534 rdataset = vzone._versions[0].get_rdataset(dns.name.empty,
535 dns.rdatatype.SOA,
536 dns.rdatatype.NONE)
537 assert rdataset[0].serial == 3
538 rdataset = vzone._versions[1].get_rdataset(dns.name.empty,
539 dns.rdatatype.SOA,
540 dns.rdatatype.NONE)
541 assert rdataset[0].serial == 1000
542 with pytest.raises(ValueError):
543 vzone.set_max_versions(0)
544
545 # for debugging if needed
546 def _dump(zone):
547 for v in zone._versions:
548 print('VERSION', v.id)
549 for (name, n) in v.nodes.items():
550 for rdataset in n:
551 print(rdataset.to_text(name))
552
553 def test_vzone_open_txn_pins_versions(vzone):
554 assert len(vzone._versions) == 1
555 vzone.set_max_versions(None) # unlimited!
556 with vzone.writer() as txn:
557 txn.update_serial()
558 with vzone.writer() as txn:
559 txn.update_serial()
560 with vzone.writer() as txn:
561 txn.update_serial()
562 with vzone.reader(id=2) as txn:
563 vzone.set_max_versions(1)
564 with vzone.reader(id=3) as txn:
565 rdataset = txn.get('@', 'soa')
566 assert rdataset[0].serial == 2
567 assert len(vzone._versions) == 4
568 assert len(vzone._versions) == 1
569 rdataset = vzone.find_rdataset('@', 'soa')
570 assert vzone._versions[0].id == 5
571 assert rdataset[0].serial == 4
572
573
574 try:
575 import threading
576
577 one_got_lock = threading.Event()
578
579 def run_one(zone):
580 with zone.writer() as txn:
581 one_got_lock.set()
582 # wait until two blocks
583 while len(zone._write_waiters) == 0:
584 time.sleep(0.01)
585 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.98')
586 txn.add('a98', rds)
587
588 def run_two(zone):
589 # wait until one has the lock so we know we will block if we
590 # get the call done before the sleep in one completes
591 one_got_lock.wait()
592 with zone.writer() as txn:
593 rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.99')
594 txn.add('a99', rds)
595
596 def test_vzone_concurrency(vzone):
597 t1 = threading.Thread(target=run_one, args=(vzone,))
598 t1.start()
599 t2 = threading.Thread(target=run_two, args=(vzone,))
600 t2.start()
601 t1.join()
602 t2.join()
603 with vzone.reader() as txn:
604 assert txn.name_exists('a98')
605 assert txn.name_exists('a99')
606
607 except ImportError: # pragma: no cover
608 pass
00 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
11
2 import hashlib
32 import unittest
3 from unittest.mock import Mock
44 import time
5 import base64
56
67 import dns.rcode
78 import dns.tsig
89 import dns.tsigkeyring
910 import dns.message
11 import dns.rdtypes.ANY.TKEY
1012
1113 keyring = dns.tsigkeyring.from_text(
1214 {
1618
1719 keyname = dns.name.from_text('keyname')
1820
21
1922 class TSIGTestCase(unittest.TestCase):
2023
2124 def test_get_context(self):
2932 with self.assertRaises(NotImplementedError):
3033 dns.tsig.get_context(bogus)
3134
35 def test_tsig_message_properties(self):
36 m = dns.message.make_query('example', 'a')
37 self.assertIsNone(m.keyname)
38 self.assertIsNone(m.keyalgorithm)
39 self.assertIsNone(m.tsig_error)
40 m.use_tsig(keyring, keyname)
41 self.assertEqual(m.keyname, keyname)
42 self.assertEqual(m.keyalgorithm, dns.tsig.default_algorithm)
43 self.assertEqual(m.tsig_error, dns.rcode.NOERROR)
44 m = dns.message.make_query('example', 'a')
45 m.use_tsig(keyring, keyname, tsig_error=dns.rcode.BADKEY)
46 self.assertEqual(m.tsig_error, dns.rcode.BADKEY)
47
48 def test_verify_mac_for_context(self):
49 key = dns.tsig.Key('foo.com', 'abcd', 'hmac-sha512')
50 ctx = dns.tsig.get_context(key)
51 bad_expected = b'xxxxxxxxxx'
52 with self.assertRaises(dns.tsig.BadSignature):
53 ctx.verify(bad_expected)
54
55 def test_validate(self):
56 # make message and grab the TSIG
57 m = dns.message.make_query('example', 'a')
58 m.use_tsig(keyring, keyname, algorithm=dns.tsig.HMAC_SHA256)
59 w = m.to_wire()
60 tsig = m.tsig[0]
61
62 # get the time and create a key with matching characteristics
63 now = int(time.time())
64 key = dns.tsig.Key('foo.com', 'abcd', 'hmac-sha256')
65
66 # add enough to the time to take it over the fudge amount
67 with self.assertRaises(dns.tsig.BadTime):
68 dns.tsig.validate(w, key, dns.name.from_text('foo.com'),
69 tsig, now + 1000, b'', 0)
70
71 # change the key name
72 with self.assertRaises(dns.tsig.BadKey):
73 dns.tsig.validate(w, key, dns.name.from_text('bar.com'),
74 tsig, now, b'', 0)
75
76 # change the key algorithm
77 key = dns.tsig.Key('foo.com', 'abcd', 'hmac-sha512')
78 with self.assertRaises(dns.tsig.BadAlgorithm):
79 dns.tsig.validate(w, key, dns.name.from_text('foo.com'),
80 tsig, now, b'', 0)
81
82 def test_gssapi_context(self):
83 def verify_signature(data, mac):
84 if data == b'throw':
85 raise Exception
86 return None
87
88 # mock out the gssapi context to return some dummy values
89 gssapi_context_mock = Mock()
90 gssapi_context_mock.get_signature.return_value = b'xxxxxxxxxxx'
91 gssapi_context_mock.verify_signature.side_effect = verify_signature
92
93 # create the key and add it to the keyring
94 keyname = 'gsstsigtest'
95 key = dns.tsig.Key(keyname, gssapi_context_mock, 'gss-tsig')
96 ctx = dns.tsig.get_context(key)
97 self.assertEqual(ctx.name, 'gss-tsig')
98 gsskeyname = dns.name.from_text(keyname)
99 keyring[gsskeyname] = key
100
101 # make sure we can get the keyring (no exception == success)
102 text = dns.tsigkeyring.to_text(keyring)
103 self.assertNotEqual(text, '')
104
105 # test exceptional case for _verify_mac_for_context
106 with self.assertRaises(dns.tsig.BadSignature):
107 ctx.update(b'throw')
108 ctx.verify(b'bogus')
109 gssapi_context_mock.verify_signature.assert_called()
110 self.assertEqual(gssapi_context_mock.verify_signature.call_count, 1)
111
112 # simulate case where TKEY message is used to establish the context;
113 # first, the query from the client
114 tkey_message = dns.message.make_query(keyname, 'tkey', 'any')
115
116 # test existent/non-existent keys in the keyring
117 adapted_keyring = dns.tsig.GSSTSigAdapter(keyring)
118
119 fetched_key = adapted_keyring(tkey_message, gsskeyname)
120 self.assertEqual(fetched_key, key)
121 key = adapted_keyring(None, gsskeyname)
122 self.assertEqual(fetched_key, key)
123 key = adapted_keyring(tkey_message, "dummy")
124 self.assertEqual(key, None)
125
126 # create a response, TKEY and turn it into bytes, simulating the server
127 # sending the response to the query
128 tkey_response = dns.message.make_response(tkey_message)
129 key = base64.b64decode('KEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEYKEY')
130 tkey = dns.rdtypes.ANY.TKEY.TKEY(dns.rdataclass.ANY,
131 dns.rdatatype.TKEY,
132 dns.name.from_text('gss-tsig.'),
133 1594203795, 1594206664,
134 3, 0, key)
135
136 # add the TKEY answer and sign it
137 tkey_response.set_rcode(dns.rcode.NOERROR)
138 tkey_response.answer = [
139 dns.rrset.from_rdata(dns.name.from_text(keyname), 0, tkey)]
140 tkey_response.use_tsig(keyring=dns.tsig.GSSTSigAdapter(keyring),
141 keyname=gsskeyname,
142 algorithm=dns.tsig.GSS_TSIG)
143
144 # "send" it to the client
145 tkey_wire = tkey_response.to_wire()
146
147 # grab the response from the "server" and simulate the client side
148 dns.message.from_wire(tkey_wire, dns.tsig.GSSTSigAdapter(keyring))
149
150 # assertions to make sure the "gssapi" functions were called
151 gssapi_context_mock.get_signature.assert_called()
152 self.assertEqual(gssapi_context_mock.get_signature.call_count, 1)
153 gssapi_context_mock.verify_signature.assert_called()
154 self.assertEqual(gssapi_context_mock.verify_signature.call_count, 2)
155 gssapi_context_mock.step.assert_called()
156 self.assertEqual(gssapi_context_mock.step.call_count, 1)
157
158 # create example message and go to/from wire to simulate sign/verify
159 # of regular messages
160 a_message = dns.message.make_query('example', 'a')
161 a_message.use_tsig(dns.tsig.GSSTSigAdapter(keyring), gsskeyname)
162 a_wire = a_message.to_wire()
163 # not raising is passing
164 dns.message.from_wire(a_wire, dns.tsig.GSSTSigAdapter(keyring))
165
166 # assertions to make sure the "gssapi" functions were called again
167 gssapi_context_mock.get_signature.assert_called()
168 self.assertEqual(gssapi_context_mock.get_signature.call_count, 2)
169 gssapi_context_mock.verify_signature.assert_called()
170 self.assertEqual(gssapi_context_mock.verify_signature.call_count, 3)
171
32172 def test_sign_and_validate(self):
33173 m = dns.message.make_query('example', 'a')
34174 m.use_tsig(keyring, keyname)
36176 # not raising is passing
37177 dns.message.from_wire(w, keyring)
38178
179 def test_validate_with_bad_keyring(self):
180 m = dns.message.make_query('example', 'a')
181 m.use_tsig(keyring, keyname)
182 w = m.to_wire()
183
184 # keyring == None is an error
185 with self.assertRaises(dns.message.UnknownTSIGKey):
186 dns.message.from_wire(w, None)
187 # callable keyring that returns None is an error
188 with self.assertRaises(dns.message.UnknownTSIGKey):
189 dns.message.from_wire(w, lambda m, n: None)
190
39191 def test_sign_and_validate_with_other_data(self):
40192 m = dns.message.make_query('example', 'a')
41 other = b'other data'
42193 m.use_tsig(keyring, keyname, other_data=b'other')
43194 w = m.to_wire()
44195 # not raising is passing
45196 dns.message.from_wire(w, keyring)
197
198 def test_sign_respond_and_validate(self):
199 mq = dns.message.make_query('example', 'a')
200 mq.use_tsig(keyring, keyname)
201 wq = mq.to_wire()
202 mq_with_tsig = dns.message.from_wire(wq, keyring)
203 mr = dns.message.make_response(mq)
204 mr.use_tsig(keyring, keyname)
205 wr = mr.to_wire()
206 dns.message.from_wire(wr, keyring, request_mac=mq_with_tsig.mac)
46207
47208 def make_message_pair(self, qname='example', rdtype='A', tsig_error=0):
48209 q = dns.message.make_query(qname, rdtype)
64225 def bad():
65226 dns.message.from_wire(w, keyring=keyring, request_mac=q.mac)
66227 self.assertRaises(ex, bad)
228
229 def _test_truncated_algorithm(self, alg, length):
230 key = dns.tsig.Key('foo', b'abcdefg', algorithm=alg)
231 q = dns.message.make_query('example', 'a')
232 q.use_tsig(key)
233 q2 = dns.message.from_wire(q.to_wire(), keyring=key)
234
235 self.assertTrue(q2.had_tsig)
236 self.assertEqual(q2.tsig[0].algorithm, q.tsig[0].algorithm)
237 self.assertEqual(len(q2.tsig[0].mac), length // 8)
238
239 def test_hmac_sha256_128(self):
240 self._test_truncated_algorithm(dns.tsig.HMAC_SHA256_128, 128)
241
242 def test_hmac_sha384_192(self):
243 self._test_truncated_algorithm(dns.tsig.HMAC_SHA384_192, 192)
244
245 def test_hmac_sha512_256(self):
246 self._test_truncated_algorithm(dns.tsig.HMAC_SHA512_256, 256)
247
248 def _test_text_format(self, alg):
249 key = dns.tsig.Key('foo', b'abcdefg', algorithm=alg)
250 q = dns.message.make_query('example', 'a')
251 q.use_tsig(key)
252 _ = q.to_wire()
253
254 text = q.tsig[0].to_text()
255 tsig2 = dns.rdata.from_text('ANY', 'TSIG', text)
256 self.assertEqual(tsig2, q.tsig[0])
257
258 q = dns.message.make_query('example', 'a')
259 q.use_tsig(key, other_data=b'abc')
260 q.use_tsig(key)
261 _ = q.to_wire()
262
263 text = q.tsig[0].to_text()
264 tsig2 = dns.rdata.from_text('ANY', 'TSIG', text)
265 self.assertEqual(tsig2, q.tsig[0])
266
267 def test_text_hmac_sha256_128(self):
268 self._test_text_format(dns.tsig.HMAC_SHA256_128)
269
270 def test_text_hmac_sha384_192(self):
271 self._test_text_format(dns.tsig.HMAC_SHA384_192)
272
273 def test_text_hmac_sha512_256(self):
274 self._test_text_format(dns.tsig.HMAC_SHA512_256)
275
276 def test_non_gss_key_repr(self):
277 key = dns.tsig.Key('foo', b'0123456789abcdef' * 2,
278 algorithm=dns.tsig.HMAC_SHA256)
279 self.assertEqual(repr(key),
280 "<DNS key name='foo.', algorithm='hmac-sha256.', " +
281 "secret=" +
282 "'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY='>")
283
284 def test_gss_key_repr(self):
285 key = dns.tsig.Key('foo', None,
286 algorithm=dns.tsig.GSS_TSIG)
287 self.assertEqual(repr(key),
288 "<DNS key name='foo.', algorithm='gss-tsig.'>")
2121 def test_bind_style_no_unit(self):
2222 with self.assertRaises(dns.ttl.BadTTL):
2323 dns.ttl.from_text('1d5')
24
25 def test_bind_style_leading_unit(self):
26 with self.assertRaises(dns.ttl.BadTTL):
27 dns.ttl.from_text('s')
28
29 def test_bind_style_unit_without_digits(self):
30 with self.assertRaises(dns.ttl.BadTTL):
31 dns.ttl.from_text('1mw')
32
33 def test_empty(self):
34 with self.assertRaises(dns.ttl.BadTTL):
35 dns.ttl.from_text('')
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
01
12 import unittest
23
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import asyncio
3
4 import pytest
5
6 import dns.asyncbackend
7 import dns.asyncquery
8 import dns.message
9 import dns.query
10 import dns.tsigkeyring
11 import dns.versioned
12 import dns.xfr
13
14 # Some tests use a "nano nameserver" for testing. It requires trio
15 # and threading, so try to import it and if it doesn't work, skip
16 # those tests.
17 try:
18 from .nanonameserver import Server
19 _nanonameserver_available = True
20 except ImportError:
21 _nanonameserver_available = False
22 class Server(object):
23 pass
24
25 axfr = '''id 1
26 opcode QUERY
27 rcode NOERROR
28 flags AA
29 ;QUESTION
30 example. IN AXFR
31 ;ANSWER
32 @ 3600 IN SOA foo bar 1 2 3 4 5
33 @ 3600 IN NS ns1
34 @ 3600 IN NS ns2
35 bar.foo 300 IN MX 0 blaz.foo
36 ns1 3600 IN A 10.0.0.1
37 ns2 3600 IN A 10.0.0.2
38 @ 3600 IN SOA foo bar 1 2 3 4 5
39 '''
40
41 axfr1 = '''id 1
42 opcode QUERY
43 rcode NOERROR
44 flags AA
45 ;QUESTION
46 example. IN AXFR
47 ;ANSWER
48 @ 3600 IN SOA foo bar 1 2 3 4 5
49 @ 3600 IN NS ns1
50 @ 3600 IN NS ns2
51 '''
52 axfr2 = '''id 1
53 opcode QUERY
54 rcode NOERROR
55 flags AA
56 ;ANSWER
57 bar.foo 300 IN MX 0 blaz.foo
58 ns1 3600 IN A 10.0.0.1
59 ns2 3600 IN A 10.0.0.2
60 @ 3600 IN SOA foo bar 1 2 3 4 5
61 '''
62
63 base = """@ 3600 IN SOA foo bar 1 2 3 4 5
64 @ 3600 IN NS ns1
65 @ 3600 IN NS ns2
66 bar.foo 300 IN MX 0 blaz.foo
67 ns1 3600 IN A 10.0.0.1
68 ns2 3600 IN A 10.0.0.2
69 """
70
71 axfr_unexpected_origin = '''id 1
72 opcode QUERY
73 rcode NOERROR
74 flags AA
75 ;QUESTION
76 example. IN AXFR
77 ;ANSWER
78 @ 3600 IN SOA foo bar 1 2 3 4 5
79 @ 3600 IN SOA foo bar 1 2 3 4 7
80 '''
81
82 ixfr = '''id 1
83 opcode QUERY
84 rcode NOERROR
85 flags AA
86 ;QUESTION
87 example. IN IXFR
88 ;ANSWER
89 @ 3600 IN SOA foo bar 4 2 3 4 5
90 @ 3600 IN SOA foo bar 1 2 3 4 5
91 bar.foo 300 IN MX 0 blaz.foo
92 ns2 3600 IN A 10.0.0.2
93 @ 3600 IN SOA foo bar 2 2 3 4 5
94 ns2 3600 IN A 10.0.0.4
95 @ 3600 IN SOA foo bar 2 2 3 4 5
96 @ 3600 IN SOA foo bar 3 2 3 4 5
97 ns3 3600 IN A 10.0.0.3
98 @ 3600 IN SOA foo bar 3 2 3 4 5
99 @ 3600 IN NS ns2
100 @ 3600 IN SOA foo bar 4 2 3 4 5
101 @ 3600 IN SOA foo bar 4 2 3 4 5
102 '''
103
104 compressed_ixfr = '''id 1
105 opcode QUERY
106 rcode NOERROR
107 flags AA
108 ;QUESTION
109 example. IN IXFR
110 ;ANSWER
111 @ 3600 IN SOA foo bar 4 2 3 4 5
112 @ 3600 IN SOA foo bar 1 2 3 4 5
113 bar.foo 300 IN MX 0 blaz.foo
114 ns2 3600 IN A 10.0.0.2
115 @ 3600 IN NS ns2
116 @ 3600 IN SOA foo bar 4 2 3 4 5
117 ns2 3600 IN A 10.0.0.4
118 ns3 3600 IN A 10.0.0.3
119 @ 3600 IN SOA foo bar 4 2 3 4 5
120 '''
121
122 ixfr_expected = """@ 3600 IN SOA foo bar 4 2 3 4 5
123 @ 3600 IN NS ns1
124 ns1 3600 IN A 10.0.0.1
125 ns2 3600 IN A 10.0.0.4
126 ns3 3600 IN A 10.0.0.3
127 """
128
129 ixfr_first_message = '''id 1
130 opcode QUERY
131 rcode NOERROR
132 flags AA
133 ;QUESTION
134 example. IN IXFR
135 ;ANSWER
136 @ 3600 IN SOA foo bar 4 2 3 4 5
137 '''
138
139 ixfr_header = '''id 1
140 opcode QUERY
141 rcode NOERROR
142 flags AA
143 ;ANSWER
144 '''
145
146 ixfr_body = [
147 '@ 3600 IN SOA foo bar 1 2 3 4 5',
148 'bar.foo 300 IN MX 0 blaz.foo',
149 'ns2 3600 IN A 10.0.0.2',
150 '@ 3600 IN SOA foo bar 2 2 3 4 5',
151 'ns2 3600 IN A 10.0.0.4',
152 '@ 3600 IN SOA foo bar 2 2 3 4 5',
153 '@ 3600 IN SOA foo bar 3 2 3 4 5',
154 'ns3 3600 IN A 10.0.0.3',
155 '@ 3600 IN SOA foo bar 3 2 3 4 5',
156 '@ 3600 IN NS ns2',
157 '@ 3600 IN SOA foo bar 4 2 3 4 5',
158 '@ 3600 IN SOA foo bar 4 2 3 4 5',
159 ]
160
161 ixfrs = [ixfr_first_message]
162 ixfrs.extend([ixfr_header + l for l in ixfr_body])
163
164 good_empty_ixfr = '''id 1
165 opcode QUERY
166 rcode NOERROR
167 flags AA
168 ;QUESTION
169 example. IN IXFR
170 ;ANSWER
171 @ 3600 IN SOA foo bar 1 2 3 4 5
172 '''
173
174 retry_tcp_ixfr = '''id 1
175 opcode QUERY
176 rcode NOERROR
177 flags AA
178 ;QUESTION
179 example. IN IXFR
180 ;ANSWER
181 @ 3600 IN SOA foo bar 5 2 3 4 5
182 '''
183
184 bad_empty_ixfr = '''id 1
185 opcode QUERY
186 rcode NOERROR
187 flags AA
188 ;QUESTION
189 example. IN IXFR
190 ;ANSWER
191 @ 3600 IN SOA foo bar 4 2 3 4 5
192 @ 3600 IN SOA foo bar 4 2 3 4 5
193 '''
194
195 unexpected_end_ixfr = '''id 1
196 opcode QUERY
197 rcode NOERROR
198 flags AA
199 ;QUESTION
200 example. IN IXFR
201 ;ANSWER
202 @ 3600 IN SOA foo bar 4 2 3 4 5
203 @ 3600 IN SOA foo bar 1 2 3 4 5
204 bar.foo 300 IN MX 0 blaz.foo
205 ns2 3600 IN A 10.0.0.2
206 @ 3600 IN NS ns2
207 @ 3600 IN SOA foo bar 3 2 3 4 5
208 ns2 3600 IN A 10.0.0.4
209 ns3 3600 IN A 10.0.0.3
210 @ 3600 IN SOA foo bar 4 2 3 4 5
211 '''
212
213 unexpected_end_ixfr_2 = '''id 1
214 opcode QUERY
215 rcode NOERROR
216 flags AA
217 ;QUESTION
218 example. IN IXFR
219 ;ANSWER
220 @ 3600 IN SOA foo bar 4 2 3 4 5
221 @ 3600 IN SOA foo bar 1 2 3 4 5
222 bar.foo 300 IN MX 0 blaz.foo
223 ns2 3600 IN A 10.0.0.2
224 @ 3600 IN NS ns2
225 '''
226
227 bad_serial_ixfr = '''id 1
228 opcode QUERY
229 rcode NOERROR
230 flags AA
231 ;QUESTION
232 example. IN IXFR
233 ;ANSWER
234 @ 3600 IN SOA foo bar 4 2 3 4 5
235 @ 3600 IN SOA foo bar 2 2 3 4 5
236 bar.foo 300 IN MX 0 blaz.foo
237 ns2 3600 IN A 10.0.0.2
238 @ 3600 IN NS ns2
239 @ 3600 IN SOA foo bar 4 2 3 4 5
240 ns2 3600 IN A 10.0.0.4
241 ns3 3600 IN A 10.0.0.3
242 @ 3600 IN SOA foo bar 4 2 3 4 5
243 '''
244
245 ixfr_axfr = '''id 1
246 opcode QUERY
247 rcode NOERROR
248 flags AA
249 ;QUESTION
250 example. IN IXFR
251 ;ANSWER
252 @ 3600 IN SOA foo bar 1 2 3 4 5
253 @ 3600 IN NS ns1
254 @ 3600 IN NS ns2
255 bar.foo 300 IN MX 0 blaz.foo
256 ns1 3600 IN A 10.0.0.1
257 ns2 3600 IN A 10.0.0.2
258 @ 3600 IN SOA foo bar 1 2 3 4 5
259 '''
260
261 def test_basic_axfr():
262 z = dns.versioned.Zone('example.')
263 m = dns.message.from_text(axfr, origin=z.origin,
264 one_rr_per_rrset=True)
265 with dns.xfr.Inbound(z, dns.rdatatype.AXFR) as xfr:
266 done = xfr.process_message(m)
267 assert done
268 ez = dns.zone.from_text(base, 'example.')
269 assert z == ez
270
271 def test_basic_axfr_two_parts():
272 z = dns.versioned.Zone('example.')
273 m1 = dns.message.from_text(axfr1, origin=z.origin,
274 one_rr_per_rrset=True)
275 m2 = dns.message.from_text(axfr2, origin=z.origin,
276 one_rr_per_rrset=True)
277 with dns.xfr.Inbound(z, dns.rdatatype.AXFR) as xfr:
278 done = xfr.process_message(m1)
279 assert not done
280 done = xfr.process_message(m2)
281 assert done
282 ez = dns.zone.from_text(base, 'example.')
283 assert z == ez
284
285 def test_axfr_unexpected_origin():
286 z = dns.versioned.Zone('example.')
287 m = dns.message.from_text(axfr_unexpected_origin, origin=z.origin,
288 one_rr_per_rrset=True)
289 with dns.xfr.Inbound(z, dns.rdatatype.AXFR) as xfr:
290 with pytest.raises(dns.exception.FormError):
291 xfr.process_message(m)
292
293 def test_basic_ixfr():
294 z = dns.zone.from_text(base, 'example.',
295 zone_factory=dns.versioned.Zone)
296 m = dns.message.from_text(ixfr, origin=z.origin,
297 one_rr_per_rrset=True)
298 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
299 done = xfr.process_message(m)
300 assert done
301 ez = dns.zone.from_text(ixfr_expected, 'example.')
302 assert z == ez
303
304 def test_compressed_ixfr():
305 z = dns.zone.from_text(base, 'example.',
306 zone_factory=dns.versioned.Zone)
307 m = dns.message.from_text(compressed_ixfr, origin=z.origin,
308 one_rr_per_rrset=True)
309 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
310 done = xfr.process_message(m)
311 assert done
312 ez = dns.zone.from_text(ixfr_expected, 'example.')
313 assert z == ez
314
315 def test_basic_ixfr_many_parts():
316 z = dns.zone.from_text(base, 'example.',
317 zone_factory=dns.versioned.Zone)
318 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
319 done = False
320 for text in ixfrs:
321 assert not done
322 m = dns.message.from_text(text, origin=z.origin,
323 one_rr_per_rrset=True)
324 done = xfr.process_message(m)
325 assert done
326 ez = dns.zone.from_text(ixfr_expected, 'example.')
327 assert z == ez
328
329 def test_good_empty_ixfr():
330 z = dns.zone.from_text(ixfr_expected, 'example.',
331 zone_factory=dns.versioned.Zone)
332 m = dns.message.from_text(good_empty_ixfr, origin=z.origin,
333 one_rr_per_rrset=True)
334 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
335 done = xfr.process_message(m)
336 assert done
337 ez = dns.zone.from_text(ixfr_expected, 'example.')
338 assert z == ez
339
340 def test_retry_tcp_ixfr():
341 z = dns.zone.from_text(ixfr_expected, 'example.',
342 zone_factory=dns.versioned.Zone)
343 m = dns.message.from_text(retry_tcp_ixfr, origin=z.origin,
344 one_rr_per_rrset=True)
345 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1, is_udp=True) as xfr:
346 with pytest.raises(dns.xfr.UseTCP):
347 xfr.process_message(m)
348
349 def test_bad_empty_ixfr():
350 z = dns.zone.from_text(ixfr_expected, 'example.',
351 zone_factory=dns.versioned.Zone)
352 m = dns.message.from_text(bad_empty_ixfr, origin=z.origin,
353 one_rr_per_rrset=True)
354 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=3) as xfr:
355 with pytest.raises(dns.exception.FormError):
356 xfr.process_message(m)
357
358 def test_serial_went_backwards_ixfr():
359 z = dns.zone.from_text(ixfr_expected, 'example.',
360 zone_factory=dns.versioned.Zone)
361 m = dns.message.from_text(bad_empty_ixfr, origin=z.origin,
362 one_rr_per_rrset=True)
363 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=5) as xfr:
364 with pytest.raises(dns.xfr.SerialWentBackwards):
365 xfr.process_message(m)
366
367 def test_ixfr_is_axfr():
368 z = dns.zone.from_text(base, 'example.',
369 zone_factory=dns.versioned.Zone)
370 m = dns.message.from_text(ixfr_axfr, origin=z.origin,
371 one_rr_per_rrset=True)
372 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=0xffffffff) as xfr:
373 done = xfr.process_message(m)
374 assert done
375 ez = dns.zone.from_text(base, 'example.')
376 assert z == ez
377
378 def test_ixfr_requires_serial():
379 z = dns.zone.from_text(base, 'example.',
380 zone_factory=dns.versioned.Zone)
381 with pytest.raises(ValueError):
382 dns.xfr.Inbound(z, dns.rdatatype.IXFR)
383
384 def test_ixfr_unexpected_end_bad_diff_sequence():
385 # This is where we get the end serial, but haven't seen all of
386 # the expected diffs
387 z = dns.zone.from_text(base, 'example.',
388 zone_factory=dns.versioned.Zone)
389 m = dns.message.from_text(unexpected_end_ixfr, origin=z.origin,
390 one_rr_per_rrset=True)
391 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
392 with pytest.raises(dns.exception.FormError):
393 xfr.process_message(m)
394
395 def test_udp_ixfr_unexpected_end_just_stops():
396 # This is where everything looks good, but the IXFR just stops
397 # in the middle.
398 z = dns.zone.from_text(base, 'example.',
399 zone_factory=dns.versioned.Zone)
400 m = dns.message.from_text(unexpected_end_ixfr_2, origin=z.origin,
401 one_rr_per_rrset=True)
402 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1, is_udp=True) as xfr:
403 with pytest.raises(dns.exception.FormError):
404 xfr.process_message(m)
405
406 def test_ixfr_bad_serial():
407 z = dns.zone.from_text(base, 'example.',
408 zone_factory=dns.versioned.Zone)
409 m = dns.message.from_text(bad_serial_ixfr, origin=z.origin,
410 one_rr_per_rrset=True)
411 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
412 with pytest.raises(dns.exception.FormError):
413 xfr.process_message(m)
414
415 def test_no_udp_with_axfr():
416 z = dns.versioned.Zone('example.')
417 with pytest.raises(ValueError):
418 with dns.xfr.Inbound(z, dns.rdatatype.AXFR, is_udp=True) as xfr:
419 pass
420
421 refused = '''id 1
422 opcode QUERY
423 rcode REFUSED
424 flags AA
425 ;QUESTION
426 example. IN AXFR
427 '''
428
429 bad_qname = '''id 1
430 opcode QUERY
431 rcode NOERROR
432 flags AA
433 ;QUESTION
434 not-example. IN IXFR
435 '''
436
437 bad_qtype = '''id 1
438 opcode QUERY
439 rcode NOERROR
440 flags AA
441 ;QUESTION
442 example. IN AXFR
443 '''
444
445 soa_not_first = '''id 1
446 opcode QUERY
447 rcode NOERROR
448 flags AA
449 ;QUESTION
450 example. IN IXFR
451 ;ANSWER
452 bar.foo 300 IN MX 0 blaz.foo
453 '''
454
455 soa_not_first_2 = '''id 1
456 opcode QUERY
457 rcode NOERROR
458 flags AA
459 ;QUESTION
460 example. IN IXFR
461 ;ANSWER
462 @ 300 IN MX 0 blaz.foo
463 '''
464
465 no_answer = '''id 1
466 opcode QUERY
467 rcode NOERROR
468 flags AA
469 ;QUESTION
470 example. IN IXFR
471 ;ADDITIONAL
472 bar.foo 300 IN MX 0 blaz.foo
473 '''
474
475 axfr_answers_after_final_soa = '''id 1
476 opcode QUERY
477 rcode NOERROR
478 flags AA
479 ;QUESTION
480 example. IN AXFR
481 ;ANSWER
482 @ 3600 IN SOA foo bar 1 2 3 4 5
483 @ 3600 IN NS ns1
484 @ 3600 IN NS ns2
485 bar.foo 300 IN MX 0 blaz.foo
486 ns1 3600 IN A 10.0.0.1
487 ns2 3600 IN A 10.0.0.2
488 @ 3600 IN SOA foo bar 1 2 3 4 5
489 ns3 3600 IN A 10.0.0.3
490 '''
491
492 def test_refused():
493 z = dns.zone.from_text(base, 'example.',
494 zone_factory=dns.versioned.Zone)
495 m = dns.message.from_text(refused, origin=z.origin,
496 one_rr_per_rrset=True)
497 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
498 with pytest.raises(dns.xfr.TransferError):
499 xfr.process_message(m)
500
501 def test_bad_qname():
502 z = dns.zone.from_text(base, 'example.',
503 zone_factory=dns.versioned.Zone)
504 m = dns.message.from_text(bad_qname, origin=z.origin,
505 one_rr_per_rrset=True)
506 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
507 with pytest.raises(dns.exception.FormError):
508 xfr.process_message(m)
509
510 def test_bad_qtype():
511 z = dns.zone.from_text(base, 'example.',
512 zone_factory=dns.versioned.Zone)
513 m = dns.message.from_text(bad_qtype, origin=z.origin,
514 one_rr_per_rrset=True)
515 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
516 with pytest.raises(dns.exception.FormError):
517 xfr.process_message(m)
518
519 def test_soa_not_first():
520 z = dns.zone.from_text(base, 'example.',
521 zone_factory=dns.versioned.Zone)
522 m = dns.message.from_text(soa_not_first, origin=z.origin,
523 one_rr_per_rrset=True)
524 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
525 with pytest.raises(dns.exception.FormError):
526 xfr.process_message(m)
527 m = dns.message.from_text(soa_not_first_2, origin=z.origin,
528 one_rr_per_rrset=True)
529 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
530 with pytest.raises(dns.exception.FormError):
531 xfr.process_message(m)
532
533 def test_no_answer():
534 z = dns.zone.from_text(base, 'example.',
535 zone_factory=dns.versioned.Zone)
536 m = dns.message.from_text(no_answer, origin=z.origin,
537 one_rr_per_rrset=True)
538 with dns.xfr.Inbound(z, dns.rdatatype.IXFR, serial=1) as xfr:
539 with pytest.raises(dns.exception.FormError):
540 xfr.process_message(m)
541
542 def test_axfr_answers_after_final_soa():
543 z = dns.versioned.Zone('example.')
544 m = dns.message.from_text(axfr_answers_after_final_soa, origin=z.origin,
545 one_rr_per_rrset=True)
546 with dns.xfr.Inbound(z, dns.rdatatype.AXFR) as xfr:
547 with pytest.raises(dns.exception.FormError):
548 xfr.process_message(m)
549
550 keyring = dns.tsigkeyring.from_text(
551 {
552 'keyname.': 'NjHwPsMKjdN++dOfE5iAiQ=='
553 }
554 )
555
556 keyname = dns.name.from_text('keyname')
557
558 def test_make_query_basic():
559 z = dns.versioned.Zone('example.')
560 (q, s) = dns.xfr.make_query(z)
561 assert q.question[0].rdtype == dns.rdatatype.AXFR
562 assert s is None
563 (q, s) = dns.xfr.make_query(z, serial=None)
564 assert q.question[0].rdtype == dns.rdatatype.AXFR
565 assert s is None
566 (q, s) = dns.xfr.make_query(z, serial=10)
567 assert q.question[0].rdtype == dns.rdatatype.IXFR
568 assert q.authority[0].rdtype == dns.rdatatype.SOA
569 assert q.authority[0][0].serial == 10
570 assert s == 10
571 with z.writer() as txn:
572 txn.add('@', 300, dns.rdata.from_text('in', 'soa', '. . 1 2 3 4 5'))
573 (q, s) = dns.xfr.make_query(z)
574 assert q.question[0].rdtype == dns.rdatatype.IXFR
575 assert q.authority[0].rdtype == dns.rdatatype.SOA
576 assert q.authority[0][0].serial == 1
577 assert s == 1
578 (q, s) = dns.xfr.make_query(z, keyring=keyring, keyname=keyname)
579 assert q.question[0].rdtype == dns.rdatatype.IXFR
580 assert q.authority[0].rdtype == dns.rdatatype.SOA
581 assert q.authority[0][0].serial == 1
582 assert s == 1
583 assert q.keyname == keyname
584
585
586 def test_make_query_bad_serial():
587 z = dns.versioned.Zone('example.')
588 with pytest.raises(ValueError):
589 dns.xfr.make_query(z, serial='hi')
590 with pytest.raises(ValueError):
591 dns.xfr.make_query(z, serial=-1)
592 with pytest.raises(ValueError):
593 dns.xfr.make_query(z, serial=4294967296)
594
595 def test_extract_serial_from_query():
596 z = dns.versioned.Zone('example.')
597 (q, s) = dns.xfr.make_query(z)
598 xs = dns.xfr.extract_serial_from_query(q)
599 assert s is None
600 assert s == xs
601 (q, s) = dns.xfr.make_query(z, serial=10)
602 xs = dns.xfr.extract_serial_from_query(q)
603 assert s == 10
604 assert s == xs
605 q = dns.message.make_query('example', 'a')
606 with pytest.raises(ValueError):
607 dns.xfr.extract_serial_from_query(q)
608
609
610 class XFRNanoNameserver(Server):
611
612 def __init__(self):
613 super().__init__(origin=dns.name.from_text('example'))
614
615 def handle(self, request):
616 try:
617 if request.message.question[0].rdtype == dns.rdatatype.IXFR:
618 text = ixfr
619 else:
620 text = axfr
621 r = dns.message.from_text(text, one_rr_per_rrset=True,
622 origin=self.origin)
623 r.id = request.message.id
624 return r
625 except Exception:
626 pass
627
628 @pytest.mark.skipif(not _nanonameserver_available,
629 reason="requires nanonameserver")
630 def test_sync_inbound_xfr():
631 with XFRNanoNameserver() as ns:
632 zone = dns.versioned.Zone('example')
633 dns.query.inbound_xfr(ns.tcp_address[0], zone, port=ns.tcp_address[1],
634 udp_mode=dns.query.UDPMode.TRY_FIRST)
635 dns.query.inbound_xfr(ns.tcp_address[0], zone, port=ns.tcp_address[1],
636 udp_mode=dns.query.UDPMode.TRY_FIRST)
637 expected = dns.zone.from_text(ixfr_expected, 'example')
638 assert zone == expected
639
640 async def async_inbound_xfr():
641 with XFRNanoNameserver() as ns:
642 zone = dns.versioned.Zone('example')
643 await dns.asyncquery.inbound_xfr(ns.tcp_address[0], zone,
644 port=ns.tcp_address[1],
645 udp_mode=dns.query.UDPMode.TRY_FIRST)
646 await dns.asyncquery.inbound_xfr(ns.tcp_address[0], zone,
647 port=ns.tcp_address[1],
648 udp_mode=dns.query.UDPMode.TRY_FIRST)
649 expected = dns.zone.from_text(ixfr_expected, 'example')
650 assert zone == expected
651
652 @pytest.mark.skipif(not _nanonameserver_available,
653 reason="requires nanonameserver")
654 def test_asyncio_inbound_xfr():
655 dns.asyncbackend.set_default_backend('asyncio')
656 async def run():
657 await async_inbound_xfr()
658 try:
659 runner = asyncio.run
660 except AttributeError:
661 # this is only needed for 3.6
662 def old_runner(awaitable):
663 loop = asyncio.get_event_loop()
664 return loop.run_until_complete(awaitable)
665 runner = old_runner
666 runner(run())
667
668 #
669 # We don't need to do this as it's all generic code, but
670 # just for extra caution we do it for each backend.
671 #
672
673 try:
674 import trio
675
676 @pytest.mark.skipif(not _nanonameserver_available,
677 reason="requires nanonameserver")
678 def test_trio_inbound_xfr():
679 dns.asyncbackend.set_default_backend('trio')
680 async def run():
681 await async_inbound_xfr()
682 trio.run(run)
683 except ImportError:
684 pass
685
686 try:
687 import curio
688
689 @pytest.mark.skipif(not _nanonameserver_available,
690 reason="requires nanonameserver")
691 def test_curio_inbound_xfr():
692 dns.asyncbackend.set_default_backend('curio')
693 async def run():
694 await async_inbound_xfr()
695 curio.run(run)
696 except ImportError:
697 pass
698
699
700 class UDPXFRNanoNameserver(Server):
701
702 def __init__(self):
703 super().__init__(origin=dns.name.from_text('example'))
704 self.did_truncation = False
705
706 def handle(self, request):
707 try:
708 if request.message.question[0].rdtype == dns.rdatatype.IXFR:
709 if self.did_truncation:
710 text = ixfr
711 else:
712 text = retry_tcp_ixfr
713 self.did_truncation = True
714 else:
715 text = axfr
716 r = dns.message.from_text(text, one_rr_per_rrset=True,
717 origin=self.origin)
718 r.id = request.message.id
719 return r
720 except Exception:
721 pass
722
723 @pytest.mark.skipif(not _nanonameserver_available,
724 reason="requires nanonameserver")
725 def test_sync_retry_tcp_inbound_xfr():
726 with UDPXFRNanoNameserver() as ns:
727 zone = dns.versioned.Zone('example')
728 dns.query.inbound_xfr(ns.tcp_address[0], zone, port=ns.tcp_address[1],
729 udp_mode=dns.query.UDPMode.TRY_FIRST)
730 dns.query.inbound_xfr(ns.tcp_address[0], zone, port=ns.tcp_address[1],
731 udp_mode=dns.query.UDPMode.TRY_FIRST)
732 expected = dns.zone.from_text(ixfr_expected, 'example')
733 assert zone == expected
734
735 async def udp_async_inbound_xfr():
736 with UDPXFRNanoNameserver() as ns:
737 zone = dns.versioned.Zone('example')
738 await dns.asyncquery.inbound_xfr(ns.tcp_address[0], zone,
739 port=ns.tcp_address[1],
740 udp_mode=dns.query.UDPMode.TRY_FIRST)
741 await dns.asyncquery.inbound_xfr(ns.tcp_address[0], zone,
742 port=ns.tcp_address[1],
743 udp_mode=dns.query.UDPMode.TRY_FIRST)
744 expected = dns.zone.from_text(ixfr_expected, 'example')
745 assert zone == expected
746
747 @pytest.mark.skipif(not _nanonameserver_available,
748 reason="requires nanonameserver")
749 def test_asyncio_retry_tcp_inbound_xfr():
750 dns.asyncbackend.set_default_backend('asyncio')
751 async def run():
752 await udp_async_inbound_xfr()
753 try:
754 runner = asyncio.run
755 except AttributeError:
756 def old_runner(awaitable):
757 loop = asyncio.get_event_loop()
758 return loop.run_until_complete(awaitable)
759 runner = old_runner
760 runner(run())
3131 import dns.rdataclass
3232 import dns.rdatatype
3333 import dns.rrset
34 import dns.versioned
3435 import dns.zone
3536 import dns.node
3637
37 def here(filename):
38 return os.path.join(os.path.dirname(__file__), filename)
38 from tests.util import here
3939
4040 example_text = """$TTL 3600
4141 $ORIGIN example.
172172 @ soa foo bar 1 2 3 4 5
173173 @ 300 ns ns1
174174 @ 300 ns ns2
175 """
176
177 example_comments_text = """$TTL 3600
178 $ORIGIN example.
179 @ soa foo bar (1 ; not kept
180 2 3 4 5) ; kept
181 @ ns ns1
182 @ ns ns2
183 ns1 a 10.0.0.1 ; comment1
184 ns2 a 10.0.0.2 ; comment2
185 """
186
187 example_comments_text_output = """@ 3600 IN SOA foo bar 1 2 3 4 5 ; kept
188 @ 3600 IN NS ns1
189 @ 3600 IN NS ns2
190 ns1 3600 IN A 10.0.0.1 ; comment1
191 ns2 3600 IN A 10.0.0.2 ; comment2
175192 """
176193
177194 _keep_output = True
216233
217234 class ZoneTestCase(unittest.TestCase):
218235
219 def testFromFile1(self): # type: () -> None
236 def testFromFile1(self):
220237 z = dns.zone.from_file(here('example'), 'example')
221238 ok = False
222239 try:
229246 os.unlink(here('example1.out'))
230247 self.assertTrue(ok)
231248
232 def testFromFile2(self): # type: () -> None
249 def testFromFile2(self):
233250 z = dns.zone.from_file(here('example'), 'example', relativize=False)
234251 ok = False
235252 try:
242259 os.unlink(here('example2.out'))
243260 self.assertTrue(ok)
244261
245 def testToFileTextualStream(self): # type: () -> None
262 def testToFileTextualStream(self):
246263 z = dns.zone.from_text(example_text, 'example.', relativize=True)
247264 f = StringIO()
248265 z.to_file(f)
250267 f.close()
251268 self.assertEqual(out, example_text_output)
252269
253 def testToFileBinaryStream(self): # type: () -> None
270 def testToFileBinaryStream(self):
254271 z = dns.zone.from_text(example_text, 'example.', relativize=True)
255272 f = BytesIO()
256273 z.to_file(f, nl=b'\n')
258275 f.close()
259276 self.assertEqual(out, example_text_output.encode())
260277
261 def testToFileTextual(self): # type: () -> None
278 def testToFileTextual(self):
262279 z = dns.zone.from_file(here('example'), 'example')
263280 try:
264281 f = open(here('example3-textual.out'), 'w')
272289 os.unlink(here('example3-textual.out'))
273290 self.assertTrue(ok)
274291
275 def testToFileBinary(self): # type: () -> None
292 def testToFileBinary(self):
276293 z = dns.zone.from_file(here('example'), 'example')
277294 try:
278295 f = open(here('example3-binary.out'), 'wb')
286303 os.unlink(here('example3-binary.out'))
287304 self.assertTrue(ok)
288305
289 def testToFileFilename(self): # type: () -> None
306 def testToFileFilename(self):
290307 z = dns.zone.from_file(here('example'), 'example')
291308 try:
292309 z.to_file(here('example3-filename.out'))
298315 os.unlink(here('example3-filename.out'))
299316 self.assertTrue(ok)
300317
301 def testToText(self): # type: () -> None
318 def testToText(self):
302319 z = dns.zone.from_file(here('example'), 'example')
303320 ok = False
304321 try:
314331 os.unlink(here('example3.out'))
315332 self.assertTrue(ok)
316333
317 def testFromText(self): # type: () -> None
334 def testFromText(self):
318335 z = dns.zone.from_text(example_text, 'example.', relativize=True)
319336 f = StringIO()
320337 names = list(z.nodes.keys())
324341 f.write('\n')
325342 self.assertEqual(f.getvalue(), example_text_output)
326343
327 def testTorture1(self): # type: () -> None
344 def testTorture1(self):
328345 #
329346 # Read a zone containing all our supported RR types, and
330347 # for each RR in the zone, convert the rdata into wire format
345362 origin=o)
346363 self.assertEqual(rd, rd2)
347364
348 def testEqual(self): # type: () -> None
365 def testEqual(self):
349366 z1 = dns.zone.from_text(example_text, 'example.', relativize=True)
350367 z2 = dns.zone.from_text(example_text_output, 'example.',
351368 relativize=True)
352369 self.assertEqual(z1, z2)
353370
354 def testNotEqual1(self): # type: () -> None
371 def testNotEqual1(self):
355372 z1 = dns.zone.from_text(example_text, 'example.', relativize=True)
356373 z2 = dns.zone.from_text(something_quite_similar, 'example.',
357374 relativize=True)
358375 self.assertNotEqual(z1, z2)
359376
360 def testNotEqual2(self): # type: () -> None
377 def testNotEqual2(self):
361378 z1 = dns.zone.from_text(example_text, 'example.', relativize=True)
362379 z2 = dns.zone.from_text(something_different, 'example.',
363380 relativize=True)
364381 self.assertNotEqual(z1, z2)
365382
366 def testNotEqual3(self): # type: () -> None
383 def testNotEqual3(self):
367384 z1 = dns.zone.from_text(example_text, 'example.', relativize=True)
368385 z2 = dns.zone.from_text(something_different, 'example2.',
369386 relativize=True)
370387 self.assertNotEqual(z1, z2)
371388
372 def testFindRdataset1(self): # type: () -> None
389 def testFindRdataset1(self):
373390 z = dns.zone.from_text(example_text, 'example.', relativize=True)
374391 rds = z.find_rdataset('@', 'soa')
375392 exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5')
376393 self.assertEqual(rds, exrds)
377394
378 def testFindRdataset2(self): # type: () -> None
379 def bad(): # type: () -> None
395 def testFindRdataset2(self):
396 def bad():
380397 z = dns.zone.from_text(example_text, 'example.', relativize=True)
381398 z.find_rdataset('@', 'loc')
382399 self.assertRaises(KeyError, bad)
383400
384 def testFindRRset1(self): # type: () -> None
401 def testFindRRset1(self):
385402 z = dns.zone.from_text(example_text, 'example.', relativize=True)
386403 rrs = z.find_rrset('@', 'soa')
387404 exrrs = dns.rrset.from_text('@', 300, 'IN', 'SOA', 'foo bar 1 2 3 4 5')
388405 self.assertEqual(rrs, exrrs)
389406
390 def testFindRRset2(self): # type: () -> None
391 def bad(): # type: () -> None
407 def testFindRRset2(self):
408 def bad():
392409 z = dns.zone.from_text(example_text, 'example.', relativize=True)
393410 z.find_rrset('@', 'loc')
394411 self.assertRaises(KeyError, bad)
395412
396 def testGetRdataset1(self): # type: () -> None
413 def testGetRdataset1(self):
397414 z = dns.zone.from_text(example_text, 'example.', relativize=True)
398415 rds = z.get_rdataset('@', 'soa')
399416 exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5')
400417 self.assertEqual(rds, exrds)
401418
402 def testGetRdataset2(self): # type: () -> None
419 def testGetRdataset2(self):
403420 z = dns.zone.from_text(example_text, 'example.', relativize=True)
404421 rds = z.get_rdataset('@', 'loc')
405422 self.assertTrue(rds is None)
406423
407 def testGetRRset1(self): # type: () -> None
424 def testGetRRset1(self):
408425 z = dns.zone.from_text(example_text, 'example.', relativize=True)
409426 rrs = z.get_rrset('@', 'soa')
410427 exrrs = dns.rrset.from_text('@', 300, 'IN', 'SOA', 'foo bar 1 2 3 4 5')
411428 self.assertEqual(rrs, exrrs)
412429
413 def testGetRRset2(self): # type: () -> None
430 def testGetRRset2(self):
414431 z = dns.zone.from_text(example_text, 'example.', relativize=True)
415432 rrs = z.get_rrset('@', 'loc')
416433 self.assertTrue(rrs is None)
417434
418 def testReplaceRdataset1(self): # type: () -> None
435 def testReplaceRdataset1(self):
419436 z = dns.zone.from_text(example_text, 'example.', relativize=True)
420437 rdataset = dns.rdataset.from_text('in', 'ns', 300, 'ns3', 'ns4')
421438 z.replace_rdataset('@', rdataset)
422439 rds = z.get_rdataset('@', 'ns')
423440 self.assertTrue(rds is rdataset)
424441
425 def testReplaceRdataset2(self): # type: () -> None
442 def testReplaceRdataset2(self):
426443 z = dns.zone.from_text(example_text, 'example.', relativize=True)
427444 rdataset = dns.rdataset.from_text('in', 'txt', 300, '"foo"')
428445 z.replace_rdataset('@', rdataset)
429446 rds = z.get_rdataset('@', 'txt')
430447 self.assertTrue(rds is rdataset)
431448
432 def testDeleteRdataset1(self): # type: () -> None
449 def testDeleteRdataset1(self):
433450 z = dns.zone.from_text(example_text, 'example.', relativize=True)
434451 z.delete_rdataset('@', 'ns')
435452 rds = z.get_rdataset('@', 'ns')
436453 self.assertTrue(rds is None)
437454
438 def testDeleteRdataset2(self): # type: () -> None
455 def testDeleteRdataset2(self):
439456 z = dns.zone.from_text(example_text, 'example.', relativize=True)
440457 z.delete_rdataset('ns1', 'a')
441458 node = z.get_node('ns1')
442459 self.assertTrue(node is None)
443460
444 def testNodeFindRdataset1(self): # type: () -> None
461 def testNodeFindRdataset1(self):
445462 z = dns.zone.from_text(example_text, 'example.', relativize=True)
446463 node = z['@']
447464 rds = node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA)
448465 exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5')
449466 self.assertEqual(rds, exrds)
450467
451 def testNodeFindRdataset2(self): # type: () -> None
452 def bad(): # type: () -> None
468 def testNodeFindRdataset2(self):
469 def bad():
453470 z = dns.zone.from_text(example_text, 'example.', relativize=True)
454471 node = z['@']
455472 node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC)
456473 self.assertRaises(KeyError, bad)
457474
458 def testNodeGetRdataset1(self): # type: () -> None
475 def testNodeGetRdataset1(self):
459476 z = dns.zone.from_text(example_text, 'example.', relativize=True)
460477 node = z['@']
461478 rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA)
462479 exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5')
463480 self.assertEqual(rds, exrds)
464481
465 def testNodeGetRdataset2(self): # type: () -> None
482 def testNodeGetRdataset2(self):
466483 z = dns.zone.from_text(example_text, 'example.', relativize=True)
467484 node = z['@']
468485 rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC)
469486 self.assertTrue(rds is None)
470487
471 def testNodeDeleteRdataset1(self): # type: () -> None
488 def testNodeDeleteRdataset1(self):
472489 z = dns.zone.from_text(example_text, 'example.', relativize=True)
473490 node = z['@']
474491 node.delete_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA)
475492 rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA)
476493 self.assertTrue(rds is None)
477494
478 def testNodeDeleteRdataset2(self): # type: () -> None
495 def testNodeDeleteRdataset2(self):
479496 z = dns.zone.from_text(example_text, 'example.', relativize=True)
480497 node = z['@']
481498 node.delete_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC)
482499 rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC)
483500 self.assertTrue(rds is None)
484501
485 def testIterateRdatasets(self): # type: () -> None
502 def testIterateNodes(self):
503 z = dns.zone.from_text(example_text, 'example.', relativize=True)
504 count = 0
505 for n in z:
506 count += 1
507 self.assertEqual(count, 4)
508
509 def testIterateRdatasets(self):
486510 z = dns.zone.from_text(example_text, 'example.', relativize=True)
487511 ns = [n for n, r in z.iterate_rdatasets('A')]
488512 ns.sort()
489513 self.assertEqual(ns, [dns.name.from_text('ns1', None),
490514 dns.name.from_text('ns2', None)])
491515
492 def testIterateAllRdatasets(self): # type: () -> None
516 def testIterateAllRdatasets(self):
493517 z = dns.zone.from_text(example_text, 'example.', relativize=True)
494518 ns = [n for n, r in z.iterate_rdatasets()]
495519 ns.sort()
499523 dns.name.from_text('ns1', None),
500524 dns.name.from_text('ns2', None)])
501525
502 def testIterateRdatas(self): # type: () -> None
526 def testIterateRdatas(self):
503527 z = dns.zone.from_text(example_text, 'example.', relativize=True)
504528 l = list(z.iterate_rdatas('A'))
505529 l.sort()
513537 '10.0.0.2'))]
514538 self.assertEqual(l, exl)
515539
516 def testIterateAllRdatas(self): # type: () -> None
540 def testIterateAllRdatas(self):
517541 z = dns.zone.from_text(example_text, 'example.', relativize=True)
518542 l = list(z.iterate_rdatas())
519543 l.sort(key=_rdata_sort)
555579 self.assertEqual(z.get('foo'), n)
556580 del z['foo']
557581 self.assertEqual(z.get('foo'), None)
558 def bad1():
582 with self.assertRaises(KeyError):
559583 z[123] = n
560 self.assertRaises(KeyError, bad1)
561 def bad2():
584 with self.assertRaises(KeyError):
562585 z['foo.'] = n
563 self.assertRaises(KeyError, bad2)
564 def bad3():
586 with self.assertRaises(KeyError):
565587 bn = z.find_node('bar')
566 self.assertRaises(KeyError, bad3)
567588 bn = z.find_node('bar', True)
568589 self.assertTrue(isinstance(bn, dns.node.Node))
590 # The next two tests pass by not raising KeyError
591 z.delete_node('foo')
592 z.delete_node('bar')
569593
570594 def testBadReplacement(self):
571595 z = dns.zone.from_text(example_text, 'example.', relativize=True)
574598 z.replace_rdataset('foo', rds)
575599 self.assertRaises(ValueError, bad)
576600
577 def testTTLs(self): # type: () -> None
601 def testTTLs(self):
578602 z = dns.zone.from_text(ttl_example_text, 'example.', relativize=True)
579603 n = z['@'] # type: dns.node.Node
580604 rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA))
586610 rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A))
587611 self.assertEqual(rds.ttl, 694861)
588612
589 def testTTLFromSOA(self): # type: () -> None
613 def testTTLFromSOA(self):
590614 z = dns.zone.from_text(ttl_from_soa_text, 'example.', relativize=True)
591615 n = z['@']
592616 rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA))
599623 rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A))
600624 self.assertEqual(rds.ttl, soa_rd.minimum)
601625
602 def testTTLFromLast(self): # type: () -> None
626 def testTTLFromLast(self):
603627 z = dns.zone.from_text(ttl_from_last_text, 'example.', check_origin=False)
604628 n = z['@']
605629 rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.NS))
611635 rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A))
612636 self.assertEqual(rds.ttl, 694861)
613637
614 def testNoTTL(self): # type: () -> None
615 def bad(): # type: () -> None
638 def testNoTTL(self):
639 def bad():
616640 dns.zone.from_text(no_ttl_text, 'example.', check_origin=False)
617641 self.assertRaises(dns.exception.SyntaxError, bad)
618642
619 def testNoSOA(self): # type: () -> None
620 def bad(): # type: () -> None
643 def testNoSOA(self):
644 def bad():
621645 dns.zone.from_text(no_soa_text, 'example.', relativize=True)
622646 self.assertRaises(dns.zone.NoSOA, bad)
623647
624 def testNoNS(self): # type: () -> None
625 def bad(): # type: () -> None
648 def testNoNS(self):
649 def bad():
626650 dns.zone.from_text(no_ns_text, 'example.', relativize=True)
627651 self.assertRaises(dns.zone.NoNS, bad)
628652
629 def testInclude(self): # type: () -> None
653 def testInclude(self):
630654 z1 = dns.zone.from_text(include_text, 'example.', relativize=True,
631655 allow_include=True)
632656 z2 = dns.zone.from_file(here('example'), 'example.', relativize=True)
633657 self.assertEqual(z1, z2)
634658
635 def testBadDirective(self): # type: () -> None
636 def bad(): # type: () -> None
659 def testBadDirective(self):
660 def bad():
637661 dns.zone.from_text(bad_directive_text, 'example.', relativize=True)
638662 self.assertRaises(dns.exception.SyntaxError, bad)
639663
640 def testFirstRRStartsWithWhitespace(self): # type: () -> None
664 def testFirstRRStartsWithWhitespace(self):
641665 # no name is specified, so default to the initial origin
642666 z = dns.zone.from_text(' 300 IN A 10.0.0.1', origin='example.',
643667 check_origin=False)
645669 rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A))
646670 self.assertEqual(rds.ttl, 300)
647671
648 def testZoneOrigin(self): # type: () -> None
672 def testZoneOrigin(self):
649673 z = dns.zone.Zone('example.')
650674 self.assertEqual(z.origin, dns.name.from_text('example.'))
651 def bad1(): # type: () -> None
675 def bad1():
652676 o = dns.name.from_text('example', None)
653677 dns.zone.Zone(o)
654678 self.assertRaises(ValueError, bad1)
655 def bad2(): # type: () -> None
679 def bad2():
656680 dns.zone.Zone(cast(str, 1.0))
657681 self.assertRaises(ValueError, bad2)
658682
659 def testZoneOriginNone(self): # type: () -> None
683 def testZoneOriginNone(self):
660684 dns.zone.Zone(cast(str, None))
661685
662 def testZoneFromXFR(self): # type: () -> None
686 def testZoneFromXFR(self):
663687 z1_abs = dns.zone.from_text(example_text, 'example.', relativize=False)
664688 z2_abs = dns.zone.from_xfr(make_xfr(z1_abs), relativize=False)
665689 self.assertEqual(z1_abs, z2_abs)
745769 self.assertEqual(z._validate_name('foo.bar.example.'),
746770 dns.name.from_text('foo.bar', None))
747771
772 def testComments(self):
773 z = dns.zone.from_text(example_comments_text, 'example.',
774 relativize=True)
775 f = StringIO()
776 z.to_file(f, want_comments=True)
777 out = f.getvalue()
778 f.close()
779 self.assertEqual(out, example_comments_text_output)
780
781 def testUncomparable(self):
782 z = dns.zone.from_text(example_comments_text, 'example.',
783 relativize=True)
784 self.assertFalse(z == 'a')
785
786 def testUnsorted(self):
787 z = dns.zone.from_text(example_text, 'example.', relativize=True)
788 f = StringIO()
789 z.to_file(f, sorted=False)
790 out = f.getvalue()
791 f.close()
792 z2 = dns.zone.from_text(out, 'example.', relativize=True)
793 self.assertEqual(z, z2)
794
795 def testNodeReplaceRdatasetConvertsRRsets(self):
796 node = dns.node.Node()
797 rrs = dns.rrset.from_text('foo', 300, 'in', 'a', '10.0.0.1')
798 node.replace_rdataset(rrs)
799 rds = node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.A)
800 self.assertEqual(rds, rrs)
801 self.assertTrue(rds is not rrs)
802 self.assertFalse(isinstance(rds, dns.rrset.RRset))
803
804
805 class VersionedZoneTestCase(unittest.TestCase):
806 def testUseTransaction(self):
807 z = dns.zone.from_text(example_text, 'example.', relativize=True,
808 zone_factory=dns.versioned.Zone)
809 with self.assertRaises(dns.versioned.UseTransaction):
810 z.find_node('not_there', True)
811 with self.assertRaises(dns.versioned.UseTransaction):
812 z.delete_node('not_there')
813 with self.assertRaises(dns.versioned.UseTransaction):
814 z.find_rdataset('not_there', 'a', create=True)
815 with self.assertRaises(dns.versioned.UseTransaction):
816 z.get_rdataset('not_there', 'a', create=True)
817 with self.assertRaises(dns.versioned.UseTransaction):
818 z.delete_rdataset('not_there', 'a')
819 with self.assertRaises(dns.versioned.UseTransaction):
820 z.replace_rdataset('not_there', None)
821
822 def testImmutableNodes(self):
823 z = dns.zone.from_text(example_text, 'example.', relativize=True,
824 zone_factory=dns.versioned.Zone)
825 node = z.find_node('@')
826 with self.assertRaises(TypeError):
827 node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.RP,
828 create=True)
829 with self.assertRaises(TypeError):
830 node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.RP,
831 create=True)
832 with self.assertRaises(TypeError):
833 node.delete_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA)
834 with self.assertRaises(TypeError):
835 node.replace_rdataset(None)
836
837 def testSelectDefaultPruningPolicy(self):
838 z = dns.zone.from_text(example_text, 'example.', relativize=True,
839 zone_factory=dns.versioned.Zone)
840 z.set_pruning_policy(None)
841 self.assertEqual(z._pruning_policy, z._default_pruning_policy)
842
843 def testSetAlternatePruningPolicyInConstructor(self):
844 def never_prune(version):
845 return False
846 z = dns.versioned.Zone('example', pruning_policy=never_prune)
847 self.assertEqual(z._pruning_policy, never_prune)
848
849 def testCannotSpecifyBothSerialAndVersionIdToReader(self):
850 z = dns.zone.from_text(example_text, 'example.', relativize=True,
851 zone_factory=dns.versioned.Zone)
852 with self.assertRaises(ValueError):
853 z.reader(1, 1)
854
855 def testUnknownVersion(self):
856 z = dns.zone.from_text(example_text, 'example.', relativize=True,
857 zone_factory=dns.versioned.Zone)
858 with self.assertRaises(KeyError):
859 z.reader(99999)
860
861 def testUnknownSerial(self):
862 z = dns.zone.from_text(example_text, 'example.', relativize=True,
863 zone_factory=dns.versioned.Zone)
864 with self.assertRaises(KeyError):
865 z.reader(serial=99999)
866
867 def testNoRelativizeReader(self):
868 z = dns.zone.from_text(example_text, 'example.', relativize=False,
869 zone_factory=dns.versioned.Zone)
870 with z.reader(serial=1) as txn:
871 rds = txn.get('example.', 'soa')
872 self.assertEqual(rds[0].serial, 1)
873
874 def testNameInZoneWithStr(self):
875 z = dns.zone.from_text(example_text, 'example.', relativize=False)
876 self.assertTrue('ns1.example.' in z)
877 self.assertTrue('bar.foo.example.' in z)
878
879 def testNameInZoneWhereNameIsNotValid(self):
880 z = dns.zone.from_text(example_text, 'example.', relativize=False)
881 with self.assertRaises(KeyError):
882 self.assertTrue(1 in z)
883
748884 if __name__ == '__main__':
749885 unittest.main()
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 import io
3 import textwrap
4 import unittest
5
6 import dns.rdata
7 import dns.rrset
8 import dns.zone
9
10 class ZoneDigestTestCase(unittest.TestCase):
11 # Examples from RFC 8976, fixed per errata.
12
13 simple_example = textwrap.dedent('''
14 example. 86400 IN SOA ns1 admin 2018031900 (
15 1800 900 604800 86400 )
16 86400 IN NS ns1
17 86400 IN NS ns2
18 86400 IN ZONEMD 2018031900 1 1 (
19 c68090d90a7aed71
20 6bc459f9340e3d7c
21 1370d4d24b7e2fc3
22 a1ddc0b9a87153b9
23 a9713b3c9ae5cc27
24 777f98b8e730044c )
25 ns1 3600 IN A 203.0.113.63
26 ns2 3600 IN AAAA 2001:db8::63
27 ''')
28
29 complex_example = textwrap.dedent('''
30 example. 86400 IN SOA ns1 admin 2018031900 (
31 1800 900 604800 86400 )
32 86400 IN NS ns1
33 86400 IN NS ns2
34 86400 IN ZONEMD 2018031900 1 1 (
35 a3b69bad980a3504
36 e1cffcb0fd6397f9
37 3848071c93151f55
38 2ae2f6b1711d4bd2
39 d8b39808226d7b9d
40 b71e34b72077f8fe )
41 ns1 3600 IN A 203.0.113.63
42 NS2 3600 IN AAAA 2001:db8::63
43 occluded.sub 7200 IN TXT "I'm occluded but must be digested"
44 sub 7200 IN NS ns1
45 duplicate 300 IN TXT "I must be digested just once"
46 duplicate 300 IN TXT "I must be digested just once"
47 foo.test. 555 IN TXT "out-of-zone data must be excluded"
48 UPPERCASE 3600 IN TXT "canonicalize uppercase owner names"
49 * 777 IN PTR dont-forget-about-wildcards
50 mail 3600 IN MX 20 MAIL1
51 mail 3600 IN MX 10 Mail2.Example.
52 sortme 3600 IN AAAA 2001:db8::5:61
53 sortme 3600 IN AAAA 2001:db8::3:62
54 sortme 3600 IN AAAA 2001:db8::4:63
55 sortme 3600 IN AAAA 2001:db8::1:65
56 sortme 3600 IN AAAA 2001:db8::2:64
57 non-apex 900 IN ZONEMD 2018031900 1 1 (
58 616c6c6f77656420
59 6275742069676e6f
60 7265642e20616c6c
61 6f77656420627574
62 2069676e6f726564
63 2e20616c6c6f7765 )
64 ''')
65
66 multiple_digests_example = textwrap.dedent('''
67 example. 86400 IN SOA ns1 admin 2018031900 (
68 1800 900 604800 86400 )
69 example. 86400 IN NS ns1.example.
70 example. 86400 IN NS ns2.example.
71 example. 86400 IN ZONEMD 2018031900 1 1 (
72 62e6cf51b02e54b9
73 b5f967d547ce4313
74 6792901f9f88e637
75 493daaf401c92c27
76 9dd10f0edb1c56f8
77 080211f8480ee306 )
78 example. 86400 IN ZONEMD 2018031900 1 2 (
79 08cfa1115c7b948c
80 4163a901270395ea
81 226a930cd2cbcf2f
82 a9a5e6eb85f37c8a
83 4e114d884e66f176
84 eab121cb02db7d65
85 2e0cc4827e7a3204
86 f166b47e5613fd27 )
87 example. 86400 IN ZONEMD 2018031900 1 240 (
88 e2d523f654b9422a
89 96c5a8f44607bbee )
90 example. 86400 IN ZONEMD 2018031900 241 1 (
91 e1846540e33a9e41
92 89792d18d5d131f6
93 05fc283eaaaaaaaa
94 aaaaaaaaaaaaaaaa
95 aaaaaaaaaaaaaaaa
96 aaaaaaaaaaaaaaaa)
97 ns1.example. 3600 IN A 203.0.113.63
98 ns2.example. 86400 IN TXT "This example has multiple digests"
99 NS2.EXAMPLE. 3600 IN AAAA 2001:db8::63
100 ''')
101
102 def _get_zonemd(self, zone):
103 return zone.get_rdataset(zone.origin, 'ZONEMD')
104
105 def test_zonemd_simple(self):
106 zone = dns.zone.from_text(self.simple_example, origin='example')
107 zone.verify_digest()
108 zonemd = self._get_zonemd(zone)
109 self.assertEqual(zonemd[0],
110 zone.compute_digest(zonemd[0].hash_algorithm))
111
112 def test_zonemd_simple_absolute(self):
113 zone = dns.zone.from_text(self.simple_example, origin='example',
114 relativize=False)
115 zone.verify_digest()
116 zonemd = self._get_zonemd(zone)
117 self.assertEqual(zonemd[0],
118 zone.compute_digest(zonemd[0].hash_algorithm))
119
120 def test_zonemd_complex(self):
121 zone = dns.zone.from_text(self.complex_example, origin='example')
122 zone.verify_digest()
123 zonemd = self._get_zonemd(zone)
124 self.assertEqual(zonemd[0],
125 zone.compute_digest(zonemd[0].hash_algorithm))
126
127 def test_zonemd_multiple_digests(self):
128 zone = dns.zone.from_text(self.multiple_digests_example,
129 origin='example')
130 zone.verify_digest()
131
132 zonemd = self._get_zonemd(zone)
133 for rr in zonemd:
134 if rr.scheme == 1 and rr.hash_algorithm in (1, 2):
135 zone.verify_digest(rr)
136 self.assertEqual(rr, zone.compute_digest(rr.hash_algorithm))
137 else:
138 with self.assertRaises(dns.zone.DigestVerificationFailure):
139 zone.verify_digest(rr)
140
141 def test_zonemd_no_digest(self):
142 zone = dns.zone.from_text(self.simple_example, origin='example')
143 zone.delete_rdataset(dns.name.empty, 'ZONEMD')
144 with self.assertRaises(dns.zone.NoDigest):
145 zone.verify_digest()
146
147 sha384_hash = 'ab' * 48
148 sha512_hash = 'ab' * 64
149
150 def test_zonemd_parse_rdata(self):
151 dns.rdata.from_text('IN', 'ZONEMD', '100 1 1 ' + self.sha384_hash)
152 dns.rdata.from_text('IN', 'ZONEMD', '100 1 2 ' + self.sha512_hash)
153 dns.rdata.from_text('IN', 'ZONEMD', '100 100 1 ' + self.sha384_hash)
154 dns.rdata.from_text('IN', 'ZONEMD', '100 1 100 abcd')
155
156 def test_zonemd_unknown_scheme(self):
157 zone = dns.zone.from_text(self.simple_example, origin='example')
158 with self.assertRaises(dns.zone.UnsupportedDigestScheme):
159 zone.compute_digest(dns.zone.DigestHashAlgorithm.SHA384, 2)
160
161 def test_zonemd_unknown_hash_algorithm(self):
162 zone = dns.zone.from_text(self.simple_example, origin='example')
163 with self.assertRaises(dns.zone.UnsupportedDigestHashAlgorithm):
164 zone.compute_digest(5)
165
166 def test_zonemd_invalid_digest_length(self):
167 with self.assertRaises(dns.exception.SyntaxError):
168 dns.rdata.from_text('IN', 'ZONEMD', '100 1 2 ' + self.sha384_hash)
169 with self.assertRaises(dns.exception.SyntaxError):
170 dns.rdata.from_text('IN', 'ZONEMD', '100 2 1 ' + self.sha512_hash)
171
172 def test_zonemd_parse_rdata_reserved(self):
173 with self.assertRaises(dns.exception.SyntaxError):
174 dns.rdata.from_text('IN', 'ZONEMD', '100 0 1 ' + self.sha384_hash)
175 with self.assertRaises(dns.exception.SyntaxError):
176 dns.rdata.from_text('IN', 'ZONEMD', '100 1 0 ' + self.sha384_hash)
177
0 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
1
2 # Copyright (C) 2003-2007, 2009-2011 Nominum, Inc.
3 #
4 # Permission to use, copy, modify, and distribute this software and its
5 # documentation for any purpose with or without fee is hereby granted,
6 # provided that the above copyright notice and this permission notice
7 # appear in all copies.
8 #
9 # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
10 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
12 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
15 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
17 import enum
18 import inspect
19 import os.path
20
21 def here(filename):
22 return os.path.join(os.path.dirname(__file__), filename)
23
24 def enumerate_module(module, super_class):
25 """Yield module attributes which are subclasses of given class"""
26 for attr_name in dir(module):
27 attr = getattr(module, attr_name)
28 if inspect.isclass(attr) and issubclass(attr, super_class):
29 yield attr
30
31 def check_enum_exports(module, eq_callback, only=None):
32 """Make sure module exports all mnemonics from enums"""
33 for attr in enumerate_module(module, enum.Enum):
34 if only is not None and attr not in only:
35 #print('SKIP', attr)
36 continue
37 for flag, value in attr.__members__.items():
38 #print(module, flag, value)
39 eq_callback(getattr(module, flag), value)
0 #!/usr/bin/env python3
1
2 # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
3
4 from importlib import import_module
5 import os
6 import sys
7
8 enum_names = [
9 'dns.dnssec.Algorithm',
10 'dns.edns.OptionType',
11 'dns.flags.Flag',
12 'dns.flags.EDNSFlag',
13 'dns.message.MessageSection',
14 'dns.opcode.Opcode',
15 'dns.rcode.Rcode',
16 'dns.rdataclass.RdataClass',
17 'dns.rdatatype.RdataType',
18 'dns.rdtypes.dnskeybase.Flag',
19 'dns.update.UpdateSection',
20 ]
21
22 def generate():
23 for enum_name in enum_names:
24 dot = enum_name.rindex('.')
25 module_name = enum_name[:dot]
26 type_name = enum_name[dot + 1:]
27 lname = type_name.lower()
28 mod = import_module(module_name)
29 enum = getattr(mod, type_name)
30 filename = module_name.replace('.', '/') + '.py'
31 new_filename = filename + '.new'
32 with open(filename) as f:
33 with open(new_filename, 'w') as nf:
34 lines = f.readlines()
35 found = False
36 i = 0
37 length = len(lines)
38 while i < length:
39 l = lines[i].rstrip()
40 i += 1
41 if l.startswith(f'### BEGIN generated {type_name} ' +
42 'constants') or \
43 l.startswith(f'### BEGIN generated {lname} constants'):
44 found = True
45 break
46 else:
47 print(l, file=nf)
48 if found:
49 found = False
50 while i < length:
51 l = lines[i].rstrip()
52 i += 1
53 if l.startswith(f'### END generated {type_name} ' +
54 'constants') or \
55 l.startswith(f'### END generated {lname} constants'):
56 found = True
57 break
58 if not found:
59 print(f'Found begin marker for {type_name} but did ' +
60 'not find end marker!', file=sys.stderr)
61 sys.exit(1)
62 if not found:
63 print('', file=nf)
64 print(f'### BEGIN generated {type_name} constants', file=nf)
65 print('', file=nf)
66 # We have to use __members__.items() and not "in enum" because
67 # otherwise we miss values that have multiple names (e.g. NONE
68 # and TYPE0 for rdatatypes).
69 for name, value in enum.__members__.items():
70 print(f'{name} = {type_name}.{name}', file=nf)
71 print('', file=nf)
72 print(f'### END generated {type_name} constants', file=nf)
73 # Copy remaining lines (if any)
74 while i < length:
75 l = lines[i].rstrip()
76 i += 1
77 print(l, file=nf)
78 os.rename(new_filename, filename)
79
80 def main():
81 generate()
82
83 if __name__ == '__main__':
84 main()
0 import pickle
1 import sys
2
3 import dns.rdata
4 import dns.version
5
6 # Generate a pickled mx RR for the current dnspython version
7
8 mx = dns.rdata.from_text('in', 'mx', '10 mx.example.')
9 filename = f'pickled-{dns.version.MAJOR}-{dns.version.MINOR}.pickle'
10 with open(filename, 'wb') as f:
11 pickle.dump(mx, f)
12 with open(filename, 'rb') as f:
13 mx2 = pickle.load(f)
14 if mx == mx2:
15 print('ok')
16 else:
17 print('DIFFERENT!')
18 sys.exit(1)
0
1 import dns.rdatatype
2
3 print('Rdatatypes')
4 print('----------')
5 print()
6 by_name = {}
7 for rdtype in dns.rdatatype.RdataType:
8 short_name = str(rdtype).split('.')[1]
9 by_name[short_name] = int(rdtype)
10 for k in sorted(by_name.keys()):
11 v = by_name[k]
12 print(f'.. py:data:: dns.rdatatype.{k}')
13 print(f' :annotation: = {v}')