https server and client working

This commit is contained in:
Benjamin Fry 2018-09-08 19:15:49 -07:00
parent 73b61adb06
commit 0850f1cf6f
41 changed files with 1339 additions and 420 deletions

View File

@ -5,11 +5,17 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## 0.15.0
### Added
- feature `dns-over-rustls` to `trust-dns-server`
- feature `dns-over-https-rustls`, `dns-over-https-openssl` *experimental*
### Changed
- *breaking* Overhauled all `ClientFuture` implementations to align with new `DnsExchange` and `DnsMultiplexer` components in proto.
- *breaking* `ClientFuture` after construction, now returns a "background" `ClientFuture` and a "foreground" `BasicClientHandle`
- *breaking* `Client` has more type parameters, these match with the same types returned by the `*ClientConnection` constructors
- feature `tls` renamed to `dns-over-openssl`
## 0.14.0

View File

@ -42,7 +42,10 @@ codecov = { repository = "bluejekyll/trust-dns", branch = "master", service = "g
[features]
# TODO: the rustls and openssl crates are not deps... should we change that to make them easier to use?
# or change this to also be external?
dns-over-https = ["trust-dns-https", "rustls", "webpki"]
dns-over-https-rustls = ["trust-dns-https", "rustls", "webpki", "dns-over-https"]
dns-over-https-openssl = ["trust-dns-https", "openssl", "dns-over-https"]
dns-over-https = []
dnssec-openssl = ["dnssec", "openssl", "trust-dns-proto/dnssec-openssl"]
dnssec-ring = ["dnssec", "ring", "trust-dns-proto/dnssec-ring", "untrusted"]
dnssec = []

View File

@ -271,7 +271,7 @@ extern crate radix_trie;
extern crate rand;
#[cfg(feature = "ring")]
extern crate ring;
#[cfg(feature = "dns-over-https")]
#[cfg(feature = "dns-over-https-rustls")]
extern crate rustls;
#[cfg(feature = "serde-config")]
extern crate serde;

View File

@ -11,7 +11,7 @@ use op::Query;
use rr::{DNSClass, LowerName, RecordType};
use serialize::binary::*;
/// Identical to [`trust_dns::op::Query`], except that the Name is guaranteed to be in lower case form
/// Identical to [`trust_dns::op::Query`], except that the Name is guaranteed to be in lower case form
#[derive(Clone, Debug, PartialEq)]
pub struct LowerQuery {
name: LowerName,
@ -80,4 +80,4 @@ impl<'r> BinDecodable<'r> for LowerQuery {
let original = Query::read(decoder)?;
Ok(LowerQuery::query(original))
}
}
}

View File

@ -46,6 +46,7 @@ path = "src/lib.rs"
[dependencies]
bytes = "0.4"
data-encoding = "2.1.0"
failure = "0.1"
futures = "0.1.17"
h2 = "0.1"
http = "0.1"
@ -58,6 +59,7 @@ tokio-tcp = "0.1"
# disables default features, i.e. openssl...
trust-dns-proto = { version = "0.5.0-alpha", path = "../proto", default-features = false }
trust-dns-rustls = { version = "0.4.0-alpha", path = "../rustls", default-features = false }
typed-headers = "0.1"
webpki-roots = { version = "0.15" }
webpki = "^0.18.0"

116
https/src/error.rs Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::{fmt, io};
use failure::{Backtrace, Context, Fail};
use h2;
use http;
use trust_dns_proto::error::{ProtoError, ProtoErrorKind};
use typed_headers;
/// An alias for results returned by functions of this crate
pub type Result<T> = ::std::result::Result<T, Error>;
#[derive(Debug, Fail)]
pub enum ErrorKind {
/// An error with an arbitrary message, referenced as &'static str
#[fail(display = "{}", _0)]
Message(&'static str),
/// An error with an arbitrary message, stored as String
#[fail(display = "{}", _0)]
Msg(String),
#[fail(display = "proto error: {}", _0)]
ProtoError(ProtoError),
#[fail(display = "bad header: {}", _0)]
TypedHeaders(typed_headers::Error),
#[fail(display = "h2: {}", _0)]
H2(h2::Error),
}
/// The error type for errors that get returned in the crate
#[derive(Debug)]
pub struct Error {
inner: Context<ErrorKind>,
}
impl Error {
/// Get the kind of the error
pub fn kind(&self) -> &ErrorKind {
self.inner.get_context()
}
}
impl Fail for Error {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.inner, f)
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error {
inner: Context::new(kind),
}
}
}
impl From<Context<ErrorKind>> for Error {
fn from(inner: Context<ErrorKind>) -> Error {
Error { inner }
}
}
impl From<&'static str> for Error {
fn from(msg: &'static str) -> Error {
ErrorKind::Message(msg).into()
}
}
impl From<String> for Error {
fn from(msg: String) -> Error {
ErrorKind::Msg(msg).into()
}
}
impl From<ProtoError> for Error {
fn from(msg: ProtoError) -> Self {
ErrorKind::ProtoError(msg).into()
}
}
impl From<typed_headers::Error> for Error {
fn from(msg: typed_headers::Error) -> Self {
ErrorKind::TypedHeaders(msg).into()
}
}
impl From<h2::Error> for Error {
fn from(msg: h2::Error) -> Self {
ErrorKind::H2(msg).into()
}
}
impl From<Error> for io::Error {
fn from(err: Error) -> Self {
io::Error::new(io::ErrorKind::Other, format!("https: {}", err))
}
}

View File

