use crate::{
encode::encoder::Encoder,
question::QType::{HTTPS, SVCB},
question::{QClass, Question},
rr::{ServiceBinding, ServiceBindingMode, ServiceParameter, RR},
{Dns, DomainName, Flags, Opcode, RCode},
};
use std::{
collections::BTreeSet,
net::{Ipv4Addr, Ipv6Addr},
str::FromStr,
};
#[test]
fn test_service_binding_encode_decode() {
let mut encoder = Encoder::default();
let domain_name = "_8765._baz.api.test".parse().unwrap();
let target_name = "svc4-baz.test".parse().unwrap();
let service_binding = ServiceBinding {
name: domain_name,
ttl: 7200,
priority: 0,
target_name,
parameters: BTreeSet::default(),
https: false,
};
let dns = service_binding_dns(0xeced, service_binding);
encoder.dns(&dns).unwrap();
let result = Dns::decode(encoder.bytes.freeze()).expect("Unable to parse encoded DNS");
assert_eq!(result.answers.len(), 1);
let answer = &result.answers[0];
assert!(
matches!(answer, RR::SVCB(service_binding) if service_binding.mode() == ServiceBindingMode::Alias)
)
}
#[test]
fn test_service_binding_alias_form() {
let mut encoder = Encoder::default();
let domain_name = "example.com".parse().unwrap();
let target_name = "foo.example.com".parse().unwrap();
let service_binding = ServiceBinding {
name: domain_name,
ttl: 300,
priority: 0,
target_name,
parameters: BTreeSet::default(),
https: false,
};
let dns = service_binding_dns(0xcccd, service_binding);
encoder.dns(&dns).unwrap();
let mut expected = vec![];
expected.extend_from_slice(b"\x00\x00"); expected.extend_from_slice(b"\x03foo"); expected.extend_from_slice(b"\xc0\x0c");
let (_, suffix) = encoder.bytes.split_at(encoder.bytes.len() - expected.len());
assert_eq!(suffix, expected.as_slice());
}
#[test]
fn test_service_binding_use_the_owername() {
let mut encoder = Encoder::default();
let domain_name = "example.test".parse().unwrap();
let target_name = DomainName::default();
let service_binding = ServiceBinding {
name: domain_name,
ttl: 7200,
priority: 1,
target_name,
parameters: BTreeSet::default(),
https: false,
};
let dns = service_binding_dns(0xccce, service_binding);
encoder.dns(&dns).unwrap();
let (_, suffix) = encoder.bytes.split_at(encoder.bytes.len() - 3);
assert_eq!(suffix, b"\x00\x01\x00");
}
#[test]
fn test_service_binding_map_port() {
let mut encoder = Encoder::default();
let domain_name = "example.com".parse().unwrap();
let target_name = "foo.example.com".parse().unwrap();
let service_binding = ServiceBinding {
name: domain_name,
ttl: 300,
priority: 16,
target_name,
parameters: vec![ServiceParameter::PORT { port: 53 }]
.into_iter()
.collect::<BTreeSet<ServiceParameter>>(),
https: false,
};
let dns = service_binding_dns(0xcddc, service_binding);
encoder.dns(&dns).unwrap();
let mut expected = vec![];
expected.extend_from_slice(b"\x00\x10"); expected.extend_from_slice(b"\x03foo"); expected.extend_from_slice(b"\xc0\x0c"); expected.extend_from_slice(b"\x00\x03"); expected.extend_from_slice(b"\x00\x02"); expected.extend_from_slice(b"\x00\x35");
let (_, suffix) = encoder.bytes.split_at(encoder.bytes.len() - expected.len());
assert_eq!(suffix, expected.as_slice());
}
#[test]
fn test_service_binding_unregistered_key_value() {
let mut encoder = Encoder::default();
let domain_name = "example.com".parse().unwrap();
let target_name = "foo.example.com".parse().unwrap();
let service_binding = ServiceBinding {
name: domain_name,
ttl: 300,
priority: 16,
target_name,
parameters: vec![ServiceParameter::PRIVATE {
number: 667,
wire_data: b"hello".to_vec(),
}]
.into_iter()
.collect::<BTreeSet<ServiceParameter>>(),
https: false,
};
let dns = service_binding_dns(0xcfcc, service_binding);
encoder.dns(&dns).unwrap();
let mut expected = vec![];
expected.extend_from_slice(b"\x00\x10"); expected.extend_from_slice(b"\x03foo"); expected.extend_from_slice(b"\xc0\x0c"); expected.extend_from_slice(b"\x02\x9b"); expected.extend_from_slice(b"\x00\x05"); expected.extend_from_slice(b"hello");
let (_, suffix) = encoder.bytes.split_at(encoder.bytes.len() - expected.len());
assert_eq!(suffix, expected.as_slice());
}
#[test]
fn test_service_binding_unregistered_key_unquoted_value() {
let mut encoder = Encoder::default();
let domain_name = "example.com".parse().unwrap();
let target_name = "foo.example.com".parse().unwrap();
let service_binding = ServiceBinding {
name: domain_name,
ttl: 300,
priority: 1,
target_name,
parameters: vec![ServiceParameter::PRIVATE {
number: 667,
wire_data: b"hello\xd2qoo".to_vec(),
}]
.into_iter()
.collect::<BTreeSet<ServiceParameter>>(),
https: false,
};
let dns = service_binding_dns(0xdffd, service_binding);
encoder.dns(&dns).unwrap();
let mut expected = vec![];
expected.extend_from_slice(b"\x00\x01"); expected.extend_from_slice(b"\x03foo"); expected.extend_from_slice(b"\xc0\x0c"); expected.extend_from_slice(b"\x02\x9b"); expected.extend_from_slice(b"\x00\x09"); expected.extend_from_slice(b"hello\xd2qoo");
let (_, suffix) = encoder.bytes.split_at(encoder.bytes.len() - expected.len());
assert_eq!(suffix, expected.as_slice());
}
#[test]
fn test_service_binding_ipv6_hints() {
let mut encoder = Encoder::default();
let domain_name = "example.com".parse().unwrap();
let target_name = "foo.example.com".parse().unwrap();
let service_binding = ServiceBinding {
name: domain_name,
ttl: 300,
priority: 1,
target_name,
parameters: vec![ServiceParameter::IPV6_HINT {
hints: vec![
Ipv6Addr::from_str("2001:db8::1").unwrap(),
Ipv6Addr::from_str("2001:db8::53:1").unwrap(),
],
}]
.into_iter()
.collect::<BTreeSet<ServiceParameter>>(),
https: false,
};
let dns = service_binding_dns(0xacca, service_binding);
encoder.dns(&dns).unwrap();
let mut expected = vec![];
expected.extend_from_slice(b"\x00\x01"); expected.extend_from_slice(b"\x03foo"); expected.extend_from_slice(b"\xc0\x0c"); expected.extend_from_slice(b"\x00\x06"); expected.extend_from_slice(b"\x00\x20"); expected.extend_from_slice(b"\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"); expected.extend_from_slice(b"\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x53\x00\x01");
let (_, suffix) = encoder.bytes.split_at(encoder.bytes.len() - expected.len());
assert_eq!(suffix, expected.as_slice());
}
#[test]
fn test_service_binding_ipv6_hint_in_ipv4_mapped_ipv6_presentation_format() {
let mut encoder = Encoder::default();
let domain_name = "example.com".parse().unwrap();
let target_name = "example.com".parse().unwrap();
let service_binding = ServiceBinding {
name: domain_name,
ttl: 300,
priority: 1,
target_name,
parameters: vec![ServiceParameter::IPV6_HINT {
hints: vec![Ipv6Addr::from_str("2001:db8:ffff:ffff:ffff:ffff:198.51.100.100").unwrap()],
}]
.into_iter()
.collect::<BTreeSet<ServiceParameter>>(),
https: false,
};
let dns = service_binding_dns(0xaedc, service_binding);
encoder.dns(&dns).unwrap();
let mut expected = vec![];
expected.extend_from_slice(b"\x00\x01"); expected.extend_from_slice(b"\xc0\x0c"); expected.extend_from_slice(b"\x00\x06"); expected.extend_from_slice(b"\x00\x10"); expected.extend_from_slice(b"\x20\x01\x0d\xb8\xff\xff\xff\xff\xff\xff\xff\xff\xc6\x33\x64\x64");
let (_, suffix) = encoder.bytes.split_at(encoder.bytes.len() - expected.len());
assert_eq!(suffix, expected.as_slice());
}
#[test]
fn test_service_binding_sort_multiple_parameters() {
let mut encoder = Encoder::default();
let domain_name = "example.org".parse().unwrap();
let target_name = "foo.example.org".parse().unwrap();
let service_binding = ServiceBinding {
name: domain_name,
ttl: 300,
priority: 16,
target_name,
parameters: vec![
ServiceParameter::ALPN {
alpn_ids: vec!["h2".to_string(), "h3-19".to_string()],
},
ServiceParameter::MANDATORY {
key_ids: vec![4, 1],
},
ServiceParameter::IPV4_HINT {
hints: vec![Ipv4Addr::from_str("192.0.2.1").unwrap()],
},
]
.into_iter()
.collect::<BTreeSet<ServiceParameter>>(),
https: false,
};
let dns = service_binding_dns(0xadda, service_binding);
encoder.dns(&dns).unwrap();
let mut expected = vec![];
expected.extend_from_slice(b"\x00\x10"); expected.extend_from_slice(b"\x03foo"); expected.extend_from_slice(b"\xc0\x0c"); expected.extend_from_slice(b"\x00\x00"); expected.extend_from_slice(b"\x00\x04"); expected.extend_from_slice(b"\x00\x01"); expected.extend_from_slice(b"\x00\x04"); expected.extend_from_slice(b"\x00\x01"); expected.extend_from_slice(b"\x00\x09"); expected.extend_from_slice(b"\x02"); expected.extend_from_slice(b"h2"); expected.extend_from_slice(b"\x05"); expected.extend_from_slice(b"h3-19"); expected.extend_from_slice(b"\x00\x04"); expected.extend_from_slice(b"\x00\x04"); expected.extend_from_slice(b"\xc0\x00\x02\x01");
let (_, suffix) = encoder.bytes.split_at(encoder.bytes.len() - expected.len());
assert_eq!(suffix, expected.as_slice());
}
#[test]
fn test_service_binding_alpn_with_escaped_values() {
let mut encoder = Encoder::default();
let domain_name = "example.org".parse().unwrap();
let target_name = "foo.example.org".parse().unwrap();
let service_binding = ServiceBinding {
name: domain_name,
ttl: 300,
priority: 16,
target_name,
parameters: vec![ServiceParameter::ALPN {
alpn_ids: vec!["f\\oo,bar".to_string(), "h2".to_string()],
}]
.into_iter()
.collect::<BTreeSet<ServiceParameter>>(),
https: false,
};
let dns = service_binding_dns(0xdfaf, service_binding);
encoder.dns(&dns).unwrap();
let mut expected = vec![];
expected.extend_from_slice(b"\x00\x10"); expected.extend_from_slice(b"\x03foo"); expected.extend_from_slice(b"\xc0\x0c"); expected.extend_from_slice(b"\x00\x01"); expected.extend_from_slice(b"\x00\x0c"); expected.extend_from_slice(b"\x08"); expected.extend_from_slice(b"f\\oo,bar"); expected.extend_from_slice(b"\x02"); expected.extend_from_slice(b"h2");
let (_, suffix) = encoder.bytes.split_at(encoder.bytes.len() - expected.len());
assert_eq!(suffix, expected.as_slice());
}
fn service_binding_dns(id: u16, service_binding: ServiceBinding) -> Dns {
Dns {
id,
flags: response_flag(),
questions: vec![Question {
domain_name: service_binding.name.to_owned(),
q_class: QClass::IN,
q_type: if service_binding.https { HTTPS } else { SVCB },
}],
answers: vec![if service_binding.https {
RR::HTTPS(service_binding)
} else {
RR::SVCB(service_binding)
}],
authorities: vec![],
additionals: vec![],
}
}
fn response_flag() -> Flags {
Flags {
qr: true,
opcode: Opcode::Query,
aa: false,
tc: false,
rd: false,
ra: false,
ad: false,
cd: false,
rcode: RCode::NoError,
}
}