Add TransportTargetAddr as the target address for a PT-using channel

As with `TransportId`, this type only gets complicated when
`pt-client` is enabled: it's meant to stay simple for relays and
non-PT-using clients.
This commit is contained in:
Nick Mathewson 2022-09-22 14:54:42 -04:00
parent 63dbc7b22d
commit b056e69e1b
2 changed files with 118 additions and 1 deletions

View File

@ -83,4 +83,4 @@ pub use ids::{set::RelayIdSet, RelayId, RelayIdError, RelayIdRef, RelayIdType, R
pub use ls::LinkSpec;
pub use owned::{OwnedChanTarget, OwnedCircTarget, RelayIds};
pub use traits::{ChanTarget, CircTarget, HasAddrs, HasRelayIds, HasRelayIdsLegacy};
pub use transport::{TransportId, TransportIdError};
pub use transport::{TransportAddrError, TransportId, TransportIdError, TransportTargetAddr};

View File

@ -101,6 +101,99 @@ impl TransportId {
}
}
/// This identifier is used to indicate no transport address.
const NONE_ADDR: &str = "<none>";
/// An address that an be passed to a transport to tell it where to
/// connect (typically, to a bridge).
///
/// Not every transport accepts all kinds of addresses.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TransportTargetAddr {
/// An IP address and port for a Tor relay.
///
/// This is the only address type supported by the BuiltIn transport.
IpPort(std::net::SocketAddr),
/// A hostname-and-port target address. Some transports may support this.
#[cfg(feature = "pt-client")]
HostPort(String, u16),
/// A completely absent target address. Some transports support this.
#[cfg(feature = "pt-client")]
None,
}
/// An error from parsing a [`TransportTargetAddr`].
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum TransportAddrError {
/// We were compiled without support for addresses of this type.
#[error("Not compiled with pluggable transport support.")]
NoSupport,
/// We cannot parse this address.
#[error("Cannot parse {0:?} as an address.")]
BadAddress(String),
}
#[allow(clippy::unnecessary_wraps)]
impl TransportTargetAddr {
/// Helper: Construct a `HostPort` instance or return a `NoSupport` error.
#[cfg(feature = "pt-client")]
fn host_port(host: &str, port: u16) -> Result<Self, TransportAddrError> {
Ok(TransportTargetAddr::HostPort(host.to_string(), port))
}
/// Helper: Construct a `None` instance or return a `NoSupport` error.
#[cfg(feature = "pt-client")]
fn none() -> Result<Self, TransportAddrError> {
Ok(TransportTargetAddr::None)
}
/// Helper: Construct a `HostPort` instance or return a `NoSupport` error.
#[cfg(not(feature = "pt-client"))]
fn host_port(_host: &str, _port: u16) -> Result<Self, TransportAddrError> {
Err(TransportAddrError::NoSupport)
}
/// Helper: Construct a `None` instance or return a `NoSupport` error.
#[cfg(not(feature = "pt-client"))]
fn none() -> Result<Self, TransportAddrError> {
Err(TransportAddrError::NoSupport)
}
}
impl std::str::FromStr for TransportTargetAddr {
type Err = TransportAddrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(addr) = s.parse() {
Ok(TransportTargetAddr::IpPort(addr))
} else if let Some((name, port)) = s.rsplit_once(':') {
let port = port
.parse()
.map_err(|_| TransportAddrError::BadAddress(s.to_string()))?;
Self::host_port(name, port)
} else if s == NONE_ADDR {
Self::none()
} else {
Err(TransportAddrError::BadAddress(s.to_string()))
}
}
}
impl std::fmt::Display for TransportTargetAddr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TransportTargetAddr::IpPort(addr) => write!(f, "{}", addr),
#[cfg(feature = "pt-client")]
TransportTargetAddr::HostPort(host, port) => write!(f, "{}:{}", host, port),
#[cfg(feature = "pt-client")]
TransportTargetAddr::None => write!(f, "{}", NONE_ADDR),
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
@ -157,4 +250,28 @@ mod test {
Err(TransportIdError::BadId(_))
));
}
#[test]
#[cfg(feature = "pt-client")]
fn addr() {
for addr in &["1.2.3.4:555", "[::1]:9999"] {
let a: TransportTargetAddr = addr.parse().unwrap();
assert_eq!(&a.to_string(), addr);
}
for addr in &["www.example.com:9100", "<none>"] {
if cfg!(feature = "pt-client") {
let a: TransportTargetAddr = addr.parse().unwrap();
assert_eq!(&a.to_string(), addr);
} else {
let e = TransportTargetAddr::from_str(addr).unwrap_err();
assert!(matches!(e, TransportAddrError::NoSupport));
}
}
for addr in &["foobar", "<<<>>>"] {
let e = TransportTargetAddr::from_str(addr).unwrap_err();
assert!(matches!(e, TransportAddrError::BadAddress(_)));
}
}
}