From d247291572c040810814bae0f6f5dbac49687bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Sat, 7 Mar 2020 16:11:28 +0100 Subject: [PATCH] implement APL type --- Cargo.lock | 23 ++++ lib/dnsbox-base/Cargo.toml | 1 + lib/dnsbox-base/src/records/registry.rs | 2 +- lib/dnsbox-base/src/records/structs.rs | 4 +- lib/dnsbox-base/src/records/tests.rs | 9 ++ lib/dnsbox-base/src/records/weird_structs.rs | 125 ++++++++++++++++++- 6 files changed, 159 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f1440d..0a25a16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,11 @@ name = "bitflags" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitstring" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "block-buffer" version = "0.3.3" @@ -105,6 +110,15 @@ name = "cfg-if" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cidr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitstring 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -190,6 +204,7 @@ version = "0.1.0" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "cidr 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "dnsbox-derive 0.1.0", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -620,6 +635,11 @@ name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "sha-1" version = "0.8.1" @@ -920,6 +940,7 @@ dependencies = [ "checksum backtrace 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "18b50f5258d1a9ad8396d2d345827875de4261b158124d4c819d9b351454fae5" "checksum backtrace-sys 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "5b3a000b9c543553af61bc01cbfc403b04b5caa9e421033866f2e98061eb3e61" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" +"checksum bitstring 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3e54f7b7a46d7b183eb41e2d82965261fa8a1597c68b50aced268ee1fc70272d" "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" "checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" @@ -929,6 +950,7 @@ dependencies = [ "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" "checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum cidr 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2da1cf0f275bb8dc1867a7f40cdb3b746951db73a183048e6e37fa89ed81bd01" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" "checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" @@ -987,6 +1009,7 @@ dependencies = [ "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" diff --git a/lib/dnsbox-base/Cargo.toml b/lib/dnsbox-base/Cargo.toml index 784307b..711f8ab 100644 --- a/lib/dnsbox-base/Cargo.toml +++ b/lib/dnsbox-base/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] byteorder = "1.1.0" bytes = "0.4" +cidr = "0.1.0" data-encoding = "2.1.0" dnsbox-derive = { path = "../dnsbox-derive" } failure = "0.1.5" diff --git a/lib/dnsbox-base/src/records/registry.rs b/lib/dnsbox-base/src/records/registry.rs index cbc7d09..0a70cb3 100644 --- a/lib/dnsbox-base/src/records/registry.rs +++ b/lib/dnsbox-base/src/records/registry.rs @@ -154,7 +154,7 @@ impl Registry { r.register_known::(); r.register_unknown("SINK" , types::SINK); r.register_unknown("OPT" , types::OPT); - r.register_unknown("APL" , types::APL); + r.register_known::(); r.register_known::(); r.register_known::(); r.register_known::(); diff --git a/lib/dnsbox-base/src/records/structs.rs b/lib/dnsbox-base/src/records/structs.rs index 15e6a59..618bbba 100644 --- a/lib/dnsbox-base/src/records/structs.rs +++ b/lib/dnsbox-base/src/records/structs.rs @@ -298,9 +298,7 @@ pub struct DNAME { // fields too. // pub struct OPT; -// #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] -// #[RRClass(IN)] -// pub struct APL; +pub use super::weird_structs::APL; #[derive(Clone, PartialEq, Eq, Debug, DnsPacketData, DnsTextData, RRData)] #[RRClass(ANY)] diff --git a/lib/dnsbox-base/src/records/tests.rs b/lib/dnsbox-base/src/records/tests.rs index 1bad78d..d3bd1da 100644 --- a/lib/dnsbox-base/src/records/tests.rs +++ b/lib/dnsbox-base/src/records/tests.rs @@ -144,3 +144,12 @@ fn test_nsec3param() { fn test_spf() { test_txt_for::(); } + +#[test] +fn test_apl() { + check::("1:0.0.0.0/0", b"\x00\x01\x00\x00").unwrap(); + check::("!1:0.0.0.0/0", b"\x00\x01\x00\x80").unwrap(); + check::("2:::/0", b"\x00\x02\x00\x00").unwrap(); + check::("!2:::/0", b"\x00\x02\x00\x80").unwrap(); + check::("1:192.0.2.0/24 !2:2001:db8::/32", b"\x00\x01\x18\x03\xc0\x00\x02\x00\x02\x20\x84\x20\x01\x0d\xb8").unwrap(); +} diff --git a/lib/dnsbox-base/src/records/weird_structs.rs b/lib/dnsbox-base/src/records/weird_structs.rs index 2d4bedb..83b8ee1 100644 --- a/lib/dnsbox-base/src/records/weird_structs.rs +++ b/lib/dnsbox-base/src/records/weird_structs.rs @@ -3,7 +3,7 @@ use crate::errors::*; use crate::common_types::*; use failure::ResultExt; use crate::ser::RRData; -use crate::ser::packet::{DnsPacketData, DnsPacketWriteContext, remaining_bytes}; +use crate::ser::packet::{DnsPacketData, DnsPacketWriteContext, remaining_bytes, get_blob}; use crate::ser::text::{DnsTextData, DnsTextFormatter, DnsTextContext, next_field}; use std::fmt; use std::io::Read; @@ -360,6 +360,129 @@ impl DnsTextData for A6 { } } +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AplItem { + pub prefix: cidr::IpCidr, + pub negation: bool, +} + +#[derive(Clone, PartialEq, Eq, Debug, RRData)] +#[RRClass(IN)] +pub struct APL { + items: Vec, +} + +impl DnsPacketData for APL { + fn deserialize(data: &mut ::std::io::Cursor) -> Result { + let mut items = Vec::new(); + while data.has_remaining() { + let family: u16 = DnsPacketData::deserialize(data) + .context("failed parsing APL::ADDRESSFAMILY")?; + failure::ensure!(family == 1 || family == 2, "unknown APL::ADDRESSFAMILY {}", family); + let prefix: u8 = DnsPacketData::deserialize(data) + .context("failed parsing field APL::PREFIX")?; + let afd_length: u8 = DnsPacketData::deserialize(data) + .context("failed parsing field APL::AFDLENGTH")?; + let negation = 0 != (afd_length & 0x80); + let afd_length = afd_length & 0x7f; + let data = get_blob(data, afd_length as usize)?; + + failure::ensure!(!data.ends_with(b"\0"), "APL::AFDPART ends with trailing zero"); + + let address = if family == 1 { + failure::ensure!(prefix <= 32, "invalid APL::prefix {} for IPv4", prefix); + failure::ensure!((afd_length as u32) * 8 < (prefix as u32) + 7, "APL::AFDPART too long {} for prefix {}", afd_length, prefix); + assert!(afd_length <= 4); + let mut buf = [0u8; 4]; + buf[..data.len()].copy_from_slice(&data); + std::net::IpAddr::from(std::net::Ipv4Addr::from(buf)) + } else { + assert!(family == 2); + failure::ensure!(prefix <= 128, "invalid APL::prefix {} for IPv6", prefix); + failure::ensure!((afd_length as u32) * 8 < (prefix as u32) + 7, "AFD::AFDPART too long {} for prefix {}", afd_length, prefix); + assert!(afd_length <= 16); + let mut buf = [0u8; 16]; + buf[..data.len()].copy_from_slice(&data); + std::net::IpAddr::from(std::net::Ipv6Addr::from(buf)) + }; + use cidr::Cidr; + let prefix = cidr::IpCidr::new(address, prefix)?; + + items.push(AplItem { + prefix, + negation, + }) + } + Ok(APL { items }) + } + + fn serialize(&self, _context: &mut DnsPacketWriteContext, packet: &mut Vec) -> Result<()> { + use cidr::Cidr; + for item in &self.items { + packet.reserve(4); + packet.put_u16_be(if item.prefix.is_ipv4() { 1 } else { 2 }); + packet.put_u8(item.prefix.network_length()); + let negation_flag = if item.negation { 0x80 } else { 0x00 }; + let mut l = (item.prefix.network_length() + 7) / 4; + match &item.prefix { + cidr::IpCidr::V4(p) => { + let addr = p.first_address().octets(); + while l > 0 && addr[l as usize -1] == 0 { l -= 1; } + packet.put_u8(l | negation_flag); + packet.extend_from_slice(&addr[..l as usize]); + }, + cidr::IpCidr::V6(p) => { + let addr = p.first_address().octets(); + while l > 0 && addr[l as usize -1] == 0 { l -= 1; } + packet.put_u8(l | negation_flag); + packet.extend_from_slice(&addr[..l as usize]); + }, + } + } + Ok(()) + } +} + +impl DnsTextData for APL { + fn dns_parse(_context: &DnsTextContext, data: &mut &str) -> Result { + let mut items = Vec::new(); + for item in data.split_ascii_whitespace() { + let (negation, content) = if item.starts_with('!') { + (true, &item[1..]) + } else { + (false, item) + }; + let (afi, prefix) = match content.find(':') { + Some(colon) => (&content[..colon], &content[colon+1..]), + None => failure::bail!("no colon in APL item: {:?}", item), + }; + let afi = afi.parse::()?; + let prefix = match afi { + 1 => prefix.parse::()?.into(), + 2 => prefix.parse::()?.into(), + _ => failure::bail!("Unknown address family {} in item: {:?}", afi, item), + }; + items.push(AplItem { + prefix, + negation, + }); + } + *data = ""; + Ok(APL { items }) + } + + fn dns_format(&self, f: &mut DnsTextFormatter) -> fmt::Result { + for item in &self.items { + f.next_field()?; + let family = if item.prefix.is_ipv4() { 1 } else { 2 }; + let negation = if item.negation { "!" } else { "" }; + write!(f, "{}{}:{}", negation, family, item.prefix)?; + } + Ok(()) + } +} + + #[derive(Clone, PartialEq, Eq, Debug)] pub enum IpsecKeyGateway { None,