Browse Source

Support HINFO records

Benjamin Sago 4 years ago
parent
commit
33d8622c63
13 changed files with 234 additions and 2 deletions
  1. 117 0
      dns/src/record/hinfo.rs
  2. 6 0
      dns/src/record/mod.rs
  3. 1 1
      dns/src/record/mx.rs
  4. 0 1
      dns/src/record/others.rs
  5. 2 0
      dns/src/wire.rs
  6. 2 0
      src/colours.rs
  7. 10 0
      src/output.rs
  8. 1 0
      src/table.rs
  9. 19 0
      xtests/https.toml
  10. 19 0
      xtests/json.toml
  11. 19 0
      xtests/tcp.toml
  12. 19 0
      xtests/tls.toml
  13. 19 0
      xtests/udp.toml

+ 117 - 0
dns/src/record/hinfo.rs

@@ -0,0 +1,117 @@
+use log::*;
+
+use crate::wire::*;
+
+
+/// A (an?) **HINFO** _(host information)_ record, which contains the CPU and
+/// OS information about a host.
+///
+/// It also gets used as the response for an `ANY` query, if it is blocked.
+///
+/// # References
+///
+/// - [RFC 1035 §3.3.2](https://tools.ietf.org/html/rfc1035) — Domain Names, Implementation and Specification (November 1987)
+/// - [RFC 8482 §6](https://tools.ietf.org/html/rfc8482#section-6) — Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY (January 2019)
+#[derive(PartialEq, Debug)]
+pub struct HINFO {
+
+    /// The CPU field, specifying the CPU type.
+    pub cpu: String,
+
+    /// The OS field, specifying the operating system.
+    pub os: String,
+}
+
+impl Wire for HINFO {
+    const NAME: &'static str = "HINFO";
+    const RR_TYPE: u16 = 13;
+
+    #[cfg_attr(all(test, feature = "with_mutagen"), ::mutagen::mutate)]
+    fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result<Self, WireError> {
+
+        let cpu_length = c.read_u8()?;
+        trace!("Parsed CPU length -> {:?}", cpu_length);
+
+        let mut cpu_buffer = Vec::with_capacity(cpu_length.into());
+        for _ in 0 .. cpu_length {
+            cpu_buffer.push(c.read_u8()?);
+        }
+
+        let cpu = String::from_utf8_lossy(&cpu_buffer).to_string();
+        trace!("Parsed CPU -> {:?}", cpu);
+
+        let os_length = c.read_u8()?;
+        trace!("Parsed OS length -> {:?}", os_length);
+
+        let mut os_buffer = Vec::with_capacity(os_length.into());
+        for _ in 0 .. os_length {
+            os_buffer.push(c.read_u8()?);
+        }
+
+        let os = String::from_utf8_lossy(&os_buffer).to_string();
+        trace!("Parsed OS -> {:?}", cpu);
+
+        let length_after_labels = 1 + u16::from(cpu_length) + 1 + u16::from(os_length);
+        if stated_length == length_after_labels {
+            trace!("Length is correct");
+            Ok(Self { cpu, os })
+        }
+        else {
+            warn!("Length is incorrect (stated length {:?}, cpu plus length {:?}", stated_length, length_after_labels);
+            Err(WireError::WrongLabelLength { stated_length, length_after_labels })
+        }
+    }
+}
+
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn parses() {
+        let buf = &[
+            0x0e,  // cpu length
+            0x73, 0x6f, 0x6d, 0x65, 0x2d, 0x6b, 0x69, 0x6e, 0x64, 0x61, 0x2d,
+            0x63, 0x70, 0x75,  // cpu
+            0x0d,  // os length
+            0x73, 0x6f, 0x6d, 0x65, 0x2d, 0x6b, 0x69, 0x6e, 0x64, 0x61, 0x2d,
+            0x6f, 0x73,  // os
+        ];
+
+        assert_eq!(HINFO::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
+                   HINFO {
+                       cpu: String::from("some-kinda-cpu"),
+                       os: String::from("some-kinda-os"),
+                   });
+    }
+
+    #[test]
+    fn incorrect_record_length() {
+        let buf = &[
+            0x03,  // cpu length
+            0x65, 0x66, 0x67,  // cpu
+            0x03,  // os length
+            0x68, 0x69, 0x70,  // os
+        ];
+
+        assert_eq!(HINFO::read(6, &mut Cursor::new(buf)),
+                   Err(WireError::WrongLabelLength { stated_length: 6, length_after_labels: 8 }));
+    }
+
+    #[test]
+    fn record_empty() {
+        assert_eq!(HINFO::read(0, &mut Cursor::new(&[])),
+                   Err(WireError::IO));
+    }
+
+    #[test]
+    fn buffer_ends_abruptly() {
+        let buf = &[
+            0x14, 0x0A, 0x0B, 0x0C,  // 32-bit CPU
+        ];
+
+        assert_eq!(HINFO::read(23, &mut Cursor::new(buf)),
+                   Err(WireError::IO));
+    }
+}

