1mod setup;
4mod store;
5
6use std::{
7 fmt::Write,
8 net::SocketAddr,
9 sync::{Arc, Mutex, OnceLock, Weak},
10 time::{Instant, SystemTime},
11};
12
13use pharos_proto::{
14 Message, MessageBuilder, MessageMut, Name, NameRef, Opcode, Rcode, Rdata, ResponseType, Type,
15};
16use pharos_server::{Context, Next, Outcome, Plugin};
17
18pub struct Cache {
20 config: setup::Config,
21 success: store::Store,
22 denial: store::Store,
23 metrics: Vec<(SocketAddr, pharos_server::metrics::CacheMetrics)>,
24 chain: OnceLock<Weak<crate::Chain>>,
25 writes: Mutex<()>,
26}
27
28struct RefreshGuard {
29 chain: Arc<crate::Chain>,
30 key: u64,
31 stored: Instant,
32 denial: bool,
33 recheck: std::time::Duration,
34 success: bool,
35}
36
37struct LocalRefreshGuard<'a> {
38 store: &'a store::Store,
39 key: u64,
40 stored: Instant,
41 recheck: std::time::Duration,
42 success: bool,
43}
44
45struct QueryGuard<'a, 'b> {
46 context: &'a mut Context<'b>,
47 query: Message<'b>,
48}
49
50impl Drop for QueryGuard<'_, '_> {
51 fn drop(&mut self) {
52 self.context.query = self.query;
53 }
54}
55
56impl Drop for LocalRefreshGuard<'_> {
57 fn drop(&mut self) {
58 self.store
59 .finish_refresh(self.key, self.stored, self.recheck, self.success);
60 }
61}
62
63impl Drop for RefreshGuard {
64 fn drop(&mut self) {
65 if let Some(cache) = &self.chain.cache {
66 let store = if self.denial {
67 &cache.denial
68 } else {
69 &cache.success
70 };
71 store.finish_refresh(self.key, self.stored, self.recheck, self.success);
72 }
73 }
74}
75
76impl std::fmt::Debug for Cache {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 f.debug_struct("Cache")
79 .field("config", &self.config)
80 .finish_non_exhaustive()
81 }
82}
83
84impl Cache {
85 pub(crate) fn parse(
86 cursor: &mut pharos_corefile::Cursor<'_>,
87 zones: &[String],
88 ) -> Result<Self, pharos_corefile::Error> {
89 let zones: Vec<&str> = zones.iter().map(String::as_str).collect();
90 let config = setup::parse(cursor, &zones)?;
91 Ok(Self {
92 success: store::Store::new(config.success_capacity),
93 denial: store::Store::new(config.denial_capacity),
94 metrics: Vec::new(),
95 chain: OnceLock::new(),
96 writes: Mutex::new(()),
97 config,
98 })
99 }
100
101 pub(crate) fn bind(&self, chain: &Arc<crate::Chain>) {
102 let _ = self.chain.set(Arc::downgrade(chain));
103 }
104
105 pub(crate) fn set_metrics(
106 &mut self,
107 registry: &pharos_server::metrics::Registry,
108 listeners: &[pharos_server::ListenerKey],
109 ) {
110 let zones = self
111 .config
112 .zones
113 .iter()
114 .map(Name::canonical)
115 .collect::<Vec<_>>()
116 .join(",");
117 for listener in listeners {
118 let address = listener.address;
119 let server = if address.ip().is_unspecified() {
120 format!("dns://:{}", address.port())
121 } else {
122 format!("dns://{address}")
123 };
124 self.metrics
125 .push((address, registry.cache(&server, &zones, "")));
126 }
127 }
128}
129
130impl Plugin for Cache {
131 async fn serve<N: Next + Send>(&self, cx: &mut Context<'_>, next: N) -> Outcome {
132 let query = cx.query;
133 let Some(question) = query.questions().next() else {
134 return next.run(cx).await;
135 };
136 if query.opcode() != Opcode::QUERY
137 || query.additionals().any(|record| record.rtype == Type::TSIG)
138 || question.name.wire_len()
139 != question
140 .name
141 .labels()
142 .map(|label| label.len() + 1)
143 .sum::<usize>()
144 + 1
145 || !self
146 .config
147 .zones
148 .iter()
149 .any(|zone| contains(question.name, zone))
150 {
151 return next.run(cx).await;
152 }
153 let metrics = self
154 .metrics
155 .iter()
156 .find_map(|(address, metrics)| (*address == cx.local).then_some(metrics));
157 if let Some(metrics) = metrics {
158 metrics.request();
159 }
160 let key = key(&query);
161 let can_grow = cx.protocol == pharos_server::Protocol::Tcp;
162 let positive_first = self.config.prefer_positive && !self.config.stale.is_zero();
163 let positive = positive_first
164 .then(|| {
165 self.success
166 .serve(key, &query, cx.response, &self.config, true, can_grow)
167 })
168 .flatten();
169 let hit = if let Some(hit) = positive {
170 Some((false, hit))
171 } else if let Some(denial) =
172 self.denial
173 .serve(key, &query, cx.response, &self.config, false, can_grow)
174 {
175 if cx.response[3] & 15 == 2 {
176 if let Some(positive) =
177 self.success
178 .serve(key, &query, cx.response, &self.config, false, can_grow)
179 {
180 if let Some(stored) = denial.refresh {
181 self.denial
182 .finish_refresh(key, stored, std::time::Duration::ZERO, true);
183 }
184 Some((false, positive))
185 } else {
186 Some((true, denial))
187 }
188 } else {
189 Some((true, denial))
190 }
191 } else {
192 self.success
193 .serve(key, &query, cx.response, &self.config, false, can_grow)
194 .map(|hit| (false, hit))
195 };
196 if let Some((denial, hit)) = hit {
197 if let Some(metrics) = metrics {
198 metrics.hit(denial);
199 }
200 if let Some(stored) = hit.refresh {
201 let store = if denial { &self.denial } else { &self.success };
202 if !query.can_set_ad_in_wire() {
203 store.finish_refresh(key, stored, self.config.stale_recheck, false);
204 } else if let Some(chain) = self.chain.get().and_then(Weak::upgrade) {
205 if (!hit.stale || !self.config.verify)
206 && let Some(metrics) = metrics
207 {
208 metrics.prefetch();
209 }
210 let task = self.spawn_refresh(chain, cx, key, denial, hit);
211 if hit.stale && self.config.verify {
212 let result = if self.config.verify_timeout.is_zero() {
213 task.await.ok()
214 } else {
215 tokio::time::timeout(self.config.verify_timeout, task)
216 .await
217 .ok()
218 .and_then(Result::ok)
219 };
220 if let Some(Some(mut response)) = result {
221 if !query.ad() && !query.opt().is_some_and(|opt| opt.dnssec_ok()) {
223 response[3] &= !0x20;
224 }
225 *cx.response = response;
226 return Outcome::Response;
227 }
228 }
229 } else if hit.stale && self.config.verify {
230 let mut guard = LocalRefreshGuard {
231 store,
232 key,
233 stored,
234 recheck: self.config.stale_recheck,
235 success: false,
236 };
237 let stale = cx.response.clone();
238 let outcome = {
239 cx.query = query.with_ad();
240 let guard = QueryGuard {
241 context: &mut *cx,
242 query,
243 };
244 next.run(&mut *guard.context).await
245 };
246 let valid =
247 outcome == Outcome::Response && self.accept_refresh(&query, cx.response);
248 if valid
249 || (self.config.prefer_positive
250 && outcome == Outcome::Response
251 && Self::matches_refresh(&query, cx.response))
252 {
253 self.process_response(cx, outcome, Some((denial, hit.generation)));
254 }
255 if !valid {
256 cx.response.clear();
257 cx.response.extend_from_slice(&stale);
258 }
259 guard.success = valid;
260 if valid {
261 return Outcome::Response;
262 }
263 } else {
264 store.finish_refresh(key, stored, self.config.stale_recheck, false);
265 }
266 }
267 if hit.stale
268 && let Some(metrics) = metrics
269 {
270 metrics.served_stale();
271 }
272 return Outcome::Response;
273 }
274 if let Some(metrics) = metrics {
275 metrics.miss();
276 }
277 if !query.can_set_ad_in_wire() {
278 return next.run(cx).await;
279 }
280 let outcome = {
281 cx.query = query.with_ad();
282 let guard = QueryGuard {
283 context: &mut *cx,
284 query,
285 };
286 next.run(&mut *guard.context).await
287 };
288 self.process_response(cx, outcome, None)
289 }
290}
291
292impl Cache {
293 fn matches_refresh(query: &Message<'_>, wire: &[u8]) -> bool {
294 let Ok(response) = Message::parse(wire) else {
295 return false;
296 };
297 let (Some(expected), Some(actual)) =
298 (query.questions().next(), response.questions().next())
299 else {
300 return false;
301 };
302 if response.tc()
303 || !response.qr()
304 || response.question_count() != 1
305 || response
306 .additionals()
307 .any(|record| record.rtype == Type::TSIG)
308 || expected.name != actual.name
309 || expected.qtype != actual.qtype
310 || expected.qclass != actual.qclass
311 {
312 return false;
313 }
314 true
315 }
316
317 fn accept_refresh(&self, query: &Message<'_>, wire: &[u8]) -> bool {
318 if !Self::matches_refresh(query, wire) {
319 return false;
320 }
321 let Ok(response) = Message::parse(wire) else {
322 return false;
323 };
324 if !matches!(response.rcode(), Rcode::NOERROR | Rcode::NXDOMAIN) {
325 return false;
326 }
327 let now = SystemTime::now()
328 .duration_since(SystemTime::UNIX_EPOCH)
329 .map_or(0, |duration| duration.as_secs().min(i64::MAX as u64) as i64);
330 let kind = pharos_proto::typify_response(Some(&response), now);
331 if !matches!(
332 kind,
333 ResponseType::Positive
334 | ResponseType::Referral
335 | ResponseType::NoData
336 | ResponseType::NxDomain
337 ) {
338 return false;
339 }
340 !self.config.prefer_positive
341 || (response.rcode() == Rcode::NOERROR && answers_question(&response))
342 }
343
344 fn spawn_refresh(
345 &self,
346 chain: Arc<crate::Chain>,
347 cx: &Context<'_>,
348 key: u64,
349 denial: bool,
350 hit: store::Hit,
351 ) -> tokio::task::JoinHandle<Option<Vec<u8>>> {
352 let stored = hit.refresh.expect("refresh requested");
353 let generation = hit.generation;
354 let stale = hit.stale;
355 let safe_query = cx.query.can_set_ad_in_wire();
356 let mut wire = Vec::with_capacity(cx.query.as_wire().len());
357 cx.query.copy_wire(&mut wire);
358 wire[3] |= 0x20;
359 let domain = cx.domain.clone();
360 let synthetic = !stale || !self.config.verify;
361 let client = if synthetic {
362 ([0, 0, 0, 0], 0).into()
363 } else {
364 cx.client
365 };
366 let local = cx.local;
367 let transport = cx.transport;
368 let protocol = if synthetic {
369 pharos_server::Protocol::Tcp
370 } else {
371 cx.protocol
372 };
373 let max_response_size = if synthetic {
374 65535
375 } else {
376 cx.max_response_size
377 };
378 let self_lookup = cx.self_lookup_handle();
379 let guard = RefreshGuard {
380 chain,
381 key,
382 stored,
383 denial,
384 recheck: if stale {
385 self.config.stale_recheck
386 } else {
387 std::time::Duration::ZERO
388 },
389 success: false,
390 };
391 tokio::spawn(async move {
392 let mut guard = guard;
393 if !safe_query {
394 return None;
395 }
396 let Some(cache) = &guard.chain.cache else {
397 return None;
398 };
399 let Ok(query) = Message::parse(&wire) else {
400 return None;
401 };
402 let mut response = Vec::with_capacity(65535);
403 let mut slots = [None; 16];
404 let mut context = Context {
405 query,
406 client,
407 local,
408 transport,
409 protocol,
410 domain: &domain,
411 metadata: pharos_server::Metadata::with_self_lookup(
412 &mut slots,
413 self_lookup.as_ref(),
414 ),
415 response: &mut response,
416 max_response_size,
417 };
418 let outcome = guard.chain.cache_refresh_next(&mut context).await;
419 let valid = outcome == Outcome::Response
420 && cache.accept_refresh(&context.query, context.response);
421 if valid
422 || (!stale && outcome == Outcome::Response)
423 || (stale
424 && cache.config.prefer_positive
425 && outcome == Outcome::Response
426 && Self::matches_refresh(&context.query, context.response))
427 {
428 cache.process_response(&mut context, outcome, Some((denial, generation)));
429 if valid
430 && denial
431 && Message::parse(context.response).is_ok_and(|message| {
432 message.rcode() == Rcode::NOERROR
433 && message.answer_count() > 0
434 && answers_question(&message)
435 })
436 {
437 let _write = cache.writes.lock().unwrap();
438 if cache.denial.remove_if_stored(key, stored)
439 && let Some(metrics) = cache
440 .metrics
441 .iter()
442 .find_map(|(address, metrics)| (*address == local).then_some(metrics))
443 {
444 metrics.entries(true, cache.denial.len());
445 }
446 }
447 }
448 guard.success = valid;
449 valid.then_some(response)
450 })
451 }
452
453 fn insert_response(
454 &self,
455 key: u64,
456 response: &Message<'_>,
457 ttl: u32,
458 denial: bool,
459 expected: Option<(bool, Instant)>,
460 ) -> Option<bool> {
461 let _write = self.writes.lock().unwrap();
462 if let Some((old_denial, stored)) = expected {
463 let old = if old_denial {
464 &self.denial
465 } else {
466 &self.success
467 };
468 if !old.is_current(key, stored) {
469 return None;
470 }
471 }
472 if denial {
473 self.denial
474 .insert(key, response, ttl, false, expected.is_some())
475 } else {
476 self.success.insert(
477 key,
478 response,
479 ttl,
480 self.config.prefer_positive,
481 expected.is_some(),
482 )
483 }
484 }
485
486 fn process_response(
487 &self,
488 cx: &mut Context<'_>,
489 outcome: Outcome,
490 expected: Option<(bool, Instant)>,
491 ) -> Outcome {
492 let query = cx.query;
493 let Some(question) = query.questions().next() else {
494 return outcome;
495 };
496 let key = key(&query);
497 let metrics = self
498 .metrics
499 .iter()
500 .find_map(|(address, metrics)| (*address == cx.local).then_some(metrics));
501 if outcome != Outcome::Response {
502 if matches!(
503 outcome,
504 Outcome::Unanswered(Rcode::SERVFAIL) | Outcome::Error(_)
505 ) && !self.config.fail_ttl.is_zero()
506 && !self
507 .config
508 .disable_denial
509 .iter()
510 .any(|zone| contains(question.name, zone))
511 {
512 let ttl = self.config.fail_ttl.as_secs() as u32;
513 if ttl > 0
514 && let Ok(mut builder) = MessageBuilder::new(
515 cx.response,
516 65535,
517 query.id(),
518 0x8002 | (query.flags() & 0x0110),
519 )
520 && matches!(
521 builder.question(question.name, question.qtype, question.qclass),
522 Ok(true)
523 )
524 {
525 let _ = builder.finish();
526 if let Ok(response) = Message::parse(cx.response)
527 && let Some(evicted) =
528 self.insert_response(key, &response, ttl, true, expected)
529 && let Some(metrics) = metrics
530 {
531 if evicted {
532 metrics.evict(true);
533 }
534 metrics.entries(false, self.success.len());
535 metrics.entries(true, self.denial.len());
536 }
537 }
538 }
539 return outcome;
540 }
541 let Ok(response) = Message::parse(cx.response) else {
542 return outcome;
543 };
544 if response
545 .additionals()
546 .any(|record| record.rtype == Type::TSIG)
547 {
548 return outcome;
549 }
550 let now = SystemTime::now()
551 .duration_since(SystemTime::UNIX_EPOCH)
552 .map_or(0, |duration| duration.as_secs().min(i64::MAX as u64) as i64);
553 let mut response_type = pharos_proto::typify_response(Some(&response), now);
554 let has_soa = response.authorities().any(|rr| rr.rtype == Type::SOA);
555 let incomplete = response_type == ResponseType::Positive
556 && response.answer_count() > 0
557 && !answers_question(&response);
558 if incomplete && has_soa {
560 response_type = ResponseType::NoData;
561 }
562 let denial = matches!(
563 response_type,
564 ResponseType::NxDomain | ResponseType::NoData | ResponseType::ServerError
565 );
566 let (minimum, maximum) = if denial {
567 (self.config.denial_min_ttl, self.config.denial_max_ttl)
568 } else {
569 (self.config.success_min_ttl, self.config.success_max_ttl)
570 };
571 let minimum = minimum.as_secs().min(u64::from(u32::MAX)) as u32;
572 let maximum = maximum.as_secs().min(u64::from(u32::MAX)) as u32;
573 let mut ttl = maximum;
574 let mut found = false;
575 for record in response
576 .answers()
577 .chain(response.authorities())
578 .chain(response.additionals())
579 {
580 if record.rtype == Type::OPT {
581 continue;
582 }
583 found = true;
584 ttl = ttl.min(record.ttl);
585 if denial && let Ok(Rdata::Soa { minimum, .. }) = record.data() {
587 ttl = ttl.min(minimum);
588 }
589 }
590 if !found
591 || !matches!(
592 response_type,
593 ResponseType::Positive | ResponseType::NxDomain | ResponseType::NoData
594 )
595 {
596 ttl = 5;
597 }
598 ttl = ttl.max(minimum).min(maximum);
599 if response_type == ResponseType::ServerError {
600 ttl = self.config.fail_ttl.as_secs() as u32;
601 }
602 let matches = response.questions().next().is_some_and(|q| {
603 q.name == question.name && q.qtype == question.qtype && q.qclass == question.qclass
604 });
605 let eligible = !response.tc()
606 && ttl > 0
607 && (!incomplete || has_soa)
608 && (matches!(
609 response_type,
610 ResponseType::Positive
611 | ResponseType::Referral
612 | ResponseType::NoData
613 | ResponseType::ServerError
614 ) || (response_type == ResponseType::NxDomain && has_soa));
615 if eligible
616 && !matches
617 && let Some(metrics) = metrics
618 {
619 metrics.drop_response();
620 }
621 let cacheable = eligible && matches;
622 let disabled = if denial {
623 &self.config.disable_denial
624 } else {
625 &self.config.disable_success
626 };
627 let cacheable = cacheable && !disabled.iter().any(|zone| contains(question.name, zone));
628 if let Ok(mut mutable) = MessageMut::parse(cx.response) {
629 mutable.rewrite_ttls(|_| ttl);
630 }
631 if cacheable && let Ok(response) = Message::parse(cx.response) {
632 let inserted = self.insert_response(key, &response, ttl, denial, expected);
633 if let Some(evicted) = inserted
634 && let Some(metrics) = metrics
635 {
636 if evicted {
637 metrics.evict(denial);
638 }
639 metrics.entries(false, self.success.len());
640 metrics.entries(true, self.denial.len());
641 }
642 }
643 if !query.ad() && !query.opt().is_some_and(|opt| opt.dnssec_ok()) {
645 cx.response[3] &= !0x20;
646 }
647 strip_trailing_opt(cx.response);
648 outcome
649 }
650}
651
652fn strip_trailing_opt(wire: &mut Vec<u8>) {
653 let Ok(message) = Message::parse(wire) else {
654 return;
655 };
656 if message.rcode().0 > 15 {
657 return;
658 }
659 let Some(opt) = message
660 .additionals()
661 .last()
662 .filter(|record| record.rtype == Type::OPT)
663 else {
664 return;
665 };
666 let start = opt.ttl_offset - 4 - opt.name.wire_len();
667 let count = message.additional_count() - 1;
668 wire.truncate(start);
669 wire[10..12].copy_from_slice(&count.to_be_bytes());
670}
671
672fn answers_question(message: &Message<'_>) -> bool {
673 let Some(question) = message.questions().next() else {
674 return false;
675 };
676 let matches_class =
677 |class| question.qclass == pharos_proto::Class::ANY || question.qclass == class;
678 if question.qtype == Type::ANY {
679 return message
680 .answers()
681 .any(|rr| rr.name == question.name && matches_class(rr.class));
682 }
683 let mut name = question.name.to_name();
685 let mut visited = Vec::new();
686 loop {
687 if visited.contains(&name) {
688 return false;
689 }
690 visited.push(name.clone());
691 let mut target: Option<Name> = None;
692 for rr in message.answers() {
693 if rr.rtype != Type::CNAME || rr.name != name || !matches_class(rr.class) {
694 continue;
695 }
696 let Ok(Rdata::Cname(alias)) = rr.data() else {
697 return false;
698 };
699 if target.as_ref().is_some_and(|existing| alias != *existing) {
700 return false;
701 }
702 target = Some(alias.to_name());
703 }
704 if question.qtype == Type::CNAME {
705 return target.is_some();
706 }
707 match target {
708 Some(alias) => name = alias,
709 None => {
710 return message.answers().any(|rr| {
711 rr.rtype == question.qtype && rr.name == name && matches_class(rr.class)
712 });
713 }
714 }
715 }
716}
717
718fn contains(name: NameRef<'_>, domain: &Name) -> bool {
719 let domain = NameRef::parse(domain.as_wire(), 0).expect("configured Domain");
720 let count = name.labels().count();
721 let suffix = domain.labels().count();
722 count >= suffix
723 && name
724 .labels()
725 .skip(count - suffix)
726 .zip(domain.labels())
727 .all(|(a, b)| a.eq_ignore_ascii_case(b))
728}
729
730struct KeyHash(u64);
731impl KeyHash {
732 fn bytes(&mut self, bytes: &[u8]) {
733 for byte in bytes {
734 self.0 = (self.0 ^ u64::from(*byte)).wrapping_mul(0x100000001b3);
735 }
736 }
737}
738impl std::fmt::Write for KeyHash {
739 fn write_str(&mut self, value: &str) -> std::fmt::Result {
740 for byte in value.bytes() {
741 self.bytes(&[byte.to_ascii_lowercase()]);
742 }
743 Ok(())
744 }
745}
746
747fn key(query: &Message<'_>) -> u64 {
748 let question = query.questions().next().expect("Query has a Question");
749 let mut hash = KeyHash(0xcbf29ce484222325);
750 hash.bytes(&[if query.opt().is_some_and(|opt| opt.dnssec_ok()) {
751 b'1'
752 } else {
753 b'0'
754 }]);
755 hash.bytes(&[if query.cd() { b'1' } else { b'0' }]);
756 hash.bytes(&question.qtype.0.to_be_bytes());
757 hash.bytes(&question.qclass.0.to_be_bytes());
758 write!(hash, "{}", question.name).expect("hash writer never fails");
759 hash.0
760}