浏览代码

Merge branch 'down-with-integer-conversions'

Benjamin Sago 4 年之前
父节点
当前提交
ad1c88a456

+ 3 - 3
dns-transport/src/auto.rs

@@ -12,10 +12,10 @@ use super::{Transport, Error, UdpTransport, TcpTransport};
 ///
 /// ```no_run
 /// use dns_transport::{Transport, AutoTransport};
-/// use dns::{Request, Flags, Query, QClass, qtype, record::NS};
+/// use dns::{Request, Flags, Query, Labels, QClass, qtype, record::NS};
 ///
 /// let query = Query {
-///     qname: String::from("dns.lookup.dog"),
+///     qname: Labels::encode("dns.lookup.dog").unwrap(),
 ///     qclass: QClass::IN,
 ///     qtype: qtype!(NS),
 /// };
@@ -23,7 +23,7 @@ use super::{Transport, Error, UdpTransport, TcpTransport};
 /// let request = Request {
 ///     transaction_id: 0xABCD,
 ///     flags: Flags::query(),
-///     queries: vec![ query ],
+///     query: query,
 ///     additional: None,
 /// };
 ///

+ 3 - 3
dns-transport/src/https.rs

@@ -15,10 +15,10 @@ use super::{Transport, Error};
 ///
 /// ```no_run
 /// use dns_transport::{Transport, HttpsTransport};
-/// use dns::{Request, Flags, Query, QClass, qtype, record::A};
+/// use dns::{Request, Flags, Query, Labels, QClass, qtype, record::A};
 ///
 /// let query = Query {
-///     qname: String::from("dns.lookup.dog"),
+///     qname: Labels::encode("dns.lookup.dog").unwrap(),
 ///     qclass: QClass::IN,
 ///     qtype: qtype!(A),
 /// };
@@ -26,7 +26,7 @@ use super::{Transport, Error};
 /// let request = Request {
 ///     transaction_id: 0xABCD,
 ///     flags: Flags::query(),
-///     queries: vec![ query ],
+///     query: query,
 ///     additional: None,
 /// };
 ///

+ 4 - 0
dns-transport/src/lib.rs

@@ -16,6 +16,10 @@
 #![allow(clippy::pub_enum_variant_names)]
 #![allow(clippy::wildcard_imports)]
 
+#![deny(clippy::cast_possible_truncation)]
+#![deny(clippy::cast_lossless)]
+#![deny(clippy::cast_possible_wrap)]
+#![deny(clippy::cast_sign_loss)]
 #![deny(unsafe_code)]
 
 use async_trait::async_trait;

+ 6 - 4
dns-transport/src/tcp.rs

@@ -1,3 +1,5 @@
+use std::convert::TryFrom;
+
 use async_trait::async_trait;
 use log::*;
 use tokio::net::TcpStream;
@@ -13,10 +15,10 @@ use super::{Transport, Error};
 ///
 /// ```no_run
 /// use dns_transport::{Transport, TcpTransport};
-/// use dns::{Request, Flags, Query, QClass, qtype, record::MX};
+/// use dns::{Request, Flags, Query, Labels, QClass, qtype, record::MX};
 ///
 /// let query = Query {
-///     qname: String::from("dns.lookup.dog"),
+///     qname: Labels::encode("dns.lookup.dog").unwrap(),
 ///     qclass: QClass::IN,
 ///     qtype: qtype!(MX),
 /// };
@@ -24,7 +26,7 @@ use super::{Transport, Error};
 /// let request = Request {
 ///     transaction_id: 0xABCD,
 ///     flags: Flags::query(),
-///     queries: vec![ query ],
+///     query: query,
 ///     additional: None,
 /// };
 ///