@ -23,13 +23,17 @@ use tokio_executor;
use tokio_rustls::ClientConfigExt;
use tokio_rustls::{ConnectAsync, TlsStream as TokioTlsStream};
use tokio_tcp::{ConnectFuture, TcpStream as TokioTcpStream};
use typed_headers::{
mime::Mime, Accept, ContentLength, ContentType, HeaderMapExt, Quality, QualityItem,
};
use webpki::DNSNameRef;
use trust_dns_proto::error::ProtoError;
use trust_dns_proto::xfer::{DnsRequest, DnsRequestSender, DnsResponse, SerialMessage};
use HttpsError;
const ALPN_H2: &str = "h2";
const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
/// A DNS client connection for DNS-over-HTTPS
#[derive(Clone)]
@ -80,7 +84,7 @@ impl DnsRequestSender for HttpsClientStream {
}
fn error_response(error: ProtoError) -> Self::DnsResponseFuture {
HttpsSerialResponse(HttpsSerialResponseInner::Errored(Some(error)))
HttpsSerialResponse(HttpsSerialResponseInner::Errored(Some(error.into())))
}
fn shutdown(&mut self) {
@ -107,8 +111,7 @@ impl Stream for HttpsClientStream {
.map(|readiness| match readiness {
Async::Ready(()) => Async::Ready(Some(())),
Async::NotReady => Async::NotReady,
})
.map_err(|e| ProtoError::from(format!("h2 stream errored: {}", e)))
}).map_err(|e| ProtoError::from(format!("h2 stream errored: {}", e)))
}
}
@ -118,6 +121,7 @@ pub struct HttpsSerialResponse(HttpsSerialResponseInner);
impl Future for HttpsSerialResponse {
type Item = DnsResponse;
// FIXME: make changes to allow this to be a crate specific error type
type Error = ProtoError;
/// This indicates that the HTTP message was successfully sent, and we now have the response.RecvStream
@ -168,7 +172,11 @@ impl Future for HttpsSerialResponse {
/// process.
/// ```
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let serial_message = try_ready!(self.0.poll());
let serial_message = try_ready!(
self.0
.poll()
.map_err(|e| ProtoError::from(format!("https error: {}", e)))
);
let message = serial_message.to_message()?;
Ok(Async::Ready(message.into()))
}
@ -197,12 +205,12 @@ enum HttpsSerialResponseInner {
status_code: StatusCode,
},
Complete(Option<SerialMessage>),
Errored(Option<ProtoError>),
Errored(Option<HttpsError>),
}
impl Future for HttpsSerialResponseInner {
type Item = SerialMessage;
type Error = ProtoError;
type Error = HttpsError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
@ -219,48 +227,15 @@ impl Future for HttpsSerialResponseInner {
Ok(Async::Ready(())) => (),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => {
return Err(ProtoError::from(format!("h2 send_request error: {}", err)))
// TODO: make specific error
return Err(HttpsError::from(format!("h2 send_request error: {}", err)));
}
}
};
// build up the http request
// https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-10#section-5.1
// The URI Template defined in this document is processed without any
// variables when the HTTP method is POST. When the HTTP method is GET
// the single variable "dns" is defined as the content of the DNS
// request (as described in Section 7), encoded with base64url
// [RFC4648].
// let (message, _) = message.unwrap();
// TODO: this is basically the GET version, but it is more expesive than POST
// perhaps add an option if people want better HTTP caching options.
// let query = BASE64URL_NOPAD.encode(&message);
// let url = format!("/dns-query?dns={}", query);
// let request = Request::get(&url)
// .header(header::CONTENT_TYPE, ::ACCEPTS_DNS_BINARY)
// .header(header::HOST, &self.name_server_name as &str)
// .header("authority", &self.name_server_name as &str)
// .header(header::USER_AGENT, USER_AGENT)
// .body(());
let mut parts = uri::Parts::default();
parts.scheme = Some(uri::Scheme::HTTPS);
parts.authority = Some(
uri::Authority::from_str(&name_server_name)
.map_err(|e| ProtoError::from(format!("invalid authority: {}", e)))?,
);
parts.path_and_query = Some(uri::PathAndQuery::from_static("/dns-query"));
let url = Uri::from_parts(parts)
.map_err(|e| ProtoError::from(format!("uri parse error: {}", e)))?;
let request = Request::post(url)
.header(header::CONTENT_TYPE, ::ACCEPTS_DNS_BINARY)
.header(header::ACCEPT, ::ACCEPTS_DNS_BINARY)
.header(header::USER_AGENT, USER_AGENT)
.version(Version::HTTP_2)
.body(());
let bytes = Bytes::from(message.bytes());
let request = ::request::new(&name_server_name, bytes.len());
let request = request
.map_err(|err| ProtoError::from(format!("bad http request: {}", err)))?;
@ -274,7 +249,7 @@ impl Future for HttpsSerialResponseInner {
})?;
send_stream
.send_data(Bytes::from(message.bytes()), true)
.send_data(bytes, true)
.map_err(|e| ProtoError::from(format!("h2 send_data error: {}", e)))?;
HttpsSerialResponseInner::Incoming {
@ -288,36 +263,23 @@ impl Future for HttpsSerialResponseInner {
name_server,
..
} => {
let response_stream = try_ready!(response_future.poll().map_err(|err| {
ProtoError::from(format!("recieved a stream error: {}", err))
}));
let response_stream =
try_ready!(response_future.poll().map_err(|err| ProtoError::from(
format!("recieved a stream error: {}", err)
)));
debug!("got response: {:#?}", response_stream);
// get the length of packet
let content_length: usize = response_stream
let content_length: Option<usize> = response_stream
.headers()
.get(header::CONTENT_LENGTH)
.map_or(Ok(512), |h| {
h.to_str()
.map_err(|err| {
ProtoError::from(format!(
"ContentLength header not a string: {}",
err
))
})
.and_then(|s| {
usize::from_str(s).map_err(|err| {
ProtoError::from(format!(
"ContentLength header not a number: {}",
err
))
})
})
})?;
.typed_get()?
.map(|c: ContentLength| *c as usize);
Receiving {
response_stream,
response_bytes: Bytes::with_capacity(content_length),
content_length: Some(content_length),
response_bytes: Bytes::with_capacity(content_length.unwrap_or(512)),
content_length: content_length,
name_server: *name_server,
}
}
@ -333,12 +295,22 @@ impl Future for HttpsSerialResponseInner {
.poll()
.map_err(|e| ProtoError::from(format!("bad http request: {}", e)))
) {
debug!("got bytes: {}", partial_bytes.len());
response_bytes.extend(partial_bytes);
// assert the length
if let Some(content_length) = content_length {
if response_bytes.len() >= *content_length {
break;
}
}
}
// assert the length
if let Some(content_length) = content_length {
if *content_length != response_bytes.len() {
return Err(ProtoError::from(format!(
if response_bytes.len() != *content_length {
// TODO: make explicit error type
return Err(HttpsError::from(format!(
"expected byte length: {}, got: {}",
content_length,
response_bytes.len()
@ -355,25 +327,26 @@ impl Future for HttpsSerialResponseInner {
} else {
// verify content type
{
// in the case that the ContentType is not specified, we assume it's the standard DNS format
let content_type = response_stream
.headers()
.get(header::CONTENT_TYPE)
.ok_or_else(|| ProtoError::from("ContentLength header missing"))
.and_then(|h| {
.map(|h| {
h.to_str().map_err(|err| {
ProtoError::from(format!(
// TODO: make explicit error type
HttpsError::from(format!(
"ContentType header not a string: {}",
err
))
})
})?;
}).unwrap_or(Ok(::MIME_APPLICATION_DNS))?;
if content_type != ::ACCEPTS_DNS_BINARY {
return Err(ProtoError::from(format!(
"ContentType unsupported (must be 'application/dns-message'): {}",
content_type
),
));
if content_type != ::MIME_APPLICATION_DNS {
return Err(HttpsError::from(format!(
"ContentType unsupported (must be '{}'): '{}'",
::MIME_APPLICATION_DNS,
content_type
)));
}
};
@ -389,7 +362,8 @@ impl Future for HttpsSerialResponseInner {
} => {
let error_string = String::from_utf8_lossy(response_bytes.as_ref());
return Err(ProtoError::from(format!(
// TODO: make explicit error type
return Err(HttpsError::from(format!(
"http unsuccessful code: {}, message: {}",
status_code, error_string
)));
@ -511,6 +485,7 @@ impl Future for HttpsClientConnectState {
loop {
let next = match self {
HttpsClientConnectState::ConnectTcp { name_server, tls } => {
debug!("tcp connecting to: {}", name_server);
let connect = TokioTcpStream::connect(&name_server);
HttpsClientConnectState::TcpConnecting {
connect,
@ -524,6 +499,7 @@ impl Future for HttpsClientConnectState {
tls,
} => {
let tcp = try_ready!(connect.poll());
debug!("tcp connection established to: {}", name_server);
let tls = tls
.take()
.expect("programming error, tls should not be None here");
@ -550,6 +526,7 @@ impl Future for HttpsClientConnectState {
tls,
} => {
let tls = try_ready!(tls.poll());
debug!("tls connection established to: {}", name_server);
let mut handshake = h2::client::Builder::new();
handshake.enable_push(false);
@ -571,6 +548,7 @@ impl Future for HttpsClientConnectState {
.map_err(|e| ProtoError::from(format!("h2 handshake error: {}", e)))
);
debug!("h2 connection established to: {}", name_server);
tokio_executor::spawn(
connection.map_err(|e| warn!("h2 connection failed: {}", e)),
);

146
https/src/https_server.rs Normal file
View File

@ -0,0 +1,146 @@
// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::borrow::Borrow;
use std::fmt::Debug;
use std::sync::Arc;
use bytes::Bytes;
use futures::{Async, Future, Poll, Stream};
use h2::RecvStream;
use http::{Method, Request};
use typed_headers::{ContentLength, HeaderMapExt};
use trust_dns_proto::op::Message;
use {HttpsError, HttpsResult};
// TODO: change RecvStream to Generic over Stream of Bytes
pub fn message_from(this_server_name: Arc<String>, request: Request<RecvStream>) -> HttpsToMessage {
debug!("Received request: {:#?}", request);
let this_server_name: &String = this_server_name.borrow();
match ::request::verify(this_server_name, &request) {
Ok(_) => (),
Err(err) => return HttpsToMessageInner::Error(Some(err)).into(),
}
// attempt to get the content length
let content_length: Option<ContentLength> = match request.headers().typed_get() {
Ok(l) => l,
Err(err) => return HttpsToMessageInner::Error(Some(err.into())).into(),
};
let content_length: Option<usize> = content_length.map(|c| {
let length = *c as usize;
debug!("got message length: {}", length);
length
});
match *request.method() {
Method::GET => HttpsToMessageInner::Error(Some(
format!("GET unimplemented: {}", request.method()).into(),
)).into(),
Method::POST => message_from_post(request, content_length).into(),
_ => HttpsToMessageInner::Error(Some(format!("bad method: {}", request.method()).into()))
.into(),
}
}
pub struct HttpsToMessage(HttpsToMessageInner);
impl From<HttpsToMessageInner> for HttpsToMessage {
fn from(inner: HttpsToMessageInner) -> Self {
HttpsToMessage(inner)
}
}
impl From<MessageFromPost> for HttpsToMessage {
fn from(inner: MessageFromPost) -> Self {
HttpsToMessage(HttpsToMessageInner::FromPost(inner))
}
}
impl Future for HttpsToMessage {
type Item = Bytes;
type Error = HttpsError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.0.poll()
}
}
enum HttpsToMessageInner {
FromPost(MessageFromPost),
Error(Option<HttpsError>),
}
impl Future for HttpsToMessageInner {
type Item = Bytes;
type Error = HttpsError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self {
HttpsToMessageInner::FromPost(from_post) => from_post.poll(),
HttpsToMessageInner::Error(error) => {
Err(error.take().expect("cannot poll after complete"))
}
}
}
}
fn message_from_post(request: Request<RecvStream>, length: Option<usize>) -> MessageFromPost {
let body = request.into_body();
MessageFromPost {
stream: body,
length: length,
}
}
struct MessageFromPost {
stream: RecvStream,
length: Option<usize>,
}
impl Future for MessageFromPost {
type Item = Bytes;
type Error = HttpsError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
let mut bytes = match self.stream.poll() {
Ok(Async::NotReady) => return Ok(Async::NotReady),
Ok(Async::Ready(Some(bytes))) => bytes,
Ok(Async::Ready(None)) => return Err("not all bytes received".into()),
Err(e) => return Err(e.into()),
};
let bytes = if let Some(length) = self.length {
// wait until we have all the bytes
if bytes.len() < length {
continue;
}
// this will trim the bytes back to whatever we didn't consume
bytes.slice_to(length)
} else {
warn!("no content-length, assuming we have all the bytes");
bytes.slice_from(0)
};
//let message = Message::from_vec(&bytes)?;
return Ok(Async::Ready(bytes));
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_from_post() {
panic!("need test")
}
}

View File

@ -16,6 +16,7 @@ extern crate h2;
extern crate http;
#[macro_use]
extern crate log;
extern crate failure;
extern crate rustls;
extern crate tokio_executor;
extern crate tokio_reactor;
@ -23,15 +24,26 @@ extern crate tokio_rustls;
extern crate tokio_tcp;
extern crate trust_dns_proto;
extern crate trust_dns_rustls;
extern crate typed_headers;
extern crate webpki;
extern crate webpki_roots;
const ACCEPTS_DNS_BINARY: &str = "application/dns-message";
const MIME_APPLICATION: &str = "application";
const MIME_DNS_BINARY: &str = "dns-message";
const MIME_APPLICATION_DNS: &str = "application/dns-message";
const DNS_QUERY_PATH: &str = "/dns-query";
const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
//pub mod https_client_connection;
mod error;
mod https_client_stream;
pub mod https_server;
pub mod request;
pub mod response;
//pub mod https_stream;
pub use self::error::{Error as HttpsError, Result as HttpsResult};
//pub use self::https_client_connection::{HttpsClientConnection, HttpsClientConnectionBuilder};
pub use self::https_client_stream::{
HttpsClientConnect, HttpsClientStream, HttpsClientStreamBuilder, HttpsSerialResponse,

152
https/src/request.rs Normal file
View File

@ -0,0 +1,152 @@
// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! HTTP request creation and validation
use std::str::FromStr;
use http::{header, uri, Method, Request, Response, StatusCode, Uri, Version};
use typed_headers::{
mime::Mime, Accept, ContentLength, ContentType, HeaderMapExt, Quality, QualityItem,
};
use trust_dns_proto::error::ProtoError;
use trust_dns_proto::op::Message;
use {HttpsError, HttpsResult};
/// Create a new Reqeust for an http/2 dns-message request
///
/// ```text
/// https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-10#section-5.1
/// The URI Template defined in this document is processed without any
/// variables when the HTTP method is POST. When the HTTP method is GET
/// the single variable "dns" is defined as the content of the DNS
/// request (as described in Section 7), encoded with base64url
/// [RFC4648].
/// ```
pub fn new(name_server_name: &str, message_len: usize) -> HttpsResult<Request<()>> {
// TODO: this is basically the GET version, but it is more expesive than POST
// perhaps add an option if people want better HTTP caching options.
// let query = BASE64URL_NOPAD.encode(&message);
// let url = format!("/dns-query?dns={}", query);
// let request = Request::get(&url)
// .header(header::CONTENT_TYPE, ::MIME_DNS_BINARY)
// .header(header::HOST, &self.name_server_name as &str)
// .header("authority", &self.name_server_name as &str)
// .header(header::USER_AGENT, USER_AGENT)
// .body(());
let mut parts = uri::Parts::default();
parts.path_and_query = Some(uri::PathAndQuery::from_static(::DNS_QUERY_PATH));
parts.scheme = Some(uri::Scheme::HTTPS);
parts.authority = Some(
uri::Authority::from_str(&name_server_name)
.map_err(|e| ProtoError::from(format!("invalid authority: {}", e)))?,
);
let url =
Uri::from_parts(parts).map_err(|e| ProtoError::from(format!("uri parse error: {}", e)))?;
let accepts_dns = Mime::from_str(::MIME_APPLICATION_DNS).unwrap();
let content_type = ContentType(accepts_dns.clone());
let accept = Accept(vec![QualityItem::new(accepts_dns, Quality::from_u16(1000))]);
// TODO: add user agent to TypedHeaders
let mut request = Request::post(url)
.header(header::USER_AGENT, ::USER_AGENT)
.version(Version::HTTP_2)
.body(())
.map_err(|e| ProtoError::from(format!("h2 stream errored: {}", e)))?;
request.headers_mut().typed_insert(&content_type);
request.headers_mut().typed_insert(&accept);
// future proof for when GET is supported
if &Method::POST == request.method() {
request
.headers_mut()
.typed_insert(&ContentLength(message_len as u64));
}
Ok(request)
}
/// Verifies the request is something we know what to deal with
pub fn verify<T>(name_server: &str, request: &Request<T>) -> HttpsResult<()> {
// Verify all HTTP parameters
let uri = request.uri();
// validate path
if uri.path() != ::DNS_QUERY_PATH {
return Err(format!("bad path: {}, expected: {}", uri.path(), ::DNS_QUERY_PATH).into());
}
// we only accept HTTPS
if Some(&uri::Scheme::HTTPS) != uri.scheme_part() {
return Err("must be HTTPS scheme".into());
}
// the authority must match our nameserver name
if let Some(authority) = uri.authority_part() {
if authority.host() != name_server {
return Err("incorrect authority".into());
}
} else {
return Err("no authority in HTTPS request".into());
}
let content_type: Option<ContentType> = request.headers().typed_get()?;
let accept: Option<Accept> = request.headers().typed_get()?;
// TODO: switch to mime::APPLICATION_DNS when that stabilizes
if !content_type
.map(|c| (c.type_() == ::MIME_APPLICATION && c.subtype() == ::MIME_DNS_BINARY))
.unwrap_or(true)
{
return Err("unsupported content type".into());
}
let accept = accept.ok_or_else(|| "Accept is unspecified")?;
// TODO: switch to mime::APPLICATION_DNS when that stabilizes
if !accept
.iter()
.any(|q| (q.item.type_() == ::MIME_APPLICATION && q.item.subtype() == ::MIME_DNS_BINARY))
{
return Err("does not accept content type".into());
}
if request.version() != Version::HTTP_2 {
return Err("only HTTP/2 supported".into());
}
debug!(
"verified request from: {}",
request
.headers()
.get(header::USER_AGENT)
.map(|h| h.to_str().unwrap_or("bad user agent"))
.unwrap_or("unknown user agent")
);
return Ok(());
}
#[cfg(test)]
mod tests {
use trust_dns_proto::op::*;
use super::*;
#[test]
fn test_new_verify() {
let request = new("ns.example.com").expect("error converting to http");
let decoded_msg = verify("ns.example.com", &request);
}
}

64
https/src/response.rs Normal file
View File

@ -0,0 +1,64 @@
// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! HTTP request creation and validation
use std::str::FromStr;
use http::{header, uri, Response, StatusCode, Uri, Version};
use typed_headers::{
mime::Mime, Accept, ContentLength, ContentType, HeaderMapExt, Quality, QualityItem,
};
use trust_dns_proto::error::ProtoError;
use trust_dns_proto::op::Message;
use {HttpsError, HttpsResult};
/// Create a new Response for an http/2 dns-message request
///
/// ```text
/// 4.2.1. Handling DNS and HTTP Errors
///
/// DNS response codes indicate either success or failure for the DNS
/// query. A successful HTTP response with a 2xx status code ([RFC7231]
/// Section 6.3) is used for any valid DNS response, regardless of the
/// DNS response code. For example, a successful 2xx HTTP status code is
/// used even with a DNS message whose DNS response code indicates
/// failure, such as SERVFAIL or NXDOMAIN.
///
/// HTTP responses with non-successful HTTP status codes do not contain
/// replies to the original DNS question in the HTTP request. DoH
///
/// clients need to use the same semantic processing of non-successful
/// HTTP status codes as other HTTP clients. This might mean that the
/// DoH client retries the query with the same DoH server, such as if
/// there are authorization failures (HTTP status code 401 [RFC7235]
/// Section 3.1). It could also mean that the DoH client retries with a
/// different DoH server, such as for unsupported media types (HTTP
/// status code 415, [RFC7231] Section 6.5.13), or where the server
/// cannot generate a representation suitable for the client (HTTP status
/// code 406, [RFC7231] Section 6.5.6), and so on.
/// ```
pub fn new(message_len: usize) -> HttpsResult<Response<()>> {
let mut response = Response::builder();
response.status(StatusCode::OK);
response.version(Version::HTTP_2);
let mut response = response
.body(())
.map_err(|e| ProtoError::from(format!("invalid response: {}", e)))?;
let accepts_dns = Mime::from_str(::MIME_APPLICATION_DNS).unwrap();
let content_type = ContentType(accepts_dns.clone());
response.headers_mut().typed_insert(&content_type);
response
.headers_mut()
.typed_insert(&ContentLength(message_len as u64));
Ok(response)
}

View File

@ -53,7 +53,8 @@ dnssec = []
# enables experimental the mDNS (multicast) feature
mdns = ["trust-dns/mdns", "trust-dns-proto/mdns", "trust-dns-resolver/mdns"]
dns-over-https = ["trust-dns/dns-over-https", "trust-dns-resolver/dns-over-https", "webpki-roots"]
dns-over-https-rustls = ["trust-dns/dns-over-https-rustls", "trust-dns-resolver/dns-over-https-rustls", "dns-over-https", "webpki-roots"]
dns-over-https = []
# TODO: need to make server support rustls and native-tls
# dns-over-native-tls = ["dns-over-tls", "trust-dns-resolver/dns-over-native-tls", "trust-dns-server/dns-over-native-tls"]

View File

@ -3,7 +3,7 @@ extern crate env_logger;
extern crate futures;
extern crate log;
extern crate openssl;
#[cfg(feature = "dns-over-https")]
#[cfg(feature = "dns-over-https-rustls")]
extern crate rustls;
extern crate tokio;
extern crate trust_dns;
@ -12,7 +12,7 @@ extern crate trust_dns_https;
extern crate trust_dns_integration;
extern crate trust_dns_proto;
extern crate trust_dns_server;
#[cfg(feature = "dns-over-https")]
#[cfg(feature = "dns-over-https-rustls")]
extern crate webpki_roots;
use std::net::*;
@ -119,7 +119,7 @@ fn test_query_tcp_ipv6() {
}
#[test]
#[cfg(feature = "dns-over-https")]
#[cfg(feature = "dns-over-https-rustls")]
fn test_query_https() {
use rustls::{ClientConfig, ProtocolVersion, RootCertStore};
use trust_dns_https::HttpsClientStreamBuilder;

View File

@ -10,8 +10,9 @@ use std::io;
use std::io::Read;
use std::path::Path;
use openssl::dh::Dh;
use openssl::pkcs12::*;
use openssl::ssl::{SslAcceptor, SslMethod, SslOptions};
use openssl::ssl::{self, SslAcceptor, SslMethod, SslMode, SslOptions, SslVerifyMode};
pub use openssl::pkcs12::ParsedPkcs12;
pub use tokio_openssl::SslAcceptorExt;
@ -40,6 +41,14 @@ pub fn new_acceptor(pkcs12: &ParsedPkcs12) -> io::Result<SslAcceptor> {
builder.set_private_key(&pkcs12.pkey)?;
builder.set_certificate(&pkcs12.cert)?;
builder.set_verify(SslVerifyMode::NONE);
builder.set_options(
SslOptions::NO_COMPRESSION
| SslOptions::NO_SSLV2
| SslOptions::NO_SSLV3
| SslOptions::NO_TLSV1
| SslOptions::NO_TLSV1_1,
);
if let Some(ref chain) = pkcs12.chain {
for cert in chain {
@ -47,15 +56,8 @@ pub fn new_acceptor(pkcs12: &ParsedPkcs12) -> io::Result<SslAcceptor> {
}
}
// mut block
{
let ssl_context_bldr = &mut builder;
ssl_context_bldr.set_options(
SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_TLSV1
| SslOptions::NO_TLSV1_1,
);
}
// validate our certificate and private key match
builder.check_private_key()?;
Ok(builder.build())
}

View File

@ -119,7 +119,7 @@ where
// if there is no peer, this connection should die...
let (dns_request, serial_response): (DnsRequest, _) = dns_request.unwrap();
debug!("sending message via: {}", self.io_stream);
info!("sending message via: {}", self.io_stream);
match serial_response.send_response(self.io_stream.send_message(dns_request)) {
Ok(()) => (),
@ -230,7 +230,7 @@ where
}
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(error) => {
debug!("stream errored while connecting: {}", error);
debug!("stream errored while connecting: {:?}", error);
next = DnsExchangeConnectInner::FailAll {
error,
outbound_messages: outbound_messages

View File

@ -42,7 +42,8 @@ dns-over-rustls = ["dns-over-tls", "rustls", "trust-dns-rustls", "webpki-roots"]
dns-over-tls = []
# This requires some TLS library, currently only rustls is supported
dns-over-https = ["trust-dns-https", "dns-over-rustls"]
dns-over-https-rustls = ["trust-dns-https", "dns-over-rustls", "dns-over-https"]
dns-over-https = []
dnssec-openssl = ["dnssec", "trust-dns-proto/dnssec-openssl"]
dnssec-ring = ["dnssec", "trust-dns-proto/dnssec-ring"]

View File

@ -26,6 +26,7 @@ extern crate trust_dns_proto;
extern crate webpki;
pub mod tls_client_stream;
pub mod tls_server;
pub mod tls_stream;
pub use self::tls_client_stream::{TlsClientStream, TlsClientStreamBuilder};

58
rustls/src/tls_server.rs Normal file
View File

@ -0,0 +1,58 @@
// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;
use rustls::{self, Certificate, PrivateKey, ProtocolVersion, ServerConfig};
use trust_dns_proto::error::ProtoResult;
/// Read the certificate from the specified path.
///
/// If the password is specified, then it will be used to decode the Certificate
pub fn read_cert(
cert_path: &Path,
private_key_path: &Path,
) -> ProtoResult<(Certificate, PrivateKey)> {
let mut cert_file = File::open(&cert_path)
.map_err(|e| format!("error opening cert file: {:?}: {}", cert_path, e))?;
let mut cert_bytes = vec![];
cert_file
.read_to_end(&mut cert_bytes)
.map_err(|e| format!("could not read cert from: {:?}: {}", cert_path, e))?;
drop(cert_file);
let mut key_file = File::open(&private_key_path).map_err(|e| {
format!(
"error opening private_key file: {:?}: {}",
private_key_path, e
)
})?;
let mut key_bytes = vec![];
key_file.read_to_end(&mut key_bytes).map_err(|e| {
format!(
"could not read private_key from: {:?}: {}",
private_key_path, e
)
})?;
Ok((Certificate(cert_bytes), PrivateKey(key_bytes)))
}
/// Construct the new Acceptor with the associated pkcs12 data
pub fn new_acceptor(cert: Certificate, key: PrivateKey) -> Result<ServerConfig, rustls::TLSError> {
let mut config = ServerConfig::new(rustls::NoClientAuth::new());
config.set_protocols(&["h2".to_string()]);
config.set_single_cert(vec![cert], key)?;
Ok(config)
}

View File

@ -6,4 +6,6 @@ trust_dns_dir=$(dirname $0)/..
cd ${trust_dns_dir:?}
# Build all tests
cargo test --manifest-path resolver/Cargo.toml --features dns-over-rustls
cargo test --manifest-path resolver/Cargo.toml --features dns-over-rustls,dns-over-https-rustls
cargo test --manifest-path server/Cargo.toml --features dns-over-https-rustls,dns-over-rustls
cargo test --manifest-path integration-tests/Cargo.toml --features dns-over-https-rustls

View File

@ -47,17 +47,18 @@ dnssec-ring = ["dnssec", "trust-dns/dnssec-ring", "trust-dns-proto/dnssec-ring"]
dnssec = []
# TODO: Need to figure out how to be consistent with ring/openssl usage...
dns-over-https = ["dns-over-openssl", "trust-dns/dns-over-https"]
# dns-over-https-openssl = ["dns-over-openssl", "trust-dns/dns-over-https-openssl", "dns-over-https"]
dns-over-https-rustls = ["dns-over-rustls", "trust-dns/dns-over-https-rustls", "dns-over-https"]
dns-over-https = ["h2", "http"]
# TODO: migrate all tls and tls-openssl features to dns-over-tls, et al
dns-over-openssl = ["dns-over-tls", "trust-dns-openssl", "tls"]
dns-over-openssl = ["dns-over-tls", "trust-dns-openssl"]
dns-over-rustls = ["dns-over-tls", "trust-dns-rustls", "rustls"]
dns-over-tls = []
# This is a deprecated feature...
tls-openssl = ["dns-over-openssl"]
# TODO: not yet supported on the server side
# tls-ring = ["tls", "trust-dns-rustls"]
tls = []
tls = ["dns-over-openssl"]
# WARNING: there is a bug in the mutual tls auth code at the moment see issue #100
# mtls = ["trust-dns/mtls"]
@ -72,21 +73,27 @@ path = "src/named.rs"
[dependencies]
backtrace = "^0.3"
bytes = "0.4.9"
chrono = "^0.4"
clap = "^2.27"
env_logger = "^0.5"
failure = "0.1"
futures = "^0.1.17"
h2 = { version = "0.1", optional = true }
http = { version = "0.1", optional = true }
lazy_static = "^1.0"
log = "^0.4.1"
rand = "^0.5"
rusqlite = { version = "0.14.0", features = ["bundled"] }
rustls = { version = "0.13", optional = true }
serde = "^1.0"
serde_derive = "^1.0"
time = "^0.1"
tokio = "^0.1.6"
tokio-executor = "^0.1"
tokio-io = "^0.1"
tokio-reactor = "^0.1"
tokio-rustls = "^0.7"
tokio-tcp = "^0.1"
tokio-timer = "^0.2"
tokio-udp = "^0.1"
@ -95,9 +102,9 @@ trust-dns = { version = "0.15.0-alpha", path = "../client" }
trust-dns-https = { version = "0.1.0-alpha", path = "../https" }
trust-dns-proto = { version = "0.5.0-alpha", path = "../proto" }
trust-dns-openssl = { version = "0.4.0-alpha", path = "../openssl", optional = true }
trust-dns-rustls = { version = "0.4.0-alpha", path = "../rustls", optional = true }
[dev-dependencies]
native-tls = "^0.1"
trust-dns-native-tls = { version = "0.4.0-alpha", path = "../native-tls" }
tokio-tls = "^0.1"
rustls = { version = "0.13" }
tokio-tls = "^0.1"

View File

@ -59,7 +59,7 @@ fn send_response<R: ResponseHandler + 'static>(
response.set_edns(resp_edns);
}
response_handle.send(response)
response_handle.send_response(response)
}
impl RequestHandler for Catalog {
@ -104,7 +104,7 @@ impl RequestHandler for Catalog {
response.edns(resp_edns);
// TODO: should ResponseHandle consume self?
return response_handle.send(response.build(response_header));
return response_handle.send_response(response.build(response_header));
}
response_edns = Some(resp_edns);
@ -125,7 +125,7 @@ impl RequestHandler for Catalog {
c @ _ => {
error!("unimplemented op_code: {:?}", c);
let response = MessageResponseBuilder::new(Some(request_message.raw_queries()));
return response_handle.send(response.error_msg(
return response_handle.send_response(response.error_msg(
request_message.id(),
request_message.op_code(),
ResponseCode::NotImp,
@ -139,7 +139,7 @@ impl RequestHandler for Catalog {
);
let response = MessageResponseBuilder::new(Some(request_message.raw_queries()));
return response_handle.send(response.error_msg(
return response_handle.send_response(response.error_msg(
request_message.id(),
request_message.op_code(),
ResponseCode::FormErr,
@ -345,20 +345,20 @@ impl Catalog {
response_header.set_op_code(OpCode::Query);
response_header.set_message_type(MessageType::Response);
let (is_dnssec, supported_algorithms) = request.edns().map_or(
(false, SupportedAlgorithms::new()),
|edns| {
let supported_algorithms =
if let Some(&EdnsOption::DAU(algs)) = edns.option(&EdnsCode::DAU) {
algs
} else {
debug!("no DAU in request, used default SupportAlgorithms");
Default::default()
};
let (is_dnssec, supported_algorithms) =
request
.edns()
.map_or((false, SupportedAlgorithms::new()), |edns| {
let supported_algorithms =
if let Some(&EdnsOption::DAU(algs)) = edns.option(&EdnsCode::DAU) {
algs
} else {
debug!("no DAU in request, used default SupportAlgorithms");
Default::default()
};
(edns.dnssec_ok(), supported_algorithms)
},
);
(edns.dnssec_ok(), supported_algorithms)
});
// log algorithms being requested
if is_dnssec {
@ -440,14 +440,13 @@ impl Catalog {
/// Recursively searches the catalog for a matching authority
pub fn find(&self, name: &LowerName) -> Option<&RwLock<Authority>> {
self.authorities.get(name)
.or_else(|| {
let name = name.base_name();
if !name.is_root() {
self.find(&name)
} else {
None
}
})
self.authorities.get(name).or_else(|| {
let name = name.base_name();
if !name.is_root() {
self.find(&name)
} else {
None
}
})
}
}

View File

@ -28,9 +28,9 @@ use toml;
#[cfg(feature = "dnssec")]
use trust_dns::error::*;
use trust_dns::rr::Name;
#[cfg(feature = "dnssec")]
use trust_dns::rr::dnssec::{Algorithm, KeyFormat};
use trust_dns::rr::Name;
use trust_dns_proto::error::ProtoResult;
use authority::ZoneType;
@ -39,6 +39,7 @@ use error::{ConfigError, ConfigResult};
static DEFAULT_PATH: &'static str = "/var/named"; // TODO what about windows (do I care? ;)
static DEFAULT_PORT: u16 = 53;
static DEFAULT_TLS_PORT: u16 = 853;
static DEFAULT_HTTPS_PORT: u16 = 443;
static DEFAULT_TCP_REQUEST_TIMEOUT: u64 = 5;
/// Server configuration
@ -54,6 +55,8 @@ pub struct Config {
listen_port: Option<u16>,
/// Secure port to listen on
tls_listen_port: Option<u16>,
/// HTTPS port to listen on
https_listen_port: Option<u16>,
/// Timeout associated to a request before it is closed.
tcp_request_timeout: Option<u64>,
/// Level at which to log, default is INFO
@ -63,7 +66,7 @@ pub struct Config {
/// List of configurations for zones
#[serde(default)]
zones: Vec<ZoneConfig>,
/// Certificate to associate to TLS connections
/// Certificate to associate to TLS connections (currently the same is used for HTTPS and TLS)
tls_cert: Option<TlsCertConfig>,
}
@ -83,6 +86,7 @@ impl Config {
.map(|s| s.parse().unwrap())
.collect()
}
/// set of listening ipv6 addresses (for TCP and UDP)
pub fn get_listen_addrs_ipv6(&self) -> Vec<Ipv6Addr> {
self.listen_addrs_ipv6
@ -90,14 +94,22 @@ impl Config {
.map(|s| s.parse().unwrap())
.collect()
}
/// port on which to listen for connections on specified addresses
pub fn get_listen_port(&self) -> u16 {
self.listen_port.unwrap_or(DEFAULT_PORT)
}
/// port on which to listen for TLS connections
pub fn get_tls_listen_port(&self) -> u16 {
self.tls_listen_port.unwrap_or(DEFAULT_TLS_PORT)
}
/// port on which to listen for HTTPS connections
pub fn get_https_listen_port(&self) -> u16 {
self.https_listen_port.unwrap_or(DEFAULT_HTTPS_PORT)
}
/// default timeout for all TCP connections before forceably shutdown
pub fn get_tcp_request_timeout(&self) -> Duration {
Duration::from_secs(
@ -106,7 +118,6 @@ impl Config {
)
}
// TODO: also support env_logger
/// specify the log level which should be used, ["Trace", "Debug", "Info", "Warn", "Error"]
pub fn get_log_level(&self) -> log::Level {
if let Some(ref level_str) = self.log_level {
@ -122,16 +133,19 @@ impl Config {
log::Level::Info
}
}
/// the path for all zone configurations, defaults to `/var/named`
pub fn get_directory(&self) -> &Path {
self.directory
.as_ref()
.map_or(Path::new(DEFAULT_PATH), |s| Path::new(s))
}
/// the set of zones which should be loaded
pub fn get_zones(&self) -> &[ZoneConfig] {
&self.zones
}
/// the tls certificate to use for accepting tls connections
pub fn get_tls_cert(&self) -> Option<&TlsCertConfig> {
self.tls_cert.as_ref()

View File

@ -27,6 +27,7 @@
//! * Secure dynamic update
//! * New features for securing public information
extern crate bytes;
extern crate chrono;
extern crate env_logger;
#[macro_use]
@ -38,19 +39,31 @@ extern crate rusqlite;
extern crate serde;
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "dns-over-https")]
extern crate h2;
#[cfg(feature = "dns-over-https")]
extern crate http;
#[cfg(feature = "dns-over-rustls")]
extern crate rustls;
extern crate time;
extern crate tokio;
extern crate tokio_executor;
extern crate tokio_io;
extern crate tokio_reactor;
#[cfg(feature = "dns-over-rustls")]
extern crate tokio_rustls;
extern crate tokio_tcp;
extern crate tokio_timer;
extern crate tokio_udp;
extern crate toml;
extern crate trust_dns;
extern crate trust_dns_proto;
#[cfg(feature = "tls")]
#[cfg(feature = "dns-over-https")]
extern crate trust_dns_https;
#[cfg(feature = "dns-over-openssl")]
extern crate trust_dns_openssl;
extern crate trust_dns_proto;
#[cfg(feature = "dns-over-rustls")]
extern crate trust_dns_rustls;
pub mod authority;
pub mod config;

View File

@ -8,15 +8,21 @@
//! Default logger configuration for the project
use std::env;
use std::io::{self, Write};
use std::fmt::Display;
use std::io::{self, Write};
use chrono::Utc;
use env_logger;
use env_logger::fmt::Formatter;
use log;
fn format<L, M, LN, A>(fmt: &mut Formatter, level: L, module: M, line: LN, args: A) -> io::Result<()>
fn format<L, M, LN, A>(
fmt: &mut Formatter,
level: L,
module: M,
line: LN,
args: A,
) -> io::Result<()>
where
L: Display,
M: Display,

View File

@ -37,43 +37,48 @@ extern crate clap;
extern crate futures;
#[macro_use]
extern crate log;
#[cfg(feature = "dns-over-rustls")]
extern crate rustls;
extern crate tokio;
extern crate tokio_tcp;
extern crate tokio_udp;
extern crate trust_dns;
#[cfg(feature = "dns-over-openssl")]
extern crate trust_dns_openssl;
#[cfg(feature = "dns-over-rustls")]
extern crate trust_dns_rustls;
extern crate trust_dns_server;
#[cfg(feature = "tls")]
extern crate trust_dns_openssl;
use std::fs::File;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{self, Read};
use std::net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs};
use std::path::{Path, PathBuf};
use std::io::{self, Read};
#[cfg(feature = "dnssec")]
use chrono::Duration;
use clap::{Arg, ArgMatches};
use futures::{Future, future};
use futures::{future, Future};
#[cfg(feature = "dns-over-rustls")]
use rustls::{Certificate, PrivateKey};
use tokio::runtime::current_thread::Runtime;
use tokio_tcp::TcpListener;
use tokio_udp::UdpSocket;
use trust_dns::error::ParseResult;
use trust_dns::serialize::txt::{Lexer, Parser};
use trust_dns::rr::Name;
#[cfg(feature = "dnssec")]
use trust_dns::rr::dnssec::{KeyPair, Signer, Private};
use trust_dns::rr::dnssec::{KeyPair, Private, Signer};
use trust_dns::rr::Name;
use trust_dns::serialize::txt::{Lexer, Parser};
#[cfg(feature = "dns-over-openssl")]
use trust_dns_openssl::tls_server::*;
use trust_dns_server::authority::{Authority, Catalog, Journal, ZoneType};
use trust_dns_server::config::{Config, TlsCertConfig, ZoneConfig};
use trust_dns_server::logger;
#[cfg(feature = "dnssec")]
use trust_dns_server::config::KeyConfig;
use trust_dns_server::config::{Config, TlsCertConfig, ZoneConfig};
use trust_dns_server::logger;
use trust_dns_server::server::ServerFuture;
#[cfg(feature = "tls")]
use trust_dns_openssl::tls_server::*;
fn parse_zone_file(
file: File,
@ -111,9 +116,8 @@ fn load_zone(zone_dir: &Path, zone_config: &ZoneConfig) -> Result<Authority, Str
// load the zone
let mut authority = if zone_config.is_update_allowed() && journal_path.exists() {
info!("recovering zone from journal: {:?}", journal_path);
let journal = Journal::from_file(&journal_path).map_err(|e| {
format!("error opening journal: {:?}: {}", journal_path, e)
})?;
let journal = Journal::from_file(&journal_path)
.map_err(|e| format!("error opening journal: {:?}: {}", journal_path, e))?;
let mut authority = Authority::new(
zone_name.clone(),
@ -133,9 +137,8 @@ fn load_zone(zone_dir: &Path, zone_config: &ZoneConfig) -> Result<Authority, Str
} else if zone_path.exists() {
info!("loading zone file: {:?}", zone_path);
let zone_file = File::open(&zone_path).map_err(|e| {
format!("error opening zone file: {:?}: {}", zone_path, e)
})?;
let zone_file = File::open(&zone_path)
.map_err(|e| format!("error opening zone file: {:?}: {}", zone_path, e))?;
let mut authority = parse_zone_file(
zone_file,
@ -148,16 +151,15 @@ fn load_zone(zone_dir: &Path, zone_config: &ZoneConfig) -> Result<Authority, Str
// if dynamic update is enabled, enable the journal
if zone_config.is_update_allowed() {
info!("enabling journal: {:?}", journal_path);
let journal = Journal::from_file(&journal_path).map_err(|e| {
format!("error creating journal {:?}: {}", journal_path, e)
})?;
let journal = Journal::from_file(&journal_path)
.map_err(|e| format!("error creating journal {:?}: {}", journal_path, e))?;
authority.set_journal(journal);
// preserve to the new journal, i.e. we just loaded the zone from disk, start the journal
authority.persist_to_journal().map_err(|e| {
format!("error persisting to journal {:?}: {}", journal_path, e)
})?;
authority
.persist_to_journal()
.map_err(|e| format!("error persisting to journal {:?}: {}", journal_path, e))?;
}
info!("zone file loaded: {}", zone_name);
@ -241,14 +243,12 @@ fn load_key(zone_name: Name, key_config: &KeyConfig) -> Result<Signer, String> {
let key: KeyPair<Private> = {
info!("reading key: {:?}", key_path);
let mut file = File::open(&key_path).map_err(|e| {
format!("error opening private key file: {:?}: {}", key_path, e)
})?;
let mut file = File::open(&key_path)
.map_err(|e| format!("error opening private key file: {:?}: {}", key_path, e))?;
let mut key_bytes = Vec::with_capacity(256);
file.read_to_end(&mut key_bytes).map_err(|e| {
format!("could not read key from: {:?}: {}", key_path, e)
})?;
file.read_to_end(&mut key_bytes)
.map_err(|e| format!("could not read key from: {:?}: {}", key_path, e))?;
format
.decode_key(&key_bytes, key_config.password(), algorithm)
@ -262,7 +262,8 @@ fn load_key(zone_name: Name, key_config: &KeyConfig) -> Result<Signer, String> {
// add the key to the zone
// TODO: allow the duration of signatutes to be customized
let dnskey = key.to_dnskey(algorithm)
let dnskey = key
.to_dnskey(algorithm)
.map_err(|e| format!("error converting to dnskey: {}", e))?;
Ok(Signer::dnssec(
dnskey.clone(),
@ -272,7 +273,10 @@ fn load_key(zone_name: Name, key_config: &KeyConfig) -> Result<Signer, String> {
))
}
#[cfg(feature = "tls")]
#[cfg(all(
feature = "dns-over-openssl",
not(feature = "dns-over-rustls")
))]
fn load_cert(zone_dir: &Path, tls_cert_config: &TlsCertConfig) -> Result<ParsedPkcs12, String> {
let path = zone_dir.to_owned().join(tls_cert_config.get_path());
let password = tls_cert_config.get_password();
@ -281,6 +285,21 @@ fn load_cert(zone_dir: &Path, tls_cert_config: &TlsCertConfig) -> Result<ParsedP
read_cert(&path, password)
}
#[cfg(feature = "dns-over-rustls")]
fn load_cert(
zone_dir: &Path,
tls_cert_config: &TlsCertConfig,
) -> Result<(Certificate, PrivateKey), String> {
use trust_dns_rustls::tls_server::read_cert;
// FIXME: this can't be hard-coded...
let cert_path = "/Users/benjaminfry/Development/rust/trust-dns/server/tests/named_test_configs/sec/example.cert";
let private_key_path = "/Users/benjaminfry/Development/rust/trust-dns/server/tests/named_test_configs/sec/example.key";
read_cert(Path::new(cert_path), Path::new(private_key_path))
.map_err(|e| format!("error reading cert: {}", e))
}
// argument name constants for the CLI options
const QUIET_ARG: &str = "quiet";
const DEBUG_ARG: &str = "debug";
@ -288,6 +307,7 @@ const CONFIG_ARG: &str = "config";
const ZONEDIR_ARG: &str = "zonedir";
const PORT_ARG: &str = "port";
const TLS_PORT_ARG: &str = "tls-port";
const HTTPS_PORT_ARG: &str = "https-port";
/// Args struct for all options
struct Args {
@ -297,6 +317,7 @@ struct Args {
pub flag_zonedir: Option<String>,
pub flag_port: Option<u16>,
pub flag_tls_port: Option<u16>,
pub flag_https_port: Option<u16>,
}
impl<'a> From<ArgMatches<'a>> for Args {
@ -312,9 +333,12 @@ impl<'a> From<ArgMatches<'a>> for Args {
flag_port: matches
.value_of(PORT_ARG)
.map(|s| u16::from_str_radix(s, 10).expect("bad port argument")),
flag_tls_port: matches.value_of(TLS_PORT_ARG).map(|s| {
u16::from_str_radix(s, 10).expect("bad tls-port argument")
}),
flag_tls_port: matches
.value_of(TLS_PORT_ARG)
.map(|s| u16::from_str_radix(s, 10).expect("bad tls-port argument")),
flag_https_port: matches
.value_of(HTTPS_PORT_ARG)
.map(|s| u16::from_str_radix(s, 10).expect("bad https-port argument")),
}
}
}
@ -330,47 +354,46 @@ pub fn main() {
.short("q")
.help("Disable INFO messages, WARN and ERROR will remain")
.conflicts_with(DEBUG_ARG),
)
.arg(
).arg(
Arg::with_name(DEBUG_ARG)
.long(DEBUG_ARG)
.short("d")
.help("Turn on DEBUG messages (default is only INFO)")
.conflicts_with(QUIET_ARG),
)
.arg(
).arg(
Arg::with_name(CONFIG_ARG)
.long(CONFIG_ARG)
.short("c")
.help("Path to configuration file")
.value_name("FILE")
.default_value("/etc/named.toml"),
)
.arg(
).arg(
Arg::with_name(ZONEDIR_ARG)
.long(ZONEDIR_ARG)
.short("z")
.help("Path to the root directory for all zone files, see also config toml")
.value_name("DIR"),
)
.arg(
).arg(
Arg::with_name(PORT_ARG)
.long(PORT_ARG)
.short("p")
.help("Listening port for DNS queries, overrides any value in config file")
.value_name(PORT_ARG),
)
.arg(
).arg(
Arg::with_name(TLS_PORT_ARG)
.long(TLS_PORT_ARG)
.help("Listening port for DNS over TLS queries, overrides any value in config file")
.value_name(TLS_PORT_ARG),
)
.get_matches();
).arg(
Arg::with_name(HTTPS_PORT_ARG)
.long(HTTPS_PORT_ARG)
.help(
"Listening port for DNS over HTTPS queries, overrides any value in config file",
).value_name(HTTPS_PORT_ARG),
).get_matches();
let args: Args = args.into();
// FIXME: add env_logger support
// TODO: this should be set after loading config, but it's necessary for initial log lines, no?
if args.flag_quiet {
logger::quiet();
@ -398,7 +421,8 @@ pub fn main() {
let mut catalog: Catalog = Catalog::new();
// configure our server based on the config_path
for zone in config.get_zones() {
let zone_name = zone.get_zone()
let zone_name = zone
.get_zone()
.expect(&format!("bad zone name in {:?}", config_path));
match load_zone(zone_dir, zone) {
@ -428,15 +452,11 @@ pub fn main() {
.collect();
let udp_sockets: Vec<UdpSocket> = sockaddrs
.iter()
.map(|x| {
UdpSocket::bind(x).expect(&format!("could not bind to udp: {}", x))
})
.map(|x| UdpSocket::bind(x).expect(&format!("could not bind to udp: {}", x)))
.collect();
let tcp_listeners: Vec<TcpListener> = sockaddrs
.iter()
.map(|x| {
TcpListener::bind(x).expect(&format!("could not bind to tcp: {}", x))
})
.map(|x| TcpListener::bind(x).expect(&format!("could not bind to tcp: {}", x)))
.collect();
let mut io_loop = Runtime::new().expect("error when creating tokio Runtime");
@ -444,59 +464,73 @@ pub fn main() {
// now, run the server, based on the config
let mut server = ServerFuture::new(catalog);
let server_future : Box<Future<Item=(), Error=()> + Send> = Box::new(future::lazy(move ||{
// load all the listeners
for udp_socket in udp_sockets {
info!("listening for UDP on {:?}", udp_socket);
server.register_socket(udp_socket);
}
let server_future: Box<Future<Item = (), Error = ()> + Send> =
Box::new(future::lazy(move || {
// load all the listeners
for udp_socket in udp_sockets {
info!("listening for UDP on {:?}", udp_socket);
server.register_socket(udp_socket);
}
// and TCP as necessary
for tcp_listener in tcp_listeners {
info!("listening for TCP on {:?}", tcp_listener);
server
.register_listener(tcp_listener, tcp_request_timeout)
.expect("could not register TCP listener");
}
// and TCP as necessary
for tcp_listener in tcp_listeners {
info!("listening for TCP on {:?}", tcp_listener);
server
.register_listener(tcp_listener, tcp_request_timeout)
.expect("could not register TCP listener");
}
let tls_cert_config = config.get_tls_cert().clone();
let tls_cert_config = config.get_tls_cert().clone();
// and TLS as necessary
if let Some(tls_cert_config) = tls_cert_config {
config_tls(
&args,
&mut server,
&config,
tls_cert_config,
zone_dir,
&listen_addrs,
);
}
// and TLS as necessary
// TODO: we should add some more control from configs to enable/disable TLS/HTTPS
if let Some(tls_cert_config) = tls_cert_config {
// setup TLS listeners
config_tls(
&args,
&mut server,
&config,
tls_cert_config.clone(),
zone_dir,
&listen_addrs,
);
// config complete, starting!
banner();
info!("awaiting connections...");
// setup HTTPS listeners
config_https(
&args,
&mut server,
&config,
tls_cert_config.clone(),
zone_dir,
&listen_addrs,
);
}
/// TODO: how to do threads? should we do a bunch of listener threads and then query threads?
/// Ideally the processing would be n-threads for recieving, which hand off to m-threads for
/// request handling. It would generally be the case that n <= m.
info!("Server starting up");
future::empty()
}));
// config complete, starting!
banner();
info!("awaiting connections...");
if let Err(e) = io_loop.block_on(server_future.map_err(|_| io::Error::new(
io::ErrorKind::Interrupted,
"Server stopping due to interruption",
))) {
/// TODO: how to do threads? should we do a bunch of listener threads and then query threads?
/// Ideally the processing would be n-threads for recieving, which hand off to m-threads for
/// request handling. It would generally be the case that n <= m.
info!("Server starting up");
future::empty()
}));
if let Err(e) = io_loop.block_on(server_future.map_err(|_| {
io::Error::new(
io::ErrorKind::Interrupted,
"Server stopping due to interruption",
)
})) {
error!("failed to listen: {}", e);
}
// we're exiting for some reason...
info!("Trust-DNS {} stopping", trust_dns::version());
}
#[cfg(not(feature = "tls"))]
#[cfg(not(feature = "dns-over-tls"))]
fn config_tls(
_args: &Args,
_server: &mut ServerFuture<Catalog>,
@ -508,7 +542,7 @@ fn config_tls(
panic!("TLS not enabled");
}
#[cfg(feature = "tls")]
#[cfg(feature = "dns-over-tls")]
fn config_tls(
args: &Args,
server: &mut ServerFuture<Catalog>,
@ -517,23 +551,75 @@ fn config_tls(
zone_dir: &Path,
listen_addrs: &[IpAddr],
) {
let tls_listen_port: u16 = args.flag_tls_port
.unwrap_or_else(|| config.get_tls_listen_port());
let tls_sockaddrs: Vec<SocketAddr> = listen_addrs
// FIXME: uncomment this...
// let tls_listen_port: u16 = args
// .flag_tls_port
// .unwrap_or_else(|| config.get_tls_listen_port());
// let tls_sockaddrs: Vec<SocketAddr> = listen_addrs
// .iter()
// .flat_map(|x| (*x, tls_listen_port).to_socket_addrs().unwrap())
// .collect();
// let tls_listeners: Vec<TcpListener> = tls_sockaddrs
// .iter()
// .map(|x| TcpListener::bind(x).expect(&format!("could not bind to tls: {}", x)))
// .collect();
// if tls_listeners.is_empty() {
// warn!("a tls certificate was specified, but no TLS addresses configured to listen on");
// }
// for tls_listener in tls_listeners {
// info!(
// "loading cert for DNS over TLS: {:?}",
// tls_cert_config.get_path()
// );
// // TODO: see about modifying native_tls to impl Clone for Pkcs12
// let tls_cert =
// load_cert(zone_dir, tls_cert_config).expect("error loading tls certificate file");
// info!("listening for TLS on {:?}", tls_listener);
// server
// .register_tls_listener(tls_listener, config.get_tcp_request_timeout(), tls_cert)
// .expect("could not register TLS listener");
// }
}
#[cfg(not(feature = "dns-over-https"))]
fn config_https(
_args: &Args,
_server: &mut ServerFuture<Catalog>,
_config: &Config,
_tls_cert_config: &TlsCertConfig,
_zone_dir: &Path,
_listen_addrs: &[IpAddr],
) {
panic!("TLS not enabled");
}
#[cfg(feature = "dns-over-https")]
fn config_https(
args: &Args,
server: &mut ServerFuture<Catalog>,
config: &Config,
tls_cert_config: &TlsCertConfig,
zone_dir: &Path,
listen_addrs: &[IpAddr],
) {
let https_listen_port: u16 = args
.flag_https_port
.unwrap_or_else(|| config.get_https_listen_port());
let https_sockaddrs: Vec<SocketAddr> = listen_addrs
.iter()
.flat_map(|x| (*x, tls_listen_port).to_socket_addrs().unwrap())
.flat_map(|x| (*x, https_listen_port).to_socket_addrs().unwrap())
.collect();
let tls_listeners: Vec<TcpListener> = tls_sockaddrs
let https_listeners: Vec<TcpListener> = https_sockaddrs
.iter()
.map(|x| {
TcpListener::bind(x).expect(&format!("could not bind to tls: {}", x))
})
.map(|x| TcpListener::bind(x).expect(&format!("could not bind to tls: {}", x)))
.collect();
if tls_listeners.is_empty() {
warn!("a tls certificate was specified, but no TCP addresses configured to listen on");
if https_listeners.is_empty() {
warn!("a tls certificate was specified, but no HTTPS addresses configured to listen on");
}
for tls_listener in tls_listeners {
for https_listener in https_listeners {
info!(
"loading cert for DNS over TLS: {:?}",
tls_cert_config.get_path()
@ -542,9 +628,10 @@ fn config_tls(
let tls_cert =
load_cert(zone_dir, tls_cert_config).expect("error loading tls certificate file");
info!("listening for TLS on {:?}", tls_listener);
info!("listening for HTTPS on {:?}", https_listener);
server
.register_tls_listener(tls_listener, config.get_tcp_request_timeout(), tls_cert)
// FIXME: need to passthrough the DNS authority name
.register_https_listener(https_listener, config.get_tcp_request_timeout(), tls_cert, "ns.example.com".to_string())
.expect("could not register TLS listener");
}
}

View File

@ -0,0 +1,101 @@
// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::io;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use bytes::Bytes;
use futures::{Future, Stream};
use h2::{server, server::Connection};
use http::{Response, StatusCode};
use rustls::ServerConfig;
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_rustls::ServerConfigExt;
use trust_dns_https::https_server;
use trust_dns_proto::serialize::binary::BinDecodable;
use trust_dns_rustls::tls_server;
use authority::MessageResponse;
use server::request_handler::RequestHandler;
use server::response_handler::ResponseHandler;
use server::server_future;
pub fn h2_handler<T, I>(
handler: Arc<Mutex<T>>,
io: I,
src_addr: SocketAddr,
dns_hostname: Arc<String>,
) -> impl Future<Item = (), Error = io::Error>
where
T: RequestHandler,
I: AsyncRead + AsyncWrite,
{
// Start the HTTP/2.0 connection handshake
server::handshake(io)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))
.and_then(move |h2| {
let dns_hostname = dns_hostname.clone();
// Accept all inbound HTTP/2.0 streams sent over the
// connection.
h2.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))
.for_each(move |(request, respond)| {
debug!("Received request: {:#?}", request);
let dns_hostname = dns_hostname.clone();
let handler = handler.clone();
let responder = HttpsResponseHandle(respond);
https_server::message_from(dns_hostname, request)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))
.and_then(move |bytes| {
let message = BinDecodable::from_bytes(&bytes)?;
debug!("reieved message: {:?}", message);
server_future::handle_request(
message,
src_addr,
handler.clone(),
responder,
)
})
})
}).map_err(|e| io::Error::new(io::ErrorKind::Other, format!("error in h2 handler: {}", e)))
}
struct HttpsResponseHandle(::h2::server::SendResponse<::bytes::Bytes>);
impl ResponseHandler for HttpsResponseHandle {
fn send_response(mut self, response: MessageResponse) -> io::Result<()> {
use bytes::Bytes;
use h2::server::SendResponse;
use trust_dns_https::response;
use trust_dns_https::HttpsError;
use trust_dns_proto::serialize::binary::BinEncoder;
let mut bytes = Vec::with_capacity(512);
// mut block
{
let mut encoder = BinEncoder::new(&mut bytes);
response.destructive_emit(&mut encoder)?;
};
let bytes = Bytes::from(bytes);
let response = response::new(bytes.len())?;
debug!("sending response: {:#?}", response);
let mut stream = self
.0
.send_response(response, false)
.map_err(|e| HttpsError::from(e))?;
stream
.send_data(bytes, true)
.map_err(|e| HttpsError::from(e))?;
Ok(())
}
}

View File

@ -1,27 +1,19 @@
/*
* Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! `Server` component for hosting a domain name servers operations.
mod server_future;
mod timeout_stream;
mod https_handler;
mod request_handler;
mod response_handler;
mod server_future;
mod timeout_stream;
pub use self::request_handler::{Request, RequestHandler};
pub use self::response_handler::{ResponseHandle, ResponseHandler};
pub use self::server_future::ServerFuture;
pub use self::timeout_stream::TimeoutStream;
pub use self::request_handler::{Request, RequestHandler};

View File

@ -7,8 +7,8 @@
//! Request Handler for incoming requests
use std::net::SocketAddr;
use std::io;
use std::net::SocketAddr;
use authority::MessageRequest;
use server::ResponseHandler;
@ -22,7 +22,7 @@ pub struct Request<'r> {
}
/// Trait for handling incoming requests, and providing a message response.
pub trait RequestHandler {
pub trait RequestHandler: Send + 'static {
// TODO: allow associated error type
// type Error;

View File

@ -1,4 +1,4 @@
// Copyright 2015-2017 Benjamin Fry <benjaminfry@me.com>
// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
@ -16,10 +16,13 @@ use authority::MessageResponse;
/// A handler for send a response to a client
pub trait ResponseHandler {
// TODO: add associated error type
//type Error;
/// Serializes and sends a message to to the wrapped handle
///
/// self is consumed as only one message should ever be sent in response to a Request
fn send(self, response: MessageResponse) -> io::Result<()>;
fn send_response(self, response: MessageResponse) -> io::Result<()>;
}
/// A handler for wraping a BufStreamHandle, which will properly serialize the message and add the
@ -40,7 +43,7 @@ impl ResponseHandler for ResponseHandle {
/// Serializes and sends a message to to the wrapped handle
///
/// self is consumed as only one message should ever be sent in response to a Request
fn send(self, response: MessageResponse) -> io::Result<()> {
fn send_response(self, response: MessageResponse) -> io::Result<()> {
info!(
"response: {} response_code: {}",
response.header().id(),

View File

@ -5,40 +5,43 @@
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std;
use std::borrow::Borrow;
use std::io;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use futures::{Future, Stream};
#[cfg(feature = "dns-over-https-rustls")]
use rustls::{Certificate, PrivateKey};
use tokio_executor;
use tokio_reactor::Handle;
use tokio_tcp;
use tokio_udp;
use trust_dns::serialize::binary::{BinDecodable, BinDecoder};
use trust_dns::tcp::TcpStream;
use trust_dns::udp::UdpStream;
use trust_dns::BufStreamHandle;
use trust_dns_proto::xfer::SerialMessage;
#[cfg(feature = "dns-over-openssl")]
use trust_dns_openssl::{tls_server, TlsStream};
#[cfg(feature = "dns-over-openssl")]
use trust_dns_openssl::tls_server::*;
#[cfg(feature = "dns-over-openssl")]
use trust_dns_openssl::{tls_server, TlsStream};
use trust_dns_proto::op::Message;
use trust_dns_proto::serialize::binary::{BinDecodable, BinDecoder};
use trust_dns_proto::tcp::TcpStream;
use trust_dns_proto::udp::UdpStream;
use trust_dns_proto::xfer::SerialMessage;
use trust_dns_proto::BufStreamHandle;
use authority::MessageRequest;
use server::{Request, RequestHandler, ResponseHandle, TimeoutStream};
use authority::{MessageRequest, MessageResponse};
use server::{Request, RequestHandler, ResponseHandle, ResponseHandler, TimeoutStream};
// TODO, would be nice to have a Slab for buffers here...
/// A Futures based implementation of a DNS server
pub struct ServerFuture<T: RequestHandler + Send + 'static> {
pub struct ServerFuture<T: RequestHandler> {
handler: Arc<Mutex<T>>,
}
impl<T: RequestHandler + Send> ServerFuture<T> {
impl<T: RequestHandler> ServerFuture<T> {
/// Creates a new ServerFuture with the specified Handler.
pub fn new(handler: T) -> ServerFuture<T> {
ServerFuture {
@ -60,16 +63,14 @@ impl<T: RequestHandler + Send> ServerFuture<T> {
buf_stream
.for_each(move |message| {
let src_addr = message.addr();
Self::handle_request(message, stream_handle.clone(), handler.clone())
self::handle_raw_request(message, handler.clone(), stream_handle.clone())
.map_err(move |e| {
debug!("error parsing UDP request src: {:?} error: {}", src_addr, e)
})
.ok();
}).ok();
// continue processing...
Ok(())
})
.map_err(|e| panic!("error in UDP request_stream handler: {}", e)),
}).map_err(|e| panic!("error in UDP request_stream handler: {}", e)),
);
}
@ -117,13 +118,13 @@ impl<T: RequestHandler + Send> ServerFuture<T> {
tokio_executor::spawn(
timeout_stream
.for_each(move |message| {
Self::handle_request(
let src_addr = message.addr();
self::handle_raw_request(
message,
stream_handle.clone(),
handler.clone(),
stream_handle.clone(),
)
})
.map_err(move |e| {
}).map_err(move |e| {
debug!(
"error in TCP request_stream src: {:?} error: {}",
src_addr, e
@ -132,8 +133,7 @@ impl<T: RequestHandler + Send> ServerFuture<T> {
);
Ok(())
})
.map_err(|e| panic!("error in inbound tcp_stream: {}", e)),
}).map_err(|e| panic!("error in inbound tcp_stream: {}", e)),
);
Ok(())
@ -206,8 +206,7 @@ impl<T: RequestHandler + Send> ServerFuture<T> {
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
})
.and_then(move |tls_stream| {
}).and_then(move |tls_stream| {
let (buf_stream, stream_handle) =
TlsStream::from_stream(tls_stream, src_addr);
let timeout_stream = TimeoutStream::new(buf_stream, timeout);
@ -218,15 +217,14 @@ impl<T: RequestHandler + Send> ServerFuture<T> {
tokio_executor::spawn(
timeout_stream
.for_each(move |message| {
Self::handle_request(
self::handle_raw_request(
message,
stream_handle.clone(),
handler.clone(),
stream_handle.clone(),
)
})
.map_err(move |e| {
}).map_err(move |e| {
debug!(
"error in TCP request_stream src: {:?} error: {}",
"error in TLS request_stream src: {:?} error: {}",
src_addr, e
)
}),
@ -234,9 +232,11 @@ impl<T: RequestHandler + Send> ServerFuture<T> {
Ok(())
})
//.map_err(move |e| debug!("error TLS handshake: {:?} error: {:?}", src_addr, e))
})
.map_err(|e| panic!("error in inbound tls_stream: {}", e))
// FIXME: need to map this error to Ok, otherwise this is a DOS potential
// .map_err(move |e| {
// debug!("error TLS handshake: {:?} error: {:?}", src_addr, e)
// })
}).map_err(|e| panic!("error in inbound tls_stream: {}", e))
}));
Ok(())
@ -269,46 +269,159 @@ impl<T: RequestHandler + Send> ServerFuture<T> {
)
}
fn handle_request(
message: SerialMessage,
stream_handle: BufStreamHandle,
handler: Arc<Mutex<T>>,
/// Register a TlsListener to the Server. The TlsListener should already be bound to either an
/// IPv6 or an IPv4 address.
///
/// To make the server more resilient to DOS issues, there is a timeout. Care should be taken
/// to not make this too low depending on use cases.
///
/// # Arguments
/// * `listener` - a bound TCP (needs to be on a different port from standard TCP connections) socket
/// * `timeout` - timeout duration of incoming requests, any connection that does not send
/// requests within this time period will be closed. In the future it should be
/// possible to create long-lived queries, but these should be from trusted sources
/// only, this would require some type of whitelisting.
/// * `pkcs12` - certificate used to announce to clients
#[cfg(all(
feature = "dns-over-https-openssl",
not(feature = "dns-over-https-rustls")
))]
pub fn register_https_listener(
&self,
listener: tokio_tcp::TcpListener,
timeout: Duration,
pkcs12: ParsedPkcs12,
) -> io::Result<()> {
let src_addr = message.addr();
let response_handle = ResponseHandle::new(message.addr(), stream_handle);
unimplemented!("openssl based `dns-over-https` not yet supported. see the `dns-over-https-rustls` feature")
}
// TODO: rather than decoding the message here, this RequestStream should instead
// forward the request to another sender such that we could pull serialization off
// the IO thread.
// decode any messages that are ready
let mut decoder = BinDecoder::new(message.bytes());
let message = MessageRequest::read(&mut decoder)?;
/// Register a TlsListener to the Server. The TlsListener should already be bound to either an
/// IPv6 or an IPv4 address.
///
/// To make the server more resilient to DOS issues, there is a timeout. Care should be taken
/// to not make this too low depending on use cases.
///
/// # Arguments
/// * `listener` - a bound TCP (needs to be on a different port from standard TCP connections) socket
/// * `timeout` - timeout duration of incoming requests, any connection that does not send
/// requests within this time period will be closed. In the future it should be
/// possible to create long-lived queries, but these should be from trusted sources
/// only, this would require some type of whitelisting.
/// * `pkcs12` - certificate used to announce to clients
#[cfg(feature = "dns-over-https-rustls")]
pub fn register_https_listener(
&self,
listener: tokio_tcp::TcpListener,
timeout: Duration,
certificate_and_key: (Certificate, PrivateKey),
dns_hostname: String,
) -> io::Result<()> {
use futures::{future, Stream};
use h2::server;
use http::{Response, StatusCode};
use rustls::ServerConfig;
use tokio_rustls::ServerConfigExt;
let request = Request {
message: message,
src: src_addr,
};
use server::https_handler::h2_handler;
use trust_dns_https::https_server;
use trust_dns_rustls::tls_server;
info!(
"request: {} type: {:?} op_code: {:?} dnssec: {} {}",
request.message.id(),
request.message.message_type(),
request.message.op_code(),
request
.message
.edns()
.map_or(false, |edns| edns.dnssec_ok()),
request
.message
.queries()
.first()
.map(|q| q.original().to_string())
.unwrap_or_else(|| "empty_queries".to_string()),
);
let dns_hostname = Arc::new(dns_hostname);
let handler = self.handler.clone();
debug!("registered tcp: {:?}", listener);
handler
.lock()
.unwrap()
.handle_request(&request, response_handle)
let tls_acceptor = tls_server::new_acceptor(certificate_and_key.0, certificate_and_key.1)
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("error creating TLS acceptor: {}", e),
)
})?;
let tls_acceptor: Arc<ServerConfig> = Arc::new(tls_acceptor);
// for each incoming request...
let dns_hostname = dns_hostname.clone();
tokio_executor::spawn(future::lazy(move || {
let dns_hostname = dns_hostname.clone();
listener
.incoming()
.for_each(move |tcp_stream| {
let src_addr = tcp_stream.peer_addr().unwrap();
debug!("accepted request from: {}", src_addr);
let handler = handler.clone();
let dns_hostname = dns_hostname.clone();
// TODO: need to consider timeout of total connect...
// take the created stream...
tls_acceptor
.accept_async(tcp_stream)
.map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
}).and_then(move |tls_stream| {
h2_handler(handler, tls_stream, src_addr, dns_hostname)
})
// FIXME: need to map this error to Ok, otherwise this is a DOS potential
// .map_err(move |e| {
// debug!("error HTTPS handshake: {:?} error: {:?}", src_addr, e)
// })
}).map_err(|e| panic!("error in inbound https_stream: {}", e))
}));
Ok(())
}
}
pub(crate) fn handle_raw_request<T: RequestHandler>(
message: SerialMessage,
request_handler: Arc<Mutex<T>>,
response_handler: BufStreamHandle,
) -> io::Result<()> {
let src_addr = message.addr();
let response_handler = ResponseHandle::new(message.addr(), response_handler);
// TODO: rather than decoding the message here, this RequestStream should instead
// forward the request to another sender such that we could pull serialization off
// the IO thread.
// decode any messages that are ready
let mut decoder = BinDecoder::new(message.bytes());
let message = MessageRequest::read(&mut decoder)?;
self::handle_request(message, src_addr, request_handler, response_handler)
}
pub(crate) fn handle_request<'q, R: ResponseHandler + 'static, T: RequestHandler>(
message: MessageRequest<'q>,
src_addr: SocketAddr,
request_handler: Arc<Mutex<T>>,
response_handler: R,
) -> io::Result<()> {
let request = Request {
message: message,
src: src_addr,
};
info!(
"request: {} type: {:?} op_code: {:?} dnssec: {} {}",
request.message.id(),
request.message.message_type(),
request.message.op_code(),
request
.message
.edns()
.map_or(false, |edns| edns.dnssec_ok()),
request
.message
.queries()
.first()
.map(|q| q.original().to_string())
.unwrap_or_else(|| "empty_queries".to_string()),
);
request_handler
.lock()
.expect("poisoned lock")
.handle_request(&request, response_handler)
}

View File

@ -1,20 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDQzCCAiugAwIBAgIJAK1tJmh2Bh0qMA0GCSqGSIb3DQEBCwUAMC0xEjAQBgNV
BAoMCVRSdXN0LUROUzEXMBUGA1UEAwwObnMuZXhhbXBsZS5jb20wHhcNMTcxMTEw
MDc0NzIzWhcNMTgxMTEwMDc0NzIzWjAtMRIwEAYDVQQKDAlUUnVzdC1ETlMxFzAV
MIIDQDCCAiigAwIBAgIJALzp993NYWDeMA0GCSqGSIb3DQEBCwUAMC0xEjAQBgNV
BAoMCVRSdXN0LUROUzEXMBUGA1UEAwwObnMuZXhhbXBsZS5jb20wHhcNMTgwODI2
MTUwOTE3WhcNMTkwODI2MTUwOTE3WjAtMRIwEAYDVQQKDAlUUnVzdC1ETlMxFzAV
BgNVBAMMDm5zLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEApuvvd2vmwnlzhBGnU87zYrL3DVTpP5IGqiwGDwqTAOD6GPzbh28JQ0GX
CBQjk7UKWxP8Uq46g++NL1ka9dGRKwn31T9qDVZf4lA/NB0ONV1qanZj8MBndfn2
WPBvgY2ON9mabQD8h/o3AkeGf55e1Dq9JAZl9LG4iTMzGjyq2b3PQdH3aAmrxGW3
im7FxYK3V3Eh25sGvDgjPCe3+5KFYOkHwbwYn0JJ6u03y8QLUeGoQ2wVwRMZmbYP
XXy3F/5/Nqv7rInAZCG3EAR0ZlKiaipzX8ba3ZV4VJxl9VrnY+DsHDO/AgZcr2c3
EnhPlY5ayp82QxCZmIQVzEBPscuBgwIDAQABo2YwZDAcBgNVHREBAf8EEjAQgg5u
CgKCAQEApdUXbkdd+3Nctv+KxgIUN0koIM2ePbH2RMVYYuVI6yxdO4+GBJa3O+O5
28TGENLohKQ8TVf7ZnXea68nxnB6m6+6J+HvMbLqfWkqszwvEFlMv9BGCVonpHzr
kcRrGolQpe03RQFd07GmV5LCK9tPqK04BBNigBviknj8FmCrEPlGWJRQjMBkLgXS
nSC/QTJ/ApM1fMFDNaRzVstkTexpzizPPK3lg+CJclr6bS6W5BJevtiK2D+JtzHI
O9LKSOsacExGh55Td7021kDoh17aFi2utpcI9kRhNshsNLSqOfXxqvThWs4CPFLN
0/vtpfgKS9zZws8zlPK7VMEag1vY+wIDAQABo2MwYTAcBgNVHREBAf8EEjAQgg5u
cy5leGFtcGxlLmNvbTAOBgNVHQ8BAf8EBAMCAowwIAYDVR0lAQH/BBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwDQYJKoZIhvcNAQEL
BQADggEBAFCu8ND4StLSq8sBjAW93/AOlrinGLS+hFJ2XyFJkoQvIoDXzg+5exEH
XyV0aI2IEof2QBb3/sP6pH73QWB+oRdF1NM5vLPc/9a+bgz1FkZTuz5moijFtCBc
jjOiVSVtqqdSbm1vlpEH9AEAnZZcBs4vijTLvmaa1s+lgqo+PZWYNLr1gCmXT6qh
N4CQkaBAxynlfWpzMlaoc7IqVDObVG9iEJ4r0fwYxb2pbHNgLver0x1+hZ0nHikI
Y13IZfjUjkmJplBQaeRvsVI7qoqkQ1IKKaadpv41UGluL4m7kpXOv0ALbn/BhdIy
2DfRyOyfPYWKTDtd5C6Jp3VnshktdeY=
BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMCAQAwDQYJKoZIhvcNAQELBQAD
ggEBAHyRPqCYpiutAeyrffC0BnZkLvwrYvcCO5LjB94Nq1oQOCq9LXm94dXU3WUX
nN4GHbPzNYY8Cscc2a/hulDZQlr3vQcoFOCYNDmuEcn10RGcu4TEoLE6IxD9UTU3
qfDaOKyk79QSzdMYaL3v/mr9B6x5WDVr2JtlkM9V4XUW67YWyeBbQ+EJlwT9Z+OC
ld/bahChTDs2/19vqHtpv3L/nrxGVGJBDLh0zwSmydzZASKYumgITD37LkTSyGJl
fbO59BxGAmIXVXSNZyT7hzMom0Cscb3/s8NLbJRhS25eBGV9k9wX88CXDod1ctuy
+p94GH0XXwUXh62yLr0g9wbP1V4=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEApdUXbkdd+3Nctv+KxgIUN0koIM2ePbH2RMVYYuVI6yxdO4+G
BJa3O+O528TGENLohKQ8TVf7ZnXea68nxnB6m6+6J+HvMbLqfWkqszwvEFlMv9BG
CVonpHzrkcRrGolQpe03RQFd07GmV5LCK9tPqK04BBNigBviknj8FmCrEPlGWJRQ
jMBkLgXSnSC/QTJ/ApM1fMFDNaRzVstkTexpzizPPK3lg+CJclr6bS6W5BJevtiK
2D+JtzHIO9LKSOsacExGh55Td7021kDoh17aFi2utpcI9kRhNshsNLSqOfXxqvTh
Ws4CPFLN0/vtpfgKS9zZws8zlPK7VMEag1vY+wIDAQABAoIBADkNVdFP5kqDBuvC
gOjcgD4BAjg+5WkOV86TInRrs6mNCspZ916Ox51oHGI6kXXqSaqQolptoYU/mfTs
Pr/rpJL5Yw8jkNpFVp0s2E7vrrVuM5RuQBoplSfm/liY/cwUX9WmBfTMoo4ZOUQ5
rmnOOtqqNXJZhPLUJSAFVZ0RRulAe5SPnN2rbq+xw6SzbWUgxMys4bVcw35mlK8O
jzuGpftvGVgPSfJpsnG22DN6n0YO4WHPMD+m8BYlSljmaaVxDfCC53pT8JG2EPRt
0m2wBs3ikwyWjAS4iRHyAI71doxU7qEsXiIIaPUUP8zYY+LxPY2IIVt1Bza6coOJ
J0z8D9ECgYEA1PNJfwHuPXGUTBhOs+auNYQF2KNQSM+0pjXnLuLwOIvUbs2avGBy
eXYG1LjwBks4WhtdT1DNJXYoQFDLXn/yfrPOY7mA1FYfl3G/N8Dg0ybKeCcA/huo
MClBBKkcQUkX514tZutuIfrht1isSOuG1PSn9eetwgohlSHRPMPiQVMCgYEAx1tV
Im/OY5qRtdwntatmtejC/rLgqIWujG0BnMXgMAJgIlBjlNityHY2FSs8qQAmLRi9
WLjhW5nvbB8FnvK2SPKJ8bALgGgsijv/cyDdjfhOuyypqquwWPIQCJSTf8yHZdi0
6TB6bAHYZlhWo/9zZOH32hW2lTVHgzYtObPyTLkCgYAGpem/e1HyvR8CGSgr2aHK
rep4zvBstX9QSRKEljUlrsfdBbI0+1XXkOW5smRb7fE+buhE16Lv7nZnO559vsTV
S8u/tUTeXCn0UmrD1NOwA+ACTEVtqXNgvYj4Gkd1ilCiun/0XJk9mlV9odkPFbtJ
3rF3rdnw2twdica8fOkNXQKBgQCWT/EDBBYz16mh25s9ST0qT5QnAqyNpC4Vx2L9
19zPlhryBHbxFecCTM8+atlT+77NJegua0fQD2MMvN86F3sFyYnk533kladvmwli
vxcOInkKfAR5oPZYOjuInK4SIB6+1gSiBmFn6oRFtrms8cEKAa8lilWebwu6jTDQ
XzOEUQKBgC5nSVRKlUb/5SsA3ogBmHB2PIwWVdtMzrMbcdBC1KosR9IBGbqC8M+k
JvSbmVlo+2mFy4fEqVREET3dqkZOZ6RpJLCTJLlQ6LnWcSa2/nzcvvJNYn9W1q61
bddNCYED51+XlBz13LBuX0aMouBlsXQow642HT1gYTUCwjKou0iz
-----END RSA PRIVATE KEY-----

View File

@ -1,5 +1,8 @@
#!/bin/bash
set e
set x
OPENSSL=/usr/local/opt/openssl/bin/openssl
KEY_FILE=example.key
@ -10,17 +13,19 @@ P12_FILE=example.p12
# ec key request
echo "====> generating key"
### Apple doesn't allow ECC keys? Ecc will fail native-tls
# ${OPENSSL:?} ecparam -out ${KEY_FILE:?} -name secp256k1 -genkey
# ${OPENSSL:?} ecparam -out ${KEY_FILE:?}.pem -outform pem -name secp256k1 -genkey
### Using RSA for now
${OPENSSL:?} genrsa -out ${KEY_FILE:?} 2048
${OPENSSL:?} genrsa -out ${KEY_FILE:?}.pem 2048
${OPENSSL:?} pkey -in ${KEY_FILE:?}.pem -inform pem -out ${KEY_FILE:?} -outform der
## self-signed cert...
echo "====> generating cert"
${OPENSSL:?} req -new -x509 -days 365 -sha256 \
-key ${KEY_FILE:?} -keyform pem \
-key ${KEY_FILE:?} -keyform der \
-out ${CRT_FILE:?} -outform der \
-subj '/O=TRust-DNS/CN=ns.example.com' \
-config <(cat /etc/ssl/openssl.cnf <(printf "\n[x509v3]\nsubjectAltName=critical,DNS:ns.example.com\nkeyUsage=critical,digitalSignature,keyAgreement,keyCertSign\nextendedKeyUsage=critical,serverAuth,clientAuth\nbasicConstraints=critical,CA:TRUE,pathlen:0")) \
-config <(cat /etc/ssl/openssl.cnf <(printf "\n[x509v3]\nsubjectAltName=critical,DNS:ns.example.com\nkeyUsage=critical,digitalSignature,keyAgreement,keyCertSign\nextendedKeyUsage=critical,serverAuth,clientAuth\nbasicConstraints=critical,pathlen:0")) \
-extensions x509v3 \
-reqexts x509v3
@ -29,7 +34,7 @@ ${OPENSSL:?} x509 -in ${CRT_FILE:?} -inform der -out ${CRT_FILE:?}.pem
# pkcs12 chain
echo "====> generating p12"
${OPENSSL:?} pkcs12 -export -out ${P12_FILE:?} -inkey ${KEY_FILE:?} -in ${CRT_FILE:?}.pem \
${OPENSSL:?} pkcs12 -export -out ${P12_FILE:?} -inkey ${KEY_FILE:?}.pem -in ${CRT_FILE:?}.pem \
-password pass:mypass \
-macalg sha256 \
-name "ns.example.com" \

View File

@ -28,10 +28,10 @@ use self::mut_message_client::MutMessageHandle;
#[allow(dead_code)]
pub fn named_test_harness<F, R>(toml: &str, test: F)
where
F: FnOnce(u16, u16) -> R + UnwindSafe,
F: FnOnce(u16, u16, u16) -> R + UnwindSafe,
{
// find a random port to listen on
let (test_port, test_tls_port) = {
let (test_port, test_tls_port, test_https_port) = {
let server = UdpSocket::bind(("0.0.0.0", 0)).unwrap();
let server_addr = server.local_addr().unwrap();
let test_port = server_addr.port();
@ -40,8 +40,14 @@ where
let server_addr = server.local_addr().unwrap();
let test_tls_port = server_addr.port();
let server = UdpSocket::bind(("0.0.0.0", 0)).unwrap();
let server_addr = server.local_addr().unwrap();
let test_https_port = server_addr.port();
assert!(test_port != test_tls_port);
(test_port, test_tls_port)
assert!(test_port != test_https_port);
assert!(test_tls_port != test_https_port);
(test_port, test_tls_port, test_https_port)
};
let server_path = env::var("TDNS_SERVER_SRC_ROOT").unwrap_or_else(|_| ".".to_owned());
@ -49,17 +55,19 @@ where
let mut named = Command::new(&format!("{}/../target/debug/named", server_path))
.stdout(Stdio::piped())
.arg("-d")
.env(
"RUST_LOG",
"ht=trace,trust_dns_https=debug,trust_dns_proto=debug",
).arg("-d")
.arg(&format!(
"--config={}/tests/named_test_configs/{}",
server_path, toml
))
.arg(&format!(
)).arg(&format!(
"--zonedir={}/tests/named_test_configs",
server_path
))
.arg(&format!("--port={}", test_port))
)).arg(&format!("--port={}", test_port))
.arg(&format!("--tls-port={}", test_tls_port))
.arg(&format!("--https-port={}", test_https_port))
.spawn()
.expect("failed to start named");
@ -94,8 +102,7 @@ where
kill_named();
panic!("timeout");
})
.expect("could not start thread killer");
}).expect("could not start thread killer");
// we should get the correct output before 1000 lines...
let mut output = String::new();
@ -148,12 +155,11 @@ where
stdout().write_all(output.as_bytes()).unwrap();
}
}
})
.expect("no thread available");
}).expect("no thread available");
println!("running test...");
let result = catch_unwind(move || test(test_port, test_tls_port));
let result = catch_unwind(move || test(test_port, test_tls_port, test_https_port));
println!("test completed");
succeeded.store(true, atomic::Ordering::Relaxed);
@ -168,7 +174,7 @@ pub fn query_message<C: ClientHandle>(
name: Name,
record_type: RecordType,
) -> DnsResponse {
println!("sending request");
println!("sending request: {} for: {}", name, record_type);
let response = io_loop.block_on(client.query(name.clone(), DNSClass::IN, record_type));
println!("got response: {:#?}", response);
response.expect("request failed")
@ -222,8 +228,7 @@ pub fn query_all_dnssec<R: Future<Item = DnsResponse, Error = ProtoError> + Send
} else {
panic!("wrong RDATA")
}
})
.find(|d| d.algorithm() == algorithm);
}).find(|d| d.algorithm() == algorithm);
assert!(dnskey.is_some(), "DNSKEY not found");
let response = query_message(
@ -243,8 +248,7 @@ pub fn query_all_dnssec<R: Future<Item = DnsResponse, Error = ProtoError> + Send
} else {
panic!("wrong RDATA")
}
})
.filter(|rrsig| rrsig.algorithm() == algorithm)
}).filter(|rrsig| rrsig.algorithm() == algorithm)
.find(|rrsig| rrsig.type_covered() == RecordType::DNSSEC(DNSSECRecordType::DNSKEY));
assert!(rrsig.is_some(), "Associated RRSIG not found");
}

View File

@ -27,7 +27,6 @@ use std::fs::File;
use std::io::*;
use std::net::*;
use rustls::internal::msgs::codec::Codec;
use rustls::Certificate;
use tokio::runtime::current_thread::Runtime;
use trust_dns::client::*;
@ -38,7 +37,10 @@ use server_harness::{named_test_harness, query_a};
#[test]
fn test_example_https_toml_startup() {
named_test_harness("dns_over_tls.toml", move |_, tls_port| {
extern crate env_logger;
env_logger::try_init().ok();
named_test_harness("dns_over_tls.toml", move |_, _, https_port| {
let mut cert_der = vec![];
let server_path = env::var("TDNS_SERVER_SRC_ROOT").unwrap_or_else(|_| ".".to_owned());
println!("using server src path: {}", server_path);
@ -51,16 +53,18 @@ fn test_example_https_toml_startup() {
.expect("failed to read cert");
let mut io_loop = Runtime::new().unwrap();
let addr: SocketAddr = ("127.0.0.1", tls_port)
let addr: SocketAddr = ("127.0.0.1", https_port)
.to_socket_addrs()
.unwrap()
.next()
.unwrap();
let mut tls_conn_builder = HttpsClientStreamBuilder::new();
std::thread::sleep(std::time::Duration::from_secs(1));
let mut https_conn_builder = HttpsClientStreamBuilder::new();
let cert = to_trust_anchor(&cert_der);
tls_conn_builder.add_ca(cert);
let mp = tls_conn_builder.build(addr, "ns.example.com".to_string());
https_conn_builder.add_ca(cert);
let mp = https_conn_builder.build(addr, "ns.example.com".to_string());
let (exchange, handle) = DnsExchange::connect(mp);
let (bg, mut client) = ClientFuture::from_exchange(exchange, handle);
@ -68,26 +72,26 @@ fn test_example_https_toml_startup() {
io_loop.spawn(bg);
query_a(&mut io_loop, &mut client);
let addr: SocketAddr = ("127.0.0.1", tls_port)
.to_socket_addrs()
.unwrap()
.next()
.unwrap();
let mut tls_conn_builder = HttpsClientStreamBuilder::new();
let cert = to_trust_anchor(&cert_der);
tls_conn_builder.add_ca(cert);
let mp = tls_conn_builder.build(addr, "ns.example.com".to_string());
let (exchange, handle) = DnsExchange::connect(mp);
let (bg, mut client) = ClientFuture::from_exchange(exchange, handle);
io_loop.spawn(bg);
// let addr: SocketAddr = ("127.0.0.1", https_port)
// .to_socket_addrs()
// .unwrap()
// .next()
// .unwrap();
// let mut tls_conn_builder = HttpsClientStreamBuilder::new();
// let cert = to_trust_anchor(&cert_der);
// tls_conn_builder.add_ca(cert);
// let mp = tls_conn_builder.build(addr, "ns.example.com".to_string());
// let (exchange, handle) = DnsExchange::connect(mp);
// let (bg, mut client) = ClientFuture::from_exchange(exchange, handle);
// io_loop.spawn(bg);
// ipv6 should succeed
query_a(&mut io_loop, &mut client);
// // ipv6 should succeed
// query_a(&mut io_loop, &mut client);
assert!(true);
})
}
fn to_trust_anchor(cert_der: &[u8]) -> Certificate {
Certificate::read_bytes(cert_der).unwrap()
Certificate(cert_der.to_vec())
}

View File

@ -35,7 +35,7 @@ use server_harness::{named_test_harness, query_a};
#[test]
fn test_example_tls_toml_startup() {
named_test_harness("dns_over_tls.toml", move |_, tls_port| {
named_test_harness("dns_over_tls.toml", move |_, tls_port, _| {
let mut cert_der = vec![];
let server_path = env::var("TDNS_SERVER_SRC_ROOT").unwrap_or_else(|_| ".".to_owned());
println!("using server src path: {}", server_path);

View File

@ -88,7 +88,7 @@ fn generic_test(config_toml: &str, key_path: &str, key_format: KeyFormat, algori
let server_path = env::var("TDNS_SERVER_SRC_ROOT").unwrap_or_else(|_| ".".to_owned());
let server_path = Path::new(&server_path);
named_test_harness(config_toml, |port, _| {
named_test_harness(config_toml, |port, _, _| {
let mut io_loop = Runtime::new().unwrap();
// verify all records are present

View File

@ -38,7 +38,7 @@ use server_harness::{named_test_harness, query_a};
#[test]
fn test_example_toml_startup() {
named_test_harness("example.toml", |port, _| {
named_test_harness("example.toml", |port, _, _| {
let mut io_loop = Runtime::new().unwrap();
let addr: SocketAddr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), port);
let (stream, sender) = TcpClientStream::new(addr);
@ -59,7 +59,7 @@ fn test_example_toml_startup() {
#[test]
fn test_ipv4_only_toml_startup() {
named_test_harness("ipv4_only.toml", |port, _| {
named_test_harness("ipv4_only.toml", |port, _, _| {
let mut io_loop = Runtime::new().unwrap();
let addr: SocketAddr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), port);
let (stream, sender) = TcpClientStream::new(addr);
@ -117,7 +117,7 @@ fn test_ipv4_only_toml_startup() {
#[ignore]
#[test]
fn test_ipv4_and_ipv6_toml_startup() {
named_test_harness("ipv4_and_ipv6.toml", |port, _| {
named_test_harness("ipv4_and_ipv6.toml", |port, _, _| {
let mut io_loop = Runtime::new().unwrap();
let addr: SocketAddr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), port);
let (stream, sender) = TcpClientStream::new(addr);
@ -143,7 +143,7 @@ fn test_ipv4_and_ipv6_toml_startup() {
#[test]
fn test_nodata_where_name_exists() {
named_test_harness("example.toml", |port, _| {
named_test_harness("example.toml", |port, _, _| {
let mut io_loop = Runtime::new().unwrap();
let addr: SocketAddr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), port);
let (stream, sender) = TcpClientStream::new(addr);
@ -165,7 +165,7 @@ fn test_nodata_where_name_exists() {
#[test]
fn test_nxdomain_where_no_name_exists() {
named_test_harness("example.toml", |port, _| {
named_test_harness("example.toml", |port, _, _| {
let mut io_loop = Runtime::new().unwrap();
let addr: SocketAddr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), port);
let (stream, sender) = TcpClientStream::new(addr);
@ -193,7 +193,7 @@ fn test_example_tls_toml_startup() {
use std::io::*;
use trust_dns_openssl::TlsClientStreamBuilder;
named_test_harness("dns_over_tls.toml", move |_, tls_port| {
named_test_harness("dns_over_tls.toml", move |_, tls_port, _| {
let mut cert_der = vec![];
let server_path = env::var("TDNS_SERVER_SRC_ROOT").unwrap_or_else(|_| ".".to_owned());
println!("using server src path: {}", server_path);
@ -234,7 +234,7 @@ fn test_example_tls_toml_startup() {
#[test]
fn test_server_continues_on_bad_data_udp() {
named_test_harness("example.toml", |port, _| {
named_test_harness("example.toml", |port, _, _| {
let mut io_loop = Runtime::new().unwrap();
let addr: SocketAddr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), port);
let (stream, sender) = UdpClientStream::new(addr);
@ -264,7 +264,7 @@ fn test_server_continues_on_bad_data_udp() {
#[test]
fn test_server_continues_on_bad_data_tcp() {
named_test_harness("example.toml", |port, _| {
named_test_harness("example.toml", |port, _, _| {
let mut io_loop = Runtime::new().unwrap();
let addr: SocketAddr = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), port);
let (stream, sender) = TcpClientStream::new(addr);