+ 6 - 0
dns/src/record/mod.rs

@@ -13,6 +13,9 @@ pub use self::caa::CAA;
 mod cname;
 pub use self::cname::CNAME;
 
+mod hinfo;
+pub use self::hinfo::HINFO;
+
 mod mx;
 pub use self::mx::MX;
 
@@ -55,6 +58,9 @@ pub enum Record {
     /// A **CNAME** record.
     CNAME(CNAME),
 
+    /// A **HINFO** record.
+    HINFO(HINFO),
+
     /// A **MX** record.
     MX(MX),
 

+ 1 - 1
dns/src/record/mx.rs

@@ -9,7 +9,7 @@ use crate::wire::*;
 ///
 /// # References
 ///
-/// - [RFC 1035 §3.3.s](https://tools.ietf.org/html/rfc1035) — Domain Names, Implementation and Specification (November 1987)
+/// - [RFC 1035 §3.3.9](https://tools.ietf.org/html/rfc1035) — Domain Names, Implementation and Specification (November 1987)
 #[derive(PartialEq, Debug)]
 pub struct MX {
 

+ 0 - 1
dns/src/record/others.rs

@@ -51,7 +51,6 @@ static TYPES: &[(&str, u16)] = &[
     ("DNAME",      39),
     ("DNSKEEYE",   48),
     ("DS",         43),
-    ("HINFO",      13),
     ("HIP",        55),
     ("IPSECKEY",   45),
     ("IXFR",      251),

+ 2 - 0
dns/src/wire.rs

@@ -175,6 +175,7 @@ impl Record {
         try_record!(AAAA);
         try_record!(CAA);
         try_record!(CNAME);
+        try_record!(HINFO);
         try_record!(MX);
         try_record!(NS);
         // OPT is handled separately
@@ -233,6 +234,7 @@ pub fn find_qtype_number(record_type: &str) -> Option<TypeInt> {
     try_record!(AAAA);
     try_record!(CAA);
     try_record!(CNAME);
+    try_record!(HINFO);
     try_record!(MX);
     try_record!(NS);
     // OPT is elsewhere

+ 2 - 0
src/colours.rs

@@ -17,6 +17,7 @@ pub struct Colours {
     pub aaaa: Style,
     pub caa: Style,
     pub cname: Style,
+    pub hinfo: Style,
     pub mx: Style,
     pub ns: Style,
     pub opt: Style,
@@ -43,6 +44,7 @@ impl Colours {
             aaaa: Green.bold(),
             caa: Red.normal(),
             cname: Yellow.normal(),
+            hinfo: Yellow.normal(),
             mx: Cyan.normal(),
             ns: Red.normal(),
             opt: Purple.normal(),

+ 10 - 0
src/output.rs

@@ -194,6 +194,9 @@ impl TextFormat {
             Record::CNAME(ref cname) => {
                 format!("{:?}", cname.domain.to_string())
             }
+            Record::HINFO(ref hinfo) => {
+                format!("{:?} {:?}", hinfo.cpu, hinfo.os)
+            }
             Record::MX(ref mx) => {
                 format!("{} {:?}", mx.preference, mx.exchange.to_string())
             }
@@ -345,6 +348,13 @@ fn json_record(record: &Record) -> JsonValue {
                 "domain": rec.domain.to_string(),
             })
         }
+        Record::HINFO(rec) => {
+            json!({
+                "type": "HINFO",
+                "cpu": rec.cpu,
+                "os": rec.os,
+            })
+        }
         Record::MX(rec) => {
             json!({
                 "type": "MX",

+ 1 - 0
src/table.rs

@@ -118,6 +118,7 @@ impl Table {
             Record::AAAA(_)   => self.colours.aaaa.paint("AAAA"),
             Record::CAA(_)    => self.colours.caa.paint("CAA"),
             Record::CNAME(_)  => self.colours.cname.paint("CNAME"),
+            Record::HINFO(_)  => self.colours.hinfo.paint("HINFO"),
             Record::MX(_)     => self.colours.mx.paint("MX"),
             Record::NS(_)     => self.colours.ns.paint("NS"),
             Record::PTR(_)    => self.colours.ptr.paint("PTR"),

+ 19 - 0
xtests/https.toml

@@ -74,6 +74,25 @@ status = 2
 tags = [ "live", "cloudflare" ]
 
 
+# HINFO records
+
+[[cmd]]
+name = "Look up an existing HINFO record using HTTPS"
+shell = "dog HINFO hinfo-example.lookup.dog @https://cloudflare-dns.com/dns-query --short --https"
+stdout = { string = '"some-kinda-os"' }
+stderr = { empty = true }
+status = 0
+tags = [ "live", "cloudflare" ]
+
+[[cmd]]
+name = "Look up a missing HINFO record using HTTPS"
+shell = "dog HINFO non.existent @https://cloudflare-dns.com/dns-query --short --https"
+stdout = { empty = true }
+stderr = { string = "No results" }
+status = 2
+tags = [ "live", "cloudflare" ]
+
+
 # MX records
 
 [[cmd]]

+ 19 - 0
xtests/json.toml

@@ -74,6 +74,25 @@ status = 0
 tags = [ "live", "cloudflare" ]
 
 
+# HINFO records
+
+[[cmd]]
+name = "Look up an existing HINFO record formatted as JSON"
+shell = "dog HINFO hinfo-example.lookup.dog @1.1.1.1 --json"
+stdout = { string = '"some-kinda-os"' }
+stderr = { empty = true }
+status = 0
+tags = [ "live", "cloudflare" ]
+
+[[cmd]]
+name = "Look up a missing HINFO record formatted as JSON"
+shell = "dog HINFO non.existent @1.1.1.1 --json"
+stdout = { string = '[]' }
+stderr = { empty = true }
+status = 0
+tags = [ "live", "cloudflare" ]
+
+
 # MX records
 
 [[cmd]]

+ 19 - 0
xtests/tcp.toml

@@ -74,6 +74,25 @@ status = 2
 tags = [ "live", "cloudflare" ]
 
 
+# HINFO records
+
+[[cmd]]
+name = "Look up an existing HINFO record using TCP"
+shell = "dog HINFO hinfo-example.lookup.dog @1.1.1.1 --short --tcp"
+stdout = { string = '"some-kinda-cpu"' }
+stderr = { empty = true }
+status = 0
+tags = [ "live", "cloudflare" ]
+
+[[cmd]]
+name = "Look up a missing HINFO record using TCP"
+shell = "dog HINFO non.existent @1.1.1.1 --short --tcp"
+stdout = { empty = true }
+stderr = { string = "No results" }
+status = 2
+tags = [ "live", "cloudflare" ]
+
+
 # MX records
 
 [[cmd]]

+ 19 - 0
xtests/tls.toml

@@ -74,6 +74,25 @@ status = 2
 tags = [ "live", "cloudflare" ]
 
 
+# CNAME records
+
+[[cmd]]
+name = "Look up an existing HINFO record using TLS"
+shell = "dog HINFO hinfo-example.lookup.dog @1.1.1.1 --short --tls"
+stdout = { string = '"some-kinda-os"' }
+stderr = { empty = true }
+status = 0
+tags = [ "live", "cloudflare" ]
+
+[[cmd]]
+name = "Look up a missing HINFO record using TLS"
+shell = "dog HINFO non.existent @1.1.1.1 --short --tls"
+stdout = { empty = true }
+stderr = { string = "No results" }
+status = 2
+tags = [ "live", "cloudflare" ]
+
+
 # MX records
 
 [[cmd]]

+ 19 - 0
xtests/udp.toml

@@ -74,6 +74,25 @@ status = 2
 tags = [ "live", "cloudflare" ]
 
 
+# CNAME records
+
+[[cmd]]
+name = "Look up an existing HINFO record using UDP"
+shell = "dog HINFO hinfo-example.lookup.dog @1.1.1.1 --short"
+stdout = { string = '"some-kinda-cpu"' }
+stderr = { empty = true }
+status = 0
+tags = [ "live", "cloudflare" ]
+
+[[cmd]]
+name = "Look up a missing HINFO record using UDP"
+shell = "dog HINFO non.existent @1.1.1.1 --short"
+stdout = { empty = true }
+stderr = { string = "No results" }
+status = 2
+tags = [ "live", "cloudflare" ]
+
+
 # MX records
 
 [[cmd]]