Skip to main content

pharos_plugins/kubernetes/
mod.rs

1//! Kubernetes authoritative DNS backend.
2
3#[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(&registry)
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    // RFC 1035 §3.1: labels are at most 63 octets and wire names at most 255 octets.
243    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    // Kubernetes DNS spec 1.1.0 §2.1: an explicit endpoint hostname takes precedence.
322    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    // Kubernetes DNS spec 1.1.0 §2.3.3: a ClusterIP PTR points to its service name.
357    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    // Kubernetes DNS spec 1.1.0 §2.4.3: endpoint PTRs point to hostname.service names.
378    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        // KEP-1645 MCS DNS spec §2.4.1: headless records use ready endpoint addresses.
443        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        // KEP-1645 MCS DNS spec §2.3.1: ClusterSetIP records use the service IPs.
473        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    // Kubernetes DNS spec 1.1.0 §2.4.1: headless records use ready endpoint addresses.
507    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        // Kubernetes DNS spec 1.1.0 §2.3.1: ClusterIP records use the service IPs.
545        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                        // RFC 2181 §5: duplicate RRs in an RRset are suppressed.
876                        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                        // RFC 2181 §5: duplicate RRs in an RRset are suppressed.
916                        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            // Kubernetes DNS spec 1.1.0 §2.2: dns-version carries the schema version in TXT.
953            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            // Kubernetes DNS spec 1.1.0 §2.5: ExternalName services have a CNAME to the target.
1028            // RFC 1034 §4.3.2 step 3a: the CNAME is returned whatever the QTYPE.
1029            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                            // Kubernetes DNS spec 1.1.0 §2.3.2: ClusterIP SRV targets name services.
1104                            // Kubernetes DNS spec 1.1.0 §2.4.2: headless SRV targets name endpoints.
1105                            // KEP-1645 MCS DNS spec §2.4.2: headless SRV targets include cluster IDs.
1106                            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                    // Kubernetes DNS spec 1.1.0 §2.4.2: SRV answers may include target addresses.
1159                    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            // RFC 2308 §3: authoritative NXDOMAIN and NODATA responses include the zone SOA.
1227            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}