From 1670a36cd3de173c523aba95cf239f2a3bc0478c Mon Sep 17 00:00:00 2001 From: vlad20012 Date: Tue, 19 Jan 2021 14:18:44 +0300 Subject: [PATCH] add HINFO record type --- .../client/src/serialize/txt/parse_rdata.rs | 1 + .../src/serialize/txt/rdata_parsers/hinfo.rs | 47 +++++ .../src/serialize/txt/rdata_parsers/mod.rs | 1 + crates/proto/src/rr/rdata/hinfo.rs | 188 ++++++++++++++++++ crates/proto/src/rr/rdata/mod.rs | 2 + crates/proto/src/rr/record_data.rs | 40 +++- crates/proto/src/rr/record_type.rs | 8 + 7 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 crates/client/src/serialize/txt/rdata_parsers/hinfo.rs create mode 100644 crates/proto/src/rr/rdata/hinfo.rs diff --git a/crates/client/src/serialize/txt/parse_rdata.rs b/crates/client/src/serialize/txt/parse_rdata.rs index 82654a6e81..5b67eb8714 100644 --- a/crates/client/src/serialize/txt/parse_rdata.rs +++ b/crates/client/src/serialize/txt/parse_rdata.rs @@ -45,6 +45,7 @@ impl RDataParser for RData { RecordType::AXFR => panic!("parsing AXFR doesn't make sense"), // valid panic, never should happen RecordType::CAA => caa::parse(tokens).map(RData::CAA)?, RecordType::CNAME => RData::CNAME(name::parse(tokens, origin)?), + RecordType::HINFO => RData::HINFO(hinfo::parse(tokens)?), RecordType::IXFR => panic!("parsing IXFR doesn't make sense"), // valid panic, never should happen RecordType::MX => RData::MX(mx::parse(tokens, origin)?), RecordType::NAPTR => RData::NAPTR(naptr::parse(tokens, origin)?), diff --git a/crates/client/src/serialize/txt/rdata_parsers/hinfo.rs b/crates/client/src/serialize/txt/rdata_parsers/hinfo.rs new file mode 100644 index 0000000000..9c34034ba9 --- /dev/null +++ b/crates/client/src/serialize/txt/rdata_parsers/hinfo.rs @@ -0,0 +1,47 @@ +// Copyright 2015-2021 Benjamin Fry +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! HINFO record for storing host information + +use crate::error::*; +use crate::rr::rdata::HINFO; + +/// Parse the RData from a set of Tokens +/// +/// ```text +/// IN HINFO DEC-2060 TOPS20 +/// IN HINFO VAX-11/780 UNIX +/// ``` +pub fn parse<'i, I: Iterator>(mut tokens: I) -> ParseResult { + let cpu = tokens + .next() + .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("cpu".to_string()))) + .map(ToString::to_string)?; + let os = tokens + .next() + .ok_or_else(|| ParseError::from(ParseErrorKind::MissingToken("os".to_string()))) + .map(ToString::to_string)?; + Ok(HINFO::new(cpu, os)) +} + +#[test] +fn test_parsing() { + // IN HINFO DEC-2060 TOPS20 + + assert_eq!( + parse(vec!["DEC-2060", "TOPS20"].into_iter()).expect("failed to parse NAPTR"), + HINFO::new("DEC-2060".to_string(), "TOPS20".to_string()), + ); +} + +#[test] +fn test_parsing_fails() { + // IN HINFO DEC-2060 TOPS20 + + assert!(parse(vec!["DEC-2060"].into_iter()).is_err()); + assert!(parse(vec![].into_iter()).is_err()); +} diff --git a/crates/client/src/serialize/txt/rdata_parsers/mod.rs b/crates/client/src/serialize/txt/rdata_parsers/mod.rs index 87e5d86ea7..c7fa683c8b 100644 --- a/crates/client/src/serialize/txt/rdata_parsers/mod.rs +++ b/crates/client/src/serialize/txt/rdata_parsers/mod.rs @@ -22,6 +22,7 @@ pub mod a; pub mod aaaa; pub mod caa; +pub mod hinfo; pub mod mx; pub mod name; pub mod naptr; diff --git a/crates/proto/src/rr/rdata/hinfo.rs b/crates/proto/src/rr/rdata/hinfo.rs new file mode 100644 index 0000000000..48c079db0a --- /dev/null +++ b/crates/proto/src/rr/rdata/hinfo.rs @@ -0,0 +1,188 @@ +// Copyright 2015-2021 Benjamin Fry +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! HINFO record for storing host information + +use std::fmt; + +use crate::error::*; +use crate::serialize::binary::*; + +/// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987][rfc1035] +/// +/// ```text +/// 3.3.2. HINFO RDATA format +/// +/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ +/// / CPU / +/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ +/// / OS / +/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ +/// +/// where: +/// +/// CPU A which specifies the CPU type. +/// +/// OS A which specifies the operating +/// system type. +/// +/// Standard values for CPU and OS can be found in [RFC-1010]. +/// +/// HINFO records are used to acquire general information about a host. The +/// main use is for protocols such as FTP that can use special procedures +/// when talking between machines or operating systems of the same type. +/// ``` +/// +/// [rfc1035]: https://tools.ietf.org/html/rfc1035 +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct HINFO { + cpu: Box<[u8]>, + os: Box<[u8]>, +} + +impl HINFO { + /// Creates a new HINFO record data. + /// + /// # Arguments + /// + /// * `cpu` - A which specifies the CPU type. + /// * `os` - A which specifies the operating system type. + /// + /// # Return value + /// + /// The new HINFO record data. + pub fn new(cpu: String, os: String) -> HINFO { + HINFO { + cpu: cpu.into_bytes().into_boxed_slice(), + os: os.into_bytes().into_boxed_slice(), + } + } + + /// Creates a new HINFO record data from bytes. + /// Allows creating binary record data. + /// + /// # Arguments + /// + /// * `cpu` - A which specifies the CPU type. + /// * `os` - A which specifies the operating system type. + /// + /// # Return value + /// + /// The new HINFO record data. + pub fn from_bytes(cpu: Box<[u8]>, os: Box<[u8]>) -> HINFO { + HINFO { cpu, os } + } + + /// A which specifies the CPU type. + pub fn cpu(&self) -> &[u8] { + &self.cpu + } + + /// A which specifies the operating system type. + pub fn os(&self) -> &[u8] { + &self.os + } +} + +/// Read the RData from the given Decoder +pub fn read(decoder: &mut BinDecoder<'_>) -> ProtoResult { + let cpu = decoder.read_character_data()? + .unverified(/*any data should be validate in HINFO CPU usage*/) + .to_vec() + .into_boxed_slice(); + let os = decoder.read_character_data()? + .unverified(/*any data should be validate in HINFO OS usage*/) + .to_vec() + .into_boxed_slice(); + + Ok(HINFO { cpu, os }) +} + +/// Write the RData from the given Decoder +pub fn emit(encoder: &mut BinEncoder<'_>, hinfo: &HINFO) -> ProtoResult<()> { + encoder.emit_character_data(&hinfo.cpu)?; + encoder.emit_character_data(&hinfo.os)?; + + Ok(()) +} + +/// [RFC 1033](https://tools.ietf.org/html/rfc1033), DOMAIN OPERATIONS GUIDE, November 1987 +/// +/// ```text +/// HINFO (Host Info) +/// +/// [] [] HINFO +/// +/// The HINFO record gives information about a particular host. The data +/// is two strings separated by whitespace. The first string is a +/// hardware description and the second is software. The hardware is +/// usually a manufacturer name followed by a dash and model designation. +/// The software string is usually the name of the operating system. +/// +/// Official HINFO types can be found in the latest Assigned Numbers RFC, +/// the latest of which is RFC-1010. The Hardware type is called the +/// Machine name and the Software type is called the System name. +/// +/// Some sample HINFO records: +/// +/// SRI-NIC.ARPA. HINFO DEC-2060 TOPS20 +/// UCBARPA.Berkeley.EDU. HINFO VAX-11/780 UNIX +/// ``` +impl fmt::Display for HINFO { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!( + f, + "{cpu} {os}", + cpu = &String::from_utf8_lossy(&self.cpu), + os = &String::from_utf8_lossy(&self.os) + )?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::dbg_macro, clippy::print_stdout)] + + use super::*; + + #[test] + pub fn test() { + let rdata = HINFO::new("cpu".to_string(), "os".to_string()); + + let mut bytes = Vec::new(); + let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); + assert!(emit(&mut encoder, &rdata).is_ok()); + let bytes = encoder.into_bytes(); + + println!("bytes: {:?}", bytes); + + let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); + let read_rdata = read(&mut decoder).expect("Decoding error"); + assert_eq!(rdata, read_rdata); + } + + #[test] + fn test_binary() { + let bin_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8]; + let rdata = HINFO::from_bytes( + b"cpu".to_vec().into_boxed_slice(), + bin_data.into_boxed_slice(), + ); + + let mut bytes = Vec::new(); + let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes); + assert!(emit(&mut encoder, &rdata).is_ok()); + let bytes = encoder.into_bytes(); + + println!("bytes: {:?}", bytes); + + let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes); + let read_rdata = read(&mut decoder).expect("Decoding error"); + assert_eq!(rdata, read_rdata); + } +} diff --git a/crates/proto/src/rr/rdata/mod.rs b/crates/proto/src/rr/rdata/mod.rs index f463cdbde9..e08c686543 100644 --- a/crates/proto/src/rr/rdata/mod.rs +++ b/crates/proto/src/rr/rdata/mod.rs @@ -22,6 +22,7 @@ pub mod a; pub mod aaaa; pub mod caa; +pub mod hinfo; pub mod mx; pub mod name; pub mod naptr; @@ -35,6 +36,7 @@ pub mod tlsa; pub mod txt; pub use self::caa::CAA; +pub use self::hinfo::HINFO; pub use self::mx::MX; pub use self::naptr::NAPTR; pub use self::null::NULL; diff --git a/crates/proto/src/rr/record_data.rs b/crates/proto/src/rr/record_data.rs index a929e83249..800cf6351c 100644 --- a/crates/proto/src/rr/record_data.rs +++ b/crates/proto/src/rr/record_data.rs @@ -27,7 +27,7 @@ use log::{trace, warn}; use super::domain::Name; use super::rdata; -use super::rdata::{CAA, MX, NAPTR, NULL, OPENPGPKEY, OPT, SOA, SRV, SSHFP, TLSA, TXT}; +use super::rdata::{CAA, HINFO, MX, NAPTR, NULL, OPENPGPKEY, OPT, SOA, SRV, SSHFP, TLSA, TXT}; use super::record_type::RecordType; use crate::error::*; use crate::serialize::binary::*; @@ -169,6 +169,32 @@ pub enum RData { /// ``` CNAME(Name), + /// ```text + /// 3.3.2. HINFO RDATA format + /// + /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + /// / CPU / + /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + /// / OS / + /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + /// + /// where: + /// + /// CPU A which specifies the CPU type. + /// + /// OS A which specifies the operating + /// system type. + /// + /// Standard values for CPU and OS can be found in [RFC-1010]. + /// + /// HINFO records are used to acquire general information about a host. The + /// main use is for protocols such as FTP that can use special procedures + /// when talking between machines or operating systems of the same type. + /// ``` + /// + /// `HINFO` is also used by [RFC 8482](https://tools.ietf.org/html/rfc8482) + HINFO(HINFO), + /// ```text /// 3.3.9. MX RDATA format /// @@ -642,6 +668,10 @@ impl RData { trace!("reading CNAME"); rdata::name::read(decoder).map(RData::CNAME) } + RecordType::HINFO => { + trace!("reading HINFO"); + rdata::hinfo::read(decoder).map(RData::HINFO) + } RecordType::ZERO => { trace!("reading EMPTY"); return Ok(RData::ZERO); @@ -800,6 +830,7 @@ impl RData { RData::CNAME(ref name) | RData::NS(ref name) | RData::PTR(ref name) => { rdata::name::emit(encoder, name) } + RData::HINFO(ref hinfo) => rdata::hinfo::emit(encoder, hinfo), RData::ZERO => Ok(()), // to_lowercase for rfc4034 and rfc6840 RData::MX(ref mx) => rdata::mx::emit(encoder, mx), @@ -838,6 +869,7 @@ impl RData { RData::ANAME(..) => RecordType::ANAME, RData::CAA(..) => RecordType::CAA, RData::CNAME(..) => RecordType::CNAME, + RData::HINFO(..) => RecordType::HINFO, RData::MX(..) => RecordType::MX, RData::NAPTR(..) => RecordType::NAPTR, RData::NS(..) => RecordType::NS, @@ -880,6 +912,7 @@ impl fmt::Display for RData { RData::CAA(ref caa) => w(f, caa), // to_lowercase for rfc4034 and rfc6840 RData::CNAME(ref name) | RData::NS(ref name) | RData::PTR(ref name) => w(f, name), + RData::HINFO(ref hinfo) => w(f, hinfo), RData::ZERO => Ok(()), // to_lowercase for rfc4034 and rfc6840 RData::MX(ref mx) => w(f, mx), @@ -1025,6 +1058,10 @@ mod tests { b'm', b'p', b'l', b'e', 3, b'c', b'o', b'm', 0, ], ), + ( + RData::HINFO(HINFO::new("cpu".to_string(), "os".to_string())), + vec![3, b'c', b'p', b'u', 2, b'o', b's'], + ), ] } @@ -1120,6 +1157,7 @@ mod tests { RData::ANAME(..) => RecordType::ANAME, RData::CAA(..) => RecordType::CAA, RData::CNAME(..) => RecordType::CNAME, + RData::HINFO(..) => RecordType::HINFO, RData::MX(..) => RecordType::MX, RData::NAPTR(..) => RecordType::NAPTR, RData::NS(..) => RecordType::NS, diff --git a/crates/proto/src/rr/record_type.rs b/crates/proto/src/rr/record_type.rs index d81a2c802b..c041e8d78e 100644 --- a/crates/proto/src/rr/record_type.rs +++ b/crates/proto/src/rr/record_type.rs @@ -61,6 +61,8 @@ pub enum RecordType { CNAME, // DHCID, // 49 RFC 4701 DHCP identifier // DNAME, // 39 RFC 2672 Delegation Name + /// RFC 1035[1] host information + HINFO, // HIP, // 55 RFC 5205 Host Identity Protocol // IPSECKEY, // 45 RFC 4025 IPsec Key /// RFC 1996 Incremental Zone Transfer @@ -169,6 +171,7 @@ impl FromStr for RecordType { "ANAME" => Ok(RecordType::ANAME), "CAA" => Ok(RecordType::CAA), "CNAME" => Ok(RecordType::CNAME), + "HINFO" => Ok(RecordType::HINFO), "NULL" => Ok(RecordType::NULL), "MX" => Ok(RecordType::MX), "NAPTR" => Ok(RecordType::NAPTR), @@ -279,6 +282,7 @@ impl From for &'static str { RecordType::AXFR => "AXFR", RecordType::CAA => "CAA", RecordType::CNAME => "CNAME", + RecordType::HINFO => "HINFO", RecordType::ZERO => "", RecordType::IXFR => "IXFR", RecordType::MX => "MX", @@ -320,6 +324,7 @@ impl From for u16 { RecordType::AXFR => 252, RecordType::CAA => 257, RecordType::CNAME => 5, + RecordType::HINFO => 13, RecordType::ZERO => 0, RecordType::IXFR => 251, RecordType::MX => 15, @@ -376,6 +381,7 @@ mod tests { RecordType::SOA, RecordType::NULL, RecordType::PTR, + RecordType::HINFO, RecordType::MX, RecordType::TXT, RecordType::AAAA, @@ -397,6 +403,7 @@ mod tests { RecordType::CNAME, RecordType::TXT, RecordType::AAAA, + RecordType::HINFO, ]; unordered.sort(); @@ -418,6 +425,7 @@ mod tests { "ANAME", "CAA", "CNAME", + "HINFO", "NULL", "MX", "NAPTR",