Parcourir la 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 il y a 4 ans
Parent
commit
d5e4c0c54f

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

@@ -12,10 +12,10 @@ use super::{Transport, Error, UdpTransport, TcpTransport};
 ///
 ///
 /// ```no_run
 /// ```no_run
 /// use dns_transport::{Transport, AutoTransport};
 /// 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 {
 /// let query = Query {
-///     qname: String::from("dns.lookup.dog"),
+///     qname: Labels::encode("dns.lookup.dog").unwrap(),
 ///     qclass: QClass::IN,
 ///     qclass: QClass::IN,
 ///     qtype: qtype!(NS),
 ///     qtype: qtype!(NS),
 /// };
 /// };

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

@@ -15,10 +15,10 @@ use super::{Transport, Error};
 ///
 ///
 /// ```no_run
 /// ```no_run
 /// use dns_transport::{Transport, HttpsTransport};
 /// 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 {
 /// let query = Query {
-///     qname: String::from("dns.lookup.dog"),
+///     qname: Labels::encode("dns.lookup.dog").unwrap(),
 ///     qclass: QClass::IN,
 ///     qclass: QClass::IN,
 ///     qtype: qtype!(A),
 ///     qtype: qtype!(A),
 /// };
 /// };

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

@@ -13,10 +13,10 @@ use super::{Transport, Error};
 ///
 ///
 /// ```no_run
 /// ```no_run
 /// use dns_transport::{Transport, TcpTransport};
 /// 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 {
 /// let query = Query {
-///     qname: String::from("dns.lookup.dog"),
+///     qname: Labels::encode("dns.lookup.dog").unwrap(),
 ///     qclass: QClass::IN,
 ///     qclass: QClass::IN,
 ///     qtype: qtype!(MX),
 ///     qtype: qtype!(MX),
 /// };
 /// };

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

@@ -14,10 +14,10 @@ use super::{Transport, Error};
 ///
 ///
 /// ```no_run
 /// ```no_run
 /// use dns_transport::{Transport, TlsTransport};
 /// 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 {
 /// let query = Query {
-///     qname: String::from("dns.lookup.dog"),
+///     qname: Labels::encode("dns.lookup.dog").unwrap(),
 ///     qclass: QClass::IN,
 ///     qclass: QClass::IN,
 ///     qtype: qtype!(SRV),
 ///     qtype: qtype!(SRV),
 /// };
 /// };

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

@@ -14,10 +14,10 @@ use super::{Transport, Error};
 ///
 ///
 /// ```no_run
 /// ```no_run
 /// use dns_transport::{Transport, UdpTransport};
 /// 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 {
 /// let query = Query {
-///     qname: String::from("dns.lookup.dog"),
+///     qname: Labels::encode("dns.lookup.dog").unwrap(),
 ///     qclass: QClass::IN,
 ///     qclass: QClass::IN,
 ///     qtype: qtype!(NS),
 ///     qtype: qtype!(NS),
 /// };
 /// };

+ 1 - 0
dns/src/lib.rs

@@ -27,6 +27,7 @@ mod types;
 pub use self::types::*;
 pub use self::types::*;
 
 
 mod strings;
 mod strings;
+pub use self::strings::Labels;
 
 
 mod wire;
 mod wire;
 pub use self::wire::{Wire, WireError, find_qtype_number};
 pub use self::wire::{Wire, WireError, find_qtype_number};

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

@@ -1,6 +1,6 @@
 use log::*;
 use log::*;
 
 
-use crate::strings::ReadLabels;
+use crate::strings::{Labels, ReadLabels};
 use crate::wire::*;
 use crate::wire::*;
 
 
 
 
