refactor api to clean up internals
This commit is contained in:
@@ -11,6 +11,7 @@ mod connection_manager;
|
||||
mod connection_table;
|
||||
mod network_connection;
|
||||
mod tasks;
|
||||
mod types;
|
||||
|
||||
pub mod tests;
|
||||
|
||||
@@ -18,6 +19,7 @@ pub mod tests;
|
||||
|
||||
pub use connection_manager::*;
|
||||
pub use network_connection::*;
|
||||
pub use types::*;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
use connection_handle::*;
|
||||
@@ -1552,7 +1554,7 @@ impl NetworkManager {
|
||||
let peer_stats = nr.peer_stats();
|
||||
let peer = PeerTableData {
|
||||
node_ids: nr.node_ids().iter().map(|x| x.to_string()).collect(),
|
||||
peer_address: v.last_connection.remote(),
|
||||
peer_address: v.last_connection.remote().to_string(),
|
||||
peer_stats,
|
||||
};
|
||||
out.push(peer);
|
||||
|
||||
@@ -645,7 +645,7 @@ impl Network {
|
||||
log_net!(debug "enable address {:?} as ipv4", addr);
|
||||
inner.enable_ipv4 = true;
|
||||
} else if addr.is_ipv6() {
|
||||
let address = crate::Address::from_ip_addr(addr);
|
||||
let address = Address::from_ip_addr(addr);
|
||||
if address.is_global() {
|
||||
log_net!(debug "enable address {:?} as ipv6 global", address);
|
||||
inner.enable_ipv6_global = true;
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
pub mod test_connection_table;
|
||||
pub mod test_signed_node_info;
|
||||
|
||||
use super::*;
|
||||
|
||||
167
veilid-core/src/network_manager/tests/test_signed_node_info.rs
Normal file
167
veilid-core/src/network_manager/tests/test_signed_node_info.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use super::*;
|
||||
use crate::tests::common::test_veilid_config::*;
|
||||
|
||||
pub async fn test_signed_node_info() {
|
||||
info!("--- test_signed_node_info ---");
|
||||
|
||||
let (update_callback, config_callback) = setup_veilid_core();
|
||||
let api = api_startup(update_callback, config_callback)
|
||||
.await
|
||||
.expect("startup failed");
|
||||
|
||||
let crypto = api.crypto().unwrap();
|
||||
for ck in VALID_CRYPTO_KINDS {
|
||||
let vcrypto = crypto.get(ck).unwrap();
|
||||
|
||||
// Test direct
|
||||
let node_info = NodeInfo {
|
||||
network_class: NetworkClass::InboundCapable,
|
||||
outbound_protocols: ProtocolTypeSet::all(),
|
||||
address_types: AddressTypeSet::all(),
|
||||
envelope_support: VALID_ENVELOPE_VERSIONS.to_vec(),
|
||||
crypto_support: VALID_CRYPTO_KINDS.to_vec(),
|
||||
dial_info_detail_list: vec![DialInfoDetail {
|
||||
class: DialInfoClass::Mapped,
|
||||
dial_info: DialInfo::udp(SocketAddress::default()),
|
||||
}],
|
||||
};
|
||||
|
||||
// Test correct validation
|
||||
let keypair = vcrypto.generate_keypair();
|
||||
let sni = SignedDirectNodeInfo::make_signatures(
|
||||
crypto.clone(),
|
||||
vec![TypedKeyPair::new(ck, keypair)],
|
||||
node_info.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut tks: TypedKeySet = TypedKey::new(ck, keypair.key).into();
|
||||
let oldtkslen = tks.len();
|
||||
let _ = SignedDirectNodeInfo::new(
|
||||
crypto.clone(),
|
||||
&mut tks,
|
||||
node_info.clone(),
|
||||
sni.timestamp,
|
||||
sni.signatures.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(tks.len(), oldtkslen);
|
||||
assert_eq!(tks.len(), sni.signatures.len());
|
||||
|
||||
// Test incorrect validation
|
||||
let keypair1 = vcrypto.generate_keypair();
|
||||
let mut tks1: TypedKeySet = TypedKey::new(ck, keypair1.key).into();
|
||||
let oldtks1len = tks1.len();
|
||||
let _ = SignedDirectNodeInfo::new(
|
||||
crypto.clone(),
|
||||
&mut tks1,
|
||||
node_info.clone(),
|
||||
sni.timestamp,
|
||||
sni.signatures.clone(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(tks1.len(), oldtks1len);
|
||||
assert_eq!(tks1.len(), sni.signatures.len());
|
||||
|
||||
// Test unsupported cryptosystem validation
|
||||
let fake_crypto_kind: CryptoKind = FourCC::from([0, 1, 2, 3]);
|
||||
let mut tksfake: TypedKeySet = TypedKey::new(fake_crypto_kind, PublicKey::default()).into();
|
||||
let mut sigsfake = sni.signatures.clone();
|
||||
sigsfake.push(TypedSignature::new(fake_crypto_kind, Signature::default()));
|
||||
tksfake.add(TypedKey::new(ck, keypair.key));
|
||||
let sdnifake = SignedDirectNodeInfo::new(
|
||||
crypto.clone(),
|
||||
&mut tksfake,
|
||||
node_info.clone(),
|
||||
sni.timestamp,
|
||||
sigsfake.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(tksfake.len(), 1);
|
||||
assert_eq!(sdnifake.signatures.len(), sigsfake.len());
|
||||
|
||||
// Test relayed
|
||||
let node_info2 = NodeInfo {
|
||||
network_class: NetworkClass::OutboundOnly,
|
||||
outbound_protocols: ProtocolTypeSet::all(),
|
||||
address_types: AddressTypeSet::all(),
|
||||
envelope_support: VALID_ENVELOPE_VERSIONS.to_vec(),
|
||||
crypto_support: VALID_CRYPTO_KINDS.to_vec(),
|
||||
dial_info_detail_list: vec![DialInfoDetail {
|
||||
class: DialInfoClass::Blocked,
|
||||
dial_info: DialInfo::udp(SocketAddress::default()),
|
||||
}],
|
||||
};
|
||||
|
||||
// Test correct validation
|
||||
let keypair2 = vcrypto.generate_keypair();
|
||||
let mut tks2: TypedKeySet = TypedKey::new(ck, keypair2.key).into();
|
||||
let oldtks2len = tks2.len();
|
||||
|
||||
let sni2 = SignedRelayedNodeInfo::make_signatures(
|
||||
crypto.clone(),
|
||||
vec![TypedKeyPair::new(ck, keypair2)],
|
||||
node_info2.clone(),
|
||||
tks.clone(),
|
||||
sni.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let _ = SignedRelayedNodeInfo::new(
|
||||
crypto.clone(),
|
||||
&mut tks2,
|
||||
node_info2.clone(),
|
||||
tks.clone(),
|
||||
sni.clone(),
|
||||
sni2.timestamp,
|
||||
sni2.signatures.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(tks2.len(), oldtks2len);
|
||||
assert_eq!(tks2.len(), sni2.signatures.len());
|
||||
|
||||
// Test incorrect validation
|
||||
let keypair3 = vcrypto.generate_keypair();
|
||||
let mut tks3: TypedKeySet = TypedKey::new(ck, keypair3.key).into();
|
||||
let oldtks3len = tks3.len();
|
||||
|
||||
let _ = SignedRelayedNodeInfo::new(
|
||||
crypto.clone(),
|
||||
&mut tks3,
|
||||
node_info2.clone(),
|
||||
tks.clone(),
|
||||
sni.clone(),
|
||||
sni2.timestamp,
|
||||
sni2.signatures.clone(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(tks3.len(), oldtks3len);
|
||||
assert_eq!(tks3.len(), sni2.signatures.len());
|
||||
|
||||
// Test unsupported cryptosystem validation
|
||||
let fake_crypto_kind: CryptoKind = FourCC::from([0, 1, 2, 3]);
|
||||
let mut tksfake3: TypedKeySet =
|
||||
TypedKey::new(fake_crypto_kind, PublicKey::default()).into();
|
||||
let mut sigsfake3 = sni2.signatures.clone();
|
||||
sigsfake3.push(TypedSignature::new(fake_crypto_kind, Signature::default()));
|
||||
tksfake3.add(TypedKey::new(ck, keypair2.key));
|
||||
let srnifake = SignedRelayedNodeInfo::new(
|
||||
crypto.clone(),
|
||||
&mut tksfake3,
|
||||
node_info2.clone(),
|
||||
tks.clone(),
|
||||
sni.clone(),
|
||||
sni2.timestamp,
|
||||
sigsfake3.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(tksfake3.len(), 1);
|
||||
assert_eq!(srnifake.signatures.len(), sigsfake3.len());
|
||||
}
|
||||
|
||||
api.shutdown().await;
|
||||
}
|
||||
|
||||
pub async fn test_all() {
|
||||
test_signed_node_info().await;
|
||||
}
|
||||
130
veilid-core/src/network_manager/types/address.rs
Normal file
130
veilid-core/src/network_manager/types/address.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(u8), derive(CheckBytes))]
|
||||
pub enum Address {
|
||||
IPV4(Ipv4Addr),
|
||||
IPV6(Ipv6Addr),
|
||||
}
|
||||
|
||||
impl Default for Address {
|
||||
fn default() -> Self {
|
||||
Address::IPV4(Ipv4Addr::new(0, 0, 0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Address {
|
||||
pub fn from_socket_addr(sa: SocketAddr) -> Address {
|
||||
match sa {
|
||||
SocketAddr::V4(v4) => Address::IPV4(*v4.ip()),
|
||||
SocketAddr::V6(v6) => Address::IPV6(*v6.ip()),
|
||||
}
|
||||
}
|
||||
pub fn from_ip_addr(addr: IpAddr) -> Address {
|
||||
match addr {
|
||||
IpAddr::V4(v4) => Address::IPV4(v4),
|
||||
IpAddr::V6(v6) => Address::IPV6(v6),
|
||||
}
|
||||
}
|
||||
pub fn address_type(&self) -> AddressType {
|
||||
match self {
|
||||
Address::IPV4(_) => AddressType::IPV4,
|
||||
Address::IPV6(_) => AddressType::IPV6,
|
||||
}
|
||||
}
|
||||
pub fn address_string(&self) -> String {
|
||||
match self {
|
||||
Address::IPV4(v4) => v4.to_string(),
|
||||
Address::IPV6(v6) => v6.to_string(),
|
||||
}
|
||||
}
|
||||
pub fn address_string_with_port(&self, port: u16) -> String {
|
||||
match self {
|
||||
Address::IPV4(v4) => format!("{}:{}", v4, port),
|
||||
Address::IPV6(v6) => format!("[{}]:{}", v6, port),
|
||||
}
|
||||
}
|
||||
pub fn is_unspecified(&self) -> bool {
|
||||
match self {
|
||||
Address::IPV4(v4) => ipv4addr_is_unspecified(v4),
|
||||
Address::IPV6(v6) => ipv6addr_is_unspecified(v6),
|
||||
}
|
||||
}
|
||||
pub fn is_global(&self) -> bool {
|
||||
match self {
|
||||
Address::IPV4(v4) => ipv4addr_is_global(v4) && !ipv4addr_is_multicast(v4),
|
||||
Address::IPV6(v6) => ipv6addr_is_unicast_global(v6),
|
||||
}
|
||||
}
|
||||
pub fn is_local(&self) -> bool {
|
||||
match self {
|
||||
Address::IPV4(v4) => {
|
||||
ipv4addr_is_private(v4)
|
||||
|| ipv4addr_is_link_local(v4)
|
||||
|| ipv4addr_is_ietf_protocol_assignment(v4)
|
||||
}
|
||||
Address::IPV6(v6) => {
|
||||
ipv6addr_is_unicast_site_local(v6)
|
||||
|| ipv6addr_is_unicast_link_local(v6)
|
||||
|| ipv6addr_is_unique_local(v6)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn to_ip_addr(&self) -> IpAddr {
|
||||
match self {
|
||||
Self::IPV4(a) => IpAddr::V4(*a),
|
||||
Self::IPV6(a) => IpAddr::V6(*a),
|
||||
}
|
||||
}
|
||||
pub fn to_socket_addr(&self, port: u16) -> SocketAddr {
|
||||
SocketAddr::new(self.to_ip_addr(), port)
|
||||
}
|
||||
pub fn to_canonical(&self) -> Address {
|
||||
match self {
|
||||
Address::IPV4(v4) => Address::IPV4(*v4),
|
||||
Address::IPV6(v6) => match v6.to_ipv4() {
|
||||
Some(v4) => Address::IPV4(v4),
|
||||
None => Address::IPV6(*v6),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Address::IPV4(v4) => write!(f, "{}", v4),
|
||||
Address::IPV6(v6) => write!(f, "{}", v6),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Address {
|
||||
type Err = VeilidAPIError;
|
||||
fn from_str(host: &str) -> Result<Address, VeilidAPIError> {
|
||||
if let Ok(addr) = Ipv4Addr::from_str(host) {
|
||||
Ok(Address::IPV4(addr))
|
||||
} else if let Ok(addr) = Ipv6Addr::from_str(host) {
|
||||
Ok(Address::IPV6(addr))
|
||||
} else {
|
||||
Err(VeilidAPIError::parse_error(
|
||||
"Address::from_str failed",
|
||||
host,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
22
veilid-core/src/network_manager/types/address_type.rs
Normal file
22
veilid-core/src/network_manager/types/address_type.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::derive_hash_xor_eq)]
|
||||
#[derive(
|
||||
Debug,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
EnumSetType,
|
||||
)]
|
||||
#[enumset(repr = "u8")]
|
||||
#[archive_attr(repr(u8), derive(CheckBytes))]
|
||||
pub enum AddressType {
|
||||
IPV4,
|
||||
IPV6,
|
||||
}
|
||||
pub type AddressTypeSet = EnumSet<AddressType>;
|
||||
@@ -0,0 +1,80 @@
|
||||
use super::*;
|
||||
|
||||
/// Represents the 5-tuple of an established connection
|
||||
/// Not used to specify connections to create, that is reserved for DialInfo
|
||||
///
|
||||
/// ConnectionDescriptors should never be from unspecified local addresses for connection oriented protocols
|
||||
/// If the medium does not allow local addresses, None should have been used or 'new_no_local'
|
||||
/// If we are specifying only a port, then the socket's 'local_address()' should have been used, since an
|
||||
/// established connection is always from a real address to another real address.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct ConnectionDescriptor {
|
||||
remote: PeerAddress,
|
||||
local: Option<SocketAddress>,
|
||||
}
|
||||
|
||||
impl ConnectionDescriptor {
|
||||
pub fn new(remote: PeerAddress, local: SocketAddress) -> Self {
|
||||
assert!(
|
||||
!remote.protocol_type().is_connection_oriented() || !local.address().is_unspecified()
|
||||
);
|
||||
|
||||
Self {
|
||||
remote,
|
||||
local: Some(local),
|
||||
}
|
||||
}
|
||||
pub fn new_no_local(remote: PeerAddress) -> Self {
|
||||
Self {
|
||||
remote,
|
||||
local: None,
|
||||
}
|
||||
}
|
||||
pub fn remote(&self) -> PeerAddress {
|
||||
self.remote
|
||||
}
|
||||
pub fn remote_address(&self) -> &SocketAddress {
|
||||
self.remote.socket_address()
|
||||
}
|
||||
pub fn local(&self) -> Option<SocketAddress> {
|
||||
self.local
|
||||
}
|
||||
pub fn protocol_type(&self) -> ProtocolType {
|
||||
self.remote.protocol_type()
|
||||
}
|
||||
pub fn address_type(&self) -> AddressType {
|
||||
self.remote.address_type()
|
||||
}
|
||||
pub fn make_dial_info_filter(&self) -> DialInfoFilter {
|
||||
DialInfoFilter::all()
|
||||
.with_protocol_type(self.protocol_type())
|
||||
.with_address_type(self.address_type())
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchesDialInfoFilter for ConnectionDescriptor {
|
||||
fn matches_filter(&self, filter: &DialInfoFilter) -> bool {
|
||||
if !filter.protocol_type_set.contains(self.protocol_type()) {
|
||||
return false;
|
||||
}
|
||||
if !filter.address_type_set.contains(self.address_type()) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
522
veilid-core/src/network_manager/types/dial_info/mod.rs
Normal file
522
veilid-core/src/network_manager/types/dial_info/mod.rs
Normal file
@@ -0,0 +1,522 @@
|
||||
mod tcp;
|
||||
mod udp;
|
||||
mod ws;
|
||||
mod wss;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub use tcp::*;
|
||||
pub use udp::*;
|
||||
pub use ws::*;
|
||||
pub use wss::*;
|
||||
|
||||
// Keep member order appropriate for sorting < preference
|
||||
// Must match ProtocolType order
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(u8), derive(CheckBytes))]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum DialInfo {
|
||||
UDP(DialInfoUDP),
|
||||
TCP(DialInfoTCP),
|
||||
WS(DialInfoWS),
|
||||
WSS(DialInfoWSS),
|
||||
}
|
||||
impl Default for DialInfo {
|
||||
fn default() -> Self {
|
||||
DialInfo::UDP(DialInfoUDP::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DialInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
DialInfo::UDP(di) => write!(f, "udp|{}", di.socket_address),
|
||||
DialInfo::TCP(di) => write!(f, "tcp|{}", di.socket_address),
|
||||
DialInfo::WS(di) => {
|
||||
let url = format!("ws://{}", di.request);
|
||||
let split_url = SplitUrl::from_str(&url).unwrap();
|
||||
match split_url.host {
|
||||
SplitUrlHost::Hostname(_) => {
|
||||
write!(f, "ws|{}|{}", di.socket_address.to_ip_addr(), di.request)
|
||||
}
|
||||
SplitUrlHost::IpAddr(a) => {
|
||||
if di.socket_address.to_ip_addr() == a {
|
||||
write!(f, "ws|{}", di.request)
|
||||
} else {
|
||||
panic!("resolved address does not match url: {}", di.request);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DialInfo::WSS(di) => {
|
||||
let url = format!("wss://{}", di.request);
|
||||
let split_url = SplitUrl::from_str(&url).unwrap();
|
||||
match split_url.host {
|
||||
SplitUrlHost::Hostname(_) => {
|
||||
write!(f, "wss|{}|{}", di.socket_address.to_ip_addr(), di.request)
|
||||
}
|
||||
SplitUrlHost::IpAddr(_) => {
|
||||
panic!(
|
||||
"secure websockets can not use ip address in request: {}",
|
||||
di.request
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for DialInfo {
|
||||
type Err = VeilidAPIError;
|
||||
fn from_str(s: &str) -> Result<DialInfo, VeilidAPIError> {
|
||||
let (proto, rest) = s.split_once('|').ok_or_else(|| {
|
||||
VeilidAPIError::parse_error("DialInfo::from_str missing protocol '|' separator", s)
|
||||
})?;
|
||||
match proto {
|
||||
"udp" => {
|
||||
let socket_address = SocketAddress::from_str(rest)?;
|
||||
Ok(DialInfo::udp(socket_address))
|
||||
}
|
||||
"tcp" => {
|
||||
let socket_address = SocketAddress::from_str(rest)?;
|
||||
Ok(DialInfo::tcp(socket_address))
|
||||
}
|
||||
"ws" => {
|
||||
let url = format!("ws://{}", rest);
|
||||
let split_url = SplitUrl::from_str(&url).map_err(|e| {
|
||||
VeilidAPIError::parse_error(format!("unable to split WS url: {}", e), &url)
|
||||
})?;
|
||||
if split_url.scheme != "ws" || !url.starts_with("ws://") {
|
||||
apibail_parse_error!("incorrect scheme for WS dialinfo", url);
|
||||
}
|
||||
let url_port = split_url.port.unwrap_or(80u16);
|
||||
|
||||
match rest.split_once('|') {
|
||||
Some((sa, rest)) => {
|
||||
let address = Address::from_str(sa)?;
|
||||
|
||||
DialInfo::try_ws(
|
||||
SocketAddress::new(address, url_port),
|
||||
format!("ws://{}", rest),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
let address = Address::from_str(&split_url.host.to_string())?;
|
||||
DialInfo::try_ws(
|
||||
SocketAddress::new(address, url_port),
|
||||
format!("ws://{}", rest),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
"wss" => {
|
||||
let url = format!("wss://{}", rest);
|
||||
let split_url = SplitUrl::from_str(&url).map_err(|e| {
|
||||
VeilidAPIError::parse_error(format!("unable to split WSS url: {}", e), &url)
|
||||
})?;
|
||||
if split_url.scheme != "wss" || !url.starts_with("wss://") {
|
||||
apibail_parse_error!("incorrect scheme for WSS dialinfo", url);
|
||||
}
|
||||
let url_port = split_url.port.unwrap_or(443u16);
|
||||
|
||||
let (a, rest) = rest.split_once('|').ok_or_else(|| {
|
||||
VeilidAPIError::parse_error(
|
||||
"DialInfo::from_str missing socket address '|' separator",
|
||||
s,
|
||||
)
|
||||
})?;
|
||||
|
||||
let address = Address::from_str(a)?;
|
||||
DialInfo::try_wss(
|
||||
SocketAddress::new(address, url_port),
|
||||
format!("wss://{}", rest),
|
||||
)
|
||||
}
|
||||
_ => Err(VeilidAPIError::parse_error(
|
||||
"DialInfo::from_str has invalid scheme",
|
||||
s,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DialInfo {
|
||||
pub fn udp_from_socketaddr(socket_addr: SocketAddr) -> Self {
|
||||
Self::UDP(DialInfoUDP {
|
||||
socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(),
|
||||
})
|
||||
}
|
||||
pub fn tcp_from_socketaddr(socket_addr: SocketAddr) -> Self {
|
||||
Self::TCP(DialInfoTCP {
|
||||
socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(),
|
||||
})
|
||||
}
|
||||
pub fn udp(socket_address: SocketAddress) -> Self {
|
||||
Self::UDP(DialInfoUDP {
|
||||
socket_address: socket_address.to_canonical(),
|
||||
})
|
||||
}
|
||||
pub fn tcp(socket_address: SocketAddress) -> Self {
|
||||
Self::TCP(DialInfoTCP {
|
||||
socket_address: socket_address.to_canonical(),
|
||||
})
|
||||
}
|
||||
pub fn try_ws(socket_address: SocketAddress, url: String) -> Result<Self, VeilidAPIError> {
|
||||
let split_url = SplitUrl::from_str(&url).map_err(|e| {
|
||||
VeilidAPIError::parse_error(format!("unable to split WS url: {}", e), &url)
|
||||
})?;
|
||||
if split_url.scheme != "ws" || !url.starts_with("ws://") {
|
||||
apibail_parse_error!("incorrect scheme for WS dialinfo", url);
|
||||
}
|
||||
let url_port = split_url.port.unwrap_or(80u16);
|
||||
if url_port != socket_address.port() {
|
||||
apibail_parse_error!("socket address port doesn't match url port", url);
|
||||
}
|
||||
if let SplitUrlHost::IpAddr(a) = split_url.host {
|
||||
if socket_address.to_ip_addr() != a {
|
||||
apibail_parse_error!(
|
||||
format!("request address does not match socket address: {}", a),
|
||||
socket_address
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(Self::WS(DialInfoWS {
|
||||
socket_address: socket_address.to_canonical(),
|
||||
request: url[5..].to_string(),
|
||||
}))
|
||||
}
|
||||
pub fn try_wss(socket_address: SocketAddress, url: String) -> Result<Self, VeilidAPIError> {
|
||||
let split_url = SplitUrl::from_str(&url).map_err(|e| {
|
||||
VeilidAPIError::parse_error(format!("unable to split WSS url: {}", e), &url)
|
||||
})?;
|
||||
if split_url.scheme != "wss" || !url.starts_with("wss://") {
|
||||
apibail_parse_error!("incorrect scheme for WSS dialinfo", url);
|
||||
}
|
||||
let url_port = split_url.port.unwrap_or(443u16);
|
||||
if url_port != socket_address.port() {
|
||||
apibail_parse_error!("socket address port doesn't match url port", url);
|
||||
}
|
||||
if !matches!(split_url.host, SplitUrlHost::Hostname(_)) {
|
||||
apibail_parse_error!(
|
||||
"WSS url can not use address format, only hostname format",
|
||||
url
|
||||
);
|
||||
}
|
||||
Ok(Self::WSS(DialInfoWSS {
|
||||
socket_address: socket_address.to_canonical(),
|
||||
request: url[6..].to_string(),
|
||||
}))
|
||||
}
|
||||
pub fn protocol_type(&self) -> ProtocolType {
|
||||
match self {
|
||||
Self::UDP(_) => ProtocolType::UDP,
|
||||
Self::TCP(_) => ProtocolType::TCP,
|
||||
Self::WS(_) => ProtocolType::WS,
|
||||
Self::WSS(_) => ProtocolType::WSS,
|
||||
}
|
||||
}
|
||||
pub fn address_type(&self) -> AddressType {
|
||||
self.socket_address().address_type()
|
||||
}
|
||||
pub fn address(&self) -> Address {
|
||||
match self {
|
||||
Self::UDP(di) => di.socket_address.address(),
|
||||
Self::TCP(di) => di.socket_address.address(),
|
||||
Self::WS(di) => di.socket_address.address(),
|
||||
Self::WSS(di) => di.socket_address.address(),
|
||||
}
|
||||
}
|
||||
pub fn set_address(&mut self, address: Address) {
|
||||
match self {
|
||||
Self::UDP(di) => di.socket_address.set_address(address),
|
||||
Self::TCP(di) => di.socket_address.set_address(address),
|
||||
Self::WS(di) => di.socket_address.set_address(address),
|
||||
Self::WSS(di) => di.socket_address.set_address(address),
|
||||
}
|
||||
}
|
||||
pub fn socket_address(&self) -> SocketAddress {
|
||||
match self {
|
||||
Self::UDP(di) => di.socket_address,
|
||||
Self::TCP(di) => di.socket_address,
|
||||
Self::WS(di) => di.socket_address,
|
||||
Self::WSS(di) => di.socket_address,
|
||||
}
|
||||
}
|
||||
pub fn to_ip_addr(&self) -> IpAddr {
|
||||
match self {
|
||||
Self::UDP(di) => di.socket_address.to_ip_addr(),
|
||||
Self::TCP(di) => di.socket_address.to_ip_addr(),
|
||||
Self::WS(di) => di.socket_address.to_ip_addr(),
|
||||
Self::WSS(di) => di.socket_address.to_ip_addr(),
|
||||
}
|
||||
}
|
||||
pub fn port(&self) -> u16 {
|
||||
match self {
|
||||
Self::UDP(di) => di.socket_address.port(),
|
||||
Self::TCP(di) => di.socket_address.port(),
|
||||
Self::WS(di) => di.socket_address.port(),
|
||||
Self::WSS(di) => di.socket_address.port(),
|
||||
}
|
||||
}
|
||||
pub fn set_port(&mut self, port: u16) {
|
||||
match self {
|
||||
Self::UDP(di) => di.socket_address.set_port(port),
|
||||
Self::TCP(di) => di.socket_address.set_port(port),
|
||||
Self::WS(di) => di.socket_address.set_port(port),
|
||||
Self::WSS(di) => di.socket_address.set_port(port),
|
||||
}
|
||||
}
|
||||
pub fn to_socket_addr(&self) -> SocketAddr {
|
||||
match self {
|
||||
Self::UDP(di) => di.socket_address.to_socket_addr(),
|
||||
Self::TCP(di) => di.socket_address.to_socket_addr(),
|
||||
Self::WS(di) => di.socket_address.to_socket_addr(),
|
||||
Self::WSS(di) => di.socket_address.to_socket_addr(),
|
||||
}
|
||||
}
|
||||
pub fn to_peer_address(&self) -> PeerAddress {
|
||||
match self {
|
||||
Self::UDP(di) => PeerAddress::new(di.socket_address, ProtocolType::UDP),
|
||||
Self::TCP(di) => PeerAddress::new(di.socket_address, ProtocolType::TCP),
|
||||
Self::WS(di) => PeerAddress::new(di.socket_address, ProtocolType::WS),
|
||||
Self::WSS(di) => PeerAddress::new(di.socket_address, ProtocolType::WSS),
|
||||
}
|
||||
}
|
||||
pub fn request(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::UDP(_) => None,
|
||||
Self::TCP(_) => None,
|
||||
Self::WS(di) => Some(format!("ws://{}", di.request)),
|
||||
Self::WSS(di) => Some(format!("wss://{}", di.request)),
|
||||
}
|
||||
}
|
||||
pub fn is_valid(&self) -> bool {
|
||||
let socket_address = self.socket_address();
|
||||
let address = socket_address.address();
|
||||
let port = socket_address.port();
|
||||
(address.is_global() || address.is_local()) && port > 0
|
||||
}
|
||||
|
||||
pub fn make_filter(&self) -> DialInfoFilter {
|
||||
DialInfoFilter {
|
||||
protocol_type_set: ProtocolTypeSet::only(self.protocol_type()),
|
||||
address_type_set: AddressTypeSet::only(self.address_type()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_vec_from_short<S: AsRef<str>, H: AsRef<str>>(
|
||||
short: S,
|
||||
hostname: H,
|
||||
) -> Result<Vec<Self>, VeilidAPIError> {
|
||||
let short = short.as_ref();
|
||||
let hostname = hostname.as_ref();
|
||||
|
||||
if short.len() < 2 {
|
||||
apibail_parse_error!("invalid short url length", short);
|
||||
}
|
||||
let url = match &short[0..1] {
|
||||
"U" => {
|
||||
format!("udp://{}:{}", hostname, &short[1..])
|
||||
}
|
||||
"T" => {
|
||||
format!("tcp://{}:{}", hostname, &short[1..])
|
||||
}
|
||||
"W" => {
|
||||
format!("ws://{}:{}", hostname, &short[1..])
|
||||
}
|
||||
"S" => {
|
||||
format!("wss://{}:{}", hostname, &short[1..])
|
||||
}
|
||||
_ => {
|
||||
apibail_parse_error!("invalid short url type", short);
|
||||
}
|
||||
};
|
||||
Self::try_vec_from_url(url)
|
||||
}
|
||||
|
||||
pub fn try_vec_from_url<S: AsRef<str>>(url: S) -> Result<Vec<Self>, VeilidAPIError> {
|
||||
let url = url.as_ref();
|
||||
let split_url = SplitUrl::from_str(url)
|
||||
.map_err(|e| VeilidAPIError::parse_error(format!("unable to split url: {}", e), url))?;
|
||||
|
||||
let port = match split_url.scheme.as_str() {
|
||||
"udp" | "tcp" => split_url
|
||||
.port
|
||||
.ok_or_else(|| VeilidAPIError::parse_error("Missing port in udp url", url))?,
|
||||
"ws" => split_url.port.unwrap_or(80u16),
|
||||
"wss" => split_url.port.unwrap_or(443u16),
|
||||
_ => {
|
||||
apibail_parse_error!("Invalid dial info url scheme", split_url.scheme);
|
||||
}
|
||||
};
|
||||
|
||||
let socket_addrs = {
|
||||
// Resolve if possible, WASM doesn't support resolution and doesn't need it to connect to the dialinfo
|
||||
// This will not be used on signed dialinfo, only for bootstrapping, so we don't need to worry about
|
||||
// the '0.0.0.0' address being propagated across the routing table
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_arch = "wasm32")] {
|
||||
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), port)]
|
||||
} else {
|
||||
match split_url.host {
|
||||
SplitUrlHost::Hostname(_) => split_url
|
||||
.host_port(port)
|
||||
.to_socket_addrs()
|
||||
.map_err(|_| VeilidAPIError::parse_error("couldn't resolve hostname in url", url))?
|
||||
.collect(),
|
||||
SplitUrlHost::IpAddr(a) => vec![SocketAddr::new(a, port)],
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut out = Vec::new();
|
||||
for sa in socket_addrs {
|
||||
out.push(match split_url.scheme.as_str() {
|
||||
"udp" => Self::udp_from_socketaddr(sa),
|
||||
"tcp" => Self::tcp_from_socketaddr(sa),
|
||||
"ws" => Self::try_ws(
|
||||
SocketAddress::from_socket_addr(sa).to_canonical(),
|
||||
url.to_string(),
|
||||
)?,
|
||||
"wss" => Self::try_wss(
|
||||
SocketAddress::from_socket_addr(sa).to_canonical(),
|
||||
url.to_string(),
|
||||
)?,
|
||||
_ => {
|
||||
unreachable!("Invalid dial info url scheme")
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub async fn to_short(&self) -> (String, String) {
|
||||
match self {
|
||||
DialInfo::UDP(di) => (
|
||||
format!("U{}", di.socket_address.port()),
|
||||
intf::ptr_lookup(di.socket_address.to_ip_addr())
|
||||
.await
|
||||
.unwrap_or_else(|_| di.socket_address.to_string()),
|
||||
),
|
||||
DialInfo::TCP(di) => (
|
||||
format!("T{}", di.socket_address.port()),
|
||||
intf::ptr_lookup(di.socket_address.to_ip_addr())
|
||||
.await
|
||||
.unwrap_or_else(|_| di.socket_address.to_string()),
|
||||
),
|
||||
DialInfo::WS(di) => {
|
||||
let mut split_url = SplitUrl::from_str(&format!("ws://{}", di.request)).unwrap();
|
||||
if let SplitUrlHost::IpAddr(a) = split_url.host {
|
||||
if let Ok(host) = intf::ptr_lookup(a).await {
|
||||
split_url.host = SplitUrlHost::Hostname(host);
|
||||
}
|
||||
}
|
||||
(
|
||||
format!(
|
||||
"W{}{}",
|
||||
split_url.port.unwrap_or(80),
|
||||
split_url
|
||||
.path
|
||||
.map(|p| format!("/{}", p))
|
||||
.unwrap_or_default()
|
||||
),
|
||||
split_url.host.to_string(),
|
||||
)
|
||||
}
|
||||
DialInfo::WSS(di) => {
|
||||
let mut split_url = SplitUrl::from_str(&format!("wss://{}", di.request)).unwrap();
|
||||
if let SplitUrlHost::IpAddr(a) = split_url.host {
|
||||
if let Ok(host) = intf::ptr_lookup(a).await {
|
||||
split_url.host = SplitUrlHost::Hostname(host);
|
||||
}
|
||||
}
|
||||
(
|
||||
format!(
|
||||
"S{}{}",
|
||||
split_url.port.unwrap_or(443),
|
||||
split_url
|
||||
.path
|
||||
.map(|p| format!("/{}", p))
|
||||
.unwrap_or_default()
|
||||
),
|
||||
split_url.host.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn to_url(&self) -> String {
|
||||
match self {
|
||||
DialInfo::UDP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr())
|
||||
.await
|
||||
.map(|h| format!("udp://{}:{}", h, di.socket_address.port()))
|
||||
.unwrap_or_else(|_| format!("udp://{}", di.socket_address)),
|
||||
DialInfo::TCP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr())
|
||||
.await
|
||||
.map(|h| format!("tcp://{}:{}", h, di.socket_address.port()))
|
||||
.unwrap_or_else(|_| format!("tcp://{}", di.socket_address)),
|
||||
DialInfo::WS(di) => {
|
||||
let mut split_url = SplitUrl::from_str(&format!("ws://{}", di.request)).unwrap();
|
||||
if let SplitUrlHost::IpAddr(a) = split_url.host {
|
||||
if let Ok(host) = intf::ptr_lookup(a).await {
|
||||
split_url.host = SplitUrlHost::Hostname(host);
|
||||
}
|
||||
}
|
||||
split_url.to_string()
|
||||
}
|
||||
DialInfo::WSS(di) => {
|
||||
let mut split_url = SplitUrl::from_str(&format!("wss://{}", di.request)).unwrap();
|
||||
if let SplitUrlHost::IpAddr(a) = split_url.host {
|
||||
if let Ok(host) = intf::ptr_lookup(a).await {
|
||||
split_url.host = SplitUrlHost::Hostname(host);
|
||||
}
|
||||
}
|
||||
split_url.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ordered_sequencing_sort(a: &DialInfo, b: &DialInfo) -> core::cmp::Ordering {
|
||||
let ca = a.protocol_type().sort_order(Sequencing::EnsureOrdered);
|
||||
let cb = b.protocol_type().sort_order(Sequencing::EnsureOrdered);
|
||||
if ca < cb {
|
||||
return core::cmp::Ordering::Less;
|
||||
}
|
||||
if ca > cb {
|
||||
return core::cmp::Ordering::Greater;
|
||||
}
|
||||
match (a, b) {
|
||||
(DialInfo::UDP(a), DialInfo::UDP(b)) => a.cmp(b),
|
||||
(DialInfo::TCP(a), DialInfo::TCP(b)) => a.cmp(b),
|
||||
(DialInfo::WS(a), DialInfo::WS(b)) => a.cmp(b),
|
||||
(DialInfo::WSS(a), DialInfo::WSS(b)) => a.cmp(b),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchesDialInfoFilter for DialInfo {
|
||||
fn matches_filter(&self, filter: &DialInfoFilter) -> bool {
|
||||
if !filter.protocol_type_set.contains(self.protocol_type()) {
|
||||
return false;
|
||||
}
|
||||
if !filter.address_type_set.contains(self.address_type()) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
21
veilid-core/src/network_manager/types/dial_info/tcp.rs
Normal file
21
veilid-core/src/network_manager/types/dial_info/tcp.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Default,
|
||||
Debug,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct DialInfoTCP {
|
||||
pub socket_address: SocketAddress,
|
||||
}
|
||||
21
veilid-core/src/network_manager/types/dial_info/udp.rs
Normal file
21
veilid-core/src/network_manager/types/dial_info/udp.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Default,
|
||||
Debug,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct DialInfoUDP {
|
||||
pub socket_address: SocketAddress,
|
||||
}
|
||||
22
veilid-core/src/network_manager/types/dial_info/ws.rs
Normal file
22
veilid-core/src/network_manager/types/dial_info/ws.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Default,
|
||||
Debug,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct DialInfoWS {
|
||||
pub socket_address: SocketAddress,
|
||||
pub request: String,
|
||||
}
|
||||
22
veilid-core/src/network_manager/types/dial_info/wss.rs
Normal file
22
veilid-core/src/network_manager/types/dial_info/wss.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Default,
|
||||
Debug,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct DialInfoWSS {
|
||||
pub socket_address: SocketAddress,
|
||||
pub request: String,
|
||||
}
|
||||
50
veilid-core/src/network_manager/types/dial_info_class.rs
Normal file
50
veilid-core/src/network_manager/types/dial_info_class.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use super::*;
|
||||
|
||||
// Keep member order appropriate for sorting < preference
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(u8), derive(CheckBytes))]
|
||||
pub enum DialInfoClass {
|
||||
Direct = 0, // D = Directly reachable with public IP and no firewall, with statically configured port
|
||||
Mapped = 1, // M = Directly reachable with via portmap behind any NAT or firewalled with dynamically negotiated port
|
||||
FullConeNAT = 2, // F = Directly reachable device without portmap behind full-cone NAT
|
||||
Blocked = 3, // B = Inbound blocked at firewall but may hole punch with public address
|
||||
AddressRestrictedNAT = 4, // A = Device without portmap behind address-only restricted NAT
|
||||
PortRestrictedNAT = 5, // P = Device without portmap behind address-and-port restricted NAT
|
||||
}
|
||||
|
||||
impl DialInfoClass {
|
||||
// Is a signal required to do an inbound hole-punch?
|
||||
pub fn requires_signal(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Blocked | Self::AddressRestrictedNAT | Self::PortRestrictedNAT
|
||||
)
|
||||
}
|
||||
|
||||
// Does a relay node need to be allocated for this dial info?
|
||||
// For full cone NAT, the relay itself may not be used but the keepalive sent to it
|
||||
// is required to keep the NAT mapping valid in the router state table
|
||||
pub fn requires_relay(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::FullConeNAT
|
||||
| Self::Blocked
|
||||
| Self::AddressRestrictedNAT
|
||||
| Self::PortRestrictedNAT
|
||||
)
|
||||
}
|
||||
}
|
||||
86
veilid-core/src/network_manager/types/dial_info_filter.rs
Normal file
86
veilid-core/src/network_manager/types/dial_info_filter.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct DialInfoFilter {
|
||||
#[with(RkyvEnumSet)]
|
||||
pub protocol_type_set: ProtocolTypeSet,
|
||||
#[with(RkyvEnumSet)]
|
||||
pub address_type_set: AddressTypeSet,
|
||||
}
|
||||
|
||||
impl Default for DialInfoFilter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
protocol_type_set: ProtocolTypeSet::all(),
|
||||
address_type_set: AddressTypeSet::all(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DialInfoFilter {
|
||||
pub fn all() -> Self {
|
||||
Self {
|
||||
protocol_type_set: ProtocolTypeSet::all(),
|
||||
address_type_set: AddressTypeSet::all(),
|
||||
}
|
||||
}
|
||||
pub fn with_protocol_type(mut self, protocol_type: ProtocolType) -> Self {
|
||||
self.protocol_type_set = ProtocolTypeSet::only(protocol_type);
|
||||
self
|
||||
}
|
||||
pub fn with_protocol_type_set(mut self, protocol_set: ProtocolTypeSet) -> Self {
|
||||
self.protocol_type_set = protocol_set;
|
||||
self
|
||||
}
|
||||
pub fn with_address_type(mut self, address_type: AddressType) -> Self {
|
||||
self.address_type_set = AddressTypeSet::only(address_type);
|
||||
self
|
||||
}
|
||||
pub fn with_address_type_set(mut self, address_set: AddressTypeSet) -> Self {
|
||||
self.address_type_set = address_set;
|
||||
self
|
||||
}
|
||||
pub fn filtered(mut self, other_dif: &DialInfoFilter) -> Self {
|
||||
self.protocol_type_set &= other_dif.protocol_type_set;
|
||||
self.address_type_set &= other_dif.address_type_set;
|
||||
self
|
||||
}
|
||||
pub fn is_dead(&self) -> bool {
|
||||
self.protocol_type_set.is_empty() || self.address_type_set.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for DialInfoFilter {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
let mut out = String::new();
|
||||
if self.protocol_type_set != ProtocolTypeSet::all() {
|
||||
out += &format!("+{:?}", self.protocol_type_set);
|
||||
} else {
|
||||
out += "*";
|
||||
}
|
||||
if self.address_type_set != AddressTypeSet::all() {
|
||||
out += &format!("+{:?}", self.address_type_set);
|
||||
} else {
|
||||
out += "*";
|
||||
}
|
||||
write!(f, "[{}]", out)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MatchesDialInfoFilter {
|
||||
fn matches_filter(&self, filter: &DialInfoFilter) -> bool;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
use super::*;
|
||||
|
||||
// Keep member order appropriate for sorting < preference
|
||||
// Must match DialInfo order
|
||||
#[allow(clippy::derive_hash_xor_eq)]
|
||||
#[derive(
|
||||
Debug,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
EnumSetType,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[enumset(repr = "u8")]
|
||||
#[archive_attr(repr(u8), derive(CheckBytes))]
|
||||
pub enum LowLevelProtocolType {
|
||||
UDP,
|
||||
TCP,
|
||||
}
|
||||
|
||||
impl LowLevelProtocolType {
|
||||
pub fn is_connection_oriented(&self) -> bool {
|
||||
matches!(self, LowLevelProtocolType::TCP)
|
||||
}
|
||||
}
|
||||
pub type LowLevelProtocolTypeSet = EnumSet<LowLevelProtocolType>;
|
||||
|
||||
31
veilid-core/src/network_manager/types/mod.rs
Normal file
31
veilid-core/src/network_manager/types/mod.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
mod address;
|
||||
mod address_type;
|
||||
mod connection_descriptor;
|
||||
mod dial_info;
|
||||
mod dial_info_class;
|
||||
mod dial_info_filter;
|
||||
mod low_level_protocol_type;
|
||||
mod network_class;
|
||||
mod peer_address;
|
||||
mod protocol_type;
|
||||
mod signal_info;
|
||||
mod socket_address;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub use address::*;
|
||||
pub use address_type::*;
|
||||
pub use connection_descriptor::*;
|
||||
pub use dial_info::*;
|
||||
pub use dial_info_class::*;
|
||||
pub use dial_info_filter::*;
|
||||
pub use low_level_protocol_type::*;
|
||||
pub use network_class::*;
|
||||
pub use peer_address::*;
|
||||
pub use protocol_type::*;
|
||||
pub use signal_info::*;
|
||||
pub use socket_address::*;
|
||||
|
||||
use enumset::*;
|
||||
use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||
use serde::*;
|
||||
37
veilid-core/src/network_manager/types/network_class.rs
Normal file
37
veilid-core/src/network_manager/types/network_class.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(u8), derive(CheckBytes))]
|
||||
pub enum NetworkClass {
|
||||
InboundCapable = 0, // I = Inbound capable without relay, may require signal
|
||||
OutboundOnly = 1, // O = Outbound only, inbound relay required except with reverse connect signal
|
||||
WebApp = 2, // W = PWA, outbound relay is required in most cases
|
||||
Invalid = 3, // X = Invalid network class, we don't know how to reach this node
|
||||
}
|
||||
|
||||
impl Default for NetworkClass {
|
||||
fn default() -> Self {
|
||||
Self::Invalid
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkClass {
|
||||
// Should an outbound relay be kept available?
|
||||
pub fn outbound_wants_relay(&self) -> bool {
|
||||
matches!(self, Self::WebApp)
|
||||
}
|
||||
}
|
||||
66
veilid-core/src/network_manager/types/peer_address.rs
Normal file
66
veilid-core/src/network_manager/types/peer_address.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Eq,
|
||||
Ord,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct PeerAddress {
|
||||
protocol_type: ProtocolType,
|
||||
#[serde(with = "json_as_string")]
|
||||
socket_address: SocketAddress,
|
||||
}
|
||||
|
||||
impl PeerAddress {
|
||||
pub fn new(socket_address: SocketAddress, protocol_type: ProtocolType) -> Self {
|
||||
Self {
|
||||
socket_address: socket_address.to_canonical(),
|
||||
protocol_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn socket_address(&self) -> &SocketAddress {
|
||||
&self.socket_address
|
||||
}
|
||||
|
||||
pub fn protocol_type(&self) -> ProtocolType {
|
||||
self.protocol_type
|
||||
}
|
||||
|
||||
pub fn to_socket_addr(&self) -> SocketAddr {
|
||||
self.socket_address.to_socket_addr()
|
||||
}
|
||||
|
||||
pub fn address_type(&self) -> AddressType {
|
||||
self.socket_address.address_type()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PeerAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.protocol_type, self.socket_address)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PeerAddress {
|
||||
type Err = VeilidAPIError;
|
||||
fn from_str(s: &str) -> Result<PeerAddress, VeilidAPIError> {
|
||||
let Some((first, second)) = s.split_once(':') else {
|
||||
return Err(VeilidAPIError::parse_error("PeerAddress is missing a colon: {}", s));
|
||||
};
|
||||
let protocol_type = ProtocolType::from_str(first)?;
|
||||
let socket_address = SocketAddress::from_str(second)?;
|
||||
Ok(PeerAddress::new(socket_address, protocol_type))
|
||||
}
|
||||
}
|
||||
104
veilid-core/src/network_manager/types/protocol_type.rs
Normal file
104
veilid-core/src/network_manager/types/protocol_type.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use super::*;
|
||||
|
||||
// Keep member order appropriate for sorting < preference
|
||||
// Must match DialInfo order
|
||||
#[allow(clippy::derive_hash_xor_eq)]
|
||||
#[derive(
|
||||
Debug,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
EnumSetType,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[enumset(repr = "u8")]
|
||||
#[archive_attr(repr(u8), derive(CheckBytes))]
|
||||
pub enum ProtocolType {
|
||||
UDP,
|
||||
TCP,
|
||||
WS,
|
||||
WSS,
|
||||
}
|
||||
|
||||
impl ProtocolType {
|
||||
pub fn is_connection_oriented(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS
|
||||
)
|
||||
}
|
||||
pub fn low_level_protocol_type(&self) -> LowLevelProtocolType {
|
||||
match self {
|
||||
ProtocolType::UDP => LowLevelProtocolType::UDP,
|
||||
ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS => LowLevelProtocolType::TCP,
|
||||
}
|
||||
}
|
||||
pub fn sort_order(&self, sequencing: Sequencing) -> usize {
|
||||
match self {
|
||||
ProtocolType::UDP => {
|
||||
if sequencing != Sequencing::NoPreference {
|
||||
3
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
ProtocolType::TCP => {
|
||||
if sequencing != Sequencing::NoPreference {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
ProtocolType::WS => {
|
||||
if sequencing != Sequencing::NoPreference {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
}
|
||||
ProtocolType::WSS => {
|
||||
if sequencing != Sequencing::NoPreference {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn all_ordered_set() -> ProtocolTypeSet {
|
||||
ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ProtocolType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ProtocolType::UDP => write!(f, "UDP"),
|
||||
ProtocolType::TCP => write!(f, "TCP"),
|
||||
ProtocolType::WS => write!(f, "WS"),
|
||||
ProtocolType::WSS => write!(f, "WSS"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ProtocolType {
|
||||
type Err = VeilidAPIError;
|
||||
fn from_str(s: &str) -> Result<ProtocolType, VeilidAPIError> {
|
||||
match s.to_ascii_uppercase().as_str() {
|
||||
"UDP" => Ok(ProtocolType::UDP),
|
||||
"TCP" => Ok(ProtocolType::TCP),
|
||||
"WS" => Ok(ProtocolType::WS),
|
||||
"WSS" => Ok(ProtocolType::WSS),
|
||||
_ => Err(VeilidAPIError::parse_error(
|
||||
"ProtocolType::from_str failed",
|
||||
s,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ProtocolTypeSet = EnumSet<ProtocolType>;
|
||||
22
veilid-core/src/network_manager/types/signal_info.rs
Normal file
22
veilid-core/src/network_manager/types/signal_info.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use super::*;
|
||||
|
||||
/// Parameter for Signal operation
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[archive_attr(repr(u8), derive(CheckBytes))]
|
||||
pub enum SignalInfo {
|
||||
/// UDP Hole Punch Request
|
||||
HolePunch {
|
||||
/// /// Receipt to be returned after the hole punch
|
||||
receipt: Vec<u8>,
|
||||
/// Sender's peer info
|
||||
peer_info: PeerInfo,
|
||||
},
|
||||
/// Reverse Connection Request
|
||||
ReverseConnect {
|
||||
/// Receipt to be returned by the reverse connection
|
||||
receipt: Vec<u8>,
|
||||
/// Sender's peer info
|
||||
peer_info: PeerInfo,
|
||||
},
|
||||
// XXX: WebRTC
|
||||
}
|
||||
77
veilid-core/src/network_manager/types/socket_address.rs
Normal file
77
veilid-core/src/network_manager/types/socket_address.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Default,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RkyvArchive,
|
||||
RkyvSerialize,
|
||||
RkyvDeserialize,
|
||||
)]
|
||||
#[archive_attr(repr(C), derive(CheckBytes))]
|
||||
pub struct SocketAddress {
|
||||
address: Address,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
impl SocketAddress {
|
||||
pub fn new(address: Address, port: u16) -> Self {
|
||||
Self { address, port }
|
||||
}
|
||||
pub fn from_socket_addr(sa: SocketAddr) -> SocketAddress {
|
||||
Self {
|
||||
address: Address::from_socket_addr(sa),
|
||||
port: sa.port(),
|
||||
}
|
||||
}
|
||||
pub fn address(&self) -> Address {
|
||||
self.address
|
||||
}
|
||||
pub fn set_address(&mut self, address: Address) {
|
||||
self.address = address;
|
||||
}
|
||||
pub fn address_type(&self) -> AddressType {
|
||||
self.address.address_type()
|
||||
}
|
||||
pub fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
pub fn set_port(&mut self, port: u16) {
|
||||
self.port = port
|
||||
}
|
||||
pub fn to_canonical(&self) -> SocketAddress {
|
||||
SocketAddress {
|
||||
address: self.address.to_canonical(),
|
||||
port: self.port,
|
||||
}
|
||||
}
|
||||
pub fn to_ip_addr(&self) -> IpAddr {
|
||||
self.address.to_ip_addr()
|
||||
}
|
||||
pub fn to_socket_addr(&self) -> SocketAddr {
|
||||
self.address.to_socket_addr(self.port)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SocketAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "{}", self.to_socket_addr())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SocketAddress {
|
||||
type Err = VeilidAPIError;
|
||||
fn from_str(s: &str) -> Result<SocketAddress, VeilidAPIError> {
|
||||
let sa = SocketAddr::from_str(s)
|
||||
.map_err(|e| VeilidAPIError::parse_error("Failed to parse SocketAddress", e))?;
|
||||
Ok(SocketAddress::from_socket_addr(sa))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user