Browse Source

Introduce Labels type to hold labels

There was a bug in the function to encode labels: the length of a label segment must fit in a u8, so there's a maximum length of 255; however, the domain comes from the user when making requests, and the length was lossily casted to u8 (integer conversion again). This meant that the packet would effectively contain junk data after the domain, which DNS servers did not like. This led to a further crash in the error-handling code when a server sends back an empty response (this is not fixed yet).

The simplest way to deal with this would be to use u8::try_from and unwrap when writing the label to the buffer, but this isn't very user-friendly, as the error should be detected up-front. So the Labels type was introduced, which validates the length of its input's segments. Everything that encodes or decodes a domain name now uses this type.
Benjamin Sago 4 years ago
parent
commit
d5e4c0c54f

+ 2 - 2
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),
 /// };

+ 2 - 2
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),
 /// };

+ 2 - 2
dns-transport/src/tcp.rs

@@ -13,10 +13,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),
 /// };

+ 2 - 2
dns-transport/src/tls.rs

@@ -14,10 +14,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),
 /// };

+ 2 - 2
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),
 /// };

+ 1 - 0
dns/src/lib.rs

@@ -27,6 +27,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};

+ 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(),
                    });
     }
 

+ 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]

+ 4 - 3
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.
@@ -56,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,
@@ -74,7 +75,7 @@ pub enum Answer {
     Standard {
 
         /// The domain name being answered for.
-        qname: String,
+        qname: Labels,
 
         /// This answer’s class.
         qclass: QClass,
@@ -91,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,

+ 10 - 10
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::*;
 
 
@@ -109,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);
 
@@ -126,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);
 
@@ -492,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 {
@@ -509,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,
@@ -525,7 +525,7 @@ mod test {
             ],
             additionals: vec![
                 Answer::Standard {
-                    qname: "".into(),
+                    qname: Labels::root(),
                     qclass: QClass::Other(153),
                     ttl: 305419896,
                     record: Record::Other {
@@ -534,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 {

+ 3 - 1
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>,

+ 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