@@ -13,7 +13,7 @@ use crate::wire::*;
 pub struct CNAME {
 pub struct CNAME {
 
 
     /// The domain name that this CNAME record is responding with.
     /// The domain name that this CNAME record is responding with.
-    pub domain: String,
+    pub domain: Labels,
 }
 }
 
 
 impl Wire for CNAME {
 impl Wire for CNAME {
@@ -50,7 +50,7 @@ mod test {
 
 
         assert_eq!(CNAME::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
         assert_eq!(CNAME::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    CNAME {
                    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 log::*;
 
 
-use crate::strings::ReadLabels;
+use crate::strings::{Labels, ReadLabels};
 use crate::wire::*;
 use crate::wire::*;
 
 
 
 
@@ -18,7 +18,7 @@ pub struct MX {
     pub preference: u16,
     pub preference: u16,
 
 
     /// The domain name of the mail exchange server.
     /// The domain name of the mail exchange server.
-    pub exchange: String,
+    pub exchange: Labels,
 }
 }
 
 
 impl Wire for MX {
 impl Wire for MX {
@@ -61,7 +61,7 @@ mod test {
         assert_eq!(MX::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
         assert_eq!(MX::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    MX {
                    MX {
                        preference: 10,
                        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 log::*;
 
 
+use crate::strings::{Labels, ReadLabels};
+use crate::wire::*;
+
 
 
 /// A **NS** _(name server)_ record, which is used to point domains to name
 /// A **NS** _(name server)_ record, which is used to point domains to name
 /// servers.
 /// servers.
@@ -14,7 +14,7 @@ use log::*;
 pub struct NS {
 pub struct NS {
 
 
     /// The address of a nameserver that provides this DNS response.
     /// The address of a nameserver that provides this DNS response.
-    pub nameserver: String,
+    pub nameserver: Labels,
 }
 }
 
 
 impl Wire for NS {
 impl Wire for NS {
@@ -52,7 +52,7 @@ mod test {
 
 
         assert_eq!(NS::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
         assert_eq!(NS::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    NS {
                    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 log::*;
 
 
-use crate::strings::ReadLabels;
+use crate::strings::{Labels, ReadLabels};
 use crate::wire::*;
 use crate::wire::*;
 
 
 
 
@@ -19,7 +19,7 @@ use crate::wire::*;
 pub struct PTR {
 pub struct PTR {
 
 
     /// The CNAME contained in the record.
     /// The CNAME contained in the record.
-    pub cname: String,
+    pub cname: Labels,
 }
 }
 
 
 impl Wire for PTR {
 impl Wire for PTR {
@@ -56,7 +56,7 @@ mod test {
 
 
         assert_eq!(PTR::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
         assert_eq!(PTR::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    PTR {
                    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 log::*;
 
 
-use crate::strings::ReadLabels;
+use crate::strings::{Labels, ReadLabels};
 use crate::wire::*;
 use crate::wire::*;
 
 
 
 
@@ -15,10 +15,10 @@ use crate::wire::*;
 pub struct SOA {
 pub struct SOA {
 
 
     /// The primary master name for this server.
     /// 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.
     /// 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.
     /// A serial number for this DNS zone.
     pub serial: u32,
     pub serial: u32,
@@ -104,8 +104,8 @@ mod test {
 
 
         assert_eq!(SOA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
         assert_eq!(SOA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
                    SOA {
                    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,
                        serial: 1564274434,
                        refresh_interval: 86400,
                        refresh_interval: 86400,
                        retry_interval: 7200,
                        retry_interval: 7200,

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

@@ -1,6 +1,6 @@
 use log::*;
 use log::*;
 
 
-use crate::strings::ReadLabels;
+use crate::strings::{Labels, ReadLabels};
 use crate::wire::*;
 use crate::wire::*;
 
 
 
 
@@ -25,7 +25,7 @@ pub struct SRV {
     pub port: u16,
     pub port: u16,
 
 
     /// The hostname of the machine the service is running on.
     /// The hostname of the machine the service is running on.
-    pub target: String,
+    pub target: Labels,
 }
 }
 
 
 impl Wire for SRV {
 impl Wire for SRV {
@@ -80,7 +80,7 @@ mod test {
                        priority: 1,
                        priority: 1,
                        weight: 1,
                        weight: 1,
                        port: 37500,
                        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.
 //! Reading strings from the DNS wire protocol.
 
 
+use std::convert::TryFrom;
+use std::fmt;
 use std::io::{self, Write};
 use std::io::{self, Write};
 
 
 use byteorder::{ReadBytesExt, WriteBytesExt};
 use byteorder::{ReadBytesExt, WriteBytesExt};
@@ -8,20 +10,72 @@ use log::*;
 use crate::wire::*;
 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
 /// An extension for `Cursor` that enables reading compressed domain names
 /// from DNS packets.
 /// from DNS packets.
 pub(crate) trait ReadLabels {
 pub(crate) trait ReadLabels {
 
 
     /// Read and expand a compressed domain name.
     /// 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]> {
 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:
     /// So “dns.lookup.dog” would be encoded as:
     /// “3, dns, 6, lookup, 3, dog, 0”.
     /// “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 {
 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() {
             for b in label.as_bytes() {
                 self.write_u8(*b)?;
                 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
 /// that had to be read to produce the string, including the bytes to signify
 /// backtracking, but not including the bytes read _during_ backtracking.
 /// backtracking, but not including the bytes read _during_ backtracking.
 #[cfg_attr(all(test, feature = "with_mutagen"), ::mutagen::mutate)]
 #[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;
     let mut bytes_read = 0;
 
 
     loop {
     loop {
@@ -96,7 +150,7 @@ fn read_string_recursive(name_buf: &mut Vec<u8>, c: &mut Cursor<&[u8]>, recursio
             let new_pos = c.position();
             let new_pos = c.position();
             c.set_position(u64::from(offset));
             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);
             trace!("Coming back to {}", new_pos);
             c.set_position(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
         // Otherwise, treat the byte as the length of a label, and read that
         // many characters.
         // many characters.
         else {
         else {
+            let mut name_buf = Vec::new();
+
             for _ in 0 .. byte {
             for _ in 0 .. byte {
                 let c = c.read_u8()?;
                 let c = c.read_u8()?;
                 bytes_read += 1;
                 bytes_read += 1;
                 name_buf.push(c);
                 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(),
         assert_eq!(Cursor::new(buf).read_labels(),
-                   Ok(("".into(), 1)));
+                   Ok((Labels::root(), 1)));
     }
     }
 
 
     #[test]
     #[test]
@@ -148,7 +205,7 @@ mod test {
         ];
         ];
 
 
         assert_eq!(Cursor::new(buf).read_labels(),
         assert_eq!(Cursor::new(buf).read_labels(),
-                   Ok(("one.".into(), 5)));
+                   Ok((Labels::encode("one.").unwrap(), 5)));
     }
     }
 
 
     #[test]
     #[test]
@@ -162,7 +219,7 @@ mod test {
         ];
         ];
 
 
         assert_eq!(Cursor::new(buf).read_labels(),
         assert_eq!(Cursor::new(buf).read_labels(),
-                   Ok(("one.two.".into(), 9)));
+                   Ok((Labels::encode("one.two.").unwrap(), 9)));
     }
     }
 
 
     #[test]
     #[test]
@@ -178,7 +235,7 @@ mod test {
         ];
         ];
 
 
         assert_eq!(Cursor::new(buf).read_labels(),
         assert_eq!(Cursor::new(buf).read_labels(),
-                   Ok(("one.two.".into(), 6)));
+                   Ok((Labels::encode("one.two.").unwrap(), 6)));
     }
     }
 
 
     #[test]
     #[test]

+ 4 - 3
dns/src/types.rs

@@ -4,6 +4,7 @@
 //! having at least one record in its answer fields.
 //! having at least one record in its answer fields.
 
 
 use crate::record::{Record, OPT};
 use crate::record::{Record, OPT};
+use crate::strings::Labels;
 
 
 
 
 /// A request that gets sent out over a transport.
 /// A request that gets sent out over a transport.
@@ -56,7 +57,7 @@ pub struct Response {
 pub struct Query {
 pub struct Query {
 
 
     /// The domain name being queried, in human-readable dotted notation.
     /// The domain name being queried, in human-readable dotted notation.
-    pub qname: String,
+    pub qname: Labels,
 
 
     /// The class number.
     /// The class number.
     pub qclass: QClass,
     pub qclass: QClass,
@@ -74,7 +75,7 @@ pub enum Answer {
     Standard {
     Standard {
 
 
         /// The domain name being answered for.
         /// The domain name being answered for.
-        qname: String,
+        qname: Labels,
 
 
         /// This answer’s class.
         /// This answer’s class.
         qclass: QClass,
         qclass: QClass,
@@ -91,7 +92,7 @@ pub enum Answer {
     Pseudo {
     Pseudo {
 
 
         /// The domain name being answered for.
         /// The domain name being answered for.
-        qname: String,
+        qname: Labels,
 
 
         /// The OPT record contained in this answer.
         /// The OPT record contained in this answer.
         opt: OPT,
         opt: OPT,

+ 10 - 10
dns/src/wire.rs

@@ -7,7 +7,7 @@ use std::io;
 use log::*;
 use log::*;
 
 
 use crate::record::{Record, OPT};
 use crate::record::{Record, OPT};
-use crate::strings::{ReadLabels, WriteLabels};
+use crate::strings::{Labels, ReadLabels, WriteLabels};
 use crate::types::*;
 use crate::types::*;
 
 
 
 
@@ -109,7 +109,7 @@ impl Query {
     /// Reads bytes from the given cursor, and parses them into a query with
     /// Reads bytes from the given cursor, and parses them into a query with
     /// the given domain name.
     /// the given domain name.
     #[cfg_attr(all(test, feature = "with_mutagen"), ::mutagen::mutate)]
     #[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>()?;
         let qtype = c.read_u16::<BigEndian>()?;
         trace!("Read qtype -> {:?}", qtype);
         trace!("Read qtype -> {:?}", qtype);
 
 
@@ -126,7 +126,7 @@ impl Answer {
     /// Reads bytes from the given cursor, and parses them into an answer with
     /// Reads bytes from the given cursor, and parses them into an answer with
     /// the given domain name.
     /// the given domain name.
     #[cfg_attr(all(test, feature = "with_mutagen"), ::mutagen::mutate)]
     #[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>()?;
         let qtype = c.read_u16::<BigEndian>()?;
         trace!("Read qtype -> {:?}", qtype);
         trace!("Read qtype -> {:?}", qtype);
 
 
@@ -492,14 +492,14 @@ mod test {
             flags: Flags::standard_response(),
             flags: Flags::standard_response(),
             queries: vec![
             queries: vec![
                 Query {
                 Query {
-                    qname: "bsago.me.".into(),
+                    qname: Labels::encode("bsago.me").unwrap(),
                     qclass: QClass::IN,
                     qclass: QClass::IN,
                     qtype: qtype!(A),
                     qtype: qtype!(A),
                 },
                 },
             ],
             ],
             answers: vec![
             answers: vec![
                 Answer::Standard {
                 Answer::Standard {
-                    qname: "bsago.me.".into(),
+                    qname: Labels::encode("bsago.me").unwrap(),
                     qclass: QClass::IN,
                     qclass: QClass::IN,
                     ttl: 887,
                     ttl: 887,
                     record: Record::A(A {
                     record: Record::A(A {
@@ -509,12 +509,12 @@ mod test {
             ],
             ],
             authorities: vec![
             authorities: vec![
                 Answer::Standard {
                 Answer::Standard {
-                    qname: "".into(),
+                    qname: Labels::root(),
                     qclass: QClass::IN,
                     qclass: QClass::IN,
                     ttl: 4294967295,
                     ttl: 4294967295,
                     record: Record::SOA(SOA {
                     record: Record::SOA(SOA {
-                        mname: "a.".into(),
-                        rname: "mx.".into(),
+                        mname: Labels::encode("a").unwrap(),
+                        rname: Labels::encode("mx").unwrap(),
                         serial: 2020102700,
                         serial: 2020102700,
                         refresh_interval: 1800,
                         refresh_interval: 1800,
                         retry_interval: 900,
                         retry_interval: 900,
@@ -525,7 +525,7 @@ mod test {
             ],
             ],
             additionals: vec![
             additionals: vec![
                 Answer::Standard {
                 Answer::Standard {
-                    qname: "".into(),
+                    qname: Labels::root(),
                     qclass: QClass::Other(153),
                     qclass: QClass::Other(153),
                     ttl: 305419896,
                     ttl: 305419896,
                     record: Record::Other {
                     record: Record::Other {
@@ -534,7 +534,7 @@ mod test {
                     },
                     },
                 },
                 },
                 Answer::Pseudo {
                 Answer::Pseudo {
-                    qname: "".into(),
+                    qname: Labels::root(),
                     opt: OPT {
                     opt: OPT {
                         udp_payload_size: 512,
                         udp_payload_size: 512,
                         higher_bits: 0,
                         higher_bits: 0,

+ 7 - 7
dns/tests/wire_parsing_tests.rs

@@ -1,6 +1,6 @@
 use std::net::Ipv4Addr;
 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};
 use dns::record::{Record, A, CNAME, OPT};
 
 
 
 
@@ -56,14 +56,14 @@ fn parse_response_standard() {
         },
         },
         queries: vec![
         queries: vec![
             Query {
             Query {
-                qname: "dns.lookup.dog.".into(),
+                qname: Labels::encode("dns.lookup.dog").unwrap(),
                 qclass: QClass::IN,
                 qclass: QClass::IN,
                 qtype: qtype!(A),
                 qtype: qtype!(A),
             },
             },
         ],
         ],
         answers: vec![
         answers: vec![
             Answer::Standard {
             Answer::Standard {
-                qname: "dns.lookup.dog.".into(),
+                qname: Labels::encode("dns.lookup.dog").unwrap(),
                 qclass: QClass::IN,
                 qclass: QClass::IN,
                 ttl: 933,
                 ttl: 933,
                 record: Record::A(A {
                 record: Record::A(A {
@@ -74,7 +74,7 @@ fn parse_response_standard() {
         authorities: vec![],
         authorities: vec![],
         additionals: vec![
         additionals: vec![
             Answer::Pseudo {
             Answer::Pseudo {
-                qname: "".into(),
+                qname: Labels::root(),
                 opt: OPT {
                 opt: OPT {
                     udp_payload_size: 512,
                     udp_payload_size: 512,
                     higher_bits: 0,
                     higher_bits: 0,
@@ -129,18 +129,18 @@ fn parse_response_with_mixed_string() {
         },
         },
         queries: vec![
         queries: vec![
             Query {
             Query {
-                qname: "cname-example.lookup.dog.".into(),
+                qname: Labels::encode("cname-example.lookup.dog").unwrap(),
                 qclass: QClass::IN,
                 qclass: QClass::IN,
                 qtype: qtype!(CNAME),
                 qtype: qtype!(CNAME),
             },
             },
         ],
         ],
         answers: vec![
         answers: vec![
             Answer::Standard {
             Answer::Standard {
-                qname: "cname-example.lookup.dog.".into(),
+                qname: Labels::encode("cname-example.lookup.dog").unwrap(),
                 qclass: QClass::IN,
                 qclass: QClass::IN,
                 ttl: 873,
                 ttl: 873,
                 record: Record::CNAME(CNAME {
                 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 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 dns::record::{A, find_other_qtype_number};
 
 
 use crate::connect::TransportType;
 use crate::connect::TransportType;
@@ -154,7 +154,10 @@ impl Inputs {
 
 
     fn load_named_args(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> {
     fn load_named_args(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> {
         for domain in matches.opt_strs("query") {
         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") {
         for qtype in matches.opt_strs("type") {
@@ -229,7 +232,10 @@ impl Inputs {
             }
             }
             else {
             else {
                 trace!("Got domain -> {:?}", &a);
                 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)]
 #[derive(PartialEq, Debug)]
 pub enum OptionsError {
 pub enum OptionsError {
     TooManyProtocols,
     TooManyProtocols,
+    InvalidDomain(String),
     InvalidEDNS(String),
     InvalidEDNS(String),
     InvalidQueryType(String),
     InvalidQueryType(String),
     InvalidQueryClass(String),
     InvalidQueryClass(String),
@@ -423,6 +430,7 @@ impl fmt::Display for OptionsError {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
         match self {
             Self::TooManyProtocols       => write!(f, "Too many protocols"),
             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::InvalidEDNS(edns)      => write!(f, "Invalid EDNS setting {:?}", edns),
             Self::InvalidQueryType(qt)   => write!(f, "Invalid query type {:?}", qt),
             Self::InvalidQueryType(qt)   => write!(f, "Invalid query type {:?}", qt),
             Self::InvalidQueryClass(qc)  => write!(f, "Invalid query class {:?}", qc),
             Self::InvalidQueryClass(qc)  => write!(f, "Invalid query class {:?}", qc),
@@ -512,7 +520,7 @@ mod test {
     fn just_domain() {
     fn just_domain() {
         let options = Options::getopts(&[ "lookup.dog" ]).unwrap();
         let options = Options::getopts(&[ "lookup.dog" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             .. Inputs::fallbacks()
             .. Inputs::fallbacks()
         });
         });
     }
     }
@@ -521,7 +529,7 @@ mod test {
     fn just_named_domain() {
     fn just_named_domain() {
         let options = Options::getopts(&[ "-q", "lookup.dog" ]).unwrap();
         let options = Options::getopts(&[ "-q", "lookup.dog" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             .. Inputs::fallbacks()
             .. Inputs::fallbacks()
         });
         });
     }
     }
@@ -530,7 +538,7 @@ mod test {
     fn domain_and_type() {
     fn domain_and_type() {
         let options = Options::getopts(&[ "lookup.dog", "SOA" ]).unwrap();
         let options = Options::getopts(&[ "lookup.dog", "SOA" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             types:      vec![ qtype!(SOA) ],
             types:      vec![ qtype!(SOA) ],
             .. Inputs::fallbacks()
             .. Inputs::fallbacks()
         });
         });
@@ -540,7 +548,7 @@ mod test {
     fn domain_and_nameserver() {
     fn domain_and_nameserver() {
         let options = Options::getopts(&[ "lookup.dog", "@1.1.1.1" ]).unwrap();
         let options = Options::getopts(&[ "lookup.dog", "@1.1.1.1" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         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()) ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
             .. Inputs::fallbacks()
             .. Inputs::fallbacks()
         });
         });
@@ -550,7 +558,7 @@ mod test {
     fn domain_and_class() {
     fn domain_and_class() {
         let options = Options::getopts(&[ "lookup.dog", "CH" ]).unwrap();
         let options = Options::getopts(&[ "lookup.dog", "CH" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             classes:    vec![ QClass::CH ],
             classes:    vec![ QClass::CH ],
             .. Inputs::fallbacks()
             .. Inputs::fallbacks()
         });
         });
@@ -560,7 +568,7 @@ mod test {
     fn all_free() {
     fn all_free() {
         let options = Options::getopts(&[ "lookup.dog", "CH", "NS", "@1.1.1.1" ]).unwrap();
         let options = Options::getopts(&[ "lookup.dog", "CH", "NS", "@1.1.1.1" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             classes:    vec![ QClass::CH ],
             classes:    vec![ QClass::CH ],
             types:      vec![ qtype!(NS) ],
             types:      vec![ qtype!(NS) ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
@@ -572,7 +580,7 @@ mod test {
     fn all_parameters() {
     fn all_parameters() {
         let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "CH", "--type", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
         let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "CH", "--type", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             classes:    vec![ QClass::CH ],
             classes:    vec![ QClass::CH ],
             types:      vec![ qtype!(SOA) ],
             types:      vec![ qtype!(SOA) ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
@@ -584,7 +592,7 @@ mod test {
     fn two_types() {
     fn two_types() {
         let options = Options::getopts(&[ "-q", "lookup.dog", "--type", "SRV", "--type", "AAAA" ]).unwrap();
         let options = Options::getopts(&[ "-q", "lookup.dog", "--type", "SRV", "--type", "AAAA" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         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) ],
             types:      vec![ qtype!(SRV), qtype!(AAAA) ],
             .. Inputs::fallbacks()
             .. Inputs::fallbacks()
         });
         });
@@ -594,7 +602,7 @@ mod test {
     fn two_classes() {
     fn two_classes() {
         let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "IN", "--class", "CH" ]).unwrap();
         let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "IN", "--class", "CH" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         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 ],
             classes:    vec![ QClass::IN, QClass::CH ],
             .. Inputs::fallbacks()
             .. Inputs::fallbacks()
         });
         });
@@ -604,7 +612,7 @@ mod test {
     fn all_mixed_1() {
     fn all_mixed_1() {
         let options = Options::getopts(&[ "lookup.dog", "--class", "CH", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
         let options = Options::getopts(&[ "lookup.dog", "--class", "CH", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("lookup.dog") ],
+            domains:    vec![ Labels::encode("lookup.dog").unwrap() ],
             classes:    vec![ QClass::CH ],
             classes:    vec![ QClass::CH ],
             types:      vec![ qtype!(SOA) ],
             types:      vec![ qtype!(SOA) ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()) ],
@@ -616,7 +624,7 @@ mod test {
     fn all_mixed_2() {
     fn all_mixed_2() {
         let options = Options::getopts(&[ "CH", "SOA", "MX", "IN", "-q", "lookup.dog", "--class", "HS" ]).unwrap();
         let options = Options::getopts(&[ "CH", "SOA", "MX", "IN", "-q", "lookup.dog", "--class", "HS" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         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 ],
             classes:    vec![ QClass::HS, QClass::CH, QClass::IN ],
             types:      vec![ qtype!(SOA), qtype!(MX) ],
             types:      vec![ qtype!(SOA), qtype!(MX) ],
             .. Inputs::fallbacks()
             .. Inputs::fallbacks()
@@ -627,7 +635,7 @@ mod test {
     fn all_mixed_3() {
     fn all_mixed_3() {
         let options = Options::getopts(&[ "lookup.dog", "--nameserver", "1.1.1.1", "--nameserver", "1.0.0.1" ]).unwrap();
         let options = Options::getopts(&[ "lookup.dog", "--nameserver", "1.1.1.1", "--nameserver", "1.0.0.1" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         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()),
             resolvers:  vec![ Resolver::Specified("1.1.1.1".into()),
                               Resolver::Specified("1.0.0.1".into()), ],
                               Resolver::Specified("1.0.0.1".into()), ],
             .. Inputs::fallbacks()
             .. Inputs::fallbacks()
@@ -638,7 +646,7 @@ mod test {
     fn explicit_numerics() {
     fn explicit_numerics() {
         let options = Options::getopts(&[ "11", "--class", "22", "--type", "33" ]).unwrap();
         let options = Options::getopts(&[ "11", "--class", "22", "--type", "33" ]).unwrap();
         assert_eq!(options.requests.inputs, Inputs {
         assert_eq!(options.requests.inputs, Inputs {
-            domains:    vec![ String::from("11") ],
+            domains:    vec![ Labels::encode("11").unwrap() ],
             classes:    vec![ QClass::Other(22) ],
             classes:    vec![ QClass::Other(22) ],
             types:      vec![ 33 ],
             types:      vec![ 33 ],
             .. Inputs::fallbacks()
             .. Inputs::fallbacks()

+ 16 - 14
src/output.rs

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

+ 3 - 1
src/requests.rs

@@ -1,3 +1,5 @@
+use dns::Labels;
+
 use crate::connect::TransportType;
 use crate::connect::TransportType;
 use crate::resolve::Resolver;
 use crate::resolve::Resolver;
 use crate::txid::TxidGenerator;
 use crate::txid::TxidGenerator;
@@ -26,7 +28,7 @@ pub struct RequestGenerator {
 pub struct Inputs {
 pub struct Inputs {
 
 
     /// The list of domain names to query.
     /// The list of domain names to query.
-    pub domains: Vec<String>,
+    pub domains: Vec<Labels>,
 
 
     /// The list of DNS record types to query for.
     /// The list of DNS record types to query for.
     pub types: Vec<u16>,
     pub types: Vec<u16>,

+ 2 - 0
src/table.rs

@@ -58,12 +58,14 @@ impl Table {
         match answer {
         match answer {
             Answer::Standard { record, qname, ttl, .. } => {
             Answer::Standard { record, qname, ttl, .. } => {
                 let qtype = self.coloured_record_type(&record);
                 let qtype = self.coloured_record_type(&record);
+                let qname = qname.to_string();
                 let summary = self.text_format.record_payload_summary(&record);
                 let summary = self.text_format.record_payload_summary(&record);
                 let ttl = Some(self.text_format.format_duration(ttl));
                 let ttl = Some(self.text_format.format_duration(ttl));
                 self.rows.push(Row { qtype, qname, ttl, summary, section });
                 self.rows.push(Row { qtype, qname, ttl, summary, section });
             }
             }
             Answer::Pseudo { qname, opt } => {
             Answer::Pseudo { qname, opt } => {
                 let qtype = self.colours.opt.paint("OPT");
                 let qtype = self.colours.opt.paint("OPT");
+                let qname = qname.to_string();
                 let summary = self.text_format.pseudo_record_payload_summary(&opt);
                 let summary = self.text_format.pseudo_record_payload_summary(&opt);
                 self.rows.push(Row { qtype, qname, ttl: None, summary, section });
                 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 }
 stdout = { empty = true }
 stderr = { string = "OPT request is sent by default" }
 stderr = { string = "OPT request is sent by default" }
 status = 3
 status = 3
+
+[[cmd]]
+name = "A domain label long than 255 bytes gets rejected"
+shell = "dog 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
+stdout = { empty = true }
+stderr = { string = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" }
+status = 3