feat: DNS-over-HTTPS (DoH) upstream forwarding (#14)

* feat: DNS-over-HTTPS upstream forwarding

Encrypt upstream queries via DoH — ISPs see HTTPS traffic on port 443,
not plaintext DNS on port 53. URL scheme determines transport:
https:// = DoH, bare IP = plain UDP. Falls back to Quad9 DoH when
system resolver cannot be detected.

- Upstream enum (Udp/Doh) with Display and PartialEq
- BytePacketBuffer::from_bytes constructor
- reqwest http2 feature for DoH server compatibility
- network_watch_loop guards against DoH→UDP silent downgrade
- 5 new tests (mock DoH server, HTTP errors, timeout)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: cargo fmt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add DoH to README — Why Numa, comparison table, roadmap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #14.
This commit is contained in:
Razvan Dimescu
2026-03-24 00:39:58 +02:00
committed by GitHub
parent 9c313ef06a
commit d274500308
9 changed files with 296 additions and 25 deletions

View File

@@ -9,7 +9,7 @@ use axum::{Json, Router};
use serde::{Deserialize, Serialize};
use crate::ctx::ServerCtx;
use crate::forward::forward_query;
use crate::forward::{forward_query, Upstream};
use crate::query_log::QueryLogFilter;
use crate::question::QueryType;
use crate::stats::QueryPath;
@@ -355,9 +355,9 @@ async fn diagnose(
}
// Check upstream (async, no locks held)
let upstream = *ctx.upstream.lock().unwrap();
let upstream = ctx.upstream.lock().unwrap().clone();
let (upstream_matched, upstream_detail) =
forward_query_for_diagnose(&domain_lower, upstream, ctx.timeout).await;
forward_query_for_diagnose(&domain_lower, &upstream, ctx.timeout).await;
steps.push(DiagnoseStep {
source: "upstream".to_string(),
matched: upstream_matched,
@@ -373,7 +373,7 @@ async fn diagnose(
async fn forward_query_for_diagnose(
domain: &str,
upstream: std::net::SocketAddr,
upstream: &Upstream,
timeout: std::time::Duration,
) -> (bool, String) {
use crate::packet::DnsPacket;