Configuration of bind address for resolver.

Allows configuration of the local address that is used by the resolver.
This is useful if the system has multiple active network connections
and it is desirable to specify which interface is used for name
resolution.
This commit is contained in:
Sebastian Urban 2020-11-04 00:07:30 +01:00 committed by Benjamin Fry
parent aeb39cca38
commit 6ef9b4ec4a
34 changed files with 526 additions and 98 deletions

42
Cargo.lock generated
View File

@ -134,6 +134,23 @@ dependencies = [
"event-listener",
]
[[package]]
name = "async-process"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6"
dependencies = [
"async-io",
"blocking",
"cfg-if",
"event-listener",
"futures-lite",
"libc",
"once_cell",
"signal-hook",
"winapi",
]
[[package]]
name = "async-std"
version = "1.9.0"
@ -145,6 +162,7 @@ dependencies = [
"async-global-executor",
"async-io",
"async-lock",
"async-process",
"crossbeam-utils",
"futures-channel",
"futures-core",
@ -171,6 +189,7 @@ dependencies = [
"futures-io",
"futures-util",
"pin-utils",
"socket2",
"trust-dns-resolver",
]
@ -1313,6 +1332,25 @@ dependencies = [
"syn",
]
[[package]]
name = "signal-hook"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.4"
@ -1327,9 +1365,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "socket2"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad"
checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
dependencies = [
"libc",
"winapi",

View File

@ -64,12 +64,13 @@ name = "async_std_resolver"
path = "src/lib.rs"
[dependencies]
async-std = "1.6"
async-std = { version = "1.6", features = ["unstable"] }
async-trait = "0.1.42"
futures-io = { version = "0.3.5", default-features = false, features = ["std"] }
futures-util = { version = "0.3.5", default-features = false, features = ["std"] }
pin-utils = "0.1.0"
trust-dns-resolver = { version = "0.21.0-alpha.4", path = "../resolver", default-features = false }
socket2 = "0.4.2"
[dev-dependencies]
async-std = { version = "1.6", features = ["attributes"] }

View File

@ -10,10 +10,12 @@ use std::net::SocketAddr;
use std::pin::Pin;
use std::task::{Context, Poll};
use async_std::task::spawn_blocking;
use async_trait::async_trait;
use futures_io::{AsyncRead, AsyncWrite};
use futures_util::future::FutureExt;
use pin_utils::pin_mut;
use socket2::{Domain, Protocol, Socket, Type};
use trust_dns_resolver::proto::tcp::{Connect, DnsTcpStream};
use trust_dns_resolver::proto::udp::UdpSocket;
@ -71,8 +73,28 @@ impl DnsTcpStream for AsyncStdTcpStream {
#[async_trait]
impl Connect for AsyncStdTcpStream {
async fn connect(addr: SocketAddr) -> io::Result<Self> {
let stream = async_std::net::TcpStream::connect(addr).await?;
async fn connect_with_bind(
addr: SocketAddr,
bind_addr: Option<SocketAddr>,
) -> io::Result<Self> {
let stream = match bind_addr {
Some(bind_addr) => {
spawn_blocking(move || {
let domain = match bind_addr {
SocketAddr::V4(_) => Domain::IPV4,
SocketAddr::V6(_) => Domain::IPV6,
};
let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
socket.bind(&bind_addr.into())?;
socket.connect(&addr.into())?;
let std_stream: std::net::TcpStream = socket.into();
let stream = async_std::net::TcpStream::from(std_stream);
Ok::<_, io::Error>(stream)
})
.await?
}
None => async_std::net::TcpStream::connect(addr).await?,
};
stream.set_nodelay(true)?;
Ok(AsyncStdTcpStream(stream))
}

View File

@ -23,6 +23,7 @@ use crate::client::{ClientConnection, Signer};
#[derive(Clone)]
pub struct HttpsClientConnection<T> {
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
client_config: Arc<ClientConfig>,
marker: PhantomData<T>,
@ -44,9 +45,31 @@ impl<T> HttpsClientConnection<T> {
name_server: SocketAddr,
dns_name: String,
client_config: Arc<ClientConfig>,
) -> Self {
Self::new_with_bind_addr(name_server, None, dns_name, client_config)
}
/// Creates a new client connection with a specified source address.
///
/// *Note* this has side affects of starting the listening event_loop. Expect this to change in
/// the future.
///
/// # Arguments
///
/// * `name_server` - IP and Port for the remote DNS resolver
/// * `bind_addr` - IP and port to connect from
/// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate
/// * `client_config` - The TLS config
#[allow(clippy::new_ret_no_self)]
pub fn new_with_bind_addr(
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
client_config: Arc<ClientConfig>,
) -> Self {
Self {
name_server,
bind_addr,
dns_name,
client_config,
marker: PhantomData,
@ -67,8 +90,11 @@ where
_signer: Option<Arc<Signer>>,
) -> Self::SenderFuture {
// TODO: maybe signer needs to be applied in https...
let https_builder =
let mut https_builder =
HttpsClientStreamBuilder::with_client_config(Arc::clone(&self.client_config));
if let Some(bind_addr) = self.bind_addr {
https_builder.bind_addr(bind_addr);
}
https_builder.build(self.name_server, self.dns_name.clone())
}
}

View File

@ -25,6 +25,7 @@ use crate::proto::xfer::{DnsMultiplexer, DnsMultiplexerConnect};
#[derive(Clone, Copy)]
pub struct TcpClientConnection {
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
timeout: Duration,
}
@ -51,9 +52,29 @@ impl TcpClientConnection {
/// # Arguments
///
/// * `name_server` - address of the name server to use for queries
/// * `timeout` - connection timeout
pub fn with_timeout(name_server: SocketAddr, timeout: Duration) -> ClientResult<Self> {
Self::with_bind_addr_and_timeout(name_server, None, timeout)
}
/// Creates a new client connection.
///
/// *Note* this has side affects of establishing the connection to the specified DNS server and
/// starting the event_loop. Expect this to change in the future.
///
/// # Arguments
///
/// * `name_server` - address of the name server to use for queries
/// * `bind_addr` - IP address and port to connect from
/// * `timeout` - connection timeout
pub fn with_bind_addr_and_timeout(
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
timeout: Duration,
) -> ClientResult<Self> {
Ok(TcpClientConnection {
name_server,
bind_addr,
timeout,
})
}
@ -69,8 +90,9 @@ impl ClientConnection for TcpClientConnection {
fn new_stream(&self, signer: Option<Arc<Signer>>) -> Self::SenderFuture {
let (tcp_client_stream, handle) =
TcpClientStream::<AsyncIoTokioAsStd<TcpStream>>::with_timeout(
TcpClientStream::<AsyncIoTokioAsStd<TcpStream>>::with_bind_addr_and_timeout(
self.name_server,
self.bind_addr,
self.timeout,
);
DnsMultiplexer::new(tcp_client_stream, handle, signer)

View File

@ -25,6 +25,7 @@ use tokio::net::UdpSocket;
#[derive(Clone, Copy)]
pub struct UdpClientConnection {
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
timeout: Duration,
}
@ -40,8 +41,24 @@ impl UdpClientConnection {
/// Allows a custom timeout
pub fn with_timeout(name_server: SocketAddr, timeout: Duration) -> ClientResult<Self> {
Self::with_bind_addr_and_timeout(name_server, None, timeout)
}
/// Creates a new client connection. With a default timeout of 5 seconds
///
/// # Arguments
///
/// * `name_server` - address of the name server to use for queries
/// * `bind_addr` - IP address and port to connect from
/// * `timeout` - connection timeout
pub fn with_bind_addr_and_timeout(
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
timeout: Duration,
) -> ClientResult<Self> {
Ok(UdpClientConnection {
name_server,
bind_addr,
timeout,
})
}
@ -52,6 +69,11 @@ impl ClientConnection for UdpClientConnection {
type SenderFuture = UdpClientConnect<UdpSocket, Signer>;
fn new_stream(&self, signer: Option<Arc<Signer>>) -> Self::SenderFuture {
UdpClientStream::with_timeout_and_signer(self.name_server, self.timeout, signer)
UdpClientStream::with_timeout_and_signer_and_bind_addr(
self.name_server,
self.timeout,
signer,
self.bind_addr,
)
}
}

View File

@ -282,12 +282,21 @@ impl Stream for HttpsClientStream {
#[derive(Clone)]
pub struct HttpsClientStreamBuilder {
client_config: Arc<ClientConfig>,
bind_addr: Option<SocketAddr>,
}
impl HttpsClientStreamBuilder {
/// Constructs a new TlsStreamBuilder with the associated ClientConfig
pub fn with_client_config(client_config: Arc<ClientConfig>) -> Self {
HttpsClientStreamBuilder { client_config }
HttpsClientStreamBuilder {
client_config,
bind_addr: None,
}
}
/// Sets the address to connect from.
pub fn bind_addr(&mut self, bind_addr: SocketAddr) {
self.bind_addr = Some(bind_addr);
}
/// Creates a new HttpsStream to the specified name_server
@ -314,6 +323,7 @@ impl HttpsClientStreamBuilder {
HttpsClientConnect::<S>(HttpsClientConnectState::ConnectTcp {
name_server,
bind_addr: self.bind_addr,
tls: Some(tls),
})
}
@ -348,6 +358,7 @@ where
{
ConnectTcp {
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
tls: Option<TlsConfig>,
},
TcpConnecting {
@ -393,10 +404,11 @@ where
let next = match *self {
HttpsClientConnectState::ConnectTcp {
name_server,
bind_addr,
ref mut tls,
} => {
debug!("tcp connecting to: {}", name_server);
let connect = S::connect(name_server);
let connect = S::connect_with_bind(name_server, bind_addr);
HttpsClientConnectState::TcpConnecting {
connect,
name_server,

View File

@ -52,6 +52,11 @@ impl<S: Connect> TlsClientStreamBuilder<S> {
self.0.identity(pkcs12);
}
/// Sets the address to connect from.
pub fn bind_addr(&mut self, bind_addr: SocketAddr) {
self.0.bind_addr(bind_addr);
}
/// Creates a new TlsStream to the specified name_server
///
/// # Arguments

View File

@ -67,6 +67,7 @@ pub fn tls_from_stream<S: Connect>(
pub struct TlsStreamBuilder<S> {
ca_chain: Vec<Certificate>,
identity: Option<Identity>,
bind_addr: Option<SocketAddr>,
marker: PhantomData<S>,
}
@ -76,6 +77,7 @@ impl<S: Connect> TlsStreamBuilder<S> {
TlsStreamBuilder {
ca_chain: vec![],
identity: None,
bind_addr: None,
marker: PhantomData,
}
}
@ -93,6 +95,11 @@ impl<S: Connect> TlsStreamBuilder<S> {
self.identity = Some(identity);
}
/// Sets the address to connect from.
pub fn bind_addr(&mut self, bind_addr: SocketAddr) {
self.bind_addr = Some(bind_addr);
}
/// Creates a new TlsStream to the specified name_server
///
/// [RFC 7858](https://tools.ietf.org/html/rfc7858), DNS over TLS, May 2016
@ -145,7 +152,7 @@ impl<S: Connect> TlsStreamBuilder<S> {
let ca_chain = self.ca_chain.clone();
let identity = self.identity;
let tcp_stream = S::connect(name_server).await;
let tcp_stream = S::connect_with_bind(name_server, self.bind_addr).await;
// TODO: for some reason the above wouldn't accept a ?
let tcp_stream = match tcp_stream {

View File

@ -60,11 +60,17 @@ impl<S: Connect> TlsClientStreamBuilder<S> {
self.0.identity(pkcs12);
}
/// Sets the address to connect from.
pub fn bind_addr(&mut self, bind_addr: SocketAddr) {
self.0.bind_addr(bind_addr);
}
/// Creates a new TlsStream to the specified name_server
///
/// # Arguments
///
/// * `name_server` - IP and Port for the remote DNS resolver
/// * `bind_addr` - IP and port to connect from
/// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate
#[allow(clippy::type_complexity)]
pub fn build(

View File

@ -129,13 +129,16 @@ async fn connect_tls<S: Connect>(
tls_config: ConnectConfiguration,
dns_name: String,
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
) -> Result<TokioTlsStream<AsyncIoStdAsTokio<S>>, io::Error> {
let tcp = S::connect(name_server).await.map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
})?;
let tcp = S::connect_with_bind(name_server, bind_addr)
.await
.map_err(|e| {
io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("tls error: {}", e),
)
})?;
let mut stream = tls_config
.into_ssl(&dns_name)
.and_then(|ssl| TokioTlsStream::new(ssl, AsyncIoStdAsTokio(tcp)))
@ -154,6 +157,7 @@ async fn connect_tls<S: Connect>(
pub struct TlsStreamBuilder<S> {
ca_chain: Vec<X509>,
identity: Option<ParsedPkcs12>,
bind_addr: Option<SocketAddr>,
marker: PhantomData<S>,
}
@ -163,6 +167,7 @@ impl<S: Connect> TlsStreamBuilder<S> {
TlsStreamBuilder {
ca_chain: vec![],
identity: None,
bind_addr: None,
marker: PhantomData,
}
}
@ -180,6 +185,11 @@ impl<S: Connect> TlsStreamBuilder<S> {
self.identity = Some(pkcs12);
}
/// Sets the address to connect from.
pub fn bind_addr(&mut self, bind_addr: SocketAddr) {
self.bind_addr = Some(bind_addr);
}
/// Creates a new TlsStream to the specified name_server
///
/// [RFC 7858](https://tools.ietf.org/html/rfc7858), DNS over TLS, May 2016
@ -248,7 +258,7 @@ impl<S: Connect> TlsStreamBuilder<S> {
// This set of futures collapses the next tcp socket into a stream which can be used for
// sending and receiving tcp packets.
let stream = Box::pin(
connect_tls(tls_config, dns_name, name_server).map_ok(move |s| {
connect_tls(tls_config, dns_name, name_server, self.bind_addr).map_ok(move |s| {
TcpStream::from_stream_with_receiver(
AsyncIoTokioAsStd(s),
name_server,

View File

@ -11,8 +11,10 @@ pub mod tls_client_stream;
pub mod tls_server;
pub mod tls_stream;
pub use self::tls_client_stream::{tls_client_connect, TlsClientStream};
pub use self::tls_stream::{tls_connect, tls_from_stream, TlsStream};
pub use self::tls_client_stream::{
tls_client_connect, tls_client_connect_with_bind_addr, TlsClientStream,
};
pub use self::tls_stream::{tls_connect, tls_connect_with_bind_addr, tls_from_stream, TlsStream};
#[cfg(test)]
pub(crate) mod tests;

View File

@ -18,7 +18,7 @@ use rustls::ClientConfig;
use crate::error::ProtoError;
use crate::iocompat::AsyncIoStdAsTokio;
use crate::iocompat::AsyncIoTokioAsStd;
use crate::rustls::tls_stream::tls_connect;
use crate::rustls::tls_stream::tls_connect_with_bind_addr;
use crate::tcp::{Connect, TcpClientStream};
use crate::xfer::BufDnsStreamHandle;
@ -31,6 +31,7 @@ pub type TlsClientStream<S> =
/// # Arguments
///
/// * `name_server` - IP and Port for the remote DNS resolver
/// * `bind_addr` - IP and port to connect from
/// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate
#[allow(clippy::type_complexity)]
pub fn tls_client_connect<S: Connect>(
@ -41,7 +42,28 @@ pub fn tls_client_connect<S: Connect>(
Pin<Box<dyn Future<Output = Result<TlsClientStream<S>, ProtoError>> + Send + Unpin>>,
BufDnsStreamHandle,
) {
let (stream_future, sender) = tls_connect(name_server, dns_name, client_config);
tls_client_connect_with_bind_addr(name_server, None, dns_name, client_config)
}
/// Creates a new TlsStream to the specified name_server connecting from a specific address.
///
/// # Arguments
///
/// * `name_server` - IP and Port for the remote DNS resolver
/// * `bind_addr` - IP and port to connect from
/// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate
#[allow(clippy::type_complexity)]
pub fn tls_client_connect_with_bind_addr<S: Connect>(
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
client_config: Arc<ClientConfig>,
) -> (
Pin<Box<dyn Future<Output = Result<TlsClientStream<S>, ProtoError>> + Send + Unpin>>,
BufDnsStreamHandle,
) {
let (stream_future, sender) =
tls_connect_with_bind_addr(name_server, bind_addr, dns_name, client_config);
let new_future = Box::pin(
stream_future

View File

@ -70,6 +70,7 @@ pub fn tls_from_stream<S: DnsTcpStream>(
/// # Arguments
///
/// * `name_server` - IP and Port for the remote DNS resolver
/// * `bind_addr` - IP and port to connect from
/// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate
#[allow(clippy::type_complexity)]
pub fn tls_connect<S: Connect>(
@ -88,6 +89,35 @@ pub fn tls_connect<S: Connect>(
>,
>,
BufDnsStreamHandle,
) {
tls_connect_with_bind_addr(name_server, None, dns_name, client_config)
}
/// Creates a new TlsStream to the specified name_server connecting from a specific address.
///
/// # Arguments
///
/// * `name_server` - IP and Port for the remote DNS resolver
/// * `bind_addr` - IP and port to connect from
/// * `dns_name` - The DNS name, Subject Public Key Info (SPKI) name, as associated to a certificate
#[allow(clippy::type_complexity)]
pub fn tls_connect_with_bind_addr<S: Connect>(
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
client_config: Arc<ClientConfig>,
) -> (
Pin<
Box<
dyn Future<
Output = Result<
TlsStream<AsyncIoTokioAsStd<TokioTlsClientStream<S>>>,
io::Error,
>,
> + Send,
>,
>,
BufDnsStreamHandle,
) {
let (message_sender, outbound_messages) = BufDnsStreamHandle::new(name_server);
let early_data_enabled = client_config.enable_early_data;
@ -98,6 +128,7 @@ pub fn tls_connect<S: Connect>(
let stream = Box::pin(connect_tls(
tls_connector,
name_server,
bind_addr,
dns_name,
outbound_messages,
));
@ -108,10 +139,11 @@ pub fn tls_connect<S: Connect>(
async fn connect_tls<S: Connect>(
tls_connector: TlsConnector,
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
outbound_messages: StreamReceiver,
) -> io::Result<TcpStream<AsyncIoTokioAsStd<TokioTlsClientStream<S>>>> {
let tcp = S::connect(name_server).await?;
let tcp = S::connect_with_bind(name_server, bind_addr).await?;
let dns_name = match dns_name.as_str().try_into() {
Ok(name) => name,

View File

@ -26,10 +26,28 @@ pub use self::tcp_stream::{Connect, DnsTcpStream, TcpStream};
pub mod tokio {
use std::io;
use std::net::SocketAddr;
use tokio::net::TcpSocket as TokioTcpSocket;
use tokio::net::TcpStream as TokioTcpStream;
pub async fn connect(addr: &SocketAddr) -> Result<TokioTcpStream, io::Error> {
let stream = TokioTcpStream::connect(addr).await?;
connect_with_bind(addr, &None).await
}
pub async fn connect_with_bind(
addr: &SocketAddr,
bind_addr: &Option<SocketAddr>,
) -> Result<TokioTcpStream, io::Error> {
let stream = match bind_addr {
Some(bind_addr) => {
let socket = match bind_addr {
SocketAddr::V4(_) => TokioTcpSocket::new_v4()?,
SocketAddr::V6(_) => TokioTcpSocket::new_v6()?,
};
socket.bind(*bind_addr)?;
socket.connect(*addr).await?
}
None => TokioTcpStream::connect(addr).await?,
};
stream.set_nodelay(true)?;
Ok(stream)
}

View File

@ -61,7 +61,24 @@ impl<S: Connect> TcpClientStream<S> {
name_server: SocketAddr,
timeout: Duration,
) -> (TcpClientConnect<S>, BufDnsStreamHandle) {
let (stream_future, sender) = TcpStream::<S>::with_timeout(name_server, timeout);
Self::with_bind_addr_and_timeout(name_server, None, timeout)
}
/// Constructs a new TcpStream for a client to the specified SocketAddr.
///
/// # Arguments
///
/// * `name_server` - the IP and Port of the DNS server to connect to
/// * `bind_addr` - the IP and port to connect from
/// * `timeout` - connection timeout
#[allow(clippy::new_ret_no_self)]
pub fn with_bind_addr_and_timeout(
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
timeout: Duration,
) -> (TcpClientConnect<S>, BufDnsStreamHandle) {
let (stream_future, sender) =
TcpStream::<S>::with_bind_addr_and_timeout(name_server, bind_addr, timeout);
let new_future = Box::pin(
stream_future
@ -139,8 +156,13 @@ where
#[cfg(feature = "tokio-runtime")]
#[async_trait]
impl Connect for AsyncIoTokioAsStd<TokioTcpStream> {
async fn connect(addr: SocketAddr) -> io::Result<Self> {
super::tokio::connect(&addr).await.map(AsyncIoTokioAsStd)
async fn connect_with_bind(
addr: SocketAddr,
bind_addr: Option<SocketAddr>,
) -> io::Result<Self> {
super::tokio::connect_with_bind(&addr, &bind_addr)
.await
.map(AsyncIoTokioAsStd)
}
}

View File

@ -35,7 +35,13 @@ pub trait DnsTcpStream: AsyncRead + AsyncWrite + Unpin + Send + Sync + Sized + '
#[async_trait]
pub trait Connect: DnsTcpStream {
/// connect to tcp
async fn connect(addr: SocketAddr) -> io::Result<Self>;
async fn connect(addr: SocketAddr) -> io::Result<Self> {
Self::connect_with_bind(addr, None).await
}
/// connect to tcp with address to connect from
async fn connect_with_bind(addr: SocketAddr, bind_addr: Option<SocketAddr>)
-> io::Result<Self>;
}
/// Current state while writing to the remote of the TCP connection
@ -127,17 +133,40 @@ impl<S: Connect> TcpStream<S> {
// This set of futures collapses the next tcp socket into a stream which can be used for
// sending and receiving tcp packets.
let stream_fut = Self::connect(name_server, timeout, outbound_messages);
let stream_fut = Self::connect(name_server, None, timeout, outbound_messages);
(stream_fut, message_sender)
}
/// Creates a new future of the eventually establish a IO stream connection or fail trying
///
/// # Arguments
///
/// * `name_server` - the IP and Port of the DNS server to connect to
/// * `bind_addr` - the IP and port to connect from
/// * `timeout` - connection timeout
#[allow(clippy::type_complexity)]
pub fn with_bind_addr_and_timeout(
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
timeout: Duration,
) -> (
impl Future<Output = Result<TcpStream<S>, io::Error>> + Send,
BufDnsStreamHandle,
) {
let (message_sender, outbound_messages) = BufDnsStreamHandle::new(name_server);
let stream_fut = Self::connect(name_server, bind_addr, timeout, outbound_messages);
(stream_fut, message_sender)
}
async fn connect(
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
timeout: Duration,
outbound_messages: StreamReceiver,
) -> Result<TcpStream<S>, io::Error> {
let tcp = S::connect(name_server);
let tcp = S::connect_with_bind(name_server, bind_addr);
S::Time::timeout(timeout, tcp)
.map(move |tcp_stream: Result<Result<S, io::Error>, _>| {
tcp_stream

View File

@ -10,8 +10,10 @@ use crate::{Executor, Time};
/// Test next random udpsocket.
pub fn next_random_socket_test<S: UdpSocket + Send + 'static, E: Executor>(mut exec: E) {
let (stream, _) =
UdpStream::<S>::new(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 52));
let (stream, _) = UdpStream::<S>::new(
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 52),
None,
);
drop(
exec.block_on(stream)
.expect("failed to get next socket address"),

View File

@ -35,6 +35,7 @@ where
MF: MessageFinalizer,
{
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
timeout: Duration,
is_shutdown: bool,
signer: Option<Arc<MF>>,
@ -65,7 +66,22 @@ impl<S: Send> UdpClientStream<S, NoopMessageFinalizer> {
name_server: SocketAddr,
timeout: Duration,
) -> UdpClientConnect<S, NoopMessageFinalizer> {
Self::with_timeout_and_signer(name_server, timeout, None)
Self::with_bind_addr_and_timeout(name_server, None, timeout)
}
/// Constructs a new UdpStream for a client to the specified SocketAddr.
///
/// # Arguments
///
/// * `name_server` - the IP and Port of the DNS server to connect to
/// * `bind_addr` - the IP and port to connect from
/// * `timeout` - connection timeout
pub fn with_bind_addr_and_timeout(
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
timeout: Duration,
) -> UdpClientConnect<S, NoopMessageFinalizer> {
Self::with_timeout_and_signer_and_bind_addr(name_server, timeout, None, bind_addr)
}
}
@ -82,7 +98,30 @@ impl<S: Send, MF: MessageFinalizer> UdpClientStream<S, MF> {
signer: Option<Arc<MF>>,
) -> UdpClientConnect<S, MF> {
UdpClientConnect {
name_server: Some(name_server),
name_server,
bind_addr: None,
timeout,
signer,
marker: PhantomData::<S>,
}
}
/// Constructs a new TcpStream for a client to the specified SocketAddr.
///
/// # Arguments
///
/// * `name_server` - the IP and Port of the DNS server to connect to
/// * `timeout` - connection timeout
/// * `bind_addr` - the IP address and port to connect from
pub fn with_timeout_and_signer_and_bind_addr(
name_server: SocketAddr,
timeout: Duration,
signer: Option<Arc<MF>>,
bind_addr: Option<SocketAddr>,
) -> UdpClientConnect<S, MF> {
UdpClientConnect {
name_server,
bind_addr,
timeout,
signer,
marker: PhantomData::<S>,
@ -146,10 +185,13 @@ impl<S: UdpSocket + Send + 'static, MF: MessageFinalizer> DnsRequestSender
let message_id = message.id();
let message = SerialMessage::new(bytes, self.name_server);
let bind_addr = self.bind_addr;
S::Time::timeout::<Pin<Box<dyn Future<Output = Result<DnsResponse, ProtoError>> + Send>>>(
self.timeout,
Box::pin(send_serial_message::<S>(message, message_id, verifier)),
Box::pin(send_serial_message::<S>(
message, message_id, verifier, bind_addr,
)),
)
.into()
}
@ -183,7 +225,8 @@ where
S: Send,
MF: MessageFinalizer,
{
name_server: Option<SocketAddr>,
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
timeout: Duration,
signer: Option<Arc<MF>>,
marker: PhantomData<S>,
@ -195,10 +238,8 @@ impl<S: Send + Unpin, MF: MessageFinalizer> Future for UdpClientConnect<S, MF> {
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
// TODO: this doesn't need to be a future?
Poll::Ready(Ok(UdpClientStream::<S, MF> {
name_server: self
.name_server
.take()
.expect("UdpClientConnect invalid state: name_server"),
name_server: self.name_server,
bind_addr: self.bind_addr,
is_shutdown: false,
timeout: self.timeout,
signer: self.signer.take(),
@ -211,9 +252,10 @@ async fn send_serial_message<S: UdpSocket + Send>(
msg: SerialMessage,
msg_id: u16,
verifier: Option<MessageVerifier>,
bind_addr: Option<SocketAddr>,
) -> Result<DnsResponse, ProtoError> {
let name_server = msg.addr();
let socket: S = NextRandomUdpSocket::new(&name_server).await?;
let socket: S = NextRandomUdpSocket::new(&name_server, &bind_addr).await?;
let bytes = msg.bytes();
let addr = msg.addr();
let len_sent: usize = socket.send_to(bytes, addr).await?;

View File

@ -85,6 +85,7 @@ impl<S: UdpSocket + Send + 'static> UdpStream<S> {
#[allow(clippy::type_complexity)]
pub fn new(
remote_addr: SocketAddr,
bind_addr: Option<SocketAddr>,
) -> (
Box<dyn Future<Output = Result<UdpStream<S>, io::Error>> + Send + Unpin>,
BufDnsStreamHandle,
@ -93,7 +94,7 @@ impl<S: UdpSocket + Send + 'static> UdpStream<S> {
// TODO: allow the bind address to be specified...
// constructs a future for getting the next randomly bound port to a UdpSocket
let next_socket = NextRandomUdpSocket::new(&remote_addr);
let next_socket = NextRandomUdpSocket::new(&remote_addr, &bind_addr);
// This set of futures collapses the next udp socket into a stream which can be used for
// sending and receiving udp packets.
@ -183,68 +184,85 @@ impl<S: UdpSocket + Send + 'static> Stream for UdpStream<S> {
#[must_use = "futures do nothing unless polled"]
pub(crate) struct NextRandomUdpSocket<S> {
bind_address: IpAddr,
bind_address: SocketAddr,
marker: PhantomData<S>,
}
impl<S: UdpSocket> NextRandomUdpSocket<S> {
/// Creates a future for randomly binding to a local socket address for client connections.
pub(crate) fn new(name_server: &SocketAddr) -> NextRandomUdpSocket<S> {
let zero_addr: IpAddr = match *name_server {
SocketAddr::V4(..) => IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
SocketAddr::V6(..) => IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)),
/// Creates a future for randomly binding to a local socket address for client connections,
/// if no port is specified.
///
/// If a port is specified in the bind address it is used.
pub(crate) fn new(
name_server: &SocketAddr,
bind_addr: &Option<SocketAddr>,
) -> NextRandomUdpSocket<S> {
let bind_address = match bind_addr {
Some(ba) => *ba,
None => match *name_server {
SocketAddr::V4(..) => SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0),
SocketAddr::V6(..) => {
SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), 0)
}
},
};
NextRandomUdpSocket {
bind_address: zero_addr,
bind_address,
marker: PhantomData,
}
}
async fn bind(zero_addr: SocketAddr) -> Result<S, io::Error> {
S::bind(zero_addr).await
async fn bind(addr: SocketAddr) -> Result<S, io::Error> {
S::bind(addr).await
}
}
impl<S: UdpSocket> Future for NextRandomUdpSocket<S> {
type Output = Result<S, io::Error>;
/// polls until there is an available next random UDP port.
/// polls until there is an available next random UDP port,
/// if no port has been specified in bind_addr.
///
/// if there is no port available after 10 attempts, returns NotReady
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Per RFC 6056 Section 2.1:
//
// The dynamic port range defined by IANA consists of the 49152-65535
// range, and is meant for the selection of ephemeral ports.
let rand_port_range = Uniform::new_inclusive(49152_u16, u16::max_value());
let mut rand = rand::thread_rng();
if self.bind_address.port() == 0 {
// Per RFC 6056 Section 2.1:
//
// The dynamic port range defined by IANA consists of the 49152-65535
// range, and is meant for the selection of ephemeral ports.
let rand_port_range = Uniform::new_inclusive(49152_u16, u16::max_value());
let mut rand = rand::thread_rng();
for attempt in 0..10 {
let port = rand_port_range.sample(&mut rand);
let zero_addr = SocketAddr::new(self.bind_address, port);
for attempt in 0..10 {
let port = rand_port_range.sample(&mut rand);
let bind_addr = SocketAddr::new(self.bind_address.ip(), port);
// TODO: allow TTL to be adjusted...
// TODO: this immediate poll might be wrong in some cases...
match Box::pin(Self::bind(zero_addr)).as_mut().poll(cx) {
Poll::Ready(Ok(socket)) => {
debug!("created socket successfully");
return Poll::Ready(Ok(socket));
// TODO: allow TTL to be adjusted...
// TODO: this immediate poll might be wrong in some cases...
match Box::pin(Self::bind(bind_addr)).as_mut().poll(cx) {
Poll::Ready(Ok(socket)) => {
debug!("created socket successfully");
return Poll::Ready(Ok(socket));
}
Poll::Ready(Err(err)) => {
debug!("unable to bind port, attempt: {}: {}", attempt, err)
}
Poll::Pending => debug!("unable to bind port, attempt: {}", attempt),
}
Poll::Ready(Err(err)) => {
debug!("unable to bind port, attempt: {}: {}", attempt, err)
}
Poll::Pending => debug!("unable to bind port, attempt: {}", attempt),
}
debug!("could not get next random port, delaying");
// TODO: because no interest is registered anywhere, we must awake.
cx.waker().wake_by_ref();
// returning NotReady here, perhaps the next poll there will be some more socket available.
Poll::Pending
} else {
// Use port that was specified in bind address.
Box::pin(Self::bind(self.bind_address)).as_mut().poll(cx)
}
debug!("could not get next random port, delaying");
// TODO: because no interest is registered anywhere, we must awake.
cx.waker().wake_by_ref();
// returning NotReady here, perhaps the next poll there will be some more socket available.
Poll::Pending
}
}

View File

@ -402,6 +402,8 @@ pub struct NameServerConfig {
#[cfg_attr(feature = "serde-config", serde(skip))]
/// optional configuration for the tls client
pub tls_config: Option<TlsClientConfig>,
/// The client address (IP and port) to use for connecting to the server.
pub bind_addr: Option<SocketAddr>,
}
impl fmt::Display for NameServerConfig {
@ -478,6 +480,7 @@ impl NameServerConfigGroup {
trust_nx_responses,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
};
let tcp = NameServerConfig {
socket_addr: SocketAddr::new(*ip, port),
@ -486,6 +489,7 @@ impl NameServerConfigGroup {
trust_nx_responses,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
};
name_servers.push(udp);
@ -515,6 +519,7 @@ impl NameServerConfigGroup {
trust_nx_responses,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
};
name_servers.push(config);
@ -648,6 +653,14 @@ impl NameServerConfigGroup {
pub fn with_client_config(self, client_config: Arc<ClientConfig>) -> Self {
Self(self.0, Some(TlsClientConfig(client_config)))
}
/// Sets the client address (IP and port) to connect from on all name servers.
pub fn with_bind_addr(mut self, bind_addr: Option<SocketAddr>) -> Self {
for server in &mut self.0 {
server.bind_addr = bind_addr;
}
self
}
}
impl Default for NameServerConfigGroup {

View File

@ -12,6 +12,7 @@ use crate::config::TlsClientConfig;
#[allow(clippy::type_complexity)]
pub(crate) fn new_https_stream<R>(
socket_addr: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
client_config: Option<TlsClientConfig>,
) -> DnsExchangeConnect<HttpsClientConnect<R::Tcp>, HttpsClientStream, TokioTime>
@ -23,7 +24,10 @@ where
|TlsClientConfig(client_config)| client_config,
);
let https_builder = HttpsClientStreamBuilder::with_client_config(client_config);
let mut https_builder = HttpsClientStreamBuilder::with_client_config(client_config);
if let Some(bind_addr) = bind_addr {
https_builder.bind_addr(bind_addr);
}
DnsExchange::connect(https_builder.build::<R::Tcp>(socket_addr, dns_name))
}

View File

@ -119,17 +119,24 @@ where
) -> Self::FutureConn {
let dns_connect = match config.protocol {
Protocol::Udp => {
let stream =
UdpClientStream::<R::Udp>::with_timeout(config.socket_addr, options.timeout);
let stream = UdpClientStream::<R::Udp>::with_bind_addr_and_timeout(
config.socket_addr,
config.bind_addr,
options.timeout,
);
let exchange = DnsExchange::connect(stream);
ConnectionConnect::Udp(exchange)
}
Protocol::Tcp => {
let socket_addr = config.socket_addr;
let bind_addr = config.bind_addr;
let timeout = options.timeout;
let (stream, handle) =
TcpClientStream::<R::Tcp>::with_timeout(socket_addr, timeout);
let (stream, handle) = TcpClientStream::<R::Tcp>::with_bind_addr_and_timeout(
socket_addr,
bind_addr,
timeout,
);
// TODO: need config for Signer...
let dns_conn = DnsMultiplexer::with_timeout(
stream,
@ -144,17 +151,24 @@ where
#[cfg(feature = "dns-over-tls")]
Protocol::Tls => {
let socket_addr = config.socket_addr;
let bind_addr = config.bind_addr;
let timeout = options.timeout;
let tls_dns_name = config.tls_dns_name.clone().unwrap_or_default();
#[cfg(feature = "dns-over-rustls")]
let client_config = config.tls_config.clone();
#[cfg(feature = "dns-over-rustls")]
let (stream, handle) =
{ crate::tls::new_tls_stream::<R>(socket_addr, tls_dns_name, client_config) };
let (stream, handle) = {
crate::tls::new_tls_stream::<R>(
socket_addr,
bind_addr,
tls_dns_name,
client_config,
)
};
#[cfg(not(feature = "dns-over-rustls"))]
let (stream, handle) =
{ crate::tls::new_tls_stream::<R>(socket_addr, tls_dns_name) };
{ crate::tls::new_tls_stream::<R>(socket_addr, bind_addr, tls_dns_name) };
let dns_conn = DnsMultiplexer::with_timeout(
stream,
@ -169,12 +183,17 @@ where
#[cfg(feature = "dns-over-https")]
Protocol::Https => {
let socket_addr = config.socket_addr;
let bind_addr = config.bind_addr;
let tls_dns_name = config.tls_dns_name.clone().unwrap_or_default();
#[cfg(feature = "dns-over-rustls")]
let client_config = config.tls_config.clone();
let exchange =
crate::https::new_https_stream::<R>(socket_addr, tls_dns_name, client_config);
let exchange = crate::https::new_https_stream::<R>(
socket_addr,
bind_addr,
tls_dns_name,
client_config,
);
ConnectionConnect::Https(exchange)
}
#[cfg(feature = "mdns")]

View File

@ -253,6 +253,7 @@ where
trust_nx_responses,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
};
NameServer::new_with_provider(config, options, conn_provider)
}
@ -284,6 +285,7 @@ mod tests {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
};
let io_loop = Runtime::new().unwrap();
let runtime_handle = TokioHandle;
@ -322,6 +324,7 @@ mod tests {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
};
let io_loop = Runtime::new().unwrap();
let runtime_handle = TokioHandle;

View File

@ -446,6 +446,7 @@ mod tests {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
};
let config2 = NameServerConfig {
@ -455,6 +456,7 @@ mod tests {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
};
let mut resolver_config = ResolverConfig::new();
@ -518,6 +520,7 @@ mod tests {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
};
let opts = ResolverOpts {

View File

@ -67,6 +67,7 @@ fn into_resolver_config(
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
});
nameservers.push(NameServerConfig {
socket_addr: SocketAddr::new(ip.into(), DEFAULT_PORT),
@ -75,6 +76,7 @@ fn into_resolver_config(
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
});
}
if nameservers.is_empty() {
@ -126,6 +128,7 @@ mod tests {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
},
NameServerConfig {
socket_addr: addr,
@ -134,6 +137,7 @@ mod tests {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
},
]
}

View File

@ -35,6 +35,7 @@ fn get_name_servers() -> ResolveResult<Vec<NameServerConfig>> {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
});
name_servers.push(NameServerConfig {
socket_addr,
@ -43,6 +44,7 @@ fn get_name_servers() -> ResolveResult<Vec<NameServerConfig>> {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: None,
});
}
Ok(name_servers)

View File

@ -22,11 +22,15 @@ use crate::name_server::RuntimeProvider;
#[allow(clippy::type_complexity)]
pub(crate) fn new_tls_stream<R: RuntimeProvider>(
socket_addr: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
) -> (
Pin<Box<dyn Future<Output = Result<TlsClientStream<R::Tcp>, ProtoError>> + Send>>,
BufDnsStreamHandle,
) {
let tls_builder = TlsClientStreamBuilder::new();
let mut tls_builder = TlsClientStreamBuilder::new();
if let Some(bind_addr) = bind_addr {
tls_builder.bind_addr(bind_addr);
}
tls_builder.build(socket_addr, dns_name)
}

View File

@ -22,11 +22,15 @@ use crate::name_server::RuntimeProvider;
#[allow(clippy::type_complexity)]
pub(crate) fn new_tls_stream<R: RuntimeProvider>(
socket_addr: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
) -> (
Pin<Box<dyn Future<Output = Result<TlsClientStream<R::Tcp>, ProtoError>> + Send>>,
BufDnsStreamHandle,
) {
let tls_builder = TlsClientStreamBuilder::new();
let mut tls_builder = TlsClientStreamBuilder::new();
if let Some(bind_addr) = bind_addr {
tls_builder.bind_addr(bind_addr);
}
tls_builder.build(socket_addr, dns_name)
}

View File

@ -16,7 +16,7 @@ use futures_util::future::Future;
use rustls::{ClientConfig, OwnedTrustAnchor, RootCertStore};
use proto::error::ProtoError;
use proto::rustls::{tls_client_connect, TlsClientStream};
use proto::rustls::{tls_client_connect_with_bind_addr, TlsClientStream};
use proto::BufDnsStreamHandle;
use crate::config::TlsClientConfig;
@ -53,6 +53,7 @@ lazy_static! {
#[allow(clippy::type_complexity)]
pub(crate) fn new_tls_stream<R: RuntimeProvider>(
socket_addr: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
client_config: Option<TlsClientConfig>,
) -> (
@ -63,6 +64,7 @@ pub(crate) fn new_tls_stream<R: RuntimeProvider>(
|| CLIENT_CONFIG.clone(),
|TlsClientConfig(client_config)| client_config,
);
let (stream, handle) = tls_client_connect(socket_addr, dns_name, client_config);
let (stream, handle) =
tls_client_connect_with_bind_addr(socket_addr, bind_addr, dns_name, client_config);
(Box::pin(stream), handle)
}

View File

@ -8,9 +8,10 @@
//! TLS based DNS client connection for Client impls
//! TODO: This modules was moved from trust-dns-rustls, it really doesn't need to exist if tests are refactored...
use std::marker::PhantomData;
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::Arc;
use std::{marker::PhantomData, net::SocketAddr};
use futures::Future;
use rustls::ClientConfig;
@ -18,7 +19,7 @@ use rustls::ClientConfig;
use trust_dns_client::client::ClientConnection;
use trust_dns_client::client::Signer;
use trust_dns_proto::error::ProtoError;
use trust_dns_proto::rustls::{tls_client_connect, TlsClientStream};
use trust_dns_proto::rustls::{tls_client_connect_with_bind_addr, TlsClientStream};
use trust_dns_proto::tcp::Connect;
use trust_dns_proto::xfer::{DnsMultiplexer, DnsMultiplexerConnect};
@ -27,6 +28,7 @@ use trust_dns_proto::xfer::{DnsMultiplexer, DnsMultiplexerConnect};
/// Use with `trust_dns_client::client::Client` impls
pub struct TlsClientConnection<T> {
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
client_config: Arc<ClientConfig>,
marker: PhantomData<T>,
@ -35,11 +37,13 @@ pub struct TlsClientConnection<T> {
impl<T> TlsClientConnection<T> {
pub fn new(
name_server: SocketAddr,
bind_addr: Option<SocketAddr>,
dns_name: String,
client_config: Arc<ClientConfig>,
) -> Self {
TlsClientConnection {
name_server,
bind_addr,
dns_name,
client_config,
marker: PhantomData,
@ -57,8 +61,9 @@ impl<T: Connect> ClientConnection for TlsClientConnection<T> {
>;
fn new_stream(&self, signer: Option<Arc<Signer>>) -> Self::SenderFuture {
let (tls_client_stream, handle) = tls_client_connect(
let (tls_client_stream, handle) = tls_client_connect_with_bind_addr(
self.name_server,
self.bind_addr,
self.dns_name.clone(),
self.client_config.clone(),
);

View File

@ -95,6 +95,7 @@ fn mock_nameserver_on_send_nx<O: OnSend + Unpin>(
trust_nx_responses,
#[cfg(any(feature = "dns-over-rustls", feature = "dns-over-https-rustls"))]
tls_config: None,
bind_addr: None,
},
options,
client,

View File

@ -284,7 +284,7 @@ fn lazy_tls_client(
.with_root_certificates(root_store)
.with_no_client_auth();
TlsClientConnection::new(ipaddr, dns_name, Arc::new(config))
TlsClientConnection::new(ipaddr, None, dns_name, Arc::new(config))
}
fn client_thread_www<C: ClientConnection>(conn: C)

View File

@ -19,7 +19,7 @@
unreachable_pub
)]
use std::net::SocketAddr;
use std::net::{IpAddr, SocketAddr};
use console::style;
use structopt::StructOpt;
@ -72,6 +72,10 @@ struct Opts {
#[structopt(short = "n", long, require_delimiter = true)]
nameserver: Vec<SocketAddr>,
/// Specify the IP address to connect from.
#[structopt(long)]
bind: Option<IpAddr>,
/// Use ipv4 addresses only, default is both ipv4 and ipv6
#[structopt(long)]
ipv4: bool,
@ -152,6 +156,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: opts.bind.map(|ip| SocketAddr::new(ip, 0)),
});
name_servers.push(NameServerConfig {
@ -161,6 +166,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
trust_nx_responses: false,
#[cfg(feature = "dns-over-rustls")]
tls_config: None,
bind_addr: opts.bind.map(|ip| SocketAddr::new(ip, 0)),
});
}