1pub mod health;
8pub use health::{Health, HealthConfig};
9
10use pharos_proto::{Message, MessageError, NameError, Rcode, Type};
11use pharos_server::metrics::{ProxyConnectionCache, ProxyMetrics, ProxyRequestDuration, Registry};
12use std::{
13 collections::VecDeque,
14 fmt, io,
15 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
16 sync::{
17 Mutex, OnceLock,
18 atomic::{AtomicBool, AtomicI64, AtomicU64, AtomicUsize, Ordering},
19 },
20 time::{Duration, Instant},
21};
22use tokio::{
23 io::{AsyncReadExt, AsyncWriteExt},
24 net::{TcpSocket, TcpStream, UdpSocket},
25 time::timeout,
26};
27
28const SHARDS: usize = 32;
29static NEXT_SHARD: AtomicUsize = AtomicUsize::new(0);
30thread_local! { static SHARD: usize = NEXT_SHARD.fetch_add(1, Ordering::Relaxed) % SHARDS; }
31const EXPIRE: Duration = Duration::from_secs(10);
32const READ_TIMEOUT: Duration = Duration::from_secs(2);
33
34#[cfg(target_os = "linux")]
35fn enable_recv_err(socket: &UdpSocket, address: SocketAddr) {
36 use nix::sys::socket::{
37 setsockopt,
38 sockopt::{Ipv4RecvErr, Ipv6RecvErr},
39 };
40 let result = if address.is_ipv4() {
41 setsockopt(socket, Ipv4RecvErr, &true)
42 } else {
43 setsockopt(socket, Ipv6RecvErr, &true)
44 };
45 if let Err(error) = result {
46 pharos_server::logging::log(
47 "WARN",
48 "proxy",
49 &format!("failed to enable UDP error queue: {error}"),
50 );
51 }
52}
53
54#[cfg(not(target_os = "linux"))]
55fn enable_recv_err(_: &UdpSocket, _: SocketAddr) {}
56
57async fn connect_tcp(address: SocketAddr, source: Option<IpAddr>) -> io::Result<TcpStream> {
58 let socket = if address.is_ipv4() {
59 TcpSocket::new_v4()?
60 } else {
61 TcpSocket::new_v6()?
62 };
63 if let Some(source) = source {
64 socket.bind(SocketAddr::new(source, 0))?;
65 }
66 socket.connect(address).await
67}
68
69fn is_overflow(error: &MessageError) -> bool {
70 matches!(
71 error,
72 MessageError::Truncated | MessageError::Name(NameError::Truncated)
73 )
74}
75
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77pub enum Transport {
78 Udp,
79 Tcp,
80}
81
82#[derive(Clone, Copy, Debug, Default)]
83pub struct Options {
84 pub force_tcp: bool,
85 pub prefer_udp: bool,
86}
87
88#[derive(Debug)]
89pub enum Error {
90 InvalidRequest,
91 UnsupportedRequest,
92 NoHealthy,
93 NoForward,
94 Stopped,
95 Mismatch,
96 CachedClosed,
97 Io(io::Error),
98}
99impl fmt::Display for Error {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 match self {
102 Self::InvalidRequest => f.write_str("invalid Query"),
103 Self::UnsupportedRequest => f.write_str("unsupported Query"),
104 Self::NoHealthy => f.write_str("no healthy Upstreams"),
105 Self::NoForward => f.write_str("no Upstream configured"),
106 Self::Stopped => f.write_str("proxy: transport stopped"),
107 Self::Mismatch => f.write_str("Upstream Response does not match Query"),
108 Self::CachedClosed => f.write_str("cached connection was closed by peer"),
109 Self::Io(error) => write!(f, "{error}"),
110 }
111 }
112}
113impl std::error::Error for Error {
114 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
115 match self {
116 Self::Io(error) => Some(error),
117 _ => None,
118 }
119 }
120}
121impl From<io::Error> for Error {
122 fn from(error: io::Error) -> Self {
123 Self::Io(error)
124 }
125}
126
127#[derive(Debug)]
128pub struct Exchange {
129 pub len: usize,
130 pub local_addr: SocketAddr,
131 pub transport: Transport,
132}
133
134struct Cached {
135 socket: Socket,
136 created: Instant,
137 used: Instant,
138}
139
140enum Socket {
141 Udp(UdpSocket),
142 Tcp(TcpStream),
143}
144
145impl Socket {
146 fn local_addr(&self) -> io::Result<SocketAddr> {
147 match self {
148 Self::Udp(s) => s.local_addr(),
149 Self::Tcp(s) => s.local_addr(),
150 }
151 }
152 async fn send(&mut self, wire: &[u8]) -> io::Result<()> {
153 match self {
154 Self::Udp(s) => {
155 s.send(wire).await?;
156 Ok(())
157 }
158 Self::Tcp(s) => {
160 s.write_all(&(wire.len() as u16).to_be_bytes()).await?;
161 s.write_all(wire).await
162 }
163 }
164 }
165 async fn receive(&mut self, wire: &mut [u8]) -> io::Result<usize> {
166 match self {
167 Self::Udp(s) => s.recv(wire).await,
168 Self::Tcp(s) => {
170 let len = usize::from(s.read_u16().await?);
171 if len > wire.len() {
172 return Err(io::ErrorKind::InvalidData.into());
173 }
174 s.read_exact(&mut wire[..len]).await?;
175 Ok(len)
176 }
177 }
178 }
179}
180
181pub struct Proxy {
182 address: SocketAddr,
183 shards: [[Mutex<VecDeque<Cached>>; SHARDS]; 2],
184 expire: Duration,
185 max_age: Duration,
186 max_idle_conns: usize,
187 idle: [AtomicUsize; 2],
188 stopped: AtomicBool,
189 started: Instant,
190 next_sweep: AtomicU64,
191 read_timeout: Duration,
192 average_dial_nanos: AtomicI64,
193 source_address: Option<IpAddr>,
194 metrics: Option<Metrics>,
195 health: Health,
196}
197
198struct Metrics {
199 proxy: ProxyMetrics,
200 cache: [ProxyConnectionCache; 2],
201 durations: Box<[OnceLock<ProxyRequestDuration>]>,
202}
203
204impl Proxy {
205 #[must_use]
206 pub fn new(address: SocketAddr) -> Self {
207 Self {
208 address,
209 shards: std::array::from_fn(|_| std::array::from_fn(|_| Mutex::new(VecDeque::new()))),
210 expire: EXPIRE,
211 max_age: Duration::ZERO,
212 max_idle_conns: 0,
213 idle: [AtomicUsize::new(0), AtomicUsize::new(0)],
214 stopped: AtomicBool::new(false),
215 started: Instant::now(),
216 next_sweep: AtomicU64::new(EXPIRE.as_nanos() as u64),
217 read_timeout: READ_TIMEOUT,
218 average_dial_nanos: AtomicI64::new(15_000_000_000),
219 source_address: None,
220 metrics: None,
221 health: Health::new(address, 2),
222 }
223 }
224
225 pub fn set_expire(&mut self, expire: Duration) {
227 self.expire = expire;
228 self.next_sweep
229 .store(self.sweep_deadline(), Ordering::Relaxed);
230 }
231
232 fn sweep_deadline(&self) -> u64 {
233 self.started
234 .elapsed()
235 .as_nanos()
236 .saturating_add(self.expire.as_nanos())
237 .min(u128::from(u64::MAX)) as u64
238 }
239
240 fn sweep(&self) {
241 let now = self.started.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64;
242 let next = self.next_sweep.load(Ordering::Relaxed);
243 if now < next
244 || self
245 .next_sweep
246 .compare_exchange(
247 next,
248 self.sweep_deadline(),
249 Ordering::Relaxed,
250 Ordering::Relaxed,
251 )
252 .is_err()
253 {
254 return;
255 }
256 for (protocol, shard) in self
258 .shards
259 .iter()
260 .enumerate()
261 .flat_map(|(p, s)| s.iter().map(move |s| (p, s)))
262 {
263 let mut queue = shard.lock().unwrap();
264 let before = queue.len();
265 queue.retain(|conn| {
266 conn.used.elapsed() <= self.expire
267 && (self.max_age.is_zero() || conn.created.elapsed() <= self.max_age)
268 });
269 self.idle[protocol].fetch_sub(before - queue.len(), Ordering::Relaxed);
270 }
271 }
272
273 pub fn stop(&self) {
275 self.stopped.store(true, Ordering::Release);
276 self.health.shutdown();
277 for (protocol, shard) in self
278 .shards
279 .iter()
280 .enumerate()
281 .flat_map(|(p, s)| s.iter().map(move |s| (p, s)))
282 {
283 let mut queue = shard.lock().unwrap();
284 self.idle[protocol].fetch_sub(queue.len(), Ordering::Relaxed);
285 queue.clear();
286 }
287 }
288
289 pub fn set_max_idle_conns(&mut self, max: usize) {
291 self.max_idle_conns = max;
292 }
293
294 pub fn set_max_age(&mut self, max_age: Duration) {
296 self.max_age = max_age;
297 }
298
299 pub fn set_read_timeout(&mut self, read_timeout: Duration) {
301 self.read_timeout = read_timeout;
302 }
303
304 fn dial_timeout(&self) -> Duration {
305 let average = self.average_dial_nanos.load(Ordering::Relaxed);
306 Duration::from_nanos(if average < 1_000_000_000 {
307 1_000_000_000
308 } else if average < 15_000_000_000 {
309 (2 * average) as u64
310 } else {
311 30_000_000_000
312 })
313 }
314
315 fn update_dial_timeout(&self, duration: Duration) {
316 let average = self.average_dial_nanos.load(Ordering::Relaxed);
317 let observed = duration.as_nanos().min(i64::MAX as u128) as i64;
318 self.average_dial_nanos
319 .fetch_add((observed - average) / 4, Ordering::Relaxed);
320 }
321
322 async fn dial(&self, transport: Transport) -> io::Result<Socket> {
323 let start = Instant::now();
324 let result = timeout(self.dial_timeout(), async {
325 if transport == Transport::Tcp {
326 Ok(Socket::Tcp(
327 connect_tcp(self.address, self.source_address).await?,
328 ))
329 } else {
330 let ip = if self.address.is_ipv4() {
331 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
332 } else {
333 IpAddr::V6(Ipv6Addr::UNSPECIFIED)
334 };
335 let socket =
336 UdpSocket::bind(SocketAddr::new(self.source_address.unwrap_or(ip), 0)).await?;
337 enable_recv_err(&socket, self.address);
338 socket.connect(self.address).await?;
339 Ok(Socket::Udp(socket))
340 }
341 })
342 .await;
343 self.update_dial_timeout(start.elapsed());
344 result.map_err(|_| io::ErrorKind::TimedOut)?
345 }
346
347 pub fn set_source_address(&mut self, source: Option<IpAddr>) {
349 if source == self.source_address {
350 return;
351 }
352 for shard in self.shards.iter_mut().flatten() {
353 shard.get_mut().unwrap().clear();
354 }
355 for idle in &self.idle {
356 idle.store(0, Ordering::Relaxed);
357 }
358 self.source_address = source;
359 let mut config = self.health.config();
360 config.source_address = source;
361 self.health = Health::with_config(config);
362 }
363
364 pub fn set_metrics(&mut self, registry: &Registry, name: &str) {
366 let proxy = registry.proxy(name, &self.address.to_string());
367 let mut config = self.health.config();
368 config.metrics = Some(proxy.clone());
369 self.health = Health::with_config(config);
370 self.metrics = Some(Metrics {
371 cache: [proxy.connection_cache("udp"), proxy.connection_cache("tcp")],
372 durations: (0..4096).map(|_| OnceLock::new()).collect(),
373 proxy,
374 });
375 }
376
377 pub fn set_health_config(&mut self, mut config: HealthConfig) {
379 assert_eq!(config.address, self.address);
380 if config.source_address.is_none() {
381 config.source_address = self.source_address;
382 }
383 if let Some(metrics) = &self.metrics {
384 config.metrics = Some(metrics.proxy.clone());
385 }
386 self.health = Health::with_config(config);
387 }
388
389 #[must_use]
390 pub fn down(&self) -> bool {
391 self.health.down()
392 }
393 pub async fn exchange(
394 &self,
395 query: &[u8],
396 response: &mut [u8],
397 transport: Transport,
398 options: Options,
399 ) -> Result<Exchange, Error> {
400 if self.stopped.load(Ordering::Acquire) {
401 return Err(Error::Stopped);
402 }
403 self.sweep();
404 let selected = if options.force_tcp {
405 Transport::Tcp
406 } else if options.prefer_udp {
407 Transport::Udp
408 } else {
409 transport
410 };
411 let result = self.exchange_once(query, response, selected).await;
412 let result = match result {
414 Ok(_result)
415 if selected == Transport::Udp
416 && options.prefer_udp
417 && !options.force_tcp
418 && response[2] & 0x02 != 0 =>
419 {
420 self.exchange_once(query, response, Transport::Tcp).await
421 }
422 other => other,
423 };
424 if matches!(result, Err(Error::Io(_))) {
425 self.health.start();
426 }
427 result
428 }
429
430 async fn exchange_once(
431 &self,
432 query: &[u8],
433 response: &mut [u8],
434 transport: Transport,
435 ) -> Result<Exchange, Error> {
436 let start = Instant::now();
437 let request = Message::parse(query).map_err(|_| Error::InvalidRequest)?;
438 if query.len() > response.len() || query.len() > u16::MAX as usize {
439 return Err(Error::InvalidRequest);
440 }
441 let protocol = usize::from(transport == Transport::Tcp);
442 let shard = SHARD.with(|index| *index);
443 let cached = {
444 let mut queue = self.shards[protocol][shard].lock().unwrap();
445 loop {
446 match queue.pop_front() {
447 Some(cached) => {
448 self.idle[protocol].fetch_sub(1, Ordering::Relaxed);
449 if cached.used.elapsed() <= self.expire
450 && (self.max_age.is_zero() || cached.created.elapsed() <= self.max_age)
451 {
452 break Some(cached);
453 }
454 }
455 None => break None,
456 }
457 }
458 };
459 let reused = cached.is_some();
460 if let Some(metrics) = &self.metrics {
461 if reused {
462 metrics.cache[protocol].hit();
463 } else {
464 metrics.cache[protocol].miss();
465 }
466 }
467 let (mut socket, created) = if let Some(cached) = cached {
469 (cached.socket, cached.created)
470 } else {
471 (self.dial(transport).await?, Instant::now())
472 };
473 let mut id = [0; 2];
474 if request
476 .additionals()
477 .any(|record| record.rtype == Type::TSIG)
478 {
479 id.copy_from_slice(&query[..2]);
480 } else {
481 getrandom::getrandom(&mut id)
483 .map_err(|_| Error::Io(io::Error::other("random ID generation failed")))?;
484 }
485 response[..query.len()].copy_from_slice(query);
486 response[..2].copy_from_slice(&id);
487 let local_addr = socket.local_addr()?;
488 let sent = timeout(READ_TIMEOUT, socket.send(&response[..query.len()]))
489 .await
490 .map_err(|_| Error::Io(io::ErrorKind::TimedOut.into()))?;
491 if let Err(err) = sent {
492 return Err(if reused && err.kind() == io::ErrorKind::UnexpectedEof {
493 Error::CachedClosed
494 } else {
495 Error::Io(err)
496 });
497 }
498 let received = timeout(self.read_timeout, async {
499 loop {
500 let len = socket.receive(response).await?;
501 if len < 12 && transport == Transport::Tcp {
502 return Err(io::ErrorKind::InvalidData.into());
503 }
504 if len >= 12 && response[..2] == id {
506 return Ok::<_, io::Error>(len);
507 }
508 }
509 })
510 .await
511 .map_err(|_| Error::Io(io::ErrorKind::TimedOut.into()))?;
512 let mut length = received.map_err(|err| {
513 if reused && err.kind() == io::ErrorKind::UnexpectedEof {
514 Error::CachedClosed
515 } else {
516 Error::Io(err)
517 }
518 })?;
519 if let Err(error) = Message::parse(&response[..length]) {
520 if transport != Transport::Udp || !is_overflow(&error) {
521 return Err(Error::Io(io::ErrorKind::InvalidData.into()));
522 }
523 let counts: [u8; 6] = response[6..12].try_into().unwrap();
524 response[6..12].fill(0);
525 let question_matches = Message::parse(&response[..length]).is_ok_and(|answer| {
526 answer.qr()
527 && request.question_count() == answer.question_count()
528 && request.questions().zip(answer.questions()).all(|(a, b)| {
529 a.name == b.name && a.qtype == b.qtype && a.qclass == b.qclass
530 })
531 });
532 response[6..12].copy_from_slice(&counts);
533 if !question_matches {
534 return Err(Error::Io(io::ErrorKind::InvalidData.into()));
535 }
536 length = 12
537 + request
538 .questions()
539 .map(|q| q.name.wire_len() + 4)
540 .sum::<usize>();
541 let flags = [response[2], response[3]];
542 response[..length].copy_from_slice(&query[..length]);
543 response[2] = flags[0] | 0x02;
545 response[3] = flags[1];
546 response[6..12].fill(0);
547 }
548 let answer = Message::parse(&response[..length])
549 .map_err(|_| Error::Io(io::ErrorKind::InvalidData.into()))?;
550 if let Some(metrics) = &self.metrics {
551 let code = answer.rcode().0;
552 metrics.durations[usize::from(code)]
554 .get_or_init(|| metrics.proxy.request_duration(&Rcode(code).to_string()))
555 .observe(start.elapsed().as_secs_f64());
556 }
557 let same_questions = request.question_count() == answer.question_count()
559 && request
560 .questions()
561 .zip(answer.questions())
562 .all(|(a, b)| a.name == b.name && a.qtype == b.qtype && a.qclass == b.qclass);
563 if !answer.qr() || !same_questions {
564 return Err(Error::Mismatch);
565 }
566 response[..2].copy_from_slice(&query[..2]);
568 {
569 let mut queue = self.shards[protocol][shard].lock().unwrap();
570 if !self.stopped.load(Ordering::Acquire)
571 && self.idle[protocol]
572 .try_update(Ordering::Relaxed, Ordering::Relaxed, |count| {
573 (self.max_idle_conns == 0 || count < self.max_idle_conns).then(|| count + 1)
574 })
575 .is_ok()
576 {
577 queue.push_back(Cached {
578 socket,
579 created,
580 used: Instant::now(),
581 });
582 }
583 }
584 Ok(Exchange {
585 len: length,
586 local_addr,
587 transport,
588 })
589 }
590}
591
592impl Drop for Proxy {
593 fn drop(&mut self) {
594 self.stop();
595 }
596}
597
598#[cfg(test)]
599mod tests {
600 use super::*;
601
602 #[cfg(target_os = "linux")]
603 #[tokio::test]
604 async fn udp_sockets_enable_error_queues_for_both_families() {
605 use nix::sys::socket::{
606 getsockopt,
607 sockopt::{Ipv4RecvErr, Ipv6RecvErr},
608 };
609 let v4 = UdpSocket::bind("127.0.0.1:0").await.unwrap();
610 enable_recv_err(&v4, v4.local_addr().unwrap());
611 assert!(getsockopt(&v4, Ipv4RecvErr).unwrap());
612 let v6 = UdpSocket::bind("[::1]:0").await.unwrap();
613 enable_recv_err(&v6, v6.local_addr().unwrap());
614 assert!(getsockopt(&v6, Ipv6RecvErr).unwrap());
615 }
616
617 #[test]
618 fn adaptive_dial_timeout_uses_weighted_average_and_bounds() {
619 let proxy = Proxy::new("127.0.0.1:53".parse().unwrap());
620 assert_eq!(proxy.dial_timeout(), Duration::from_secs(30));
621 proxy.update_dial_timeout(Duration::from_secs(3));
622 assert_eq!(proxy.dial_timeout(), Duration::from_secs(24));
623 for _ in 0..40 {
624 proxy.update_dial_timeout(Duration::ZERO);
625 }
626 assert_eq!(proxy.dial_timeout(), Duration::from_secs(1));
627 proxy.update_dial_timeout(Duration::from_secs(120));
628 assert_eq!(proxy.dial_timeout(), Duration::from_secs(30));
629 }
630
631 #[test]
632 fn only_truncated_message_and_name_errors_are_overflows() {
633 for (error, expected) in [
634 (MessageError::Truncated, true),
635 (MessageError::Name(NameError::Truncated), true),
636 (MessageError::Name(NameError::InvalidLabelType), false),
637 (MessageError::Name(NameError::PointerNotBackwards), false),
638 (MessageError::InvalidOpt, false),
639 ] {
640 assert_eq!(is_overflow(&error), expected, "{error:?}");
641 }
642 }
643}