@@ -65,7 +67,7 @@ impl Transport for TcpTransport {
         // The message is prepended with the length when sent over TCP,
         // so the server knows how long it is (RFC 1035 §4.2.2)
         let mut bytes = request.to_bytes().expect("failed to serialise request");
-        let len_bytes = (bytes.len() as u16).to_be_bytes();
+        let len_bytes = u16::try_from(bytes.len()).expect("request too long").to_be_bytes();
         bytes.insert(0, len_bytes[0]);
         bytes.insert(1, len_bytes[1]);
 

+ 6 - 4
dns-transport/src/tls.rs

@@ -1,3 +1,5 @@
+use std::convert::TryFrom;
+
 use async_trait::async_trait;
 use log::*;
 use native_tls::TlsConnector;
@@ -14,10 +16,10 @@ use super::{Transport, Error};
 ///
 /// ```no_run
 /// use dns_transport::{Transport, TlsTransport};
-/// use dns::{Request, Flags, Query, QClass, qtype, record::SRV};
+/// use dns::{Request, Flags, Query, Labels, QClass, qtype, record::SRV};
 ///
 /// let query = Query {
-///     qname: String::from("dns.lookup.dog"),
+///     qname: Labels::encode("dns.lookup.dog").unwrap(),
 ///     qclass: QClass::IN,
 ///     qtype: qtype!(SRV),
 /// };
@@ -25,7 +27,7 @@ use super::{Transport, Error};
 /// let request = Request {
 ///     transaction_id: 0xABCD,
 ///     flags: Flags::query(),
-///     queries: vec![ query ],
+///     query: query,
 ///     additional: None,
 /// };
 ///
@@ -66,7 +68,7 @@ impl Transport for TlsTransport {
 
         // As with TCP, we need to prepend the message with its length.
         let mut bytes = request.to_bytes().expect("failed to serialise request");
-        let len_bytes = (bytes.len() as u16).to_be_bytes();
+        let len_bytes = u16::try_from(bytes.len()).expect("request too long").to_be_bytes();
         bytes.insert(0, len_bytes[0]);
         bytes.insert(1, len_bytes[1]);
 

+ 3 - 3
dns-transport/src/udp.rs

@@ -14,10 +14,10 @@ use super::{Transport, Error};
 ///
 /// ```no_run
 /// use dns_transport::{Transport, UdpTransport};
-/// use dns::{Request, Flags, Query, QClass, qtype, record::NS};
+/// use dns::{Request, Flags, Query, Labels, QClass, qtype, record::NS};
 ///
 /// let query = Query {
-///     qname: String::from("dns.lookup.dog"),
+///     qname: Labels::encode("dns.lookup.dog").unwrap(),
 ///     qclass: QClass::IN,
 ///     qtype: qtype!(NS),
 /// };
@@ -25,7 +25,7 @@ use super::{Transport, Error};
 /// let request = Request {
 ///     transaction_id: 0xABCD,
 ///     flags: Flags::query(),
-///     queries: vec![ query ],
+///     query: query,
 ///     additional: None,
 /// };
 ///

+ 5 - 0
dns/src/lib.rs

@@ -16,6 +16,10 @@
 #![allow(clippy::struct_excessive_bools)]
 #![allow(clippy::wildcard_imports)]
 
+#![deny(clippy::cast_possible_truncation)]
+#![deny(clippy::cast_lossless)]
+#![deny(clippy::cast_possible_wrap)]
+#![deny(clippy::cast_sign_loss)]
 #![deny(unsafe_code)]
 
 
@@ -27,6 +31,7 @@ mod types;
 pub use self::types::*;
 
 mod strings;
+pub use self::strings::Labels;
 
 mod wire;
 pub use self::wire::{Wire, WireError, find_qtype_number};

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

@@ -35,7 +35,7 @@ impl Wire for A {
         }
         else {
             warn!("Length is incorrect (record length {:?}, but should be four)", len);
-            Err(WireError::WrongRecordLength { expected: 4, got: buf.len() as u16 })
+            Err(WireError::WrongRecordLength { expected: 4, got: len })
         }
     }
 }

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

@@ -37,7 +37,7 @@ impl Wire for AAAA {
         }
         else {
             warn!("Length is incorrect (record length {:?}, but should be sixteen)", len);
-            Err(WireError::WrongRecordLength { expected: 16, got: buf.len() as u16 })
+            Err(WireError::WrongRecordLength { expected: 16, got: len })
         }
     }
 }

+ 3 - 3
dns/src/record/cname.rs

@@ -1,6 +1,6 @@
 use log::*;
 
-use crate::strings::ReadLabels;
+use crate::strings::{Labels, ReadLabels};
 use crate::wire::*;
 
 
