Skip to content

Commit

Permalink
Add v6 transport support for TCP and UDP.
Browse files Browse the repository at this point in the history
Additionally, this supports url-style IP literals: v6 addresses must be
surrounded by brackets.

Signed-off-by: Casey Callendrello <[email protected]>
  • Loading branch information
squeed committed Aug 2, 2021
1 parent 640818d commit e8e3107
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 22 deletions.
75 changes: 75 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions dns-transport/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
3 changes: 3 additions & 0 deletions dns-transport/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 26 additions & 0 deletions dns-transport/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,29 @@ pub trait Transport {
/// the TLS and HTTPS transports.
fn send(&self, request: &dns::Request) -> Result<dns::Response, Error>;
}


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
// cheat and just parse the pseudo-url "dns://<address>"
fn lookup_addr(addr: &str) -> Result<SocketAddr, Error> {
if addr.contains('/') {
return Err(Error::NameserverError(std::io::Error::new(ErrorKind::InvalidInput, "contains invalid characters")));
}

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)
let mut addrs = url.socket_addrs(|| Some(53)).map_err(Error::NameserverError)?;
addrs.pop().ok_or_else( ||
// paranoia -- addrs should never be empty
Error::NameserverError(std::io::Error::new(ErrorKind::NotFound, "invalid nameserver")))
}
14 changes: 5 additions & 9 deletions dns-transport/src/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,11 @@ impl TcpTransport {

impl Transport for TcpTransport {
fn send(&self, request: &Request) -> Result<Response, Error> {
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 dstaddr = crate::lookup_addr(&self.addr)?;

let mut stream = TcpStream::connect(dstaddr)?;
debug!("Opened connection to {}", dstaddr);

// The message is prepended with the length when sent over TCP,
// so the server knows how long it is (RFC 1035 §4.2.2)
Expand Down
26 changes: 14 additions & 12 deletions dns-transport/src/udp.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::net::{Ipv4Addr, UdpSocket};
use std::net::{Ipv4Addr, Ipv6Addr, UdpSocket, SocketAddr};

use log::*;

Expand Down Expand Up @@ -27,17 +27,19 @@ impl UdpTransport {

impl Transport for UdpTransport {
fn send(&self, request: &Request) -> Result<Response, Error> {
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)?;
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");

Expand Down
2 changes: 1 addition & 1 deletion man/dog.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
2 changes: 2 additions & 0 deletions src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(_) |
Expand All @@ -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")]
Expand Down

0 comments on commit e8e3107

Please sign in to comment.