dosev 1.0.0__py3-none-any.whl

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.
dosev/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from .resolver import DNSResolver
2
+ from .server import run_server_sync
3
+ from .config import load_config
4
+ from .utils import fetch_blocklists
5
+
6
+ __all__ = ["DNSResolver", "run_server_sync", "load_config", "fetch_blocklists"]
dosev/cli.py ADDED
@@ -0,0 +1,81 @@
1
+ import os
2
+ import sys
3
+ import argparse
4
+ from .config import load_config, _default_log_dir
5
+ from .server import run_server_sync
6
+
7
+ def main() -> None:
8
+ parser = argparse.ArgumentParser(description="dosev DNS server")
9
+ parser.add_argument("--config", "-c", default="config/dosev.conf",
10
+ help="Path to configuration file (default: config/dosev.conf)")
11
+ parser.add_argument("--check-config", action="store_true",
12
+ help="Validate the configuration and exit without starting the server")
13
+ args = parser.parse_args()
14
+
15
+ try:
16
+ config = load_config(args.config)
17
+ except Exception as e:
18
+ print(f"Failed to load config: {e}", file=sys.stderr)
19
+ sys.exit(1)
20
+
21
+ if args.check_config:
22
+ print("Configuration is valid.")
23
+ return
24
+
25
+ run_server_sync(
26
+ listen_ip=config["listen_ip"],
27
+ listen_port=config["listen_port"],
28
+ upstream_dns=config["upstream_dns"],
29
+ protocol=config["protocol"],
30
+ verbose=config.get("verbose", False),
31
+ blocklists=config.get("blocklists"),
32
+ disable_ipv6=config.get("disable_ipv6", False),
33
+ dns_cache_ttl=config.get("dns_cache_ttl", 300),
34
+ dns_cache_max_size=config.get("dns_cache_max_size", 1024),
35
+ dns_negative_cache_ttl=config.get("dns_negative_cache_ttl", 5),
36
+ dns_logging_enabled=config.get("dns_logging_enabled", False),
37
+ dns_log_retention_days=config.get("dns_log_retention_days", 7),
38
+ dns_log_dir=config.get("dns_log_dir", _default_log_dir()),
39
+ dns_log_prefix=config.get("dns_log_prefix", "dns-log"),
40
+ dns_pinned_certs=config.get("dns_pinned_certs", {}),
41
+ dnssec_enabled=config.get("dnssec_enabled", False),
42
+ auto_update_trust_anchor=config.get("auto_update_trust_anchor", True), # NEW
43
+ trust_anchors_file=config.get("trust_anchors_file", ""),
44
+ metrics_enabled=config.get("metrics_enabled", False),
45
+ metrics_port=config.get("metrics_port", 8000),
46
+ uvloop_enable=config.get("uvloop_enable", False),
47
+ upstream_retries=config.get("upstream_retries", 2),
48
+ upstream_udp_timeout=config.get("upstream_udp_timeout", 2.0),
49
+ upstream_tcp_timeout=config.get("upstream_tcp_timeout", 5.0),
50
+ upstream_doh_timeout=config.get("upstream_doh_timeout", 5.0),
51
+ rate_limit_rps=config.get("rate_limit_rps", 0.0),
52
+ rate_limit_burst=config.get("rate_limit_burst", 0.0),
53
+ upstreams=config.get("upstreams", []),
54
+ optimistic_cache_enabled=config.get("optimistic_cache_enabled", False),
55
+ optimistic_stale_max_age=config.get("optimistic_stale_max_age", 86400),
56
+ optimistic_stale_response_ttl=config.get("optimistic_stale_response_ttl", 30),
57
+ dns_privilege_drop_user=config.get("dns_privilege_drop_user", ""),
58
+ dns_privilege_drop_group=config.get("dns_privilege_drop_group", ""),
59
+ dns_chroot_dir=config.get("dns_chroot_dir", ""),
60
+ dns_rebind_protection=config.get("dns_rebind_protection", False),
61
+ dns_rebind_action=config.get("dns_rebind_action", "strip"),
62
+ dns_ecs_enabled=config.get("dns_ecs_enabled", True),
63
+ dns_max_payload=config.get("dns_max_payload", 4096),
64
+ dns_enable_dot=config.get("dns_enable_dot", False),
65
+ dns_dot_port=config.get("dns_dot_port", 853),
66
+ dns_dot_cert_file=config.get("dns_dot_cert_file", ""),
67
+ dns_dot_key_file=config.get("dns_dot_key_file", ""),
68
+ dns_enable_doh=config.get("dns_enable_doh", False),
69
+ dns_doh_port=config.get("dns_doh_port", 443),
70
+ dns_doh_cert_file=config.get("dns_doh_cert_file", ""),
71
+ dns_doh_key_file=config.get("dns_doh_key_file", ""),
72
+ dns_doh_path=config.get("dns_doh_path", "/dns-query"),
73
+ pool_max_size=config.get("pool_max_size", 5),
74
+ pool_idle_timeout=config.get("pool_idle_timeout", 60.0),
75
+ doh_version=config.get("doh_version", "auto"),
76
+ doh_auto_cache_ttl=config.get("doh_auto_cache_ttl", 3600),
77
+ bootstrap=config.get("bootstrap", {"servers": [], "timeout": 2.0, "retries": 2}),
78
+ )
79
+
80
+ if __name__ == "__main__":
81
+ main()
dosev/config.py ADDED
@@ -0,0 +1,295 @@
1
+ import os
2
+ import configparser
3
+ import warnings
4
+ from typing import Dict, Any, List, Optional
5
+
6
+
7
+ def _default_log_dir() -> str:
8
+ if os.name == 'nt':
9
+ return os.path.join(os.getenv('LOCALAPPDATA') or os.path.expanduser('~'), 'dosev', 'logs')
10
+ return '/var/log/dosev'
11
+
12
+
13
+ def _validate_and_warn(config: Dict[str, Any]) -> None:
14
+ listen_port = config.get('listen_port', 53)
15
+ if not isinstance(listen_port, int) or not 1 <= listen_port <= 65535:
16
+ raise ValueError('listen_port must be between 1 and 65535')
17
+
18
+ dns_max_payload = config.get('dns_max_payload', 4096)
19
+ if not isinstance(dns_max_payload, int) or not 512 <= dns_max_payload <= 4096:
20
+ raise ValueError('dns_max_payload must be between 512 and 4096')
21
+
22
+ protocol = config.get('protocol', 'udp')
23
+ if protocol not in {'udp', 'tcp', 'tls', 'https', 'quic'}:
24
+ raise ValueError('protocol must be one of: udp, tcp, tls, https, quic')
25
+
26
+ if config.get('dns_enable_dot', False) and not (config.get('dns_dot_cert_file') and config.get('dns_dot_key_file')):
27
+ raise ValueError('dns_enable_dot requires dns_dot_cert_file and dns_dot_key_file')
28
+
29
+ if config.get('dns_enable_doh', False) and not (config.get('dns_doh_cert_file') and config.get('dns_doh_key_file')):
30
+ raise ValueError('dns_enable_doh requires dns_doh_cert_file and dns_doh_key_file')
31
+
32
+ if config.get('dns_enable_dot', False) or config.get('dns_enable_doh', False):
33
+ if config.get('protocol') == 'udp':
34
+ warnings.warn('Secure listeners are enabled while the main resolver protocol is udp; this is usually intentional but verify your upstream defaults.', RuntimeWarning)
35
+
36
+ rate_limit_rps = config.get('rate_limit_rps', 0.0)
37
+ if rate_limit_rps < 0:
38
+ raise ValueError('rate_limit_rps must be non-negative')
39
+
40
+ rate_limit_burst = config.get('rate_limit_burst', 0.0)
41
+ if rate_limit_burst < 0:
42
+ raise ValueError('rate_limit_burst must be non-negative')
43
+
44
+ if config.get('metrics_enabled', False) and config.get('metrics_port', 8000) == 53:
45
+ warnings.warn('Metrics are enabled on port 53; this may conflict with DNS traffic.', RuntimeWarning)
46
+
47
+ if config.get('doh_version', 'auto') not in {'auto', '1.1', '2', '3'}:
48
+ raise ValueError('doh_version must be auto, 1.1, 2, or 3')
49
+
50
+
51
+ def load_config(path: str = 'config/dosev.conf') -> Dict[str, Any]:
52
+ config = configparser.ConfigParser()
53
+ if not os.path.exists(path):
54
+ return {
55
+ 'listen_ip': '0.0.0.0',
56
+ 'listen_port': 53,
57
+ 'upstream_dns': '1.1.1.1',
58
+ 'protocol': 'udp',
59
+ 'verbose': False,
60
+ 'disable_ipv6': False,
61
+ 'strip_ipv6_records': None, # None = use disable_ipv6 value
62
+ 'dns_cache_ttl': 300,
63
+ 'dns_cache_max_size': 1024,
64
+ 'dns_negative_cache_ttl': 5,
65
+ 'dns_logging_enabled': False,
66
+ 'dns_log_retention_days': 7,
67
+ 'dns_log_dir': _default_log_dir(),
68
+ 'dns_log_prefix': 'dns-log',
69
+ 'dns_pinned_certs': {},
70
+ 'dns_ecs_enabled': True,
71
+ 'dnssec_enabled': False,
72
+ 'auto_update_trust_anchor': True,
73
+ 'trust_anchors_file': '',
74
+ 'metrics_enabled': False,
75
+ 'metrics_port': 8000,
76
+ 'uvloop_enable': False,
77
+ 'upstream_retries': 2,
78
+ 'upstream_udp_timeout': 2.0,
79
+ 'upstream_tcp_timeout': 5.0,
80
+ 'upstream_doh_timeout': 5.0,
81
+ 'rate_limit_rps': 0.0,
82
+ 'rate_limit_burst': 0.0,
83
+ 'upstreams': [],
84
+ 'optimistic_cache_enabled': False,
85
+ 'optimistic_stale_max_age': 86400,
86
+ 'optimistic_stale_response_ttl': 30,
87
+ 'dns_privilege_drop_user': '',
88
+ 'dns_privilege_drop_group': '',
89
+ 'dns_chroot_dir': '',
90
+ 'dns_rebind_protection': False,
91
+ 'dns_rebind_action': 'strip',
92
+ 'pool_max_size': 5,
93
+ 'pool_idle_timeout': 60.0,
94
+ 'doh_version': 'auto',
95
+ 'doh_auto_cache_ttl': 3600,
96
+ 'bootstrap': {'servers': ['1.1.1.1:53', '8.8.8.8:53'], 'timeout': 2.0, 'retries': 2},
97
+ 'blocklists': {
98
+ 'enabled': False,
99
+ 'urls': [],
100
+ 'interval_seconds': 86400,
101
+ 'action': 'NXDOMAIN',
102
+ 'local_blocklist_dir': 'blocklists',
103
+ 'reload_on_change': True,
104
+ },
105
+ }
106
+
107
+ config.read(path)
108
+
109
+ listen_ip = config.get('server', 'listen_ip', fallback='0.0.0.0')
110
+ listen_port = config.getint('server', 'listen_port', fallback=53)
111
+
112
+ upstream_dns = config.get('resolver', 'upstream_dns', fallback='1.1.1.1')
113
+ protocol = config.get('resolver', 'protocol', fallback='udp').lower()
114
+ verbose = config.getboolean('resolver', 'verbose', fallback=False)
115
+ disable_ipv6 = config.getboolean('resolver', 'disable_ipv6', fallback=False)
116
+ dns_ecs_enabled = config.getboolean('resolver', 'dns_ecs_enabled', fallback=True)
117
+ dns_max_payload = config.getint('resolver', 'dns_max_payload', fallback=4096)
118
+ dns_enable_dot = config.getboolean('resolver', 'dns_enable_dot', fallback=False)
119
+ dns_dot_port = config.getint('resolver', 'dns_dot_port', fallback=853)
120
+ dns_dot_cert_file = config.get('resolver', 'dns_dot_cert_file', fallback='')
121
+ dns_dot_key_file = config.get('resolver', 'dns_dot_key_file', fallback='')
122
+ dns_enable_doh = config.getboolean('resolver', 'dns_enable_doh', fallback=False)
123
+ dns_doh_port = config.getint('resolver', 'dns_doh_port', fallback=443)
124
+ dns_doh_cert_file = config.get('resolver', 'dns_doh_cert_file', fallback='')
125
+ dns_doh_key_file = config.get('resolver', 'dns_doh_key_file', fallback='')
126
+ dns_doh_path = config.get('resolver', 'dns_doh_path', fallback='/dns-query')
127
+ strip_ipv6_records_raw = config.get('resolver', 'strip_ipv6_records', fallback=None)
128
+ if strip_ipv6_records_raw is None:
129
+ strip_ipv6_records = None
130
+ else:
131
+ strip_ipv6_records = config.getboolean('resolver', 'strip_ipv6_records', fallback=None)
132
+
133
+ dns_cache_ttl = config.getint('cache', 'ttl', fallback=300)
134
+ dns_cache_max_size = config.getint('cache', 'max_size', fallback=1024)
135
+ dns_negative_cache_ttl = config.getint('cache', 'negative_ttl', fallback=5)
136
+
137
+ upstream_udp_timeout = config.getfloat('timeouts', 'udp', fallback=2.0)
138
+ upstream_tcp_timeout = config.getfloat('timeouts', 'tcp', fallback=5.0)
139
+ upstream_doh_timeout = config.getfloat('timeouts', 'doh', fallback=5.0)
140
+
141
+ upstream_retries = config.getint('advanced', 'retries', fallback=2)
142
+ rate_limit_rps = config.getfloat('advanced', 'rate_limit_rps', fallback=0.0)
143
+ rate_limit_burst = config.getfloat('advanced', 'rate_limit_burst', fallback=0.0)
144
+ optimistic_cache_enabled = config.getboolean('advanced', 'optimistic_cache_enabled', fallback=False)
145
+ optimistic_stale_max_age = config.getint('advanced', 'optimistic_stale_max_age', fallback=86400)
146
+ optimistic_stale_response_ttl = config.getint('advanced', 'optimistic_stale_response_ttl', fallback=30)
147
+ pool_max_size = config.getint('advanced', 'pool_max_size', fallback=5)
148
+ pool_idle_timeout = config.getfloat('advanced', 'pool_idle_timeout', fallback=60.0)
149
+ doh_version = config.get('advanced', 'doh_version', fallback='auto').lower()
150
+ if doh_version not in ('auto', '1.1', '2', '3'):
151
+ doh_version = 'auto'
152
+ doh_auto_cache_ttl = config.getint('advanced', 'doh_auto_cache_ttl', fallback=3600)
153
+
154
+ dnssec_enabled = config.getboolean('security', 'dnssec_enabled', fallback=False)
155
+ auto_update_trust_anchor = config.getboolean('security', 'auto_update_trust_anchor', fallback=True)
156
+ trust_anchors_file = config.get('security', 'trust_anchors_file', fallback='')
157
+
158
+ pinned_raw = config.get('security', 'pinned_certs', fallback='')
159
+ dns_pinned_certs = {}
160
+ for item in [s.strip() for s in pinned_raw.split(',') if s.strip()]:
161
+ if '=' in item:
162
+ host, fp = item.split('=', 1)
163
+ dns_pinned_certs[host.strip()] = fp.strip()
164
+
165
+ dns_rebind_protection = config.getboolean('security', 'rebind_protection', fallback=False)
166
+ dns_rebind_action = config.get('security', 'rebind_action', fallback='strip').lower()
167
+ if dns_rebind_action not in ('strip', 'block'):
168
+ dns_rebind_action = 'strip'
169
+
170
+ dns_privilege_drop_user = config.get('security', 'dns_privilege_drop_user', fallback='')
171
+ dns_privilege_drop_group = config.get('security', 'dns_privilege_drop_group', fallback='')
172
+ dns_chroot_dir = config.get('security', 'dns_chroot_dir', fallback='')
173
+
174
+ dns_logging_enabled = config.getboolean('logging', 'enabled', fallback=False)
175
+ dns_log_dir = config.get('logging', 'log_dir', fallback=_default_log_dir())
176
+ dns_log_retention_days = config.getint('logging', 'retention_days', fallback=7)
177
+ dns_log_prefix = config.get('logging', 'log_prefix', fallback='dns-log')
178
+
179
+ metrics_enabled = config.getboolean('metrics', 'enabled', fallback=False)
180
+ metrics_port = config.getint('metrics', 'port', fallback=8000)
181
+ uvloop_enable = config.getboolean('metrics', 'uvloop_enable', fallback=False)
182
+
183
+ bootstrap_servers_raw = config.get('bootstrap', 'servers', fallback='')
184
+ if bootstrap_servers_raw.strip():
185
+ bootstrap_servers = [s.strip() for s in bootstrap_servers_raw.split(',') if s.strip()]
186
+ else:
187
+ bootstrap_servers = ['1.1.1.1:53', '8.8.8.8:53']
188
+ bootstrap_timeout = config.getfloat('bootstrap', 'timeout', fallback=2.0)
189
+ bootstrap_retries = config.getint('bootstrap', 'retries', fallback=2)
190
+ bootstrap = {
191
+ 'servers': bootstrap_servers,
192
+ 'timeout': bootstrap_timeout,
193
+ 'retries': bootstrap_retries,
194
+ }
195
+
196
+ upstreams = []
197
+ if config.has_section('upstreams') and config.has_option('upstreams', 'servers'):
198
+ server_names = [s.strip() for s in config.get('upstreams', 'servers').split(',') if s.strip()]
199
+ for name in server_names:
200
+ section = f'upstreams.{name}'
201
+ if not config.has_section(section):
202
+ continue
203
+ address = config.get(section, 'address', fallback='')
204
+ if not address:
205
+ continue
206
+ proto = config.get(section, 'protocol', fallback='udp').lower()
207
+ port_str = config.get(section, 'port', fallback=None)
208
+ if port_str:
209
+ try:
210
+ port = int(port_str)
211
+ except ValueError:
212
+ port = None
213
+ else:
214
+ default_ports = {'tls': 853, 'https': 443, 'quic': 853, 'udp': 53, 'tcp': 53}
215
+ port = default_ports.get(proto, 53)
216
+ hostname = config.get(section, 'hostname', fallback='') or address
217
+ path = config.get(section, 'path', fallback='')
218
+ us_doh_version = config.get(section, 'doh_version', fallback=doh_version).lower()
219
+ if us_doh_version not in ('auto', '1.1', '2', '3'):
220
+ us_doh_version = doh_version
221
+ upstreams.append({
222
+ 'address': address,
223
+ 'protocol': proto,
224
+ 'port': port,
225
+ 'hostname': hostname,
226
+ 'doh_version': us_doh_version,
227
+ 'path': path,
228
+ })
229
+
230
+ blocklists = {
231
+ 'enabled': config.getboolean('blocklists', 'enabled', fallback=False),
232
+ 'urls': [u.strip() for u in config.get('blocklists', 'urls', fallback='').split(',') if u.strip()],
233
+ 'interval_seconds': config.getint('blocklists', 'interval_seconds', fallback=86400),
234
+ 'action': config.get('blocklists', 'action', fallback='NXDOMAIN').upper(),
235
+ 'local_blocklist_dir': config.get('blocklists', 'local_blocklist_dir', fallback='blocklists'),
236
+ 'reload_on_change': config.getboolean('blocklists', 'reload_on_change', fallback=True),
237
+ }
238
+
239
+ validated_config = {
240
+ 'listen_ip': listen_ip,
241
+ 'listen_port': listen_port,
242
+ 'upstream_dns': upstream_dns,
243
+ 'protocol': protocol,
244
+ 'dns_enable_dot': dns_enable_dot,
245
+ 'dns_dot_port': dns_dot_port,
246
+ 'dns_dot_cert_file': dns_dot_cert_file,
247
+ 'dns_dot_key_file': dns_dot_key_file,
248
+ 'dns_enable_doh': dns_enable_doh,
249
+ 'dns_doh_port': dns_doh_port,
250
+ 'dns_doh_cert_file': dns_doh_cert_file,
251
+ 'dns_doh_key_file': dns_doh_key_file,
252
+ 'dns_doh_path': dns_doh_path,
253
+ 'verbose': verbose,
254
+ 'disable_ipv6': disable_ipv6,
255
+ 'strip_ipv6_records': strip_ipv6_records,
256
+ 'dns_cache_ttl': dns_cache_ttl,
257
+ 'dns_cache_max_size': dns_cache_max_size,
258
+ 'dns_negative_cache_ttl': dns_negative_cache_ttl,
259
+ 'dns_logging_enabled': dns_logging_enabled,
260
+ 'dns_log_retention_days': dns_log_retention_days,
261
+ 'dns_log_dir': dns_log_dir,
262
+ 'dns_log_prefix': dns_log_prefix,
263
+ 'dns_pinned_certs': dns_pinned_certs,
264
+ 'dns_ecs_enabled': dns_ecs_enabled,
265
+ 'dns_max_payload': dns_max_payload,
266
+ 'dnssec_enabled': dnssec_enabled,
267
+ 'auto_update_trust_anchor': auto_update_trust_anchor,
268
+ 'metrics_enabled': metrics_enabled,
269
+ 'metrics_port': metrics_port,
270
+ 'uvloop_enable': uvloop_enable,
271
+ 'upstream_retries': upstream_retries,
272
+ 'upstream_udp_timeout': upstream_udp_timeout,
273
+ 'upstream_tcp_timeout': upstream_tcp_timeout,
274
+ 'upstream_doh_timeout': upstream_doh_timeout,
275
+ 'rate_limit_rps': rate_limit_rps,
276
+ 'rate_limit_burst': rate_limit_burst,
277
+ 'upstreams': upstreams,
278
+ 'optimistic_cache_enabled': optimistic_cache_enabled,
279
+ 'optimistic_stale_max_age': optimistic_stale_max_age,
280
+ 'optimistic_stale_response_ttl': optimistic_stale_response_ttl,
281
+ 'dns_privilege_drop_user': dns_privilege_drop_user,
282
+ 'dns_privilege_drop_group': dns_privilege_drop_group,
283
+ 'dns_chroot_dir': dns_chroot_dir,
284
+ 'dns_rebind_protection': dns_rebind_protection,
285
+ 'dns_rebind_action': dns_rebind_action,
286
+ 'pool_max_size': pool_max_size,
287
+ 'pool_idle_timeout': pool_idle_timeout,
288
+ 'doh_version': doh_version,
289
+ 'doh_auto_cache_ttl': doh_auto_cache_ttl,
290
+ 'bootstrap': bootstrap,
291
+ 'blocklists': blocklists,
292
+ }
293
+
294
+ _validate_and_warn(validated_config)
295
+ return validated_config