@@ -13,7 +13,7 @@ use crate::wire::*;
 pub struct CNAME {
 
     /// The domain name that this CNAME record is responding with.
-    pub domain: String,
+    pub domain: Labels,
 }
 
 impl Wire for CNAME {
@@ -50,7 +50,7 @@ mod test {
 
         assert_eq!(CNAME::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    CNAME {
-                       domain: String::from("bsago.me."),
+                       domain: Labels::encode("bsago.me").unwrap(),
                    });
     }
 

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

@@ -1,6 +1,6 @@
 use log::*;
 
-use crate::strings::ReadLabels;
+use crate::strings::{Labels, ReadLabels};
 use crate::wire::*;
 
 
@@ -18,7 +18,7 @@ pub struct MX {
     pub preference: u16,
 
     /// The domain name of the mail exchange server.
-    pub exchange: String,
+    pub exchange: Labels,
 }
 
 impl Wire for MX {
@@ -61,7 +61,7 @@ mod test {
         assert_eq!(MX::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    MX {
                        preference: 10,
-                       exchange: String::from("bsago.me."),
+                       exchange: Labels::encode("bsago.me").unwrap(),
                    });
     }
 

+ 5 - 5
dns/src/record/ns.rs

@@ -1,8 +1,8 @@
-use crate::strings::ReadLabels;
-use crate::wire::*;
-
 use log::*;
 
+use crate::strings::{Labels, ReadLabels};
+use crate::wire::*;
+
 
 /// A **NS** _(name server)_ record, which is used to point domains to name
 /// servers.
@@ -14,7 +14,7 @@ use log::*;
 pub struct NS {
 
     /// The address of a nameserver that provides this DNS response.
-    pub nameserver: String,
+    pub nameserver: Labels,
 }
 
 impl Wire for NS {
@@ -52,7 +52,7 @@ mod test {
 
         assert_eq!(NS::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    NS {
-                       nameserver: String::from("a.gtld-servers.net."),
+                       nameserver: Labels::encode("a.gtld-servers.net").unwrap(),
                    });
     }
 

+ 7 - 1
dns/src/record/opt.rs

@@ -1,3 +1,4 @@
+use std::convert::TryFrom;
 use std::io;
 
 use log::*;
@@ -97,7 +98,12 @@ impl OPT {
         bytes.write_u8(self.higher_bits)?;
         bytes.write_u8(self.edns0_version)?;
         bytes.write_u16::<BigEndian>(self.flags)?;
-        bytes.write_u16::<BigEndian>(self.data.len() as u16)?;
+
+        // We should not be sending any data at all in the request, really,
+        // so sending too much data is downright nonsensical
+        let data_len = u16::try_from(self.data.len()).expect("Sending too much data");
+        bytes.write_u16::<BigEndian>(data_len)?;
+
         for b in &self.data {
             bytes.write_u8(*b)?;
         }

+ 3 - 3
dns/src/record/ptr.rs

@@ -1,6 +1,6 @@
 use log::*;
 
-use crate::strings::ReadLabels;
+use crate::strings::{Labels, ReadLabels};
 use crate::wire::*;
 
 
@@ -19,7 +19,7 @@ use crate::wire::*;
 pub struct PTR {
 
     /// The CNAME contained in the record.
-    pub cname: String,
+    pub cname: Labels,
 }
 
 impl Wire for PTR {
@@ -56,7 +56,7 @@ mod test {
 
         assert_eq!(PTR::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    PTR {
-                       cname: String::from("dns.google."),
+                       cname: Labels::encode("dns.google").unwrap(),
                    });
     }
 

+ 5 - 5
dns/src/record/soa.rs

@@ -1,6 +1,6 @@
 use log::*;
 
-use crate::strings::ReadLabels;
+use crate::strings::{Labels, ReadLabels};
 use crate::wire::*;
 
 
@@ -15,10 +15,10 @@ use crate::wire::*;
 pub struct SOA {
 
     /// The primary master name for this server.
-    pub mname: String,
+    pub mname: Labels,
 
     /// The e-mail address of the administrator responsible for this DNS zone.
-    pub rname: String,
+    pub rname: Labels,
 
     /// A serial number for this DNS zone.
     pub serial: u32,
@@ -104,8 +104,8 @@ mod test {
 
         assert_eq!(SOA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    SOA {
-                       mname: String::from("bsago.me."),
-                       rname: String::from("bsago.me."),
+                       mname: Labels::encode("bsago.me").unwrap(),
+                       rname: Labels::encode("bsago.me").unwrap(),
                        serial: 1564274434,
                        refresh_interval: 86400,
                        retry_interval: 7200,

+ 3 - 3
dns/src/record/srv.rs

@@ -1,6 +1,6 @@
 use log::*;
 
-use crate::strings::ReadLabels;
+use crate::strings::{Labels, ReadLabels};
 use crate::wire::*;
 
 
@@ -25,7 +25,7 @@ pub struct SRV {
     pub port: u16,
 
     /// The hostname of the machine the service is running on.
-    pub target: String,
+    pub target: Labels,
 }
 
 impl Wire for SRV {
@@ -80,7 +80,7 @@ mod test {
                        priority: 1,
                        weight: 1,
                        port: 37500,
-                       target: String::from("ata.local.node.dc1.consul."),
+                       target: Labels::encode("ata.local.node.dc1.consul").unwrap(),
                    });
     }
 

+ 74 - 17
dns/src/strings.rs

@@ -1,5 +1,7 @@
 //! Reading strings from the DNS wire protocol.
 
+use std::convert::TryFrom;
+use std::fmt;
 use std::io::{self, Write};
 
 use byteorder::{ReadBytesExt, WriteBytesExt};
@@ -8,20 +10,72 @@ use log::*;
 use crate::wire::*;
 
 
+/// Domain names in the DNS protocol are encoded as **Labels**, which are
+/// segments of ASCII characters prefixed by their length. When written out,
+/// each segment is followed by a dot.
+///
+/// The maximum length of a segment is 255 characters.
+#[derive(PartialEq, Debug, Clone)]
+pub struct Labels {
+    segments: Vec<(u8, String)>,
+}
+
+impl Labels {
+
+    /// Creates a new empty set of labels, which represent the root of the DNS
+    /// as a domain with no name.
+    pub fn root() -> Self {
+        Self { segments: Vec::new() }
+    }
+
+    /// Encodes the given input string as labels. If any segment is too long,
+    /// returns that segment as an error.
+    pub fn encode<'a>(input: &'a str) -> Result<Self, &'a str> {
+        let mut segments = Vec::new();
+
+        for label in input.split('.') {
+            if label.is_empty() {
+                continue;
+            }
+
+            match u8::try_from(label.len()) {
+                Ok(length) => {
+                    segments.push((length, label.to_owned()));
+                }
+                Err(e) => {
+                    warn!("Could not encode label {:?}: {}", label, e);
+                    return Err(label);
+                }
+            }
+        }
+
+        Ok(Self { segments })
+    }
+}
+
+impl fmt::Display for Labels {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        for (_, segment) in &self.segments {
+            write!(f, "{}.", segment)?;
+        }
+
+        Ok(())
+    }
+}
+
 /// An extension for `Cursor` that enables reading compressed domain names
 /// from DNS packets.
 pub(crate) trait ReadLabels {
 
     /// Read and expand a compressed domain name.
-    fn read_labels(&mut self) -> Result<(String, u16), WireError>;
+    fn read_labels(&mut self) -> Result<(Labels, u16), WireError>;
 }
 
 impl ReadLabels for Cursor<&[u8]> {
-    fn read_labels(&mut self) -> Result<(String, u16), WireError> {
-        let mut name_buf = Vec::new();
-        let bytes_read = read_string_recursive(&mut name_buf, self, &mut Vec::new())?;
-        let string = String::from_utf8_lossy(&*name_buf).to_string();
-        Ok((string, bytes_read))
+    fn read_labels(&mut self) -> Result<(Labels, u16), WireError> {
+        let mut labels = Labels { segments: Vec::new() };
+        let bytes_read = read_string_recursive(&mut labels, self, &mut Vec::new())?;
+        Ok((labels, bytes_read))
     }
 }
 
@@ -37,13 +91,13 @@ pub(crate) trait WriteLabels {
     ///
     /// So “dns.lookup.dog” would be encoded as:
     /// “3, dns, 6, lookup, 3, dog, 0”.
-    fn write_labels(&mut self, input: &str) -> io::Result<()>;
+    fn write_labels(&mut self, input: &Labels) -> io::Result<()>;
 }
 
 impl<W: Write> WriteLabels for W {
-    fn write_labels(&mut self, input: &str) -> io::Result<()> {
-        for label in input.split('.') {
-            self.write_u8(label.len() as u8)?;
+    fn write_labels(&mut self, input: &Labels) -> io::Result<()> {
+        for (length, label) in &input.segments {
+            self.write_u8(*length)?;
 
             for b in label.as_bytes() {
                 self.write_u8(*b)?;
@@ -63,7 +117,7 @@ const RECURSION_LIMIT: usize = 8;
 /// that had to be read to produce the string, including the bytes to signify
 /// backtracking, but not including the bytes read _during_ backtracking.
 #[cfg_attr(all(test, feature = "with_mutagen"), ::mutagen::mutate)]
-fn read_string_recursive(name_buf: &mut Vec<u8>, c: &mut Cursor<&[u8]>, recursions: &mut Vec<u16>) -> Result<u16, WireError> {
+fn read_string_recursive(labels: &mut Labels, c: &mut Cursor<&[u8]>, recursions: &mut Vec<u16>) -> Result<u16, WireError> {
     let mut bytes_read = 0;
 
     loop {
@@ -96,7 +150,7 @@ fn read_string_recursive(name_buf: &mut Vec<u8>, c: &mut Cursor<&[u8]>, recursio
             let new_pos = c.position();
             c.set_position(u64::from(offset));
 
-            read_string_recursive(name_buf, c, recursions)?;
+            read_string_recursive(labels, c, recursions)?;
 
             trace!("Coming back to {}", new_pos);
             c.set_position(new_pos);
@@ -106,13 +160,16 @@ fn read_string_recursive(name_buf: &mut Vec<u8>, c: &mut Cursor<&[u8]>, recursio
         // Otherwise, treat the byte as the length of a label, and read that
         // many characters.
         else {
+            let mut name_buf = Vec::new();
+
             for _ in 0 .. byte {
                 let c = c.read_u8()?;
                 bytes_read += 1;
                 name_buf.push(c);
             }
 
-            name_buf.push(b'.');
+            let string = String::from_utf8_lossy(&*name_buf).to_string();
+            labels.segments.push((byte, string));
         }
     }
 
@@ -136,7 +193,7 @@ mod test {
         ];
 
         assert_eq!(Cursor::new(buf).read_labels(),
-                   Ok(("".into(), 1)));
+                   Ok((Labels::root(), 1)));
     }
 
     #[test]
@@ -148,7 +205,7 @@ mod test {
         ];
 
         assert_eq!(Cursor::new(buf).read_labels(),
-                   Ok(("one.".into(), 5)));
+                   Ok((Labels::encode("one.").unwrap(), 5)));
     }
 
     #[test]
@@ -162,7 +219,7 @@ mod test {
         ];
 
         assert_eq!(Cursor::new(buf).read_labels(),
-                   Ok(("one.two.".into(), 9)));
+                   Ok((Labels::encode("one.two.").unwrap(), 9)));
     }
 
     #[test]
@@ -178,7 +235,7 @@ mod test {
         ];
 
         assert_eq!(Cursor::new(buf).read_labels(),
-                   Ok(("one.two.".into(), 6)));
+                   Ok((Labels::encode("one.two.").unwrap(), 6)));
     }
 
     #[test]

+ 8 - 5
dns/src/types.rs

@@ -4,6 +4,7 @@
 //! having at least one record in its answer fields.
 
 use crate::record::{Record, OPT};
+use crate::strings::Labels;
 
 
 /// A request that gets sent out over a transport.
@@ -17,8 +18,10 @@ pub struct Request {
     /// The flags that accompany every DNS packet.
     pub flags: Flags,
 
-    /// The queries that this request is making.
-    pub queries: Vec<Query>,
+    /// The query that this request is making. Only one query is allowed per
+    /// request, as traditionally, DNS servers only respond to the first query
+    /// in a packet.
+    pub query: Query,
 
     /// An additional record that may be sent as part of the query.
     pub additional: Option<OPT>,
@@ -54,7 +57,7 @@ pub struct Response {
 pub struct Query {
 
     /// The domain name being queried, in human-readable dotted notation.
-    pub qname: String,
+    pub qname: Labels,
 
     /// The class number.
     pub qclass: QClass,
@@ -72,7 +75,7 @@ pub enum Answer {
     Standard {
 
         /// The domain name being answered for.
-        qname: String,
+        qname: Labels,
 
         /// This answer’s class.
         qclass: QClass,
@@ -89,7 +92,7 @@ pub enum Answer {
     Pseudo {
 
         /// The domain name being answered for.
-        qname: String,
+        qname: Labels,
 
         /// The OPT record contained in this answer.
         opt: OPT,

+ 17 - 19
dns/src/wire.rs

@@ -7,7 +7,7 @@ use std::io;
 use log::*;
 
 use crate::record::{Record, OPT};
-use crate::strings::{ReadLabels, WriteLabels};
+use crate::strings::{Labels, ReadLabels, WriteLabels};
 use crate::types::*;
 
 
@@ -20,16 +20,14 @@ impl Request {
         bytes.write_u16::<BigEndian>(self.transaction_id)?;
         bytes.write_u16::<BigEndian>(self.flags.to_u16())?;
 
-        bytes.write_u16::<BigEndian>(self.queries.len() as u16)?;
-        bytes.write_u16::<BigEndian>(0)?;  // usually answers
-        bytes.write_u16::<BigEndian>(0)?;  // usually authority RRs
-        bytes.write_u16::<BigEndian>(if self.additional.is_some() { 1 } else { 0 })?;  // additional RRs
+        bytes.write_u16::<BigEndian>(1)?;  // query count
+        bytes.write_u16::<BigEndian>(0)?;  // answer count
+        bytes.write_u16::<BigEndian>(0)?;  // authority RR count
+        bytes.write_u16::<BigEndian>(if self.additional.is_some() { 1 } else { 0 })?;  // additional RR count
 
-        for query in &self.queries {
-            bytes.write_labels(&query.qname)?;
-            bytes.write_u16::<BigEndian>(query.qtype)?;
-            bytes.write_u16::<BigEndian>(query.qclass.to_u16())?;
-        }
+        bytes.write_labels(&self.query.qname)?;
+        bytes.write_u16::<BigEndian>(self.query.qtype)?;
+        bytes.write_u16::<BigEndian>(self.query.qclass.to_u16())?;
 
         if let Some(opt) = &self.additional {
             bytes.write_u8(0)?;  // usually a name
@@ -111,7 +109,7 @@ impl Query {
     /// Reads bytes from the given cursor, and parses them into a query with
     /// the given domain name.
     #[cfg_attr(all(test, feature = "with_mutagen"), ::mutagen::mutate)]
-    fn from_bytes(qname: String, c: &mut Cursor<&[u8]>) -> Result<Self, WireError> {
+    fn from_bytes(qname: Labels, c: &mut Cursor<&[u8]>) -> Result<Self, WireError> {
         let qtype = c.read_u16::<BigEndian>()?;
         trace!("Read qtype -> {:?}", qtype);
 
@@ -128,7 +126,7 @@ impl Answer {
     /// Reads bytes from the given cursor, and parses them into an answer with
     /// the given domain name.
     #[cfg_attr(all(test, feature = "with_mutagen"), ::mutagen::mutate)]
-    fn from_bytes(qname: String, c: &mut Cursor<&[u8]>) -> Result<Self, WireError> {
+    fn from_bytes(qname: Labels, c: &mut Cursor<&[u8]>) -> Result<Self, WireError> {
         let qtype = c.read_u16::<BigEndian>()?;
         trace!("Read qtype -> {:?}", qtype);
 
@@ -494,14 +492,14 @@ mod test {
             flags: Flags::standard_response(),
             queries: vec![
                 Query {
-                    qname: "bsago.me.".into(),
+                    qname: Labels::encode("bsago.me").unwrap(),
                     qclass: QClass::IN,
                     qtype: qtype!(A),
                 },
             ],
             answers: vec![
                 Answer::Standard {
-                    qname: "bsago.me.".into(),
+                    qname: Labels::encode("bsago.me").unwrap(),
                     qclass: QClass::IN,
                     ttl: 887,
                     record: Record::A(A {
@@ -511,12 +509,12 @@ mod test {
             ],
             authorities: vec![
                 Answer::Standard {
-                    qname: "".into(),
+                    qname: Labels::root(),
                     qclass: QClass::IN,
                     ttl: 4294967295,
                     record: Record::SOA(SOA {
-                        mname: "a.".into(),
-                        rname: "mx.".into(),
+                        mname: Labels::encode("a").unwrap(),
+                        rname: Labels::encode("mx").unwrap(),
                         serial: 2020102700,
                         refresh_interval: 1800,
                         retry_interval: 900,
@@ -527,7 +525,7 @@ mod test {
             ],
             additionals: vec![
                 Answer::Standard {
-                    qname: "".into(),
+                    qname: Labels::root(),
                     qclass: QClass::Other(153),
                     ttl: 305419896,
                     record: Record::Other {
@@ -536,7 +534,7 @@ mod test {
                     },
                 },
                 Answer::Pseudo {
-                    qname: "".into(),
+                    qname: Labels::root(),
                     opt: OPT {
                         udp_payload_size: 512,
                         higher_bits: 0,

+ 7 - 7
dns/tests/wire_parsing_tests.rs

@@ -1,6 +1,6 @@
 use std::net::Ipv4Addr;
 
-use dns::{Response, Query, Answer, Flags, QClass, qtype};
+use dns::{Response, Query, Answer, Labels, Flags, QClass, qtype};
 use dns::record::{Record, A, CNAME, OPT};
 
 
@@ -56,14 +56,14 @@ fn parse_response_standard() {
         },
         queries: vec![
             Query {
-                qname: "dns.lookup.dog.".into(),
+                qname: Labels::encode("dns.lookup.dog").unwrap(),
                 qclass: QClass::IN,
                 qtype: qtype!(A),
             },
         ],
         answers: vec![
             Answer::Standard {
-                qname: "dns.lookup.dog.".into(),
+                qname: Labels::encode("dns.lookup.dog").unwrap(),
                 qclass: QClass::IN,
                 ttl: 933,
                 record: Record::A(A {
@@ -74,7 +74,7 @@ fn parse_response_standard() {
         authorities: vec![],
         additionals: vec![
             Answer::Pseudo {
-                qname: "".into(),
+                qname: Labels::root(),
                 opt: OPT {
                     udp_payload_size: 512,
                     higher_bits: 0,
@@ -129,18 +129,18 @@ fn parse_response_with_mixed_string() {
         },
         queries: vec![
             Query {
-                qname: "cname-example.lookup.dog.".into(),
+                qname: Labels::encode("cname-example.lookup.dog").unwrap(),
                 qclass: QClass::IN,
                 qtype: qtype!(CNAME),
             },
         ],
         answers: vec![
             Answer::Standard {
-                qname: "cname-example.lookup.dog.".into(),
+                qname: Labels::encode("cname-example.lookup.dog").unwrap(),
                 qclass: QClass::IN,
                 ttl: 873,
                 record: Record::CNAME(CNAME {
-                    domain: "dns.lookup.dog.".into(),
+                    domain: Labels::encode("dns.lookup.dog").unwrap(),
                 }),
             }
         ],

+ 24 - 16
src/options.rs

@@ -3,7 +3,7 @@ use std::fmt;
 
 use log::*;
 
-use dns::{QClass, find_qtype_number, qtype};
+use dns::{QClass, Labels, find_qtype_number, qtype};
 use dns::record::{A, find_other_qtype_number};
 
 use crate::connect::TransportType;
@@ -154,7 +154,10 @@ impl Inputs {
 
     fn load_named_args(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> {
         for domain in matches.opt_strs("query") {
-            self.domains.push(domain);
+            match Labels::encode(&domain) {
+                Ok(d)   => self.domains.push(d),
+                Err(e)  => return Err(OptionsError::InvalidDomain(e.into())),
+            }
         }
 
         for qtype in matches.opt_strs("type") {
@@ -229,7 +232,10 @@ impl Inputs {
             }
             else {
                 trace!("Got domain -> {:?}", &a);
-                self.domains.push(a);
+                match Labels::encode(&a) {
+                    Ok(d)   => self.domains.push(d),
+                    Err(e)  => return Err(OptionsError::InvalidDomain(e.into())),
+                }
             }
         }
 
@@ -411,6 +417,7 @@ pub enum HelpReason {
 #[derive(PartialEq, Debug)]
 pub enum OptionsError {
     TooManyProtocols,
+    InvalidDomain(String),
     InvalidEDNS(String),
     InvalidQueryType(String),
     InvalidQueryClass(String),
@@ -423,6 +430,7 @@ impl fmt::Display for OptionsError {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
             Self::TooManyProtocols       => write!(f, "Too many protocols"),
+            Self::InvalidDomain(domain)  => write!(f, "Invalid domain {:?}", domain),
             Self::InvalidEDNS(edns)      => write!(f, "Invalid EDNS setting {:?}", edns),
             Self::InvalidQueryType(qt)   => write!(f, "Invalid query type {:?}", qt),
             Self::InvalidQueryClass(qc)  => write!(f, "Invalid query class {:?}", qc),
@@ -512,7 +520,7 @@ mod test {
     fn just_domain() {
         let options = Options::getopts(&[ "lookup.dog" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             .. Inputs::fallbacks()
         });
     }
@@ -521,7 +529,7 @@ mod test {
     fn just_named_domain() {
         let options = Options::getopts(&[ "-q", "lookup.dog" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             .. Inputs::fallbacks()
         });
     }
@@ -530,7 +538,7 @@ mod test {
     fn domain_and_type() {
         let options = Options::getopts(&[ "lookup.dog", "SOA" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             types:      vec![ qtype!(SOA) ],
             .. Inputs::fallbacks()
         });
@@ -540,7 +548,7 @@ mod test {
     fn domain_and_nameserver() {
         let options = Options::getopts(&[ "lookup.dog", "@1.1.1.1" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
             .. Inputs::fallbacks()
         });
@@ -550,7 +558,7 @@ mod test {
     fn domain_and_class() {
         let options = Options::getopts(&[ "lookup.dog", "CH" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             classes:    vec![ QClass::CH ],
             .. Inputs::fallbacks()
         });
@@ -560,7 +568,7 @@ mod test {
     fn all_free() {
         let options = Options::getopts(&[ "lookup.dog", "CH", "NS", "@1.1.1.1" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             classes:    vec![ QClass::CH ],
             types:      vec![ qtype!(NS) ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
@@ -572,7 +580,7 @@ mod test {
     fn all_parameters() {
         let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "CH", "--type", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             classes:    vec![ QClass::CH ],
             types:      vec![ qtype!(SOA) ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
@@ -584,7 +592,7 @@ mod test {
     fn two_types() {
         let options = Options::getopts(&[ "-q", "lookup.dog", "--type", "SRV", "--type", "AAAA" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             types:      vec![ qtype!(SRV), qtype!(AAAA) ],
             .. Inputs::fallbacks()
         });
@@ -594,7 +602,7 @@ mod test {
     fn two_classes() {
         let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "IN", "--class", "CH" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             classes:    vec![ QClass::IN, QClass::CH ],
             .. Inputs::fallbacks()
         });
@@ -604,7 +612,7 @@ mod test {
     fn all_mixed_1() {
         let options = Options::getopts(&[ "lookup.dog", "--class", "CH", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             classes:    vec![ QClass::CH ],
             types:      vec![ qtype!(SOA) ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
@@ -616,7 +624,7 @@ mod test {
     fn all_mixed_2() {
         let options = Options::getopts(&[ "CH", "SOA", "MX", "IN", "-q", "lookup.dog", "--class", "HS" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             classes:    vec![ QClass::HS, QClass::CH, QClass::IN ],
             types:      vec![ qtype!(SOA), qtype!(MX) ],
             .. Inputs::fallbacks()
@@ -627,7 +635,7 @@ mod test {
     fn all_mixed_3() {
         let options = Options::getopts(&[ "lookup.dog", "--nameserver", "1.1.1.1", "--nameserver", "1.0.0.1" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()),
                               Resolver::Specified("1.0.0.1".into()), ],
             .. Inputs::fallbacks()
@@ -638,7 +646,7 @@ mod test {
     fn explicit_numerics() {
         let options = Options::getopts(&[ "11", "--class", "22", "--type", "33" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("11") ],
+            domains:    vec![ Labels::encode("11").unwrap() ],
             classes:    vec![ QClass::Other(22) ],
             types:      vec![ 33 ],
             .. Inputs::fallbacks()

+ 16 - 14
src/output.rs

@@ -212,20 +212,22 @@ impl TextFormat {
                 }
             }
             Record::CNAME(ref cname) => {
-                format!("{:?}", cname.domain)
+                format!("{:?}", cname.domain.to_string())
             }
             Record::MX(ref mx) => {
-                format!("{} {:?}", mx.preference, mx.exchange)
+                format!("{} {:?}", mx.preference, mx.exchange.to_string())
             }
             Record::NS(ref ns) => {
-                format!("{:?}", ns.nameserver)
+                format!("{:?}", ns.nameserver.to_string())
             }
             Record::PTR(ref ptr) => {
-                format!("{:?}", ptr.cname)
+                format!("{:?}", ptr.cname.to_string())
             }
             Record::SOA(ref soa) => {
                 format!("{:?} {:?} {} {} {} {} {}",
-                    soa.mname, soa.rname, soa.serial,
+                    soa.mname.to_string(),
+                    soa.rname.to_string(),
+                    soa.serial,
                     self.format_duration(soa.refresh_interval),
                     self.format_duration(soa.retry_interval),
                     self.format_duration(soa.expire_limit),
@@ -233,7 +235,7 @@ impl TextFormat {
                 )
             }
             Record::SRV(ref srv) => {
-                format!("{} {} {:?}:{}", srv.priority, srv.weight, srv.target, srv.port)
+                format!("{} {} {:?}:{}", srv.priority, srv.weight, srv.target.to_string(), srv.port)
             }
             Record::TXT(ref txt) => {
                 format!("{:?}", txt.message)
@@ -276,7 +278,7 @@ impl OutputFormat {
     fn json_queries(self, queries: &[Query]) -> JsonValue {
         let queries = queries.iter().map(|q| {
             json!({
-                "name": q.qname,
+                "name": q.qname.to_string(),
                 "class": format!("{:?}", q.qclass),
                 "type": q.qtype,
             })
@@ -291,14 +293,14 @@ impl OutputFormat {
                 Answer::Standard { qname, qclass, ttl, record } => {
                     let mut object = self.json_record(record);
                     let omut = object.as_object_mut().unwrap();
-                    omut.insert("name".into(), qname.as_str().into());
+                    omut.insert("name".into(), qname.to_string().into());
                     omut.insert("class".into(), format!("{:?}", qclass).into());
                     omut.insert("ttl".into(), (*ttl).into());
                     json!(object)
                 }
                 Answer::Pseudo { qname, opt } => {
                     let object = json!({
-                        "name": qname,
+                        "name": qname.to_string(),
                         "type": "OPT",
                         "version": opt.edns0_version,
                         "data": opt.data,
@@ -318,11 +320,11 @@ impl OutputFormat {
             Record::AAAA(rec)   => json!({ "type": "AAAA",  "address": rec.address.to_string() }),
             Record::CAA(rec)    => json!({ "type": "CAA",   "critical": rec.critical, "tag": rec.tag, "value": rec.value }),
             Record::CNAME(rec)  => json!({ "type": "CNAME", "domain": rec.domain.to_string() }),
-            Record::MX(rec)     => json!({ "type": "MX",    "preference": rec.preference, "exchange": rec.exchange }),
-            Record::NS(rec)     => json!({ "type": "NS",    "nameserver": rec.nameserver }),
-            Record::PTR(rec)    => json!({ "type": "PTR",   "cname": rec.cname }),
-            Record::SOA(rec)    => json!({ "type": "SOA",   "mname": rec.mname }),
-            Record::SRV(rec)    => json!({ "type": "SRV",   "priority": rec.priority, "weight": rec.weight, "port": rec.port, "target": rec.target, }),
+            Record::MX(rec)     => json!({ "type": "MX",    "preference": rec.preference, "exchange": rec.exchange.to_string() }),
+            Record::NS(rec)     => json!({ "type": "NS",    "nameserver": rec.nameserver.to_string() }),
+            Record::PTR(rec)    => json!({ "type": "PTR",   "cname": rec.cname.to_string() }),
+            Record::SOA(rec)    => json!({ "type": "SOA",   "mname": rec.mname.to_string() }),
+            Record::SRV(rec)    => json!({ "type": "SRV",   "priority": rec.priority, "weight": rec.weight, "port": rec.port, "target": rec.target.to_string() }),
             Record::TXT(rec)    => json!({ "type": "TXT",   "message": rec.message }),
             Record::Other { type_number, bytes } => {
                 let type_name = match type_number {

+ 5 - 6
src/requests.rs

@@ -1,3 +1,5 @@
+use dns::Labels;
+
 use crate::connect::TransportType;
 use crate::resolve::Resolver;
 use crate::txid::TxidGenerator;
@@ -26,7 +28,7 @@ pub struct RequestGenerator {
 pub struct Inputs {
 
     /// The list of domain names to query.
-    pub domains: Vec<String>,
+    pub domains: Vec<Labels>,
 
     /// The list of DNS record types to query for.
     pub types: Vec<u16>,
@@ -93,11 +95,8 @@ impl RequestGenerator {
                                 additional = Some(dns::Request::additional_record());
                             }
 
-                            let queries = vec![
-                                dns::Query { qname: domain.clone(), qtype, qclass },
-                            ];
-
-                            let request = dns::Request { transaction_id, flags, queries, additional };
+                            let query = dns::Query { qname: domain.clone(), qtype, qclass };
+                            let request = dns::Request { transaction_id, flags, query, additional };
 
                             let transport = transport_type.make_transport(nameserver.clone());
                             requests.push((request, transport));

+ 2 - 0
src/table.rs

@@ -58,12 +58,14 @@ impl Table {
         match answer {
             Answer::Standard { record, qname, ttl, .. } => {
                 let qtype = self.coloured_record_type(&record);
+                let qname = qname.to_string();
                 let summary = self.text_format.record_payload_summary(&record);
                 let ttl = Some(self.text_format.format_duration(ttl));
                 self.rows.push(Row { qtype, qname, ttl, summary, section });
             }
             Answer::Pseudo { qname, opt } => {
                 let qtype = self.colours.opt.paint("OPT");
+                let qname = qname.to_string();
                 let summary = self.text_format.pseudo_record_payload_summary(&opt);
                 self.rows.push(Row { qtype, qname, ttl: None, summary, section });
             }

+ 7 - 0
xtests/errors.toml

@@ -15,3 +15,10 @@ shell = "dog OPT dns.google"
 stdout = { empty = true }
 stderr = { string = "OPT request is sent by default" }
 status = 3
+
+[[cmd]]
+name = "A domain label long than 255 bytes gets rejected"
+shell = "dog 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+stdout = { empty = true }
+stderr = { string = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
+status = 3