https server and client working
This commit is contained in:
parent
73b61adb06
commit
0850f1cf6f
@ -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
|
||||
|
||||
|
@ -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 = []
|
||||
|
@ -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;
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
116
https/src/error.rs
Normal 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))
|
||||
}
|
||||
}
|
@ -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
146
https/src/https_server.rs
Normal 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")
|
||||
}
|
||||
}
|
@ -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
152
https/src/request.rs
Normal 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
64
https/src/response.rs
Normal 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)
|
||||
}
|
@ -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"]
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"]
|
||||
|
@ -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
58
rustls/src/tls_server.rs
Normal 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)
|
||||
}
|
@ -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
|
||||
|
@ -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"
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
101
server/src/server/https_handler.rs
Normal file
101
server/src/server/https_handler.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -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};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
}
|
||||
|
Binary file not shown.
@ -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-----
|
||||
|
Binary file not shown.
27
server/tests/named_test_configs/sec/example.key.pem
Normal file
27
server/tests/named_test_configs/sec/example.key.pem
Normal 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-----
|
Binary file not shown.
@ -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" \
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user