fmtr.tools 1.2.3__tar.gz → 1.2.5__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.
Potentially problematic release.
This version of fmtr.tools might be problematic. Click here for more details.
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/PKG-INFO +46 -46
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/__init__.py +1 -5
- fmtr_tools-1.2.5/fmtr/tools/dns_tools/__init__.py +6 -0
- fmtr_tools-1.2.5/fmtr/tools/dns_tools/client.py +78 -0
- fmtr_tools-1.2.5/fmtr/tools/dns_tools/dm.py +110 -0
- fmtr_tools-1.2.5/fmtr/tools/dns_tools/server.py +96 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/pattern_tools.py +6 -3
- fmtr_tools-1.2.5/fmtr/tools/version +1 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr.tools.egg-info/PKG-INFO +46 -46
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr.tools.egg-info/SOURCES.txt +8 -2
- fmtr_tools-1.2.3/fmtr/tools/dns_tools.py +0 -221
- fmtr_tools-1.2.3/fmtr/tools/version +0 -1
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/LICENSE +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/README.md +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/ai_tools/__init__.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/ai_tools/agentic_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/ai_tools/inference_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/api_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/async_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/augmentation_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/caching_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/constants.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/data_modelling_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/dataclass_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/datatype_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/debugging_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/docker_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/entrypoints/__init__.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/entrypoints/cache_hfh.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/entrypoints/ep_test.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/entrypoints/remote_debug_test.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/entrypoints/shell_debug.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/environment_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/function_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/google_api_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/hash_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/hfh_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/html_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/http_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/import_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/inspection_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/interface_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/iterator_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/json_fix_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/json_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/logging_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/merging_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/metric_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/name_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/netrc_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/openai_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/packaging_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/parallel_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/path_tools/__init__.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/path_tools/app_path_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/path_tools/path_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/path_tools/type_path_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/pdf_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/platform_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/process_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/profiling_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/random_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/semantic_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/settings_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/setup_tools/__init__.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/setup_tools/setup_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/spaces_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/string_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tabular_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tests/__init__.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tests/conftest.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tests/helpers.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tests/test_datatype.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tests/test_environment.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tests/test_json.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tests/test_path.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tests/test_yaml.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tokenization_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/unicode_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/version_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr/tools/yaml_tools.py +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr.tools.egg-info/dependency_links.txt +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr.tools.egg-info/entry_points.txt +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr.tools.egg-info/requires.txt +45 -45
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/fmtr.tools.egg-info/top_level.txt +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/pyproject.toml +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/setup.cfg +0 -0
- {fmtr_tools-1.2.3 → fmtr_tools-1.2.5}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fmtr.tools
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.5
|
|
4
4
|
Summary: Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML
|
|
5
5
|
Home-page: https://github.com/fmtr/tools
|
|
6
6
|
Author: Frontmatter
|
|
@@ -128,60 +128,60 @@ Requires-Dist: logfire[httpx]; extra == "http"
|
|
|
128
128
|
Provides-Extra: setup
|
|
129
129
|
Requires-Dist: setuptools; extra == "setup"
|
|
130
130
|
Provides-Extra: all
|
|
131
|
-
Requires-Dist: logfire; extra == "all"
|
|
132
|
-
Requires-Dist: tokenizers; extra == "all"
|
|
133
|
-
Requires-Dist: appdirs; extra == "all"
|
|
134
|
-
Requires-Dist: pydantic-settings; extra == "all"
|
|
135
|
-
Requires-Dist: filetype; extra == "all"
|
|
136
|
-
Requires-Dist: pymupdf; extra == "all"
|
|
137
|
-
Requires-Dist: json_repair; extra == "all"
|
|
138
|
-
Requires-Dist: sentence_transformers; extra == "all"
|
|
139
|
-
Requires-Dist: fastapi; extra == "all"
|
|
140
|
-
Requires-Dist: tabulate; extra == "all"
|
|
141
|
-
Requires-Dist: distributed; extra == "all"
|
|
142
|
-
Requires-Dist: faker; extra == "all"
|
|
143
|
-
Requires-Dist: semver; extra == "all"
|
|
144
|
-
Requires-Dist: uvicorn[standard]; extra == "all"
|
|
145
|
-
Requires-Dist: html2text; extra == "all"
|
|
146
|
-
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
147
|
-
Requires-Dist: bokeh; extra == "all"
|
|
148
|
-
Requires-Dist: flet-webview; extra == "all"
|
|
149
|
-
Requires-Dist: logfire[httpx]; extra == "all"
|
|
150
131
|
Requires-Dist: torchaudio; extra == "all"
|
|
151
|
-
Requires-Dist: Unidecode; extra == "all"
|
|
152
|
-
Requires-Dist: flet-video; extra == "all"
|
|
153
|
-
Requires-Dist: google-auth; extra == "all"
|
|
154
|
-
Requires-Dist: flet[all]; extra == "all"
|
|
155
|
-
Requires-Dist: openai; extra == "all"
|
|
156
|
-
Requires-Dist: pymupdf4llm; extra == "all"
|
|
157
|
-
Requires-Dist: dnspython[doh]; extra == "all"
|
|
158
|
-
Requires-Dist: deepmerge; extra == "all"
|
|
159
|
-
Requires-Dist: contexttimer; extra == "all"
|
|
160
|
-
Requires-Dist: openpyxl; extra == "all"
|
|
161
|
-
Requires-Dist: tinynetrc; extra == "all"
|
|
162
|
-
Requires-Dist: ollama; extra == "all"
|
|
163
|
-
Requires-Dist: huggingface_hub; extra == "all"
|
|
164
132
|
Requires-Dist: google-auth-oauthlib; extra == "all"
|
|
133
|
+
Requires-Dist: deepmerge; extra == "all"
|
|
165
134
|
Requires-Dist: httpx_retries; extra == "all"
|
|
166
|
-
Requires-Dist: docker; extra == "all"
|
|
167
|
-
Requires-Dist: setuptools; extra == "all"
|
|
168
|
-
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
169
|
-
Requires-Dist: google-api-python-client; extra == "all"
|
|
170
|
-
Requires-Dist: torchvision; extra == "all"
|
|
171
|
-
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
172
|
-
Requires-Dist: pandas; extra == "all"
|
|
173
135
|
Requires-Dist: regex; extra == "all"
|
|
174
|
-
Requires-Dist:
|
|
136
|
+
Requires-Dist: dask[bag]; extra == "all"
|
|
137
|
+
Requires-Dist: pytest-cov; extra == "all"
|
|
138
|
+
Requires-Dist: flet-video; extra == "all"
|
|
139
|
+
Requires-Dist: uvicorn[standard]; extra == "all"
|
|
140
|
+
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
141
|
+
Requires-Dist: logfire; extra == "all"
|
|
142
|
+
Requires-Dist: bokeh; extra == "all"
|
|
175
143
|
Requires-Dist: sre_yield; extra == "all"
|
|
144
|
+
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
145
|
+
Requires-Dist: pymupdf4llm; extra == "all"
|
|
146
|
+
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
147
|
+
Requires-Dist: semver; extra == "all"
|
|
148
|
+
Requires-Dist: yamlscript; extra == "all"
|
|
149
|
+
Requires-Dist: torchvision; extra == "all"
|
|
150
|
+
Requires-Dist: pydevd-pycharm; extra == "all"
|
|
151
|
+
Requires-Dist: tinynetrc; extra == "all"
|
|
176
152
|
Requires-Dist: pyyaml; extra == "all"
|
|
153
|
+
Requires-Dist: openai; extra == "all"
|
|
154
|
+
Requires-Dist: sentence_transformers; extra == "all"
|
|
155
|
+
Requires-Dist: pymupdf; extra == "all"
|
|
156
|
+
Requires-Dist: pydantic-settings; extra == "all"
|
|
157
|
+
Requires-Dist: httpx; extra == "all"
|
|
158
|
+
Requires-Dist: tabulate; extra == "all"
|
|
159
|
+
Requires-Dist: setuptools; extra == "all"
|
|
160
|
+
Requires-Dist: diskcache; extra == "all"
|
|
161
|
+
Requires-Dist: distributed; extra == "all"
|
|
162
|
+
Requires-Dist: huggingface_hub; extra == "all"
|
|
163
|
+
Requires-Dist: flet-webview; extra == "all"
|
|
177
164
|
Requires-Dist: peft; extra == "all"
|
|
165
|
+
Requires-Dist: google-api-python-client; extra == "all"
|
|
166
|
+
Requires-Dist: contexttimer; extra == "all"
|
|
167
|
+
Requires-Dist: docker; extra == "all"
|
|
178
168
|
Requires-Dist: pydantic; extra == "all"
|
|
179
|
-
Requires-Dist:
|
|
180
|
-
Requires-Dist:
|
|
169
|
+
Requires-Dist: dnspython[doh]; extra == "all"
|
|
170
|
+
Requires-Dist: openpyxl; extra == "all"
|
|
171
|
+
Requires-Dist: filetype; extra == "all"
|
|
172
|
+
Requires-Dist: fastapi; extra == "all"
|
|
173
|
+
Requires-Dist: faker; extra == "all"
|
|
181
174
|
Requires-Dist: transformers[sentencepiece]; extra == "all"
|
|
182
|
-
Requires-Dist:
|
|
183
|
-
Requires-Dist:
|
|
184
|
-
Requires-Dist:
|
|
175
|
+
Requires-Dist: Unidecode; extra == "all"
|
|
176
|
+
Requires-Dist: appdirs; extra == "all"
|
|
177
|
+
Requires-Dist: logfire[httpx]; extra == "all"
|
|
178
|
+
Requires-Dist: tokenizers; extra == "all"
|
|
179
|
+
Requires-Dist: html2text; extra == "all"
|
|
180
|
+
Requires-Dist: ollama; extra == "all"
|
|
181
|
+
Requires-Dist: json_repair; extra == "all"
|
|
182
|
+
Requires-Dist: flet[all]; extra == "all"
|
|
183
|
+
Requires-Dist: google-auth; extra == "all"
|
|
184
|
+
Requires-Dist: pandas; extra == "all"
|
|
185
185
|
Dynamic: author
|
|
186
186
|
Dynamic: author-email
|
|
187
187
|
Dynamic: description
|
|
@@ -29,6 +29,7 @@ from fmtr.tools.constants import Constants
|
|
|
29
29
|
# Submodules
|
|
30
30
|
from fmtr.tools.path_tools import Path, PackagePaths, AppPaths
|
|
31
31
|
from fmtr.tools import ai_tools as ai
|
|
32
|
+
from fmtr.tools import dns_tools as dns
|
|
32
33
|
|
|
33
34
|
import fmtr.tools.setup_tools as setup
|
|
34
35
|
from fmtr.tools.setup_tools import Setup, SetupPaths, Dependencies, Tools
|
|
@@ -172,11 +173,6 @@ try:
|
|
|
172
173
|
except ImportError as exception:
|
|
173
174
|
patterns = MissingExtraMockModule('patterns', exception)
|
|
174
175
|
|
|
175
|
-
try:
|
|
176
|
-
from fmtr.tools import dns_tools as dns
|
|
177
|
-
except ImportError as exception:
|
|
178
|
-
dns = MissingExtraMockModule('dns', exception)
|
|
179
|
-
|
|
180
176
|
try:
|
|
181
177
|
from fmtr.tools import http_tools as http
|
|
182
178
|
from fmtr.tools.http_tools import Client
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import dns
|
|
2
|
+
from dns import query
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from httpx_retries import Retry, RetryTransport
|
|
5
|
+
|
|
6
|
+
from fmtr.tools import http_tools as http
|
|
7
|
+
from fmtr.tools.dns_tools.dm import Exchange, Response, Request
|
|
8
|
+
from fmtr.tools.logging_tools import logger
|
|
9
|
+
|
|
10
|
+
RETRY_STRATEGY = Retry(
|
|
11
|
+
total=2, # initial + 1 retry
|
|
12
|
+
allowed_methods={"GET", "POST"},
|
|
13
|
+
status_forcelist={502, 503, 504},
|
|
14
|
+
retry_on_exceptions=None, # defaults to httpx.TransportError etc.
|
|
15
|
+
backoff_factor=0.25, # short backoff (e.g. 0.25s, 0.5s)
|
|
16
|
+
max_backoff_wait=0.75, # max total delay before giving up
|
|
17
|
+
backoff_jitter=0.1, # small jitter to avoid retry bursts
|
|
18
|
+
respect_retry_after_header=False, # DoH resolvers probably won't set this
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HTTPClientDoH(http.Client):
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
Base HTTP client for DoH-appropriate retry strategy.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
TRANSPORT = RetryTransport(retry=RETRY_STRATEGY)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ClientBasePlain:
|
|
32
|
+
def __init__(self, host, port=53):
|
|
33
|
+
self.host = host
|
|
34
|
+
self.port = port
|
|
35
|
+
|
|
36
|
+
def resolve(self, exchange: Exchange):
|
|
37
|
+
with logger.span(f'UDP {self.host}:{self.port}'):
|
|
38
|
+
response = query.udp(q=exchange.request.message, where=self.host, port=self.port)
|
|
39
|
+
exchange.response_upstream = Response.from_message(response)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ClientDoH:
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
Base DoH client.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
HEADERS = {"Content-Type": "application/dns-message"}
|
|
50
|
+
client = HTTPClientDoH()
|
|
51
|
+
|
|
52
|
+
def __init__(self, host, url):
|
|
53
|
+
self.host = host
|
|
54
|
+
self.url = url
|
|
55
|
+
self.bootstrap = ClientBasePlain('8.8.8.8')
|
|
56
|
+
|
|
57
|
+
@cached_property
|
|
58
|
+
def ip(self):
|
|
59
|
+
message = dns.message.make_query(self.host, dns.rdatatype.A, flags=0)
|
|
60
|
+
request = Request.from_message(message)
|
|
61
|
+
exchange = Exchange(request=request, ip=None, port=None)
|
|
62
|
+
self.bootstrap.resolve(exchange)
|
|
63
|
+
ip = next(iter(exchange.response_upstream.answer.items.keys())).address
|
|
64
|
+
return ip
|
|
65
|
+
|
|
66
|
+
def resolve(self, exchange: Exchange):
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
Resolve via DoH
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
request = exchange.request
|
|
73
|
+
headers = self.HEADERS | dict(Host=self.host)
|
|
74
|
+
url = self.url.format(host=self.ip)
|
|
75
|
+
response_doh = self.client.post(url, headers=headers, content=request.wire)
|
|
76
|
+
response_doh.raise_for_status()
|
|
77
|
+
response = Response.from_http(response_doh)
|
|
78
|
+
exchange.response_upstream = response
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import dns
|
|
2
|
+
import httpx
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from dns.message import Message
|
|
5
|
+
from functools import cached_property
|
|
6
|
+
from typing import Self, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class BaseDNSData:
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
DNS response object.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
wire: bytes
|
|
17
|
+
|
|
18
|
+
@cached_property
|
|
19
|
+
def message(self) -> Message:
|
|
20
|
+
return dns.message.from_wire(self.wire)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_message(cls, message: Message) -> Self:
|
|
24
|
+
return cls(message.to_wire())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class Response(BaseDNSData):
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
DNS response object.
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
http: Optional[httpx.Response] = None
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_http(cls, response: httpx.Response) -> Self:
|
|
39
|
+
self = cls(response.content, http=response)
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
@cached_property
|
|
43
|
+
def answer(self):
|
|
44
|
+
return self.message.answer[-1]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class Request(BaseDNSData):
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
DNS request object.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
wire: bytes
|
|
55
|
+
|
|
56
|
+
@cached_property
|
|
57
|
+
def question(self):
|
|
58
|
+
return self.message.question[0]
|
|
59
|
+
|
|
60
|
+
@cached_property
|
|
61
|
+
def is_valid(self):
|
|
62
|
+
return len(self.message.question) != 0
|
|
63
|
+
|
|
64
|
+
@cached_property
|
|
65
|
+
def type(self):
|
|
66
|
+
return self.question.rdtype
|
|
67
|
+
|
|
68
|
+
@cached_property
|
|
69
|
+
def type_text(self):
|
|
70
|
+
return dns.rdatatype.to_text(self.type)
|
|
71
|
+
|
|
72
|
+
@cached_property
|
|
73
|
+
def name(self):
|
|
74
|
+
return self.question.name
|
|
75
|
+
|
|
76
|
+
@cached_property
|
|
77
|
+
def name_text(self):
|
|
78
|
+
return self.name.to_text()
|
|
79
|
+
|
|
80
|
+
@cached_property
|
|
81
|
+
def blackhole(self) -> Response:
|
|
82
|
+
blackhole = dns.message.make_response(self.message)
|
|
83
|
+
blackhole.flags |= dns.flags.RA
|
|
84
|
+
blackhole.set_rcode(dns.rcode.NXDOMAIN)
|
|
85
|
+
response = Response.from_message(blackhole)
|
|
86
|
+
return response
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class Exchange:
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
Entire DNS exchange for a DNS Proxy: request -> upstream response -> response
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
ip: str
|
|
97
|
+
port: int
|
|
98
|
+
|
|
99
|
+
request: Request
|
|
100
|
+
response: Optional[Response] = None
|
|
101
|
+
response_upstream: Optional[Response] = None
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def from_wire(cls, wire: bytes, ip: str, port: int) -> Self:
|
|
105
|
+
request = Request(wire)
|
|
106
|
+
return cls(request=request, ip=ip, port=port)
|
|
107
|
+
|
|
108
|
+
@cached_property
|
|
109
|
+
def client(self):
|
|
110
|
+
return f'{self.ip}:{self.port}'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
|
|
3
|
+
from fmtr.tools import logger
|
|
4
|
+
from fmtr.tools.dns_tools.client import ClientDoH
|
|
5
|
+
from fmtr.tools.dns_tools.dm import Exchange, Response
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ServerBasePlain:
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
Base for starting a plain DNS server
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, host, port):
|
|
16
|
+
self.host = host
|
|
17
|
+
self.port = port
|
|
18
|
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
19
|
+
|
|
20
|
+
def resolve(self, exchange: Exchange):
|
|
21
|
+
raise NotImplemented
|
|
22
|
+
|
|
23
|
+
def start(self):
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
Listen and resolve via overridden resolve method.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
30
|
+
sock.bind((self.host, self.port))
|
|
31
|
+
print(f"Listening on {self.host}:{self.port}")
|
|
32
|
+
while True:
|
|
33
|
+
data, (ip, port) = sock.recvfrom(512)
|
|
34
|
+
exchange = Exchange.from_wire(data, ip=ip, port=port)
|
|
35
|
+
self.resolve(exchange)
|
|
36
|
+
sock.sendto(exchange.response.wire, (ip, port))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ServerBaseDoHProxy(ServerBasePlain):
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
Base for a DNS Proxy server
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, host, port, client: ClientDoH):
|
|
47
|
+
super().__init__(host, port)
|
|
48
|
+
self.client = client
|
|
49
|
+
|
|
50
|
+
def process_question(self, exchange: Exchange):
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
def process_upstream(self, exchange: Exchange):
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
def from_upstream(self, exchange: Exchange) -> Exchange:
|
|
57
|
+
|
|
58
|
+
request = exchange.request
|
|
59
|
+
response_doh = self.client.post(self.URL, headers=self.HEADERS, content=request.wire)
|
|
60
|
+
response_doh.raise_for_status()
|
|
61
|
+
response = Response.from_http(response_doh)
|
|
62
|
+
exchange.response_upstream = response
|
|
63
|
+
|
|
64
|
+
return exchange
|
|
65
|
+
|
|
66
|
+
def resolve(self, exchange: Exchange):
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
Resolve a request, processing each stage, initial question, upstream response etc.
|
|
70
|
+
Subclasses can override the relevant processing methods to implement custom behaviour.
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
request = exchange.request
|
|
75
|
+
|
|
76
|
+
with logger.span(f'Handling request for {request.name_text} from {exchange.client}...'):
|
|
77
|
+
|
|
78
|
+
if not request.is_valid:
|
|
79
|
+
raise ValueError(f'Only one question per request is supported. Got {len(request.question)} questions.')
|
|
80
|
+
|
|
81
|
+
with logger.span(f'Processing question...'):
|
|
82
|
+
self.process_question(exchange)
|
|
83
|
+
if exchange.response:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
with logger.span(f'Making upstream request for {request.name_text}...'):
|
|
87
|
+
self.client.resolve(exchange)
|
|
88
|
+
|
|
89
|
+
with logger.span(f'Processing upstream response...'):
|
|
90
|
+
self.process_upstream(exchange)
|
|
91
|
+
|
|
92
|
+
if exchange.response:
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
exchange.response = exchange.response_upstream
|
|
96
|
+
return
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import regex as re
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
from functools import cached_property
|
|
3
4
|
from typing import List
|
|
4
5
|
|
|
5
|
-
import regex as re
|
|
6
|
-
|
|
7
6
|
from fmtr.tools.logging_tools import logger
|
|
8
7
|
|
|
9
8
|
|
|
@@ -158,7 +157,11 @@ class Rewriter:
|
|
|
158
157
|
|
|
159
158
|
previous = new
|
|
160
159
|
|
|
161
|
-
|
|
160
|
+
if len(history) == 1:
|
|
161
|
+
history_str = 'No rewrites performed.'
|
|
162
|
+
else:
|
|
163
|
+
history_str = get_history_str()
|
|
164
|
+
logger.debug(f'Finished rewriting: {history_str}')
|
|
162
165
|
|
|
163
166
|
return previous
|
|
164
167
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.2.5
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fmtr.tools
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.5
|
|
4
4
|
Summary: Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML
|
|
5
5
|
Home-page: https://github.com/fmtr/tools
|
|
6
6
|
Author: Frontmatter
|
|
@@ -128,60 +128,60 @@ Requires-Dist: logfire[httpx]; extra == "http"
|
|
|
128
128
|
Provides-Extra: setup
|
|
129
129
|
Requires-Dist: setuptools; extra == "setup"
|
|
130
130
|
Provides-Extra: all
|
|
131
|
-
Requires-Dist: logfire; extra == "all"
|
|
132
|
-
Requires-Dist: tokenizers; extra == "all"
|
|
133
|
-
Requires-Dist: appdirs; extra == "all"
|
|
134
|
-
Requires-Dist: pydantic-settings; extra == "all"
|
|
135
|
-
Requires-Dist: filetype; extra == "all"
|
|
136
|
-
Requires-Dist: pymupdf; extra == "all"
|
|
137
|
-
Requires-Dist: json_repair; extra == "all"
|
|
138
|
-
Requires-Dist: sentence_transformers; extra == "all"
|
|
139
|
-
Requires-Dist: fastapi; extra == "all"
|
|
140
|
-
Requires-Dist: tabulate; extra == "all"
|
|
141
|
-
Requires-Dist: distributed; extra == "all"
|
|
142
|
-
Requires-Dist: faker; extra == "all"
|
|
143
|
-
Requires-Dist: semver; extra == "all"
|
|
144
|
-
Requires-Dist: uvicorn[standard]; extra == "all"
|
|
145
|
-
Requires-Dist: html2text; extra == "all"
|
|
146
|
-
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
147
|
-
Requires-Dist: bokeh; extra == "all"
|
|
148
|
-
Requires-Dist: flet-webview; extra == "all"
|
|
149
|
-
Requires-Dist: logfire[httpx]; extra == "all"
|
|
150
131
|
Requires-Dist: torchaudio; extra == "all"
|
|
151
|
-
Requires-Dist: Unidecode; extra == "all"
|
|
152
|
-
Requires-Dist: flet-video; extra == "all"
|
|
153
|
-
Requires-Dist: google-auth; extra == "all"
|
|
154
|
-
Requires-Dist: flet[all]; extra == "all"
|
|
155
|
-
Requires-Dist: openai; extra == "all"
|
|
156
|
-
Requires-Dist: pymupdf4llm; extra == "all"
|
|
157
|
-
Requires-Dist: dnspython[doh]; extra == "all"
|
|
158
|
-
Requires-Dist: deepmerge; extra == "all"
|
|
159
|
-
Requires-Dist: contexttimer; extra == "all"
|
|
160
|
-
Requires-Dist: openpyxl; extra == "all"
|
|
161
|
-
Requires-Dist: tinynetrc; extra == "all"
|
|
162
|
-
Requires-Dist: ollama; extra == "all"
|
|
163
|
-
Requires-Dist: huggingface_hub; extra == "all"
|
|
164
132
|
Requires-Dist: google-auth-oauthlib; extra == "all"
|
|
133
|
+
Requires-Dist: deepmerge; extra == "all"
|
|
165
134
|
Requires-Dist: httpx_retries; extra == "all"
|
|
166
|
-
Requires-Dist: docker; extra == "all"
|
|
167
|
-
Requires-Dist: setuptools; extra == "all"
|
|
168
|
-
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
169
|
-
Requires-Dist: google-api-python-client; extra == "all"
|
|
170
|
-
Requires-Dist: torchvision; extra == "all"
|
|
171
|
-
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
172
|
-
Requires-Dist: pandas; extra == "all"
|
|
173
135
|
Requires-Dist: regex; extra == "all"
|
|
174
|
-
Requires-Dist:
|
|
136
|
+
Requires-Dist: dask[bag]; extra == "all"
|
|
137
|
+
Requires-Dist: pytest-cov; extra == "all"
|
|
138
|
+
Requires-Dist: flet-video; extra == "all"
|
|
139
|
+
Requires-Dist: uvicorn[standard]; extra == "all"
|
|
140
|
+
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
141
|
+
Requires-Dist: logfire; extra == "all"
|
|
142
|
+
Requires-Dist: bokeh; extra == "all"
|
|
175
143
|
Requires-Dist: sre_yield; extra == "all"
|
|
144
|
+
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
145
|
+
Requires-Dist: pymupdf4llm; extra == "all"
|
|
146
|
+
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
147
|
+
Requires-Dist: semver; extra == "all"
|
|
148
|
+
Requires-Dist: yamlscript; extra == "all"
|
|
149
|
+
Requires-Dist: torchvision; extra == "all"
|
|
150
|
+
Requires-Dist: pydevd-pycharm; extra == "all"
|
|
151
|
+
Requires-Dist: tinynetrc; extra == "all"
|
|
176
152
|
Requires-Dist: pyyaml; extra == "all"
|
|
153
|
+
Requires-Dist: openai; extra == "all"
|
|
154
|
+
Requires-Dist: sentence_transformers; extra == "all"
|
|
155
|
+
Requires-Dist: pymupdf; extra == "all"
|
|
156
|
+
Requires-Dist: pydantic-settings; extra == "all"
|
|
157
|
+
Requires-Dist: httpx; extra == "all"
|
|
158
|
+
Requires-Dist: tabulate; extra == "all"
|
|
159
|
+
Requires-Dist: setuptools; extra == "all"
|
|
160
|
+
Requires-Dist: diskcache; extra == "all"
|
|
161
|
+
Requires-Dist: distributed; extra == "all"
|
|
162
|
+
Requires-Dist: huggingface_hub; extra == "all"
|
|
163
|
+
Requires-Dist: flet-webview; extra == "all"
|
|
177
164
|
Requires-Dist: peft; extra == "all"
|
|
165
|
+
Requires-Dist: google-api-python-client; extra == "all"
|
|
166
|
+
Requires-Dist: contexttimer; extra == "all"
|
|
167
|
+
Requires-Dist: docker; extra == "all"
|
|
178
168
|
Requires-Dist: pydantic; extra == "all"
|
|
179
|
-
Requires-Dist:
|
|
180
|
-
Requires-Dist:
|
|
169
|
+
Requires-Dist: dnspython[doh]; extra == "all"
|
|
170
|
+
Requires-Dist: openpyxl; extra == "all"
|
|
171
|
+
Requires-Dist: filetype; extra == "all"
|
|
172
|
+
Requires-Dist: fastapi; extra == "all"
|
|
173
|
+
Requires-Dist: faker; extra == "all"
|
|
181
174
|
Requires-Dist: transformers[sentencepiece]; extra == "all"
|
|
182
|
-
Requires-Dist:
|
|
183
|
-
Requires-Dist:
|
|
184
|
-
Requires-Dist:
|
|
175
|
+
Requires-Dist: Unidecode; extra == "all"
|
|
176
|
+
Requires-Dist: appdirs; extra == "all"
|
|
177
|
+
Requires-Dist: logfire[httpx]; extra == "all"
|
|
178
|
+
Requires-Dist: tokenizers; extra == "all"
|
|
179
|
+
Requires-Dist: html2text; extra == "all"
|
|
180
|
+
Requires-Dist: ollama; extra == "all"
|
|
181
|
+
Requires-Dist: json_repair; extra == "all"
|
|
182
|
+
Requires-Dist: flet[all]; extra == "all"
|
|
183
|
+
Requires-Dist: google-auth; extra == "all"
|
|
184
|
+
Requires-Dist: pandas; extra == "all"
|
|
185
185
|
Dynamic: author
|
|
186
186
|
Dynamic: author-email
|
|
187
187
|
Dynamic: description
|
|
@@ -12,7 +12,6 @@ setup.py
|
|
|
12
12
|
./fmtr/tools/dataclass_tools.py
|
|
13
13
|
./fmtr/tools/datatype_tools.py
|
|
14
14
|
./fmtr/tools/debugging_tools.py
|
|
15
|
-
./fmtr/tools/dns_tools.py
|
|
16
15
|
./fmtr/tools/docker_tools.py
|
|
17
16
|
./fmtr/tools/environment_tools.py
|
|
18
17
|
./fmtr/tools/function_tools.py
|
|
@@ -55,6 +54,10 @@ setup.py
|
|
|
55
54
|
./fmtr/tools/ai_tools/__init__.py
|
|
56
55
|
./fmtr/tools/ai_tools/agentic_tools.py
|
|
57
56
|
./fmtr/tools/ai_tools/inference_tools.py
|
|
57
|
+
./fmtr/tools/dns_tools/__init__.py
|
|
58
|
+
./fmtr/tools/dns_tools/client.py
|
|
59
|
+
./fmtr/tools/dns_tools/dm.py
|
|
60
|
+
./fmtr/tools/dns_tools/server.py
|
|
58
61
|
./fmtr/tools/entrypoints/__init__.py
|
|
59
62
|
./fmtr/tools/entrypoints/cache_hfh.py
|
|
60
63
|
./fmtr/tools/entrypoints/ep_test.py
|
|
@@ -90,7 +93,6 @@ fmtr/tools/data_modelling_tools.py
|
|
|
90
93
|
fmtr/tools/dataclass_tools.py
|
|
91
94
|
fmtr/tools/datatype_tools.py
|
|
92
95
|
fmtr/tools/debugging_tools.py
|
|
93
|
-
fmtr/tools/dns_tools.py
|
|
94
96
|
fmtr/tools/docker_tools.py
|
|
95
97
|
fmtr/tools/environment_tools.py
|
|
96
98
|
fmtr/tools/function_tools.py
|
|
@@ -133,6 +135,10 @@ fmtr/tools/yaml_tools.py
|
|
|
133
135
|
fmtr/tools/ai_tools/__init__.py
|
|
134
136
|
fmtr/tools/ai_tools/agentic_tools.py
|
|
135
137
|
fmtr/tools/ai_tools/inference_tools.py
|
|
138
|
+
fmtr/tools/dns_tools/__init__.py
|
|
139
|
+
fmtr/tools/dns_tools/client.py
|
|
140
|
+
fmtr/tools/dns_tools/dm.py
|
|
141
|
+
fmtr/tools/dns_tools/server.py
|
|
136
142
|
fmtr/tools/entrypoints/__init__.py
|
|
137
143
|
fmtr/tools/entrypoints/cache_hfh.py
|
|
138
144
|
fmtr/tools/entrypoints/ep_test.py
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import dns
|
|
2
|
-
import httpx
|
|
3
|
-
import socket
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from dns.message import Message
|
|
6
|
-
from functools import cached_property
|
|
7
|
-
from httpx_retries import Retry, RetryTransport
|
|
8
|
-
from typing import Optional, Self
|
|
9
|
-
|
|
10
|
-
from fmtr.tools import Client, logger
|
|
11
|
-
|
|
12
|
-
RETRY_STRATEGY = Retry(
|
|
13
|
-
total=2, # initial + 1 retry
|
|
14
|
-
allowed_methods={"GET", "POST"},
|
|
15
|
-
status_forcelist={502, 503, 504},
|
|
16
|
-
retry_on_exceptions=None, # defaults to httpx.TransportError etc.
|
|
17
|
-
backoff_factor=0.25, # short backoff (e.g. 0.25s, 0.5s)
|
|
18
|
-
max_backoff_wait=0.75, # max total delay before giving up
|
|
19
|
-
backoff_jitter=0.1, # small jitter to avoid retry bursts
|
|
20
|
-
respect_retry_after_header=False, # DoH resolvers probably won't set this
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class HTTPClientDoH(Client):
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
Base HTTP client for DoH-appropriate retry strategy.
|
|
28
|
-
|
|
29
|
-
"""
|
|
30
|
-
TRANSPORT = RetryTransport(retry=RETRY_STRATEGY)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@dataclass
|
|
34
|
-
class BaseDNSData:
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
DNS response object.
|
|
38
|
-
|
|
39
|
-
"""
|
|
40
|
-
wire: bytes
|
|
41
|
-
|
|
42
|
-
@cached_property
|
|
43
|
-
def message(self) -> Message:
|
|
44
|
-
return dns.message.from_wire(self.wire)
|
|
45
|
-
|
|
46
|
-
@classmethod
|
|
47
|
-
def from_message(cls, message: Message) -> Self:
|
|
48
|
-
return cls(message.to_wire())
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@dataclass
|
|
52
|
-
class Response(BaseDNSData):
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
DNS response object.
|
|
56
|
-
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
http: Optional[httpx.Response] = None
|
|
60
|
-
|
|
61
|
-
@classmethod
|
|
62
|
-
def from_http(cls, response: httpx.Response) -> Self:
|
|
63
|
-
self = cls(response.content, http=response)
|
|
64
|
-
return self
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@dataclass
|
|
68
|
-
class Request(BaseDNSData):
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
|
-
DNS request object.
|
|
72
|
-
|
|
73
|
-
"""
|
|
74
|
-
wire: bytes
|
|
75
|
-
|
|
76
|
-
@cached_property
|
|
77
|
-
def question(self):
|
|
78
|
-
return self.message.question[0]
|
|
79
|
-
|
|
80
|
-
@cached_property
|
|
81
|
-
def is_valid(self):
|
|
82
|
-
return len(self.message.question) != 0
|
|
83
|
-
|
|
84
|
-
@cached_property
|
|
85
|
-
def type(self):
|
|
86
|
-
return self.question.rdtype
|
|
87
|
-
|
|
88
|
-
@cached_property
|
|
89
|
-
def type_text(self):
|
|
90
|
-
return dns.rdatatype.to_text(self.type)
|
|
91
|
-
|
|
92
|
-
@cached_property
|
|
93
|
-
def name(self):
|
|
94
|
-
return self.question.name
|
|
95
|
-
|
|
96
|
-
@cached_property
|
|
97
|
-
def name_text(self):
|
|
98
|
-
return self.name.to_text()
|
|
99
|
-
|
|
100
|
-
@cached_property
|
|
101
|
-
def blackhole(self) -> Response:
|
|
102
|
-
blackhole = dns.message.make_response(self.message)
|
|
103
|
-
blackhole.flags |= dns.flags.RA
|
|
104
|
-
blackhole.set_rcode(dns.rcode.NXDOMAIN)
|
|
105
|
-
response = Response.from_message(blackhole)
|
|
106
|
-
return response
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
@dataclass
|
|
110
|
-
class Exchange:
|
|
111
|
-
"""
|
|
112
|
-
|
|
113
|
-
Entire DNS exchange for a DNS Proxy: request -> upstream response -> response
|
|
114
|
-
|
|
115
|
-
"""
|
|
116
|
-
ip: str
|
|
117
|
-
port: int
|
|
118
|
-
|
|
119
|
-
request: Request
|
|
120
|
-
response: Optional[Response] = None
|
|
121
|
-
response_upstream: Optional[Response] = None
|
|
122
|
-
|
|
123
|
-
@classmethod
|
|
124
|
-
def from_wire(cls, wire: bytes, ip: str, port: int) -> Self:
|
|
125
|
-
request = Request(wire)
|
|
126
|
-
return cls(request=request, ip=ip, port=port)
|
|
127
|
-
|
|
128
|
-
@cached_property
|
|
129
|
-
def client(self):
|
|
130
|
-
return f'{self.ip}:{self.port}'
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class BasePlain:
|
|
134
|
-
"""
|
|
135
|
-
|
|
136
|
-
Base for starting a plain DNS server
|
|
137
|
-
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
def __init__(self, host, port):
|
|
141
|
-
self.host = host
|
|
142
|
-
self.port = port
|
|
143
|
-
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
144
|
-
|
|
145
|
-
def resolve(self, exchange: Exchange):
|
|
146
|
-
raise NotImplemented
|
|
147
|
-
|
|
148
|
-
def start(self):
|
|
149
|
-
"""
|
|
150
|
-
|
|
151
|
-
Listen and resolve via overridden resolve method.
|
|
152
|
-
|
|
153
|
-
"""
|
|
154
|
-
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
155
|
-
sock.bind((self.host, self.port))
|
|
156
|
-
print(f"Listening on {self.host}:{self.port}")
|
|
157
|
-
while True:
|
|
158
|
-
data, (ip, port) = sock.recvfrom(512)
|
|
159
|
-
exchange = Exchange.from_wire(data, ip=ip, port=port)
|
|
160
|
-
self.resolve(exchange)
|
|
161
|
-
sock.sendto(exchange.response.wire, (ip, port))
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
class BaseDoHProxy(BasePlain):
|
|
165
|
-
"""
|
|
166
|
-
|
|
167
|
-
Base for a DNS Proxy server
|
|
168
|
-
|
|
169
|
-
"""
|
|
170
|
-
|
|
171
|
-
URL = None
|
|
172
|
-
HEADERS = {"Content-Type": "application/dns-message"}
|
|
173
|
-
client = HTTPClientDoH()
|
|
174
|
-
|
|
175
|
-
def process_question(self, exchange: Exchange):
|
|
176
|
-
return
|
|
177
|
-
|
|
178
|
-
def process_upstream(self, exchange: Exchange):
|
|
179
|
-
return
|
|
180
|
-
|
|
181
|
-
def from_upstream(self, exchange: Exchange) -> Exchange:
|
|
182
|
-
|
|
183
|
-
request = exchange.request
|
|
184
|
-
response_doh = self.client.post(self.URL, headers=self.HEADERS, content=request.wire)
|
|
185
|
-
response_doh.raise_for_status()
|
|
186
|
-
response = Response.from_http(response_doh)
|
|
187
|
-
exchange.response_upstream = response
|
|
188
|
-
|
|
189
|
-
return exchange
|
|
190
|
-
|
|
191
|
-
def resolve(self, exchange: Exchange):
|
|
192
|
-
"""
|
|
193
|
-
|
|
194
|
-
Resolve a request, processing each stage, initial question, upstream response etc.
|
|
195
|
-
Subclasses can override the relevant processing methods to implement custom behaviour.
|
|
196
|
-
|
|
197
|
-
"""
|
|
198
|
-
|
|
199
|
-
request = exchange.request
|
|
200
|
-
|
|
201
|
-
with logger.span(f'Handling request for {request.name_text} from {exchange.client}...'):
|
|
202
|
-
|
|
203
|
-
if not request.is_valid:
|
|
204
|
-
raise ValueError(f'Only one question per request is supported. Got {len(request.question)} questions.')
|
|
205
|
-
|
|
206
|
-
with logger.span(f'Processing question...'):
|
|
207
|
-
self.process_question(exchange)
|
|
208
|
-
if exchange.response:
|
|
209
|
-
return
|
|
210
|
-
|
|
211
|
-
with logger.span(f'Making upstream request for {request.name_text}...'):
|
|
212
|
-
self.from_upstream(exchange)
|
|
213
|
-
|
|
214
|
-
with logger.span(f'Processing upstream response...'):
|
|
215
|
-
self.process_upstream(exchange)
|
|
216
|
-
|
|
217
|
-
if exchange.response:
|
|
218
|
-
return
|
|
219
|
-
|
|
220
|
-
exchange.response = exchange.response_upstream
|
|
221
|
-
return
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.2.3
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -15,60 +15,60 @@ pydantic-ai[logfire,openai]
|
|
|
15
15
|
ollama
|
|
16
16
|
|
|
17
17
|
[all]
|
|
18
|
-
logfire
|
|
19
|
-
tokenizers
|
|
20
|
-
appdirs
|
|
21
|
-
pydantic-settings
|
|
22
|
-
filetype
|
|
23
|
-
pymupdf
|
|
24
|
-
json_repair
|
|
25
|
-
sentence_transformers
|
|
26
|
-
fastapi
|
|
27
|
-
tabulate
|
|
28
|
-
distributed
|
|
29
|
-
faker
|
|
30
|
-
semver
|
|
31
|
-
uvicorn[standard]
|
|
32
|
-
html2text
|
|
33
|
-
google-auth-httplib2
|
|
34
|
-
bokeh
|
|
35
|
-
flet-webview
|
|
36
|
-
logfire[httpx]
|
|
37
18
|
torchaudio
|
|
38
|
-
Unidecode
|
|
39
|
-
flet-video
|
|
40
|
-
google-auth
|
|
41
|
-
flet[all]
|
|
42
|
-
openai
|
|
43
|
-
pymupdf4llm
|
|
44
|
-
dnspython[doh]
|
|
45
|
-
deepmerge
|
|
46
|
-
contexttimer
|
|
47
|
-
openpyxl
|
|
48
|
-
tinynetrc
|
|
49
|
-
ollama
|
|
50
|
-
huggingface_hub
|
|
51
19
|
google-auth-oauthlib
|
|
20
|
+
deepmerge
|
|
52
21
|
httpx_retries
|
|
53
|
-
docker
|
|
54
|
-
setuptools
|
|
55
|
-
pydantic-ai[logfire,openai]
|
|
56
|
-
google-api-python-client
|
|
57
|
-
torchvision
|
|
58
|
-
logfire[fastapi]
|
|
59
|
-
pandas
|
|
60
22
|
regex
|
|
61
|
-
|
|
23
|
+
dask[bag]
|
|
24
|
+
pytest-cov
|
|
25
|
+
flet-video
|
|
26
|
+
uvicorn[standard]
|
|
27
|
+
logfire[fastapi]
|
|
28
|
+
logfire
|
|
29
|
+
bokeh
|
|
62
30
|
sre_yield
|
|
31
|
+
google-auth-httplib2
|
|
32
|
+
pymupdf4llm
|
|
33
|
+
pydantic-ai[logfire,openai]
|
|
34
|
+
semver
|
|
35
|
+
yamlscript
|
|
36
|
+
torchvision
|
|
37
|
+
pydevd-pycharm
|
|
38
|
+
tinynetrc
|
|
63
39
|
pyyaml
|
|
40
|
+
openai
|
|
41
|
+
sentence_transformers
|
|
42
|
+
pymupdf
|
|
43
|
+
pydantic-settings
|
|
44
|
+
httpx
|
|
45
|
+
tabulate
|
|
46
|
+
setuptools
|
|
47
|
+
diskcache
|
|
48
|
+
distributed
|
|
49
|
+
huggingface_hub
|
|
50
|
+
flet-webview
|
|
64
51
|
peft
|
|
52
|
+
google-api-python-client
|
|
53
|
+
contexttimer
|
|
54
|
+
docker
|
|
65
55
|
pydantic
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
dnspython[doh]
|
|
57
|
+
openpyxl
|
|
58
|
+
filetype
|
|
59
|
+
fastapi
|
|
60
|
+
faker
|
|
68
61
|
transformers[sentencepiece]
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
Unidecode
|
|
63
|
+
appdirs
|
|
64
|
+
logfire[httpx]
|
|
65
|
+
tokenizers
|
|
66
|
+
html2text
|
|
67
|
+
ollama
|
|
68
|
+
json_repair
|
|
69
|
+
flet[all]
|
|
70
|
+
google-auth
|
|
71
|
+
pandas
|
|
72
72
|
|
|
73
73
|
[api]
|
|
74
74
|
fastapi
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|