#![doc = include_str!("../README.md")]
use core::{
fmt,
net::{Ipv4Addr, Ipv6Addr},
num::NonZeroU32,
};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
mod client;
pub mod dial;
mod error;
pub mod frame;
pub use client::{Client, DefaultClient};
pub use error::Error;
#[repr(C)]
#[derive(Copy, Clone, PartialEq, KnownLayout, Immutable, IntoBytes, FromBytes)]
pub struct Nonce(pub [u8; 24]);
impl fmt::Debug for Nonce {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for b in self.0.iter() {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
impl From<crypto_box::aead::Nonce<crypto_box::SalsaBox>> for Nonce {
fn from(value: crypto_box::aead::Nonce<crypto_box::SalsaBox>) -> Self {
Nonce(value.into())
}
}
impl From<Nonce> for crypto_box::aead::Nonce<crypto_box::SalsaBox> {
fn from(value: Nonce) -> Self {
value.0.into()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RegionId(pub NonZeroU32);
impl fmt::Display for RegionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.get().fmt(f)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegionInfo {
pub name: String,
pub code: String,
pub no_measure_no_home: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ServerConnInfo {
pub hostname: String,
pub ipv4: IpUsage<Ipv4Addr>,
pub ipv6: IpUsage<Ipv6Addr>,
pub tls_validation_config: TlsValidationConfig,
pub https_port: u16,
pub stun_port: Option<u16>,
pub stun_only: bool,
pub supports_port_80: bool,
}
impl ServerConnInfo {
pub fn default_from_url(url: &url::Url) -> Option<Self> {
if url.scheme() != "https" {
return None;
}
let hostname = url.host_str()?.to_owned();
let https_port = url.port().unwrap_or(443);
Some(Self {
hostname: hostname.clone(),
ipv4: IpUsage::UseDns,
ipv6: IpUsage::UseDns,
tls_validation_config: TlsValidationConfig::CommonName {
common_name: hostname,
},
https_port,
stun_port: Some(3478),
supports_port_80: false,
stun_only: false,
})
}
pub fn https_url_ipv4(&self) -> Result<Option<url::Url>, url::ParseError> {
match self.ipv4 {
IpUsage::Disable => Ok(None),
IpUsage::FixedAddr(addr) => {
url::Url::parse(&format!("https://{addr}:{}", self.https_port)).map(Some)
}
IpUsage::UseDns => {
url::Url::parse(&format!("https://{}:{}", self.hostname, self.https_port)).map(Some)
}
}
}
pub fn https_url_ipv6(&self) -> Result<Option<url::Url>, url::ParseError> {
match self.ipv6 {
IpUsage::Disable => Ok(None),
IpUsage::FixedAddr(addr) => {
url::Url::parse(&format!("https://{addr}:{}", self.https_port)).map(Some)
}
IpUsage::UseDns => {
url::Url::parse(&format!("https://{}:{}", self.hostname, self.https_port)).map(Some)
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum IpUsage<T> {
Disable,
UseDns,
FixedAddr(T),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum TlsValidationConfig {
SelfSigned {
sha256: [u8; 64],
},
CommonName {
common_name: String,
},
#[cfg(feature = "insecure-for-tests")]
InsecureForTests,
}
impl TlsValidationConfig {
const TLS_SELFSIGNED_PREFIX: &str = "sha256-raw:";
pub fn from_str(s: &str, hostname: &str) -> TlsValidationConfig {
match s {
"" => TlsValidationConfig::CommonName {
common_name: hostname.to_owned(),
},
x if x.starts_with(Self::TLS_SELFSIGNED_PREFIX) => {
let mut buf = [0u8; 64];
let bs = x.strip_prefix(Self::TLS_SELFSIGNED_PREFIX).unwrap().trim();
match hex::decode_to_slice(bs, &mut buf) {
Ok(()) => TlsValidationConfig::SelfSigned { sha256: buf },
Err(e) => {
tracing::error!(error = %e, "invalid tls selfsigned cert, falling back to hostname");
TlsValidationConfig::CommonName {
common_name: hostname.to_owned(),
}
}
}
}
x => TlsValidationConfig::CommonName {
common_name: x.to_owned(),
},
}
}
}