api for quic client streams implemented

This commit is contained in:
Benjamin Fry 2022-03-21 10:46:22 -07:00
parent ca2c1303d1
commit eb61a9b296
10 changed files with 687 additions and 3 deletions

58
Cargo.lock generated
View File

@ -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",

View File

@ -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

View File

@ -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 }

View File

@ -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")))]

View 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))
}
}

View 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,
};

View 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)
);
}
}
}

View File

@ -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
}

View File

@ -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 = []

View File

@ -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))?;