api for quic client streams implemented
This commit is contained in:
parent
ca2c1303d1
commit
eb61a9b296
58
Cargo.lock
generated
58
Cargo.lock
generated
@ -271,6 +271,12 @@ version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.0.1"
|
||||
@ -593,6 +599,15 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
@ -1108,6 +1123,25 @@ version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2b1562bf4998b0c6d1841a4742b7103bb82cdde61374833de826bab9e8ad498"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fxhash",
|
||||
"rand",
|
||||
"ring",
|
||||
"rustls",
|
||||
"rustls-pemfile 0.2.1",
|
||||
"slab",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.9"
|
||||
@ -1261,6 +1295,15 @@ dependencies = [
|
||||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "0.3.0"
|
||||
@ -1580,9 +1623,21 @@ checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.18"
|
||||
@ -1695,10 +1750,11 @@ dependencies = [
|
||||
"log",
|
||||
"native-tls",
|
||||
"openssl",
|
||||
"quinn-proto",
|
||||
"rand",
|
||||
"ring",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls-pemfile 0.3.0",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"socket2",
|
||||
|
@ -6,7 +6,7 @@
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
@ -27,6 +27,27 @@ pub struct AsyncStdUdpSocket(async_std::net::UdpSocket);
|
||||
impl UdpSocket for AsyncStdUdpSocket {
|
||||
type Time = AsyncStdTime;
|
||||
|
||||
// FIXME: make bind_addr ToSocketAddrs
|
||||
async fn connect_with_bind(addr: SocketAddr, bind_addr: SocketAddr) -> io::Result<Self> {
|
||||
let socket = async_std::net::UdpSocket::bind(addr).await?;
|
||||
|
||||
socket.connect(addr);
|
||||
Ok(AsyncStdUdpSocket(socket))
|
||||
}
|
||||
|
||||
// FIXME: make bind_addr ToSocketAddrs
|
||||
async fn connect(addr: SocketAddr) -> io::Result<Self> {
|
||||
let bind_addr: SocketAddr = match addr {
|
||||
SocketAddr::V4(_addr) => (Ipv4Addr::UNSPECIFIED, 0).into(),
|
||||
SocketAddr::V6(_addr) => (Ipv6Addr::UNSPECIFIED, 0).into(),
|
||||
};
|
||||
|
||||
let socket = async_std::net::UdpSocket::bind(bind_addr).await?;
|
||||
|
||||
socket.connect(addr);
|
||||
Ok(AsyncStdUdpSocket(socket))
|
||||
}
|
||||
|
||||
async fn bind(addr: SocketAddr) -> io::Result<Self> {
|
||||
async_std::net::UdpSocket::bind(addr)
|
||||
.await
|
||||
|
@ -44,6 +44,8 @@ dns-over-openssl = ["dns-over-tls", "openssl", "tokio-openssl"]
|
||||
dns-over-https-rustls = ["dns-over-https", "dns-over-rustls", "webpki-roots"]
|
||||
dns-over-https = ["bytes", "dns-over-tls", "h2", "http"]
|
||||
|
||||
dns-over-quic = ["quinn-proto", "rustls/quic", "dns-over-rustls"]
|
||||
|
||||
dnssec-openssl = ["dnssec", "openssl"]
|
||||
dnssec-ring = ["dnssec", "ring"]
|
||||
dnssec = []
|
||||
@ -86,6 +88,7 @@ lazy_static = "1.2.0"
|
||||
log = "0.4"
|
||||
native-tls = { version = "0.2", optional = true }
|
||||
openssl = { version = "0.10", features = ["v102", "v110"], optional = true }
|
||||
quinn-proto = { version = "0.8.1", optional = true }
|
||||
rand = "0.8"
|
||||
ring = { version = "0.16", optional = true, features = ["std"] }
|
||||
rustls = { version = "0.20.0", optional = true }
|
||||
|
@ -74,6 +74,9 @@ pub mod op;
|
||||
#[cfg(feature = "dns-over-openssl")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "dns-over-openssl")))]
|
||||
pub mod openssl;
|
||||
#[cfg(feature = "dns-over-quic")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "dns-over-quic")))]
|
||||
pub mod quic;
|
||||
pub mod rr;
|
||||
#[cfg(feature = "dns-over-rustls")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "dns-over-rustls")))]
|
||||
|
131
crates/proto/src/quic/error.rs
Normal file
131
crates/proto/src/quic/error.rs
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2015-2020 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::num::ParseIntError;
|
||||
use std::{fmt, io};
|
||||
|
||||
use crate::error::ProtoError;
|
||||
use h2;
|
||||
use http::header::ToStrError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(feature = "backtrace")]
|
||||
use crate::{trace, ExtBacktrace};
|
||||
|
||||
/// An alias for results returned by functions of this crate
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
|
||||
// TODO: remove this and put in ProtoError
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum ErrorKind {
|
||||
/// Unable to decode header value to string
|
||||
#[error("header decode error: {0}")]
|
||||
Decode(#[from] ToStrError),
|
||||
|
||||
/// An error with an arbitrary message, referenced as &'static str
|
||||
#[error("{0}")]
|
||||
Message(&'static str),
|
||||
|
||||
/// An error with an arbitrary message, stored as String
|
||||
#[error("{0}")]
|
||||
Msg(String),
|
||||
|
||||
/// Unable to parse header value as number
|
||||
#[error("unable to parse number: {0}")]
|
||||
ParseInt(#[from] ParseIntError),
|
||||
|
||||
#[error("proto error: {0}")]
|
||||
ProtoError(#[from] ProtoError),
|
||||
|
||||
#[error("h2: {0}")]
|
||||
H2(#[from] h2::Error),
|
||||
}
|
||||
|
||||
/// The error type for errors that get returned in the crate
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
kind: ErrorKind,
|
||||
#[cfg(feature = "backtrace")]
|
||||
backtrack: Option<ExtBacktrace>,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Get the kind of the error
|
||||
pub fn kind(&self) -> &ErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "backtrace")] {
|
||||
if let Some(ref backtrace) = self.backtrack {
|
||||
fmt::Display::fmt(&self.kind, f)?;
|
||||
fmt::Debug::fmt(backtrace, f)
|
||||
} else {
|
||||
fmt::Display::fmt(&self.kind, f)
|
||||
}
|
||||
} else {
|
||||
fmt::Display::fmt(&self.kind, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorKind> for Error {
|
||||
fn from(kind: ErrorKind) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
#[cfg(feature = "backtrace")]
|
||||
backtrack: trace!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Error {
|
||||
fn from(msg: &'static str) -> Self {
|
||||
ErrorKind::Message(msg).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(msg: String) -> Self {
|
||||
ErrorKind::Msg(msg).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for Error {
|
||||
fn from(err: ParseIntError) -> Self {
|
||||
ErrorKind::from(err).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ToStrError> for Error {
|
||||
fn from(err: ToStrError) -> Self {
|
||||
ErrorKind::from(err).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProtoError> for Error {
|
||||
fn from(msg: ProtoError) -> Self {
|
||||
ErrorKind::ProtoError(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 {
|
||||
Self::new(io::ErrorKind::Other, format!("https: {}", err))
|
||||
}
|
||||
}
|
20
crates/proto/src/quic/mod.rs
Normal file
20
crates/proto/src/quic/mod.rs
Normal file
@ -0,0 +1,20 @@
|
||||
// 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.
|
||||
|
||||
//! TLS protocol related components for DNS over HTTPS (DoH)
|
||||
|
||||
const MIME_APPLICATION_DNS: &str = "application/dns-message";
|
||||
const DNS_QUERY_PATH: &str = "/dns-query";
|
||||
|
||||
mod error;
|
||||
mod quic_client_stream;
|
||||
|
||||
pub use self::error::{Error as QuicError, Result as QuicResult};
|
||||
|
||||
pub use self::quic_client_stream::{
|
||||
QuicClientConnect, QuicClientResponse, QuicClientStream, QuicClientStreamBuilder,
|
||||
};
|
406
crates/proto/src/quic/quic_client_stream.rs
Normal file
406
crates/proto/src/quic/quic_client_stream.rs
Normal file
@ -0,0 +1,406 @@
|
||||
// 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::convert::TryInto;
|
||||
use std::fmt::{self, Display};
|
||||
use std::future::Future;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::DerefMut;
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use futures_util::future::{FutureExt, TryFutureExt};
|
||||
use futures_util::ready;
|
||||
use futures_util::stream::Stream;
|
||||
use log::{debug, warn};
|
||||
use quinn_proto::{ClientConfig, TransportConfig, VarInt};
|
||||
use rustls::ClientConfig as TlsClientConfig;
|
||||
use tokio_rustls::{
|
||||
client::TlsStream as TokioTlsClientStream, Connect as TokioTlsConnect, TlsConnector,
|
||||
};
|
||||
|
||||
use crate::error::ProtoError;
|
||||
use crate::iocompat::AsyncIoStdAsTokio;
|
||||
use crate::tcp::Connect;
|
||||
use crate::udp::UdpSocket;
|
||||
use crate::xfer::{DnsRequest, DnsRequestSender, DnsResponse, DnsResponseStream, SerialMessage};
|
||||
|
||||
/// ```
|
||||
/// 5.1. Connection Establishment
|
||||
///
|
||||
/// DoQ connections are established as described in the QUIC transport specification [RFC9000]. During connection establishment,
|
||||
/// DoQ support is indicated by selecting the ALPN token "doq" in the crypto handshake.
|
||||
/// ```
|
||||
pub const ALPN: &[u8] = b"doq";
|
||||
|
||||
/// [DoQ Error Codes](https://www.ietf.org/archive/id/draft-ietf-dprive-dnsoquic-10.html#name-doq-error-codes), draft-ietf-dprive-dnsoquic, Feb. 28, 2022
|
||||
/// ```text
|
||||
/// 5.3. DoQ Error Codes
|
||||
///
|
||||
/// The following error codes are defined for use when abruptly terminating streams, aborting reading of streams, or immediately closing connections:
|
||||
///
|
||||
/// DOQ_NO_ERROR (0x0):
|
||||
/// No error. This is used when the connection or stream needs to be closed, but there is no error to signal.
|
||||
///
|
||||
/// DOQ_INTERNAL_ERROR (0x1):
|
||||
/// The DoQ implementation encountered an internal error and is incapable of pursuing the transaction or the connection.
|
||||
///
|
||||
/// DOQ_PROTOCOL_ERROR (0x2):
|
||||
/// The DoQ implementation encountered an protocol error and is forcibly aborting the connection.
|
||||
///
|
||||
/// DOQ_REQUEST_CANCELLED (0x3):
|
||||
/// A DoQ client uses this to signal that it wants to cancel an outstanding transaction.
|
||||
///
|
||||
/// DOQ_EXCESSIVE_LOAD (0x4):
|
||||
/// A DoQ implementation uses this to signal when closing a connection due to excessive load.
|
||||
///
|
||||
/// DOQ_ERROR_RESERVED (0xd098ea5e):
|
||||
/// Alternative error code used for tests.
|
||||
/// ```
|
||||
#[repr(u32)]
|
||||
pub enum DoqErrorCode {
|
||||
/// No error. This is used when the connection or stream needs to be closed, but there is no error to signal.
|
||||
DoqNoError = 0x0,
|
||||
/// The DoQ implementation encountered an internal error and is incapable of pursuing the transaction or the connection.
|
||||
DoqInternalError = 0x1,
|
||||
/// The DoQ implementation encountered an protocol error and is forcibly aborting the connection.
|
||||
DoqProtocolError = 0x2,
|
||||
/// A DoQ client uses this to signal that it wants to cancel an outstanding transaction.
|
||||
DoqRequestCancelled = 0x3,
|
||||
/// A DoQ implementation uses this to signal when closing a connection due to excessive load.
|
||||
DoqExcessiveLoad = 0x4,
|
||||
/// Alternative error code used for tests.
|
||||
DoqErrorReserved = 0xd098ea5e,
|
||||
}
|
||||
|
||||
/// A DNS client connection for DNS-over-Quic
|
||||
#[derive(Clone)]
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct QuicClientStream<U: UdpSocket> {
|
||||
// Corresponds to the dns-name of the Quic server
|
||||
socket: U,
|
||||
quic: (),
|
||||
name_server_name: Arc<str>,
|
||||
name_server: SocketAddr,
|
||||
is_shutdown: bool,
|
||||
}
|
||||
|
||||
impl<U: UdpSocket> Display for QuicClientStream<U> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(
|
||||
formatter,
|
||||
"Quic({},{})",
|
||||
self.name_server, self.name_server_name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: UdpSocket> QuicClientStream<U> {
|
||||
/// Builder for QuicClientStream
|
||||
pub fn builder() -> QuicClientStreamBuilder {
|
||||
QuicClientStreamBuilder::default()
|
||||
}
|
||||
|
||||
async fn inner_send(
|
||||
quic: (),
|
||||
message: Bytes,
|
||||
name_server_name: Arc<str>,
|
||||
name_server: SocketAddr,
|
||||
) -> Result<DnsResponse, ProtoError> {
|
||||
todo!()
|
||||
// // and finally convert the bytes into a DNS message
|
||||
// let message = SerialMessage::new(response_bytes.to_vec(), name_server).to_message()?;
|
||||
// Ok(message.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: UdpSocket + Send + Unpin + 'static> DnsRequestSender for QuicClientStream<U> {
|
||||
/// The send loop for Quic in DNS stipulates that a new Quic "steam" should be opened and use for sending data.
|
||||
///
|
||||
/// It should be closed after receiving the response. TODO: AXFR/IXFR support...
|
||||
///
|
||||
/// ```text
|
||||
/// 5.2. Stream Mapping and Usage
|
||||
///
|
||||
/// The mapping of DNS traffic over QUIC streams takes advantage of the QUIC stream features detailed in Section 2 of [RFC9000],
|
||||
/// the QUIC transport specification.
|
||||
///
|
||||
/// DNS traffic follows a simple pattern in which the client sends a query, and the server provides one or more responses
|
||||
/// (multiple responses can occur in zone transfers).The mapping specified here requires that the client selects a separate
|
||||
/// QUIC stream for each query. The server then uses the same stream to provide all the response messages for that query. In
|
||||
/// order that multiple responses can be parsed, a 2-octet length field is used in exactly the same way as the 2-octet length
|
||||
/// field defined for DNS over TCP [RFC1035]. The practical result of this is that the content of each QUIC stream is exactly
|
||||
/// the same as the content of a TCP connection that would manage exactly one query.All DNS messages (queries and responses)
|
||||
/// sent over DoQ connections MUST be encoded as a 2-octet length field followed by the message content as specified in [RFC1035].
|
||||
/// The client MUST select the next available client-initiated bidirectional stream for each subsequent query on a QUIC connection,
|
||||
/// in conformance with the QUIC transport specification [RFC9000].The client MUST send the DNS query over the selected stream,
|
||||
/// and MUST indicate through the STREAM FIN mechanism that no further data will be sent on that stream.The server MUST send the
|
||||
/// response(s) on the same stream and MUST indicate, after the last response, through the STREAM FIN mechanism that no further
|
||||
/// data will be sent on that stream.Therefore, a single DNS transaction consumes a single bidirectional client-initiated stream.
|
||||
/// This means that the client's first query occurs on QUIC stream 0, the second on 4, and so on (see Section 2.1 of [RFC9000].
|
||||
/// Servers MAY defer processing of a query until the STREAM FIN has been indicated on the stream selected by the client. Servers
|
||||
/// and clients MAY monitor the number of "dangling" streams for which the expected queries or responses have been received but
|
||||
/// not the STREAM FIN. Implementations MAY impose a limit on the number of such dangling streams. If limits are encountered,
|
||||
/// implementations MAY close the connection.
|
||||
///
|
||||
/// 5.2.1. DNS Message IDs
|
||||
///
|
||||
/// When sending queries over a QUIC connection, the DNS Message ID MUST be set to zero. The stream mapping for DoQ allows for
|
||||
/// unambiguous correlation of queries and responses and so the Message ID field is not required.
|
||||
///
|
||||
/// This has implications for proxying DoQ message to and from other transports. For example, proxies may have to manage the
|
||||
/// fact that DoQ can support a larger number of outstanding queries on a single connection than e.g., DNS over TCP because DoQ
|
||||
/// is not limited by the Message ID space. This issue already exists for DoH, where a Message ID of 0 is recommended.When forwarding
|
||||
/// a DNS message from DoQ over another transport, a DNS Message ID MUST be generated according to the rules of the protocol that is
|
||||
/// in use. When forwarding a DNS message from another transport over DoQ, the Message ID MUST be set to zero.
|
||||
/// ```
|
||||
fn send_message(&mut self, mut message: DnsRequest) -> DnsResponseStream {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn shutdown(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn is_shutdown(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: UdpSocket> Stream for QuicClientStream<U> {
|
||||
type Item = Result<(), ProtoError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Quic connection builder for DNS-over-Quic
|
||||
#[derive(Clone)]
|
||||
pub struct QuicClientStreamBuilder {
|
||||
crypto_config: Arc<TlsClientConfig>,
|
||||
transport_config: Arc<TransportConfig>,
|
||||
bind_addr: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
impl QuicClientStreamBuilder {
|
||||
/// Constructs a new TlsStreamBuilder with the associated ClientConfig
|
||||
pub fn crypto_config(&mut self, crypto_config: Arc<TlsClientConfig>) -> &mut Self {
|
||||
self.crypto_config = crypto_config;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the address to connect from.
|
||||
pub fn bind_addr(&mut self, bind_addr: SocketAddr) -> &mut Self {
|
||||
self.bind_addr = Some(bind_addr);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a good set of defaults for the DoQ transport config
|
||||
pub fn default_transport_config(&mut self) -> &mut Self {
|
||||
self.set_transport_config(TransportConfig::default())
|
||||
}
|
||||
|
||||
/// This will override the max_concurrent_bidi_streams and max_concurrent_uni_streams to 0, as DoQ doesn't support server push
|
||||
pub fn set_transport_config(&mut self, mut transport_config: TransportConfig) -> &mut Self {
|
||||
sanitize_transport_config(&mut transport_config);
|
||||
self.transport_config = Arc::new(transport_config);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a new QuicStream to the specified name_server
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name_server` - IP and Port for the remote DNS resolver
|
||||
/// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate
|
||||
pub fn build<U: UdpSocket + 'static>(
|
||||
self,
|
||||
name_server: SocketAddr,
|
||||
dns_name: String,
|
||||
) -> QuicClientConnect<U> {
|
||||
let connect = if let Some(bind_addr) = self.bind_addr {
|
||||
U::connect_with_bind(name_server, bind_addr)
|
||||
} else {
|
||||
U::connect(name_server)
|
||||
};
|
||||
|
||||
let connect = connect
|
||||
.map_ok(move |udp| QuicClientStream {
|
||||
socket: udp,
|
||||
quic: (),
|
||||
name_server,
|
||||
name_server_name: dns_name.into(),
|
||||
is_shutdown: false,
|
||||
})
|
||||
.map_err(ProtoError::from);
|
||||
|
||||
QuicClientConnect(Box::pin(connect))
|
||||
}
|
||||
}
|
||||
|
||||
fn sanitize_transport_config(transport_config: &mut TransportConfig) {
|
||||
transport_config.max_concurrent_bidi_streams(VarInt::from_u32(0));
|
||||
transport_config.max_concurrent_uni_streams(VarInt::from_u32(0));
|
||||
}
|
||||
|
||||
fn client_config_tls12_webpki_roots() -> TlsClientConfig {
|
||||
use rustls::{OwnedTrustAnchor, RootCertStore};
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject,
|
||||
ta.spki,
|
||||
ta.name_constraints,
|
||||
)
|
||||
}));
|
||||
|
||||
let mut client_config = TlsClientConfig::builder()
|
||||
.with_safe_default_cipher_suites()
|
||||
.with_safe_default_kx_groups()
|
||||
.with_protocol_versions(&[&rustls::version::TLS12])
|
||||
.unwrap()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth();
|
||||
|
||||
client_config.alpn_protocols = vec![ALPN.to_vec()];
|
||||
client_config
|
||||
}
|
||||
|
||||
impl Default for QuicClientStreamBuilder {
|
||||
fn default() -> Self {
|
||||
let mut transport_config = TransportConfig::default();
|
||||
sanitize_transport_config(&mut transport_config);
|
||||
|
||||
let mut client_config = client_config_tls12_webpki_roots();
|
||||
|
||||
Self {
|
||||
crypto_config: Arc::new(client_config),
|
||||
transport_config: Arc::new(transport_config),
|
||||
bind_addr: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A future that resolves to an QuicClientStream
|
||||
pub struct QuicClientConnect<U: UdpSocket>(
|
||||
Pin<Box<dyn Future<Output = Result<QuicClientStream<U>, ProtoError>> + Send>>,
|
||||
);
|
||||
|
||||
impl<U: UdpSocket> Future for QuicClientConnect<U> {
|
||||
type Output = Result<QuicClientStream<U>, ProtoError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.0.poll_unpin(cx)
|
||||
}
|
||||
}
|
||||
|
||||
struct TlsConfig {
|
||||
client_config: Arc<TlsClientConfig>,
|
||||
dns_name: Arc<str>,
|
||||
}
|
||||
|
||||
/// A future that resolves to
|
||||
pub struct QuicClientResponse(
|
||||
Pin<Box<dyn Future<Output = Result<DnsResponse, ProtoError>> + Send>>,
|
||||
);
|
||||
|
||||
impl Future for QuicClientResponse {
|
||||
type Output = Result<DnsResponse, ProtoError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.0.as_mut().poll(cx).map_err(ProtoError::from)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use rustls::KeyLogFile;
|
||||
use tokio::net::UdpSocket as TokioUdpSocket;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::iocompat::AsyncIoTokioAsStd;
|
||||
use crate::op::{Message, Query, ResponseCode};
|
||||
use crate::rr::{Name, RData, RecordType};
|
||||
use crate::xfer::{DnsRequestOptions, FirstAnswer};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_quic_google() {
|
||||
env_logger::builder().is_test(true).build();
|
||||
|
||||
let google = SocketAddr::from(([8, 8, 8, 8], 853));
|
||||
let mut request = Message::new();
|
||||
let query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
|
||||
request.add_query(query);
|
||||
|
||||
let request = DnsRequest::new(request, DnsRequestOptions::default());
|
||||
|
||||
let mut client_config = client_config_tls12_webpki_roots();
|
||||
client_config.key_log = Arc::new(KeyLogFile::new());
|
||||
|
||||
let mut builder = QuicClientStreamBuilder::default();
|
||||
builder.crypto_config(Arc::new(client_config));
|
||||
|
||||
let connect = builder.build::<TokioUdpSocket>(google, "dns.google".to_string());
|
||||
|
||||
// tokio runtime stuff...
|
||||
let runtime = Runtime::new().expect("could not start runtime");
|
||||
let mut quic = runtime.block_on(connect).expect("quic connect failed");
|
||||
|
||||
let response = runtime
|
||||
.block_on(quic.send_message(request).first_answer())
|
||||
.expect("send_message failed");
|
||||
|
||||
let record = &response.answers()[0];
|
||||
let addr = record
|
||||
.data()
|
||||
.and_then(RData::as_a)
|
||||
.expect("Expected A record");
|
||||
|
||||
assert_eq!(addr, &Ipv4Addr::new(93, 184, 216, 34));
|
||||
|
||||
//
|
||||
// assert that the connection works for a second query
|
||||
let mut request = Message::new();
|
||||
let query = Query::query(
|
||||
Name::from_str("www.example.com.").unwrap(),
|
||||
RecordType::AAAA,
|
||||
);
|
||||
request.add_query(query);
|
||||
let request = DnsRequest::new(request, DnsRequestOptions::default());
|
||||
|
||||
for _ in 0..3 {
|
||||
let response = runtime
|
||||
.block_on(quic.send_message(request.clone()).first_answer())
|
||||
.expect("send_message failed");
|
||||
if response.response_code() == ResponseCode::ServFail {
|
||||
continue;
|
||||
}
|
||||
|
||||
let record = &response.answers()[0];
|
||||
let addr = record
|
||||
.data()
|
||||
.and_then(RData::as_aaaa)
|
||||
.expect("invalid response, expected A record");
|
||||
|
||||
assert_eq!(
|
||||
addr,
|
||||
&Ipv6Addr::new(0x2606, 0x2800, 0x0220, 0x0001, 0x0248, 0x1893, 0x25c8, 0x1946)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -30,7 +30,13 @@ where
|
||||
/// Time implementation used for this type
|
||||
type Time: Time;
|
||||
|
||||
/// UdpSocket
|
||||
/// setups up a "client" udp connection that will only receive packets from the associated address
|
||||
async fn connect(addr: SocketAddr) -> io::Result<Self>;
|
||||
|
||||
/// same as connect, but binds to the specified local address for seding address
|
||||
async fn connect_with_bind(addr: SocketAddr, bind_addr: SocketAddr) -> io::Result<Self>;
|
||||
|
||||
/// a "server" UDP socket, that bind to the local listening address, and unbound remote address (can receive from anything)
|
||||
async fn bind(addr: SocketAddr) -> io::Result<Self>;
|
||||
|
||||
/// Poll once Receive data from the socket and returns the number of bytes read and the address from
|
||||
@ -268,6 +274,32 @@ impl<S: UdpSocket> Future for NextRandomUdpSocket<S> {
|
||||
impl UdpSocket for tokio::net::UdpSocket {
|
||||
type Time = crate::TokioTime;
|
||||
|
||||
/// setups up a "client" udp connection that will only receive packets from the associated address
|
||||
///
|
||||
/// if the addr is ipv4 then it will bind local addr to 0.0.0.0:0, ipv6 [::]0
|
||||
// FIXME: make bind_addr ToSocketAddrs
|
||||
async fn connect(addr: SocketAddr) -> io::Result<Self> {
|
||||
let bind_addr: SocketAddr = match addr {
|
||||
SocketAddr::V4(_addr) => (Ipv4Addr::UNSPECIFIED, 0).into(),
|
||||
SocketAddr::V6(_addr) => (Ipv6Addr::UNSPECIFIED, 0).into(),
|
||||
};
|
||||
|
||||
let socket = Self::bind(bind_addr).await?;
|
||||
socket.connect(addr);
|
||||
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
/// same as connect, but binds to the specified local address for seding address
|
||||
// FIXME: make bind_addr ToSocketAddrs
|
||||
async fn connect_with_bind(addr: SocketAddr, bind_addr: SocketAddr) -> io::Result<Self> {
|
||||
let socket = Self::bind(bind_addr).await?;
|
||||
socket.connect(addr);
|
||||
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
// FIXME: make bind_addr ToSocketAddrs
|
||||
async fn bind(addr: SocketAddr) -> io::Result<Self> {
|
||||
Self::bind(addr).await
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ dns-over-tls = []
|
||||
dns-over-https-rustls = ["trust-dns-proto/dns-over-https-rustls", "dns-over-rustls", "dns-over-https"]
|
||||
dns-over-https = ["trust-dns-proto/dns-over-https"]
|
||||
|
||||
dns-over-quic = ["rustls/quic", "dns-over-rustls", "trust-dns-proto/dns-over-quic"]
|
||||
|
||||
dnssec-openssl = ["dnssec", "trust-dns-proto/dnssec-openssl"]
|
||||
dnssec-ring = ["dnssec", "trust-dns-proto/dnssec-ring"]
|
||||
dnssec = []
|
||||
|
@ -29,6 +29,8 @@ use tokio_rustls::client::TlsStream as TokioTlsStream;
|
||||
use proto::https::{HttpsClientConnect, HttpsClientStream};
|
||||
#[cfg(feature = "mdns")]
|
||||
use proto::multicast::{MdnsClientConnect, MdnsClientStream, MdnsQueryType};
|
||||
#[cfg(feature = "dns-over-quic")]
|
||||
use proto::quic::{QuicClientConnect, QuicClientStream};
|
||||
use proto::{
|
||||
self,
|
||||
error::ProtoError,
|
||||
@ -267,6 +269,8 @@ pub(crate) enum ConnectionConnect<R: RuntimeProvider> {
|
||||
),
|
||||
#[cfg(feature = "dns-over-https")]
|
||||
Https(DnsExchangeConnect<HttpsClientConnect<R::Tcp>, HttpsClientStream, TokioTime>),
|
||||
#[cfg(feature = "dns-over-quic")]
|
||||
Quic(DnsExchangeConnect<QuicClientConnect<R::Udp>, QuicClientStream<R::Udp>, TokioTime>),
|
||||
#[cfg(feature = "mdns")]
|
||||
Mdns(
|
||||
DnsExchangeConnect<
|
||||
@ -311,6 +315,12 @@ impl<R: RuntimeProvider> Future for ConnectionFuture<R> {
|
||||
self.spawner.spawn_bg(bg);
|
||||
GenericConnection(conn)
|
||||
}
|
||||
#[cfg(feature = "dns-over-quic")]
|
||||
ConnectionConnect::Quic(ref mut conn) => {
|
||||
let (conn, bg) = ready!(conn.poll_unpin(cx))?;
|
||||
self.spawner.spawn_bg(bg);
|
||||
GenericConnection(conn)
|
||||
}
|
||||
#[cfg(feature = "mdns")]
|
||||
ConnectionConnect::Mdns(ref mut conn) => {
|
||||
let (conn, bg) = ready!(conn.poll_unpin(cx))?;
|
||||
|
Loading…
Reference in New Issue
Block a user