Import upstream version 2.1.0rc1+git20210523.1.cf73187
Debian Janitor
2 years ago
5 | 5 | E129, |
6 | 6 | # Whitespace round parameter '=' can be excessive |
7 | 7 | E252, |
8 | # Multiple # in a comment is OK | |
9 | E266, | |
8 | 10 | # Not excited by the "two blank lines" rule |
9 | 11 | E302, |
10 | 12 | E305, |
2 | 2 | - "3.6" |
3 | 3 | - "3.7" |
4 | 4 | - "3.8" |
5 | - "3.9" | |
5 | 6 | branches: |
6 | 7 | except: |
7 | 8 | - python3 |
10 | 11 | - ~/.poetry/bin/poetry install -E dnssec -E doh -E idna -E trio -E curio |
11 | 12 | script: |
12 | 13 | - ~/.poetry/bin/poetry run pytest --cov=. --cov-report=xml:coverage.xml |
13 | after_success: | |
14 | - bash <(curl -s https://codecov.io/bash) |
0 | 0 | include LICENSE ChangeLog README.md |
1 | 1 | 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 |
57 | 57 | potype: |
58 | 58 | poetry run python -m mypy examples tests dns/*.py |
59 | 59 | |
60 | polint: | |
61 | poetry run pylint dns | |
62 | ||
60 | 63 | poflake: |
61 | 64 | poetry run flake8 dns |
62 | 65 | |
63 | 66 | pocov: |
64 | poetry run coverage run -m pytest | |
67 | poetry run coverage run --branch -m pytest | |
65 | 68 | poetry run coverage html --include 'dns*' |
66 | 69 | poetry run coverage report --include 'dns*' |
67 | 70 |
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 |
2 | 2 | [![Build Status](https://travis-ci.org/rthalley/dnspython.svg?branch=master)](https://travis-ci.org/rthalley/dnspython) |
3 | 3 | [![Documentation Status](https://readthedocs.org/projects/dnspython/badge/?version=latest)](https://dnspython.readthedocs.io/en/latest/?badge=latest) |
4 | 4 | [![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) | |
8 | 5 | [![License: ISC](https://img.shields.io/badge/License-ISC-brightgreen.svg)](https://opensource.org/licenses/ISC) |
9 | 6 | |
10 | 7 | ## INTRODUCTION |
30 | 27 | |
31 | 28 | ## ABOUT THIS RELEASE |
32 | 29 | |
33 | This is dnspython 2.0.0. | |
30 | This is the development version of dnspython 2.2.0. | |
34 | 31 | 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 | |
36 | 33 | information about the changes in this release. |
37 | 34 | |
38 | 35 | ## INSTALLATION |
11 | 11 | vmImage: 'vs2017-win2016' |
12 | 12 | strategy: |
13 | 13 | matrix: |
14 | Python37: | |
15 | python.version: '3.7' | |
14 | Python38: | |
15 | python.version: '3.8' | |
16 | 16 | steps: |
17 | 17 | - task: UsePythonVersion@0 |
18 | 18 | inputs: |
19 | 19 | versionSpec: '$(python.version)' |
20 | 20 | displayName: 'Use Python $(python.version)' |
21 | 21 | |
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 | ||
22 | 30 | - 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 | |
25 | 32 | 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' | |
26 | 38 | |
27 | 39 | - script: | |
28 | 40 | dotnet tool install --global Codecov.Tool |
29 | 41 | displayName: 'Install Codecov.Tool' |
30 | 42 | |
31 | 43 | - 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 | |
34 | 46 | displayName: 'pytest' |
35 | 47 | |
36 | 48 | - task: PublishTestResults@2 |
45 | 57 | # summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml' |
46 | 58 | |
47 | 59 | - script: | |
48 | codecov -f coverage.xml | |
60 | %USERPROFILE%\.dotnet\tools\codecov -f coverage.xml | |
49 | 61 | displayName: 'Upload to codecov' |
26 | 26 | 'entropy', |
27 | 27 | 'exception', |
28 | 28 | 'flags', |
29 | 'immutable', | |
29 | 30 | 'inet', |
30 | 31 | 'ipv4', |
31 | 32 | 'ipv6', |
47 | 48 | 'serial', |
48 | 49 | 'set', |
49 | 50 | 'tokenizer', |
51 | 'transaction', | |
50 | 52 | 'tsig', |
51 | 53 | 'tsigkeyring', |
52 | 54 | 'ttl', |
53 | 55 | 'rdtypes', |
54 | 56 | 'update', |
55 | 57 | 'version', |
58 | 'versioned', | |
56 | 59 | 'wire', |
60 | 'xfr', | |
57 | 61 | 'zone', |
62 | 'zonefile', | |
58 | 63 | ] |
59 | 64 | |
60 | 65 | from dns.version import version as __version__ # noqa |
26 | 26 | async def close(self): |
27 | 27 | pass |
28 | 28 | |
29 | async def getpeername(self): | |
30 | raise NotImplementedError | |
31 | ||
32 | async def getsockname(self): | |
33 | raise NotImplementedError | |
34 | ||
29 | 35 | async def __aenter__(self): |
30 | 36 | return self |
31 | 37 | |
35 | 41 | |
36 | 42 | class DatagramSocket(Socket): # pragma: no cover |
37 | 43 | async def sendto(self, what, destination, timeout): |
38 | pass | |
44 | raise NotImplementedError | |
39 | 45 | |
40 | 46 | async def recvfrom(self, size, timeout): |
41 | pass | |
47 | raise NotImplementedError | |
42 | 48 | |
43 | 49 | |
44 | 50 | 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 | |
47 | 53 | |
48 | 54 | async def recv(self, size, timeout): |
49 | pass | |
55 | raise NotImplementedError | |
50 | 56 | |
51 | 57 | |
52 | 58 | class Backend: # pragma: no cover |
57 | 63 | source=None, destination=None, timeout=None, |
58 | 64 | ssl_context=None, server_hostname=None): |
59 | 65 | raise NotImplementedError |
66 | ||
67 | def datagram_connection_required(self): | |
68 | return False |
3 | 3 | |
4 | 4 | import socket |
5 | 5 | import asyncio |
6 | import sys | |
6 | 7 | |
7 | 8 | import dns._asyncbackend |
8 | 9 | import dns.exception |
9 | 10 | |
11 | ||
12 | _is_win32 = sys.platform == 'win32' | |
10 | 13 | |
11 | 14 | def _get_running_loop(): |
12 | 15 | try: |
29 | 32 | self.recvfrom = None |
30 | 33 | |
31 | 34 | def error_received(self, exc): # pragma: no cover |
32 | if self.recvfrom: | |
35 | if self.recvfrom and not self.recvfrom.done(): | |
33 | 36 | self.recvfrom.set_exception(exc) |
34 | 37 | |
35 | 38 | def connection_lost(self, exc): |
36 | if self.recvfrom: | |
39 | if self.recvfrom and not self.recvfrom.done(): | |
37 | 40 | self.recvfrom.set_exception(exc) |
38 | 41 | |
39 | 42 | def close(self): |
78 | 81 | return self.transport.get_extra_info('sockname') |
79 | 82 | |
80 | 83 | |
81 | class StreamSocket(dns._asyncbackend.DatagramSocket): | |
84 | class StreamSocket(dns._asyncbackend.StreamSocket): | |
82 | 85 | def __init__(self, af, reader, writer): |
83 | 86 | self.family = af |
84 | 87 | self.reader = reader |
85 | 88 | self.writer = writer |
86 | 89 | |
87 | 90 | async def sendall(self, what, timeout): |
88 | self.writer.write(what), | |
91 | self.writer.write(what) | |
89 | 92 | return await _maybe_wait_for(self.writer.drain(), timeout) |
90 | raise dns.exception.Timeout(timeout=timeout) | |
91 | 93 | |
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), | |
94 | 96 | timeout) |
95 | raise dns.exception.Timeout(timeout=timeout) | |
96 | 97 | |
97 | 98 | async def close(self): |
98 | 99 | self.writer.close() |
115 | 116 | async def make_socket(self, af, socktype, proto=0, |
116 | 117 | source=None, destination=None, timeout=None, |
117 | 118 | 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') | |
118 | 124 | loop = _get_running_loop() |
119 | 125 | if socktype == socket.SOCK_DGRAM: |
120 | 126 | transport, protocol = await loop.create_datagram_endpoint( |
121 | 127 | _DatagramProtocol, source, family=af, |
122 | proto=proto) | |
128 | proto=proto, remote_addr=destination) | |
123 | 129 | return DatagramSocket(af, transport, protocol) |
124 | 130 | elif socktype == socket.SOCK_STREAM: |
125 | 131 | (r, w) = await _maybe_wait_for( |
137 | 143 | |
138 | 144 | async def sleep(self, interval): |
139 | 145 | await asyncio.sleep(interval) |
146 | ||
147 | def datagram_connection_required(self): | |
148 | return _is_win32 |
19 | 19 | |
20 | 20 | # for brevity |
21 | 21 | _lltuple = dns.inet.low_level_address_tuple |
22 | ||
23 | # pylint: disable=redefined-outer-name | |
22 | 24 | |
23 | 25 | |
24 | 26 | class DatagramSocket(dns._asyncbackend.DatagramSocket): |
46 | 48 | return self.socket.getsockname() |
47 | 49 | |
48 | 50 | |
49 | class StreamSocket(dns._asyncbackend.DatagramSocket): | |
51 | class StreamSocket(dns._asyncbackend.StreamSocket): | |
50 | 52 | def __init__(self, socket): |
51 | 53 | self.socket = socket |
52 | 54 | 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 |
19 | 19 | |
20 | 20 | # for brevity |
21 | 21 | _lltuple = dns.inet.low_level_address_tuple |
22 | ||
23 | # pylint: disable=redefined-outer-name | |
22 | 24 | |
23 | 25 | |
24 | 26 | class DatagramSocket(dns._asyncbackend.DatagramSocket): |
46 | 48 | return self.socket.getsockname() |
47 | 49 | |
48 | 50 | |
49 | class StreamSocket(dns._asyncbackend.DatagramSocket): | |
51 | class StreamSocket(dns._asyncbackend.StreamSocket): | |
50 | 52 | def __init__(self, family, stream, tls=False): |
51 | 53 | self.family = family |
52 | 54 | self.stream = stream |
1 | 1 | |
2 | 2 | import dns.exception |
3 | 3 | |
4 | # pylint: disable=unused-import | |
5 | ||
4 | 6 | from dns._asyncbackend import Socket, DatagramSocket, \ |
5 | 7 | StreamSocket, Backend # noqa: |
6 | 8 | |
9 | # pylint: enable=unused-import | |
7 | 10 | |
8 | 11 | _default_backend = None |
9 | 12 | |
24 | 27 | |
25 | 28 | Raises NotImplementError if an unknown backend name is specified. |
26 | 29 | """ |
30 | # pylint: disable=import-outside-toplevel,redefined-outer-name | |
27 | 31 | backend = _backends.get(name) |
28 | 32 | if backend: |
29 | 33 | return backend |
49 | 53 | Returns the name of the library, or raises AsyncLibraryNotFoundError |
50 | 54 | if the library cannot be determined. |
51 | 55 | """ |
56 | # pylint: disable=import-outside-toplevel | |
52 | 57 | try: |
53 | 58 | if _no_sniffio: |
54 | 59 | raise ImportError |
29 | 29 | import dns.rdataclass |
30 | 30 | import dns.rdatatype |
31 | 31 | |
32 | from dns.query import _compute_times, _matches_destination, BadResponse, ssl | |
32 | from dns.query import _compute_times, _matches_destination, BadResponse, ssl, \ | |
33 | UDPMode | |
33 | 34 | |
34 | 35 | |
35 | 36 | # for brevity |
93 | 94 | |
94 | 95 | *sock*, a ``dns.asyncbackend.DatagramSocket``. |
95 | 96 | |
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. | |
126 | 99 | """ |
127 | 100 | |
128 | 101 | wire = b'' |
144 | 117 | backend=None): |
145 | 118 | """Return the response obtained after sending a query via UDP. |
146 | 119 | |
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 | ||
175 | 120 | *sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``, |
176 | 121 | the socket to use for the query. If ``None``, the default, a |
177 | 122 | socket is created. Note that if a socket is provided, the |
180 | 125 | *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, |
181 | 126 | the default, then dnspython will use the default backend. |
182 | 127 | |
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. | |
184 | 130 | """ |
185 | 131 | wire = q.to_wire() |
186 | 132 | (begin_time, expiration) = _compute_times(timeout) |
195 | 141 | if not backend: |
196 | 142 | backend = dns.asyncbackend.get_default_backend() |
197 | 143 | 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) | |
199 | 150 | await send_udp(s, wire, destination, expiration) |
200 | 151 | (r, received_time, _) = await receive_udp(s, destination, expiration, |
201 | 152 | ignore_unexpected, |
218 | 169 | """Return the response to the query, trying UDP first and falling back |
219 | 170 | to TCP if UDP results in a truncated response. |
220 | 171 | |
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 | ||
246 | 172 | *udp_sock*, a ``dns.asyncbackend.DatagramSocket``, or ``None``, |
247 | 173 | the socket to use for the UDP query. If ``None``, the default, a |
248 | 174 | socket is created. Note that if a socket is provided the *source*, |
256 | 182 | *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, |
257 | 183 | the default, then dnspython will use the default backend. |
258 | 184 | |
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. | |
261 | 188 | """ |
262 | 189 | try: |
263 | 190 | response = await udp(q, where, timeout, port, source, source_port, |
274 | 201 | async def send_tcp(sock, what, expiration=None): |
275 | 202 | """Send a DNS message to the specified TCP socket. |
276 | 203 | |
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. | |
286 | 208 | """ |
287 | 209 | |
288 | 210 | if isinstance(what, dns.message.Message): |
293 | 215 | # onto the net |
294 | 216 | tcpmsg = struct.pack("!H", l) + what |
295 | 217 | sent_time = time.time() |
296 | await sock.sendall(tcpmsg, expiration) | |
218 | await sock.sendall(tcpmsg, _timeout(expiration, sent_time)) | |
297 | 219 | return (len(tcpmsg), sent_time) |
298 | 220 | |
299 | 221 | |
315 | 237 | keyring=None, request_mac=b'', ignore_trailing=False): |
316 | 238 | """Read a DNS message from a TCP socket. |
317 | 239 | |
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. | |
339 | 244 | """ |
340 | 245 | |
341 | 246 | ldata = await _read_exactly(sock, 2, expiration) |
353 | 258 | backend=None): |
354 | 259 | """Return the response obtained after sending a query via TCP. |
355 | 260 | |
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 | ||
378 | 261 | *sock*, a ``dns.asyncbacket.StreamSocket``, or ``None``, the |
379 | 262 | socket to use for the query. If ``None``, the default, a socket |
380 | 263 | is created. Note that if a socket is provided |
383 | 266 | *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, |
384 | 267 | the default, then dnspython will use the default backend. |
385 | 268 | |
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. | |
387 | 271 | """ |
388 | 272 | |
389 | 273 | wire = q.to_wire() |
425 | 309 | backend=None, ssl_context=None, server_hostname=None): |
426 | 310 | """Return the response obtained after sending a query via TLS. |
427 | 311 | |
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 | ||
450 | 312 | *sock*, an ``asyncbackend.StreamSocket``, or ``None``, the socket |
451 | 313 | to use for the query. If ``None``, the default, a socket is |
452 | 314 | created. Note that if a socket is provided, it must be a |
457 | 319 | *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, |
458 | 320 | the default, then dnspython will use the default backend. |
459 | 321 | |
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. | |
469 | 324 | """ |
470 | 325 | # After 3.6 is no longer supported, this can use an AsyncExitStack. |
471 | 326 | (begin_time, expiration) = _compute_times(timeout) |
497 | 352 | finally: |
498 | 353 | if not sock and s: |
499 | 354 | 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") |
33 | 33 | _tcp = dns.asyncquery.tcp |
34 | 34 | |
35 | 35 | |
36 | class Resolver(dns.resolver.Resolver): | |
36 | class Resolver(dns.resolver.BaseResolver): | |
37 | """Asynchronous DNS stub resolver.""" | |
37 | 38 | |
38 | 39 | async def resolve(self, qname, rdtype=dns.rdatatype.A, |
39 | 40 | rdclass=dns.rdataclass.IN, |
42 | 43 | backend=None): |
43 | 44 | """Query nameservers asynchronously to find the answer to the question. |
44 | 45 | |
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 | ||
75 | 46 | *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, |
76 | 47 | the default, then dnspython will use the default backend. |
77 | 48 | |
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. | |
92 | 52 | """ |
93 | 53 | |
94 | 54 | resolution = dns.resolver._Resolution(self, qname, rdtype, rdclass, tcp, |
110 | 70 | (nameserver, port, tcp, backoff) = resolution.next_nameserver() |
111 | 71 | if backoff: |
112 | 72 | await backend.sleep(backoff) |
113 | timeout = self._compute_timeout(start, lifetime) | |
73 | timeout = self._compute_timeout(start, lifetime, | |
74 | resolution.errors) | |
114 | 75 | try: |
115 | 76 | if dns.inet.is_address(nameserver): |
116 | 77 | if tcp: |
138 | 99 | if answer is not None: |
139 | 100 | return answer |
140 | 101 | |
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 | ||
146 | 102 | async def resolve_address(self, ipaddr, *args, **kwargs): |
147 | 103 | """Use an asynchronous resolver to run a reverse query for PTR |
148 | 104 | records. |
163 | 119 | rdtype=dns.rdatatype.PTR, |
164 | 120 | rdclass=dns.rdataclass.IN, |
165 | 121 | *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 | ||
166 | 146 | |
167 | 147 | default_resolver = None |
168 | 148 | |
187 | 167 | |
188 | 168 | async def resolve(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, |
189 | 169 | 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): | |
191 | 171 | """Query nameservers asynchronously to find the answer to the question. |
192 | 172 | |
193 | 173 | This is a convenience function that uses the default resolver |
194 | 174 | object to make the query. |
195 | 175 | |
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. | |
198 | 178 | """ |
199 | 179 | |
200 | 180 | return await get_default_resolver().resolve(qname, rdtype, rdclass, tcp, |
201 | 181 | source, raise_on_no_answer, |
202 | source_port, search, backend) | |
182 | source_port, lifetime, search, | |
183 | backend) | |
203 | 184 | |
204 | 185 | |
205 | 186 | async def resolve_address(ipaddr, *args, **kwargs): |
206 | 187 | """Use a resolver to run a reverse query for PTR records. |
207 | 188 | |
208 | See ``dns.asyncresolver.Resolver.resolve_address`` for more | |
189 | See :py:func:`dns.asyncresolver.Resolver.resolve_address` for more | |
209 | 190 | information on the parameters. |
210 | 191 | """ |
211 | 192 | |
212 | 193 | return await get_default_resolver().resolve_address(ipaddr, *args, **kwargs) |
213 | 194 | |
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) | |
214 | 203 | |
215 | 204 | async def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, |
216 | 205 | resolver=None, backend=None): |
217 | 206 | """Find the name of the zone which contains the specified name. |
218 | 207 | |
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. | |
236 | 210 | """ |
237 | 211 | |
238 | 212 | if isinstance(name, str): |
63 | 63 | return 255 |
64 | 64 | |
65 | 65 | |
66 | globals().update(Algorithm.__members__) | |
67 | ||
68 | ||
69 | 66 | def algorithm_from_text(text): |
70 | 67 | """Convert text into a DNSSEC algorithm value. |
71 | 68 | |
168 | 165 | |
169 | 166 | |
170 | 167 | def _find_candidate_keys(keys, rrsig): |
171 | candidate_keys = [] | |
172 | 168 | 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: | |
174 | 174 | 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] | |
188 | 177 | |
189 | 178 | |
190 | 179 | def _is_rsa(algorithm): |
253 | 242 | return int.from_bytes(b, 'big') |
254 | 243 | |
255 | 244 | |
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 | ||
256 | 321 | def _validate_rrsig(rrset, rrsig, keys, origin=None, now=None): |
257 | 322 | """Validate an RRset against a single signature rdata, throwing an |
258 | 323 | exception if validation is not successful. |
290 | 355 | if candidate_keys is None: |
291 | 356 | raise ValidationFailure('unknown key') |
292 | 357 | |
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 | ||
293 | 416 | 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) | |
411 | 417 | 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) | |
428 | 419 | return |
429 | except InvalidSignature: | |
420 | except (InvalidSignature, ValidationFailure): | |
430 | 421 | # this happens on an individual validation failure |
431 | 422 | continue |
432 | 423 | # nothing verified -- raise failure: |
545 | 536 | domain_encoded = domain.canonicalize().to_wire() |
546 | 537 | |
547 | 538 | digest = hashlib.sha1(domain_encoded + salt_encoded).digest() |
548 | for i in range(iterations): | |
539 | for _ in range(iterations): | |
549 | 540 | digest = hashlib.sha1(digest + salt_encoded).digest() |
550 | 541 | |
551 | 542 | output = base64.b32encode(digest).decode("utf-8") |
578 | 569 | validate = _validate # type: ignore |
579 | 570 | validate_rrsig = _validate_rrsig # type: ignore |
580 | 571 | _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 |
22 | 22 | |
23 | 23 | import dns.enum |
24 | 24 | import dns.inet |
25 | import dns.rdata | |
26 | ||
25 | 27 | |
26 | 28 | class OptionType(dns.enum.IntEnum): |
27 | 29 | #: NSID |
49 | 51 | def _maximum(cls): |
50 | 52 | return 65535 |
51 | 53 | |
52 | globals().update(OptionType.__members__) | |
53 | 54 | |
54 | 55 | class Option: |
55 | 56 | |
60 | 61 | |
61 | 62 | *otype*, an ``int``, is the option type. |
62 | 63 | """ |
63 | self.otype = otype | |
64 | self.otype = OptionType.make(otype) | |
64 | 65 | |
65 | 66 | def to_wire(self, file=None): |
66 | 67 | """Convert an option to wire format. |
148 | 149 | |
149 | 150 | def __init__(self, otype, data): |
150 | 151 | super().__init__(otype) |
151 | self.data = data | |
152 | self.data = dns.rdata.Rdata._as_bytes(data, True) | |
152 | 153 | |
153 | 154 | def to_wire(self, file=None): |
154 | 155 | if file: |
185 | 186 | self.family = 2 |
186 | 187 | if srclen is None: |
187 | 188 | 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) | |
188 | 192 | elif af == socket.AF_INET: |
189 | 193 | self.family = 1 |
190 | 194 | if srclen is None: |
191 | 195 | 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') | |
194 | 201 | |
195 | 202 | self.address = address |
196 | 203 | self.srclen = srclen |
341 | 348 | parser = dns.wire.Parser(wire, current) |
342 | 349 | with parser.restrict_to(olen): |
343 | 350 | 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 |
74 | 74 | |
75 | 75 | @classmethod |
76 | 76 | def _maximum(cls): |
77 | raise NotImplementedError | |
77 | raise NotImplementedError # pragma: no cover | |
78 | 78 | |
79 | 79 | @classmethod |
80 | 80 | def _short_name(cls): |
125 | 125 | """The DNS operation timed out.""" |
126 | 126 | supp_kwargs = {'timeout'} |
127 | 127 | 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 |
36 | 36 | #: Checking Disabled |
37 | 37 | CD = 0x0010 |
38 | 38 | |
39 | globals().update(Flag.__members__) | |
40 | ||
41 | 39 | |
42 | 40 | # EDNS flags |
43 | 41 | |
44 | 42 | class EDNSFlag(enum.IntFlag): |
45 | 43 | #: DNSSEC answer OK |
46 | 44 | DO = 0x8000 |
47 | ||
48 | ||
49 | globals().update(EDNSFlag.__members__) | |
50 | 45 | |
51 | 46 | |
52 | 47 | def _from_text(text, enum_class): |
103 | 98 | """ |
104 | 99 | |
105 | 100 | 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 |
27 | 27 | Returns a tuple of three ``int`` values ``(start, stop, step)``. |
28 | 28 | """ |
29 | 29 | |
30 | # TODO, figure out the bounds on start, stop and step. | |
30 | start = -1 | |
31 | stop = -1 | |
31 | 32 | step = 1 |
32 | 33 | cur = '' |
33 | 34 | state = 0 |
34 | # state 0 1 2 3 4 | |
35 | # state 0 1 2 | |
35 | 36 | # x - y / z |
36 | 37 | |
37 | 38 | if text and text[0] == '-': |
41 | 42 | if c == '-' and state == 0: |
42 | 43 | start = int(cur) |
43 | 44 | cur = '' |
44 | state = 2 | |
45 | state = 1 | |
45 | 46 | elif c == '/': |
46 | 47 | stop = int(cur) |
47 | 48 | cur = '' |
48 | state = 4 | |
49 | state = 2 | |
49 | 50 | elif c.isdigit(): |
50 | 51 | cur += c |
51 | 52 | else: |
52 | 53 | raise dns.exception.SyntaxError("Could not parse %s" % (c)) |
53 | 54 | |
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: | |
58 | 58 | stop = int(cur) |
59 | ||
60 | if state == 4: | |
59 | else: | |
60 | assert state == 2 | |
61 | 61 | step = int(cur) |
62 | 62 | |
63 | 63 | assert step >= 1 |
64 | 64 | 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') | |
67 | 67 | |
68 | 68 | 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 |
161 | 161 | return (addrpart, port, 0, int(scope)) |
162 | 162 | try: |
163 | 163 | return (addrpart, port, 0, socket.if_nametoindex(scope)) |
164 | except AttributeError: | |
164 | except AttributeError: # pragma: no cover (we can't really test this) | |
165 | 165 | ai_flags = socket.AI_NUMERICHOST |
166 | 166 | ((*_, tup), *_) = socket.getaddrinfo(address, port, flags=ai_flags) |
167 | 167 | return tup |
120 | 120 | elif l > 2: |
121 | 121 | raise dns.exception.SyntaxError |
122 | 122 | |
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'::': | |
124 | 130 | text = b'0::' |
125 | 131 | # |
126 | 132 | # Get rid of the icky dot-quad syntax if we have it. |
156 | 162 | if seen_empty: |
157 | 163 | raise dns.exception.SyntaxError |
158 | 164 | seen_empty = True |
159 | for i in range(0, 8 - l + 1): | |
165 | for _ in range(0, 8 - l + 1): | |
160 | 166 | canonical.append(b'0000') |
161 | 167 | else: |
162 | 168 | lc = len(c) |
34 | 34 | import dns.rdatatype |
35 | 35 | import dns.rrset |
36 | 36 | import dns.renderer |
37 | import dns.ttl | |
37 | 38 | import dns.tsig |
38 | 39 | import dns.rdtypes.ANY.OPT |
39 | 40 | import dns.rdtypes.ANY.TSIG |
77 | 78 | Returns a ``dns.message.Message``. |
78 | 79 | """ |
79 | 80 | 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.""" | |
80 | 96 | |
81 | 97 | |
82 | 98 | class MessageSection(dns.enum.IntEnum): |
90 | 106 | def _maximum(cls): |
91 | 107 | return 3 |
92 | 108 | |
93 | globals().update(MessageSection.__members__) | |
94 | ||
109 | ||
110 | DEFAULT_EDNS_PAYLOAD = 1232 | |
111 | MAX_CHAIN = 16 | |
95 | 112 | |
96 | 113 | class Message: |
97 | 114 | """A DNS message.""" |
168 | 185 | |
169 | 186 | s = io.StringIO() |
170 | 187 | 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())) | |
175 | 190 | s.write('flags %s\n' % dns.flags.to_text(self.flags)) |
176 | 191 | if self.edns >= 0: |
177 | 192 | s.write('edns %s\n' % self.edns) |
230 | 245 | dns.opcode.from_flags(self.flags) != \ |
231 | 246 | dns.opcode.from_flags(other.flags): |
232 | 247 | 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 | |
236 | 255 | if dns.opcode.is_update(self.flags): |
237 | 256 | # This is assuming the "sender doesn't include anything |
238 | 257 | # from the update", but we don't care to check the other |
329 | 348 | return rrset |
330 | 349 | else: |
331 | 350 | for rrset in section: |
332 | if rrset.match(name, rdclass, rdtype, covers, deleting): | |
351 | if rrset.full_match(name, rdclass, rdtype, covers, | |
352 | deleting): | |
333 | 353 | return rrset |
334 | 354 | if not create: |
335 | 355 | raise KeyError |
402 | 422 | *multi*, a ``bool``, should be set to ``True`` if this message is |
403 | 423 | part of a multiple message sequence. |
404 | 424 | |
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. | |
407 | 427 | |
408 | 428 | Raises ``dns.exception.TooBig`` if *max_size* was exceeded. |
409 | 429 | |
466 | 486 | *key*, a ``dns.tsig.Key`` is the key to use. If a key is specified, |
467 | 487 | the *keyring* and *algorithm* fields are not used. |
468 | 488 | |
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. | |
471 | 491 | |
472 | 492 | The format of a keyring dict is a mapping from TSIG key name, as |
473 | 493 | ``dns.name.Name`` to ``dns.tsig.Key`` or a TSIG secret, a ``bytes``. |
475 | 495 | used will be the first key in the *keyring*. Note that the order of |
476 | 496 | keys in a dictionary is not defined, so applications should supply a |
477 | 497 | 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. | |
479 | 501 | |
480 | 502 | *keyname*, a ``dns.name.Name``, ``str`` or ``None``, the name of |
481 | 503 | thes TSIG key to use; defaults to ``None``. If *keyring* is a |
496 | 518 | """ |
497 | 519 | |
498 | 520 | 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) | |
500 | 525 | else: |
501 | 526 | if isinstance(keyname, str): |
502 | 527 | keyname = dns.name.from_text(keyname) |
505 | 530 | key = keyring[keyname] |
506 | 531 | if isinstance(key, bytes): |
507 | 532 | key = dns.tsig.Key(keyname, key, algorithm) |
508 | self.keyring = key | |
533 | self.keyring = key | |
509 | 534 | if original_id is None: |
510 | 535 | original_id = self.id |
511 | 536 | self.tsig = self._make_tsig(keyname, self.keyring.algorithm, 0, fudge, |
544 | 569 | return bool(self.tsig) |
545 | 570 | |
546 | 571 | @staticmethod |
547 | def _make_opt(flags=0, payload=1280, options=None): | |
572 | def _make_opt(flags=0, payload=DEFAULT_EDNS_PAYLOAD, options=None): | |
548 | 573 | opt = dns.rdtypes.ANY.OPT.OPT(payload, dns.rdatatype.OPT, |
549 | 574 | options or ()) |
550 | 575 | return dns.rrset.from_rdata(dns.name.root, int(flags), opt) |
551 | 576 | |
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): | |
554 | 579 | """Configure EDNS behavior. |
555 | 580 | |
556 | 581 | *edns*, an ``int``, is the EDNS level to use. Specifying |
574 | 599 | |
575 | 600 | if edns is None or edns is False: |
576 | 601 | edns = -1 |
577 | if edns is True: | |
602 | elif edns is True: | |
578 | 603 | edns = 0 |
579 | if request_payload is None: | |
580 | request_payload = payload | |
581 | 604 | if edns < 0: |
582 | ednsflags = 0 | |
583 | payload = 0 | |
584 | request_payload = 0 | |
585 | options = [] | |
605 | self.opt = None | |
606 | self.request_payload = 0 | |
586 | 607 | else: |
587 | 608 | # make sure the EDNS version in ednsflags agrees with edns |
588 | 609 | ednsflags &= 0xFF00FFFF |
589 | 610 | ednsflags |= (edns << 16) |
590 | 611 | if options is None: |
591 | 612 | options = [] |
592 | if edns >= 0: | |
593 | 613 | 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 | |
597 | 617 | |
598 | 618 | @property |
599 | 619 | def edns(self): |
649 | 669 | |
650 | 670 | Returns an ``int``. |
651 | 671 | """ |
652 | return dns.rcode.from_flags(self.flags, self.ednsflags) | |
672 | return dns.rcode.from_flags(int(self.flags), int(self.ednsflags)) | |
653 | 673 | |
654 | 674 | def set_rcode(self, rcode): |
655 | 675 | """Set the rcode. |
667 | 687 | |
668 | 688 | Returns an ``int``. |
669 | 689 | """ |
670 | return dns.opcode.from_flags(self.flags) | |
690 | return dns.opcode.from_flags(int(self.flags)) | |
671 | 691 | |
672 | 692 | def set_opcode(self, opcode): |
673 | 693 | """Set the opcode. |
681 | 701 | # What the caller picked is fine. |
682 | 702 | return value |
683 | 703 | |
704 | # pylint: disable=unused-argument | |
705 | ||
684 | 706 | def _parse_rr_header(self, section, name, rdclass, rdtype): |
685 | 707 | return (rdclass, rdtype, None, False) |
708 | ||
709 | # pylint: enable=unused-argument | |
686 | 710 | |
687 | 711 | def _parse_special_rr_header(self, section, count, position, |
688 | 712 | name, rdclass, rdtype): |
698 | 722 | return (rdclass, rdtype, None, False) |
699 | 723 | |
700 | 724 | |
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 | ||
701 | 750 | 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 | |
703 | 840 | |
704 | 841 | |
705 | 842 | def _maybe_import_update(): |
706 | 843 | # We avoid circular imports by doing this here. We do it in another |
707 | 844 | # function as doing it in _message_factory_from_opcode() makes "dns" |
708 | 845 | # a local symbol, and the first line fails :) |
846 | ||
847 | # pylint: disable=redefined-outer-name,import-outside-toplevel,unused-import | |
709 | 848 | import dns.update # noqa: F401 |
710 | 849 | |
711 | 850 | |
752 | 891 | """ |
753 | 892 | |
754 | 893 | section = self.message.sections[section_number] |
755 | for i in range(qcount): | |
894 | for _ in range(qcount): | |
756 | 895 | qname = self.parser.get_name(self.message.origin) |
757 | 896 | (rdtype, rdclass) = self.parser.get_struct('!HH') |
758 | 897 | (rdclass, rdtype, _, _) = \ |
810 | 949 | key = self.keyring.get(absolute_name) |
811 | 950 | if isinstance(key, bytes): |
812 | 951 | key = dns.tsig.Key(absolute_name, key, rd.algorithm) |
952 | elif callable(self.keyring): | |
953 | key = self.keyring(self.message, absolute_name) | |
813 | 954 | else: |
814 | 955 | key = self.keyring |
815 | 956 | if key is None: |
846 | 987 | self.parser.get_struct('!HHHHHH') |
847 | 988 | factory = _message_factory_from_opcode(dns.opcode.from_flags(flags)) |
848 | 989 | self.message = factory(id=id) |
849 | self.message.flags = flags | |
990 | self.message.flags = dns.flags.Flag(flags) | |
850 | 991 | self.initialize_message(self.message) |
851 | 992 | self.one_rr_per_rrset = \ |
852 | 993 | self.message._get_one_rr_per_rrset(self.one_rr_per_rrset) |
853 | 994 | self._get_question(MessageSection.QUESTION, qcount) |
854 | 995 | if self.question_only: |
855 | return | |
996 | return self.message | |
856 | 997 | self._get_section(MessageSection.ANSWER, ancount) |
857 | 998 | self._get_section(MessageSection.AUTHORITY, aucount) |
858 | 999 | self._get_section(MessageSection.ADDITIONAL, adcount) |
884 | 1025 | of a zone transfer, *origin* should be the origin name of the |
885 | 1026 | zone. If not ``None``, names will be relativized to the origin. |
886 | 1027 | |
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. | |
889 | 1030 | |
890 | 1031 | *multi*, a ``bool``, should be set to ``True`` if this message is |
891 | 1032 | part of a multiple message sequence. |
970 | 1111 | self.id = None |
971 | 1112 | self.edns = -1 |
972 | 1113 | self.ednsflags = 0 |
973 | self.payload = None | |
1114 | self.payload = DEFAULT_EDNS_PAYLOAD | |
974 | 1115 | self.rcode = None |
975 | 1116 | self.opcode = dns.opcode.QUERY |
976 | 1117 | self.flags = 0 |
977 | 1118 | |
978 | def _header_line(self, section): | |
1119 | def _header_line(self, _): | |
979 | 1120 | """Process one line from the text format header section.""" |
980 | 1121 | |
981 | 1122 | token = self.tok.get() |
1027 | 1168 | self.relativize, |
1028 | 1169 | self.relativize_to) |
1029 | 1170 | name = self.last_name |
1171 | if name is None: | |
1172 | raise NoPreviousName | |
1030 | 1173 | token = self.tok.get() |
1031 | 1174 | if not token.is_identifier(): |
1032 | 1175 | raise dns.exception.SyntaxError |
1061 | 1204 | self.relativize, |
1062 | 1205 | self.relativize_to) |
1063 | 1206 | name = self.last_name |
1207 | if name is None: | |
1208 | raise NoPreviousName | |
1064 | 1209 | token = self.tok.get() |
1065 | 1210 | if not token.is_identifier(): |
1066 | 1211 | raise dns.exception.SyntaxError |
1091 | 1236 | token = self.tok.get() |
1092 | 1237 | if empty and not token.is_eol_or_eof(): |
1093 | 1238 | raise dns.exception.SyntaxError |
1239 | if not empty and token.is_eol_or_eof(): | |
1240 | raise dns.exception.UnexpectedEnd | |
1094 | 1241 | if not token.is_eol_or_eof(): |
1095 | 1242 | self.tok.unget(token) |
1096 | 1243 | rd = dns.rdata.from_text(rdclass, rdtype, self.tok, |
1291 | 1438 | kwargs = {} |
1292 | 1439 | if ednsflags is not None: |
1293 | 1440 | kwargs['ednsflags'] = ednsflags |
1294 | if use_edns is None: | |
1295 | use_edns = 0 | |
1296 | 1441 | if payload is not None: |
1297 | 1442 | kwargs['payload'] = payload |
1298 | if use_edns is None: | |
1299 | use_edns = 0 | |
1300 | 1443 | if request_payload is not None: |
1301 | 1444 | kwargs['request_payload'] = request_payload |
1302 | if use_edns is None: | |
1303 | use_edns = 0 | |
1304 | 1445 | if options is not None: |
1305 | 1446 | 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 | |
1308 | 1449 | kwargs['edns'] = use_edns |
1309 | 1450 | m.use_edns(**kwargs) |
1310 | 1451 | m.want_dnssec(want_dnssec) |
1354 | 1495 | tsig_error, b'', query.keyalgorithm) |
1355 | 1496 | response.request_mac = query.mac |
1356 | 1497 | 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 |
0 | 0 | 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 | |
2 | 2 | import hmac |
3 | 3 | |
4 | 4 | class Message: |
25 | 25 | def is_response(self, other : Message) -> bool: |
26 | 26 | ... |
27 | 27 | |
28 | def set_rcode(self, rcode : rcode.Rcode): | |
29 | ... | |
30 | ||
28 | 31 | def from_text(a : str, idna_codec : Optional[name.IDNACodec] = None) -> Message: |
29 | 32 | ... |
30 | 33 | |
31 | 34 | 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, | |
33 | 36 | question_only=False, one_rr_per_rrset=False, |
34 | 37 | ignore_trailing=False) -> Message: |
35 | 38 | ... |
29 | 29 | |
30 | 30 | import dns.wire |
31 | 31 | import dns.exception |
32 | import dns.immutable | |
32 | 33 | |
33 | 34 | # fullcompare() result values |
34 | 35 | |
214 | 215 | if not have_idna_2008: |
215 | 216 | raise NoIDNA2008 |
216 | 217 | try: |
218 | ulabel = idna.ulabel(label) | |
217 | 219 | 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) | |
220 | 222 | except (idna.IDNAError, UnicodeError) as e: |
221 | 223 | raise IDNAException(idna_exception=e) |
222 | 224 | |
303 | 305 | raise ValueError # pragma: no cover |
304 | 306 | |
305 | 307 | |
308 | @dns.immutable.immutable | |
306 | 309 | class Name: |
307 | 310 | |
308 | 311 | """A DNS name. |
319 | 322 | """ |
320 | 323 | |
321 | 324 | labels = [_maybe_convert_to_binary(x) for x in labels] |
322 | super().__setattr__('labels', tuple(labels)) | |
325 | self.labels = tuple(labels) | |
323 | 326 | _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") | |
332 | 327 | |
333 | 328 | def __copy__(self): |
334 | 329 | return Name(self.labels) |
457 | 452 | Returns a ``bool``. |
458 | 453 | """ |
459 | 454 | |
460 | (nr, o, nl) = self.fullcompare(other) | |
455 | (nr, _, _) = self.fullcompare(other) | |
461 | 456 | if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL: |
462 | 457 | return True |
463 | 458 | return False |
471 | 466 | Returns a ``bool``. |
472 | 467 | """ |
473 | 468 | |
474 | (nr, o, nl) = self.fullcompare(other) | |
469 | (nr, _, _) = self.fullcompare(other) | |
475 | 470 | if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL: |
476 | 471 | return True |
477 | 472 | return False |
10 | 10 | def is_wild(self) -> bool: ... |
11 | 11 | def fullcompare(self, other) -> Tuple[int,int,int]: ... |
12 | 12 | 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: ... | |
17 | 19 | def to_text(self, omit_final_dot=False) -> str: ... |
18 | 20 | def to_unicode(self, omit_final_dot=False, idna_codec=None) -> str: ... |
19 | 21 | def to_digestable(self, origin=None) -> bytes: ... |
20 | 22 | def to_wire(self, file=None, compress=None, origin=None, |
21 | 23 | 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: ... | |
24 | 26 | def split(self, depth) -> List[Tuple[str,str]]: ... |
25 | 27 | 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: ... | |
29 | 31 | def parent(self) -> Name: ... |
30 | 32 | |
31 | 33 | class IDNACodec: |
84 | 84 | return key in self.__store |
85 | 85 | |
86 | 86 | 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. | |
88 | 88 | |
89 | 89 | The deepest match is the longest name in the dictionary which is |
90 | 90 | a superdomain of *name*. Note that *superdomain* includes matching |
179 | 179 | |
180 | 180 | if not isinstance(replacement, dns.rdataset.Rdataset): |
181 | 181 | 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() | |
182 | 186 | self.delete_rdataset(replacement.rdclass, replacement.rdtype, |
183 | 187 | replacement.covers) |
184 | 188 | self.rdatasets.append(replacement) |
38 | 38 | @classmethod |
39 | 39 | def _unknown_exception_class(cls): |
40 | 40 | return UnknownOpcode |
41 | ||
42 | globals().update(Opcode.__members__) | |
43 | 41 | |
44 | 42 | |
45 | 43 | class UnknownOpcode(dns.exception.DNSException): |
104 | 102 | """ |
105 | 103 | |
106 | 104 | 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 |
17 | 17 | """Talk to a DNS server.""" |
18 | 18 | |
19 | 19 | import contextlib |
20 | import enum | |
20 | 21 | import errno |
21 | 22 | import os |
22 | import select | |
23 | import selectors | |
23 | 24 | import socket |
24 | 25 | import struct |
25 | 26 | import time |
34 | 35 | import dns.rdataclass |
35 | 36 | import dns.rdatatype |
36 | 37 | import dns.serial |
38 | import dns.xfr | |
37 | 39 | |
38 | 40 | try: |
39 | 41 | import requests |
72 | 74 | """A DNS query response does not respond to the question asked.""" |
73 | 75 | |
74 | 76 | |
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 | ||
84 | 77 | class NoDOH(dns.exception.DNSException): |
85 | 78 | """DNS over HTTPS (DOH) was requested but the requests module is not |
86 | 79 | available.""" |
80 | ||
81 | ||
82 | # for backwards compatibility | |
83 | TransferError = dns.xfr.TransferError | |
87 | 84 | |
88 | 85 | |
89 | 86 | def _compute_times(timeout): |
93 | 90 | else: |
94 | 91 | return (now, now + timeout) |
95 | 92 | |
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 | |
145 | 96 | # events. An "expiration" absolute time is converted into a relative |
146 | 97 | # 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): | |
168 | 123 | # Internal API. Do not use. |
169 | 124 | |
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'): | |
175 | 130 | # Prefer poll() on platforms that support it because it has no |
176 | 131 | # limits on the maximum value of a file descriptor (plus it will |
177 | 132 | # be more efficient for high values). |
178 | _polling_backend = _poll_for | |
133 | _selector_class = selectors.PollSelector | |
179 | 134 | else: |
180 | _polling_backend = _select_for # pragma: no cover | |
135 | _selector_class = selectors.SelectSelector # pragma: no cover | |
181 | 136 | |
182 | 137 | |
183 | 138 | def _wait_for_readable(s, expiration): |
322 | 277 | raise NoDOH # pragma: no cover |
323 | 278 | |
324 | 279 | 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) | |
328 | 282 | transport_adapter = None |
329 | 283 | headers = { |
330 | 284 | "accept": "application/dns-message" |
331 | 285 | } |
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: | |
335 | 288 | url = 'https://{}:{}{}'.format(where, port, path) |
336 | elif where_af == socket.AF_INET6: | |
289 | elif af == socket.AF_INET6: | |
337 | 290 | 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 | |
346 | 298 | if source is not None: |
347 | 299 | # set source port and source address |
348 | 300 | transport_adapter = SourceAddressAdapter(source) |
386 | 338 | raise BadResponse |
387 | 339 | return r |
388 | 340 | |
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 | ||
389 | 368 | def send_udp(sock, what, destination, expiration=None): |
390 | 369 | """Send a DNS message to the specified UDP socket. |
391 | 370 | |
405 | 384 | |
406 | 385 | if isinstance(what, dns.message.Message): |
407 | 386 | what = what.to_wire() |
408 | _wait_for_writable(sock, expiration) | |
409 | 387 | sent_time = time.time() |
410 | n = sock.sendto(what, destination) | |
388 | n = _udp_send(sock, what, destination, expiration) | |
411 | 389 | return (n, sent_time) |
412 | 390 | |
413 | 391 | |
457 | 435 | """ |
458 | 436 | |
459 | 437 | 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) | |
463 | 440 | if _matches_destination(sock.family, from_address, destination, |
464 | 441 | ignore_unexpected): |
465 | 442 | break |
597 | 574 | """ |
598 | 575 | s = b'' |
599 | 576 | while count > 0: |
600 | _wait_for_readable(sock, expiration) | |
601 | 577 | try: |
602 | 578 | 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) | |
605 | 585 | except ssl.SSLWantWriteError: # pragma: no cover |
606 | 586 | _wait_for_writable(sock, expiration) |
607 | continue | |
608 | if n == b'': | |
609 | raise EOFError | |
610 | count = count - len(n) | |
611 | s = s + n | |
612 | 587 | return s |
613 | 588 | |
614 | 589 | |
620 | 595 | current = 0 |
621 | 596 | l = len(data) |
622 | 597 | while current < l: |
623 | _wait_for_writable(sock, expiration) | |
624 | 598 | try: |
625 | 599 | current += sock.send(data[current:]) |
600 | except (BlockingIOError, ssl.SSLWantWriteError): | |
601 | _wait_for_writable(sock, expiration) | |
626 | 602 | except ssl.SSLWantReadError: # pragma: no cover |
627 | 603 | _wait_for_readable(sock, expiration) |
628 | continue | |
629 | except ssl.SSLWantWriteError: # pragma: no cover | |
630 | continue | |
631 | 604 | |
632 | 605 | |
633 | 606 | def send_tcp(sock, what, expiration=None): |
651 | 624 | # avoid writev() or doing a short write that would get pushed |
652 | 625 | # onto the net |
653 | 626 | tcpmsg = struct.pack("!H", l) + what |
654 | _wait_for_writable(sock, expiration) | |
655 | 627 | sent_time = time.time() |
656 | 628 | _net_write(sock, tcpmsg, expiration) |
657 | 629 | return (len(tcpmsg), sent_time) |
741 | 713 | (begin_time, expiration) = _compute_times(timeout) |
742 | 714 | with contextlib.ExitStack() as stack: |
743 | 715 | 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() | |
749 | 716 | s = sock |
750 | 717 | else: |
751 | 718 | (af, destination, source) = _destination_and_source(where, port, |
925 | 892 | _connect(s, destination, expiration) |
926 | 893 | l = len(wire) |
927 | 894 | if use_udp: |
928 | _wait_for_writable(s, expiration) | |
929 | s.send(wire) | |
895 | _udp_send(s, wire, None, expiration) | |
930 | 896 | else: |
931 | 897 | tcpmsg = struct.pack("!H", l) + wire |
932 | 898 | _net_write(s, tcpmsg, expiration) |
947 | 913 | (expiration is not None and mexpiration > expiration): |
948 | 914 | mexpiration = expiration |
949 | 915 | if use_udp: |
950 | _wait_for_readable(s, expiration) | |
951 | (wire, from_address) = s.recvfrom(65535) | |
916 | (wire, _) = _udp_recv(s, 65535, mexpiration) | |
952 | 917 | else: |
953 | 918 | ldata = _net_read(s, 2, mexpiration) |
954 | 919 | (l,) = struct.unpack("!H", ldata) |
1015 | 980 | if done and q.keyring and not r.had_tsig: |
1016 | 981 | raise dns.exception.FormError("missing TSIG") |
1017 | 982 | 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") |
71 | 71 | def _unknown_exception_class(cls): |
72 | 72 | return UnknownRcode |
73 | 73 | |
74 | globals().update(Rcode.__members__) | |
75 | 74 | |
76 | 75 | class UnknownRcode(dns.exception.DNSException): |
77 | 76 | """A DNS rcode is unknown.""" |
103 | 102 | """ |
104 | 103 | |
105 | 104 | value = (flags & 0x000f) | ((ednsflags >> 20) & 0xff0) |
106 | if value < 0 or value > 4095: | |
107 | raise ValueError('rcode must be >= 0 and <= 4095') | |
108 | 105 | return value |
109 | 106 | |
110 | 107 | |
138 | 135 | if tsig and value == Rcode.BADVERS: |
139 | 136 | return 'BADSIG' |
140 | 137 | 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 |
22 | 22 | import io |
23 | 23 | import inspect |
24 | 24 | import itertools |
25 | import random | |
25 | 26 | |
26 | 27 | import dns.wire |
27 | 28 | import dns.exception |
29 | import dns.immutable | |
30 | import dns.ipv4 | |
31 | import dns.ipv6 | |
28 | 32 | import dns.name |
29 | 33 | import dns.rdataclass |
30 | 34 | import dns.rdatatype |
31 | 35 | import dns.tokenizer |
36 | import dns.ttl | |
32 | 37 | |
33 | 38 | _chunksize = 32 |
34 | 39 | |
45 | 50 | in range(0, len(data), chunksize)]).decode() |
46 | 51 | |
47 | 52 | |
48 | def _hexify(data, chunksize=_chunksize): | |
53 | # pylint: disable=unused-argument | |
54 | ||
55 | def _hexify(data, chunksize=_chunksize, **kw): | |
49 | 56 | """Convert a binary string into its hex encoding, broken up into chunks |
50 | 57 | of chunksize characters separated by a space. |
51 | 58 | """ |
53 | 60 | return _wordbreak(binascii.hexlify(data), chunksize) |
54 | 61 | |
55 | 62 | |
56 | def _base64ify(data, chunksize=_chunksize): | |
63 | def _base64ify(data, chunksize=_chunksize, **kw): | |
57 | 64 | """Convert a binary string into its base64 encoding, broken up into chunks |
58 | 65 | of chunksize characters separated by a space. |
59 | 66 | """ |
60 | 67 | |
61 | 68 | return _wordbreak(base64.b64encode(data), chunksize) |
69 | ||
70 | # pylint: enable=unused-argument | |
62 | 71 | |
63 | 72 | __escaped = b'"\\' |
64 | 73 | |
91 | 100 | return what[0: i + 1] |
92 | 101 | return what[0:1] |
93 | 102 | |
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 | |
110 | 108 | class Rdata: |
111 | 109 | """Base class for all DNS rdata types.""" |
112 | 110 | |
113 | __slots__ = ['rdclass', 'rdtype'] | |
111 | __slots__ = ['rdclass', 'rdtype', 'rdcomment'] | |
114 | 112 | |
115 | 113 | def __init__(self, rdclass, rdtype): |
116 | 114 | """Initialize an rdata. |
120 | 118 | *rdtype*, an ``int`` is the rdatatype of the Rdata. |
121 | 119 | """ |
122 | 120 | |
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 | |
133 | 124 | |
134 | 125 | def _get_all_slots(self): |
135 | 126 | return itertools.chain.from_iterable(getattr(cls, '__slots__', []) |
152 | 143 | def __setstate__(self, state): |
153 | 144 | for slot, val in state.items(): |
154 | 145 | 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) | |
155 | 150 | |
156 | 151 | def covers(self): |
157 | 152 | """Return the type a Rdata covers. |
183 | 178 | Returns a ``str``. |
184 | 179 | """ |
185 | 180 | |
186 | raise NotImplementedError | |
181 | raise NotImplementedError # pragma: no cover | |
187 | 182 | |
188 | 183 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
189 | raise NotImplementedError | |
184 | raise NotImplementedError # pragma: no cover | |
190 | 185 | |
191 | 186 | def to_wire(self, file=None, compress=None, origin=None, |
192 | 187 | canonicalize=False): |
294 | 289 | @classmethod |
295 | 290 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
296 | 291 | 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 | |
302 | 297 | |
303 | 298 | def replace(self, **kwargs): |
304 | 299 | """ |
318 | 313 | # Ensure that all of the arguments correspond to valid fields. |
319 | 314 | # Don't allow rdclass or rdtype to be changed, though. |
320 | 315 | for key in kwargs: |
316 | if key == 'rdcomment': | |
317 | continue | |
321 | 318 | if key not in parameters: |
322 | 319 | raise AttributeError("'{}' object has no attribute '{}'" |
323 | 320 | .format(self.__class__.__name__, key)) |
330 | 327 | args = (kwargs.get(key, getattr(self, key)) for key in parameters) |
331 | 328 | |
332 | 329 | # 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. | |
336 | 330 | 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) | |
338 | 336 | 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 | |
339 | 472 | |
340 | 473 | |
341 | 474 | class GenericRdata(Rdata): |
353 | 486 | object.__setattr__(self, 'data', data) |
354 | 487 | |
355 | 488 | 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) | |
357 | 490 | |
358 | 491 | @classmethod |
359 | 492 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
363 | 496 | raise dns.exception.SyntaxError( |
364 | 497 | r'generic rdata does not start with \#') |
365 | 498 | 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() | |
373 | 500 | data = binascii.unhexlify(hex) |
374 | 501 | if len(data) != length: |
375 | 502 | raise dns.exception.SyntaxError( |
452 | 579 | Returns an instance of the chosen Rdata subclass. |
453 | 580 | |
454 | 581 | """ |
455 | ||
456 | 582 | if isinstance(tok, str): |
457 | 583 | tok = dns.tokenizer.Tokenizer(tok, idna_codec=idna_codec) |
458 | 584 | rdclass = dns.rdataclass.RdataClass.make(rdclass) |
459 | 585 | rdtype = dns.rdatatype.RdataType.make(rdtype) |
460 | 586 | 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 | |
478 | 621 | |
479 | 622 | |
480 | 623 | def from_wire_parser(rdclass, rdtype, parser, origin=None): |
504 | 647 | rdclass = dns.rdataclass.RdataClass.make(rdclass) |
505 | 648 | rdtype = dns.rdatatype.RdataType.make(rdtype) |
506 | 649 | 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) | |
508 | 652 | |
509 | 653 | |
510 | 654 | def from_wire(rdclass, rdtype, wire, current, rdlen, origin=None): |
47 | 47 | def _unknown_exception_class(cls): |
48 | 48 | return UnknownRdataclass |
49 | 49 | |
50 | globals().update(RdataClass.__members__) | |
51 | 50 | |
52 | 51 | _metaclasses = {RdataClass.NONE, RdataClass.ANY} |
53 | 52 | |
99 | 98 | if rdclass in _metaclasses: |
100 | 99 | return True |
101 | 100 | 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 |
21 | 21 | import struct |
22 | 22 | |
23 | 23 | import dns.exception |
24 | import dns.immutable | |
24 | 25 | import dns.rdatatype |
25 | 26 | import dns.rdataclass |
26 | 27 | import dns.rdata |
78 | 79 | TTL or the specified TTL. If the set contains no rdatas, set the TTL |
79 | 80 | to the specified TTL. |
80 | 81 | |
81 | *ttl*, an ``int``. | |
82 | """ | |
83 | ||
82 | *ttl*, an ``int`` or ``str``. | |
83 | """ | |
84 | ttl = dns.ttl.make(ttl) | |
84 | 85 | if len(self) == 0: |
85 | 86 | self.ttl = ttl |
86 | 87 | elif ttl < self.ttl: |
87 | 88 | self.ttl = ttl |
88 | 89 | |
89 | def add(self, rd, ttl=None): | |
90 | def add(self, rd, ttl=None): # pylint: disable=arguments-differ | |
90 | 91 | """Add the specified rdata to the rdataset. |
91 | 92 | |
92 | 93 | If the optional *ttl* parameter is supplied, then |
175 | 176 | return not self.__eq__(other) |
176 | 177 | |
177 | 178 | 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. | |
180 | 181 | |
181 | 182 | See ``dns.name.Name.choose_relativity`` for more information |
182 | 183 | on how *origin* and *relativize* determine the way names |
193 | 194 | |
194 | 195 | *relativize*, a ``bool``. If ``True``, names will be relativized |
195 | 196 | 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``. | |
196 | 203 | """ |
197 | 204 | |
198 | 205 | if name is not None: |
218 | 225 | dns.rdatatype.to_text(self.rdtype))) |
219 | 226 | else: |
220 | 227 | 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' % | |
222 | 233 | (ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass), |
223 | 234 | dns.rdatatype.to_text(self.rdtype), |
224 | 235 | rd.to_text(origin=origin, relativize=relativize, |
225 | **kw))) | |
236 | **kw), | |
237 | extra)) | |
226 | 238 | # |
227 | 239 | # We strip off the final \n for the caller's convenience in printing |
228 | 240 | # |
259 | 271 | want_shuffle = False |
260 | 272 | else: |
261 | 273 | rdclass = self.rdclass |
262 | file.seek(0, 2) | |
274 | file.seek(0, io.SEEK_END) | |
263 | 275 | if len(self) == 0: |
264 | 276 | name.to_wire(file, compress, origin) |
265 | 277 | stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0) |
283 | 295 | file.seek(start - 2) |
284 | 296 | stuff = struct.pack("!H", end - start) |
285 | 297 | file.write(stuff) |
286 | file.seek(0, 2) | |
298 | file.seek(0, io.SEEK_END) | |
287 | 299 | return len(self) |
288 | 300 | |
289 | 301 | def match(self, rdclass, rdtype, covers): |
296 | 308 | return True |
297 | 309 | return False |
298 | 310 | |
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): | |
301 | 391 | """Create an rdataset with the specified class, type, and TTL, and with |
302 | 392 | the specified list of rdatas in text format. |
303 | 393 | |
304 | 394 | *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA |
305 | 395 | encoder/decoder to use; if ``None``, the default IDNA 2003 |
306 | 396 | 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. | |
307 | 405 | |
308 | 406 | Returns a ``dns.rdataset.Rdataset`` object. |
309 | 407 | """ |
313 | 411 | r = Rdataset(rdclass, rdtype) |
314 | 412 | r.update_ttl(ttl) |
315 | 413 | 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) | |
317 | 416 | r.add(rd) |
318 | 417 | return r |
319 | 418 |
71 | 71 | NSEC3 = 50 |
72 | 72 | NSEC3PARAM = 51 |
73 | 73 | TLSA = 52 |
74 | SMIMEA = 53 | |
74 | 75 | HIP = 55 |
75 | 76 | NINFO = 56 |
76 | 77 | CDS = 59 |
77 | 78 | CDNSKEY = 60 |
78 | 79 | OPENPGPKEY = 61 |
79 | 80 | CSYNC = 62 |
81 | ZONEMD = 63 | |
82 | SVCB = 64 | |
83 | HTTPS = 65 | |
80 | 84 | SPF = 99 |
81 | 85 | UNSPEC = 103 |
82 | 86 | EUI48 = 108 |
91 | 95 | URI = 256 |
92 | 96 | CAA = 257 |
93 | 97 | AVC = 258 |
94 | AMTRELAY = 259 | |
98 | AMTRELAY = 260 | |
95 | 99 | TA = 32768 |
96 | 100 | DLV = 32769 |
97 | 101 | |
113 | 117 | |
114 | 118 | _registered_by_text = {} |
115 | 119 | _registered_by_value = {} |
116 | ||
117 | globals().update(RdataType.__members__) | |
118 | 120 | |
119 | 121 | _metatypes = {RdataType.OPT} |
120 | 122 | |
218 | 220 | _registered_by_value[rdtype] = rdtype_text |
219 | 221 | if is_singleton: |
220 | 222 | _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 |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.mxbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class AFSDB(dns.rdtypes.mxbase.UncompressedDowncasingMX): |
21 | 23 | |
22 | 24 | """AFSDB record""" |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdtypes.util |
21 | 22 | |
22 | 23 | |
23 | 24 | class Relay(dns.rdtypes.util.Gateway): |
24 | 25 | name = 'AMTRELAY relay' |
25 | 26 | |
27 | @property | |
28 | def relay(self): | |
29 | return self.gateway | |
30 | ||
31 | ||
32 | @dns.immutable.immutable | |
26 | 33 | class AMTRELAY(dns.rdata.Rdata): |
27 | 34 | |
28 | 35 | """AMTRELAY record""" |
34 | 41 | def __init__(self, rdclass, rdtype, precedence, discovery_optional, |
35 | 42 | relay_type, relay): |
36 | 43 | 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 | |
42 | 49 | |
43 | 50 | def to_text(self, origin=None, relativize=True, **kw): |
44 | 51 | relay = Relay(self.relay_type, self.relay).to_text(origin, relativize) |
56 | 63 | relay_type = tok.get_uint8() |
57 | 64 | if relay_type > 0x7f: |
58 | 65 | 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) | |
61 | 68 | return cls(rdclass, rdtype, precedence, discovery_optional, relay_type, |
62 | relay) | |
69 | relay.relay) | |
63 | 70 | |
64 | 71 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
65 | 72 | relay_type = self.relay_type | (self.discovery_optional << 7) |
73 | 80 | (precedence, relay_type) = parser.get_struct('!BB') |
74 | 81 | discovery_optional = bool(relay_type >> 7) |
75 | 82 | relay_type &= 0x7f |
76 | relay = Relay(relay_type).from_wire_parser(parser, origin) | |
83 | relay = Relay.from_wire_parser(relay_type, parser, origin) | |
77 | 84 | return cls(rdclass, rdtype, precedence, discovery_optional, relay_type, |
78 | relay) | |
85 | relay.relay) |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.txtbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class AVC(dns.rdtypes.txtbase.TXTBase): |
21 | 23 | |
22 | 24 | """AVC record""" |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | import dns.tokenizer |
22 | 23 | |
23 | 24 | |
25 | @dns.immutable.immutable | |
24 | 26 | class CAA(dns.rdata.Rdata): |
25 | 27 | |
26 | 28 | """CAA (Certification Authority Authorization) record""" |
31 | 33 | |
32 | 34 | def __init__(self, rdclass, rdtype, flags, tag, value): |
33 | 35 | 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) | |
37 | 41 | |
38 | 42 | def to_text(self, origin=None, relativize=True, **kw): |
39 | 43 | return '%u %s "%s"' % (self.flags, |
45 | 49 | relativize_to=None): |
46 | 50 | flags = tok.get_uint8() |
47 | 51 | 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") | |
52 | 52 | value = tok.get_string().encode() |
53 | 53 | return cls(rdclass, rdtype, flags, tag, value) |
54 | 54 |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.dnskeybase |
18 | import dns.immutable | |
19 | ||
20 | # pylint: disable=unused-import | |
18 | 21 | from dns.rdtypes.dnskeybase import SEP, REVOKE, ZONE # noqa: F401 |
22 | # pylint: enable=unused-import | |
19 | 23 | |
20 | ||
24 | @dns.immutable.immutable | |
21 | 25 | class CDNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase): |
22 | 26 | |
23 | 27 | """CDNSKEY record""" |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.dsbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class CDS(dns.rdtypes.dsbase.DSBase): |
21 | 23 | |
22 | 24 | """CDS record""" |
18 | 18 | import base64 |
19 | 19 | |
20 | 20 | import dns.exception |
21 | import dns.immutable | |
21 | 22 | import dns.dnssec |
22 | 23 | import dns.rdata |
23 | 24 | import dns.tokenizer |
53 | 54 | return str(what) |
54 | 55 | |
55 | 56 | |
57 | @dns.immutable.immutable | |
56 | 58 | class CERT(dns.rdata.Rdata): |
57 | 59 | |
58 | 60 | """CERT record""" |
64 | 66 | def __init__(self, rdclass, rdtype, certificate_type, key_tag, algorithm, |
65 | 67 | certificate): |
66 | 68 | 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) | |
71 | 73 | |
72 | 74 | def to_text(self, origin=None, relativize=True, **kw): |
73 | 75 | certificate_type = _ctype_to_text(self.certificate_type) |
74 | 76 | return "%s %d %s %s" % (certificate_type, self.key_tag, |
75 | 77 | dns.dnssec.algorithm_to_text(self.algorithm), |
76 | dns.rdata._base64ify(self.certificate)) | |
78 | dns.rdata._base64ify(self.certificate, **kw)) | |
77 | 79 | |
78 | 80 | @classmethod |
79 | 81 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
81 | 83 | certificate_type = _ctype_from_text(tok.get_string()) |
82 | 84 | key_tag = tok.get_uint16() |
83 | 85 | algorithm = dns.dnssec.algorithm_from_text(tok.get_string()) |
84 | if algorithm < 0 or algorithm > 255: | |
85 | raise dns.exception.SyntaxError("bad algorithm type") | |
86 | 86 | b64 = tok.concatenate_remaining_identifiers().encode() |
87 | 87 | certificate = base64.b64decode(b64) |
88 | 88 | return cls(rdclass, rdtype, certificate_type, key_tag, |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.nsbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class CNAME(dns.rdtypes.nsbase.NSBase): |
21 | 23 | |
22 | 24 | """CNAME record |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | import dns.rdatatype |
22 | 23 | import dns.name |
23 | 24 | import dns.rdtypes.util |
24 | 25 | |
25 | 26 | |
27 | @dns.immutable.immutable | |
26 | 28 | class Bitmap(dns.rdtypes.util.Bitmap): |
27 | 29 | type_name = 'CSYNC' |
28 | 30 | |
29 | 31 | |
32 | @dns.immutable.immutable | |
30 | 33 | class CSYNC(dns.rdata.Rdata): |
31 | 34 | |
32 | 35 | """CSYNC record""" |
35 | 38 | |
36 | 39 | def __init__(self, rdclass, rdtype, serial, flags, windows): |
37 | 40 | 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) | |
41 | 46 | |
42 | 47 | def to_text(self, origin=None, relativize=True, **kw): |
43 | 48 | text = Bitmap(self.windows).to_text() |
48 | 53 | relativize_to=None): |
49 | 54 | serial = tok.get_uint32() |
50 | 55 | 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) | |
53 | 58 | |
54 | 59 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
55 | 60 | file.write(struct.pack('!IH', self.serial, self.flags)) |
58 | 63 | @classmethod |
59 | 64 | def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): |
60 | 65 | (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) |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.dsbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class DLV(dns.rdtypes.dsbase.DSBase): |
21 | 23 | |
22 | 24 | """DLV record""" |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.nsbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class DNAME(dns.rdtypes.nsbase.UncompressedNS): |
21 | 23 | |
22 | 24 | """DNAME record""" |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.dnskeybase |
18 | import dns.immutable | |
19 | ||
20 | # pylint: disable=unused-import | |
18 | 21 | from dns.rdtypes.dnskeybase import SEP, REVOKE, ZONE # noqa: F401 |
22 | # pylint: enable=unused-import | |
19 | 23 | |
20 | ||
24 | @dns.immutable.immutable | |
21 | 25 | class DNSKEY(dns.rdtypes.dnskeybase.DNSKEYBase): |
22 | 26 | |
23 | 27 | """DNSKEY record""" |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.dsbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class DS(dns.rdtypes.dsbase.DSBase): |
21 | 23 | |
22 | 24 | """DS record""" |
16 | 16 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | 17 | |
18 | 18 | import dns.rdtypes.euibase |
19 | import dns.immutable | |
19 | 20 | |
20 | 21 | |
22 | @dns.immutable.immutable | |
21 | 23 | class EUI48(dns.rdtypes.euibase.EUIBase): |
22 | 24 | |
23 | 25 | """EUI48 record""" |
16 | 16 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | 17 | |
18 | 18 | import dns.rdtypes.euibase |
19 | import dns.immutable | |
19 | 20 | |
20 | 21 | |
22 | @dns.immutable.immutable | |
21 | 23 | class EUI64(dns.rdtypes.euibase.EUIBase): |
22 | 24 | |
23 | 25 | """EUI64 record""" |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | import dns.tokenizer |
22 | 23 | |
40 | 41 | raise dns.exception.FormError |
41 | 42 | |
42 | 43 | |
43 | def _sanitize(value): | |
44 | if isinstance(value, str): | |
45 | return value.encode() | |
46 | return value | |
47 | ||
48 | ||
44 | @dns.immutable.immutable | |
49 | 45 | class GPOS(dns.rdata.Rdata): |
50 | 46 | |
51 | 47 | """GPOS record""" |
65 | 61 | if isinstance(altitude, float) or \ |
66 | 62 | isinstance(altitude, int): |
67 | 63 | 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) | |
71 | 67 | _validate_float_string(latitude) |
72 | 68 | _validate_float_string(longitude) |
73 | 69 | _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 | |
77 | 73 | flat = self.float_latitude |
78 | 74 | if flat < -90.0 or flat > 90.0: |
79 | 75 | raise dns.exception.FormError('bad latitude') |
92 | 88 | latitude = tok.get_string() |
93 | 89 | longitude = tok.get_string() |
94 | 90 | altitude = tok.get_string() |
95 | tok.get_eol() | |
96 | 91 | return cls(rdclass, rdtype, latitude, longitude, altitude) |
97 | 92 | |
98 | 93 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | import dns.tokenizer |
22 | 23 | |
23 | 24 | |
25 | @dns.immutable.immutable | |
24 | 26 | class HINFO(dns.rdata.Rdata): |
25 | 27 | |
26 | 28 | """HINFO record""" |
31 | 33 | |
32 | 34 | def __init__(self, rdclass, rdtype, cpu, os): |
33 | 35 | 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) | |
42 | 38 | |
43 | 39 | def to_text(self, origin=None, relativize=True, **kw): |
44 | 40 | return '"{}" "{}"'.format(dns.rdata._escapify(self.cpu), |
49 | 45 | relativize_to=None): |
50 | 46 | cpu = tok.get_string(max_length=255) |
51 | 47 | os = tok.get_string(max_length=255) |
52 | tok.get_eol() | |
53 | 48 | return cls(rdclass, rdtype, cpu, os) |
54 | 49 | |
55 | 50 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
19 | 19 | import binascii |
20 | 20 | |
21 | 21 | import dns.exception |
22 | import dns.immutable | |
22 | 23 | import dns.rdata |
23 | 24 | import dns.rdatatype |
24 | 25 | |
25 | 26 | |
27 | @dns.immutable.immutable | |
26 | 28 | class HIP(dns.rdata.Rdata): |
27 | 29 | |
28 | 30 | """HIP record""" |
33 | 35 | |
34 | 36 | def __init__(self, rdclass, rdtype, hit, algorithm, key, servers): |
35 | 37 | 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) | |
40 | 42 | |
41 | 43 | def to_text(self, origin=None, relativize=True, **kw): |
42 | 44 | hit = binascii.hexlify(self.hit).decode() |
54 | 56 | relativize_to=None): |
55 | 57 | algorithm = tok.get_uint8() |
56 | 58 | hit = binascii.unhexlify(tok.get_string().encode()) |
57 | if len(hit) > 255: | |
58 | raise dns.exception.SyntaxError("HIT too long") | |
59 | 59 | key = base64.b64decode(tok.get_string().encode()) |
60 | 60 | servers = [] |
61 | while 1: | |
62 | token = tok.get() | |
63 | if token.is_eol_or_eof(): | |
64 | break | |
61 | for token in tok.get_remaining(): | |
65 | 62 | server = tok.as_name(token, origin, relativize, relativize_to) |
66 | 63 | servers.append(server) |
67 | 64 | return cls(rdclass, rdtype, hit, algorithm, key, servers) |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | import dns.tokenizer |
22 | 23 | |
23 | 24 | |
25 | @dns.immutable.immutable | |
24 | 26 | class ISDN(dns.rdata.Rdata): |
25 | 27 | |
26 | 28 | """ISDN record""" |
31 | 33 | |
32 | 34 | def __init__(self, rdclass, rdtype, address, subaddress): |
33 | 35 | 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) | |
42 | 38 | |
43 | 39 | def to_text(self, origin=None, relativize=True, **kw): |
44 | 40 | if self.subaddress: |
51 | 47 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
52 | 48 | relativize_to=None): |
53 | 49 | 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 | |
58 | 53 | else: |
59 | tok.unget(t) | |
60 | 54 | subaddress = '' |
61 | tok.get_eol() | |
62 | 55 | return cls(rdclass, rdtype, address, subaddress) |
63 | 56 | |
64 | 57 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | |
22 | 23 | |
33 | 34 | _MAX_LONGITUDE = 0x80000000 + 180 * 3600000 |
34 | 35 | _MIN_LONGITUDE = 0x80000000 - 180 * 3600000 |
35 | 36 | |
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 | |
40 | 37 | |
41 | 38 | def _exponent_of(what, desc): |
42 | 39 | if what == 0: |
43 | 40 | return 0 |
44 | 41 | exp = None |
45 | 42 | for (i, pow) in enumerate(_pows): |
46 | if what // pow == 0: | |
43 | if what < pow: | |
47 | 44 | exp = i - 1 |
48 | 45 | break |
49 | 46 | if exp is None or exp < 0: |
93 | 90 | return base * pow(10, exponent) |
94 | 91 | |
95 | 92 | |
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 | |
96 | 107 | class LOC(dns.rdata.Rdata): |
97 | 108 | |
98 | 109 | """LOC record""" |
118 | 129 | latitude = float(latitude) |
119 | 130 | if isinstance(latitude, float): |
120 | 131 | 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) | |
122 | 134 | if isinstance(longitude, int): |
123 | 135 | longitude = float(longitude) |
124 | 136 | if isinstance(longitude, float): |
125 | 137 | 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) | |
131 | 144 | |
132 | 145 | def to_text(self, origin=None, relativize=True, **kw): |
133 | 146 | if self.latitude[4] > 0: |
166 | 179 | vprec = _default_vprec |
167 | 180 | |
168 | 181 | latitude[0] = tok.get_int() |
169 | if latitude[0] > 90: | |
170 | raise dns.exception.SyntaxError('latitude >= 90') | |
171 | 182 | t = tok.get_string() |
172 | 183 | if t.isdigit(): |
173 | 184 | latitude[1] = int(t) |
174 | if latitude[1] >= 60: | |
175 | raise dns.exception.SyntaxError('latitude minutes >= 60') | |
176 | 185 | t = tok.get_string() |
177 | 186 | if '.' in t: |
178 | 187 | (seconds, milliseconds) = t.split('.') |
180 | 189 | raise dns.exception.SyntaxError( |
181 | 190 | 'bad latitude seconds value') |
182 | 191 | latitude[2] = int(seconds) |
183 | if latitude[2] >= 60: | |
184 | raise dns.exception.SyntaxError('latitude seconds >= 60') | |
185 | 192 | l = len(milliseconds) |
186 | 193 | if l == 0 or l > 3 or not milliseconds.isdigit(): |
187 | 194 | raise dns.exception.SyntaxError( |
203 | 210 | raise dns.exception.SyntaxError('bad latitude hemisphere value') |
204 | 211 | |
205 | 212 | longitude[0] = tok.get_int() |
206 | if longitude[0] > 180: | |
207 | raise dns.exception.SyntaxError('longitude > 180') | |
208 | 213 | t = tok.get_string() |
209 | 214 | if t.isdigit(): |
210 | 215 | longitude[1] = int(t) |
211 | if longitude[1] >= 60: | |
212 | raise dns.exception.SyntaxError('longitude minutes >= 60') | |
213 | 216 | t = tok.get_string() |
214 | 217 | if '.' in t: |
215 | 218 | (seconds, milliseconds) = t.split('.') |
217 | 220 | raise dns.exception.SyntaxError( |
218 | 221 | 'bad longitude seconds value') |
219 | 222 | longitude[2] = int(seconds) |
220 | if longitude[2] >= 60: | |
221 | raise dns.exception.SyntaxError('longitude seconds >= 60') | |
222 | 223 | l = len(milliseconds) |
223 | 224 | if l == 0 or l > 3 or not milliseconds.isdigit(): |
224 | 225 | raise dns.exception.SyntaxError( |
244 | 245 | t = t[0: -1] |
245 | 246 | altitude = float(t) * 100.0 # m -> cm |
246 | 247 | |
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 | |
250 | 251 | if value[-1] == 'm': |
251 | 252 | value = value[0: -1] |
252 | 253 | 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 | |
256 | 256 | if value[-1] == 'm': |
257 | 257 | value = value[0: -1] |
258 | 258 | 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 | |
262 | 261 | if value[-1] == 'm': |
263 | 262 | value = value[0: -1] |
264 | 263 | vprec = float(value) * 100.0 # m -> cm |
265 | tok.get_eol() | |
266 | 264 | |
267 | 265 | # Try encoding these now so we raise if they are bad |
268 | 266 | _encode_size(size, "size") |
295 | 293 | def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): |
296 | 294 | (version, size, hprec, vprec, latitude, longitude, altitude) = \ |
297 | 295 | parser.get_struct("!BBBBIII") |
296 | if version != 0: | |
297 | raise dns.exception.FormError("LOC version not zero") | |
298 | 298 | if latitude < _MIN_LATITUDE or latitude > _MAX_LATITUDE: |
299 | 299 | raise dns.exception.FormError("bad latitude") |
300 | 300 | if latitude > 0x80000000: |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.mxbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class MX(dns.rdtypes.mxbase.MXBase): |
21 | 23 | |
22 | 24 | """MX record""" |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.txtbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class NINFO(dns.rdtypes.txtbase.TXTBase): |
21 | 23 | |
22 | 24 | """NINFO record""" |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.nsbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class NS(dns.rdtypes.nsbase.NSBase): |
21 | 23 | |
22 | 24 | """NS record""" |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.exception |
18 | import dns.immutable | |
18 | 19 | import dns.rdata |
19 | 20 | import dns.rdatatype |
20 | 21 | import dns.name |
21 | 22 | import dns.rdtypes.util |
22 | 23 | |
23 | 24 | |
25 | @dns.immutable.immutable | |
24 | 26 | class Bitmap(dns.rdtypes.util.Bitmap): |
25 | 27 | type_name = 'NSEC' |
26 | 28 | |
27 | 29 | |
30 | @dns.immutable.immutable | |
28 | 31 | class NSEC(dns.rdata.Rdata): |
29 | 32 | |
30 | 33 | """NSEC record""" |
33 | 36 | |
34 | 37 | def __init__(self, rdclass, rdtype, next, windows): |
35 | 38 | 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) | |
38 | 43 | |
39 | 44 | def to_text(self, origin=None, relativize=True, **kw): |
40 | 45 | next = self.next.choose_relativity(origin, relativize) |
45 | 50 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
46 | 51 | relativize_to=None): |
47 | 52 | next = tok.get_name(origin, relativize, relativize_to) |
48 | windows = Bitmap().from_text(tok) | |
53 | windows = Bitmap.from_text(tok) | |
49 | 54 | return cls(rdclass, rdtype, next, windows) |
50 | 55 | |
51 | 56 | 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. | |
52 | 59 | self.next.to_wire(file, None, origin, False) |
53 | 60 | Bitmap(self.windows).to_wire(file) |
54 | 61 | |
55 | 62 | @classmethod |
56 | 63 | def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): |
57 | 64 | 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) |
19 | 19 | import struct |
20 | 20 | |
21 | 21 | import dns.exception |
22 | import dns.immutable | |
22 | 23 | import dns.rdata |
23 | 24 | import dns.rdatatype |
24 | 25 | import dns.rdtypes.util |
36 | 37 | OPTOUT = 1 |
37 | 38 | |
38 | 39 | |
40 | @dns.immutable.immutable | |
39 | 41 | class Bitmap(dns.rdtypes.util.Bitmap): |
40 | 42 | type_name = 'NSEC3' |
41 | 43 | |
42 | 44 | |
45 | @dns.immutable.immutable | |
43 | 46 | class NSEC3(dns.rdata.Rdata): |
44 | 47 | |
45 | 48 | """NSEC3 record""" |
49 | 52 | def __init__(self, rdclass, rdtype, algorithm, flags, iterations, salt, |
50 | 53 | next, windows): |
51 | 54 | 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) | |
61 | 63 | |
62 | 64 | def to_text(self, origin=None, relativize=True, **kw): |
63 | 65 | next = base64.b32encode(self.next).translate( |
84 | 86 | next = tok.get_string().encode( |
85 | 87 | 'ascii').upper().translate(b32_hex_to_normal) |
86 | 88 | next = base64.b32decode(next) |
87 | windows = Bitmap().from_text(tok) | |
89 | bitmap = Bitmap.from_text(tok) | |
88 | 90 | return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, |
89 | windows) | |
91 | bitmap) | |
90 | 92 | |
91 | 93 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
92 | 94 | l = len(self.salt) |
103 | 105 | (algorithm, flags, iterations) = parser.get_struct('!BBH') |
104 | 106 | salt = parser.get_counted_bytes() |
105 | 107 | next = parser.get_counted_bytes() |
106 | windows = Bitmap().from_wire_parser(parser) | |
108 | bitmap = Bitmap.from_wire_parser(parser) | |
107 | 109 | return cls(rdclass, rdtype, algorithm, flags, iterations, salt, next, |
108 | windows) | |
110 | bitmap) |
18 | 18 | import binascii |
19 | 19 | |
20 | 20 | import dns.exception |
21 | import dns.immutable | |
21 | 22 | import dns.rdata |
22 | 23 | |
23 | 24 | |
25 | @dns.immutable.immutable | |
24 | 26 | class NSEC3PARAM(dns.rdata.Rdata): |
25 | 27 | |
26 | 28 | """NSEC3PARAM record""" |
29 | 31 | |
30 | 32 | def __init__(self, rdclass, rdtype, algorithm, flags, iterations, salt): |
31 | 33 | 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) | |
39 | 38 | |
40 | 39 | def to_text(self, origin=None, relativize=True, **kw): |
41 | 40 | if self.salt == b'': |
56 | 55 | salt = '' |
57 | 56 | else: |
58 | 57 | salt = binascii.unhexlify(salt.encode()) |
59 | tok.get_eol() | |
60 | 58 | return cls(rdclass, rdtype, algorithm, flags, iterations, salt) |
61 | 59 | |
62 | 60 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
17 | 17 | import base64 |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | import dns.tokenizer |
22 | 23 | |
24 | @dns.immutable.immutable | |
23 | 25 | class OPENPGPKEY(dns.rdata.Rdata): |
24 | 26 | |
25 | 27 | """OPENPGPKEY record""" |
28 | 30 | |
29 | 31 | def __init__(self, rdclass, rdtype, key): |
30 | 32 | super().__init__(rdclass, rdtype) |
31 | object.__setattr__(self, 'key', key) | |
33 | self.key = self._as_bytes(key) | |
32 | 34 | |
33 | 35 | 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) | |
35 | 37 | |
36 | 38 | @classmethod |
37 | 39 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.edns |
20 | import dns.immutable | |
20 | 21 | import dns.exception |
21 | 22 | import dns.rdata |
22 | 23 | |
23 | 24 | |
25 | # We don't implement from_text, and that's ok. | |
26 | # pylint: disable=abstract-method | |
27 | ||
28 | @dns.immutable.immutable | |
24 | 29 | class OPT(dns.rdata.Rdata): |
25 | 30 | |
26 | 31 | """OPT record""" |
39 | 44 | """ |
40 | 45 | |
41 | 46 | 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) | |
43 | 52 | |
44 | 53 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
45 | 54 | for opt in self.options: |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.nsbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class PTR(dns.rdtypes.nsbase.NSBase): |
21 | 23 | |
22 | 24 | """PTR record""" |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.exception |
18 | import dns.immutable | |
18 | 19 | import dns.rdata |
19 | 20 | import dns.name |
20 | 21 | |
21 | 22 | |
23 | @dns.immutable.immutable | |
22 | 24 | class RP(dns.rdata.Rdata): |
23 | 25 | |
24 | 26 | """RP record""" |
29 | 31 | |
30 | 32 | def __init__(self, rdclass, rdtype, mbox, txt): |
31 | 33 | 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) | |
34 | 36 | |
35 | 37 | def to_text(self, origin=None, relativize=True, **kw): |
36 | 38 | mbox = self.mbox.choose_relativity(origin, relativize) |
42 | 44 | relativize_to=None): |
43 | 45 | mbox = tok.get_name(origin, relativize, relativize_to) |
44 | 46 | txt = tok.get_name(origin, relativize, relativize_to) |
45 | tok.get_eol() | |
46 | 47 | return cls(rdclass, rdtype, mbox, txt) |
47 | 48 | |
48 | 49 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
20 | 20 | import time |
21 | 21 | |
22 | 22 | import dns.dnssec |
23 | import dns.immutable | |
23 | 24 | import dns.exception |
24 | 25 | import dns.rdata |
25 | 26 | import dns.rdatatype |
49 | 50 | return time.strftime('%Y%m%d%H%M%S', time.gmtime(what)) |
50 | 51 | |
51 | 52 | |
53 | @dns.immutable.immutable | |
52 | 54 | class RRSIG(dns.rdata.Rdata): |
53 | 55 | |
54 | 56 | """RRSIG record""" |
61 | 63 | original_ttl, expiration, inception, key_tag, signer, |
62 | 64 | signature): |
63 | 65 | 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) | |
73 | 75 | |
74 | 76 | def covers(self): |
75 | 77 | return self.type_covered |
84 | 86 | posixtime_to_sigtime(self.inception), |
85 | 87 | self.key_tag, |
86 | 88 | self.signer.choose_relativity(origin, relativize), |
87 | dns.rdata._base64ify(self.signature) | |
89 | dns.rdata._base64ify(self.signature, **kw) | |
88 | 90 | ) |
89 | 91 | |
90 | 92 | @classmethod |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.mxbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class RT(dns.rdtypes.mxbase.UncompressedDowncasingMX): |
21 | 23 | |
22 | 24 | """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""" |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | import dns.name |
22 | 23 | |
23 | 24 | |
25 | @dns.immutable.immutable | |
24 | 26 | class SOA(dns.rdata.Rdata): |
25 | 27 | |
26 | 28 | """SOA record""" |
33 | 35 | def __init__(self, rdclass, rdtype, mname, rname, serial, refresh, retry, |
34 | 36 | expire, minimum): |
35 | 37 | 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) | |
43 | 45 | |
44 | 46 | def to_text(self, origin=None, relativize=True, **kw): |
45 | 47 | mname = self.mname.choose_relativity(origin, relativize) |
58 | 60 | retry = tok.get_ttl() |
59 | 61 | expire = tok.get_ttl() |
60 | 62 | minimum = tok.get_ttl() |
61 | tok.get_eol() | |
62 | 63 | return cls(rdclass, rdtype, mname, rname, serial, refresh, retry, |
63 | 64 | expire, minimum) |
64 | 65 |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.txtbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class SPF(dns.rdtypes.txtbase.TXTBase): |
21 | 23 | |
22 | 24 | """SPF record""" |
18 | 18 | import binascii |
19 | 19 | |
20 | 20 | import dns.rdata |
21 | import dns.immutable | |
21 | 22 | import dns.rdatatype |
22 | 23 | |
23 | 24 | |
25 | @dns.immutable.immutable | |
24 | 26 | class SSHFP(dns.rdata.Rdata): |
25 | 27 | |
26 | 28 | """SSHFP record""" |
32 | 34 | def __init__(self, rdclass, rdtype, algorithm, fp_type, |
33 | 35 | fingerprint): |
34 | 36 | 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) | |
38 | 40 | |
39 | 41 | def to_text(self, origin=None, relativize=True, **kw): |
42 | kw = kw.copy() | |
43 | chunksize = kw.pop('chunksize', 128) | |
40 | 44 | return '%d %d %s' % (self.algorithm, |
41 | 45 | self.fp_type, |
42 | 46 | dns.rdata._hexify(self.fingerprint, |
43 | chunksize=128)) | |
47 | chunksize=chunksize, | |
48 | **kw)) | |
44 | 49 | |
45 | 50 | @classmethod |
46 | 51 | 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 |
0 | 0 | # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license |
1 | 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.rdatatype | |
2 | import dns.immutable | |
3 | import dns.rdtypes.tlsabase | |
22 | 4 | |
23 | 5 | |
24 | class TLSA(dns.rdata.Rdata): | |
6 | @dns.immutable.immutable | |
7 | class TLSA(dns.rdtypes.tlsabase.TLSABase): | |
25 | 8 | |
26 | 9 | """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) |
14 | 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | import base64 | |
17 | 18 | import struct |
18 | 19 | |
19 | 20 | import dns.exception |
21 | import dns.immutable | |
22 | import dns.rcode | |
20 | 23 | import dns.rdata |
21 | 24 | |
22 | 25 | |
26 | @dns.immutable.immutable | |
23 | 27 | class TSIG(dns.rdata.Rdata): |
24 | 28 | |
25 | 29 | """TSIG record""" |
51 | 55 | """ |
52 | 56 | |
53 | 57 | 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) | |
61 | 65 | |
62 | 66 | def to_text(self, origin=None, relativize=True, **kw): |
63 | 67 | 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} " + \ | |
65 | 70 | 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) | |
68 | 97 | |
69 | 98 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
70 | 99 | self.algorithm.to_wire(file, None, origin, False) |
80 | 109 | |
81 | 110 | @classmethod |
82 | 111 | 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() | |
86 | 115 | mac = parser.get_counted_bytes(2) |
87 | 116 | (original_id, error) = parser.get_struct('!HH') |
88 | 117 | other = parser.get_counted_bytes(2) |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.txtbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class TXT(dns.rdtypes.txtbase.TXTBase): |
21 | 23 | |
22 | 24 | """TXT record""" |
18 | 18 | import struct |
19 | 19 | |
20 | 20 | import dns.exception |
21 | import dns.immutable | |
21 | 22 | import dns.rdata |
23 | import dns.rdtypes.util | |
22 | 24 | import dns.name |
23 | 25 | |
24 | 26 | |
27 | @dns.immutable.immutable | |
25 | 28 | class URI(dns.rdata.Rdata): |
26 | 29 | |
27 | 30 | """URI record""" |
32 | 35 | |
33 | 36 | def __init__(self, rdclass, rdtype, priority, weight, target): |
34 | 37 | 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: | |
38 | 42 | 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) | |
43 | 43 | |
44 | 44 | def to_text(self, origin=None, relativize=True, **kw): |
45 | 45 | return '%d %d "%s"' % (self.priority, self.weight, |
53 | 53 | target = tok.get().unescape() |
54 | 54 | if not (target.is_quoted_string() or target.is_identifier()): |
55 | 55 | raise dns.exception.SyntaxError("URI target must be a string") |
56 | tok.get_eol() | |
57 | 56 | return cls(rdclass, rdtype, priority, weight, target.value) |
58 | 57 | |
59 | 58 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
68 | 67 | if len(target) == 0: |
69 | 68 | raise dns.exception.FormError('URI target may not be empty') |
70 | 69 | 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) |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | import dns.tokenizer |
22 | 23 | |
23 | 24 | |
25 | @dns.immutable.immutable | |
24 | 26 | class X25(dns.rdata.Rdata): |
25 | 27 | |
26 | 28 | """X25 record""" |
31 | 33 | |
32 | 34 | def __init__(self, rdclass, rdtype, address): |
33 | 35 | 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) | |
38 | 37 | |
39 | 38 | def to_text(self, origin=None, relativize=True, **kw): |
40 | 39 | return '"%s"' % dns.rdata._escapify(self.address) |
43 | 42 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
44 | 43 | relativize_to=None): |
45 | 44 | address = tok.get_string() |
46 | tok.get_eol() | |
47 | 45 | return cls(rdclass, rdtype, address) |
48 | 46 | |
49 | 47 | 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) |
18 | 18 | |
19 | 19 | __all__ = [ |
20 | 20 | 'AFSDB', |
21 | 'AMTRELAY', | |
21 | 22 | 'AVC', |
22 | 23 | 'CAA', |
23 | 24 | 'CDNSKEY', |
37 | 38 | 'ISDN', |
38 | 39 | 'LOC', |
39 | 40 | 'MX', |
41 | 'NINFO', | |
40 | 42 | 'NS', |
41 | 43 | 'NSEC', |
42 | 44 | 'NSEC3', |
47 | 49 | 'RP', |
48 | 50 | 'RRSIG', |
49 | 51 | 'RT', |
52 | 'SMIMEA', | |
50 | 53 | 'SOA', |
51 | 54 | 'SPF', |
52 | 55 | 'SSHFP', |
56 | 'TKEY', | |
53 | 57 | 'TLSA', |
54 | 58 | 'TSIG', |
55 | 59 | 'TXT', |
56 | 60 | 'URI', |
57 | 61 | 'X25', |
62 | 'ZONEMD', | |
58 | 63 | ] |
14 | 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | import dns.rdtypes.mxbase | |
18 | 17 | import struct |
19 | 18 | |
19 | import dns.rdtypes.mxbase | |
20 | import dns.immutable | |
21 | ||
22 | @dns.immutable.immutable | |
20 | 23 | class A(dns.rdata.Rdata): |
21 | 24 | |
22 | 25 | """A record for Chaosnet""" |
28 | 31 | |
29 | 32 | def __init__(self, rdclass, rdtype, domain, address): |
30 | 33 | 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) | |
33 | 36 | |
34 | 37 | def to_text(self, origin=None, relativize=True, **kw): |
35 | 38 | domain = self.domain.choose_relativity(origin, relativize) |
40 | 43 | relativize_to=None): |
41 | 44 | domain = tok.get_name(origin, relativize, relativize_to) |
42 | 45 | address = tok.get_uint16(base=8) |
43 | tok.get_eol() | |
44 | 46 | return cls(rdclass, rdtype, domain, address) |
45 | 47 | |
46 | 48 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.exception |
18 | import dns.immutable | |
18 | 19 | import dns.ipv4 |
19 | 20 | import dns.rdata |
20 | 21 | import dns.tokenizer |
21 | 22 | |
22 | 23 | |
24 | @dns.immutable.immutable | |
23 | 25 | class A(dns.rdata.Rdata): |
24 | 26 | |
25 | 27 | """A record.""" |
28 | 30 | |
29 | 31 | def __init__(self, rdclass, rdtype, address): |
30 | 32 | 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) | |
34 | 34 | |
35 | 35 | def to_text(self, origin=None, relativize=True, **kw): |
36 | 36 | return self.address |
39 | 39 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
40 | 40 | relativize_to=None): |
41 | 41 | address = tok.get_identifier() |
42 | tok.get_eol() | |
43 | 42 | return cls(rdclass, rdtype, address) |
44 | 43 | |
45 | 44 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
47 | 46 | |
48 | 47 | @classmethod |
49 | 48 | def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): |
50 | address = dns.ipv4.inet_ntoa(parser.get_remaining()) | |
49 | address = parser.get_remaining() | |
51 | 50 | return cls(rdclass, rdtype, address) |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.exception |
18 | import dns.immutable | |
18 | 19 | import dns.ipv6 |
19 | 20 | import dns.rdata |
20 | 21 | import dns.tokenizer |
21 | 22 | |
22 | 23 | |
24 | @dns.immutable.immutable | |
23 | 25 | class AAAA(dns.rdata.Rdata): |
24 | 26 | |
25 | 27 | """AAAA record.""" |
28 | 30 | |
29 | 31 | def __init__(self, rdclass, rdtype, address): |
30 | 32 | 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) | |
34 | 34 | |
35 | 35 | def to_text(self, origin=None, relativize=True, **kw): |
36 | 36 | return self.address |
39 | 39 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
40 | 40 | relativize_to=None): |
41 | 41 | address = tok.get_identifier() |
42 | tok.get_eol() | |
43 | 42 | return cls(rdclass, rdtype, address) |
44 | 43 | |
45 | 44 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
47 | 46 | |
48 | 47 | @classmethod |
49 | 48 | def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): |
50 | address = dns.ipv6.inet_ntoa(parser.get_remaining()) | |
49 | address = parser.get_remaining() | |
51 | 50 | return cls(rdclass, rdtype, address) |
19 | 19 | import struct |
20 | 20 | |
21 | 21 | import dns.exception |
22 | import dns.immutable | |
22 | 23 | import dns.ipv4 |
23 | 24 | import dns.ipv6 |
24 | 25 | import dns.rdata |
25 | 26 | import dns.tokenizer |
26 | 27 | |
28 | @dns.immutable.immutable | |
27 | 29 | class APLItem: |
28 | 30 | |
29 | 31 | """An APL list item.""" |
31 | 33 | __slots__ = ['family', 'negation', 'address', 'prefix'] |
32 | 34 | |
33 | 35 | 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) | |
38 | 47 | |
39 | 48 | def __str__(self): |
40 | 49 | if self.negation: |
67 | 76 | file.write(address) |
68 | 77 | |
69 | 78 | |
79 | @dns.immutable.immutable | |
70 | 80 | class APL(dns.rdata.Rdata): |
71 | 81 | |
72 | 82 | """APL record.""" |
77 | 87 | |
78 | 88 | def __init__(self, rdclass, rdtype, items): |
79 | 89 | 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) | |
81 | 94 | |
82 | 95 | def to_text(self, origin=None, relativize=True, **kw): |
83 | 96 | return ' '.join(map(str, self.items)) |
86 | 99 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
87 | 100 | relativize_to=None): |
88 | 101 | 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 | |
94 | 104 | if item[0] == '!': |
95 | 105 | negation = True |
96 | 106 | item = item[1:] |
126 | 136 | if header[0] == 1: |
127 | 137 | if l < 4: |
128 | 138 | address += b'\x00' * (4 - l) |
129 | address = dns.ipv4.inet_ntoa(address) | |
130 | 139 | elif header[0] == 2: |
131 | 140 | if l < 16: |
132 | 141 | address += b'\x00' * (16 - l) |
133 | address = dns.ipv6.inet_ntoa(address) | |
134 | 142 | else: |
135 | 143 | # |
136 | 144 | # This isn't really right according to the RFC, but it |
17 | 17 | import base64 |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | |
21 | 22 | |
23 | @dns.immutable.immutable | |
22 | 24 | class DHCID(dns.rdata.Rdata): |
23 | 25 | |
24 | 26 | """DHCID record""" |
29 | 31 | |
30 | 32 | def __init__(self, rdclass, rdtype, data): |
31 | 33 | super().__init__(rdclass, rdtype) |
32 | object.__setattr__(self, 'data', data) | |
34 | self.data = self._as_bytes(data) | |
33 | 35 | |
34 | 36 | def to_text(self, origin=None, relativize=True, **kw): |
35 | return dns.rdata._base64ify(self.data) | |
37 | return dns.rdata._base64ify(self.data, **kw) | |
36 | 38 | |
37 | 39 | @classmethod |
38 | 40 | 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""" |
18 | 18 | import base64 |
19 | 19 | |
20 | 20 | import dns.exception |
21 | import dns.immutable | |
21 | 22 | import dns.rdtypes.util |
22 | 23 | |
23 | 24 | |
24 | 25 | class Gateway(dns.rdtypes.util.Gateway): |
25 | 26 | name = 'IPSECKEY gateway' |
26 | 27 | |
28 | @dns.immutable.immutable | |
27 | 29 | class IPSECKEY(dns.rdata.Rdata): |
28 | 30 | |
29 | 31 | """IPSECKEY record""" |
35 | 37 | def __init__(self, rdclass, rdtype, precedence, gateway_type, algorithm, |
36 | 38 | gateway, key): |
37 | 39 | 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) | |
44 | 46 | |
45 | 47 | def to_text(self, origin=None, relativize=True, **kw): |
46 | 48 | gateway = Gateway(self.gateway_type, self.gateway).to_text(origin, |
47 | 49 | relativize) |
48 | 50 | return '%d %d %d %s %s' % (self.precedence, self.gateway_type, |
49 | 51 | self.algorithm, gateway, |
50 | dns.rdata._base64ify(self.key)) | |
52 | dns.rdata._base64ify(self.key, **kw)) | |
51 | 53 | |
52 | 54 | @classmethod |
53 | 55 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
55 | 57 | precedence = tok.get_uint8() |
56 | 58 | gateway_type = tok.get_uint8() |
57 | 59 | 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) | |
60 | 62 | b64 = tok.concatenate_remaining_identifiers().encode() |
61 | 63 | key = base64.b64decode(b64) |
62 | 64 | return cls(rdclass, rdtype, precedence, gateway_type, algorithm, |
63 | gateway, key) | |
65 | gateway.gateway, key) | |
64 | 66 | |
65 | 67 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
66 | 68 | header = struct.pack("!BBB", self.precedence, self.gateway_type, |
74 | 76 | def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): |
75 | 77 | header = parser.get_struct('!BBB') |
76 | 78 | gateway_type = header[1] |
77 | gateway = Gateway(gateway_type).from_wire_parser(parser, origin) | |
79 | gateway = Gateway.from_wire_parser(gateway_type, parser, origin) | |
78 | 80 | key = parser.get_remaining() |
79 | 81 | return cls(rdclass, rdtype, header[0], gateway_type, header[2], |
80 | gateway, key) | |
82 | gateway.gateway, key) |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.mxbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class KX(dns.rdtypes.mxbase.UncompressedDowncasingMX): |
21 | 23 | |
22 | 24 | """KX record""" |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.name |
21 | 22 | import dns.rdata |
23 | import dns.rdtypes.util | |
22 | 24 | |
23 | 25 | |
24 | 26 | def _write_string(file, s): |
28 | 30 | file.write(s) |
29 | 31 | |
30 | 32 | |
31 | def _sanitize(value): | |
32 | if isinstance(value, str): | |
33 | return value.encode() | |
34 | return value | |
35 | ||
36 | ||
33 | @dns.immutable.immutable | |
37 | 34 | class NAPTR(dns.rdata.Rdata): |
38 | 35 | |
39 | 36 | """NAPTR record""" |
46 | 43 | def __init__(self, rdclass, rdtype, order, preference, flags, service, |
47 | 44 | regexp, replacement): |
48 | 45 | 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) | |
55 | 52 | |
56 | 53 | def to_text(self, origin=None, relativize=True, **kw): |
57 | 54 | replacement = self.replacement.choose_relativity(origin, relativize) |
71 | 68 | service = tok.get_string() |
72 | 69 | regexp = tok.get_string() |
73 | 70 | replacement = tok.get_name(origin, relativize, relativize_to) |
74 | tok.get_eol() | |
75 | 71 | return cls(rdclass, rdtype, order, preference, flags, service, |
76 | 72 | regexp, replacement) |
77 | 73 | |
87 | 83 | def from_wire_parser(cls, rdclass, rdtype, parser, origin=None): |
88 | 84 | (order, preference) = parser.get_struct('!HH') |
89 | 85 | strings = [] |
90 | for i in range(3): | |
86 | for _ in range(3): | |
91 | 87 | s = parser.get_counted_bytes() |
92 | 88 | strings.append(s) |
93 | 89 | replacement = parser.get_name(origin) |
94 | 90 | return cls(rdclass, rdtype, order, preference, strings[0], strings[1], |
95 | 91 | 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) |
17 | 17 | import binascii |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | import dns.tokenizer |
22 | 23 | |
23 | 24 | |
25 | @dns.immutable.immutable | |
24 | 26 | class NSAP(dns.rdata.Rdata): |
25 | 27 | |
26 | 28 | """NSAP record.""" |
31 | 33 | |
32 | 34 | def __init__(self, rdclass, rdtype, address): |
33 | 35 | super().__init__(rdclass, rdtype) |
34 | object.__setattr__(self, 'address', address) | |
36 | self.address = self._as_bytes(address) | |
35 | 37 | |
36 | 38 | def to_text(self, origin=None, relativize=True, **kw): |
37 | 39 | return "0x%s" % binascii.hexlify(self.address).decode() |
40 | 42 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
41 | 43 | relativize_to=None): |
42 | 44 | address = tok.get_string() |
43 | tok.get_eol() | |
44 | 45 | if address[0:2] != '0x': |
45 | 46 | raise dns.exception.SyntaxError('string does not start with 0x') |
46 | 47 | address = address[2:].replace('.', '') |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | import dns.rdtypes.nsbase |
18 | import dns.immutable | |
18 | 19 | |
19 | 20 | |
21 | @dns.immutable.immutable | |
20 | 22 | class NSAP_PTR(dns.rdtypes.nsbase.UncompressedNS): |
21 | 23 | |
22 | 24 | """NSAP-PTR record""" |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
22 | import dns.rdtypes.util | |
21 | 23 | import dns.name |
22 | 24 | |
23 | 25 | |
26 | @dns.immutable.immutable | |
24 | 27 | class PX(dns.rdata.Rdata): |
25 | 28 | |
26 | 29 | """PX record.""" |
31 | 34 | |
32 | 35 | def __init__(self, rdclass, rdtype, preference, map822, mapx400): |
33 | 36 | 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) | |
37 | 40 | |
38 | 41 | def to_text(self, origin=None, relativize=True, **kw): |
39 | 42 | map822 = self.map822.choose_relativity(origin, relativize) |
46 | 49 | preference = tok.get_uint16() |
47 | 50 | map822 = tok.get_name(origin, relativize, relativize_to) |
48 | 51 | mapx400 = tok.get_name(origin, relativize, relativize_to) |
49 | tok.get_eol() | |
50 | 52 | return cls(rdclass, rdtype, preference, map822, mapx400) |
51 | 53 | |
52 | 54 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
61 | 63 | map822 = parser.get_name(origin) |
62 | 64 | mapx400 = parser.get_name(origin) |
63 | 65 | 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) |
17 | 17 | import struct |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
22 | import dns.rdtypes.util | |
21 | 23 | import dns.name |
22 | 24 | |
23 | 25 | |
26 | @dns.immutable.immutable | |
24 | 27 | class SRV(dns.rdata.Rdata): |
25 | 28 | |
26 | 29 | """SRV record""" |
31 | 34 | |
32 | 35 | def __init__(self, rdclass, rdtype, priority, weight, port, target): |
33 | 36 | 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) | |
38 | 41 | |
39 | 42 | def to_text(self, origin=None, relativize=True, **kw): |
40 | 43 | target = self.target.choose_relativity(origin, relativize) |
48 | 51 | weight = tok.get_uint16() |
49 | 52 | port = tok.get_uint16() |
50 | 53 | target = tok.get_name(origin, relativize, relativize_to) |
51 | tok.get_eol() | |
52 | 54 | return cls(rdclass, rdtype, priority, weight, port, target) |
53 | 55 | |
54 | 56 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
61 | 63 | (priority, weight, port) = parser.get_struct('!HHH') |
62 | 64 | target = parser.get_name(origin) |
63 | 65 | 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""" |
18 | 18 | import struct |
19 | 19 | |
20 | 20 | import dns.ipv4 |
21 | import dns.immutable | |
21 | 22 | import dns.rdata |
22 | 23 | |
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 | |
25 | 31 | |
26 | ||
32 | @dns.immutable.immutable | |
27 | 33 | class WKS(dns.rdata.Rdata): |
28 | 34 | |
29 | 35 | """WKS record""" |
34 | 40 | |
35 | 41 | def __init__(self, rdclass, rdtype, address, protocol, bitmap): |
36 | 42 | 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) | |
40 | 46 | |
41 | 47 | def to_text(self, origin=None, relativize=True, **kw): |
42 | 48 | bits = [] |
58 | 64 | else: |
59 | 65 | protocol = socket.getprotobyname(protocol) |
60 | 66 | 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) | |
67 | 71 | else: |
68 | 72 | if protocol != _proto_udp and protocol != _proto_tcp: |
69 | 73 | raise NotImplementedError("protocol must be TCP or UDP") |
71 | 75 | protocol_text = "udp" |
72 | 76 | else: |
73 | 77 | protocol_text = "tcp" |
74 | serv = socket.getservbyname(token.value, protocol_text) | |
78 | serv = socket.getservbyname(value, protocol_text) | |
75 | 79 | i = serv // 8 |
76 | 80 | l = len(bitmap) |
77 | 81 | if l < i + 1: |
78 | for j in range(l, i + 1): | |
82 | for _ in range(l, i + 1): | |
79 | 83 | bitmap.append(0) |
80 | 84 | bitmap[i] = bitmap[i] | (0x80 >> (serv % 8)) |
81 | 85 | bitmap = dns.rdata._truncate_bitmap(bitmap) |
89 | 93 | |
90 | 94 | @classmethod |
91 | 95 | 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) | |
93 | 97 | protocol = parser.get_uint8() |
94 | 98 | bitmap = parser.get_remaining() |
95 | 99 | return cls(rdclass, rdtype, address, protocol, bitmap) |
21 | 21 | 'AAAA', |
22 | 22 | 'APL', |
23 | 23 | 'DHCID', |
24 | 'HTTPS', | |
24 | 25 | 'IPSECKEY', |
25 | 26 | 'KX', |
26 | 27 | 'NAPTR', |
28 | 29 | 'NSAP_PTR', |
29 | 30 | 'PX', |
30 | 31 | 'SRV', |
32 | 'SVCB', | |
31 | 33 | 'WKS', |
32 | 34 | ] |
20 | 20 | 'ANY', |
21 | 21 | 'IN', |
22 | 22 | 'CH', |
23 | 'dnskeybase', | |
24 | 'dsbase', | |
23 | 25 | 'euibase', |
24 | 26 | 'mxbase', |
25 | 27 | 'nsbase', |
28 | 'svcbbase', | |
29 | 'tlsabase', | |
30 | 'txtbase', | |
26 | 31 | 'util' |
27 | 32 | ] |
19 | 19 | import struct |
20 | 20 | |
21 | 21 | import dns.exception |
22 | import dns.immutable | |
22 | 23 | import dns.dnssec |
23 | 24 | import dns.rdata |
24 | 25 | |
30 | 31 | REVOKE = 0x0080 |
31 | 32 | ZONE = 0x0100 |
32 | 33 | |
33 | globals().update(Flag.__members__) | |
34 | 34 | |
35 | ||
35 | @dns.immutable.immutable | |
36 | 36 | class DNSKEYBase(dns.rdata.Rdata): |
37 | 37 | |
38 | 38 | """Base class for rdata that is like a DNSKEY record""" |
41 | 41 | |
42 | 42 | def __init__(self, rdclass, rdtype, flags, protocol, algorithm, key): |
43 | 43 | 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) | |
48 | 48 | |
49 | 49 | def to_text(self, origin=None, relativize=True, **kw): |
50 | 50 | return '%d %d %d %s' % (self.flags, self.protocol, self.algorithm, |
51 | dns.rdata._base64ify(self.key)) | |
51 | dns.rdata._base64ify(self.key, **kw)) | |
52 | 52 | |
53 | 53 | @classmethod |
54 | 54 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
55 | 55 | relativize_to=None): |
56 | 56 | flags = tok.get_uint16() |
57 | 57 | protocol = tok.get_uint8() |
58 | algorithm = dns.dnssec.algorithm_from_text(tok.get_string()) | |
58 | algorithm = tok.get_string() | |
59 | 59 | b64 = tok.concatenate_remaining_identifiers().encode() |
60 | 60 | key = base64.b64decode(b64) |
61 | 61 | return cls(rdclass, rdtype, flags, protocol, algorithm, key) |
71 | 71 | key = parser.get_remaining() |
72 | 72 | return cls(rdclass, rdtype, header[0], header[1], header[2], |
73 | 73 | 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 |
18 | 18 | import binascii |
19 | 19 | |
20 | 20 | import dns.dnssec |
21 | import dns.immutable | |
21 | 22 | import dns.rdata |
22 | 23 | import dns.rdatatype |
23 | 24 | |
24 | 25 | |
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 | |
25 | 36 | class DSBase(dns.rdata.Rdata): |
26 | 37 | |
27 | 38 | """Base class for rdata that is like a DS record""" |
31 | 42 | def __init__(self, rdclass, rdtype, key_tag, algorithm, digest_type, |
32 | 43 | digest): |
33 | 44 | 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') | |
38 | 58 | |
39 | 59 | def to_text(self, origin=None, relativize=True, **kw): |
60 | kw = kw.copy() | |
61 | chunksize = kw.pop('chunksize', 128) | |
40 | 62 | return '%d %d %d %s' % (self.key_tag, self.algorithm, |
41 | 63 | self.digest_type, |
42 | 64 | dns.rdata._hexify(self.digest, |
43 | chunksize=128)) | |
65 | chunksize=chunksize, | |
66 | **kw)) | |
44 | 67 | |
45 | 68 | @classmethod |
46 | 69 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
47 | 70 | relativize_to=None): |
48 | 71 | key_tag = tok.get_uint16() |
49 | algorithm = dns.dnssec.algorithm_from_text(tok.get_string()) | |
72 | algorithm = tok.get_string() | |
50 | 73 | digest_type = tok.get_uint8() |
51 | 74 | digest = tok.concatenate_remaining_identifiers().encode() |
52 | 75 | digest = binascii.unhexlify(digest) |
16 | 16 | import binascii |
17 | 17 | |
18 | 18 | import dns.rdata |
19 | import dns.immutable | |
19 | 20 | |
20 | 21 | |
22 | @dns.immutable.immutable | |
21 | 23 | class EUIBase(dns.rdata.Rdata): |
22 | 24 | |
23 | 25 | """EUIxx record""" |
31 | 33 | |
32 | 34 | def __init__(self, rdclass, rdtype, eui): |
33 | 35 | 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: | |
35 | 38 | raise dns.exception.FormError('EUI%s rdata has to have %s bytes' |
36 | 39 | % (self.byte_len * 8, self.byte_len)) |
37 | object.__setattr__(self, 'eui', eui) | |
38 | 40 | |
39 | 41 | 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(' ', '-') | |
41 | 43 | |
42 | 44 | @classmethod |
43 | 45 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
44 | 46 | relativize_to=None): |
45 | 47 | text = tok.get_string() |
46 | tok.get_eol() | |
47 | 48 | if len(text) != cls.text_len: |
48 | 49 | raise dns.exception.SyntaxError( |
49 | 50 | 'Input text must have %s characters' % cls.text_len) |
19 | 19 | import struct |
20 | 20 | |
21 | 21 | import dns.exception |
22 | import dns.immutable | |
22 | 23 | import dns.rdata |
23 | 24 | import dns.name |
25 | import dns.rdtypes.util | |
24 | 26 | |
25 | 27 | |
28 | @dns.immutable.immutable | |
26 | 29 | class MXBase(dns.rdata.Rdata): |
27 | 30 | |
28 | 31 | """Base class for rdata that is like an MX record.""" |
31 | 34 | |
32 | 35 | def __init__(self, rdclass, rdtype, preference, exchange): |
33 | 36 | 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) | |
36 | 39 | |
37 | 40 | def to_text(self, origin=None, relativize=True, **kw): |
38 | 41 | exchange = self.exchange.choose_relativity(origin, relativize) |
43 | 46 | relativize_to=None): |
44 | 47 | preference = tok.get_uint16() |
45 | 48 | exchange = tok.get_name(origin, relativize, relativize_to) |
46 | tok.get_eol() | |
47 | 49 | return cls(rdclass, rdtype, preference, exchange) |
48 | 50 | |
49 | 51 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
57 | 59 | exchange = parser.get_name(origin) |
58 | 60 | return cls(rdclass, rdtype, preference, exchange) |
59 | 61 | |
62 | def _processing_priority(self): | |
63 | return self.preference | |
60 | 64 | |
65 | @classmethod | |
66 | def _processing_order(cls, iterable): | |
67 | return dns.rdtypes.util.priority_processing_order(iterable) | |
68 | ||
69 | ||
70 | @dns.immutable.immutable | |
61 | 71 | class UncompressedMX(MXBase): |
62 | 72 | |
63 | 73 | """Base class for rdata that is like an MX record, but whose name |
68 | 78 | super()._to_wire(file, None, origin, False) |
69 | 79 | |
70 | 80 | |
81 | @dns.immutable.immutable | |
71 | 82 | class UncompressedDowncasingMX(MXBase): |
72 | 83 | |
73 | 84 | """Base class for rdata that is like an MX record, but whose name |
17 | 17 | """NS-like base classes.""" |
18 | 18 | |
19 | 19 | import dns.exception |
20 | import dns.immutable | |
20 | 21 | import dns.rdata |
21 | 22 | import dns.name |
22 | 23 | |
23 | 24 | |
25 | @dns.immutable.immutable | |
24 | 26 | class NSBase(dns.rdata.Rdata): |
25 | 27 | |
26 | 28 | """Base class for rdata that is like an NS record.""" |
29 | 31 | |
30 | 32 | def __init__(self, rdclass, rdtype, target): |
31 | 33 | super().__init__(rdclass, rdtype) |
32 | object.__setattr__(self, 'target', target) | |
34 | self.target = self._as_name(target) | |
33 | 35 | |
34 | 36 | def to_text(self, origin=None, relativize=True, **kw): |
35 | 37 | target = self.target.choose_relativity(origin, relativize) |
39 | 41 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
40 | 42 | relativize_to=None): |
41 | 43 | target = tok.get_name(origin, relativize, relativize_to) |
42 | tok.get_eol() | |
43 | 44 | return cls(rdclass, rdtype, target) |
44 | 45 | |
45 | 46 | def _to_wire(self, file, compress=None, origin=None, canonicalize=False): |
51 | 52 | return cls(rdclass, rdtype, target) |
52 | 53 | |
53 | 54 | |
55 | @dns.immutable.immutable | |
54 | 56 | class UncompressedNS(NSBase): |
55 | 57 | |
56 | 58 | """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) |
19 | 19 | import struct |
20 | 20 | |
21 | 21 | import dns.exception |
22 | import dns.immutable | |
22 | 23 | import dns.rdata |
23 | 24 | import dns.tokenizer |
24 | 25 | |
25 | 26 | |
27 | @dns.immutable.immutable | |
26 | 28 | class TXTBase(dns.rdata.Rdata): |
27 | 29 | |
28 | 30 | """Base class for rdata that is like a TXT record (see RFC 1035).""" |
39 | 41 | *strings*, a tuple of ``bytes`` |
40 | 42 | """ |
41 | 43 | 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)) | |
52 | 46 | |
53 | 47 | def to_text(self, origin=None, relativize=True, **kw): |
54 | 48 | txt = '' |
62 | 56 | def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True, |
63 | 57 | relativize_to=None): |
64 | 58 | 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 | |
70 | 65 | raise dns.exception.SyntaxError("expected a string") |
71 | 66 | if len(token.value) > 255: |
72 | 67 | raise dns.exception.SyntaxError("string too long") |
14 | 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | import collections | |
18 | import random | |
17 | 19 | import struct |
18 | 20 | |
19 | 21 | import dns.exception |
20 | import dns.name | |
21 | 22 | import dns.ipv4 |
22 | 23 | import dns.ipv6 |
24 | import dns.name | |
25 | import dns.rdata | |
26 | ||
23 | 27 | |
24 | 28 | class Gateway: |
25 | 29 | """A helper class for the IPSECKEY gateway and AMTRELAY relay fields""" |
26 | 30 | name = "" |
27 | 31 | |
28 | 32 | def __init__(self, type, gateway=None): |
29 | self.type = type | |
33 | self.type = dns.rdata.Rdata._as_uint8(type) | |
30 | 34 | 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): | |
36 | 42 | if self.type == 0: |
37 | 43 | if self.gateway not in (".", None): |
38 | 44 | raise SyntaxError(f"invalid {self.name} for type 0") |
47 | 53 | if not isinstance(self.gateway, dns.name.Name): |
48 | 54 | raise SyntaxError(f"invalid {self.name}; not a name") |
49 | 55 | else: |
50 | raise SyntaxError(self._invalid_type()) | |
56 | raise SyntaxError(self._invalid_type(self.type)) | |
51 | 57 | |
52 | 58 | def to_text(self, origin=None, relativize=True): |
53 | 59 | if self.type == 0: |
57 | 63 | elif self.type == 3: |
58 | 64 | return str(self.gateway.choose_relativity(origin, relativize)) |
59 | 65 | 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 | |
70 | 81 | def to_wire(self, file, compress=None, origin=None, canonicalize=False): |
71 | 82 | if self.type == 0: |
72 | 83 | pass |
77 | 88 | elif self.type == 3: |
78 | 89 | self.gateway.to_wire(file, None, origin, False) |
79 | 90 | 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 | ||
93 | 108 | |
94 | 109 | class Bitmap: |
95 | 110 | """A helper class for the NSEC/NSEC3/CSYNC type bitmaps""" |
96 | 111 | type_name = "" |
97 | 112 | |
98 | 113 | def __init__(self, windows=None): |
114 | last_window = -1 | |
99 | 115 | 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") | |
100 | 128 | |
101 | 129 | def to_text(self): |
102 | 130 | text = "" |
110 | 138 | text += (' ' + ' '.join(bits)) |
111 | 139 | return text |
112 | 140 | |
113 | def from_text(self, tok): | |
141 | @classmethod | |
142 | def from_text(cls, tok): | |
114 | 143 | 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) | |
120 | 146 | 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") | |
122 | 148 | rdtypes.append(rdtype) |
123 | 149 | rdtypes.sort() |
124 | 150 | window = 0 |
133 | 159 | new_window = rdtype // 256 |
134 | 160 | if new_window != window: |
135 | 161 | if octets != 0: |
136 | windows.append((window, bitmap[0:octets])) | |
162 | windows.append((window, bytes(bitmap[0:octets]))) | |
137 | 163 | bitmap = bytearray(b'\0' * 32) |
138 | 164 | window = new_window |
139 | 165 | offset = rdtype % 256 |
142 | 168 | octets = byte + 1 |
143 | 169 | bitmap[byte] = bitmap[byte] | (0x80 >> bit) |
144 | 170 | 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) | |
147 | 173 | |
148 | 174 | def to_wire(self, file): |
149 | 175 | for (window, bitmap) in self.windows: |
150 | 176 | file.write(struct.pack('!BB', window, len(bitmap))) |
151 | 177 | file.write(bitmap) |
152 | 178 | |
153 | def from_wire_parser(self, parser): | |
179 | @classmethod | |
180 | def from_wire_parser(cls, parser): | |
154 | 181 | windows = [] |
155 | last_window = -1 | |
156 | 182 | while parser.remaining() > 0: |
157 | 183 | window = parser.get_uint8() |
158 | if window <= last_window: | |
159 | raise dns.exception.FormError(f"bad {self.type_name} bitmap") | |
160 | 184 | 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") | |
163 | 185 | 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 |
42 | 42 | import dns.tsig |
43 | 43 | |
44 | 44 | if sys.platform == 'win32': |
45 | import winreg # pragma: no cover | |
45 | # pylint: disable=import-error | |
46 | import winreg | |
46 | 47 | |
47 | 48 | class NXDOMAIN(dns.exception.DNSException): |
48 | 49 | """The DNS query name does not exist.""" |
49 | 50 | supp_kwargs = {'qnames', 'responses'} |
50 | 51 | fmt = None # we have our own __str__ implementation |
51 | 52 | |
52 | def _check_kwargs(self, qnames, responses=None): | |
53 | # pylint: disable=arguments-differ | |
54 | ||
55 | def _check_kwargs(self, qnames, | |
56 | responses=None): | |
53 | 57 | if not isinstance(qnames, (list, tuple, set)): |
54 | 58 | raise AttributeError("qnames must be a list, tuple or set") |
55 | 59 | if len(qnames) == 0: |
77 | 81 | """Return the unresolved canonical name.""" |
78 | 82 | if 'qnames' not in self.kwargs: |
79 | 83 | raise TypeError("parametrized exception required") |
80 | IN = dns.rdataclass.IN | |
81 | CNAME = dns.rdatatype.CNAME | |
82 | cname = None | |
83 | 84 | for qname in self.kwargs['qnames']: |
84 | 85 | 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 | |
91 | 94 | return self.kwargs['qnames'][0] |
92 | 95 | |
93 | 96 | def __add__(self, e_nx): |
128 | 131 | class YXDOMAIN(dns.exception.DNSException): |
129 | 132 | """The DNS query name is too long after DNAME substitution.""" |
130 | 133 | |
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 | |
136 | 161 | |
137 | 162 | |
138 | 163 | class NoAnswer(dns.exception.DNSException): |
159 | 184 | supp_kwargs = {'request', 'errors'} |
160 | 185 | |
161 | 186 | 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']) | |
166 | 188 | return super()._fmt_kwargs(query=kwargs['request'].question, |
167 | 189 | errors='; '.join(srv_msgs)) |
168 | 190 | |
205 | 227 | self.response = response |
206 | 228 | self.nameserver = nameserver |
207 | 229 | 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 | |
253 | 236 | |
254 | 237 | def __getattr__(self, attr): # pragma: no cover |
255 | 238 | if attr == 'name': |
282 | 265 | del self.rrset[i] |
283 | 266 | |
284 | 267 | |
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): | |
286 | 316 | """Simple thread-safe DNS answer cache.""" |
287 | 317 | |
288 | 318 | def __init__(self, cleaning_interval=300.0): |
290 | 320 | periodic cleanings. |
291 | 321 | """ |
292 | 322 | |
323 | super().__init__() | |
293 | 324 | self.data = {} |
294 | 325 | self.cleaning_interval = cleaning_interval |
295 | 326 | self.next_cleaning = time.time() + self.cleaning_interval |
296 | self.lock = _threading.Lock() | |
297 | 327 | |
298 | 328 | def _maybe_clean(self): |
299 | 329 | """Clean the cache if it's time to do so.""" |
324 | 354 | self._maybe_clean() |
325 | 355 | v = self.data.get(key) |
326 | 356 | if v is None or v.expiration <= time.time(): |
357 | self.statistics.misses += 1 | |
327 | 358 | return None |
359 | self.statistics.hits += 1 | |
328 | 360 | return v |
329 | 361 | |
330 | 362 | def put(self, key, value): |
365 | 397 | def __init__(self, key, value): |
366 | 398 | self.key = key |
367 | 399 | self.value = value |
400 | self.hits = 0 | |
368 | 401 | self.prev = self |
369 | 402 | self.next = self |
370 | 403 | |
379 | 412 | self.prev.next = self.next |
380 | 413 | |
381 | 414 | |
382 | class LRUCache: | |
415 | class LRUCache(CacheBase): | |
383 | 416 | """Thread-safe, bounded, least-recently-used DNS answer cache. |
384 | 417 | |
385 | 418 | This cache is better than the simple cache (above) if you're |
394 | 427 | it must be greater than 0. |
395 | 428 | """ |
396 | 429 | |
430 | super().__init__() | |
397 | 431 | self.data = {} |
398 | 432 | self.set_max_size(max_size) |
399 | 433 | self.sentinel = LRUCacheNode(None, None) |
400 | 434 | self.sentinel.prev = self.sentinel |
401 | 435 | self.sentinel.next = self.sentinel |
402 | self.lock = _threading.Lock() | |
403 | 436 | |
404 | 437 | def set_max_size(self, max_size): |
405 | 438 | if max_size < 1: |
420 | 453 | with self.lock: |
421 | 454 | node = self.data.get(key) |
422 | 455 | if node is None: |
456 | self.statistics.misses += 1 | |
423 | 457 | return None |
424 | 458 | # Unlink because we're either going to move the node to the front |
425 | 459 | # of the LRU list or we're going to free it. |
426 | 460 | node.unlink() |
427 | 461 | if node.value.expiration <= time.time(): |
428 | 462 | del self.data[node.key] |
463 | self.statistics.misses += 1 | |
429 | 464 | return None |
430 | 465 | node.link_after(self.sentinel) |
466 | self.statistics.hits += 1 | |
467 | node.hits += 1 | |
431 | 468 | 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 | |
432 | 478 | |
433 | 479 | def put(self, key, value): |
434 | 480 | """Associate key and value in the cache. |
631 | 677 | assert response is not None |
632 | 678 | rcode = response.rcode() |
633 | 679 | 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) | |
636 | 689 | if self.resolver.cache: |
637 | 690 | self.resolver.cache.put((self.qname, self.rdtype, |
638 | 691 | self.rdclass), answer) |
640 | 693 | raise NoAnswer(response=answer.response) |
641 | 694 | return (answer, True) |
642 | 695 | 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: | |
647 | 699 | answer = Answer(self.qname, dns.rdatatype.ANY, |
648 | 700 | 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: | |
649 | 709 | self.resolver.cache.put((self.qname, |
650 | 710 | dns.rdatatype.ANY, |
651 | 711 | self.rdclass), answer) |
652 | ||
712 | # Make next_nameserver() return None, so caller breaks its | |
713 | # inner loop and calls next_request(). | |
653 | 714 | return (None, True) |
654 | 715 | elif rcode == dns.rcode.YXDOMAIN: |
655 | 716 | yex = YXDOMAIN() |
667 | 728 | dns.rcode.to_text(rcode), response)) |
668 | 729 | return (None, False) |
669 | 730 | |
670 | class Resolver: | |
731 | class BaseResolver: | |
671 | 732 | """DNS stub resolver.""" |
672 | 733 | |
673 | 734 | # We initialize in reset() |
689 | 750 | self.reset() |
690 | 751 | if configure: |
691 | 752 | if sys.platform == 'win32': |
692 | self.read_registry() # pragma: no cover | |
753 | self.read_registry() | |
693 | 754 | elif filename: |
694 | 755 | self.read_resolv_conf(filename) |
695 | 756 | |
757 | 818 | self.nameservers.append(tokens[1]) |
758 | 819 | elif tokens[0] == 'domain': |
759 | 820 | self.domain = dns.name.from_text(tokens[1]) |
821 | # domain and search are exclusive | |
822 | self.search = [] | |
760 | 823 | elif tokens[0] == 'search': |
824 | # the last search wins | |
825 | self.search = [] | |
761 | 826 | for suffix in tokens[1:]: |
762 | 827 | 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 | |
763 | 830 | elif tokens[0] == 'options': |
764 | 831 | for opt in tokens[1:]: |
765 | 832 | if opt == 'rotate': |
766 | 833 | self.rotate = True |
767 | 834 | elif opt == 'edns0': |
768 | self.use_edns(0, 0, 0) | |
835 | self.use_edns() | |
769 | 836 | elif 'timeout' in opt: |
770 | 837 | try: |
771 | 838 | self.timeout = int(opt.split(':')[1]) |
817 | 884 | self.search.append(dns.name.from_text(s)) |
818 | 885 | |
819 | 886 | def _config_win32_fromkey(self, key, always_try_domain): |
887 | # pylint: disable=undefined-variable | |
888 | # (disabled for WindowsError) | |
820 | 889 | 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 | |
823 | 892 | servers = None |
824 | 893 | if servers: |
825 | 894 | self._config_win32_nameservers(servers) |
826 | 895 | if servers or always_try_domain: |
827 | 896 | try: |
828 | dom, rtype = winreg.QueryValueEx(key, 'Domain') | |
897 | dom, _ = winreg.QueryValueEx(key, 'Domain') | |
829 | 898 | if dom: |
830 | self._config_win32_domain(dom) | |
899 | self._config_win32_domain(dom) # pragma: no cover | |
831 | 900 | except WindowsError: # pragma: no cover |
832 | 901 | pass |
833 | 902 | else: |
834 | 903 | try: |
835 | servers, rtype = winreg.QueryValueEx(key, 'DhcpNameServer') | |
904 | servers, _ = winreg.QueryValueEx(key, 'DhcpNameServer') | |
836 | 905 | except WindowsError: # pragma: no cover |
837 | 906 | servers = None |
838 | if servers: # pragma: no cover | |
907 | if servers: | |
839 | 908 | self._config_win32_nameservers(servers) |
840 | 909 | try: |
841 | dom, rtype = winreg.QueryValueEx(key, 'DhcpDomain') | |
842 | if dom: # pragma: no cover | |
910 | dom, _ = winreg.QueryValueEx(key, 'DhcpDomain') | |
911 | if dom: | |
843 | 912 | self._config_win32_domain(dom) |
844 | 913 | except WindowsError: # pragma: no cover |
845 | 914 | pass |
846 | 915 | 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 | |
849 | 918 | search = None |
850 | 919 | if search: # pragma: no cover |
851 | 920 | self._config_win32_search(search) |
872 | 941 | try: |
873 | 942 | guid = winreg.EnumKey(interfaces, i) |
874 | 943 | i += 1 |
944 | # XXXRTH why do we get this key and then not use it? | |
875 | 945 | key = winreg.OpenKey(interfaces, guid) |
876 | 946 | if not self._win32_is_nic_enabled(lm, guid, key): |
877 | 947 | continue |
886 | 956 | finally: |
887 | 957 | lm.Close() |
888 | 958 | |
889 | def _win32_is_nic_enabled(self, lm, guid, | |
890 | interface_key): | |
959 | def _win32_is_nic_enabled(self, lm, guid, _): | |
891 | 960 | # Look in the Windows Registry to determine whether the network |
892 | 961 | # interface corresponding to the given guid is enabled. |
893 | 962 | # |
907 | 976 | (pnp_id, ttype) = winreg.QueryValueEx( |
908 | 977 | connection_key, 'PnpInstanceID') |
909 | 978 | |
910 | if ttype != winreg.REG_SZ: # pragma: no cover | |
911 | raise ValueError | |
979 | if ttype != winreg.REG_SZ: | |
980 | raise ValueError # pragma: no cover | |
912 | 981 | |
913 | 982 | device_key = winreg.OpenKey( |
914 | 983 | lm, r'SYSTEM\CurrentControlSet\Enum\%s' % pnp_id) |
918 | 987 | (flags, ttype) = winreg.QueryValueEx( |
919 | 988 | device_key, 'ConfigFlags') |
920 | 989 | |
921 | if ttype != winreg.REG_DWORD: # pragma: no cover | |
922 | raise ValueError | |
990 | if ttype != winreg.REG_DWORD: | |
991 | raise ValueError # pragma: no cover | |
923 | 992 | |
924 | 993 | # Based on experimentation, bit 0x1 indicates that the |
925 | 994 | # device is disabled. |
932 | 1001 | except Exception: # pragma: no cover |
933 | 1002 | return False |
934 | 1003 | |
935 | def _compute_timeout(self, start, lifetime=None): | |
1004 | def _compute_timeout(self, start, lifetime=None, errors=None): | |
936 | 1005 | lifetime = self.lifetime if lifetime is None else lifetime |
937 | 1006 | now = time.time() |
938 | 1007 | duration = now - start |
1008 | if errors is None: | |
1009 | errors = [] | |
939 | 1010 | if duration < 0: |
940 | 1011 | if duration < -1: |
941 | 1012 | # Time going backwards is bad. Just give up. |
942 | raise Timeout(timeout=duration) | |
1013 | raise LifetimeTimeout(timeout=duration, errors=errors) | |
943 | 1014 | else: |
944 | 1015 | # Time went backwards, but only a little. This can |
945 | 1016 | # happen, e.g. under vmware with older linux kernels. |
946 | 1017 | # Pretend it didn't happen. |
947 | 1018 | now = start |
948 | 1019 | if duration >= lifetime: |
949 | raise Timeout(timeout=duration) | |
1020 | raise LifetimeTimeout(timeout=duration, errors=errors) | |
950 | 1021 | return min(lifetime - duration, self.timeout) |
951 | 1022 | |
952 | 1023 | def _get_qnames_to_try(self, qname, search): |
958 | 1029 | if qname.is_absolute(): |
959 | 1030 | qnames_to_try.append(qname) |
960 | 1031 | 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) | |
969 | 1060 | 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.""" | |
970 | 1127 | |
971 | 1128 | def resolve(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, |
972 | 1129 | 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 | |
974 | 1131 | """Query nameservers to find the answer to the question. |
975 | 1132 | |
976 | 1133 | The *qname*, *rdtype*, and *rdclass* parameters may be objects |
1003 | 1160 | which causes the value of the resolver's |
1004 | 1161 | ``use_search_by_default`` attribute to be used. |
1005 | 1162 | |
1006 | Raises ``dns.exception.Timeout`` if no answers could be found | |
1163 | Raises ``dns.resolver.LifetimeTimeout`` if no answers could be found | |
1007 | 1164 | in the specified lifetime. |
1008 | 1165 | |
1009 | 1166 | Raises ``dns.resolver.NXDOMAIN`` if the query name does not exist. |
1039 | 1196 | (nameserver, port, tcp, backoff) = resolution.next_nameserver() |
1040 | 1197 | if backoff: |
1041 | 1198 | time.sleep(backoff) |
1042 | timeout = self._compute_timeout(start, lifetime) | |
1199 | timeout = self._compute_timeout(start, lifetime, | |
1200 | resolution.errors) | |
1043 | 1201 | try: |
1044 | 1202 | if dns.inet.is_address(nameserver): |
1045 | 1203 | if tcp: |
1108 | 1266 | rdclass=dns.rdataclass.IN, |
1109 | 1267 | *args, **kwargs) |
1110 | 1268 | |
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 | ||
1169 | 1294 | |
1170 | 1295 | #: The default resolver. |
1171 | 1296 | default_resolver = None |
1230 | 1355 | """ |
1231 | 1356 | |
1232 | 1357 | 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) | |
1233 | 1368 | |
1234 | 1369 | |
1235 | 1370 | def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None): |
1314 | 1449 | raise socket.gaierror(socket.EAI_NONAME, 'Name or service not known') |
1315 | 1450 | v6addrs = [] |
1316 | 1451 | v4addrs = [] |
1317 | canonical_name = None | |
1452 | canonical_name = None # pylint: disable=redefined-outer-name | |
1318 | 1453 | # Is host None or an address literal? If so, use the system's |
1319 | 1454 | # getaddrinfo(). |
1320 | 1455 | if host is None: |
1351 | 1486 | v4addrs.append(rdata.address) |
1352 | 1487 | except dns.resolver.NXDOMAIN: |
1353 | 1488 | raise socket.gaierror(socket.EAI_NONAME, 'Name or service not known') |
1354 | except Exception as e: | |
1355 | print(e) | |
1489 | except Exception: | |
1356 | 1490 | # We raise EAI_AGAIN here as the failure may be temporary |
1357 | 1491 | # (e.g. a timeout) and EAI_SYSTEM isn't defined on Windows. |
1358 | 1492 | # [Issue #416] |
1481 | 1615 | 'Name or service not known') |
1482 | 1616 | sockaddr = (ip, 80) |
1483 | 1617 | family = socket.AF_INET |
1484 | (name, port) = _getnameinfo(sockaddr, socket.NI_NAMEREQD) | |
1618 | (name, _) = _getnameinfo(sockaddr, socket.NI_NAMEREQD) | |
1485 | 1619 | aliases = [] |
1486 | 1620 | addresses = [] |
1487 | 1621 | tuples = _getaddrinfo(name, 0, family, socket.SOCK_STREAM, socket.SOL_TCP, |
68 | 68 | return self.to_text() |
69 | 69 | |
70 | 70 | 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): | |
74 | 75 | return False |
75 | 76 | return super().__eq__(other) |
76 | 77 | |
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 | """ | |
82 | 100 | if not super().match(rdclass, rdtype, covers): |
83 | 101 | return False |
84 | 102 | if self.name != name or self.deleting != deleting: |
85 | 103 | return False |
86 | 104 | return True |
87 | 105 | |
106 | # pylint: disable=arguments-differ | |
107 | ||
88 | 108 | 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. | |
90 | 110 | |
91 | 111 | See ``dns.name.Name.choose_relativity`` for more information |
92 | 112 | on how *origin* and *relativize* determine the way names |
105 | 125 | return super().to_text(self.name, origin, relativize, |
106 | 126 | self.deleting, **kw) |
107 | 127 | |
108 | def to_wire(self, file, compress=None, origin=None, **kw): | |
128 | def to_wire(self, file, compress=None, origin=None, | |
129 | **kw): | |
109 | 130 | """Convert the RRset to wire format. |
110 | 131 | |
111 | 132 | All keyword arguments are passed to ``dns.rdataset.to_wire()``; see |
117 | 138 | return super().to_wire(self.name, file, compress, origin, |
118 | 139 | self.deleting, **kw) |
119 | 140 | |
141 | # pylint: enable=arguments-differ | |
142 | ||
120 | 143 | def to_rdataset(self): |
121 | 144 | """Convert an RRset into an Rdataset. |
122 | 145 | |
126 | 149 | |
127 | 150 | |
128 | 151 | 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): | |
130 | 154 | """Create an RRset with the specified name, TTL, class, and type, and with |
131 | 155 | the specified list of rdatas in text format. |
132 | 156 | |
133 | 157 | *idna_codec*, a ``dns.name.IDNACodec``, specifies the IDNA |
134 | 158 | encoder/decoder to use; if ``None``, the default IDNA 2003 |
135 | 159 | 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. | |
136 | 168 | |
137 | 169 | Returns a ``dns.rrset.RRset`` object. |
138 | 170 | """ |
144 | 176 | r = RRset(name, rdclass, rdtype) |
145 | 177 | r.update_ttl(ttl) |
146 | 178 | 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) | |
148 | 181 | r.add(rd) |
149 | 182 | return r |
150 | 183 |
83 | 83 | subclasses. |
84 | 84 | """ |
85 | 85 | |
86 | cls = self.__class__ | |
86 | if hasattr(self, '_clone_class'): | |
87 | cls = self._clone_class | |
88 | else: | |
89 | cls = self.__class__ | |
87 | 90 | obj = cls.__new__(cls) |
88 | obj.items = self.items.copy() | |
91 | obj.items = odict() | |
92 | obj.items.update(self.items) | |
89 | 93 | return obj |
90 | 94 | |
91 | 95 | def __copy__(self): |
14 | 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | """Tokenize DNS master file format""" | |
17 | """Tokenize DNS zone file format""" | |
18 | 18 | |
19 | 19 | import io |
20 | 20 | import sys |
40 | 40 | |
41 | 41 | |
42 | 42 | class Token: |
43 | """A DNS master file format token. | |
43 | """A DNS zone file format token. | |
44 | 44 | |
45 | 45 | ttype: The token type |
46 | 46 | value: The token value |
47 | 47 | has_escape: Does the token value contain escapes? |
48 | 48 | """ |
49 | 49 | |
50 | def __init__(self, ttype, value='', has_escape=False): | |
50 | def __init__(self, ttype, value='', has_escape=False, comment=None): | |
51 | 51 | """Initialize a token instance.""" |
52 | 52 | |
53 | 53 | self.ttype = ttype |
54 | 54 | self.value = value |
55 | 55 | self.has_escape = has_escape |
56 | self.comment = comment | |
56 | 57 | |
57 | 58 | def is_eof(self): |
58 | 59 | return self.ttype == EOF |
103 | 104 | c = self.value[i] |
104 | 105 | i += 1 |
105 | 106 | if c == '\\': |
106 | if i >= l: | |
107 | if i >= l: # pragma: no cover (can't happen via get()) | |
107 | 108 | raise dns.exception.UnexpectedEnd |
108 | 109 | c = self.value[i] |
109 | 110 | i += 1 |
118 | 119 | i += 1 |
119 | 120 | if not (c2.isdigit() and c3.isdigit()): |
120 | 121 | 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) | |
122 | 126 | unescaped += c |
123 | 127 | return Token(self.ttype, unescaped) |
124 | 128 | |
154 | 158 | c = self.value[i] |
155 | 159 | i += 1 |
156 | 160 | if c == '\\': |
157 | if i >= l: | |
161 | if i >= l: # pragma: no cover (can't happen via get()) | |
158 | 162 | raise dns.exception.UnexpectedEnd |
159 | 163 | c = self.value[i] |
160 | 164 | i += 1 |
169 | 173 | i += 1 |
170 | 174 | if not (c2.isdigit() and c3.isdigit()): |
171 | 175 | 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) | |
173 | 180 | else: |
174 | 181 | # Note that as mentioned above, if c is a Unicode |
175 | 182 | # code point outside of the ASCII range, then this |
183 | 190 | |
184 | 191 | |
185 | 192 | class Tokenizer: |
186 | """A DNS master file format tokenizer. | |
193 | """A DNS zone file format tokenizer. | |
187 | 194 | |
188 | 195 | A token object is basically a (type, value) tuple. The valid |
189 | 196 | types are EOF, EOL, WHITESPACE, IDENTIFIER, QUOTED_STRING, |
395 | 402 | if self.multiline: |
396 | 403 | raise dns.exception.SyntaxError( |
397 | 404 | 'unbalanced parentheses') |
398 | return Token(EOF) | |
405 | return Token(EOF, comment=token) | |
399 | 406 | elif self.multiline: |
400 | 407 | self.skip_whitespace() |
401 | 408 | token = '' |
402 | 409 | continue |
403 | 410 | else: |
404 | return Token(EOL, '\n') | |
411 | return Token(EOL, '\n', comment=token) | |
405 | 412 | else: |
406 | 413 | # This code exists in case we ever want a |
407 | 414 | # delimiter to be returned. It never produces |
421 | 428 | token += c |
422 | 429 | has_escape = True |
423 | 430 | c = self._get_char() |
424 | if c == '' or c == '\n': | |
431 | if c == '' or (c == '\n' and not self.quoting): | |
425 | 432 | raise dns.exception.UnexpectedEnd |
426 | 433 | token += c |
427 | 434 | if token == '' and ttype != QUOTED_STRING: |
528 | 535 | '%d is not an unsigned 32-bit integer' % value) |
529 | 536 | return value |
530 | 537 | |
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 | ||
531 | 553 | def get_string(self, max_length=None): |
532 | 554 | """Read the next token and interpret it as a string. |
533 | 555 | |
558 | 580 | raise dns.exception.SyntaxError('expecting an identifier') |
559 | 581 | return token.value |
560 | 582 | |
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 | ||
561 | 602 | def concatenate_remaining_identifiers(self): |
562 | 603 | """Read the remaining tokens on the line, which should be identifiers. |
563 | 604 | |
571 | 612 | while True: |
572 | 613 | token = self.get().unescape() |
573 | 614 | if token.is_eol_or_eof(): |
615 | self.unget(token) | |
574 | 616 | break |
575 | 617 | if not token.is_identifier(): |
576 | 618 | raise dns.exception.SyntaxError |
600 | 642 | token = self.get() |
601 | 643 | return self.as_name(token, origin, relativize, relativize_to) |
602 | 644 | |
603 | def get_eol(self): | |
645 | def get_eol_as_token(self): | |
604 | 646 | """Read the next token and raise an exception if it isn't EOL or |
605 | 647 | EOF. |
606 | 648 | |
612 | 654 | raise dns.exception.SyntaxError( |
613 | 655 | 'expected EOL or EOF, got %d "%s"' % (token.ttype, |
614 | 656 | token.value)) |
615 | return token.value | |
657 | return token | |
658 | ||
659 | def get_eol(self): | |
660 | return self.get_eol_as_token().value | |
616 | 661 | |
617 | 662 | def get_ttl(self): |
618 | 663 | """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 |
70 | 70 | |
71 | 71 | """The peer didn't like amount of truncation in the TSIG we sent""" |
72 | 72 | |
73 | ||
73 | 74 | # TSIG Algorithms |
74 | 75 | |
75 | 76 | HMAC_MD5 = dns.name.from_text("HMAC-MD5.SIG-ALG.REG.INT") |
76 | 77 | HMAC_SHA1 = dns.name.from_text("hmac-sha1") |
77 | 78 | HMAC_SHA224 = dns.name.from_text("hmac-sha224") |
78 | 79 | HMAC_SHA256 = dns.name.from_text("hmac-sha256") |
80 | HMAC_SHA256_128 = dns.name.from_text("hmac-sha256-128") | |
79 | 81 | HMAC_SHA384 = dns.name.from_text("hmac-sha384") |
82 | HMAC_SHA384_192 = dns.name.from_text("hmac-sha384-192") | |
80 | 83 | 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") | |
90 | 86 | |
91 | 87 | 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 | |
92 | 203 | |
93 | 204 | |
94 | 205 | def _digest(wire, key, rdata, time=None, request_mac=None, ctx=None, |
95 | 206 | multi=None): |
96 | 207 | """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 | |
98 | 209 | @raises ValueError: I{other_data} is too long |
99 | 210 | @raises NotImplementedError: I{algorithm} is not supported |
100 | 211 | """ |
130 | 241 | def _maybe_start_digest(key, mac, multi): |
131 | 242 | """If this is the first message in a multi-message sequence, |
132 | 243 | start a new context. |
133 | @rtype: hmac.HMAC object | |
244 | @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object | |
134 | 245 | """ |
135 | 246 | if multi: |
136 | 247 | ctx = get_context(key) |
145 | 256 | """Return a (tsig_rdata, mac, ctx) tuple containing the HMAC TSIG rdata |
146 | 257 | for the input parameters, the HMAC MAC calculated by applying the |
147 | 258 | 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) | |
149 | 260 | @raises ValueError: I{other_data} is too long |
150 | 261 | @raises NotImplementedError: I{algorithm} is not supported |
151 | 262 | """ |
152 | 263 | |
153 | 264 | 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) | |
159 | 267 | |
160 | 268 | return (tsig, _maybe_start_digest(key, mac, multi)) |
161 | 269 | |
168 | 276 | @raises BadTime: There is too much time skew between the client and the |
169 | 277 | server. |
170 | 278 | @raises BadSignature: The TSIG signature did not validate |
171 | @rtype: hmac.HMAC object""" | |
279 | @rtype: dns.tsig.HMACTSig or dns.tsig.GSSTSig object""" | |
172 | 280 | |
173 | 281 | (adcount,) = struct.unpack("!H", wire[10:12]) |
174 | 282 | if adcount == 0: |
193 | 301 | if key.algorithm != rdata.algorithm: |
194 | 302 | raise BadAlgorithm |
195 | 303 | 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) | |
200 | 306 | |
201 | 307 | |
202 | 308 | def get_context(key): |
203 | """Returns an HMAC context foe the specified key. | |
309 | """Returns an HMAC context for the specified key. | |
204 | 310 | |
205 | 311 | @rtype: HMAC context |
206 | 312 | @raises NotImplementedError: I{algorithm} is not supported |
207 | 313 | """ |
208 | 314 | |
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) | |
215 | 319 | |
216 | 320 | |
217 | 321 | class Key: |
231 | 335 | self.name == other.name and |
232 | 336 | self.secret == other.secret and |
233 | 337 | 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 |
54 | 54 | if isinstance(key, bytes): |
55 | 55 | textring[name] = b64encode(key) |
56 | 56 | 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) | |
58 | 63 | return textring |
18 | 18 | |
19 | 19 | import dns.exception |
20 | 20 | |
21 | MAX_TTL = 2147483647 | |
21 | 22 | |
22 | 23 | class BadTTL(dns.exception.SyntaxError): |
23 | 24 | """DNS TTL value is not well-formed.""" |
37 | 38 | |
38 | 39 | if text.isdigit(): |
39 | 40 | total = int(text) |
41 | elif len(text) == 0: | |
42 | raise BadTTL | |
40 | 43 | else: |
41 | if not text[0].isdigit(): | |
42 | raise BadTTL | |
43 | 44 | total = 0 |
44 | 45 | current = 0 |
46 | need_digit = True | |
45 | 47 | for c in text: |
46 | 48 | if c.isdigit(): |
47 | 49 | current *= 10 |
48 | 50 | current += int(c) |
51 | need_digit = False | |
49 | 52 | else: |
53 | if need_digit: | |
54 | raise BadTTL | |
50 | 55 | c = c.lower() |
51 | 56 | if c == 'w': |
52 | 57 | total += current * 604800 |
61 | 66 | else: |
62 | 67 | raise BadTTL("unknown unit '%s'" % c) |
63 | 68 | current = 0 |
69 | need_digit = True | |
64 | 70 | if not current == 0: |
65 | 71 | raise BadTTL("trailing integer") |
66 | if total < 0 or total > 2147483647: | |
72 | if total < 0 or total > MAX_TTL: | |
67 | 73 | raise BadTTL("TTL should be between 0 and 2^31 - 1 (inclusive)") |
68 | 74 | 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') |
36 | 36 | @classmethod |
37 | 37 | def _maximum(cls): |
38 | 38 | return 3 |
39 | ||
40 | globals().update(UpdateSection.__members__) | |
41 | 39 | |
42 | 40 | |
43 | 41 | class UpdateMessage(dns.message.Message): |
309 | 307 | |
310 | 308 | # backwards compatibility |
311 | 309 | 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 |
19 | 19 | #: MAJOR |
20 | 20 | MAJOR = 2 |
21 | 21 | #: MINOR |
22 | MINOR = 0 | |
22 | MINOR = 2 | |
23 | 23 | #: MICRO |
24 | 24 | MICRO = 0 |
25 | 25 | #: RELEASELEVEL |
26 | RELEASELEVEL = 0x0f | |
26 | RELEASELEVEL = 0x00 | |
27 | 27 | #: SERIAL |
28 | 28 | SERIAL = 0 |
29 | 29 |
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) |
41 | 41 | def get_uint32(self): |
42 | 42 | return struct.unpack('!I', self.get_bytes(4))[0] |
43 | 43 | |
44 | def get_uint48(self): | |
45 | return int.from_bytes(self.get_bytes(6), 'big') | |
46 | ||
44 | 47 | def get_struct(self, format): |
45 | 48 | return struct.unpack(format, self.get_bytes(struct.calcsize(format))) |
46 | 49 |
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 |
17 | 17 | """DNS Zones.""" |
18 | 18 | |
19 | 19 | import contextlib |
20 | import hashlib | |
20 | 21 | import io |
21 | 22 | import os |
22 | import re | |
23 | import sys | |
23 | import struct | |
24 | 24 | |
25 | 25 | import dns.exception |
26 | 26 | import dns.name |
29 | 29 | import dns.rdatatype |
30 | 30 | import dns.rdata |
31 | 31 | import dns.rdtypes.ANY.SOA |
32 | import dns.rdtypes.ANY.ZONEMD | |
32 | 33 | import dns.rrset |
33 | 34 | import dns.tokenizer |
35 | import dns.transaction | |
34 | 36 | import dns.ttl |
35 | 37 | import dns.grange |
38 | import dns.zonefile | |
36 | 39 | |
37 | 40 | |
38 | 41 | class BadZone(dns.exception.DNSException): |
55 | 58 | """The DNS zone's origin is unknown.""" |
56 | 59 | |
57 | 60 | |
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): | |
59 | 109 | |
60 | 110 | """A DNS zone. |
61 | 111 | |
76 | 126 | |
77 | 127 | *origin* is the origin of the zone. It may be a ``dns.name.Name``, |
78 | 128 | 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. | |
80 | 130 | |
81 | 131 | *rdclass*, an ``int``, the zone's rdata class; the default is class IN. |
82 | 132 | |
161 | 211 | key = self._validate_name(key) |
162 | 212 | return self.nodes.get(key) |
163 | 213 | |
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 | |
166 | 217 | |
167 | 218 | def find_node(self, name, create=False): |
168 | 219 | """Find a node in the zone, possibly creating it. |
531 | 582 | for rdata in rds: |
532 | 583 | yield (name, rds.ttl, rdata) |
533 | 584 | |
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): | |
535 | 587 | """Write a zone to a file. |
536 | 588 | |
537 | 589 | *f*, a file or `str`. If *f* is a string, it is treated |
549 | 601 | *nl*, a ``str`` or None. The end of line string. If not |
550 | 602 | ``None``, the output will use the platform's native |
551 | 603 | 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. | |
552 | 608 | """ |
553 | 609 | |
554 | 610 | with contextlib.ExitStack() as stack: |
578 | 634 | names = self.keys() |
579 | 635 | for n in names: |
580 | 636 | 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) | |
587 | 640 | |
588 | 641 | try: |
589 | 642 | f.write(l_b) |
592 | 645 | f.write(l) |
593 | 646 | f.write(nl) |
594 | 647 | |
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): | |
596 | 650 | """Return a zone's text as though it were written to a file. |
597 | 651 | |
598 | 652 | *sorted*, a ``bool``. If True, the default, then the file |
608 | 662 | ``None``, the output will use the platform's native |
609 | 663 | end-of-line marker (i.e. LF on POSIX, CRLF on Windows). |
610 | 664 | |
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 | ||
611 | 669 | Returns a ``str``. |
612 | 670 | """ |
613 | 671 | temp_buffer = io.StringIO() |
614 | self.to_file(temp_buffer, sorted, relativize, nl) | |
672 | self.to_file(temp_buffer, sorted, relativize, nl, want_comments) | |
615 | 673 | return_value = temp_buffer.getvalue() |
616 | 674 | temp_buffer.close() |
617 | 675 | return return_value |
634 | 692 | if self.get_rdataset(name, dns.rdatatype.NS) is None: |
635 | 693 | raise NoNS |
636 | 694 | |
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 | |
704 | 704 | 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): | |
714 | 723 | 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 | |
722 | 816 | 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) | |
1044 | 865 | |
1045 | 866 | |
1046 | 867 | def from_text(text, origin=None, rdclass=dns.rdataclass.IN, |
1047 | 868 | relativize=True, zone_factory=Zone, filename=None, |
1048 | 869 | 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. | |
1052 | 873 | |
1053 | 874 | *origin*, a ``dns.name.Name``, a ``str``, or ``None``. The origin |
1054 | 875 | 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. | |
1056 | 877 | |
1057 | 878 | *rdclass*, an ``int``, the zone's rdata class; the default is class IN. |
1058 | 879 | |
1093 | 914 | |
1094 | 915 | if filename is None: |
1095 | 916 | 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 | |
1102 | 931 | |
1103 | 932 | |
1104 | 933 | def from_file(f, origin=None, rdclass=dns.rdataclass.IN, |
1105 | 934 | relativize=True, zone_factory=Zone, filename=None, |
1106 | 935 | 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. | |
1108 | 937 | |
1109 | 938 | *f*, a file or ``str``. If *f* is a string, it is treated |
1110 | 939 | as the name of a file to open. |
1111 | 940 | |
1112 | 941 | *origin*, a ``dns.name.Name``, a ``str``, or ``None``. The origin |
1113 | 942 | 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. | |
1115 | 944 | |
1116 | 945 | *rdclass*, an ``int``, the zone's rdata class; the default is class IN. |
1117 | 946 |
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 |
0 | dns |
8 | 8 | you should use the higher level ``dns.asyncresolver`` module; see |
9 | 9 | :ref:`async_resolver`. |
10 | 10 | |
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. | |
13 | 13 | |
14 | 14 | UDP |
15 | 15 | --- |
30 | 30 | --- |
31 | 31 | |
32 | 32 | .. autofunction:: dns.asyncquery.tls |
33 | ||
34 | Zone Transfers | |
35 | -------------- | |
36 | ||
37 | .. autofunction:: dns.asyncquery.inbound_xfr |
4 | 4 | |
5 | 5 | .. autofunction:: dns.asyncresolver.resolve |
6 | 6 | .. autofunction:: dns.asyncresolver.resolve_address |
7 | .. autofunction:: dns.asyncresolver.canonical_name | |
7 | 8 | .. 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 |
31 | 31 | .. autofunction:: dns.edns.get_option_class |
32 | 32 | .. autofunction:: dns.edns.option_from_wire_parser |
33 | 33 | .. autofunction:: dns.edns.option_from_wire |
34 | .. autofunction:: dns.edns.register_type |
2 | 2 | The dns.message.QueryMessage Class |
3 | 3 | ---------------------------------- |
4 | 4 | |
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. | |
6 | 6 | |
7 | 7 | .. autoclass:: dns.message.QueryMessage |
8 | 8 | :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: |
28 | 28 | absolute names. |
29 | 29 | |
30 | 30 | 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. | |
34 | 34 | |
35 | 35 | .. toctree:: |
36 | 36 |
40 | 40 | Zone Transfers |
41 | 41 | -------------- |
42 | 42 | |
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 | ||
43 | 50 | .. autofunction:: dns.query.xfr |
432 | 432 | |
433 | 433 | A ``dns.name.Name``, the exchange name. |
434 | 434 | |
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 | ||
435 | 454 | .. autoclass:: dns.rdtypes.ANY.SOA.SOA |
436 | 455 | :members: |
437 | 456 |
0 | 0 | Rdatatypes |
1 | ========== | |
1 | ---------- | |
2 | 2 | |
3 | 3 | .. py:data:: dns.rdatatype.A |
4 | 4 | :annotation: = 1 |
8 | 8 | :annotation: = 28 |
9 | 9 | .. py:data:: dns.rdatatype.AFSDB |
10 | 10 | :annotation: = 18 |
11 | .. py:data:: dns.rdatatype.AMTRELAY | |
12 | :annotation: = 259 | |
11 | 13 | .. py:data:: dns.rdatatype.ANY |
12 | 14 | :annotation: = 255 |
13 | 15 | .. py:data:: dns.rdatatype.APL |
48 | 50 | :annotation: = 13 |
49 | 51 | .. py:data:: dns.rdatatype.HIP |
50 | 52 | :annotation: = 55 |
53 | .. py:data:: dns.rdatatype.HTTPS | |
54 | :annotation: = 65 | |
51 | 55 | .. py:data:: dns.rdatatype.IPSECKEY |
52 | 56 | :annotation: = 45 |
53 | 57 | .. py:data:: dns.rdatatype.ISDN |
80 | 84 | :annotation: = 15 |
81 | 85 | .. py:data:: dns.rdatatype.NAPTR |
82 | 86 | :annotation: = 35 |
83 | .. py:data:: dns.rdatatype.NONE | |
84 | :annotation: = 0 | |
87 | .. py:data:: dns.rdatatype.NINFO | |
88 | :annotation: = 56 | |
85 | 89 | .. py:data:: dns.rdatatype.NS |
86 | 90 | :annotation: = 2 |
87 | 91 | .. py:data:: dns.rdatatype.NSAP |
88 | 92 | :annotation: = 22 |
89 | .. py:data:: dns.rdatatype.NSAP-PTR | |
93 | .. py:data:: dns.rdatatype.NSAP_PTR | |
90 | 94 | :annotation: = 23 |
91 | 95 | .. py:data:: dns.rdatatype.NSEC |
92 | 96 | :annotation: = 47 |
98 | 102 | :annotation: = 10 |
99 | 103 | .. py:data:: dns.rdatatype.NXT |
100 | 104 | :annotation: = 30 |
105 | .. py:data:: dns.rdatatype.OPENPGPKEY | |
106 | :annotation: = 61 | |
101 | 107 | .. py:data:: dns.rdatatype.OPT |
102 | 108 | :annotation: = 41 |
103 | 109 | .. py:data:: dns.rdatatype.PTR |
112 | 118 | :annotation: = 21 |
113 | 119 | .. py:data:: dns.rdatatype.SIG |
114 | 120 | :annotation: = 24 |
121 | .. py:data:: dns.rdatatype.SMIMEA | |
122 | :annotation: = 53 | |
115 | 123 | .. py:data:: dns.rdatatype.SOA |
116 | 124 | :annotation: = 6 |
117 | 125 | .. py:data:: dns.rdatatype.SPF |
120 | 128 | :annotation: = 33 |
121 | 129 | .. py:data:: dns.rdatatype.SSHFP |
122 | 130 | :annotation: = 44 |
131 | .. py:data:: dns.rdatatype.SVCB | |
132 | :annotation: = 64 | |
123 | 133 | .. py:data:: dns.rdatatype.TA |
124 | 134 | :annotation: = 32768 |
125 | 135 | .. py:data:: dns.rdatatype.TKEY |
130 | 140 | :annotation: = 250 |
131 | 141 | .. py:data:: dns.rdatatype.TXT |
132 | 142 | :annotation: = 16 |
143 | .. py:data:: dns.rdatatype.TYPE0 | |
144 | :annotation: = 0 | |
133 | 145 | .. py:data:: dns.rdatatype.UNSPEC |
134 | 146 | :annotation: = 103 |
135 | 147 | .. py:data:: dns.rdatatype.URI |
10 | 10 | |
11 | 11 | Two thread-safe cache implementations are provided, a simple |
12 | 12 | 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: | |
14 | 19 | |
15 | 20 | .. autoclass:: dns.resolver.Cache |
16 | 21 | :members: |
18 | 23 | .. autoclass:: dns.resolver.LRUCache |
19 | 24 | :members: |
20 | 25 | |
26 | .. autoclass:: dns.resolver.CacheStatistics | |
27 | :members: |
4 | 4 | |
5 | 5 | .. autofunction:: dns.resolver.resolve |
6 | 6 | .. autofunction:: dns.resolver.resolve_address |
7 | .. autofunction:: dns.resolver.canonical_name | |
7 | 8 | .. autofunction:: dns.resolver.zone_for_name |
8 | 9 | .. autofunction:: dns.resolver.query |
9 | 10 | .. autodata:: dns.resolver.default_resolver |
41 | 41 | Negative Caching. |
42 | 42 | |
43 | 43 | `RFC 2845 <https://tools.ietf.org/html/rfc2845>`_ |
44 | Transaction Sigatures (TSIG) | |
44 | Transaction Signatures (TSIG) | |
45 | 45 | |
46 | 46 | `RFC 3007 <https://tools.ietf.org/html/rfc3007>`_ |
47 | 47 | Dynamic Updates |
140 | 140 | `RFC 1035 <https://tools.ietf.org/html/rfc1035>`_ |
141 | 141 | RRSIG |
142 | 142 | `RFC 4034 <https://tools.ietf.org/html/rfc4034>`_ |
143 | SMIMEA | |
144 | `RFC 8162 <https://tools.ietf.org/html/rfc8162>`_ | |
143 | 145 | SOA |
144 | 146 | `RFC 1035 <https://tools.ietf.org/html/rfc1035>`_ |
145 | 147 | 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. |
0 | 0 | .. _whatsnew: |
1 | 1 | |
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 | ----- | |
4 | 56 | |
5 | 57 | * Python 3.6 or newer is required. |
6 | 58 |
1 | 1 | |
2 | 2 | The dns.zone.Zone Class |
3 | 3 | ----------------------- |
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). | |
4 | 9 | |
5 | 10 | .. autoclass:: dns.zone.Zone |
6 | 11 | :members: |
27 | 32 | subclassed if a different node factory is desired. |
28 | 33 | The node factory is a class or callable that returns a subclass of |
29 | 34 | ``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 |
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) |
2 | 2 | # This is an example of sending DNS queries over HTTPS (DoH) with dnspython. |
3 | 3 | # Requires use of the requests module's Session object. |
4 | 4 | # |
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 | |
6 | 6 | # for more details about Session objects |
7 | 7 | import requests |
8 | 8 |
1 | 1 | |
2 | 2 | import dns.resolver |
3 | 3 | |
4 | answers = dns.resolver.query('nominum.com', 'MX') | |
4 | answers = dns.resolver.resolve('nominum.com', 'MX') | |
5 | 5 | for rdata in answers: |
6 | 6 | print('Host', rdata.exchange, 'has preference', rdata.preference) |
30 | 30 | |
31 | 31 | resolver = dns.resolver.Resolver(configure=False) |
32 | 32 | resolver.nameservers = ['8.8.8.8'] |
33 | answer = resolver.query('amazon.com', 'NS') | |
33 | answer = resolver.resolve('amazon.com', 'NS') | |
34 | 34 | print('The nameservers are:') |
35 | 35 | for rr in answer: |
36 | 36 | print(rr.target) |
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) |
3 | 3 | import dns.resolver |
4 | 4 | import dns.zone |
5 | 5 | |
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') | |
8 | 8 | |
9 | 9 | z = dns.zone.from_xfr(dns.query.xfr(master_answer[0].address, 'dnspython.org')) |
10 | 10 | for n in sorted(z.nodes.keys()): |
8 | 8 | |
9 | 9 | enable= |
10 | 10 | all, |
11 | python3 | |
12 | 11 | |
13 | 12 | # It's worth looking at len-as-condition for optimization, but it's disabled |
14 | 13 | # here as it is not a correctness thing. Similarly eq-without-hash is |
17 | 16 | disable= |
18 | 17 | R, |
19 | 18 | I, |
20 | anomalous-backslash-in-string, | |
21 | arguments-differ, | |
22 | assigning-non-slot, | |
23 | bad-builtin, | |
24 | bad-continuation, | |
25 | 19 | broad-except, |
26 | deprecated-method, | |
27 | 20 | fixme, |
28 | 21 | global-statement, |
29 | 22 | invalid-name, |
30 | missing-docstring, | |
23 | missing-module-docstring, | |
24 | missing-class-docstring, | |
25 | missing-function-docstring, | |
31 | 26 | no-absolute-import, |
32 | no-member, | |
27 | no-member, # We'd like to use this, but our immutability is too hard for it | |
33 | 28 | protected-access, |
34 | 29 | redefined-builtin, |
35 | 30 | 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 | |
43 | 32 | |
44 | 33 | [REPORTS] |
45 | 34 | |
46 | 35 | # Set the output format. Available formats are text, parseable, colorized, msvs |
47 | 36 | # (visual studio) and html. You can also give a reporter class, eg |
48 | 37 | # mypackage.mymodule.MyReporterClass. |
49 | output-format=colorized | |
38 | #output-format=colorized | |
39 | output-format=parseable | |
50 | 40 | |
51 | 41 | # Tells whether to display a full report or only the messages |
52 | 42 | reports=no |
0 | 0 | [tool.poetry] |
1 | 1 | name = "dnspython" |
2 | version = "2.0.0" | |
2 | version = "2.2.0" | |
3 | 3 | description = "DNS toolkit" |
4 | 4 | authors = ["Bob Halley <halley@dnspython.org>"] |
5 | 5 | license = "ISC" |
12 | 12 | requests-toolbelt = {version="^0.9.1", optional=true} |
13 | 13 | requests = {version="^2.23.0", optional=true} |
14 | 14 | 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} | |
17 | 17 | curio = {version="^1.2", optional=true} |
18 | 18 | sniffio = {version="^1.1", optional=true} |
19 | 19 | |
20 | 20 | [tool.poetry.dev-dependencies] |
21 | mypy = "^0.782" | |
22 | pytest = "^5.4.1" | |
21 | mypy = "^0.812" | |
22 | pytest = ">=5.4.1,<7" | |
23 | 23 | pytest-cov = "^2.10.0" |
24 | 24 | flake8 = "^3.7.9" |
25 | sphinx = "^3.0.0" | |
25 | sphinx = "^4.0.0" | |
26 | 26 | coverage = "^5.1" |
27 | 27 | twine = "^3.1.1" |
28 | wheel = "^0.34.2" | |
28 | wheel = "^0.35.0" | |
29 | pylint = "^2.7.4" | |
29 | 30 | |
30 | 31 | [tool.poetry.extras] |
31 | 32 | doh = ['requests', 'requests-toolbelt'] |
35 | 36 | curio = ['curio', 'sniffio'] |
36 | 37 | |
37 | 38 | [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]⏎ |
0 | 0 | [metadata] |
1 | name = dnspython | |
2 | author = Bob Halley | |
3 | author_email = halley@dnspython.org | |
4 | license = ISC | |
1 | 5 | 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 |
19 | 19 | import sys |
20 | 20 | from setuptools import setup |
21 | 21 | |
22 | version = '2.0.0' | |
23 | 22 | |
24 | 23 | try: |
25 | sys.argv.remove("--cython-compile") | |
24 | sys.argv.remove("--cython-compile") | |
26 | 25 | except ValueError: |
27 | compile_cython = False | |
26 | compile_cython = False | |
28 | 27 | else: |
29 | 28 | compile_cython = True |
30 | 29 | from Cython.Build import cythonize |
32 | 31 | language_level='3') |
33 | 32 | |
34 | 33 | 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 | }, | |
81 | 34 | 'ext_modules': ext_modules if compile_cython else None, |
82 | 35 | 'zip_safe': False if compile_cython else None, |
83 | 36 | } |
78 | 78 | hinfo02 HINFO "PC" "NetBSD" |
79 | 79 | isdn01 ISDN "isdn-address" |
80 | 80 | 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 | |
83 | 83 | ;key01 KEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o jqf0BaqHT+8= |
84 | 84 | ;key02 KEY HOST|FLAG4 DNSSEC RSAMD5 AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o jqf0BaqHT+8= |
85 | 85 | kx01 KX 10 kdc |
86 | 86 | 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 | |
89 | 89 | loc03 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m |
90 | 90 | loc04 LOC 60 9 1.5 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m |
91 | 91 | loc05 LOC 60 9 1.51 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m |
92 | 92 | 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 | |
94 | 94 | loc08 LOC 0 9 1 S 24 39 0.000 E 10.00m 90000000.00m 2000m 20m |
95 | 95 | ;; |
96 | 96 | ;; XXXRTH These are all obsolete and unused. dnspython doesn't implement |
137 | 137 | tlsa1 TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 |
138 | 138 | tlsa2 TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 |
139 | 139 | 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 | |
140 | 143 | txt01 TXT "foo" |
141 | 144 | txt02 TXT "foo" "bar" |
142 | 145 | txt03 TXT foo |
164 | 167 | wks03 WKS 10.0.0.2 6 ( 65535 ) |
165 | 168 | x2501 X25 "123456789" |
166 | 169 | ds01 DS 12345 3 1 123456789abcdef67890123456789abcdef67890 |
170 | dlv01 DLV 12345 3 1 123456789abcdef67890123456789abcdef67890 | |
167 | 171 | apl01 APL 1:192.168.32.0/21 !1:192.168.38.0/28 |
168 | 172 | apl02 APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8 |
169 | 173 | unknown2 TYPE999 \# 8 0a0000010a000001 |
231 | 235 | amtrelay05 AMTRELAY 128 1 3 amtrelays.example.com. |
232 | 236 | csync0 CSYNC 12345 0 A MX RRSIG NSEC TYPE1234 |
233 | 237 | 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" |
38 | 38 | dhcid01 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA= |
39 | 39 | dhcid02 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No= |
40 | 40 | dhcid03 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY= |
41 | dlv01 3600 IN DLV 12345 3 1 123456789abcdef67890123456789abcdef67890 | |
41 | 42 | dname01 3600 IN DNAME dname-target. |
42 | 43 | dname02 3600 IN DNAME dname-target |
43 | 44 | dname03 3600 IN DNAME . |
59 | 60 | hip01 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D |
60 | 61 | hip02 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. |
61 | 62 | 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" | |
62 | 65 | ipseckey01 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== |
63 | 66 | ipseckey02 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== |
64 | 67 | ipseckey03 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== |
93 | 96 | nsec03 3600 IN NSEC . NSEC TYPE65535 |
94 | 97 | nsec301 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM |
95 | 98 | 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= | |
97 | 100 | ptr01 3600 IN PTR @ |
98 | 101 | px01 3600 IN PX 65535 foo. bar. |
99 | 102 | px02 3600 IN PX 65535 . . |
105 | 108 | rt02 3600 IN RT 65535 . |
106 | 109 | s 300 IN NS ns.s |
107 | 110 | 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 | |
108 | 114 | spf 3600 IN SPF "v=spf1 mx -all" |
109 | 115 | srv01 3600 IN SRV 0 0 0 . |
110 | 116 | srv02 3600 IN SRV 65535 65535 65535 old-slow-box.example.com. |
111 | 117 | 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" | |
112 | 119 | t 301 IN A 73.80.65.49 |
113 | 120 | tlsa1 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 |
114 | 121 | tlsa2 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 |
139 | 146 | wks02 3600 IN WKS 10.0.0.1 17 0 1 2 53 |
140 | 147 | wks03 3600 IN WKS 10.0.0.2 6 65535 |
141 | 148 | 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 |
38 | 38 | dhcid01.example. 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA= |
39 | 39 | dhcid02.example. 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No= |
40 | 40 | dhcid03.example. 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY= |
41 | dlv01.example. 3600 IN DLV 12345 3 1 123456789abcdef67890123456789abcdef67890 | |
41 | 42 | dname01.example. 3600 IN DNAME dname-target. |
42 | 43 | dname02.example. 3600 IN DNAME dname-target.example. |
43 | 44 | dname03.example. 3600 IN DNAME . |
59 | 60 | hip01.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D |
60 | 61 | hip02.example. 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. |
61 | 62 | 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" | |
62 | 65 | ipseckey01.example. 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== |
63 | 66 | ipseckey02.example. 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== |
64 | 67 | ipseckey03.example. 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== |
93 | 96 | nsec03.example. 3600 IN NSEC . NSEC TYPE65535 |
94 | 97 | nsec301.example. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM |
95 | 98 | 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= | |
97 | 100 | ptr01.example. 3600 IN PTR example. |
98 | 101 | px01.example. 3600 IN PX 65535 foo. bar. |
99 | 102 | px02.example. 3600 IN PX 65535 . . |
105 | 108 | rt02.example. 3600 IN RT 65535 . |
106 | 109 | s.example. 300 IN NS ns.s.example. |
107 | 110 | 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 | |
108 | 114 | spf.example. 3600 IN SPF "v=spf1 mx -all" |
109 | 115 | srv01.example. 3600 IN SRV 0 0 0 . |
110 | 116 | srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example.com. |
111 | 117 | 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" | |
112 | 119 | t.example. 301 IN A 73.80.65.49 |
113 | 120 | tlsa1.example. 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 |
114 | 121 | tlsa2.example. 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 |
139 | 146 | wks02.example. 3600 IN WKS 10.0.0.1 17 0 1 2 53 |
140 | 147 | wks03.example. 3600 IN WKS 10.0.0.2 6 65535 |
141 | 148 | 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 |
38 | 38 | dhcid01 3600 IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69 lOjxfNuVAA2kjEA= |
39 | 39 | dhcid02 3600 IN DHCID AAEBOSD+XR3Os/0LozeXVqcNc7FwCfQd WL3b/NaiUDlW2No= |
40 | 40 | dhcid03 3600 IN DHCID AAABxLmlskllE0MVjd57zHcWmEH3pCQ6 VytcKD//7es/deY= |
41 | dlv01 3600 IN DLV 12345 3 1 123456789abcdef67890123456789abcdef67890 | |
41 | 42 | dname01 3600 IN DNAME dname-target. |
42 | 43 | dname02 3600 IN DNAME dname-target |
43 | 44 | dname03 3600 IN DNAME . |
59 | 60 | hip01 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D |
60 | 61 | hip02 3600 IN HIP 2 200100107b1a74df365639cc39f1d578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. |
61 | 62 | 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" | |
62 | 65 | ipseckey01 3600 IN IPSECKEY 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== |
63 | 66 | ipseckey02 3600 IN IPSECKEY 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== |
64 | 67 | ipseckey03 3600 IN IPSECKEY 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtu gbo6BSGvgqt4AQ== |
93 | 96 | nsec03 3600 IN NSEC . NSEC TYPE65535 |
94 | 97 | nsec301 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM |
95 | 98 | 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= | |
97 | 100 | ptr01 3600 IN PTR @ |
98 | 101 | px01 3600 IN PX 65535 foo. bar. |
99 | 102 | px02 3600 IN PX 65535 . . |
105 | 108 | rt02 3600 IN RT 65535 . |
106 | 109 | s 300 IN NS ns.s |
107 | 110 | 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 | |
108 | 114 | spf 3600 IN SPF "v=spf1 mx -all" |
109 | 115 | srv01 3600 IN SRV 0 0 0 . |
110 | 116 | srv02 3600 IN SRV 65535 65535 65535 old-slow-box.example.com. |
111 | 117 | 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" | |
112 | 119 | t 301 IN A 73.80.65.49 |
113 | 120 | tlsa1 3600 IN TLSA 3 1 1 a9cdf989b504fe5dca90c0d2167b6550570734f7c763e09fdf88904e06157065 |
114 | 121 | tlsa2 3600 IN TLSA 1 0 1 efddf0d915c7bdc5782c0881e1b2a95ad099fbdd06d7b1f77982d9364338d955 |
139 | 146 | wks02 3600 IN WKS 10.0.0.1 17 0 1 2 53 |
140 | 147 | wks03 3600 IN WKS 10.0.0.2 6 65535 |
141 | 148 | 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 |
Binary diff not shown
159 | 159 | finally: |
160 | 160 | raise EOFError |
161 | 161 | |
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 | |
165 | 165 | # how the server behaves. |
166 | 166 | # |
167 | 167 | # The return value is either a dns.message.Message, a bytes, |
172 | 172 | # returned, then the output code will run for each returned |
173 | 173 | # item. |
174 | 174 | # |
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 | |
181 | 178 | |
182 | 179 | def maybe_listify(self, thing): |
183 | 180 | if isinstance(thing, list): |
239 | 236 | out = thing.to_wire(self.origin, multi=multi, tsig_ctx=tsig_ctx) |
240 | 237 | tsig_ctx = thing.tsig_ctx |
241 | 238 | yield out |
242 | else: | |
239 | elif thing is not None: | |
243 | 240 | yield thing |
244 | 241 | |
245 | 242 | 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) |
16 | 16 | |
17 | 17 | import asyncio |
18 | 18 | import socket |
19 | import sys | |
19 | 20 | import time |
20 | 21 | import unittest |
21 | 22 | |
151 | 152 | |
152 | 153 | @unittest.skipIf(not _network_available, "Internet not reachable") |
153 | 154 | class AsyncTests(unittest.TestCase): |
155 | connect_udp = sys.platform == 'win32' | |
154 | 156 | |
155 | 157 | def setUp(self): |
156 | 158 | self.backend = dns.asyncbackend.set_default_backend('asyncio') |
181 | 183 | dnsgoogle = dns.name.from_text('dns.google.') |
182 | 184 | self.assertEqual(answer[0].target, dnsgoogle) |
183 | 185 | |
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 | ||
184 | 206 | def testResolverBadScheme(self): |
185 | 207 | res = dns.asyncresolver.Resolver(configure=False) |
186 | 208 | res.nameservers = ['bogus://dns.google/dns-query'] |
227 | 249 | qname = dns.name.from_text('dns.google.') |
228 | 250 | async def run(): |
229 | 251 | 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) | |
231 | 253 | response = self.async_run(run) |
232 | 254 | rrs = response.get_rrset(response.answer, qname, |
233 | 255 | dns.rdataclass.IN, dns.rdatatype.A) |
240 | 262 | for address in query_addresses: |
241 | 263 | qname = dns.name.from_text('dns.google.') |
242 | 264 | async def run(): |
265 | if self.connect_udp: | |
266 | dtuple=(address, 53) | |
267 | else: | |
268 | dtuple=None | |
243 | 269 | async with await self.backend.make_socket( |
244 | 270 | dns.inet.af_for_address(address), |
245 | socket.SOCK_DGRAM) as s: | |
271 | socket.SOCK_DGRAM, 0, None, dtuple) as s: | |
246 | 272 | 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) | |
248 | 275 | response = self.async_run(run) |
249 | 276 | rrs = response.get_rrset(response.answer, qname, |
250 | 277 | dns.rdataclass.IN, dns.rdatatype.A) |
258 | 285 | qname = dns.name.from_text('dns.google.') |
259 | 286 | async def run(): |
260 | 287 | 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) | |
262 | 289 | response = self.async_run(run) |
263 | 290 | rrs = response.get_rrset(response.answer, qname, |
264 | 291 | dns.rdataclass.IN, dns.rdatatype.A) |
275 | 302 | dns.inet.af_for_address(address), |
276 | 303 | socket.SOCK_STREAM, 0, |
277 | 304 | None, |
278 | (address, 53)) as s: | |
305 | (address, 53), 2) as s: | |
279 | 306 | # for basic coverage |
280 | 307 | await s.getsockname() |
281 | 308 | 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) | |
283 | 311 | response = self.async_run(run) |
284 | 312 | rrs = response.get_rrset(response.answer, qname, |
285 | 313 | dns.rdataclass.IN, dns.rdatatype.A) |
294 | 322 | qname = dns.name.from_text('dns.google.') |
295 | 323 | async def run(): |
296 | 324 | 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) | |
298 | 326 | response = self.async_run(run) |
299 | 327 | rrs = response.get_rrset(response.answer, qname, |
300 | 328 | dns.rdataclass.IN, dns.rdatatype.A) |
314 | 342 | dns.inet.af_for_address(address), |
315 | 343 | socket.SOCK_STREAM, 0, |
316 | 344 | None, |
317 | (address, 853), None, | |
345 | (address, 853), 2, | |
318 | 346 | ssl_context, None) as s: |
319 | 347 | # for basic coverage |
320 | 348 | await s.getsockname() |
321 | 349 | 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) | |
323 | 352 | response = self.async_run(run) |
324 | 353 | rrs = response.get_rrset(response.answer, qname, |
325 | 354 | dns.rdataclass.IN, dns.rdatatype.A) |
333 | 362 | qname = dns.name.from_text('.') |
334 | 363 | async def run(): |
335 | 364 | 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) | |
337 | 367 | (_, tcp) = self.async_run(run) |
338 | 368 | self.assertTrue(tcp) |
339 | 369 | |
342 | 372 | qname = dns.name.from_text('dns.google.') |
343 | 373 | async def run(): |
344 | 374 | 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) | |
346 | 377 | (_, tcp) = self.async_run(run) |
347 | 378 | self.assertFalse(tcp) |
348 | 379 | |
349 | 380 | def testUDPReceiveQuery(self): |
381 | if self.connect_udp: | |
382 | self.skipTest('test needs connectionless sockets') | |
350 | 383 | async def run(): |
351 | 384 | async with await self.backend.make_socket( |
352 | 385 | socket.AF_INET, socket.SOCK_DGRAM, |
366 | 399 | self.assertEqual(sender_address, recv_address) |
367 | 400 | |
368 | 401 | def testUDPReceiveTimeout(self): |
402 | if self.connect_udp: | |
403 | self.skipTest('test needs connectionless sockets') | |
369 | 404 | async def arun(): |
370 | 405 | async with await self.backend.make_socket(socket.AF_INET, |
371 | 406 | socket.SOCK_DGRAM, 0, |
404 | 439 | return trio.run(afunc) |
405 | 440 | |
406 | 441 | class TrioAsyncTests(AsyncTests): |
442 | connect_udp = False | |
407 | 443 | def setUp(self): |
408 | 444 | self.backend = dns.asyncbackend.set_default_backend('trio') |
409 | 445 | |
427 | 463 | return curio.run(afunc) |
428 | 464 | |
429 | 465 | class CurioAsyncTests(AsyncTests): |
466 | connect_udp = False | |
430 | 467 | def setUp(self): |
431 | 468 | self.backend = dns.asyncbackend.set_default_backend('curio') |
432 | 469 |
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) |
191 | 191 | 'MX 16 2 3600 1440021600 1438207200 38353 example.com. E1/oLjSGIbmLny/4fcgM1z4oL6aqo+izT3urCyHyvEp4Sp8Syg1eI+lJ57CSnZqjJP41O/9l4m0AsQ4f7qI1gVnML8vWWiyW2KXhT9kuAICUSxv5OWbf81Rq7Yu60npabODB0QFPb/rkW3kUZmQ0YQUA') |
192 | 192 | |
193 | 193 | when5 = 1440021600 |
194 | when5_start = 1438207200 | |
194 | 195 | |
195 | 196 | wildcard_keys = { |
196 | 197 | abs_example_com : dns.rrset.from_text( |
204 | 205 | |
205 | 206 | wildcard_when = 1593541048 |
206 | 207 | |
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=') | |
212 | 260 | |
213 | 261 | @unittest.skipUnless(dns.dnssec._have_pyca, |
214 | 262 | "Python Cryptography cannot be imported") |
215 | 263 | 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) | |
216 | 272 | |
217 | 273 | def testAbsoluteRSAGood(self): # type: () -> None |
218 | 274 | dns.dnssec.validate(abs_soa, abs_soa_rrsig, abs_keys, None, when) |
229 | 285 | def testRelativeRSAGood(self): # type: () -> None |
230 | 286 | dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys, |
231 | 287 | 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) | |
232 | 291 | |
233 | 292 | def testRelativeRSABad(self): # type: () -> None |
234 | 293 | def bad(): # type: () -> None |
294 | 353 | dns.dnssec.validate(abs_other_ed448_mx, abs_ed448_mx_rrsig_2, |
295 | 354 | abs_ed448_keys_2, None, when5) |
296 | 355 | |
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): | |
298 | 361 | dns.dnssec.validate(wildcard_txt, wildcard_txt_rrsig, |
299 | 362 | wildcard_keys, None, wildcard_when) |
300 | 363 | |
312 | 375 | abc_txt_rrsig = clone_rrset(wildcard_txt_rrsig, abc_name) |
313 | 376 | dns.dnssec.validate(abc_txt, abc_txt_rrsig, wildcard_keys, None, |
314 | 377 | 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) | |
315 | 385 | |
316 | 386 | def testAlternateParameterFormats(self): # type: () -> None |
317 | 387 | # Pass rrset and rrsigset as (name, rdataset) tuples, not rrsets |
325 | 395 | keys[name] = dns.node.Node() |
326 | 396 | keys[name].rdatasets.append(key_rrset.to_rdataset()) |
327 | 397 | 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) | |
328 | 404 | |
329 | 405 | # Pass origin as a string, not a name. |
330 | 406 | dns.dnssec.validate(rel_soa, rel_soa_rrsig, rel_keys, |
331 | 407 | '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 | ||
332 | 464 | |
333 | 465 | 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) | |
334 | 472 | |
335 | 473 | def testMakeExampleSHA1DS(self): # type: () -> None |
336 | 474 | for algorithm in ('SHA1', 'sha1', dns.dnssec.DSDigest.SHA1): |
337 | 475 | ds = dns.dnssec.make_ds(abs_example, example_sep_key, algorithm) |
338 | 476 | self.assertEqual(ds, example_ds_sha1) |
477 | ds = dns.dnssec.make_ds('example.', example_sep_key, algorithm) | |
478 | self.assertEqual(ds, example_ds_sha1) | |
339 | 479 | |
340 | 480 | def testMakeExampleSHA256DS(self): # type: () -> None |
341 | 481 | for algorithm in ('SHA256', 'sha256', dns.dnssec.DSDigest.SHA256): |
356 | 496 | with self.assertRaises(dns.dnssec.UnsupportedAlgorithm): |
357 | 497 | ds = dns.dnssec.make_ds(abs_example, example_sep_key, algorithm) |
358 | 498 | |
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 | ||
359 | 529 | if __name__ == '__main__': |
360 | 530 | unittest.main() |
31 | 31 | resolver_v6_addresses = [] |
32 | 32 | try: |
33 | 33 | with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: |
34 | s.settimeout(4) | |
34 | 35 | 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 | ] | |
36 | 41 | except Exception: |
37 | 42 | pass |
38 | 43 | try: |
42 | 47 | '2606:4700:4700::1111', |
43 | 48 | # Google says 404 |
44 | 49 | # '2001:4860:4860::8888', |
45 | '2620:fe::11' | |
50 | # '2620:fe::fe', | |
46 | 51 | ] |
47 | 52 | except Exception: |
48 | 53 | pass |
49 | 54 | |
50 | 55 | KNOWN_ANYCAST_DOH_RESOLVER_URLS = ['https://cloudflare-dns.com/dns-query', |
51 | 56 | 'https://dns.google/dns-query', |
52 | 'https://dns11.quad9.net/dns-query'] | |
57 | # 'https://dns11.quad9.net/dns-query', | |
58 | ] | |
53 | 59 | |
54 | 60 | # Some tests require the internet to be available to run, so let's |
55 | 61 | # skip those if it's not there. |
71 | 77 | def test_get_request(self): |
72 | 78 | nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS) |
73 | 79 | 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) | |
75 | 82 | self.assertTrue(q.is_response(r)) |
76 | 83 | |
77 | 84 | def test_post_request(self): |
78 | 85 | nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS) |
79 | 86 | 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) | |
81 | 89 | self.assertTrue(q.is_response(r)) |
82 | 90 | |
83 | 91 | def test_build_url_from_ip(self): |
89 | 97 | # https://8.8.8.8/dns-query |
90 | 98 | # So we're just going to do GET requests here |
91 | 99 | r = dns.query.https(q, nameserver_ip, session=self.session, |
92 | post=False) | |
100 | post=False, timeout=4) | |
93 | 101 | |
94 | 102 | self.assertTrue(q.is_response(r)) |
95 | 103 | if resolver_v6_addresses: |
96 | 104 | nameserver_ip = random.choice(resolver_v6_addresses) |
97 | 105 | q = dns.message.make_query('example.com.', dns.rdatatype.A) |
98 | 106 | r = dns.query.https(q, nameserver_ip, session=self.session, |
99 | post=False) | |
107 | post=False, timeout=4) | |
100 | 108 | self.assertTrue(q.is_response(r)) |
101 | 109 | |
102 | 110 | def test_bootstrap_address(self): |
109 | 117 | # make sure CleanBrowsing's IP address will fail TLS certificate |
110 | 118 | # check |
111 | 119 | 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) | |
113 | 122 | # use host header |
114 | 123 | r = dns.query.https(q, valid_tls_url, session=self.session, |
115 | bootstrap_address=ip) | |
124 | bootstrap_address=ip, timeout=4) | |
116 | 125 | self.assertTrue(q.is_response(r)) |
117 | 126 | |
118 | 127 | def test_new_session(self): |
119 | 128 | nameserver_url = random.choice(KNOWN_ANYCAST_DOH_RESOLVER_URLS) |
120 | 129 | 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) | |
122 | 131 | self.assertTrue(q.is_response(r)) |
123 | 132 | |
124 | 133 | def test_resolver(self): |
16 | 16 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | 17 | |
18 | 18 | import operator |
19 | import struct | |
19 | 20 | import unittest |
20 | 21 | |
21 | 22 | from io import BytesIO |
22 | 23 | |
23 | 24 | import dns.edns |
25 | import dns.wire | |
24 | 26 | |
25 | 27 | class OptionTestCase(unittest.TestCase): |
26 | 28 | def testGenericOption(self): |
30 | 32 | data = io.getvalue() |
31 | 33 | self.assertEqual(data, b'data') |
32 | 34 | self.assertEqual(dns.edns.option_from_wire(3, data, 0, len(data)), opt) |
35 | self.assertEqual(str(opt), 'Generic 3') | |
33 | 36 | |
34 | 37 | def testECSOption_prefix_length(self): |
35 | 38 | opt = dns.edns.ECSOption('1.2.255.33', 20) |
44 | 47 | opt.to_wire(io) |
45 | 48 | data = io.getvalue() |
46 | 49 | 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') | |
47 | 57 | |
48 | 58 | def testECSOption25(self): |
49 | 59 | opt = dns.edns.ECSOption('1.2.3.255', 25) |
104 | 114 | dns.edns.ECSOption.from_text('1.2.3.4/twentyfour') |
105 | 115 | |
106 | 116 | 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): | |
107 | 123 | dns.edns.ECSOption.from_text('1.2.3.4/24/O') # <-- that's not a zero |
108 | 124 | |
109 | 125 | with self.assertRaises(ValueError): |
111 | 127 | |
112 | 128 | with self.assertRaises(ValueError): |
113 | 129 | 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) | |
114 | 136 | |
115 | 137 | def test_basic_relations(self): |
116 | 138 | o1 = dns.edns.ECSOption.from_text('1.2.3.0/24/0') |
137 | 159 | self.assertTrue(o1 != o2) |
138 | 160 | self.assertFalse(o1 == 123) |
139 | 161 | 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) |
12 | 12 | self.assertEqual(pool.random_16(), 61532) |
13 | 13 | self.assertEqual(pool.random_32(), 4226376065) |
14 | 14 | 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') | |
15 | 17 | |
16 | 18 | def test_pool_random(self): |
17 | 19 | pool = dns.entropy.EntropyPool() |
71 | 71 | # In TSIG text mode, it should be BADSIG |
72 | 72 | self.assertEqual(dns.rcode.to_text(rcode, True), 'BADSIG') |
73 | 73 | |
74 | def test_unknown_rcode(self): | |
75 | with self.assertRaises(dns.rcode.UnknownRcode): | |
76 | dns.rcode.Rcode.make('BOGUS') | |
77 | ||
74 | 78 | |
75 | 79 | if __name__ == '__main__': |
76 | 80 | unittest.main() |
63 | 63 | self.assertEqual(step, 77) |
64 | 64 | |
65 | 65 | def testFailFromText1(self): |
66 | def bad(): | |
66 | with self.assertRaises(dns.exception.SyntaxError): | |
67 | 67 | start = 2 |
68 | 68 | stop = 1 |
69 | 69 | step = 1 |
70 | 70 | dns.grange.from_text('%d-%d/%d' % (start, stop, step)) |
71 | self.assertRaises(AssertionError, bad) | |
71 | self.assertTrue(False) | |
72 | 72 | |
73 | 73 | def testFailFromText2(self): |
74 | def bad(): | |
74 | with self.assertRaises(dns.exception.SyntaxError): | |
75 | 75 | start = '-1' |
76 | 76 | stop = 3 |
77 | 77 | step = 1 |
78 | 78 | dns.grange.from_text('%s-%d/%d' % (start, stop, step)) |
79 | self.assertRaises(dns.exception.SyntaxError, bad) | |
80 | 79 | |
81 | 80 | def testFailFromText3(self): |
82 | def bad(): | |
81 | with self.assertRaises(dns.exception.SyntaxError): | |
83 | 82 | start = 1 |
84 | 83 | stop = 4 |
85 | 84 | step = '-2' |
86 | 85 | 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') | |
88 | 90 | |
89 | 91 | if __name__ == '__main__': |
90 | 92 | 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 |
15 | 15 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
16 | 16 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | 17 | |
18 | import os | |
19 | 18 | import unittest |
20 | 19 | import binascii |
21 | 20 | |
26 | 25 | import dns.rdataclass |
27 | 26 | import dns.rdatatype |
28 | 27 | import dns.rrset |
28 | import dns.tsig | |
29 | 29 | 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 | |
33 | 34 | |
34 | 35 | query_text = """id 1234 |
35 | 36 | opcode QUERY |
336 | 337 | m.want_dnssec() |
337 | 338 | self.assertEqual(m.edns, 0) |
338 | 339 | 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) | |
339 | 345 | |
340 | 346 | def test_from_file(self): |
341 | 347 | m = dns.message.from_file(here('query')) |
363 | 369 | self.assertEqual(q.sections[3], []) |
364 | 370 | q.additional = [rrset] |
365 | 371 | 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)) | |
366 | 379 | |
367 | 380 | def test_not_a_response(self): |
368 | 381 | q = dns.message.QueryMessage(id=1) |
380 | 393 | q2.id = 1 |
381 | 394 | r = dns.message.make_response(q2) |
382 | 395 | 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)) | |
383 | 401 | # Test the other case of differing questions, where there is |
384 | 402 | # something in the response's question section that is not in |
385 | 403 | # the question's. We have to do multiple questions to test |
432 | 450 | self.assertFalse(isinstance(q2, dns.update.UpdateMessage)) |
433 | 451 | self.assertEqual(q1, q2) |
434 | 452 | |
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 | ||
435 | 684 | if __name__ == '__main__': |
436 | 685 | unittest.main() |
18 | 18 | from typing import Dict # pylint: disable=unused-import |
19 | 19 | import copy |
20 | 20 | import operator |
21 | import pickle | |
21 | 22 | import unittest |
22 | 23 | |
23 | 24 | from io import BytesIO |
464 | 465 | n.to_wire(f, compress) |
465 | 466 | self.assertRaises(dns.name.NeedAbsoluteNameOrOrigin, bad) |
466 | 467 | |
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 | ||
467 | 492 | def testSplit1(self): |
468 | 493 | n = dns.name.from_text('foo.bar.') |
469 | 494 | (prefix, suffix) = n.split(2) |
780 | 805 | @unittest.skipUnless(dns.name.have_idna_2008, |
781 | 806 | 'Python idna cannot be imported; no IDNA2008') |
782 | 807 | 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.') | |
789 | 832 | |
790 | 833 | def testDefaultDecodeIsJustPunycode(self): |
791 | 834 | # groß.com. in IDNA2008 form, pre-encoded. |
890 | 933 | origin = dns.name.from_text('foo.bar') |
891 | 934 | text = dns.reversename.to_address(n, v6_origin=origin) |
892 | 935 | 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) | |
893 | 941 | |
894 | 942 | def testE164ToEnum(self): |
895 | 943 | text = '+1 650 555 1212' |
1031 | 1079 | n = dns.name.from_unicode('Königsgäßchen;\ttext') |
1032 | 1080 | self.assertEqual(n.to_unicode(), 'königsgässchen\\;\\009text.') |
1033 | 1081 | |
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 | ||
1034 | 1088 | if __name__ == '__main__': |
1035 | 1089 | unittest.main() |
0 | # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license | |
1 | ||
0 | 2 | import unittest |
1 | 3 | |
2 | 4 | from dns import dnssec, name |
161 | 161 | b = binascii.unhexlify(b'0000000000000000000000000001ffff') |
162 | 162 | t = ntoa6(b) |
163 | 163 | 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') | |
164 | 170 | |
165 | 171 | def test_bad_ntoa1(self): |
166 | 172 | 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] |
67 | 67 | except Exception: |
68 | 68 | pass |
69 | 69 | |
70 | keyring = dns.tsigkeyring.from_text({'name' : 'tDz6cfXXGtNivRpQ98hr6A=='}) | |
70 | keyring = dns.tsigkeyring.from_text({'name': 'tDz6cfXXGtNivRpQ98hr6A=='}) | |
71 | 71 | |
72 | 72 | @unittest.skipIf(not _network_available, "Internet not reachable") |
73 | 73 | class QueryTests(unittest.TestCase): |
76 | 76 | for address in query_addresses: |
77 | 77 | qname = dns.name.from_text('dns.google.') |
78 | 78 | 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) | |
80 | 80 | rrs = response.get_rrset(response.answer, qname, |
81 | 81 | dns.rdataclass.IN, dns.rdatatype.A) |
82 | 82 | self.assertTrue(rrs is not None) |
91 | 91 | s.setblocking(0) |
92 | 92 | qname = dns.name.from_text('dns.google.') |
93 | 93 | 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) | |
95 | 95 | rrs = response.get_rrset(response.answer, qname, |
96 | 96 | dns.rdataclass.IN, dns.rdatatype.A) |
97 | 97 | self.assertTrue(rrs is not None) |
103 | 103 | for address in query_addresses: |
104 | 104 | qname = dns.name.from_text('dns.google.') |
105 | 105 | 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) | |
107 | 107 | rrs = response.get_rrset(response.answer, qname, |
108 | 108 | dns.rdataclass.IN, dns.rdatatype.A) |
109 | 109 | self.assertTrue(rrs is not None) |
116 | 116 | with socket.socket(dns.inet.af_for_address(address), |
117 | 117 | socket.SOCK_STREAM) as s: |
118 | 118 | ll = dns.inet.low_level_address_tuple((address, 53)) |
119 | s.settimeout(2) | |
119 | 120 | s.connect(ll) |
120 | 121 | s.setblocking(0) |
121 | 122 | qname = dns.name.from_text('dns.google.') |
122 | 123 | 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) | |
124 | 125 | rrs = response.get_rrset(response.answer, qname, |
125 | 126 | dns.rdataclass.IN, dns.rdatatype.A) |
126 | 127 | self.assertTrue(rrs is not None) |
132 | 133 | for address in query_addresses: |
133 | 134 | qname = dns.name.from_text('dns.google.') |
134 | 135 | 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) | |
136 | 137 | rrs = response.get_rrset(response.answer, qname, |
137 | 138 | dns.rdataclass.IN, dns.rdatatype.A) |
138 | 139 | self.assertTrue(rrs is not None) |
146 | 147 | with socket.socket(dns.inet.af_for_address(address), |
147 | 148 | socket.SOCK_STREAM) as base_s: |
148 | 149 | ll = dns.inet.low_level_address_tuple((address, 853)) |
150 | base_s.settimeout(2) | |
149 | 151 | base_s.connect(ll) |
150 | 152 | ctx = ssl.create_default_context() |
151 | 153 | with ctx.wrap_socket(base_s, server_hostname='dns.google') as s: |
152 | 154 | s.setblocking(0) |
153 | 155 | qname = dns.name.from_text('dns.google.') |
154 | 156 | 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) | |
156 | 158 | rrs = response.get_rrset(response.answer, qname, |
157 | 159 | dns.rdataclass.IN, dns.rdatatype.A) |
158 | 160 | self.assertTrue(rrs is not None) |
164 | 166 | for address in query_addresses: |
165 | 167 | qname = dns.name.from_text('.') |
166 | 168 | 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) | |
168 | 170 | self.assertTrue(tcp) |
169 | 171 | |
170 | 172 | def testQueryUDPFallbackWithSocket(self): |
174 | 176 | udp_s.setblocking(0) |
175 | 177 | with socket.socket(af, socket.SOCK_STREAM) as tcp_s: |
176 | 178 | ll = dns.inet.low_level_address_tuple((address, 53)) |
179 | tcp_s.settimeout(2) | |
177 | 180 | tcp_s.connect(ll) |
178 | 181 | tcp_s.setblocking(0) |
179 | 182 | qname = dns.name.from_text('.') |
180 | 183 | q = dns.message.make_query(qname, dns.rdatatype.DNSKEY) |
181 | 184 | (_, 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) | |
184 | 188 | self.assertTrue(tcp) |
185 | 189 | |
186 | 190 | def testQueryUDPFallbackNoFallback(self): |
187 | 191 | for address in query_addresses: |
188 | 192 | qname = dns.name.from_text('dns.google.') |
189 | 193 | 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) | |
191 | 195 | self.assertFalse(tcp) |
192 | 196 | |
193 | 197 | def testUDPReceiveQuery(self): |
275 | 279 | |
276 | 280 | |
277 | 281 | axfr_zone = ''' |
278 | $ORIGIN example. | |
279 | 282 | $TTL 300 |
280 | 283 | @ SOA ns1 root 1 7200 900 1209600 86400 |
281 | 284 | @ NS ns1 |
287 | 290 | class AXFRNanoNameserver(Server): |
288 | 291 | |
289 | 292 | 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) | |
291 | 294 | self.origin = self.zone.origin |
292 | 295 | items = [] |
293 | 296 | soa = self.zone.find_rrset(dns.name.empty, dns.rdatatype.SOA) |
380 | 383 | |
381 | 384 | def test_axfr(self): |
382 | 385 | expected = dns.zone.from_text(axfr_zone, origin='example') |
383 | with AXFRNanoNameserver() as ns: | |
386 | with AXFRNanoNameserver(origin='example') as ns: | |
384 | 387 | xfr = dns.query.xfr(ns.tcp_address[0], 'example', |
385 | 388 | port=ns.tcp_address[1]) |
386 | 389 | zone = dns.zone.from_xfr(xfr) |
388 | 391 | |
389 | 392 | def test_axfr_tsig(self): |
390 | 393 | 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: | |
392 | 395 | xfr = dns.query.xfr(ns.tcp_address[0], 'example', |
393 | 396 | port=ns.tcp_address[1], |
394 | 397 | keyring=keyring, keyname='name') |
395 | 398 | zone = dns.zone.from_xfr(xfr) |
396 | 399 | self.assertEqual(zone, expected) |
397 | 400 | |
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 | ||
398 | 410 | def test_axfr_udp(self): |
399 | 411 | def bad(): |
400 | with AXFRNanoNameserver() as ns: | |
412 | with AXFRNanoNameserver(origin='example') as ns: | |
401 | 413 | xfr = dns.query.xfr(ns.udp_address[0], 'example', |
402 | 414 | port=ns.udp_address[1], use_udp=True) |
403 | 415 | l = list(xfr) |
540 | 552 | l.close() |
541 | 553 | r.close() |
542 | 554 | |
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) |
15 | 15 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
16 | 16 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | 17 | |
18 | import binascii | |
19 | 18 | import io |
20 | 19 | import operator |
21 | 20 | import pickle |
30 | 29 | import dns.rdataset |
31 | 30 | import dns.rdatatype |
32 | 31 | 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 | |
33 | 39 | |
34 | 40 | import tests.stxt_module |
35 | 41 | import tests.ttxt_module |
42 | import tests.md_module | |
43 | from tests.util import here | |
36 | 44 | |
37 | 45 | class RdataTestCase(unittest.TestCase): |
38 | 46 | |
89 | 97 | |
90 | 98 | def test_invalid_replace(self): |
91 | 99 | 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): | |
93 | 101 | 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) | |
95 | 118 | |
96 | 119 | def test_to_generic(self): |
97 | 120 | a = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, "1.2.3.4") |
267 | 290 | self.assertEqual(rda, rdb) |
268 | 291 | |
269 | 292 | 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') | |
270 | 298 | # test variable length latitude |
271 | 299 | self.equal_loc('60 9 0.510 N 24 39 0.000 E 10.00m 20m 2000m 20m', |
272 | 300 | '60 9 0.51 N 24 39 0.000 E 10.00m 20m 2000m 20m') |
281 | 309 | '60 9 0.000 N 24 39 0.5 E 10.00m 20m 2000m 20m') |
282 | 310 | self.equal_loc('60 9 0.000 N 24 39 1.000 E 10.00m 20m 2000m 20m', |
283 | 311 | '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) | |
284 | 333 | |
285 | 334 | def test_bad_LOC_text(self): |
286 | 335 | bad_locs = ['60 9 a.000 N 24 39 0.000 E 10.00m 20m 2000m 20m', |
303 | 352 | '60 9 0.000 N 24 39 0.000 E 10.00m 20m 100000000m 20m', |
304 | 353 | '60 9 0.000 N 24 39 0.000 E 10.00m 20m 20m 100000000m', |
305 | 354 | ] |
306 | def bad(text): | |
307 | rd = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.LOC, | |
308 | text) | |
309 | 355 | 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) | |
312 | 358 | |
313 | 359 | def test_bad_LOC_wire(self): |
314 | 360 | bad_locs = [(0, 0, 0, 0x934fd901, 0x80000000, 100), |
323 | 369 | (0, 0, 0x0a, 0x80000000, 0x80000000, 100), |
324 | 370 | ] |
325 | 371 | 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)) | |
332 | 381 | |
333 | 382 | def equal_wks(self, a, b): |
334 | 383 | rda = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.WKS, a) |
340 | 389 | self.equal_wks('10.0.0.1 udp ( domain )', '10.0.0.1 17 ( 53 )') |
341 | 390 | |
342 | 391 | def test_misc_bad_WKS_text(self): |
343 | def bad(): | |
392 | try: | |
344 | 393 | dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.WKS, |
345 | 394 | '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) | |
347 | 414 | |
348 | 415 | def test_bad_GPOS_text(self): |
349 | 416 | bad_gpos = ['"-" "116.8652" "250"', |
364 | 431 | '"0" "180.1" "0"', |
365 | 432 | '"0" "-180.1" "0"', |
366 | 433 | ] |
367 | def bad(text): | |
368 | rd = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.GPOS, | |
369 | text) | |
370 | 434 | 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) | |
373 | 437 | |
374 | 438 | def test_bad_GPOS_wire(self): |
375 | 439 | bad_gpos = [b'\x01', |
400 | 464 | self.assertEqual(repr(opt), '<DNS CLASS4096 OPT rdata: >') |
401 | 465 | |
402 | 466 | def test_opt_short_lengths(self): |
403 | def bad1(): | |
467 | with self.assertRaises(dns.exception.FormError): | |
404 | 468 | 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): | |
408 | 471 | 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) | |
411 | 473 | |
412 | 474 | def test_from_wire_parser(self): |
413 | 475 | wire = bytes.fromhex('01020304') |
414 | 476 | rdata = dns.rdata.from_wire('in', 'a', wire, 0, 4) |
415 | 477 | self.assertEqual(rdata, dns.rdata.from_text('in', 'a', '1.2.3.4')) |
416 | 478 | |
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 | ||
417 | 868 | if __name__ == '__main__': |
418 | 869 | unittest.main() |
80 | 80 | self.assertRaises(ValueError, |
81 | 81 | lambda: dns.rdataset.from_rdata_list(300, [])) |
82 | 82 | |
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 | ||
83 | 161 | if __name__ == '__main__': |
84 | 162 | 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 | ||
0 | 2 | import unittest |
1 | 3 | |
2 | 4 | import dns.flags |
80 | 82 | '. . 1 2 3 4 300'), 300) |
81 | 83 | if nxdomain: |
82 | 84 | 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) | |
83 | 101 | return r |
84 | 102 | |
85 | 103 | def test_next_request_cache_hit(self): |
352 | 370 | self.assertTrue(answer is None) |
353 | 371 | self.assertTrue(done) |
354 | 372 | |
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 | ||
355 | 403 | def test_query_result_nxdomain_cached(self): |
356 | 404 | self.resolver.cache = dns.resolver.Cache() |
357 | 405 | q = dns.message.make_query(self.qname, dns.rdatatype.A) |
15 | 15 | # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | 16 | |
17 | 17 | from io import StringIO |
18 | import select | |
18 | import selectors | |
19 | 19 | import sys |
20 | 20 | import socket |
21 | 21 | import time |
22 | 22 | import unittest |
23 | ||
24 | import pytest | |
23 | 25 | |
24 | 26 | import dns.e164 |
25 | 27 | import dns.message |
27 | 29 | import dns.rdataclass |
28 | 30 | import dns.rdatatype |
29 | 31 | import dns.resolver |
32 | import dns.tsig | |
33 | import dns.tsigkeyring | |
30 | 34 | |
31 | 35 | # Some tests require the internet to be available to run, so let's |
32 | 36 | # skip those if it's not there. |
91 | 95 | options rotate |
92 | 96 | """ |
93 | 97 | |
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 | ||
94 | 109 | message_text = """id 1234 |
95 | 110 | opcode QUERY |
96 | 111 | rcode NOERROR |
97 | 112 | flags QR AA RD |
98 | 113 | ;QUESTION |
99 | 114 | 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 | |
100 | 127 | ;ANSWER |
101 | 128 | example. 1 IN A 10.0.0.1 |
102 | 129 | ;AUTHORITY |
147 | 174 | self.expiration = expiration |
148 | 175 | |
149 | 176 | |
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 | ||
150 | 209 | class BaseResolverTests(unittest.TestCase): |
151 | 210 | |
152 | 211 | def testRead(self): |
165 | 224 | self.assertEqual(r.timeout, 1) |
166 | 225 | self.assertEqual(r.ndots, 2) |
167 | 226 | self.assertEqual(r.edns, 0) |
227 | self.assertEqual(r.payload, dns.message.DEFAULT_EDNS_PAYLOAD) | |
168 | 228 | |
169 | 229 | def testReadOptionsBadTimeouts(self): |
170 | 230 | f = StringIO(bad_timeout_1) |
196 | 256 | with self.assertRaises(dns.resolver.NoResolverConfiguration): |
197 | 257 | r.read_resolv_conf(f) |
198 | 258 | |
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 | ||
199 | 274 | 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): | |
200 | 334 | message = dns.message.from_text(message_text) |
201 | 335 | 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) | |
240 | 340 | |
241 | 341 | def testLRUReplace(self): |
242 | 342 | cache = dns.resolver.LRUCache(4) |
279 | 379 | is None) |
280 | 380 | |
281 | 381 | 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) | |
293 | 394 | |
294 | 395 | def test_cache_flush(self): |
295 | 396 | name1 = dns.name.from_text('name1') |
296 | 397 | name2 = dns.name.from_text('name2') |
398 | name3 = dns.name.from_text('name3') | |
297 | 399 | basic_cache = dns.resolver.Cache() |
298 | 400 | lru_cache = dns.resolver.LRUCache(100) |
299 | 401 | for cache in [basic_cache, lru_cache]: |
301 | 403 | answer2 = FakeAnswer(time.time() + 10) |
302 | 404 | cache.put((name1, dns.rdatatype.A, dns.rdataclass.IN), answer1) |
303 | 405 | 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)) | |
304 | 412 | canswer = cache.get((name1, dns.rdatatype.A, dns.rdataclass.IN)) |
305 | 413 | self.assertTrue(canswer is answer1) |
306 | 414 | canswer = cache.get((name2, dns.rdatatype.A, dns.rdataclass.IN)) |
346 | 454 | self.assertFalse(on_lru_list(cache, key, answer1)) |
347 | 455 | self.assertTrue(on_lru_list(cache, key, answer2)) |
348 | 456 | |
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 | ||
349 | 492 | def testEmptyAnswerSection(self): |
350 | 493 | # TODO: dangling_cname_0_message_text was the only sample message |
351 | 494 | # with an empty answer section. Other than that it doesn't |
372 | 515 | qnames = res._get_qnames_to_try(qname, True) |
373 | 516 | self.assertEqual(qnames, |
374 | 517 | [dns.name.from_text(x) for x in |
375 | ['www.dnspython.org', 'www.dnspython.net']]) | |
518 | ['www.dnspython.org', 'www.dnspython.net', 'www.']]) | |
376 | 519 | qnames = res._get_qnames_to_try(qname, False) |
377 | 520 | self.assertEqual(qnames, |
378 | 521 | [dns.name.from_text('www.')]) |
386 | 529 | qnames = res._get_qnames_to_try(qname, None) |
387 | 530 | self.assertEqual(qnames, |
388 | 531 | [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']]) | |
390 | 553 | |
391 | 554 | def testSearchListsAbsolute(self): |
392 | 555 | res = dns.resolver.Resolver(configure=False) |
398 | 561 | qnames = res._get_qnames_to_try(qname, None) |
399 | 562 | self.assertEqual(qnames, [qname]) |
400 | 563 | |
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 | ||
401 | 595 | @unittest.skipIf(not _network_available, "Internet not reachable") |
402 | 596 | class LiveResolverTests(unittest.TestCase): |
403 | 597 | def testZoneForName1(self): |
413 | 607 | self.assertEqual(zname, ezname) |
414 | 608 | |
415 | 609 | def testZoneForName3(self): |
416 | name = dns.name.from_text('dnspython.org.') | |
417 | 610 | ezname = dns.name.from_text('dnspython.org.') |
418 | zname = dns.resolver.zone_for_name(name) | |
611 | zname = dns.resolver.zone_for_name('dnspython.org.') | |
419 | 612 | self.assertEqual(zname, ezname) |
420 | 613 | |
421 | 614 | def testZoneForName4(self): |
461 | 654 | qtype = dns.rdatatype.from_text('A') |
462 | 655 | def bad(): |
463 | 656 | 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) | |
465 | 663 | |
466 | 664 | def testResolveCacheHit(self): |
467 | 665 | res = dns.resolver.Resolver(configure=False) |
474 | 672 | answer2 = res.resolve('dns.google.', 'A') |
475 | 673 | self.assertIs(answer2, answer1) |
476 | 674 | |
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 | ||
477 | 689 | class PollingMonkeyPatchMixin(object): |
478 | 690 | 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()) | |
481 | 693 | |
482 | 694 | unittest.TestCase.setUp(self) |
483 | 695 | |
484 | 696 | def tearDown(self): |
485 | dns.query._set_polling_backend(self.__native_polling_backend) | |
697 | dns.query._set_selector_class(self.__native_selector_class) | |
486 | 698 | |
487 | 699 | unittest.TestCase.tearDown(self) |
488 | 700 | |
489 | 701 | |
490 | 702 | 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'): | |
496 | 708 | 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 | |
499 | 711 | |
500 | 712 | |
501 | 713 | class NXDOMAINExceptionTestCase(unittest.TestCase): |
701 | 913 | with NaptrNanoNameserver() as na: |
702 | 914 | res = dns.resolver.Resolver(configure=False) |
703 | 915 | res.port = na.udp_address[1] |
704 | res.nameservers = [ na.udp_address[0] ] | |
916 | res.nameservers = [na.udp_address[0]] | |
705 | 917 | answer = dns.e164.query('1650551212', ['e164.arpa'], res) |
706 | 918 | self.assertEqual(answer[0].order, 0) |
707 | 919 | self.assertEqual(answer[0].preference, 0) |
709 | 921 | self.assertEqual(answer[0].service, b'') |
710 | 922 | self.assertEqual(answer[0].regexp, b'') |
711 | 923 | 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' |
15 | 15 | socket.gethostbyname('dnspython.org') |
16 | 16 | except socket.gaierror: |
17 | 17 | _network_available = False |
18 | ||
18 | 19 | |
19 | 20 | @unittest.skipIf(not _network_available, "Internet not reachable") |
20 | 21 | class OverrideSystemResolverTestCase(unittest.TestCase): |
116 | 117 | self.assertTrue(False) # should not happen! |
117 | 118 | except socket.gaierror as e: |
118 | 119 | 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 | ||
119 | 144 | |
120 | 145 | # Give up on testing this for now as all of the names I've considered |
121 | 146 | # using for testing are part of CDNs and there is deep magic in |
144 | 169 | b = socket.gethostbyaddr('2001:4860:4860::8888') |
145 | 170 | self.assertEqual(a[0], b[0]) |
146 | 171 | 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) |
78 | 78 | self.assertFalse(r1 is r2) |
79 | 79 | self.assertTrue(r1 == r2) |
80 | 80 | |
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): | |
82 | 119 | r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a', |
83 | 120 | ['10.0.0.1', '10.0.0.2']) |
84 | 121 | self.assertTrue(r1.match(r1.name, dns.rdataclass.IN, |
85 | 122 | dns.rdatatype.A, dns.rdatatype.NONE)) |
86 | 123 | |
87 | def testMatch2(self): | |
124 | def testMatchCompatibilityWithRdatasetMatch(self): | |
88 | 125 | r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a', |
89 | 126 | ['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)) | |
117 | 129 | |
118 | 130 | def testToRdataset(self): |
119 | 131 | r1 = dns.rrset.from_text_list('foo', 30, 'in', 'a', |
63 | 63 | def test_sub(self): |
64 | 64 | self.assertEqual(S8(0) - S8(1), S8(255)) |
65 | 65 | |
66 | def test_sub(self): | |
67 | self.assertEqual(S8(0) - S8(1), S8(255)) | |
68 | ||
69 | 66 | def test_addition_bounds(self): |
70 | 67 | self.assertRaises(ValueError, lambda: S8(0) + 128) |
71 | 68 | self.assertRaises(ValueError, lambda: S8(0) - 128) |
99 | 96 | self.assertRaises(ValueError, bad2) |
100 | 97 | |
101 | 98 | def test_uncomparable(self): |
99 | self.assertFalse(S8(0) == S2(0)) | |
102 | 100 | self.assertFalse(S8(0) == 'a') |
103 | 101 | self.assertTrue(S8(0) != 'a') |
104 | 102 | self.assertRaises(TypeError, lambda: S8(0) < 'a') |
112 | 110 | |
113 | 111 | def test_repr(self): |
114 | 112 | 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') |
50 | 50 | 'foo\\010bar')) |
51 | 51 | |
52 | 52 | def testQuotedString5(self): |
53 | def bad(): | |
53 | with self.assertRaises(dns.exception.UnexpectedEnd): | |
54 | 54 | tok = dns.tokenizer.Tokenizer(r'"foo') |
55 | 55 | tok.get() |
56 | self.assertRaises(dns.exception.UnexpectedEnd, bad) | |
57 | 56 | |
58 | 57 | def testQuotedString6(self): |
59 | def bad(): | |
58 | with self.assertRaises(dns.exception.SyntaxError): | |
60 | 59 | tok = dns.tokenizer.Tokenizer(r'"foo\01') |
61 | 60 | tok.get() |
62 | self.assertRaises(dns.exception.SyntaxError, bad) | |
63 | 61 | |
64 | 62 | def testQuotedString7(self): |
65 | def bad(): | |
63 | with self.assertRaises(dns.exception.SyntaxError): | |
66 | 64 | tok = dns.tokenizer.Tokenizer('"foo\nbar"') |
67 | 65 | tok.get() |
68 | self.assertRaises(dns.exception.SyntaxError, bad) | |
69 | 66 | |
70 | 67 | def testEmpty1(self): |
71 | 68 | tok = dns.tokenizer.Tokenizer('') |
125 | 122 | self.assertEqual(tokens, [Token(dns.tokenizer.IDENTIFIER, 'foo'), |
126 | 123 | Token(dns.tokenizer.IDENTIFIER, 'bar'), |
127 | 124 | Token(dns.tokenizer.EOL, '\n')]) |
125 | ||
128 | 126 | def testMultiline3(self): |
129 | def bad(): | |
127 | with self.assertRaises(dns.exception.SyntaxError): | |
130 | 128 | tok = dns.tokenizer.Tokenizer('foo)') |
131 | 129 | list(iter(tok)) |
132 | self.assertRaises(dns.exception.SyntaxError, bad) | |
133 | 130 | |
134 | 131 | def testMultiline4(self): |
135 | def bad(): | |
132 | with self.assertRaises(dns.exception.SyntaxError): | |
136 | 133 | tok = dns.tokenizer.Tokenizer('((foo)') |
137 | 134 | list(iter(tok)) |
138 | self.assertRaises(dns.exception.SyntaxError, bad) | |
139 | 135 | |
140 | 136 | def testUnget1(self): |
141 | 137 | tok = dns.tokenizer.Tokenizer('foo') |
147 | 143 | self.assertEqual(t1.value, 'foo') |
148 | 144 | |
149 | 145 | def testUnget2(self): |
150 | def bad(): | |
146 | with self.assertRaises(dns.tokenizer.UngetBufferFull): | |
151 | 147 | tok = dns.tokenizer.Tokenizer('foo') |
152 | 148 | t1 = tok.get() |
153 | 149 | tok.unget(t1) |
154 | 150 | tok.unget(t1) |
155 | self.assertRaises(dns.tokenizer.UngetBufferFull, bad) | |
156 | 151 | |
157 | 152 | def testGetEOL1(self): |
158 | 153 | tok = dns.tokenizer.Tokenizer('\n') |
204 | 199 | tok = dns.tokenizer.Tokenizer('1234') |
205 | 200 | v = tok.get_int() |
206 | 201 | self.assertEqual(v, 1234) |
207 | def bad1(): | |
202 | with self.assertRaises(dns.exception.SyntaxError): | |
208 | 203 | 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): | |
212 | 206 | 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): | |
216 | 212 | 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): | |
220 | 215 | 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): | |
224 | 218 | tok = dns.tokenizer.Tokenizer('256') |
225 | v = tok.get_uint8() | |
226 | self.assertRaises(dns.exception.SyntaxError, bad5) | |
219 | tok.get_uint8() | |
227 | 220 | # Even though it is badly named get_int(), it's really get_unit! |
228 | def bad6(): | |
221 | with self.assertRaises(dns.exception.SyntaxError): | |
229 | 222 | 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) | |
232 | 231 | |
233 | 232 | def testGetString(self): |
234 | 233 | tok = dns.tokenizer.Tokenizer('foo') |
240 | 239 | tok = dns.tokenizer.Tokenizer('abcdefghij') |
241 | 240 | v = tok.get_string(max_length=10) |
242 | 241 | self.assertEqual(v, 'abcdefghij') |
243 | def bad(): | |
242 | with self.assertRaises(dns.exception.SyntaxError): | |
244 | 243 | 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() | |
247 | 248 | |
248 | 249 | def testMultiLineWithComment(self): |
249 | 250 | tok = dns.tokenizer.Tokenizer('( ; abc\n)') |
262 | 263 | self.assertTrue(t.is_eof()) |
263 | 264 | |
264 | 265 | def testMultiLineWithEOFAfterComment(self): |
265 | def bad(): | |
266 | with self.assertRaises(dns.exception.SyntaxError): | |
266 | 267 | tok = dns.tokenizer.Tokenizer('( ; abc') |
267 | 268 | tok.get_eol() |
268 | self.assertRaises(dns.exception.SyntaxError, bad) | |
269 | 269 | |
270 | 270 | def testEscapeUnexpectedEnd(self): |
271 | def bad(): | |
271 | with self.assertRaises(dns.exception.UnexpectedEnd): | |
272 | 272 | tok = dns.tokenizer.Tokenizer('\\') |
273 | 273 | 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() | |
275 | 282 | |
276 | 283 | def testGetUngetRegetComment(self): |
277 | 284 | tok = dns.tokenizer.Tokenizer(';comment') |
281 | 288 | self.assertEqual(t1, t2) |
282 | 289 | |
283 | 290 | def testBadAsName(self): |
284 | def bad(): | |
291 | with self.assertRaises(dns.exception.SyntaxError): | |
285 | 292 | tok = dns.tokenizer.Tokenizer('"not an identifier"') |
286 | 293 | t = tok.get() |
287 | 294 | tok.as_name(t) |
288 | self.assertRaises(dns.exception.SyntaxError, bad) | |
289 | 295 | |
290 | 296 | def testBadGetTTL(self): |
291 | def bad(): | |
297 | with self.assertRaises(dns.exception.SyntaxError): | |
292 | 298 | 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() | |
295 | 305 | |
296 | 306 | 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) | |
329 | 359 | |
330 | 360 | if __name__ == '__main__': |
331 | 361 | 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 |
0 | 0 | # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license |
1 | 1 | |
2 | import hashlib | |
3 | 2 | import unittest |
3 | from unittest.mock import Mock | |
4 | 4 | import time |
5 | import base64 | |
5 | 6 | |
6 | 7 | import dns.rcode |
7 | 8 | import dns.tsig |
8 | 9 | import dns.tsigkeyring |
9 | 10 | import dns.message |
11 | import dns.rdtypes.ANY.TKEY | |
10 | 12 | |
11 | 13 | keyring = dns.tsigkeyring.from_text( |
12 | 14 | { |
16 | 18 | |
17 | 19 | keyname = dns.name.from_text('keyname') |
18 | 20 | |
21 | ||
19 | 22 | class TSIGTestCase(unittest.TestCase): |
20 | 23 | |
21 | 24 | def test_get_context(self): |
29 | 32 | with self.assertRaises(NotImplementedError): |
30 | 33 | dns.tsig.get_context(bogus) |
31 | 34 | |
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 | ||
32 | 172 | def test_sign_and_validate(self): |
33 | 173 | m = dns.message.make_query('example', 'a') |
34 | 174 | m.use_tsig(keyring, keyname) |
36 | 176 | # not raising is passing |
37 | 177 | dns.message.from_wire(w, keyring) |
38 | 178 | |
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 | ||
39 | 191 | def test_sign_and_validate_with_other_data(self): |
40 | 192 | m = dns.message.make_query('example', 'a') |
41 | other = b'other data' | |
42 | 193 | m.use_tsig(keyring, keyname, other_data=b'other') |
43 | 194 | w = m.to_wire() |
44 | 195 | # not raising is passing |
45 | 196 | 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) | |
46 | 207 | |
47 | 208 | def make_message_pair(self, qname='example', rdtype='A', tsig_error=0): |
48 | 209 | q = dns.message.make_query(qname, rdtype) |
64 | 225 | def bad(): |
65 | 226 | dns.message.from_wire(w, keyring=keyring, request_mac=q.mac) |
66 | 227 | 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.'>") |
21 | 21 | def test_bind_style_no_unit(self): |
22 | 22 | with self.assertRaises(dns.ttl.BadTTL): |
23 | 23 | 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 | |
0 | 1 | |
1 | 2 | import unittest |
2 | 3 |
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()) |
31 | 31 | import dns.rdataclass |
32 | 32 | import dns.rdatatype |
33 | 33 | import dns.rrset |
34 | import dns.versioned | |
34 | 35 | import dns.zone |
35 | 36 | import dns.node |
36 | 37 | |
37 | def here(filename): | |
38 | return os.path.join(os.path.dirname(__file__), filename) | |
38 | from tests.util import here | |
39 | 39 | |
40 | 40 | example_text = """$TTL 3600 |
41 | 41 | $ORIGIN example. |
172 | 172 | @ soa foo bar 1 2 3 4 5 |
173 | 173 | @ 300 ns ns1 |
174 | 174 | @ 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 | |
175 | 192 | """ |
176 | 193 | |
177 | 194 | _keep_output = True |
216 | 233 | |
217 | 234 | class ZoneTestCase(unittest.TestCase): |
218 | 235 | |
219 | def testFromFile1(self): # type: () -> None | |
236 | def testFromFile1(self): | |
220 | 237 | z = dns.zone.from_file(here('example'), 'example') |
221 | 238 | ok = False |
222 | 239 | try: |
229 | 246 | os.unlink(here('example1.out')) |
230 | 247 | self.assertTrue(ok) |
231 | 248 | |
232 | def testFromFile2(self): # type: () -> None | |
249 | def testFromFile2(self): | |
233 | 250 | z = dns.zone.from_file(here('example'), 'example', relativize=False) |
234 | 251 | ok = False |
235 | 252 | try: |
242 | 259 | os.unlink(here('example2.out')) |
243 | 260 | self.assertTrue(ok) |
244 | 261 | |
245 | def testToFileTextualStream(self): # type: () -> None | |
262 | def testToFileTextualStream(self): | |
246 | 263 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
247 | 264 | f = StringIO() |
248 | 265 | z.to_file(f) |
250 | 267 | f.close() |
251 | 268 | self.assertEqual(out, example_text_output) |
252 | 269 | |
253 | def testToFileBinaryStream(self): # type: () -> None | |
270 | def testToFileBinaryStream(self): | |
254 | 271 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
255 | 272 | f = BytesIO() |
256 | 273 | z.to_file(f, nl=b'\n') |
258 | 275 | f.close() |
259 | 276 | self.assertEqual(out, example_text_output.encode()) |
260 | 277 | |
261 | def testToFileTextual(self): # type: () -> None | |
278 | def testToFileTextual(self): | |
262 | 279 | z = dns.zone.from_file(here('example'), 'example') |
263 | 280 | try: |
264 | 281 | f = open(here('example3-textual.out'), 'w') |
272 | 289 | os.unlink(here('example3-textual.out')) |
273 | 290 | self.assertTrue(ok) |
274 | 291 | |
275 | def testToFileBinary(self): # type: () -> None | |
292 | def testToFileBinary(self): | |
276 | 293 | z = dns.zone.from_file(here('example'), 'example') |
277 | 294 | try: |
278 | 295 | f = open(here('example3-binary.out'), 'wb') |
286 | 303 | os.unlink(here('example3-binary.out')) |
287 | 304 | self.assertTrue(ok) |
288 | 305 | |
289 | def testToFileFilename(self): # type: () -> None | |
306 | def testToFileFilename(self): | |
290 | 307 | z = dns.zone.from_file(here('example'), 'example') |
291 | 308 | try: |
292 | 309 | z.to_file(here('example3-filename.out')) |
298 | 315 | os.unlink(here('example3-filename.out')) |
299 | 316 | self.assertTrue(ok) |
300 | 317 | |
301 | def testToText(self): # type: () -> None | |
318 | def testToText(self): | |
302 | 319 | z = dns.zone.from_file(here('example'), 'example') |
303 | 320 | ok = False |
304 | 321 | try: |
314 | 331 | os.unlink(here('example3.out')) |
315 | 332 | self.assertTrue(ok) |
316 | 333 | |
317 | def testFromText(self): # type: () -> None | |
334 | def testFromText(self): | |
318 | 335 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
319 | 336 | f = StringIO() |
320 | 337 | names = list(z.nodes.keys()) |
324 | 341 | f.write('\n') |
325 | 342 | self.assertEqual(f.getvalue(), example_text_output) |
326 | 343 | |
327 | def testTorture1(self): # type: () -> None | |
344 | def testTorture1(self): | |
328 | 345 | # |
329 | 346 | # Read a zone containing all our supported RR types, and |
330 | 347 | # for each RR in the zone, convert the rdata into wire format |
345 | 362 | origin=o) |
346 | 363 | self.assertEqual(rd, rd2) |
347 | 364 | |
348 | def testEqual(self): # type: () -> None | |
365 | def testEqual(self): | |
349 | 366 | z1 = dns.zone.from_text(example_text, 'example.', relativize=True) |
350 | 367 | z2 = dns.zone.from_text(example_text_output, 'example.', |
351 | 368 | relativize=True) |
352 | 369 | self.assertEqual(z1, z2) |
353 | 370 | |
354 | def testNotEqual1(self): # type: () -> None | |
371 | def testNotEqual1(self): | |
355 | 372 | z1 = dns.zone.from_text(example_text, 'example.', relativize=True) |
356 | 373 | z2 = dns.zone.from_text(something_quite_similar, 'example.', |
357 | 374 | relativize=True) |
358 | 375 | self.assertNotEqual(z1, z2) |
359 | 376 | |
360 | def testNotEqual2(self): # type: () -> None | |
377 | def testNotEqual2(self): | |
361 | 378 | z1 = dns.zone.from_text(example_text, 'example.', relativize=True) |
362 | 379 | z2 = dns.zone.from_text(something_different, 'example.', |
363 | 380 | relativize=True) |
364 | 381 | self.assertNotEqual(z1, z2) |
365 | 382 | |
366 | def testNotEqual3(self): # type: () -> None | |
383 | def testNotEqual3(self): | |
367 | 384 | z1 = dns.zone.from_text(example_text, 'example.', relativize=True) |
368 | 385 | z2 = dns.zone.from_text(something_different, 'example2.', |
369 | 386 | relativize=True) |
370 | 387 | self.assertNotEqual(z1, z2) |
371 | 388 | |
372 | def testFindRdataset1(self): # type: () -> None | |
389 | def testFindRdataset1(self): | |
373 | 390 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
374 | 391 | rds = z.find_rdataset('@', 'soa') |
375 | 392 | exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5') |
376 | 393 | self.assertEqual(rds, exrds) |
377 | 394 | |
378 | def testFindRdataset2(self): # type: () -> None | |
379 | def bad(): # type: () -> None | |
395 | def testFindRdataset2(self): | |
396 | def bad(): | |
380 | 397 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
381 | 398 | z.find_rdataset('@', 'loc') |
382 | 399 | self.assertRaises(KeyError, bad) |
383 | 400 | |
384 | def testFindRRset1(self): # type: () -> None | |
401 | def testFindRRset1(self): | |
385 | 402 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
386 | 403 | rrs = z.find_rrset('@', 'soa') |
387 | 404 | exrrs = dns.rrset.from_text('@', 300, 'IN', 'SOA', 'foo bar 1 2 3 4 5') |
388 | 405 | self.assertEqual(rrs, exrrs) |
389 | 406 | |
390 | def testFindRRset2(self): # type: () -> None | |
391 | def bad(): # type: () -> None | |
407 | def testFindRRset2(self): | |
408 | def bad(): | |
392 | 409 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
393 | 410 | z.find_rrset('@', 'loc') |
394 | 411 | self.assertRaises(KeyError, bad) |
395 | 412 | |
396 | def testGetRdataset1(self): # type: () -> None | |
413 | def testGetRdataset1(self): | |
397 | 414 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
398 | 415 | rds = z.get_rdataset('@', 'soa') |
399 | 416 | exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5') |
400 | 417 | self.assertEqual(rds, exrds) |
401 | 418 | |
402 | def testGetRdataset2(self): # type: () -> None | |
419 | def testGetRdataset2(self): | |
403 | 420 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
404 | 421 | rds = z.get_rdataset('@', 'loc') |
405 | 422 | self.assertTrue(rds is None) |
406 | 423 | |
407 | def testGetRRset1(self): # type: () -> None | |
424 | def testGetRRset1(self): | |
408 | 425 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
409 | 426 | rrs = z.get_rrset('@', 'soa') |
410 | 427 | exrrs = dns.rrset.from_text('@', 300, 'IN', 'SOA', 'foo bar 1 2 3 4 5') |
411 | 428 | self.assertEqual(rrs, exrrs) |
412 | 429 | |
413 | def testGetRRset2(self): # type: () -> None | |
430 | def testGetRRset2(self): | |
414 | 431 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
415 | 432 | rrs = z.get_rrset('@', 'loc') |
416 | 433 | self.assertTrue(rrs is None) |
417 | 434 | |
418 | def testReplaceRdataset1(self): # type: () -> None | |
435 | def testReplaceRdataset1(self): | |
419 | 436 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
420 | 437 | rdataset = dns.rdataset.from_text('in', 'ns', 300, 'ns3', 'ns4') |
421 | 438 | z.replace_rdataset('@', rdataset) |
422 | 439 | rds = z.get_rdataset('@', 'ns') |
423 | 440 | self.assertTrue(rds is rdataset) |
424 | 441 | |
425 | def testReplaceRdataset2(self): # type: () -> None | |
442 | def testReplaceRdataset2(self): | |
426 | 443 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
427 | 444 | rdataset = dns.rdataset.from_text('in', 'txt', 300, '"foo"') |
428 | 445 | z.replace_rdataset('@', rdataset) |
429 | 446 | rds = z.get_rdataset('@', 'txt') |
430 | 447 | self.assertTrue(rds is rdataset) |
431 | 448 | |
432 | def testDeleteRdataset1(self): # type: () -> None | |
449 | def testDeleteRdataset1(self): | |
433 | 450 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
434 | 451 | z.delete_rdataset('@', 'ns') |
435 | 452 | rds = z.get_rdataset('@', 'ns') |
436 | 453 | self.assertTrue(rds is None) |
437 | 454 | |
438 | def testDeleteRdataset2(self): # type: () -> None | |
455 | def testDeleteRdataset2(self): | |
439 | 456 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
440 | 457 | z.delete_rdataset('ns1', 'a') |
441 | 458 | node = z.get_node('ns1') |
442 | 459 | self.assertTrue(node is None) |
443 | 460 | |
444 | def testNodeFindRdataset1(self): # type: () -> None | |
461 | def testNodeFindRdataset1(self): | |
445 | 462 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
446 | 463 | node = z['@'] |
447 | 464 | rds = node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) |
448 | 465 | exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5') |
449 | 466 | self.assertEqual(rds, exrds) |
450 | 467 | |
451 | def testNodeFindRdataset2(self): # type: () -> None | |
452 | def bad(): # type: () -> None | |
468 | def testNodeFindRdataset2(self): | |
469 | def bad(): | |
453 | 470 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
454 | 471 | node = z['@'] |
455 | 472 | node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) |
456 | 473 | self.assertRaises(KeyError, bad) |
457 | 474 | |
458 | def testNodeGetRdataset1(self): # type: () -> None | |
475 | def testNodeGetRdataset1(self): | |
459 | 476 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
460 | 477 | node = z['@'] |
461 | 478 | rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) |
462 | 479 | exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5') |
463 | 480 | self.assertEqual(rds, exrds) |
464 | 481 | |
465 | def testNodeGetRdataset2(self): # type: () -> None | |
482 | def testNodeGetRdataset2(self): | |
466 | 483 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
467 | 484 | node = z['@'] |
468 | 485 | rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) |
469 | 486 | self.assertTrue(rds is None) |
470 | 487 | |
471 | def testNodeDeleteRdataset1(self): # type: () -> None | |
488 | def testNodeDeleteRdataset1(self): | |
472 | 489 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
473 | 490 | node = z['@'] |
474 | 491 | node.delete_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) |
475 | 492 | rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) |
476 | 493 | self.assertTrue(rds is None) |
477 | 494 | |
478 | def testNodeDeleteRdataset2(self): # type: () -> None | |
495 | def testNodeDeleteRdataset2(self): | |
479 | 496 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
480 | 497 | node = z['@'] |
481 | 498 | node.delete_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) |
482 | 499 | rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) |
483 | 500 | self.assertTrue(rds is None) |
484 | 501 | |
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): | |
486 | 510 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
487 | 511 | ns = [n for n, r in z.iterate_rdatasets('A')] |
488 | 512 | ns.sort() |
489 | 513 | self.assertEqual(ns, [dns.name.from_text('ns1', None), |
490 | 514 | dns.name.from_text('ns2', None)]) |
491 | 515 | |
492 | def testIterateAllRdatasets(self): # type: () -> None | |
516 | def testIterateAllRdatasets(self): | |
493 | 517 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
494 | 518 | ns = [n for n, r in z.iterate_rdatasets()] |
495 | 519 | ns.sort() |
499 | 523 | dns.name.from_text('ns1', None), |
500 | 524 | dns.name.from_text('ns2', None)]) |
501 | 525 | |
502 | def testIterateRdatas(self): # type: () -> None | |
526 | def testIterateRdatas(self): | |
503 | 527 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
504 | 528 | l = list(z.iterate_rdatas('A')) |
505 | 529 | l.sort() |
513 | 537 | '10.0.0.2'))] |
514 | 538 | self.assertEqual(l, exl) |
515 | 539 | |
516 | def testIterateAllRdatas(self): # type: () -> None | |
540 | def testIterateAllRdatas(self): | |
517 | 541 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
518 | 542 | l = list(z.iterate_rdatas()) |
519 | 543 | l.sort(key=_rdata_sort) |
555 | 579 | self.assertEqual(z.get('foo'), n) |
556 | 580 | del z['foo'] |
557 | 581 | self.assertEqual(z.get('foo'), None) |
558 | def bad1(): | |
582 | with self.assertRaises(KeyError): | |
559 | 583 | z[123] = n |
560 | self.assertRaises(KeyError, bad1) | |
561 | def bad2(): | |
584 | with self.assertRaises(KeyError): | |
562 | 585 | z['foo.'] = n |
563 | self.assertRaises(KeyError, bad2) | |
564 | def bad3(): | |
586 | with self.assertRaises(KeyError): | |
565 | 587 | bn = z.find_node('bar') |
566 | self.assertRaises(KeyError, bad3) | |
567 | 588 | bn = z.find_node('bar', True) |
568 | 589 | 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') | |
569 | 593 | |
570 | 594 | def testBadReplacement(self): |
571 | 595 | z = dns.zone.from_text(example_text, 'example.', relativize=True) |
574 | 598 | z.replace_rdataset('foo', rds) |
575 | 599 | self.assertRaises(ValueError, bad) |
576 | 600 | |
577 | def testTTLs(self): # type: () -> None | |
601 | def testTTLs(self): | |
578 | 602 | z = dns.zone.from_text(ttl_example_text, 'example.', relativize=True) |
579 | 603 | n = z['@'] # type: dns.node.Node |
580 | 604 | rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA)) |
586 | 610 | rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A)) |
587 | 611 | self.assertEqual(rds.ttl, 694861) |
588 | 612 | |
589 | def testTTLFromSOA(self): # type: () -> None | |
613 | def testTTLFromSOA(self): | |
590 | 614 | z = dns.zone.from_text(ttl_from_soa_text, 'example.', relativize=True) |
591 | 615 | n = z['@'] |
592 | 616 | rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA)) |
599 | 623 | rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A)) |
600 | 624 | self.assertEqual(rds.ttl, soa_rd.minimum) |
601 | 625 | |
602 | def testTTLFromLast(self): # type: () -> None | |
626 | def testTTLFromLast(self): | |
603 | 627 | z = dns.zone.from_text(ttl_from_last_text, 'example.', check_origin=False) |
604 | 628 | n = z['@'] |
605 | 629 | rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.NS)) |
611 | 635 | rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A)) |
612 | 636 | self.assertEqual(rds.ttl, 694861) |
613 | 637 | |
614 | def testNoTTL(self): # type: () -> None | |
615 | def bad(): # type: () -> None | |
638 | def testNoTTL(self): | |
639 | def bad(): | |
616 | 640 | dns.zone.from_text(no_ttl_text, 'example.', check_origin=False) |
617 | 641 | self.assertRaises(dns.exception.SyntaxError, bad) |
618 | 642 | |
619 | def testNoSOA(self): # type: () -> None | |
620 | def bad(): # type: () -> None | |
643 | def testNoSOA(self): | |
644 | def bad(): | |
621 | 645 | dns.zone.from_text(no_soa_text, 'example.', relativize=True) |
622 | 646 | self.assertRaises(dns.zone.NoSOA, bad) |
623 | 647 | |
624 | def testNoNS(self): # type: () -> None | |
625 | def bad(): # type: () -> None | |
648 | def testNoNS(self): | |
649 | def bad(): | |
626 | 650 | dns.zone.from_text(no_ns_text, 'example.', relativize=True) |
627 | 651 | self.assertRaises(dns.zone.NoNS, bad) |
628 | 652 | |
629 | def testInclude(self): # type: () -> None | |
653 | def testInclude(self): | |
630 | 654 | z1 = dns.zone.from_text(include_text, 'example.', relativize=True, |
631 | 655 | allow_include=True) |
632 | 656 | z2 = dns.zone.from_file(here('example'), 'example.', relativize=True) |
633 | 657 | self.assertEqual(z1, z2) |
634 | 658 | |
635 | def testBadDirective(self): # type: () -> None | |
636 | def bad(): # type: () -> None | |
659 | def testBadDirective(self): | |
660 | def bad(): | |
637 | 661 | dns.zone.from_text(bad_directive_text, 'example.', relativize=True) |
638 | 662 | self.assertRaises(dns.exception.SyntaxError, bad) |
639 | 663 | |
640 | def testFirstRRStartsWithWhitespace(self): # type: () -> None | |
664 | def testFirstRRStartsWithWhitespace(self): | |
641 | 665 | # no name is specified, so default to the initial origin |
642 | 666 | z = dns.zone.from_text(' 300 IN A 10.0.0.1', origin='example.', |
643 | 667 | check_origin=False) |
645 | 669 | rds = cast(dns.rdataset.Rdataset, n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A)) |
646 | 670 | self.assertEqual(rds.ttl, 300) |
647 | 671 | |
648 | def testZoneOrigin(self): # type: () -> None | |
672 | def testZoneOrigin(self): | |
649 | 673 | z = dns.zone.Zone('example.') |
650 | 674 | self.assertEqual(z.origin, dns.name.from_text('example.')) |
651 | def bad1(): # type: () -> None | |
675 | def bad1(): | |
652 | 676 | o = dns.name.from_text('example', None) |
653 | 677 | dns.zone.Zone(o) |
654 | 678 | self.assertRaises(ValueError, bad1) |
655 | def bad2(): # type: () -> None | |
679 | def bad2(): | |
656 | 680 | dns.zone.Zone(cast(str, 1.0)) |
657 | 681 | self.assertRaises(ValueError, bad2) |
658 | 682 | |
659 | def testZoneOriginNone(self): # type: () -> None | |
683 | def testZoneOriginNone(self): | |
660 | 684 | dns.zone.Zone(cast(str, None)) |
661 | 685 | |
662 | def testZoneFromXFR(self): # type: () -> None | |
686 | def testZoneFromXFR(self): | |
663 | 687 | z1_abs = dns.zone.from_text(example_text, 'example.', relativize=False) |
664 | 688 | z2_abs = dns.zone.from_xfr(make_xfr(z1_abs), relativize=False) |
665 | 689 | self.assertEqual(z1_abs, z2_abs) |
745 | 769 | self.assertEqual(z._validate_name('foo.bar.example.'), |
746 | 770 | dns.name.from_text('foo.bar', None)) |
747 | 771 | |
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 | ||
748 | 884 | if __name__ == '__main__': |
749 | 885 | 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}') |