dosev 1.0.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.
dosev-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohammad Amin Kiani
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.
dosev-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: dosev
3
+ Version: 1.0.0
4
+ Summary: Multi‑protocol DNS resolver with full server and config support
5
+ Author-email: Mohammad Amin Kiani <mohammadamin.k1390@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: dnspython>=2.4.0
11
+ Requires-Dist: aiohttp>=3.8.0
12
+ Requires-Dist: aioquic>=0.9.0
13
+ Requires-Dist: cryptography>=38.0.0
14
+ Requires-Dist: cachetools>=5.0.0
15
+ Requires-Dist: httpx>=0.23.0
16
+ Requires-Dist: prometheus-client>=0.14.0
17
+ Provides-Extra: test
18
+ Requires-Dist: pytest>=7.0; extra == "test"
19
+ Requires-Dist: pytest-asyncio>=0.21; extra == "test"
20
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
21
+ Dynamic: license-file
22
+
23
+ # dosev – Multi‑Protocol DNS Resolver & Server
24
+
25
+ [![CI](https://github.com/KianiDev/dosev/actions/workflows/ci.yml/badge.svg)](https://github.com/KianiDev/dosev/actions/workflows/ci.yml)
26
+ [![PyPI version](https://img.shields.io/pypi/v/dosev)](https://pypi.org/project/dosev/)
27
+ [![Python versions](https://img.shields.io/pypi/pyversions/dosev)](https://pypi.org/project/dosev/)
28
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
29
+
30
+ **dosev** is a high‑performance, asynchronous DNS resolver and forwarding server that speaks **all major DNS protocols** – both as a client and as a server.
31
+
32
+ - **Client protocols**: DNS over UDP, TCP, TLS (DoT), HTTPS (DoH – HTTP/1.1, HTTP/2, HTTP/3), and QUIC (DoQ).
33
+ - **Server protocols**: UDP, TCP, TLS (DoT), HTTPS (DoH – HTTP/2 and HTTP/3).
34
+ - **Features**: EDNS0 (including Client Subnet), DNSSEC validation with automatic trust anchor updates, negative caching, optimistic caching (serve‑stale), blocklists, hosts overrides, upstream health checks, load balancing, Prometheus metrics, and more.
35
+
36
+ ---
37
+
38
+ ## 🚀 Quick Start
39
+
40
+ ```bash
41
+ # Install via pip
42
+ pip install dosev
43
+
44
+ # Create a minimal config file
45
+ mkdir -p config
46
+ cat > config/dosev.conf <<EOF
47
+ [server]
48
+ listen_ip = 0.0.0.0
49
+ listen_port = 53
50
+
51
+ [resolver]
52
+ upstream_dns = 1.1.1.1
53
+ protocol = udp
54
+ EOF
55
+
56
+ # Start the server
57
+ dosev --config config/dosev.conf
58
+ ```
59
+
60
+ For detailed instructions, see the [Quick Start Guide](docs/quickstart.md).
61
+
62
+ ---
63
+
64
+ ## ✨ Features
65
+
66
+ | Feature | Description |
67
+ |---------|-------------|
68
+ | **Multi‑protocol client** | Forward queries via UDP, TCP, TLS, HTTPS (HTTP/1.1, HTTP/2, HTTP/3), and QUIC. |
69
+ | **Multi‑protocol server** | Listen on UDP, TCP, TLS, and HTTPS (HTTP/2 & HTTP/3). |
70
+ | **DNSSEC validation** | Validate responses with a built‑in root trust anchor (auto‑updated from IANA). |
71
+ | **Caching** | Positive and negative caching with configurable TTLs; optimistic caching (serve‑stale). |
72
+ | **Blocklists** | Filter domains using local files or remote lists (automatically refreshed). |
73
+ | **Hosts overrides** | Custom A/AAAA records for local name resolution. |
74
+ | **EDNS0 & Client Subnet** | Pass client subnet to upstreams for geo‑optimised responses. |
75
+ | **Load balancing & health checks** | Distribute queries across multiple upstreams; automatically exclude unhealthy ones. |
76
+ | **Rate limiting** | Token‑bucket limiter per client IP. |
77
+ | **Rebinding protection** | Strip or block private IP addresses from responses. |
78
+ | **Metrics** | Prometheus‑compatible metrics (requests, errors, latency, cache stats). |
79
+ | **Privilege dropping** | Drop root privileges after binding to privileged ports. |
80
+ | **Cross‑platform** | Works on Linux, macOS, and Windows. |
81
+ | **Configuration** | Single INI file; can be reloaded without restarting. |
82
+
83
+ ---
84
+
85
+ ## 📖 Documentation
86
+
87
+ - [Quick Start](docs/quickstart.md) – get up and running in minutes.
88
+ - [Configuration Reference](docs/configuration.md) – all available options explained.
89
+ - [Deployment Guide](docs/deployment.md) – systemd, Docker, and production tuning.
90
+ - [Contributing](docs/CONTRIBUTING.md) – how to help improve dosev.
91
+
92
+ ---
93
+
94
+ ## 🔧 Configuration Example
95
+
96
+ ```ini
97
+ [server]
98
+ listen_ip = 0.0.0.0
99
+ listen_port = 53
100
+
101
+ [resolver]
102
+ upstream_dns = 1.1.1.1
103
+ protocol = udp
104
+
105
+ [security]
106
+ dnssec_enabled = true
107
+ auto_update_trust_anchor = true
108
+
109
+ [blocklists]
110
+ enabled = true
111
+ urls = https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
112
+ action = NXDOMAIN
113
+ ```
114
+
115
+ See the full [configuration reference](docs/configuration.md) for all options.
116
+
117
+ ---
118
+
119
+ ## 🧪 Running Tests
120
+
121
+ ```bash
122
+ pip install dosev[test]
123
+ pytest --cov=dosev --cov-report=term-missing
124
+ ```
125
+
126
+ ---
127
+
128
+ ## 🤝 Contributing
129
+
130
+ We welcome contributions! Please read our [Contributing Guide](docs/CONTRIBUTING.md) before opening issues or pull requests.
131
+
132
+ ---
133
+
134
+ ## 📄 License
135
+
136
+ This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details.
137
+
138
+ ---
139
+
140
+ ## 🙏 Acknowledgements
141
+
142
+ - [dnspython](https://www.dnspython.org/) – for DNS message handling.
143
+ - [aiohttp](https://docs.aiohttp.org/) – for HTTP/1.1 and HTTP/2 support.
144
+ - [aioquic](https://github.com/aiortc/aioquic) – for HTTP/3 and QUIC support.
145
+ - [Prometheus](https://prometheus.io/) – for metrics.
dosev-1.0.0/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # dosev – Multi‑Protocol DNS Resolver & Server
2
+
3
+ [![CI](https://github.com/KianiDev/dosev/actions/workflows/ci.yml/badge.svg)](https://github.com/KianiDev/dosev/actions/workflows/ci.yml)
4
+ [![PyPI version](https://img.shields.io/pypi/v/dosev)](https://pypi.org/project/dosev/)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/dosev)](https://pypi.org/project/dosev/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ **dosev** is a high‑performance, asynchronous DNS resolver and forwarding server that speaks **all major DNS protocols** – both as a client and as a server.
9
+
10
+ - **Client protocols**: DNS over UDP, TCP, TLS (DoT), HTTPS (DoH – HTTP/1.1, HTTP/2, HTTP/3), and QUIC (DoQ).
11
+ - **Server protocols**: UDP, TCP, TLS (DoT), HTTPS (DoH – HTTP/2 and HTTP/3).
12
+ - **Features**: EDNS0 (including Client Subnet), DNSSEC validation with automatic trust anchor updates, negative caching, optimistic caching (serve‑stale), blocklists, hosts overrides, upstream health checks, load balancing, Prometheus metrics, and more.
13
+
14
+ ---
15
+
16
+ ## 🚀 Quick Start
17
+
18
+ ```bash
19
+ # Install via pip
20
+ pip install dosev
21
+
22
+ # Create a minimal config file
23
+ mkdir -p config
24
+ cat > config/dosev.conf <<EOF
25
+ [server]
26
+ listen_ip = 0.0.0.0
27
+ listen_port = 53
28
+
29
+ [resolver]
30
+ upstream_dns = 1.1.1.1
31
+ protocol = udp
32
+ EOF
33
+
34
+ # Start the server
35
+ dosev --config config/dosev.conf
36
+ ```
37
+
38
+ For detailed instructions, see the [Quick Start Guide](docs/quickstart.md).
39
+
40
+ ---
41
+
42
+ ## ✨ Features
43
+
44
+ | Feature | Description |
45
+ |---------|-------------|
46
+ | **Multi‑protocol client** | Forward queries via UDP, TCP, TLS, HTTPS (HTTP/1.1, HTTP/2, HTTP/3), and QUIC. |
47
+ | **Multi‑protocol server** | Listen on UDP, TCP, TLS, and HTTPS (HTTP/2 & HTTP/3). |
48
+ | **DNSSEC validation** | Validate responses with a built‑in root trust anchor (auto‑updated from IANA). |
49
+ | **Caching** | Positive and negative caching with configurable TTLs; optimistic caching (serve‑stale). |
50
+ | **Blocklists** | Filter domains using local files or remote lists (automatically refreshed). |
51
+ | **Hosts overrides** | Custom A/AAAA records for local name resolution. |
52
+ | **EDNS0 & Client Subnet** | Pass client subnet to upstreams for geo‑optimised responses. |
53
+ | **Load balancing & health checks** | Distribute queries across multiple upstreams; automatically exclude unhealthy ones. |
54
+ | **Rate limiting** | Token‑bucket limiter per client IP. |
55
+ | **Rebinding protection** | Strip or block private IP addresses from responses. |
56
+ | **Metrics** | Prometheus‑compatible metrics (requests, errors, latency, cache stats). |
57
+ | **Privilege dropping** | Drop root privileges after binding to privileged ports. |
58
+ | **Cross‑platform** | Works on Linux, macOS, and Windows. |
59
+ | **Configuration** | Single INI file; can be reloaded without restarting. |
60
+
61
+ ---
62
+
63
+ ## 📖 Documentation
64
+
65
+ - [Quick Start](docs/quickstart.md) – get up and running in minutes.
66
+ - [Configuration Reference](docs/configuration.md) – all available options explained.
67
+ - [Deployment Guide](docs/deployment.md) – systemd, Docker, and production tuning.
68
+ - [Contributing](docs/CONTRIBUTING.md) – how to help improve dosev.
69
+
70
+ ---
71
+
72
+ ## 🔧 Configuration Example
73
+
74
+ ```ini
75
+ [server]
76
+ listen_ip = 0.0.0.0
77
+ listen_port = 53
78
+
79
+ [resolver]
80
+ upstream_dns = 1.1.1.1
81
+ protocol = udp
82
+
83
+ [security]
84
+ dnssec_enabled = true
85
+ auto_update_trust_anchor = true
86
+
87
+ [blocklists]
88
+ enabled = true
89
+ urls = https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
90
+ action = NXDOMAIN
91
+ ```
92
+
93
+ See the full [configuration reference](docs/configuration.md) for all options.
94
+
95
+ ---
96
+
97
+ ## 🧪 Running Tests
98
+
99
+ ```bash
100
+ pip install dosev[test]
101
+ pytest --cov=dosev --cov-report=term-missing
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 🤝 Contributing
107
+
108
+ We welcome contributions! Please read our [Contributing Guide](docs/CONTRIBUTING.md) before opening issues or pull requests.
109
+
110
+ ---
111
+
112
+ ## 📄 License
113
+
114
+ This project is licensed under the MIT License – see the [LICENSE](LICENSE) file for details.
115
+
116
+ ---
117
+
118
+ ## 🙏 Acknowledgements
119
+
120
+ - [dnspython](https://www.dnspython.org/) – for DNS message handling.
121
+ - [aiohttp](https://docs.aiohttp.org/) – for HTTP/1.1 and HTTP/2 support.
122
+ - [aioquic](https://github.com/aiortc/aioquic) – for HTTP/3 and QUIC support.
123
+ - [Prometheus](https://prometheus.io/) – for metrics.
@@ -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"]
@@ -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()
@@ -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