Skip to content

Commit 925fa63

Browse files
committed
Add support for QUIC forwarding
1 parent 935632a commit 925fa63

8 files changed

Lines changed: 570 additions & 35 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The proxy supports the following protocols:
1313
- [DNSCrypt v2, including post-quantum DNSCrypt (PQDNSCrypt)](https://github.com/DNSCrypt/dnscrypt-protocol/blob/master/DNSCRYPT-V2-PROTOCOL.txt)
1414
- [Anonymized DNSCrypt](https://github.com/DNSCrypt/dnscrypt-protocol/blob/master/ANONYMIZED-DNSCRYPT.txt)
1515
- DNS-over-HTTPS (DoH) forwarding
16+
- QUIC forwarding (e.g. to an HTTP/3 server), sharing the UDP port with DNSCrypt
1617

1718
All of these can be served simultaneously, on the same port (usually port 443). The proxy automatically detects what protocol is being used by each client.
1819

‎example-encrypted-dns.toml‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,32 @@ key_cache_capacity = 10000
185185

186186

187187

188+
###############################
189+
# QUIC settings #
190+
###############################
191+
192+
[quic]
193+
194+
## Where to proxy QUIC datagrams to (e.g. an HTTP/3 server), sharing
195+
## the UDP port with DNSCrypt. Incoming packets are only forwarded there
196+
## once every DNSCrypt interpretation has been ruled out.
197+
198+
# upstream_addr = "127.0.0.1:4443"
199+
200+
201+
## How long a QUIC flow without any traffic in either direction is kept
202+
## around, in seconds.
203+
204+
# idle_timeout = 30
205+
206+
207+
## Maximum number of simultaneous QUIC flows. When the limit is reached,
208+
## the flow that has been quiet the longest is dropped.
209+
210+
# max_active_flows = 512
211+
212+
213+
188214
#######################################
189215
# Server-side filtering #
190216
#######################################

‎src/anonymized_dns.rs‎

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use std::hash::Hasher;
2-
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
2+
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
33
use std::sync::Arc;
44

55
use byteorder::{BigEndian, ByteOrder};
66
use ipext::IpExt;
77
use siphasher::sip128::Hasher128;
8-
use tokio::net::UdpSocket;
98

109
use crate::errors::*;
1110
use crate::*;
@@ -15,6 +14,15 @@ pub const ANONYMIZED_DNSCRYPT_QUERY_MAGIC: [u8; 10] =
1514

1615
pub const ANONYMIZED_DNSCRYPT_OVERHEAD: usize = 16 + 2;
1716

17+
pub const ANONYMIZED_DNSCRYPT_UDP_QUERY_MAX_SIZE: usize = ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()
18+
+ ANONYMIZED_DNSCRYPT_OVERHEAD
19+
+ DNSCRYPT_UDP_QUERY_MAX_SIZE;
20+
21+
pub fn starts_with_relay_magic(packet: &[u8]) -> bool {
22+
packet.len() >= ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()
23+
&& packet[..ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()] == ANONYMIZED_DNSCRYPT_QUERY_MAGIC
24+
}
25+
1826
pub const RELAYED_CERT_CACHE_SIZE: usize = 1000;
1927
pub const RELAYED_CERT_CACHE_TTL: u32 = 600;
2028

@@ -74,29 +82,9 @@ pub async fn handle_anonymized_dns(
7482
"Protocol confusion with QUIC"
7583
);
7684
debug_assert!(DNSCRYPT_UDP_QUERY_MIN_SIZE > ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len());
77-
ensure!(
78-
encrypted_packet[..ANONYMIZED_DNSCRYPT_QUERY_MAGIC.len()]
79-
!= ANONYMIZED_DNSCRYPT_QUERY_MAGIC,
80-
"Loop detected"
81-
);
82-
let ext_socket = match globals.external_addr {
83-
Some(x) => UdpSocket::bind(x).await?,
84-
None => match upstream_address {
85-
SocketAddr::V4(_) => {
86-
UdpSocket::bind(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))).await?
87-
}
88-
SocketAddr::V6(s) => {
89-
UdpSocket::bind(SocketAddr::V6(SocketAddrV6::new(
90-
Ipv6Addr::UNSPECIFIED,
91-
0,
92-
s.flowinfo(),
93-
s.scope_id(),
94-
)))
95-
.await?
96-
}
97-
},
98-
};
99-
ext_socket.connect(&upstream_address).await?;
85+
ensure!(!starts_with_relay_magic(encrypted_packet), "Loop detected");
86+
let ext_socket =
87+
crate::resolver::upstream_udp_socket(upstream_address, globals.external_addr).await?;
10088
ext_socket.send(encrypted_packet).await?;
10189
let mut response = vec![0u8; DNSCRYPT_UDP_RESPONSE_MAX_SIZE];
10290
let (response_len, is_certificate_response) = loop {

‎src/config.rs‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,33 @@ pub struct TLSConfig {
5454
pub upstream_addr: Option<SocketAddr>,
5555
}
5656

57+
fn quic_default_idle_timeout() -> u32 {
58+
crate::quic_proxy::QUIC_PROXY_DEFAULT_IDLE_TIMEOUT_SECS
59+
}
60+
61+
fn quic_default_max_active_flows() -> u32 {
62+
crate::quic_proxy::QUIC_PROXY_DEFAULT_MAX_ACTIVE_FLOWS
63+
}
64+
65+
#[derive(Serialize, Deserialize, Debug, Clone)]
66+
pub struct QUICConfig {
67+
pub upstream_addr: Option<SocketAddr>,
68+
#[serde(default = "quic_default_idle_timeout")]
69+
pub idle_timeout: u32,
70+
#[serde(default = "quic_default_max_active_flows")]
71+
pub max_active_flows: u32,
72+
}
73+
74+
impl Default for QUICConfig {
75+
fn default() -> Self {
76+
QUICConfig {
77+
upstream_addr: None,
78+
idle_timeout: quic_default_idle_timeout(),
79+
max_active_flows: quic_default_max_active_flows(),
80+
}
81+
}
82+
}
83+
5784
#[derive(Serialize, Deserialize, Debug, Clone)]
5885
pub struct ListenAddrConfig {
5986
pub local: SocketAddr,
@@ -110,6 +137,8 @@ pub struct Config {
110137
pub filtering: FilteringConfig,
111138
pub dnscrypt: DNSCryptConfig,
112139
pub tls: TLSConfig,
140+
#[serde(default)]
141+
pub quic: QUICConfig,
113142
pub daemonize: bool,
114143
pub pid_file: Option<PathBuf>,
115144
pub log_file: Option<PathBuf>,

‎src/globals.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::blacklist::*;
1414
use crate::cache::*;
1515
use crate::crypto::*;
1616
use crate::dnscrypt_certs::*;
17+
use crate::quic_proxy::QuicProxy;
1718
use crate::rate_limiter::*;
1819
#[cfg(feature = "metrics")]
1920
use crate::varz::*;
@@ -30,6 +31,8 @@ pub struct Globals {
3031
pub external_addr: Option<SocketAddr>,
3132
pub upstream_addrs: Vec<SocketAddr>,
3233
pub tls_upstream_addr: Option<SocketAddr>,
34+
#[educe(Debug(ignore))]
35+
pub quic_proxy: Option<Arc<QuicProxy>>,
3336
pub udp_timeout: Duration,
3437
pub tcp_timeout: Duration,
3538
pub udp_concurrent_connections: Arc<AtomicU32>,

‎src/main.rs‎

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ mod globals;
3232
mod pq;
3333
#[cfg(feature = "metrics")]
3434
mod metrics;
35+
mod quic_proxy;
3536
mod rate_limiter;
3637
mod resolver;
3738
#[cfg(feature = "metrics")]
@@ -64,6 +65,7 @@ use futures::prelude::*;
6465
use globals::*;
6566
use parking_lot::Mutex;
6667
use parking_lot::RwLock;
68+
use quic_proxy::*;
6769
#[cfg(target_family = "unix")]
6870
use privdrop::PrivDrop;
6971
use rand::prelude::*;
@@ -169,6 +171,30 @@ async fn encrypt_and_respond_to_query(
169171
respond_to_query(client_ctx, response).await
170172
}
171173

174+
/// Hand the packet to the QUIC upstream, or return `None` when proxying
175+
/// doesn't apply: disabled, TCP client, or outside the RFC 9443 ranges.
176+
async fn maybe_relay_quic(
177+
globals: &Arc<Globals>,
178+
client_ctx: ClientCtx,
179+
packet: &[u8],
180+
) -> Option<Result<(), Error>> {
181+
if globals.quic_proxy.is_none() || !may_be_quic(packet) {
182+
return None;
183+
}
184+
match client_ctx {
185+
ClientCtx::Udp(udp_client_ctx) => Some(
186+
quic_proxy::relay(
187+
globals,
188+
udp_client_ctx.net_udp_socket,
189+
udp_client_ctx.client_addr,
190+
packet,
191+
)
192+
.await,
193+
),
194+
ClientCtx::Tcp(_) => None,
195+
}
196+
}
197+
172198
async fn handle_client_query(
173199
globals: Arc<Globals>,
174200
client_ctx: ClientCtx,
@@ -225,8 +251,22 @@ async fn handle_client_query(
225251
)
226252
.await
227253
}
228-
Ok(None) => return Ok(()),
254+
Ok(None) => {
255+
// Random QUIC bytes can parse as a valid non-certificate DNS query.
256+
// Relaying one is harmless: a real stray DNS query gets no answer either way.
257+
if let Some(result) = maybe_relay_quic(&globals, client_ctx, &packet).await
258+
{
259+
return result;
260+
}
261+
return Ok(());
262+
}
229263
Err(_) => {
264+
// Not encrypted for us and not a certificate query;
265+
// nothing DNSCrypt-shaped is left for this packet to be.
266+
if let Some(result) = maybe_relay_quic(&globals, client_ctx, &packet).await
267+
{
268+
return result;
269+
}
230270
if may_be_quic(&packet) {
231271
bail!("Likely a QUIC packet") // RFC 9443
232272
}
@@ -428,13 +468,18 @@ async fn udp_acceptor(
428468
let timeout = globals.udp_timeout;
429469
let concurrent_connections = globals.udp_concurrent_connections.clone();
430470
let active_connections = globals.udp_active_connections.clone();
471+
// Large enough that a proxied QUIC datagram is never truncated.
472+
let mut recv_buffer = vec![0u8; QUIC_PROXY_BUFFER_SIZE];
431473
loop {
432-
let mut packet = vec![0u8; DNSCRYPT_UDP_QUERY_MAX_SIZE];
433-
let (packet_len, client_addr) = tokio_udp_socket.recv_from(&mut packet).await?;
474+
let (packet_len, client_addr) = tokio_udp_socket.recv_from(&mut recv_buffer).await?;
434475
if packet_len < DNS_HEADER_SIZE {
435476
continue;
436477
}
437478

479+
if quic_proxy::try_forward_established(&globals, client_addr, &recv_buffer[..packet_len]) {
480+
continue;
481+
}
482+
438483
if let Some(ref rate_limiter) = globals.rate_limiter {
439484
if !rate_limiter.is_allowed(client_addr.ip()) {
440485
debug!("Rate limit exceeded for {}", client_addr.ip());
@@ -444,7 +489,29 @@ async fn udp_acceptor(
444489
}
445490
}
446491

447-
packet.truncate(packet_len);
492+
let packet = recv_buffer[..packet_len].to_vec();
493+
494+
// Only relayed Anonymized DNSCrypt queries may exceed the DNSCrypt
495+
// maximum, by the size of the relay header; anything else oversized
496+
// never needs DNS processing.
497+
let oversized_anonymized_dns = globals.anonymized_dns_enabled
498+
&& packet_len <= ANONYMIZED_DNSCRYPT_UDP_QUERY_MAX_SIZE
499+
&& starts_with_relay_magic(&packet);
500+
if packet_len > DNSCRYPT_UDP_QUERY_MAX_SIZE && !oversized_anonymized_dns {
501+
if globals.quic_proxy.is_some() && may_be_quic(&packet) {
502+
if let Ok(net_udp_socket) = net_udp_socket.try_clone() {
503+
let globals = globals.clone();
504+
runtime_handle.spawn(async move {
505+
if let Err(e) =
506+
quic_proxy::relay(&globals, net_udp_socket, client_addr, &packet).await
507+
{
508+
debug!("QUIC relay error: {:?}", e);
509+
}
510+
});
511+
}
512+
}
513+
continue;
514+
}
448515

449516
let active_count = concurrent_connections.load(Ordering::Relaxed);
450517
if active_count >= globals.udp_max_active_connections {
@@ -629,11 +696,16 @@ fn set_limits(_config: &Config) -> Result<(), Error> {
629696
#[cfg(target_family = "unix")]
630697
fn set_limits(config: &Config) -> Result<(), Error> {
631698
use rlimit::Resource;
699+
let quic_max_active_flows = match config.quic.upstream_addr {
700+
Some(_) => config.quic.max_active_flows,
701+
None => 0,
702+
};
632703
let nb_descriptors = 4u32
633704
.saturating_mul(
634705
config
635706
.tcp_max_active_connections
636707
.saturating_add(config.udp_max_active_connections)
708+
.saturating_add(quic_max_active_flows)
637709
.saturating_add(config.listen_addrs.len() as u32),
638710
)
639711
.saturating_add(16);
@@ -888,6 +960,39 @@ fn main() -> Result<(), Error> {
888960
}
889961
_ => None,
890962
};
963+
let quic_proxy = match config.quic.upstream_addr {
964+
None => None,
965+
Some(quic_upstream_addr) => {
966+
// Connecting to an unspecified address reaches the local host,
967+
// which could loop back to one of our own listening ports.
968+
if quic_upstream_addr.ip().is_unspecified() {
969+
bail!(
970+
"quic.upstream_addr [{}] must be a specific address",
971+
quic_upstream_addr
972+
);
973+
}
974+
// A packet forwarded to one of our own UDP ports would fail
975+
// decryption there, look like QUIC again, and be forwarded again.
976+
for listen_addr in &listen_addrs {
977+
if listen_addr.port() == quic_upstream_addr.port()
978+
&& (listen_addr.ip().is_unspecified()
979+
|| listen_addr.ip() == quic_upstream_addr.ip())
980+
{
981+
bail!(
982+
"quic.upstream_addr [{}] would loop back to a listening UDP port",
983+
quic_upstream_addr
984+
);
985+
}
986+
}
987+
info!("QUIC proxying enabled, forwarding to [{}]", quic_upstream_addr);
988+
Some(Arc::new(QuicProxy::new(
989+
quic_upstream_addr,
990+
external_addr,
991+
config.quic.max_active_flows as usize,
992+
config.quic.idle_timeout,
993+
)))
994+
}
995+
};
891996
let runtime_handle = runtime.handle();
892997
let globals = Arc::new(Globals {
893998
runtime_handle: runtime_handle.clone(),
@@ -900,6 +1005,7 @@ fn main() -> Result<(), Error> {
9001005
listen_addrs,
9011006
upstream_addrs: config.upstream_addrs,
9021007
tls_upstream_addr: config.tls.upstream_addr,
1008+
quic_proxy,
9031009
external_addr,
9041010
tcp_timeout: Duration::from_secs(u64::from(config.tcp_timeout)),
9051011
udp_timeout: Duration::from_secs(u64::from(config.udp_timeout)),

0 commit comments

Comments
 (0)