1#[cfg(test)]
4#[allow(unused_attributes)]
5mod authority_test;
6mod local;
7mod name;
8mod reverse;
9mod scratch;
10pub mod setup;
11
12use pharos_kubernetes::{
13 Controller,
14 object::{EndpointAddress, Port},
15 store::{Index, Store},
16};
17use pharos_proto::{
18 BuildError, Class, Message, MessageBuilder, Name, NameRef, Rcode, Section, Type,
19};
20use pharos_server::{Context, Next, Outcome, Phase, Protocol};
21use std::{
22 net::IpAddr,
23 sync::{Arc, Mutex, RwLock},
24};
25
26#[derive(Default)]
27struct State {
28 controller: Mutex<Option<Controller>>,
29 cache: RwLock<Cache>,
30 metrics: Mutex<pharos_server::metrics::Registry>,
31 scratch: scratch::Pool,
32}
33
34#[derive(Default)]
35struct Cache {
36 store: Option<Store>,
37 sync: Option<Arc<dyn pharos_server::Readiness>>,
38}
39
40pub struct Kubernetes {
41 config: Arc<setup::Config>,
42 zones: Vec<Name>,
43 multicluster_zones: Vec<Name>,
44 fallthrough: Vec<Name>,
45 local_ips: Vec<IpAddr>,
46 state: Arc<State>,
47}
48
49impl std::fmt::Debug for Kubernetes {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct("Kubernetes")
52 .field("config", &self.config)
53 .finish_non_exhaustive()
54 }
55}
56
57impl Kubernetes {
58 #[cfg(feature = "plugin-metadata")]
59 pub(crate) fn collect_metadata(&self, cx: &mut Context<'_>) {
60 if self.config.pod_mode == "verified" {
61 let ip = scratch::IpText::new(cx.client.ip());
62 let store = self.state.cache.read().unwrap().store.clone();
63 if let Some(pod) = store
64 .and_then(|store| store.with_read(|index| index.pod_snapshot_by_ip(ip.as_str())))
65 {
66 cx.metadata.set_source(pod);
67 }
68 }
69 let Some(question) = cx.query.questions().next() else {
70 return;
71 };
72 let Some(zone) = self
73 .zones
74 .iter()
75 .filter(|zone| contains(question.name, zone))
76 .max_by_key(|zone| zone.label_count())
77 else {
78 return;
79 };
80 let mut lower = [0u8; 255];
81 let mut len = 0;
82 for label in question.name.labels() {
83 lower[len] = label.len() as u8;
84 len += 1;
85 for byte in label {
86 lower[len] = byte.to_ascii_lowercase();
87 len += 1;
88 }
89 }
90 let name = NameRef::parse(&lower[..=len], 0).unwrap();
91 let mut labels = [&[][..]; 127];
92 let count = name.labels().count() - zone.label_count();
93 for (slot, label) in labels.iter_mut().zip(name.labels().take(count)) {
94 *slot = label;
95 }
96 let multicluster = self.multicluster_zones.contains(zone);
97 let Some(request) =
98 name::parse_multicluster(&labels[..count], self.config.zonal, multicluster)
99 else {
100 cx.metadata
101 .insert("kubernetes/parse-error", "invalid query name");
102 return;
103 };
104 for (key, value) in [
105 ("kubernetes/port-name", request.port),
106 ("kubernetes/protocol", request.protocol),
107 ("kubernetes/endpoint", request.endpoint),
108 ("kubernetes/service", request.service),
109 ("kubernetes/namespace", request.namespace),
110 ("kubernetes/kind", request.kind),
111 ] {
112 if let Ok(value) = std::str::from_utf8(value) {
113 cx.metadata.insert_copied(key, value);
114 }
115 }
116 if multicluster && let Ok(cluster) = std::str::from_utf8(request.cluster) {
117 cx.metadata.insert_copied("kubernetes/cluster", cluster);
118 }
119 if self.config.zonal {
120 let mut zone = [0u8; 255];
121 let mut used = 0;
122 for label in request.zone {
123 if used > 0 {
124 zone[used] = b'.';
125 used += 1;
126 }
127 zone[used..used + label.len()].copy_from_slice(label);
128 used += label.len();
129 }
130 if let Ok(value) = std::str::from_utf8(&zone[..used]) {
131 cx.metadata.insert_copied("kubernetes/zone", value);
132 }
133 }
134 }
135
136 pub fn new(config: setup::Config) -> Result<Self, String> {
137 Self::with_listen_addresses(config, &[])
138 }
139
140 pub(crate) fn with_listen_addresses(
141 config: setup::Config,
142 listen_addresses: &[String],
143 ) -> Result<Self, String> {
144 let zones = config
145 .zones
146 .iter()
147 .map(|z| z.parse::<Name>().map_err(|e| e.to_string()))
148 .collect::<Result<_, _>>()?;
149 let fallthrough = config
150 .fallthrough
151 .domains
152 .iter()
153 .map(|z| z.parse::<Name>().map_err(|e| e.to_string()))
154 .collect::<Result<_, _>>()?;
155 let multicluster_zones = config
156 .multicluster_zones
157 .iter()
158 .map(|z| z.parse::<Name>().map_err(|e| e.to_string()))
159 .collect::<Result<_, _>>()?;
160 Ok(Self {
161 config: Arc::new(config),
162 zones,
163 multicluster_zones,
164 fallthrough,
165 local_ips: local::snapshot(listen_addresses),
166 state: Arc::default(),
167 })
168 }
169
170 pub(crate) fn set_metrics(&self, registry: &pharos_server::metrics::Registry) {
171 *self.state.metrics.lock().unwrap() = registry.clone();
172 }
173
174 pub(crate) fn register_hooks(&self, lifecycle: &mut pharos_server::Lifecycle) {
175 let state = self.state.clone();
176 let config = self.config.clone();
177 let start = move || {
178 let state = state.clone();
179 let config = config.clone();
180 async move {
181 let previous = state.controller.lock().unwrap().take();
182 let controller = if let Some(controller) = previous {
183 controller.restart().await
184 } else {
185 let registry = state.metrics.lock().unwrap().clone();
186 let client = config
187 .client_config()
188 .client(®istry)
189 .await
190 .map_err(std::io::Error::other)?;
191 Controller::start_with_metrics(
192 client,
193 config.watch_config(),
194 registry.kubernetes(),
195 )
196 };
197 let sync = controller.sync_status();
198 let store = controller.store();
199 *state.controller.lock().unwrap() = Some(controller);
200 *state.cache.write().unwrap() = Cache {
201 store: Some(store),
202 sync: Some(sync.clone()),
203 };
204 Controller::wait_for_sync_status(sync.as_ref(), config.startup_timeout).await;
205 Ok(())
206 }
207 };
208 lifecycle.register(Phase::Startup, start.clone());
209 lifecycle.register(Phase::RestartFailed, start);
210 let state = self.state.clone();
211 let stop = move || {
212 state.cache.write().unwrap().sync = None;
213 let controller = state.controller.lock().unwrap().take();
214 let state = state.clone();
215 async move {
216 if let Some(mut controller) = controller {
217 controller.stop().await;
218 *state.controller.lock().unwrap() = Some(controller);
219 }
220 Ok(())
221 }
222 };
223 lifecycle.register(Phase::Restart, stop.clone());
224 lifecycle.register(Phase::Shutdown, stop);
225 }
226}
227
228fn contains(name: NameRef<'_>, zone: &Name) -> bool {
229 let z = NameRef::parse(zone.as_wire(), 0).unwrap();
230 let n = name.labels().count();
231 let count = z.labels().count();
232 n >= count
233 && name
234 .labels()
235 .skip(n - count)
236 .zip(z.labels())
237 .all(|(a, b)| a.eq_ignore_ascii_case(b))
238}
239
240fn child<'a>(prefix: &[&[u8]], zone: NameRef<'_>, wire: &'a mut [u8; 255]) -> Option<NameRef<'a>> {
241 let mut n = 0;
242 for label in prefix.iter().copied().chain(zone.labels()) {
244 if label.is_empty() || label.len() > 63 || n + label.len() + 2 > 255 {
245 return None;
246 }
247 wire[n] = label.len() as u8;
248 n += 1;
249 wire[n..n + label.len()].copy_from_slice(label);
250 n += label.len();
251 }
252 wire[n] = 0;
253 NameRef::parse(&wire[..n + 1], 0).ok()
254}
255
256fn soa(
257 builder: &mut MessageBuilder<'_>,
258 section: Section,
259 zone: NameRef<'_>,
260 ttl: u32,
261 serial: u32,
262) -> Result<(), BuildError> {
263 let mut ns_wire = [0; 255];
264 let mut host_wire = [0; 255];
265 let ns = child(&[b"ns", b"dns"], zone, &mut ns_wire).ok_or(BuildError::InvalidRdata)?;
266 let host = child(&[b"hostmaster"], zone, &mut host_wire).ok_or(BuildError::InvalidRdata)?;
267 builder.record_with(section, zone, Type::SOA, Class::IN, ttl.min(300), |w| {
268 w.write_name(ns)?;
269 w.write_name(host)?;
270 for number in [serial, 7200, 1800, 86400, ttl] {
271 w.write_bytes(&number.to_be_bytes())?;
272 }
273 Ok(())
274 })?;
275 Ok(())
276}
277
278fn matching(request: &name::Request<'_>, port: &Port) -> bool {
279 (request.port.is_empty() || request.port.eq_ignore_ascii_case(port.name.as_bytes()))
280 && (request.protocol.is_empty()
281 || request
282 .protocol
283 .eq_ignore_ascii_case(port.protocol.as_bytes()))
284}
285
286fn pod_ip(index: &Index, req: &name::Request<'_>, config: &setup::Config) -> Option<IpAddr> {
287 if config.pod_mode == "disabled" {
288 return None;
289 }
290 let namespace = std::str::from_utf8(req.namespace).ok()?;
291 if index.namespace(namespace).is_none() || req.service.len() > 63 {
292 return None;
293 }
294 let ipv4 = req.service.iter().filter(|&&b| b == b'-').count() == 3
295 && !req.service.windows(2).any(|w| w == b"--");
296 let mut buf = [0; 63];
297 for (i, &byte) in req.service.iter().enumerate() {
298 buf[i] = if byte == b'-' {
299 if ipv4 { b'.' } else { b':' }
300 } else {
301 byte
302 };
303 }
304 let text = std::str::from_utf8(&buf[..req.service.len()]).ok()?;
305 let ip = text.parse().ok()?;
306 if config.pod_mode == "verified"
307 && !index
308 .pods_by_ip(text)
309 .any(|pod| pod.namespace.as_ref().eq_ignore_ascii_case(namespace))
310 {
311 return None;
312 }
313 Some(ip)
314}
315
316fn endpoint_label<'a>(
317 address: &'a EndpointAddress,
318 pod_names: bool,
319 buf: &'a mut [u8; 64],
320) -> &'a [u8] {
321 if !address.hostname.is_empty() {
323 return address.hostname.as_bytes();
324 }
325 if pod_names && !address.target_ref_name.is_empty() {
326 return address.target_ref_name.as_bytes();
327 }
328 let bytes = address.ip.as_bytes();
329 if bytes.len() + 1 > buf.len() {
330 return b"";
331 }
332 for (i, byte) in bytes.iter().enumerate() {
333 buf[i] = if *byte == b'.' || *byte == b':' {
334 b'-'
335 } else {
336 *byte
337 };
338 }
339 let mut n = bytes.len();
340 if bytes.last() == Some(&b':') {
341 buf[n] = b'0';
342 n += 1;
343 }
344 &buf[..n]
345}
346
347fn visit_reverse(
348 index: &Index,
349 ip: &str,
350 zone: NameRef<'_>,
351 config: &setup::Config,
352 mut visit: impl FnMut(NameRef<'_>),
353) -> bool {
354 let exposed =
355 |namespace: &str| config.namespaces.is_empty() || index.namespace(namespace).is_some();
356 if let Some(service) = index
358 .services_by_ip_unfiltered(ip)
359 .find(|service| exposed(&service.namespace))
360 {
361 let mut wire = [0; 255];
362 if let Some(target) = child(
363 &[
364 service.name.as_bytes(),
365 service.namespace.as_bytes(),
366 b"svc",
367 ],
368 zone,
369 &mut wire,
370 ) {
371 visit(target);
372 return true;
373 }
374 return false;
375 }
376 let mut found = false;
377 for endpoints in index.endpoints_by_ip_unfiltered(ip) {
379 if !exposed(&endpoints.namespace) {
380 continue;
381 }
382 for address in &endpoints.addresses {
383 if address.ip != ip {
384 continue;
385 }
386 let mut label = [0; 64];
387 let hostname = endpoint_label(address, config.endpoint_pod_names, &mut label);
388 let mut wire = [0; 255];
389 if let Some(target) = child(
390 &[
391 hostname,
392 endpoints.service_name.as_bytes(),
393 endpoints.namespace.as_bytes(),
394 b"svc",
395 ],
396 zone,
397 &mut wire,
398 ) {
399 visit(target);
400 found = true;
401 }
402 }
403 }
404 found
405}
406
407fn external_name(index: &Index, req: &name::Request<'_>) -> Option<String> {
408 let service = index.service(
409 std::str::from_utf8(req.namespace).ok()?,
410 std::str::from_utf8(req.service).ok()?,
411 )?;
412 (service.service_type == "ExternalName").then(|| service.external_name.clone())
413}
414
415fn visit(
416 index: &Index,
417 req: &name::Request<'_>,
418 config: &setup::Config,
419 mut f: impl FnMut(&str, &Port, Option<(&[u8], Option<&[u8]>)>),
420) -> bool {
421 let Ok(namespace) = std::str::from_utf8(req.namespace) else {
422 return false;
423 };
424 let Ok(service) = std::str::from_utf8(req.service) else {
425 return false;
426 };
427 if index.namespace(namespace).is_none() {
428 return false;
429 }
430 if req.multicluster {
431 let Some(svc) = index.service_import(namespace, service) else {
432 return false;
433 };
434 if config.ignore_empty_service
435 && !svc.headless()
436 && !index
437 .multicluster_endpoints(namespace, service)
438 .any(|ep| !ep.endpoints.addresses.is_empty())
439 {
440 return false;
441 }
442 if svc.headless() || !req.endpoint.is_empty() {
444 let mut found = false;
445 for ep in index.multicluster_endpoints(namespace, service) {
446 if !req.endpoint.is_empty()
447 && !req.cluster.eq_ignore_ascii_case(ep.cluster_id.as_bytes())
448 {
449 continue;
450 }
451 for addr in &ep.endpoints.addresses {
452 let mut buf = [0; 64];
453 let label = endpoint_label(addr, config.endpoint_pod_names, &mut buf);
454 if !req.endpoint.is_empty() && !req.endpoint.eq_ignore_ascii_case(label) {
455 continue;
456 }
457 for port in &ep.endpoints.ports {
458 if matching(req, port) {
459 found = true;
460 f(
461 &addr.ip,
462 port,
463 Some((label, Some(ep.cluster_id.as_bytes()))),
464 );
465 }
466 }
467 }
468 }
469 return found;
470 }
471 let mut found = false;
472 for port in &svc.ports {
474 if matching(req, port) {
475 found = true;
476 for ip in &svc.cluster_ips {
477 f(ip, port, None);
478 }
479 }
480 }
481 return found;
482 }
483 let Some(svc) = index.service(namespace, service) else {
484 return false;
485 };
486 if config.ignore_empty_service
487 && !svc.headless()
488 && svc.service_type != "ExternalName"
489 && !index
490 .endpoints(namespace, service)
491 .any(|e| !e.addresses.is_empty())
492 {
493 return false;
494 }
495 if !req.zone.is_empty() && !svc.headless() {
496 return false;
497 }
498 if svc.service_type == "ExternalName" {
499 return req.endpoint.is_empty()
500 && req.port.is_empty()
501 && req.protocol.is_empty()
502 && !svc.external_name.is_empty()
503 && svc.external_name.parse::<IpAddr>().is_err();
504 }
505 let mut found = !req.zone.is_empty();
506 if svc.headless() || !req.endpoint.is_empty() {
508 let zone_matches = |ep: &pharos_kubernetes::object::Endpoints, addr: &EndpointAddress| {
509 ep.zones
510 .as_ref()
511 .and_then(|zones| zones.get(&addr.ip))
512 .is_some_and(|zone| {
513 zone.as_bytes()
514 .split(|b| *b == b'.')
515 .eq(req.zone.iter().copied())
516 })
517 };
518 let filter_zone = !req.zone.is_empty()
519 && (!req.prefer
520 || index.endpoints(namespace, service).any(|ep| {
521 ep.addresses.iter().any(|addr| {
522 zone_matches(ep, addr) && ep.ports.iter().any(|port| matching(req, port))
523 })
524 }));
525 for ep in index.endpoints(namespace, service) {
526 for addr in &ep.addresses {
527 if filter_zone && !zone_matches(ep, addr) {
528 continue;
529 }
530 let mut buf = [0; 64];
531 let label = endpoint_label(addr, config.endpoint_pod_names, &mut buf);
532 if !req.endpoint.is_empty() && !req.endpoint.eq_ignore_ascii_case(label) {
533 continue;
534 }
535 for port in &ep.ports {
536 if matching(req, port) {
537 found = true;
538 f(&addr.ip, port, Some((label, None)));
539 }
540 }
541 }
542 }
543 } else {
544 for port in &svc.ports {
546 if matching(req, port) {
547 found = true;
548 for ip in &svc.cluster_ips {
549 f(ip, port, None);
550 }
551 }
552 }
553 }
554 found
555}
556
557fn visit_ns(
558 index: &Index,
559 locals: &[IpAddr],
560 zone: NameRef<'_>,
561 config: &setup::Config,
562 mut f: impl FnMut(NameRef<'_>, IpAddr),
563) {
564 let mut found = false;
565 for local in locals {
566 let local_text = scratch::IpText::new(*local);
567 for ep in index.endpoints_by_ip_unfiltered(local_text.as_str()) {
568 found = true;
569 let Some(svc) = index.service_unfiltered(&ep.namespace, &ep.service_name) else {
570 continue;
571 };
572 if svc.headless() {
573 for addr in &ep.addresses {
574 let Ok(ip) = addr.ip.parse() else { continue };
575 let mut label = [0; 64];
576 let hostname = endpoint_label(addr, config.endpoint_pod_names, &mut label);
577 let mut wire = [0; 255];
578 if let Some(target) = child(
579 &[
580 hostname,
581 svc.name.as_bytes(),
582 svc.namespace.as_bytes(),
583 b"svc",
584 ],
585 zone,
586 &mut wire,
587 ) {
588 f(target, ip);
589 }
590 }
591 } else {
592 for addr in &svc.cluster_ips {
593 let Ok(ip) = addr.parse() else { continue };
594 let mut wire = [0; 255];
595 if let Some(target) = child(
596 &[svc.name.as_bytes(), svc.namespace.as_bytes(), b"svc"],
597 zone,
598 &mut wire,
599 ) {
600 f(target, ip);
601 }
602 }
603 }
604 }
605 }
606 if !found {
607 let mut wire = [0; 255];
608 if let Some(target) = child(&[b"ns", b"dns"], zone, &mut wire) {
609 for local in locals {
610 f(target, *local);
611 }
612 }
613 }
614}
615
616impl pharos_server::Plugin for Kubernetes {
617 fn readiness(&self) -> Option<&dyn pharos_server::Readiness> {
618 Some(self)
619 }
620
621 async fn serve<N: Next + Send>(&self, cx: &mut Context<'_>, next: N) -> Outcome {
622 let Some(q) = cx.query.questions().next() else {
623 return next.run(cx).await;
624 };
625 let Some(zone) = self
626 .zones
627 .iter()
628 .filter(|z| contains(q.name, z))
629 .max_by_key(|z| z.label_count())
630 else {
631 return next.run(cx).await;
632 };
633 if q.qtype == Type::AXFR || q.qtype == Type::IXFR {
634 return Outcome::Unanswered(Rcode::REFUSED);
635 }
636 let mut lower = [0u8; 255];
637 let mut n = 0;
638 for label in q.name.labels() {
639 lower[n] = label.len() as u8;
640 n += 1;
641 for b in label {
642 lower[n] = b.to_ascii_lowercase();
643 n += 1;
644 }
645 }
646 n += 1;
647 let lower_name = NameRef::parse(&lower[..n], 0).unwrap();
648 let mut labels = [&[][..]; 127];
649 let mut count = 0;
650 for label in lower_name.labels() {
651 labels[count] = label;
652 count += 1;
653 }
654 let reverse_name = reverse::is_reverse(&labels[..count]);
655 let reverse_ip = (q.qtype == Type::PTR)
656 .then(|| reverse::address(&labels[..count]))
657 .flatten()
658 .map(scratch::IpText::new);
659 let count = count - zone.label_count();
660 let mut zone_wire = [0u8; 255];
661 let mut zone_len = 0;
662 for label in q.name.labels().skip(count) {
663 zone_wire[zone_len] = label.len() as u8;
664 zone_len += 1;
665 zone_wire[zone_len..zone_len + label.len()].copy_from_slice(label);
666 zone_len += label.len();
667 }
668 let zone_ref = NameRef::parse(&zone_wire[..zone_len + 1], 0).unwrap();
669 let multicluster = self.multicluster_zones.contains(zone);
670 let req = name::parse_multicluster(&labels[..count], self.config.zonal, multicluster);
671 let (store, synced) = {
672 let guard = self.state.cache.read().unwrap();
673 (
674 guard.store.clone(),
675 guard.sync.as_ref().is_some_and(|sync| sync.ready()),
676 )
677 };
678 let store = store.unwrap_or_default();
679 let mut external = None;
680 let mut pod = None;
681 let primary =
682 NameRef::parse(self.zones[self.config.primary_zone_index].as_wire(), 0).unwrap();
683 let exists = if let Some(ip) = &reverse_ip {
684 store
685 .with_read(|index| visit_reverse(index, ip.as_str(), primary, &self.config, |_| {}))
686 } else if q.qtype == Type::SOA
687 || count == 1 && labels[0] == b"dns-version" && q.qtype == Type::TXT
688 || q.qtype != Type::TXT
689 && (count == 0
690 || count == 1 && (labels[0] == b"svc" || labels[0] == b"pod")
691 || count == 2 && labels[0] == b"ns" && labels[1] == b"dns")
692 {
693 if q.qtype == Type::SOA
694 && !multicluster
695 && !reverse_name
696 && let Some(req) = &req
697 && req.kind == b"svc"
698 && !req.service.is_empty()
699 {
700 external = store.with_read(|i| {
701 visit(i, req, &self.config, |_, _, _| {})
702 .then(|| external_name(i, req))
703 .flatten()
704 });
705 }
706 true
707 } else if let Some(req) = &req {
708 if reverse_name && !req.kind.is_empty() {
709 false
710 } else if req.kind == b"svc" && req.service.is_empty() {
711 q.qtype != Type::TXT
712 && std::str::from_utf8(req.namespace)
713 .ok()
714 .is_some_and(|ns| store.with_read(|i| i.namespace(ns).is_some()))
715 } else if req.kind == b"svc" {
716 store.with_read(|i| {
717 let ok = visit(i, req, &self.config, |_, _, _| {});
718 if ok && !multicluster {
719 external = external_name(i, req);
720 }
721 ok
722 })
723 } else if req.kind == b"pod" {
724 if req.service.is_empty() {
725 q.qtype != Type::TXT
726 && self.config.pod_mode != "disabled"
727 && std::str::from_utf8(req.namespace)
728 .ok()
729 .is_some_and(|ns| store.with_read(|i| i.namespace(ns).is_some()))
730 } else {
731 pod = store.with_read(|i| pod_ip(i, req, &self.config));
732 pod.is_some()
733 }
734 } else {
735 false
736 }
737 } else {
738 false
739 };
740 if !exists && self.fallthrough.iter().any(|z| contains(q.name, z)) {
741 return next.run(cx).await;
742 }
743 let code = if exists {
744 Rcode::NOERROR
745 } else if synced {
746 Rcode::NXDOMAIN
747 } else {
748 Rcode::SERVFAIL
749 };
750 let target = external.as_ref().and_then(|s| {
751 let fqdn = if s.ends_with('.') {
752 s.clone()
753 } else {
754 format!("{s}.")
755 };
756 fqdn.parse::<Name>().ok()
757 });
758 let mut truncated = false;
759 let chased = if matches!(q.qtype, Type::A | Type::AAAA)
760 && target.as_ref().is_some_and(|t| {
761 NameRef::parse(t.as_wire(), 0).is_ok_and(|name| name != lower_name)
762 }) {
763 if let Some(target) = &target {
764 let result = cx.self_lookup(target, q.qtype).await.ok();
765 truncated = result
766 .as_deref()
767 .and_then(|wire| Message::parse(wire).ok())
768 .is_some_and(|message| message.tc());
769 if NameRef::parse(target.as_wire(), 0).is_ok_and(|name| contains(name, zone)) {
770 result.filter(|wire| {
771 Message::parse(wire).is_ok_and(|msg| {
772 msg.rcode() == Rcode::NOERROR && msg.answer_count() > 0
773 })
774 })
775 } else {
776 result
777 }
778 } else {
779 None
780 }
781 } else {
782 None
783 };
784 let ns_ips = &self.local_ips;
785 let max = cx.max_response_size.max(512);
786 if cx.response.capacity() < max {
787 if cx.protocol == Protocol::Tcp {
788 cx.response.reserve(max - cx.response.len());
789 } else {
790 return Outcome::Error(pharos_server::Error::ResponseBufferTooSmall);
791 }
792 }
793 let mut scratch = self.state.scratch.lease();
794 let result = (|| -> Result<(), BuildError> {
795 let mut b = MessageBuilder::new(
796 cx.response,
797 max,
798 cx.query.id(),
799 0x8400
800 | (cx.query.flags() & 0x7910)
801 | code.0
802 | if truncated && chased.is_some() {
803 0x0200
804 } else {
805 0
806 },
807 )?;
808 b.question(q.name, q.qtype, q.qclass)?;
809 let mut answers = 0usize;
810 if exists && let Some(ip) = &reverse_ip {
811 let mut error = None;
812 store.with_read(|index| {
813 visit_reverse(index, ip.as_str(), primary, &self.config, |target| {
814 if error.is_none() && scratch.names.insert(scratch::Name::new(target)) {
815 match b.record_with(
816 Section::Answer,
817 q.name,
818 Type::PTR,
819 Class::IN,
820 self.config.ttl,
821 |writer| writer.write_name(target),
822 ) {
823 Ok(true) => answers += 1,
824 Ok(false) => {}
825 Err(err) => error = Some(err),
826 }
827 }
828 });
829 });
830 if let Some(err) = error {
831 return Err(err);
832 }
833 }
834 if count == 0 && q.qtype == Type::SOA {
835 soa(
836 &mut b,
837 Section::Answer,
838 zone_ref,
839 self.config.ttl,
840 store.with_read(|i| {
841 if multicluster {
842 i.multicluster_serial()
843 } else {
844 i.serial()
845 }
846 }),
847 )?;
848 answers += 1;
849 }
850 if count == 0 && q.qtype == Type::NS {
851 let mut err = None;
852 store.with_read(|i| {
853 visit_ns(i, ns_ips, zone_ref, &self.config, |ns, _| {
854 if err.is_none() && scratch.names.insert(scratch::Name::new(ns)) {
855 match b.record_with(
856 Section::Answer,
857 q.name,
858 Type::NS,
859 Class::IN,
860 self.config.ttl,
861 |w| w.write_name(ns),
862 ) {
863 Ok(true) => answers += 1,
864 Ok(false) => {}
865 Err(e) => err = Some(e),
866 }
867 }
868 })
869 });
870 if let Some(e) = err {
871 return Err(e);
872 }
873 store.with_read(|i| {
874 visit_ns(i, ns_ips, zone_ref, &self.config, |ns, ip| {
875 if err.is_some()
877 || !scratch.extra.insert((
878 scratch::Name::new(ns),
879 scratch::Address::new(scratch::IpText::new(ip).as_str()),
880 ))
881 {
882 return;
883 }
884 let result = match ip {
885 IpAddr::V4(ip) => b.record(
886 Section::Additional,
887 ns,
888 Type::A,
889 Class::IN,
890 self.config.ttl,
891 &ip.octets(),
892 ),
893 IpAddr::V6(ip) => b.record(
894 Section::Additional,
895 ns,
896 Type::AAAA,
897 Class::IN,
898 self.config.ttl,
899 &ip.octets(),
900 ),
901 };
902 if let Err(e) = result {
903 err = Some(e);
904 }
905 })
906 });
907 if let Some(e) = err {
908 return Err(e);
909 }
910 }
911 if count == 2 && labels[0] == b"ns" && labels[1] == b"dns" {
912 let mut err = None;
913 store.with_read(|i| {
914 visit_ns(i, ns_ips, zone_ref, &self.config, |_, ip| {
915 if !scratch
917 .addresses
918 .insert(scratch::Address::new(scratch::IpText::new(ip).as_str()))
919 {
920 return;
921 }
922 let result = match ip {
923 IpAddr::V4(ip) if q.qtype == Type::A => b.record(
924 Section::Answer,
925 q.name,
926 Type::A,
927 Class::IN,
928 self.config.ttl,
929 &ip.octets(),
930 ),
931 IpAddr::V6(ip) if q.qtype == Type::AAAA => b.record(
932 Section::Answer,
933 q.name,
934 Type::AAAA,
935 Class::IN,
936 self.config.ttl,
937 &ip.octets(),
938 ),
939 _ => return,
940 };
941 match result {
942 Ok(true) => answers += 1,
943 Ok(false) => {}
944 Err(e) => err = Some(e),
945 }
946 })
947 });
948 if let Some(e) = err {
949 return Err(e);
950 }
951 }
952 if count == 1 && labels[0] == b"dns-version" && q.qtype == Type::TXT {
954 b.record(
955 Section::Answer,
956 q.name,
957 Type::TXT,
958 Class::IN,
959 28800,
960 b"\x051.1.0",
961 )?;
962 answers += 1;
963 }
964 if let Some(ip) = pod {
965 if q.qtype == Type::SRV
966 && let Some(req) = &req
967 {
968 let mut wire = [0; 255];
969 if let Some(target) =
970 child(&[req.service, req.namespace, b"pod"], zone_ref, &mut wire)
971 && b.record_with(
972 Section::Answer,
973 q.name,
974 Type::SRV,
975 Class::IN,
976 self.config.ttl,
977 |w| {
978 w.write_bytes(&[0, 0, 0, 100, 0, 0])?;
979 w.write_uncompressed_name(target)
980 },
981 )?
982 {
983 answers += 1;
984 match ip {
985 IpAddr::V4(ip) => b.record(
986 Section::Additional,
987 target,
988 Type::A,
989 Class::IN,
990 self.config.ttl,
991 &ip.octets(),
992 )?,
993 IpAddr::V6(ip) => b.record(
994 Section::Additional,
995 target,
996 Type::AAAA,
997 Class::IN,
998 self.config.ttl,
999 &ip.octets(),
1000 )?,
1001 };
1002 }
1003 }
1004 let result = match ip {
1005 IpAddr::V4(ip) if q.qtype == Type::A => b.record(
1006 Section::Answer,
1007 q.name,
1008 Type::A,
1009 Class::IN,
1010 self.config.ttl,
1011 &ip.octets(),
1012 )?,
1013 IpAddr::V6(ip) if q.qtype == Type::AAAA => b.record(
1014 Section::Answer,
1015 q.name,
1016 Type::AAAA,
1017 Class::IN,
1018 self.config.ttl,
1019 &ip.octets(),
1020 )?,
1021 _ => false,
1022 };
1023 if result {
1024 answers += 1;
1025 }
1026 }
1027 if let Some(target) = &target
1030 && (!matches!(q.qtype, Type::A | Type::AAAA) || chased.is_some())
1031 {
1032 let target_ref = NameRef::parse(target.as_wire(), 0).unwrap();
1033 b.record_with(
1034 Section::Answer,
1035 q.name,
1036 Type::CNAME,
1037 Class::IN,
1038 self.config.ttl,
1039 |w| w.write_name(target_ref),
1040 )?;
1041 answers += 1;
1042 if let Some(wire) = &chased
1043 && let Ok(message) = Message::parse(wire)
1044 {
1045 for rr in message.answers() {
1046 if let Ok(data) = rr.data() {
1047 b.record_with(
1048 Section::Answer,
1049 rr.name,
1050 rr.rtype,
1051 rr.class,
1052 rr.ttl,
1053 |w| data.write(w),
1054 )?;
1055 answers += 1;
1056 }
1057 }
1058 }
1059 }
1060 if exists
1061 && external.is_none()
1062 && let Some(req) = &req
1063 && req.kind == b"svc"
1064 && !req.service.is_empty()
1065 {
1066 store.with_read(|i| -> Result<(), BuildError> {
1067 let mut total = 0usize;
1068 visit(i, req, &self.config, |_, _, _| total += 1);
1069 let mut write_error = None;
1070 let mut stopped = false;
1071 visit(i, req, &self.config, |ip, port, endpoint| {
1072 if stopped || write_error.is_some() {
1073 return;
1074 }
1075 let ip_text = ip;
1076 let Ok(ip) = ip_text.parse::<IpAddr>() else {
1077 return;
1078 };
1079 let result = if q.qtype == Type::A || q.qtype == Type::AAAA {
1080 if !scratch.addresses.insert(scratch::Address::new(ip_text)) {
1081 return;
1082 }
1083 match ip {
1084 IpAddr::V4(ip) if q.qtype == Type::A => b.record(
1085 Section::Answer,
1086 q.name,
1087 Type::A,
1088 Class::IN,
1089 self.config.ttl,
1090 &ip.octets(),
1091 ),
1092 IpAddr::V6(ip) if q.qtype == Type::AAAA => b.record(
1093 Section::Answer,
1094 q.name,
1095 Type::AAAA,
1096 Class::IN,
1097 self.config.ttl,
1098 &ip.octets(),
1099 ),
1100 _ => return,
1101 }
1102 } else if q.qtype == Type::SRV && port.port >= 0 {
1103 let mut wire = [0; 255];
1107 let mut parts = [&[][..]; 5];
1108 let start = if let Some((endpoint, cluster)) = endpoint {
1109 parts[0] = endpoint;
1110 if let Some(cluster) = cluster {
1111 parts[1] = cluster;
1112 0
1113 } else {
1114 parts[1] = endpoint;
1115 1
1116 }
1117 } else {
1118 2
1119 };
1120 parts[2] = req.service;
1121 parts[3] = req.namespace;
1122 parts[4] = b"svc";
1123 let Some(target) = child(&parts[start..], zone_ref, &mut wire) else {
1124 return;
1125 };
1126 if !scratch
1127 .srv
1128 .insert((scratch::Name::new(target), port.port as u16))
1129 {
1130 return;
1131 }
1132 let weight = (100 / total.max(1)).max(1) as u16;
1133 b.record_with(
1134 Section::Answer,
1135 q.name,
1136 Type::SRV,
1137 Class::IN,
1138 self.config.ttl,
1139 |w| {
1140 w.write_bytes(&0u16.to_be_bytes())?;
1141 w.write_bytes(&weight.to_be_bytes())?;
1142 w.write_bytes(&(port.port as u16).to_be_bytes())?;
1143 w.write_uncompressed_name(target)
1144 },
1145 )
1146 } else {
1147 return;
1148 };
1149 match result {
1150 Ok(true) => answers += 1,
1151 Ok(false) => stopped = true,
1152 Err(e) => write_error = Some(e),
1153 }
1154 });
1155 if let Some(err) = write_error {
1156 return Err(err);
1157 }
1158 if !stopped && q.qtype == Type::SRV && answers > 0 {
1160 let mut error = None;
1161 visit(i, req, &self.config, |ip, port, endpoint| {
1162 if stopped || error.is_some() || port.port < 0 {
1163 return;
1164 }
1165 let ip_text = ip;
1166 let Ok(ip) = ip_text.parse::<IpAddr>() else {
1167 return;
1168 };
1169 let mut wire = [0; 255];
1170 let mut parts = [&[][..]; 5];
1171 let start = if let Some((endpoint, cluster)) = endpoint {
1172 parts[0] = endpoint;
1173 if let Some(cluster) = cluster {
1174 parts[1] = cluster;
1175 0
1176 } else {
1177 parts[1] = endpoint;
1178 1
1179 }
1180 } else {
1181 2
1182 };
1183 parts[2] = req.service;
1184 parts[3] = req.namespace;
1185 parts[4] = b"svc";
1186 let Some(target) = child(&parts[start..], zone_ref, &mut wire) else {
1187 return;
1188 };
1189 if !scratch.extra.insert((
1190 scratch::Name::new(target),
1191 scratch::Address::new(ip_text),
1192 )) {
1193 return;
1194 }
1195 let result = match ip {
1196 IpAddr::V4(ip) => b.record(
1197 Section::Additional,
1198 target,
1199 Type::A,
1200 Class::IN,
1201 self.config.ttl,
1202 &ip.octets(),
1203 ),
1204 IpAddr::V6(ip) => b.record(
1205 Section::Additional,
1206 target,
1207 Type::AAAA,
1208 Class::IN,
1209 self.config.ttl,
1210 &ip.octets(),
1211 ),
1212 };
1213 match result {
1214 Ok(true) => {}
1215 Ok(false) => stopped = true,
1216 Err(e) => error = Some(e),
1217 }
1218 });
1219 if let Some(err) = error {
1220 return Err(err);
1221 }
1222 }
1223 Ok(())
1224 })?;
1225 }
1226 if answers == 0 {
1228 soa(
1229 &mut b,
1230 Section::Authority,
1231 zone_ref,
1232 self.config.ttl,
1233 store.with_read(|i| {
1234 if multicluster {
1235 i.multicluster_serial()
1236 } else {
1237 i.serial()
1238 }
1239 }),
1240 )?;
1241 }
1242 let _ = b.finish();
1243 Ok(())
1244 })();
1245 result.map_or_else(
1246 |e| Outcome::Error(pharos_server::Error::Build(e)),
1247 |()| Outcome::Response,
1248 )
1249 }
1250}
1251
1252impl pharos_server::Readiness for Kubernetes {
1253 fn ready(&self) -> bool {
1254 self.state
1255 .cache
1256 .read()
1257 .unwrap()
1258 .sync
1259 .as_ref()
1260 .is_some_and(|sync| sync.ready())
1261 }
1262}