diff --git a/Cargo.lock b/Cargo.lock index 93d4c8c..5769850 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "addr2line" version = "0.14.1" @@ -148,6 +150,7 @@ dependencies = [ "httparse", "log", "native-tls", + "url", ] [[package]] @@ -204,6 +207,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "getopts" version = "0.2.21" @@ -245,6 +258,17 @@ version = "1.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "ipconfig" version = "0.2.2" @@ -411,6 +435,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "pkg-config" version = "0.3.19" @@ -641,6 +671,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "tinyvec" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "unic-char-property" version = "0.9.0" @@ -744,6 +789,24 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicode-bidi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.8" @@ -756,6 +819,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + [[package]] name = "vcpkg" version = "0.2.11" diff --git a/dns-transport/Cargo.toml b/dns-transport/Cargo.toml index 7b8da00..c985651 100644 --- a/dns-transport/Cargo.toml +++ b/dns-transport/Cargo.toml @@ -20,6 +20,8 @@ log = "0.4" # tls networking native-tls = { version = "0.2", optional = true } +url = "2.2.2" + # http response parsing httparse = { version = "1.3", optional = true } diff --git a/dns-transport/src/error.rs b/dns-transport/src/error.rs index 54a20a2..0b7a426 100644 --- a/dns-transport/src/error.rs +++ b/dns-transport/src/error.rs @@ -9,6 +9,9 @@ pub enum Error { /// There was a problem with the network making a TCP or UDP request. NetworkError(std::io::Error), + /// The supplied nameserver address was somehow invalid + NameserverError(std::io::Error), + /// Not enough information was received from the server before a `read` /// call returned zero bytes. TruncatedResponse, diff --git a/dns-transport/src/lib.rs b/dns-transport/src/lib.rs index 756f8be..2a0bac5 100644 --- a/dns-transport/src/lib.rs +++ b/dns-transport/src/lib.rs @@ -58,3 +58,26 @@ pub trait Transport { /// the TLS and HTTPS transports. fn send(&self, request: &dns::Request) -> Result; } + + +use std::net::SocketAddr; +use std::io::ErrorKind; +use url::Url; + +// parse address:port literals in the style of RFC2732: +// [2001:db8::1] +// [2001:db8::2]:53 +// 192.0.2.1 +// 192.0.2.1:53 +fn lookup_addr(addr: &str) -> Result, Error> { + if addr.contains('/') { + return Err(Error::NameserverError(std::io::Error::new(ErrorKind::InvalidInput, "contains invalid characters"))); + } + + // cheat and just parse the pseudo-url "dns://
" + let url = Url::parse(format!("dns://{}/", addr).as_str()).map_err( + |e| Error::NameserverError(std::io::Error::new(ErrorKind::InvalidInput, e)))?; + + // This resolves the URL in to 1 or more SocketAddr's (or errors) + url.socket_addrs(|| Some(53)).map_err(Error::NameserverError) +} \ No newline at end of file diff --git a/dns-transport/src/tcp.rs b/dns-transport/src/tcp.rs index f9327a9..e9459a7 100644 --- a/dns-transport/src/tcp.rs +++ b/dns-transport/src/tcp.rs @@ -31,15 +31,11 @@ impl TcpTransport { impl Transport for TcpTransport { fn send(&self, request: &Request) -> Result { - info!("Opening TCP stream"); - let mut stream = - if self.addr.contains(':') { - TcpStream::connect(&*self.addr)? - } - else { - TcpStream::connect((&*self.addr, 53))? - }; - debug!("Opened"); + info!("Opening TCP stream to {}", self.addr); + let dstaddrs = crate::lookup_addr(&self.addr)?; + + let mut stream = TcpStream::connect(&*dstaddrs)?; + debug!("Opened connection to {}", stream.peer_addr().unwrap()); // The message is prepended with the length when sent over TCP, // so the server knows how long it is (RFC 1035 ยง4.2.2) diff --git a/dns-transport/src/udp.rs b/dns-transport/src/udp.rs index 785b3d4..766d242 100644 --- a/dns-transport/src/udp.rs +++ b/dns-transport/src/udp.rs @@ -1,4 +1,4 @@ -use std::net::{Ipv4Addr, UdpSocket}; +use std::net::{Ipv4Addr, Ipv6Addr, UdpSocket, SocketAddr}; use log::*; @@ -27,17 +27,19 @@ impl UdpTransport { impl Transport for UdpTransport { fn send(&self, request: &Request) -> Result { - info!("Opening UDP socket"); - // TODO: This will need to be changed for IPv6 support. - let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?; - - if self.addr.contains(':') { - socket.connect(&*self.addr)?; - } - else { - socket.connect((&*self.addr, 53))?; - } - debug!("Opened"); + info!("Opening UDP socket to {}", self.addr); + + let dstaddr = crate::lookup_addr(&self.addr)?.pop().unwrap(); + let srcaddr = if dstaddr.is_ipv4() { + SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)) + } else { + SocketAddr::from((Ipv6Addr::UNSPECIFIED, 0)) + }; + + let socket = UdpSocket::bind(srcaddr)?; + socket.connect(dstaddr)?; + + debug!("Opened connection to {}", dstaddr); let bytes_to_send = request.to_bytes().expect("failed to serialise request"); diff --git a/man/dog.1.md b/man/dog.1.md index 34f5f89..cbc187a 100644 --- a/man/dog.1.md +++ b/man/dog.1.md @@ -49,7 +49,7 @@ QUERY OPTIONS : Type of the DNS record being queried (`A`, `MX`, `NS`...) `-n`, `--nameserver=ADDR` -: Address of the nameserver to send packets to. +: Address of the nameserver to send packets to. Accepts `ADDR` and `ADDR:PORT` format. If `ADDR` is an ipv6 literal, then it must be in brackets (e.g. `[2606:4700:4700::1111]:53`). `--class=CLASS` : Network class of the DNS record being queried (`IN`, `CH`, `HS`) diff --git a/src/output.rs b/src/output.rs index 9ef1fba..ce8e524 100644 --- a/src/output.rs +++ b/src/output.rs @@ -646,6 +646,7 @@ fn erroneous_phase(error: &TransportError) -> &'static str { match error { TransportError::WireError(_) => "protocol", TransportError::TruncatedResponse | + TransportError::NameserverError(_) | TransportError::NetworkError(_) => "network", #[cfg(feature = "with_tls")] TransportError::TlsError(_) | @@ -662,6 +663,7 @@ fn error_message(error: TransportError) -> String { TransportError::WireError(e) => wire_error_message(e), TransportError::TruncatedResponse => "Truncated response".into(), TransportError::NetworkError(e) => e.to_string(), + TransportError::NameserverError(e) => format!("invalid nameserver: {}", e.to_string()), #[cfg(feature = "with_tls")] TransportError::TlsError(e) => e.to_string(), #[cfg(feature = "with_tls")]