From 4daa1f4f0b36b10443c0925f52855cd870421e01 Mon Sep 17 00:00:00 2001 From: Alejandro Pedraza Date: Mon, 8 Apr 2024 10:30:27 -0500 Subject: [PATCH] feat(dns): Expand SRV resolver to account for IPv6 (#2864) Given the limitations of the dns resolver lib, to retrieve a service IP we currently take the SRV response target, which has the form `.svc.ns.svc.cluster-domain.`, and extract the first segment replacing dashes with dots. This fails for IPv6 address, so in this change we attempt parsing for IPv4 and if that fails fallback to IPv6 by instead replacing dots with colons. The `trust-dns-resolver` has been renamed to `hickory-resolver` upstream, so we follow suit and also upgrade the version. --- linkerd/dns/src/lib.rs | 49 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/linkerd/dns/src/lib.rs b/linkerd/dns/src/lib.rs index 70b8605384..c0905af2d2 100644 --- a/linkerd/dns/src/lib.rs +++ b/linkerd/dns/src/lib.rs @@ -130,13 +130,17 @@ impl Resolver { // because of: https://github.com/hickory-dns/hickory-dns/issues/872 // Here we rely in on the fact that the first label of the SRV // record's target will be the ip of the pod delimited by dashes - // instead of dots. We can alternatively do another lookup + // instead of dots/colons. We can alternatively do another lookup // on the pod's DNS but it seems unnecessary since the pod's // ip is in the target of the SRV record. fn srv_to_socket_addr(srv: rdata::SRV) -> Result { if let Some(first_label) = srv.target().iter().next() { if let Ok(utf8) = std::str::from_utf8(first_label) { - if let Ok(ip) = utf8.replace('-', ".").parse::() { + let mut res = utf8.replace('-', ".").parse::(); + if res.is_err() { + res = utf8.replace('-', ":").parse::(); + } + if let Ok(ip) = res { return Ok(net::SocketAddr::new(ip, srv.port())); } } @@ -185,8 +189,9 @@ impl ResolveError { #[cfg(test)] mod tests { - use super::{Name, Suffix}; - use std::str::FromStr; + use super::{Name, Resolver, Suffix}; + use std::{net, str::FromStr}; + use trust_dns_resolver::proto::rr::{domain, rdata}; #[test] fn test_dns_name_parsing() { @@ -289,6 +294,42 @@ mod tests { assert!(Suffix::from_str("").is_err(), "suffix must not be empty"); } + + #[test] + fn srv_to_socket_addr_invalid() { + let name = "foobar.linkerd-dst-headless.linkerd.svc.cluster.local."; + let target = domain::Name::from_str(name).unwrap(); + let srv = rdata::SRV::new(1, 1, 8086, target); + assert!(Resolver::srv_to_socket_addr(srv).is_err()); + } + + #[test] + fn srv_to_socket_addr_valid() { + struct Case { + input: &'static str, + output: &'static str, + } + + for case in &[ + Case { + input: "10-42-0-15.linkerd-dst-headless.linkerd.svc.cluster.local.", + output: "10.42.0.15", + }, + Case { + input: "2001-0db8-0000-0000-0000-ff00-0042-8329.linkerd-dst-headless.linkerd.svc.cluster.local.", + output: "2001:0db8:0000:0000:0000:ff00:0042:8329", + }, + Case { + input: "2001-0db8--0042-8329.linkerd-dst-headless.linkerd.svc.cluster.local.", + output: "2001:0db8::0042:8329", + }, + ] { + let target = domain::Name::from_str(case.input).unwrap(); + let srv = rdata::SRV::new(1, 1, 8086, target); + let socket = Resolver::srv_to_socket_addr(srv).unwrap(); + assert_eq!(socket.ip(), net::IpAddr::from_str(case.output).unwrap()); + } + } } #[cfg(fuzzing)]