cyares 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cyares-0.1.0/LICENSE +21 -0
- cyares-0.1.0/PKG-INFO +77 -0
- cyares-0.1.0/README.md +65 -0
- cyares-0.1.0/cyares/__init__.py +2 -0
- cyares-0.1.0/cyares/aio.py +426 -0
- cyares-0.1.0/cyares/callbacks.c +13488 -0
- cyares-0.1.0/cyares/channel.c +21769 -0
- cyares-0.1.0/cyares/channel.pyi +324 -0
- cyares-0.1.0/cyares/exception.c +10141 -0
- cyares-0.1.0/cyares/exception.pyi +11 -0
- cyares-0.1.0/cyares/inc/cares_flag_check.h +64 -0
- cyares-0.1.0/cyares/inc/cares_headers.h +46 -0
- cyares-0.1.0/cyares/inc/cyares_err_lookup.h +74 -0
- cyares-0.1.0/cyares/inc/cyares_utils.h +177 -0
- cyares-0.1.0/cyares/py.typed +0 -0
- cyares-0.1.0/cyares/resulttypes.c +37446 -0
- cyares-0.1.0/cyares/resulttypes.pyi +90 -0
- cyares-0.1.0/cyares/socket_handle.c +10677 -0
- cyares-0.1.0/cyares.egg-info/PKG-INFO +77 -0
- cyares-0.1.0/cyares.egg-info/SOURCES.txt +27 -0
- cyares-0.1.0/cyares.egg-info/dependency_links.txt +1 -0
- cyares-0.1.0/cyares.egg-info/not-zip-safe +1 -0
- cyares-0.1.0/cyares.egg-info/requires.txt +3 -0
- cyares-0.1.0/cyares.egg-info/top_level.txt +1 -0
- cyares-0.1.0/pyproject.toml +29 -0
- cyares-0.1.0/setup.cfg +4 -0
- cyares-0.1.0/setup.py +309 -0
- cyares-0.1.0/tests/test_aio_cyares.py +249 -0
- cyares-0.1.0/tests/test_channel.py +198 -0
cyares-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vizonex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
cyares-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cyares
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Cython Version of c-ares
|
|
5
|
+
Author-email: Vizonex <VizonexBusiness@gmail.com>
|
|
6
|
+
Project-URL: repository, https://github.com/Vizonex/cyares.git
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: typing-extensions; python_version < "3.11"
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
An Upgraded version of __pycares__ with faster and safer features.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Small Examples of How to Use
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from cyares import Channel
|
|
23
|
+
|
|
24
|
+
# NOTE: Dns Queries contain a Future object
|
|
25
|
+
# This is a little bit different from pycares
|
|
26
|
+
# Handles were used to prevent new or unwanted vulnerabilities along
|
|
27
|
+
# with easier control given to the user...
|
|
28
|
+
# The Future object comes from the python concurrent.futures standard library
|
|
29
|
+
|
|
30
|
+
def main():
|
|
31
|
+
with Channel(servers=["8.8.8.8", "8.8.4.4"], event_thread=True) as channel:
|
|
32
|
+
data = channel.query("google.com", "A").result()
|
|
33
|
+
print(data)
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## In Asyncio
|
|
40
|
+
There's a module for an aiodns version if aiodns were using cyares.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import winloop # or uvloop (asyncio will also work)
|
|
44
|
+
# I'll add trio support in a future update as well...
|
|
45
|
+
from cyares.aio import DNSResolver
|
|
46
|
+
|
|
47
|
+
async def test():
|
|
48
|
+
async with DNSResolver(["8.8.8.8", "8.8.4.4"]) as dns:
|
|
49
|
+
data = await dns.query("google.com", "A")
|
|
50
|
+
print(data)
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
winloop.run(test())
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## Story
|
|
59
|
+
As Someone who as recently started to contribute to projects such as __aiohttp__ and a few of it's smaller libraries. The asynchronous dns resolver __aiodns__ that __aiohttp__ can optionally use, felt like the oddest one in the group. With __aiodns__ using __pycares__ under the hood I wanted to learn how it worked to see if I could use my skills to optimize it like I had previously done with __propcache__ and __multidict__ as well but I soon came to learn of it's many problems and when __pycares__ started to hang on me with lingering threads when it ran, something didn't feel right to me and it lead me down a very large rabbit-hole waiting to be re-explored.
|
|
60
|
+
|
|
61
|
+
__Pycares__ was quick, dirty and fast but as the years of it's existance went by something wasn't exactly right with it and it could use safer features, better optimization and safety practices. While __cffi__ might be both quick and easy to use I didn't see the benefit of using it. The many vulnerabilities it was getting told me something needed to change if not big. With __Pycares__ reaching version __5.0.0__ I wanted to celebrate it the same way node-js celebrated the 10 year annversery of the original __http-parser__. Write a new one.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
### The Rewrite
|
|
65
|
+
|
|
66
|
+
While contributing to the aio-libs repos whenever I had a gap of free-time on hand while waiting on a question to be answered and when my other python libraries felt stable enough such as (deprecated-params, aiocallback, aiothreading & winloop) this is where I spent my time on. It had a total of 3 re-writes until I was satisfied with the result. Turns out just doing everything in small chunks makes all the difference.
|
|
67
|
+
|
|
68
|
+
It only felt right to me that migrating the library from __cffi__ to __cython__ would be the best solution to the problem. Not __Rust__ or __C__, just __pure-cython__ and small amounts of __C__ whenever nessesary and re-changing many of the internals to incorperate a safer approch. The reason for not picking __Rust__ is that there wasn't nessesarly a need to be memory-safe. What I cared about was speed, when people are doing DNS Lookups, speed matters and __Rust__ did not have a friendly enough archetecture that would set a future or allow me to access the parent from the child.
|
|
69
|
+
|
|
70
|
+
There was no better canadate than to move to using handles the same way __uvloop__ & __winloop__ do it and having authored one of them I was familliar with this concept.
|
|
71
|
+
|
|
72
|
+
The idea was not to reinvent the wheel rather move parts of the library over in chunks and look at __pycares__ for clues on how certain things should be laid out. At the end of the day, __Pycares__ was the blueprint and using __concurrent.futures__ & __Cython__ was the cure. __Py_buffer__ techniques brought over from __msgspec__ were utilized incase users were planning to use any bytes or string objects of any sort and I made sure the license was on it incase users wanted to know where it came from.
|
|
73
|
+
|
|
74
|
+
If there was a deprecated function with an alternative & safer approch to use I felt using it would be a better move. For example, servers are now set with the csv functions rather than the original setup and because of that change you could now set dns servers in url formats and I might possibly look at adding in __yarl__ to be the cherry on top for those who might wish to get real creative about dns server urls.
|
|
75
|
+
|
|
76
|
+
Moving over the __ares_query__ function to use the dns recursion function is planned for the future (maybe after I do the first release of cyares). I still wanted to retain the original response data it gives so that migrating from __pycares__ to __cyares__ wouldn't be a pain to anyone who planned on moving and I also didn't want to take away from __pycares__ either if you prefer using __cffi__ over __cython__ the same way __curl-cffi__ and __cycurl__ were both done. I have my fingers crossed that __aiodns__ will adopt this library in the future or allow users to choose between __pycares__ and __cyares__.
|
|
77
|
+
|
cyares-0.1.0/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
An Upgraded version of __pycares__ with faster and safer features.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Small Examples of How to Use
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from cyares import Channel
|
|
11
|
+
|
|
12
|
+
# NOTE: Dns Queries contain a Future object
|
|
13
|
+
# This is a little bit different from pycares
|
|
14
|
+
# Handles were used to prevent new or unwanted vulnerabilities along
|
|
15
|
+
# with easier control given to the user...
|
|
16
|
+
# The Future object comes from the python concurrent.futures standard library
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
with Channel(servers=["8.8.8.8", "8.8.4.4"], event_thread=True) as channel:
|
|
20
|
+
data = channel.query("google.com", "A").result()
|
|
21
|
+
print(data)
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
main()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## In Asyncio
|
|
28
|
+
There's a module for an aiodns version if aiodns were using cyares.
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import winloop # or uvloop (asyncio will also work)
|
|
32
|
+
# I'll add trio support in a future update as well...
|
|
33
|
+
from cyares.aio import DNSResolver
|
|
34
|
+
|
|
35
|
+
async def test():
|
|
36
|
+
async with DNSResolver(["8.8.8.8", "8.8.4.4"]) as dns:
|
|
37
|
+
data = await dns.query("google.com", "A")
|
|
38
|
+
print(data)
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
winloop.run(test())
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Story
|
|
47
|
+
As Someone who as recently started to contribute to projects such as __aiohttp__ and a few of it's smaller libraries. The asynchronous dns resolver __aiodns__ that __aiohttp__ can optionally use, felt like the oddest one in the group. With __aiodns__ using __pycares__ under the hood I wanted to learn how it worked to see if I could use my skills to optimize it like I had previously done with __propcache__ and __multidict__ as well but I soon came to learn of it's many problems and when __pycares__ started to hang on me with lingering threads when it ran, something didn't feel right to me and it lead me down a very large rabbit-hole waiting to be re-explored.
|
|
48
|
+
|
|
49
|
+
__Pycares__ was quick, dirty and fast but as the years of it's existance went by something wasn't exactly right with it and it could use safer features, better optimization and safety practices. While __cffi__ might be both quick and easy to use I didn't see the benefit of using it. The many vulnerabilities it was getting told me something needed to change if not big. With __Pycares__ reaching version __5.0.0__ I wanted to celebrate it the same way node-js celebrated the 10 year annversery of the original __http-parser__. Write a new one.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### The Rewrite
|
|
53
|
+
|
|
54
|
+
While contributing to the aio-libs repos whenever I had a gap of free-time on hand while waiting on a question to be answered and when my other python libraries felt stable enough such as (deprecated-params, aiocallback, aiothreading & winloop) this is where I spent my time on. It had a total of 3 re-writes until I was satisfied with the result. Turns out just doing everything in small chunks makes all the difference.
|
|
55
|
+
|
|
56
|
+
It only felt right to me that migrating the library from __cffi__ to __cython__ would be the best solution to the problem. Not __Rust__ or __C__, just __pure-cython__ and small amounts of __C__ whenever nessesary and re-changing many of the internals to incorperate a safer approch. The reason for not picking __Rust__ is that there wasn't nessesarly a need to be memory-safe. What I cared about was speed, when people are doing DNS Lookups, speed matters and __Rust__ did not have a friendly enough archetecture that would set a future or allow me to access the parent from the child.
|
|
57
|
+
|
|
58
|
+
There was no better canadate than to move to using handles the same way __uvloop__ & __winloop__ do it and having authored one of them I was familliar with this concept.
|
|
59
|
+
|
|
60
|
+
The idea was not to reinvent the wheel rather move parts of the library over in chunks and look at __pycares__ for clues on how certain things should be laid out. At the end of the day, __Pycares__ was the blueprint and using __concurrent.futures__ & __Cython__ was the cure. __Py_buffer__ techniques brought over from __msgspec__ were utilized incase users were planning to use any bytes or string objects of any sort and I made sure the license was on it incase users wanted to know where it came from.
|
|
61
|
+
|
|
62
|
+
If there was a deprecated function with an alternative & safer approch to use I felt using it would be a better move. For example, servers are now set with the csv functions rather than the original setup and because of that change you could now set dns servers in url formats and I might possibly look at adding in __yarl__ to be the cherry on top for those who might wish to get real creative about dns server urls.
|
|
63
|
+
|
|
64
|
+
Moving over the __ares_query__ function to use the dns recursion function is planned for the future (maybe after I do the first release of cyares). I still wanted to retain the original response data it gives so that migrating from __pycares__ to __cyares__ wouldn't be a pain to anyone who planned on moving and I also didn't want to take away from __pycares__ either if you prefer using __cffi__ over __cython__ the same way __curl-cffi__ and __cycurl__ were both done. I have my fingers crossed that __aiodns__ will adopt this library in the future or allow users to choose between __pycares__ and __cyares__.
|
|
65
|
+
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
"""
|
|
2
|
+
aio
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A Version of Aiodns if Aiodns theoretically supported cyares by default.
|
|
6
|
+
This module is experimental and could be removed in the future if merged
|
|
7
|
+
with aiodns.
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Inspired by aiodns, joint library/modue was made incase
|
|
12
|
+
# aiodns devs decided not to adopt this library optionally.
|
|
13
|
+
# This was also made to test socket callbacks to see if they
|
|
14
|
+
# were working properly...
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import socket
|
|
19
|
+
import sys
|
|
20
|
+
from concurrent.futures import Future as cc_Future
|
|
21
|
+
from logging import getLogger
|
|
22
|
+
from typing import Any, Iterable, Literal, Sequence, TypeVar, overload
|
|
23
|
+
|
|
24
|
+
from .channel import * # type: ignore
|
|
25
|
+
from .exception import AresError
|
|
26
|
+
from .resulttypes import *
|
|
27
|
+
|
|
28
|
+
_T = TypeVar("_T")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
WINDOWS_SELECTOR_ERR_MSG = (
|
|
32
|
+
"cyares.aio cannot use ProactorEventLoop on Windows"
|
|
33
|
+
" if cyares has no threadsafety. See more: "
|
|
34
|
+
"https://github.com/aio-libs/aiodns#note-for-windows-users",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
_LOGGER = getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
query_type_map = {
|
|
41
|
+
"A": QUERY_TYPE_A,
|
|
42
|
+
"AAAA": QUERY_TYPE_AAAA,
|
|
43
|
+
"ANY": QUERY_TYPE_ANY,
|
|
44
|
+
"CAA": QUERY_TYPE_CAA,
|
|
45
|
+
"CNAME": QUERY_TYPE_CNAME,
|
|
46
|
+
"MX": QUERY_TYPE_MX,
|
|
47
|
+
"NAPTR": QUERY_TYPE_NAPTR,
|
|
48
|
+
"NS": QUERY_TYPE_NS,
|
|
49
|
+
"PTR": QUERY_TYPE_PTR,
|
|
50
|
+
"SOA": QUERY_TYPE_SOA,
|
|
51
|
+
"SRV": QUERY_TYPE_SRV,
|
|
52
|
+
"TXT": QUERY_TYPE_TXT,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
query_class_map = {
|
|
56
|
+
"IN": QUERY_CLASS_IN,
|
|
57
|
+
"CHAOS": QUERY_CLASS_CHAOS,
|
|
58
|
+
"HS": QUERY_CLASS_HS,
|
|
59
|
+
"NONE": QUERY_CLASS_NONE,
|
|
60
|
+
"ANY": QUERY_CLASS_ANY,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Simillar to aiodns's version but more compact
|
|
65
|
+
class DNSResolver:
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
nameservers: list[str] | None = None,
|
|
69
|
+
loop: asyncio.AbstractEventLoop | None = None,
|
|
70
|
+
event_thread: bool = True,
|
|
71
|
+
**kwargs,
|
|
72
|
+
) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Params
|
|
75
|
+
------
|
|
76
|
+
|
|
77
|
+
:param nameservers: a list of dns servers to connect to
|
|
78
|
+
:param loop: the asyncio event loop to utilize, supported
|
|
79
|
+
eventloops include, winloop, uvloop and the asyncio standard library
|
|
80
|
+
NOTE for windows users: `SelectorEventLoop` & `winloop.Loop` are the only
|
|
81
|
+
eventloops you can use when the event_thread is disabled or when event_thread
|
|
82
|
+
is not supported SEE: https://github.com/aio-libs/aiodns#note-for-windows-users
|
|
83
|
+
for more information
|
|
84
|
+
|
|
85
|
+
:param event_thread: if enabled try to see if cyares can utilize \
|
|
86
|
+
event threads otherwise fallback to using a socket callback handle \
|
|
87
|
+
if `false` socket callback will be used no matter what,
|
|
88
|
+
setting to `false` is good for testing purposes or
|
|
89
|
+
when trying to act threadsafe until closure.
|
|
90
|
+
A quick word of caution , deletion is **Not Thread-Safe**
|
|
91
|
+
SEE: https://c-ares.org/docs/ares_destroy.html
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
self._closed = True
|
|
95
|
+
self.loop = loop or asyncio.get_event_loop()
|
|
96
|
+
|
|
97
|
+
# XXX: Do not use, we override this argument by default
|
|
98
|
+
kwargs.pop("sock_state_cb", None)
|
|
99
|
+
|
|
100
|
+
timeout = kwargs.pop("timeout", None)
|
|
101
|
+
self._timeout = timeout
|
|
102
|
+
# Internal (Using with pytest to help debug socket_cb handles)
|
|
103
|
+
if event_thread:
|
|
104
|
+
self._event_thread, self._channel = self._make_channel(**kwargs)
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
self._raise_if_windows_proctor()
|
|
108
|
+
self._event_thread, self._channel = (
|
|
109
|
+
False,
|
|
110
|
+
Channel(
|
|
111
|
+
sock_state_cb=self._sock_state_cb, timeout=self._timeout, **kwargs
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if nameservers:
|
|
116
|
+
self.nameservers = nameservers
|
|
117
|
+
self._read_fds: set[int] = set()
|
|
118
|
+
self._write_fds: set[int] = set()
|
|
119
|
+
|
|
120
|
+
# As an extra safety concern lets ensure
|
|
121
|
+
# that the asyncio futures being carried &
|
|
122
|
+
# can't trigger segfaults at cleanup...
|
|
123
|
+
self._handles: set[asyncio.Future[Any]] = set()
|
|
124
|
+
self._empty = asyncio.Event()
|
|
125
|
+
|
|
126
|
+
self._timer: asyncio.TimerHandle | None = None
|
|
127
|
+
self._closed = False
|
|
128
|
+
|
|
129
|
+
def _raise_if_windows_proctor(self):
|
|
130
|
+
if sys.platform == "win32" and type(self.loop) is asyncio.ProactorEventLoop:
|
|
131
|
+
raise RuntimeError(WINDOWS_SELECTOR_ERR_MSG)
|
|
132
|
+
|
|
133
|
+
def _on_fut_done(self, fut: asyncio.Future[Any]):
|
|
134
|
+
self._handles.remove(fut)
|
|
135
|
+
if not self._handles:
|
|
136
|
+
self._empty.set()
|
|
137
|
+
|
|
138
|
+
def _wrap_future(self, fut: cc_Future[_T]) -> asyncio.Future[_T]:
|
|
139
|
+
out_fut = asyncio.wrap_future(fut)
|
|
140
|
+
self._handles.add(out_fut)
|
|
141
|
+
out_fut.add_done_callback(self._on_fut_done)
|
|
142
|
+
self._empty.clear()
|
|
143
|
+
return out_fut
|
|
144
|
+
|
|
145
|
+
def _make_channel(self, **kwargs: Any) -> tuple[bool, Channel]:
|
|
146
|
+
if cyares_threadsafety():
|
|
147
|
+
# CyAres is Threadsafe
|
|
148
|
+
try:
|
|
149
|
+
return True, Channel(event_thread=True, timeout=self._timeout, **kwargs)
|
|
150
|
+
except AresError as e:
|
|
151
|
+
if sys.platform == "linux":
|
|
152
|
+
_LOGGER.warning(
|
|
153
|
+
"Failed to create DNS resolver channel with automatic "
|
|
154
|
+
"monitoring of resolver configuration changes. This "
|
|
155
|
+
"usually means the system ran out of inotify watches. "
|
|
156
|
+
"Falling back to socket state callback. Consider "
|
|
157
|
+
"increasing the system inotify watch limit: %s",
|
|
158
|
+
e,
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
_LOGGER.warning(
|
|
162
|
+
"Failed to create DNS resolver channel with automatic "
|
|
163
|
+
"monitoring of resolver configuration changes. "
|
|
164
|
+
"Falling back to socket state callback: %s",
|
|
165
|
+
e,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
self._raise_if_windows_proctor()
|
|
169
|
+
|
|
170
|
+
return False, Channel(
|
|
171
|
+
sock_state_cb=self._sock_state_cb, timeout=self._timeout, **kwargs
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def _timer_cb(self) -> None:
|
|
175
|
+
if self._read_fds or self._write_fds:
|
|
176
|
+
self._channel.process_fd(CYARES_SOCKET_BAD, CYARES_SOCKET_BAD)
|
|
177
|
+
self._start_timer()
|
|
178
|
+
else:
|
|
179
|
+
self._timer = None
|
|
180
|
+
|
|
181
|
+
def _start_timer(self) -> None:
|
|
182
|
+
timeout = self._timeout
|
|
183
|
+
if timeout is None or timeout < 0 or timeout > 1:
|
|
184
|
+
timeout = 1
|
|
185
|
+
elif timeout == 0:
|
|
186
|
+
timeout = 0.1
|
|
187
|
+
|
|
188
|
+
self._timer = self.loop.call_later(timeout, self._timer_cb)
|
|
189
|
+
|
|
190
|
+
def cancel(self) -> None:
|
|
191
|
+
"""Cancels all running futures queued by this dns resolver"""
|
|
192
|
+
self._channel.cancel()
|
|
193
|
+
|
|
194
|
+
async def _cleanup(self) -> None:
|
|
195
|
+
"""Cleanup timers and file descriptors when closing resolver."""
|
|
196
|
+
if self._closed:
|
|
197
|
+
return
|
|
198
|
+
# Mark as closed first to prevent double cleanup
|
|
199
|
+
self._closed = True
|
|
200
|
+
# Cancel timer if running
|
|
201
|
+
if self._timer is not None:
|
|
202
|
+
self._timer.cancel()
|
|
203
|
+
self._timer = None
|
|
204
|
+
|
|
205
|
+
# Remove all file descriptors
|
|
206
|
+
for fd in self._read_fds:
|
|
207
|
+
self.loop.remove_reader(fd)
|
|
208
|
+
for fd in self._write_fds:
|
|
209
|
+
self.loop.remove_writer(fd)
|
|
210
|
+
|
|
211
|
+
self._read_fds.clear()
|
|
212
|
+
self._write_fds.clear()
|
|
213
|
+
self.cancel()
|
|
214
|
+
|
|
215
|
+
# Something a little different is that unline aiodns
|
|
216
|
+
# we're carrying handles to prevent crashing.
|
|
217
|
+
# This could be removed in the future if provent that
|
|
218
|
+
# it doesn't crash anymore.
|
|
219
|
+
|
|
220
|
+
if self._handles:
|
|
221
|
+
# wait for all handles to empty out otherwise assume it completed
|
|
222
|
+
try:
|
|
223
|
+
await asyncio.wait_for(self._empty.wait(), 0.1)
|
|
224
|
+
except asyncio.TimeoutError:
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
def _sock_state_cb(self, fd: int, readable: bool, writable: bool) -> None:
|
|
228
|
+
if readable or writable:
|
|
229
|
+
if readable:
|
|
230
|
+
self.loop.add_reader(fd, self._channel.process_read_fd, fd)
|
|
231
|
+
self._read_fds.add(fd)
|
|
232
|
+
if writable:
|
|
233
|
+
self.loop.add_writer(fd, self._channel.process_write_fd, fd)
|
|
234
|
+
self._write_fds.add(fd)
|
|
235
|
+
if self._timer is None:
|
|
236
|
+
self._start_timer()
|
|
237
|
+
else:
|
|
238
|
+
# socket is now closed
|
|
239
|
+
if fd in self._read_fds:
|
|
240
|
+
self._read_fds.discard(fd)
|
|
241
|
+
self.loop.remove_reader(fd)
|
|
242
|
+
|
|
243
|
+
if fd in self._write_fds:
|
|
244
|
+
self._write_fds.discard(fd)
|
|
245
|
+
self.loop.remove_writer(fd)
|
|
246
|
+
|
|
247
|
+
if not self._read_fds and not self._write_fds and self._timer is not None:
|
|
248
|
+
self._timer.cancel()
|
|
249
|
+
self._timer = None
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def nameservers(self) -> Sequence[str]:
|
|
253
|
+
return self._channel.servers
|
|
254
|
+
|
|
255
|
+
@nameservers.setter
|
|
256
|
+
def nameservers(self, value: Iterable[str | bytes]) -> None:
|
|
257
|
+
self._channel.servers = value
|
|
258
|
+
|
|
259
|
+
@overload
|
|
260
|
+
def query(
|
|
261
|
+
self, host: str, qtype: Literal["A"], qclass: str | None = ...
|
|
262
|
+
) -> asyncio.Future[list[ares_query_a_result]]: ...
|
|
263
|
+
@overload
|
|
264
|
+
def query(
|
|
265
|
+
self, host: str, qtype: Literal["AAAA"], qclass: str | None = ...
|
|
266
|
+
) -> asyncio.Future[list[ares_query_aaaa_result]]: ...
|
|
267
|
+
@overload
|
|
268
|
+
def query(
|
|
269
|
+
self, host: str, qtype: Literal["CAA"], qclass: str | None = ...
|
|
270
|
+
) -> asyncio.Future[list[ares_query_caa_result]]: ...
|
|
271
|
+
@overload
|
|
272
|
+
def query(
|
|
273
|
+
self, host: str, qtype: Literal["CNAME"], qclass: str | None = ...
|
|
274
|
+
) -> asyncio.Future[ares_query_cname_result]: ...
|
|
275
|
+
@overload
|
|
276
|
+
def query(
|
|
277
|
+
self, host: str, qtype: Literal["MX"], qclass: str | None = ...
|
|
278
|
+
) -> asyncio.Future[list[ares_query_mx_result]]: ...
|
|
279
|
+
@overload
|
|
280
|
+
def query(
|
|
281
|
+
self, host: str, qtype: Literal["NAPTR"], qclass: str | None = ...
|
|
282
|
+
) -> asyncio.Future[list[ares_query_naptr_result]]: ...
|
|
283
|
+
@overload
|
|
284
|
+
def query(
|
|
285
|
+
self, host: str, qtype: Literal["NS"], qclass: str | None = ...
|
|
286
|
+
) -> asyncio.Future[list[ares_query_ns_result]]: ...
|
|
287
|
+
@overload
|
|
288
|
+
def query(
|
|
289
|
+
self, host: str, qtype: Literal["PTR"], qclass: str | None = ...
|
|
290
|
+
) -> asyncio.Future[list[ares_query_ptr_result]]: ...
|
|
291
|
+
@overload
|
|
292
|
+
def query(
|
|
293
|
+
self, host: str, qtype: Literal["SOA"], qclass: str | None = ...
|
|
294
|
+
) -> asyncio.Future[ares_query_soa_result]: ...
|
|
295
|
+
@overload
|
|
296
|
+
def query(
|
|
297
|
+
self, host: str, qtype: Literal["SRV"], qclass: str | None = ...
|
|
298
|
+
) -> asyncio.Future[list[ares_query_srv_result]]: ...
|
|
299
|
+
@overload
|
|
300
|
+
def query(
|
|
301
|
+
self, host: str, qtype: Literal["TXT"], qclass: str | None = ...
|
|
302
|
+
) -> asyncio.Future[list[ares_query_txt_result]]: ...
|
|
303
|
+
|
|
304
|
+
def query(
|
|
305
|
+
self, host: str, qtype: str, qclass: str | None = None
|
|
306
|
+
) -> asyncio.Future[list[Any]] | asyncio.Future[Any]:
|
|
307
|
+
try:
|
|
308
|
+
qtype = query_type_map[qtype]
|
|
309
|
+
except KeyError as e:
|
|
310
|
+
raise ValueError(f"invalid query type: {qtype}") from e
|
|
311
|
+
if qclass is not None:
|
|
312
|
+
try:
|
|
313
|
+
qclass = query_class_map[qclass]
|
|
314
|
+
except KeyError as e:
|
|
315
|
+
raise ValueError(f"invalid query class: {qclass}") from e
|
|
316
|
+
|
|
317
|
+
# we use a different technique than pycares to try and
|
|
318
|
+
# aggressively prevent vulnerabilities
|
|
319
|
+
|
|
320
|
+
return self._wrap_future(self._channel.query(host, qtype, qclass))
|
|
321
|
+
|
|
322
|
+
def gethostbyname(
|
|
323
|
+
self, host: str, family: socket.AddressFamily
|
|
324
|
+
) -> asyncio.Future[ares_host_result]:
|
|
325
|
+
return self._wrap_future(self._channel.gethostbyname(host, family))
|
|
326
|
+
|
|
327
|
+
def getaddrinfo(
|
|
328
|
+
self,
|
|
329
|
+
host: str,
|
|
330
|
+
family: socket.AddressFamily = socket.AF_UNSPEC,
|
|
331
|
+
port: int | None = None,
|
|
332
|
+
proto: int = 0,
|
|
333
|
+
type: int = 0,
|
|
334
|
+
flags: int = 0,
|
|
335
|
+
) -> asyncio.Future[ares_addrinfo_result]:
|
|
336
|
+
return self._wrap_future(
|
|
337
|
+
self._channel.getaddrinfo(
|
|
338
|
+
host, port, family=family, type=type, proto=proto, flags=flags
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def gethostbyaddr(
|
|
343
|
+
self, name: str | bytes | bytearray | memoryview[int]
|
|
344
|
+
) -> asyncio.Future[ares_host_result]:
|
|
345
|
+
return self._wrap_future(self._channel.gethostbyaddr(name))
|
|
346
|
+
|
|
347
|
+
async def close(self) -> None:
|
|
348
|
+
"""
|
|
349
|
+
Cleanly close the DNS resolver.
|
|
350
|
+
|
|
351
|
+
This should be called to ensure all resources are properly released.
|
|
352
|
+
After calling close(), the resolver should not be used again.
|
|
353
|
+
"""
|
|
354
|
+
await self._cleanup()
|
|
355
|
+
|
|
356
|
+
# Still needs a little bit more work on...
|
|
357
|
+
# @overload
|
|
358
|
+
# def search(
|
|
359
|
+
# self, host: str, qtype: Literal["A"], qclass: str | None = ...
|
|
360
|
+
# ) -> asyncio.Future[list[ares_query_a_result]]: ...
|
|
361
|
+
# @overload
|
|
362
|
+
# def search(
|
|
363
|
+
# self, host: str, qtype: Literal["AAAA"], qclass: str | None = ...
|
|
364
|
+
# ) -> asyncio.Future[list[ares_query_aaaa_result]]: ...
|
|
365
|
+
# @overload
|
|
366
|
+
# def search(
|
|
367
|
+
# self, host: str, qtype: Literal["CAA"], qclass: str | None = ...
|
|
368
|
+
# ) -> asyncio.Future[list[ares_query_caa_result]]: ...
|
|
369
|
+
# @overload
|
|
370
|
+
# def search(
|
|
371
|
+
# self, host: str, qtype: Literal["CNAME"], qclass: str | None = ...
|
|
372
|
+
# ) -> asyncio.Future[ares_query_cname_result]: ...
|
|
373
|
+
# @overload
|
|
374
|
+
# def search(
|
|
375
|
+
# self, host: str, qtype: Literal["MX"], qclass: str | None = ...
|
|
376
|
+
# ) -> asyncio.Future[list[ares_query_mx_result]]: ...
|
|
377
|
+
# @overload
|
|
378
|
+
# def search(
|
|
379
|
+
# self, host: str, qtype: Literal["NAPTR"], qclass: str | None = ...
|
|
380
|
+
# ) -> asyncio.Future[list[ares_query_naptr_result]]: ...
|
|
381
|
+
# @overload
|
|
382
|
+
# def search(
|
|
383
|
+
# self, host: str, qtype: Literal["NS"], qclass: str | None = ...
|
|
384
|
+
# ) -> asyncio.Future[list[ares_query_ns_result]]: ...
|
|
385
|
+
# @overload
|
|
386
|
+
# def search(
|
|
387
|
+
# self, host: str, qtype: Literal["PTR"], qclass: str | None = ...
|
|
388
|
+
# ) -> asyncio.Future[list[ares_query_ptr_result]]: ...
|
|
389
|
+
# @overload
|
|
390
|
+
# def search(
|
|
391
|
+
# self, host: str, qtype: Literal["SOA"], qclass: str | None = ...
|
|
392
|
+
# ) -> asyncio.Future[ares_query_soa_result]: ...
|
|
393
|
+
# @overload
|
|
394
|
+
# def search(
|
|
395
|
+
# self, host: str, qtype: Literal["SRV"], qclass: str | None = ...
|
|
396
|
+
# ) -> asyncio.Future[list[ares_query_srv_result]]: ...
|
|
397
|
+
# @overload
|
|
398
|
+
# def search(
|
|
399
|
+
# self, host: str, qtype: Literal["TXT"], qclass: str | None = ...
|
|
400
|
+
# ) -> asyncio.Future[list[ares_query_txt_result]]: ...
|
|
401
|
+
|
|
402
|
+
# def search(
|
|
403
|
+
# self, host: str, qtype: str, qclass: str | None = None
|
|
404
|
+
# ) -> asyncio.Future[list[Any]] | asyncio.Future[Any]:
|
|
405
|
+
# try:
|
|
406
|
+
# qtype = query_type_map[qtype]
|
|
407
|
+
# except KeyError as e:
|
|
408
|
+
# raise ValueError(f"invalid query type: {qtype}") from e
|
|
409
|
+
# if qclass is not None:
|
|
410
|
+
# try:
|
|
411
|
+
# qclass = query_class_map[qclass]
|
|
412
|
+
# except KeyError as e:
|
|
413
|
+
# raise ValueError(f"invalid query class: {qclass}") from e
|
|
414
|
+
|
|
415
|
+
# # we use a different technique than pycares to try and
|
|
416
|
+
# # aggressively prevent vulnerabilities
|
|
417
|
+
|
|
418
|
+
# return self._wrap_future(self._channel.query(host, qtype, qclass))
|
|
419
|
+
|
|
420
|
+
async def __aenter__(self):
|
|
421
|
+
return self
|
|
422
|
+
|
|
423
|
+
async def __aexit__(self, *args):
|
|
424
|
+
await self.close()
|
|
425
|
+
|
|
426
|
+
# TODO: I will do the rest of the functionality a bit later...
|