diff --git a/doc/config/sample.config b/doc/config/sample.config index b59cd7c2..c2af31a9 100644 --- a/doc/config/sample.config +++ b/doc/config/sample.config @@ -50,6 +50,7 @@ core: reverse_connection_receipt_time_ms: 5000 hole_punch_receipt_time_ms: 5000 network_key_password: null + disable_capabilites: [] routing_table: node_id: null node_id_secret: null diff --git a/doc/config/veilid-server-config.md b/doc/config/veilid-server-config.md index 116d2dec..5c81b6b3 100644 --- a/doc/config/veilid-server-config.md +++ b/doc/config/veilid-server-config.md @@ -189,6 +189,7 @@ network: reverse_connection_receipt_time_ms: 5000 hole_punch_receipt_time_ms: 5000 network_key_password: null + disable_capabilites: [] node_id: null node_id_secret: null bootstrap: ['bootstrap.veilid.net'] diff --git a/veilid-core/proto/veilid.capnp b/veilid-core/proto/veilid.capnp index 3d813450..8fabe0f7 100644 --- a/veilid-core/proto/veilid.capnp +++ b/veilid-core/proto/veilid.capnp @@ -34,6 +34,7 @@ using TunnelID = UInt64; # Id for tunnels using CryptoKind = UInt32; # FOURCC code for cryptography type using ValueSeqNum = UInt32; # sequence numbers for values using Subkey = UInt32; # subkey index for dht +using Capability = UInt32; # FOURCC code for capability struct TypedKey @0xe2d567a9f1e61b29 { kind @0 :CryptoKind; @@ -189,24 +190,8 @@ struct DialInfoDetail @0x96423aa1d67b74d8 { class @1 :DialInfoClass; } -struct PublicInternetNodeStatus @0x9c9d7f1f12eb088f { - willRoute @0 :Bool; - willTunnel @1 :Bool; - willSignal @2 :Bool; - willRelay @3 :Bool; - willValidateDialInfo @4 :Bool; -} - -struct LocalNetworkNodeStatus @0x957f5bfed2d0b5a5 { - willRelay @0 :Bool; - willValidateDialInfo @1 :Bool; -} - struct NodeStatus @0xd36b9e7a3bf3330d { - union { - publicInternet @0 :PublicInternetNodeStatus; - localNetwork @1 :LocalNetworkNodeStatus; - } + # Reserved for non-nodeinfo status } struct ProtocolTypeSet @0x82f12f55a1b73326 { @@ -231,7 +216,8 @@ struct NodeInfo @0xe125d847e3f9f419 { addressTypes @2 :AddressTypeSet; # address types supported envelopeSupport @3 :List(UInt8); # supported rpc envelope/receipt versions cryptoSupport @4 :List(CryptoKind); # cryptography systems supported - dialInfoDetailList @5 :List(DialInfoDetail); # inbound dial info details for this node + capabilities @5 :List(Capability); # capabilities supported by the node + dialInfoDetailList @6 :List(DialInfoDetail); # inbound dial info details for this node } struct SignedDirectNodeInfo @0xe0e7ea3e893a3dd7 { @@ -287,7 +273,8 @@ struct OperationReturnReceipt @0xeb0fb5b5a9160eeb { } struct OperationFindNodeQ @0xfdef788fe9623bcd { - nodeId @0 :TypedKey; # node id to locate + nodeId @0 :TypedKey; # node id to locate + capabilities @1 :List(Capability); # required capabilities returned peers must have } struct OperationFindNodeA @0xa84cf2fb40c77089 { diff --git a/veilid-core/src/crypto/none/mod.rs b/veilid-core/src/crypto/none/mod.rs index b81056b6..48b80106 100644 --- a/veilid-core/src/crypto/none/mod.rs +++ b/veilid-core/src/crypto/none/mod.rs @@ -4,7 +4,7 @@ use data_encoding::BASE64URL_NOPAD; use digest::Digest; use rand::RngCore; const AEAD_OVERHEAD: usize = PUBLIC_KEY_LENGTH; -pub const CRYPTO_KIND_NONE: CryptoKind = FourCC([b'N', b'O', b'N', b'E']); +pub const CRYPTO_KIND_NONE: CryptoKind = FourCC(*b"NONE"); pub fn none_generate_keypair() -> KeyPair { let mut csprng = VeilidRng {}; diff --git a/veilid-core/src/crypto/vld0/mod.rs b/veilid-core/src/crypto/vld0/mod.rs index 680e7468..e13ec49d 100644 --- a/veilid-core/src/crypto/vld0/mod.rs +++ b/veilid-core/src/crypto/vld0/mod.rs @@ -15,7 +15,7 @@ use ed25519_dalek as ed; use x25519_dalek as xd; const AEAD_OVERHEAD: usize = 16; -pub const CRYPTO_KIND_VLD0: CryptoKind = FourCC([b'V', b'L', b'D', b'0']); +pub const CRYPTO_KIND_VLD0: CryptoKind = FourCC(*b"VLD0"); fn ed25519_to_x25519_pk(key: &ed::PublicKey) -> VeilidAPIResult { let bytes = key.to_bytes(); diff --git a/veilid-core/src/network_manager/address_filter.rs b/veilid-core/src/network_manager/address_filter.rs index 9818c671..326a41b0 100644 --- a/veilid-core/src/network_manager/address_filter.rs +++ b/veilid-core/src/network_manager/address_filter.rs @@ -115,6 +115,7 @@ impl AddressFilter { } } for key in dead_keys { + log_net!(debug ">>> FORGIVING: {}", key); inner.punishments_by_ip4.remove(&key); } } @@ -130,6 +131,7 @@ impl AddressFilter { } } for key in dead_keys { + log_net!(debug ">>> FORGIVING: {}", key); inner.punishments_by_ip6_prefix.remove(&key); } } diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index 3be13686..18cacbf6 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -5,31 +5,31 @@ mod native; #[cfg(target_arch = "wasm32")] mod wasm; -mod direct_boot; -mod send_data; -mod connection_handle; mod address_filter; +mod connection_handle; mod connection_manager; mod connection_table; +mod direct_boot; mod network_connection; +mod send_data; +mod stats; mod tasks; mod types; -mod stats; pub mod tests; //////////////////////////////////////////////////////////////////////////////////////// pub use connection_manager::*; -pub use network_connection::*; -pub use types::*; -pub use send_data::*; pub use direct_boot::*; +pub use network_connection::*; +pub use send_data::*; pub use stats::*; +pub use types::*; //////////////////////////////////////////////////////////////////////////////////////// -use connection_handle::*; use address_filter::*; +use connection_handle::*; use crypto::*; use futures_util::stream::FuturesUnordered; use hashlink::LruCache; @@ -47,13 +47,16 @@ use wasm::*; pub const MAX_MESSAGE_SIZE: usize = MAX_ENVELOPE_SIZE; pub const IPADDR_TABLE_SIZE: usize = 1024; -pub const IPADDR_MAX_INACTIVE_DURATION_US: TimestampDuration = TimestampDuration::new(300_000_000u64); // 5 minutes +pub const IPADDR_MAX_INACTIVE_DURATION_US: TimestampDuration = + TimestampDuration::new(300_000_000u64); // 5 minutes pub const NODE_CONTACT_METHOD_CACHE_SIZE: usize = 1024; pub const PUBLIC_ADDRESS_CHANGE_DETECTION_COUNT: usize = 3; pub const PUBLIC_ADDRESS_CHECK_CACHE_SIZE: usize = 8; pub const PUBLIC_ADDRESS_CHECK_TASK_INTERVAL_SECS: u32 = 60; -pub const PUBLIC_ADDRESS_INCONSISTENCY_TIMEOUT_US: TimestampDuration = TimestampDuration::new(300_000_000u64); // 5 minutes -pub const PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US: TimestampDuration = TimestampDuration::new(3600_000_000u64); // 60 minutes +pub const PUBLIC_ADDRESS_INCONSISTENCY_TIMEOUT_US: TimestampDuration = + TimestampDuration::new(300_000_000u64); // 5 minutes +pub const PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US: TimestampDuration = + TimestampDuration::new(3600_000_000u64); // 60 minutes pub const ADDRESS_FILTER_TASK_INTERVAL_SECS: u32 = 60; pub const BOOT_MAGIC: &[u8; 4] = b"BOOT"; @@ -75,7 +78,6 @@ struct NetworkComponents { receipt_manager: ReceiptManager, } - #[derive(Debug)] struct ClientWhitelistEntry { last_seen_ts: Timestamp, @@ -134,7 +136,7 @@ struct NetworkManagerUnlockedInner { storage_manager: StorageManager, protected_store: ProtectedStore, table_store: TableStore, - #[cfg(feature="unstable-blockstore")] + #[cfg(feature = "unstable-blockstore")] block_store: BlockStore, crypto: Crypto, address_filter: AddressFilter, @@ -171,8 +173,7 @@ impl NetworkManager { storage_manager: StorageManager, protected_store: ProtectedStore, table_store: TableStore, - #[cfg(feature="unstable-blockstore")] - block_store: BlockStore, + #[cfg(feature = "unstable-blockstore")] block_store: BlockStore, crypto: Crypto, network_key: Option, ) -> NetworkManagerUnlockedInner { @@ -181,7 +182,7 @@ impl NetworkManager { storage_manager, protected_store, table_store, - #[cfg(feature="unstable-blockstore")] + #[cfg(feature = "unstable-blockstore")] block_store, crypto, address_filter: AddressFilter::new(config), @@ -200,18 +201,20 @@ impl NetworkManager { storage_manager: StorageManager, protected_store: ProtectedStore, table_store: TableStore, - #[cfg(feature="unstable-blockstore")] - block_store: BlockStore, + #[cfg(feature = "unstable-blockstore")] block_store: BlockStore, crypto: Crypto, ) -> Self { - // Make the network key let network_key = { let c = config.get(); let network_key_password = if let Some(nkp) = c.network.network_key_password.clone() { Some(nkp) } else { - if c.network.routing_table.bootstrap.contains(&"bootstrap.veilid.net".to_owned()) { + if c.network + .routing_table + .bootstrap + .contains(&"bootstrap.veilid.net".to_owned()) + { None } else { Some(c.network.routing_table.bootstrap.join(",")) @@ -224,7 +227,13 @@ impl NetworkManager { let bcs = crypto.best(); // Yes the use of the salt this way is generally bad, but this just needs to be hashed - Some(bcs.derive_shared_secret(network_key_password.as_bytes(), network_key_password.as_bytes()).expect("failed to derive network key")) + Some( + bcs.derive_shared_secret( + network_key_password.as_bytes(), + network_key_password.as_bytes(), + ) + .expect("failed to derive network key"), + ) } else { None } @@ -242,7 +251,7 @@ impl NetworkManager { storage_manager, protected_store, table_store, - #[cfg(feature="unstable-blockstore")] + #[cfg(feature = "unstable-blockstore")] block_store, crypto, network_key, @@ -271,7 +280,7 @@ impl NetworkManager { pub fn table_store(&self) -> TableStore { self.unlocked_inner.table_store.clone() } - #[cfg(feature="unstable-blockstore")] + #[cfg(feature = "unstable-blockstore")] pub fn block_store(&self) -> BlockStore { self.unlocked_inner.block_store.clone() } @@ -443,7 +452,7 @@ impl NetworkManager { pub fn update_client_whitelist(&self, client: TypedKey) { let mut inner = self.inner.lock(); - match inner.client_whitelist.entry(client, |_k,_v| { + match inner.client_whitelist.entry(client, |_k, _v| { // do nothing on LRU evict }) { hashlink::lru_cache::Entry::Occupied(mut entry) => { @@ -461,7 +470,7 @@ impl NetworkManager { pub fn check_client_whitelist(&self, client: TypedKey) -> bool { let mut inner = self.inner.lock(); - match inner.client_whitelist.entry(client, |_k,_v| { + match inner.client_whitelist.entry(client, |_k, _v| { // do nothing on LRU evict }) { hashlink::lru_cache::Entry::Occupied(mut entry) => { @@ -475,7 +484,8 @@ impl NetworkManager { pub fn purge_client_whitelist(&self) { let timeout_ms = self.with_config(|c| c.network.client_whitelist_timeout_ms); let mut inner = self.inner.lock(); - let cutoff_timestamp = get_aligned_timestamp() - TimestampDuration::new((timeout_ms as u64) * 1000u64); + let cutoff_timestamp = + get_aligned_timestamp() - TimestampDuration::new((timeout_ms as u64) * 1000u64); // Remove clients from the whitelist that haven't been since since our whitelist timeout while inner .client_whitelist @@ -493,66 +503,8 @@ impl NetworkManager { net.needs_restart() } - /// Get our node's capabilities in the PublicInternet routing domain - fn generate_public_internet_node_status(&self) -> PublicInternetNodeStatus { - let Some(own_peer_info) = self - .routing_table() - .get_own_peer_info(RoutingDomain::PublicInternet) else { - return PublicInternetNodeStatus { - will_route: false, - will_tunnel: false, - will_signal: false, - will_relay: false, - will_validate_dial_info: false, - }; - }; - let own_node_info = own_peer_info.signed_node_info().node_info(); - - let will_route = own_node_info.can_inbound_relay(); // xxx: eventually this may have more criteria added - let will_tunnel = own_node_info.can_inbound_relay(); // xxx: we may want to restrict by battery life and network bandwidth at some point - let will_signal = own_node_info.can_signal(); - let will_relay = own_node_info.can_inbound_relay(); - let will_validate_dial_info = own_node_info.can_validate_dial_info(); - - PublicInternetNodeStatus { - will_route, - will_tunnel, - will_signal, - will_relay, - will_validate_dial_info, - } - } - /// Get our node's capabilities in the LocalNetwork routing domain - fn generate_local_network_node_status(&self) -> LocalNetworkNodeStatus { - let Some(own_peer_info) = self - .routing_table() - .get_own_peer_info(RoutingDomain::LocalNetwork) else { - return LocalNetworkNodeStatus { - will_relay: false, - will_validate_dial_info: false, - }; - }; - - let own_node_info = own_peer_info.signed_node_info().node_info(); - - let will_relay = own_node_info.can_inbound_relay(); - let will_validate_dial_info = own_node_info.can_validate_dial_info(); - - LocalNetworkNodeStatus { - will_relay, - will_validate_dial_info, - } - } - - pub fn generate_node_status(&self, routing_domain: RoutingDomain) -> NodeStatus { - match routing_domain { - RoutingDomain::PublicInternet => { - NodeStatus::PublicInternet(self.generate_public_internet_node_status()) - } - RoutingDomain::LocalNetwork => { - NodeStatus::LocalNetwork(self.generate_local_network_node_status()) - } - } + pub fn generate_node_status(&self, _routing_domain: RoutingDomain) -> NodeStatus { + NodeStatus {} } /// Generates a multi-shot/normal receipt @@ -569,12 +521,18 @@ impl NetworkManager { // Generate receipt and serialized form to return let vcrypto = self.crypto().best(); - + let nonce = vcrypto.random_nonce(); let node_id = routing_table.node_id(vcrypto.kind()); let node_id_secret = routing_table.node_id_secret_key(vcrypto.kind()); - - let receipt = Receipt::try_new(best_envelope_version(), node_id.kind, nonce, node_id.value, extra_data)?; + + let receipt = Receipt::try_new( + best_envelope_version(), + node_id.kind, + nonce, + node_id.value, + extra_data, + )?; let out = receipt .to_signed_data(self.crypto(), &node_id_secret) .wrap_err("failed to generate signed receipt")?; @@ -598,12 +556,18 @@ impl NetworkManager { // Generate receipt and serialized form to return let vcrypto = self.crypto().best(); - + let nonce = vcrypto.random_nonce(); let node_id = routing_table.node_id(vcrypto.kind()); let node_id_secret = routing_table.node_id_secret_key(vcrypto.kind()); - - let receipt = Receipt::try_new(best_envelope_version(), node_id.kind, nonce, node_id.value, extra_data)?; + + let receipt = Receipt::try_new( + best_envelope_version(), + node_id.kind, + nonce, + node_id.value, + extra_data, + )?; let out = receipt .to_signed_data(self.crypto(), &node_id_secret) .wrap_err("failed to generate signed receipt")?; @@ -715,9 +679,10 @@ impl NetworkManager { ) { Ok(nr) => nr, Err(e) => { - return Ok(NetworkResult::invalid_message( - format!("unable to register reverse connect peerinfo: {}", e) - )); + return Ok(NetworkResult::invalid_message(format!( + "unable to register reverse connect peerinfo: {}", + e + ))); } }; @@ -738,9 +703,10 @@ impl NetworkManager { ) { Ok(nr) => nr, Err(e) => { - return Ok(NetworkResult::invalid_message( - format!("unable to register hole punch connect peerinfo: {}", e) - )); + return Ok(NetworkResult::invalid_message(format!( + "unable to register hole punch connect peerinfo: {}", + e + ))); } }; @@ -784,7 +750,10 @@ impl NetworkManager { } /// Builds an envelope for sending over the network - #[cfg_attr(feature="verbose-tracing", instrument(level = "trace", skip(self, body), err))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self, body), err) + )] fn build_envelope>( &self, dest_node_id: TypedKey, @@ -805,9 +774,21 @@ impl NetworkManager { let nonce = vcrypto.random_nonce(); // Encode envelope - let envelope = Envelope::new(version, node_id.kind, ts, nonce, node_id.value, dest_node_id.value); + let envelope = Envelope::new( + version, + node_id.kind, + ts, + nonce, + node_id.value, + dest_node_id.value, + ); envelope - .to_encrypted_data(self.crypto(), body.as_ref(), &node_id_secret, &self.unlocked_inner.network_key) + .to_encrypted_data( + self.crypto(), + body.as_ref(), + &node_id_secret, + &self.unlocked_inner.network_key, + ) .wrap_err("envelope failed to encode") } @@ -815,18 +796,20 @@ impl NetworkManager { /// node_ref is the direct destination to which the envelope will be sent /// If 'destination_node_ref' is specified, it can be different than the node_ref being sent to /// which will cause the envelope to be relayed - #[cfg_attr(feature="verbose-tracing", instrument(level = "trace", skip(self, body), ret, err))] + #[cfg_attr( + feature = "verbose-tracing", + instrument(level = "trace", skip(self, body), ret, err) + )] pub async fn send_envelope>( &self, node_ref: NodeRef, destination_node_ref: Option, body: B, ) -> EyreResult> { - let destination_node_ref = destination_node_ref.as_ref().unwrap_or(&node_ref).clone(); - + if !node_ref.same_entry(&destination_node_ref) { - log_net!( + log_net!( "sending envelope to {:?} via {:?}", destination_node_ref, node_ref @@ -886,7 +869,7 @@ impl NetworkManager { data: &mut [u8], connection_descriptor: ConnectionDescriptor, ) -> EyreResult { - #[cfg(feature="verbose-tracing")] + #[cfg(feature = "verbose-tracing")] let root = span!( parent: None, Level::TRACE, @@ -894,7 +877,7 @@ impl NetworkManager { "data.len" = data.len(), "descriptor" = ?connection_descriptor ); - #[cfg(feature="verbose-tracing")] + #[cfg(feature = "verbose-tracing")] let _root_enter = root.enter(); log_net!( @@ -905,10 +888,7 @@ impl NetworkManager { let remote_addr = connection_descriptor.remote_address().to_ip_addr(); // Network accounting - self.stats_packet_rcvd( - remote_addr, - ByteCount::new(data.len() as u64), - ); + self.stats_packet_rcvd(remote_addr, ByteCount::new(data.len() as u64)); // If this is a zero length packet, just drop it, because these are used for hole punching // and possibly other low-level network connectivity tasks and will never require @@ -949,20 +929,30 @@ impl NetworkManager { } // Decode envelope header (may fail signature validation) - let envelope = match Envelope::from_signed_data(self.crypto(), data, &self.unlocked_inner.network_key) { - Ok(v) => v, - Err(e) => { - log_net!(debug "envelope failed to decode: {}", e); - self.address_filter().punish(remote_addr); - return Ok(false); - } - }; + let envelope = + match Envelope::from_signed_data(self.crypto(), data, &self.unlocked_inner.network_key) + { + Ok(v) => v, + Err(e) => { + log_net!(debug "envelope failed to decode: {}", e); + self.address_filter().punish(remote_addr); + return Ok(false); + } + }; // Get timestamp range let (tsbehind, tsahead) = self.with_config(|c| { ( - c.network.rpc.max_timestamp_behind_ms.map(ms_to_us).map(TimestampDuration::new), - c.network.rpc.max_timestamp_ahead_ms.map(ms_to_us).map(TimestampDuration::new), + c.network + .rpc + .max_timestamp_behind_ms + .map(ms_to_us) + .map(TimestampDuration::new), + c.network + .rpc + .max_timestamp_ahead_ms + .map(ms_to_us) + .map(TimestampDuration::new), ) }); @@ -1005,7 +995,10 @@ impl NetworkManager { let some_relay_nr = if self.check_client_whitelist(sender_id) { // Full relay allowed, do a full resolve_node - match rpc.resolve_node(recipient_id, SafetySelection::Unsafe(Sequencing::default())).await { + match rpc + .resolve_node(recipient_id, SafetySelection::Unsafe(Sequencing::default())) + .await + { Ok(v) => v, Err(e) => { log_net!(debug "failed to resolve recipient node for relay, dropping outbound relayed packet: {}" ,e); @@ -1030,7 +1023,7 @@ impl NetworkManager { }; if let Some(relay_nr) = some_relay_nr { - // Force sequencing if this came in sequenced. + // Force sequencing if this came in sequenced. // The sender did the prefer/ensure calculation when it did get_contact_method, // so we don't need to do it here. let relay_nr = if connection_descriptor.remote().protocol_type().is_ordered() { @@ -1049,7 +1042,7 @@ impl NetworkManager { Ok(v) => v, Err(e) => { log_net!(debug "failed to forward envelope: {}" ,e); - return Ok(false); + return Ok(false); } } => [ format!(": relay_nr={}, data.len={}", relay_nr, data.len()) ] { return Ok(false); @@ -1064,15 +1057,19 @@ impl NetworkManager { let node_id_secret = routing_table.node_id_secret_key(envelope.get_crypto_kind()); // Decrypt the envelope body - let body = match envelope - .decrypt_body(self.crypto(), data, &node_id_secret, &self.unlocked_inner.network_key) { - Ok(v) => v, - Err(e) => { - log_net!(debug "failed to decrypt envelope body: {}",e); - self.address_filter().punish(remote_addr); - return Ok(false); - } - }; + let body = match envelope.decrypt_body( + self.crypto(), + data, + &node_id_secret, + &self.unlocked_inner.network_key, + ) { + Ok(v) => v, + Err(e) => { + log_net!(debug "failed to decrypt envelope body: {}",e); + self.address_filter().punish(remote_addr); + return Ok(false); + } + }; // Cache the envelope information in the routing table let source_noderef = match routing_table.register_node_with_existing_connection( @@ -1166,7 +1163,7 @@ impl NetworkManager { if pait.contains_key(&ipblock) { return; } - pacc.insert(ipblock, socket_address, |_k,_v| { + pacc.insert(ipblock, socket_address, |_k, _v| { // do nothing on LRU evict }); @@ -1224,8 +1221,8 @@ impl NetworkManager { .public_address_inconsistencies_table .entry(key) .or_insert_with(|| HashMap::new()); - let exp_ts = - get_aligned_timestamp() + PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US; + let exp_ts = get_aligned_timestamp() + + PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US; for i in inconsistencies { pait.insert(i, exp_ts); } @@ -1296,5 +1293,4 @@ impl NetworkManager { } } } - } diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index c773b944..a4cf7854 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -686,30 +686,30 @@ impl Network { let c = self.config.get(); let mut inbound = ProtocolTypeSet::new(); - if c.network.protocol.udp.enabled && c.capabilities.protocol_udp { + if c.network.protocol.udp.enabled { inbound.insert(ProtocolType::UDP); } - if c.network.protocol.tcp.listen && c.capabilities.protocol_accept_tcp { + if c.network.protocol.tcp.listen { inbound.insert(ProtocolType::TCP); } - if c.network.protocol.ws.listen && c.capabilities.protocol_accept_ws { + if c.network.protocol.ws.listen { inbound.insert(ProtocolType::WS); } - if c.network.protocol.wss.listen && c.capabilities.protocol_accept_wss { + if c.network.protocol.wss.listen { inbound.insert(ProtocolType::WSS); } let mut outbound = ProtocolTypeSet::new(); - if c.network.protocol.udp.enabled && c.capabilities.protocol_udp { + if c.network.protocol.udp.enabled { outbound.insert(ProtocolType::UDP); } - if c.network.protocol.tcp.connect && c.capabilities.protocol_connect_tcp { + if c.network.protocol.tcp.connect { outbound.insert(ProtocolType::TCP); } - if c.network.protocol.ws.connect && c.capabilities.protocol_connect_ws { + if c.network.protocol.ws.connect { outbound.insert(ProtocolType::WS); } - if c.network.protocol.wss.connect && c.capabilities.protocol_connect_wss { + if c.network.protocol.wss.connect { outbound.insert(ProtocolType::WSS); } @@ -773,16 +773,34 @@ impl Network { // set up the routing table's network config // if we have static public dialinfo, upgrade our network class + let public_internet_capabilities = { + let c = self.config.get(); + PUBLIC_INTERNET_CAPABILITIES + .iter() + .copied() + .filter(|cap| !c.capabilities.disable.contains(cap)) + .collect::>() + }; + let local_network_capabilities = { + let c = self.config.get(); + LOCAL_NETWORK_CAPABILITIES + .iter() + .copied() + .filter(|cap| !c.capabilities.disable.contains(cap)) + .collect::>() + }; editor_public_internet.setup_network( protocol_config.outbound, protocol_config.inbound, protocol_config.family_global, + public_internet_capabilities, ); editor_local_network.setup_network( protocol_config.outbound, protocol_config.inbound, protocol_config.family_local, + local_network_capabilities, ); let detect_address_changes = { let c = self.config.get(); diff --git a/veilid-core/src/network_manager/native/network_tcp.rs b/veilid-core/src/network_manager/native/network_tcp.rs index 925ebb31..3c9434bf 100644 --- a/veilid-core/src/network_manager/native/network_tcp.rs +++ b/veilid-core/src/network_manager/native/network_tcp.rs @@ -118,8 +118,8 @@ impl Network { return; } }; - let address_filter = self.network_manager().address_filter(); // Check to see if it is punished + let address_filter = self.network_manager().address_filter(); if address_filter.is_punished(peer_addr.ip()) { return; } diff --git a/veilid-core/src/network_manager/network_connection.rs b/veilid-core/src/network_manager/network_connection.rs index 4f532838..f657037b 100644 --- a/veilid-core/src/network_manager/network_connection.rs +++ b/veilid-core/src/network_manager/network_connection.rs @@ -240,6 +240,7 @@ impl NetworkConnection { ); let network_manager = connection_manager.network_manager(); + let address_filter = network_manager.address_filter(); let mut unord = FuturesUnordered::new(); let mut need_receiver = true; let mut need_sender = true; @@ -301,11 +302,20 @@ impl NetworkConnection { .then(|res| async { match res { Ok(v) => { - if v.is_no_connection() { - let peer_address = protocol_connection.descriptor().remote(); - log_net!(debug "Connection closed from: {} ({})", peer_address.socket_address().to_socket_addr(), peer_address.protocol_type()); + let peer_address = protocol_connection.descriptor().remote(); + + // Check to see if it is punished + if address_filter.is_punished(peer_address.to_socket_addr().ip()) { return RecvLoopAction::Finish; } + + // Check for connection close + if v.is_no_connection() { + log_net!(debug "Connection closed from: {} ({})", peer_address.to_socket_addr(), peer_address.protocol_type()); + return RecvLoopAction::Finish; + } + + // Log other network results let mut message = network_result_value_or_log!(v => [ format!(": protocol_connection={:?}", protocol_connection) ] { return RecvLoopAction::Finish; }); diff --git a/veilid-core/src/network_manager/tests/test_signed_node_info.rs b/veilid-core/src/network_manager/tests/test_signed_node_info.rs index e554b54a..1bcc2bb0 100644 --- a/veilid-core/src/network_manager/tests/test_signed_node_info.rs +++ b/veilid-core/src/network_manager/tests/test_signed_node_info.rs @@ -20,6 +20,7 @@ pub async fn test_signed_node_info() { AddressTypeSet::all(), VALID_ENVELOPE_VERSIONS.to_vec(), VALID_CRYPTO_KINDS.to_vec(), + PUBLIC_INTERNET_CAPABILITIES.to_vec(), vec![DialInfoDetail { class: DialInfoClass::Mapped, dial_info: DialInfo::udp(SocketAddress::default()), @@ -75,6 +76,7 @@ pub async fn test_signed_node_info() { AddressTypeSet::all(), VALID_ENVELOPE_VERSIONS.to_vec(), VALID_CRYPTO_KINDS.to_vec(), + PUBLIC_INTERNET_CAPABILITIES.to_vec(), vec![DialInfoDetail { class: DialInfoClass::Blocked, dial_info: DialInfo::udp(SocketAddress::default()), diff --git a/veilid-core/src/routing_table/bucket_entry.rs b/veilid-core/src/routing_table/bucket_entry.rs index fedf0d16..f0f6df94 100644 --- a/veilid-core/src/routing_table/bucket_entry.rs +++ b/veilid-core/src/routing_table/bucket_entry.rs @@ -51,7 +51,7 @@ pub struct BucketEntryPublicInternet { /// The last node info timestamp of ours that this entry has seen last_seen_our_node_info_ts: Timestamp, /// Last known node status - node_status: Option, + node_status: Option, } /// Bucket entry information specific to the LocalNetwork RoutingDomain @@ -63,7 +63,7 @@ pub struct BucketEntryLocalNetwork { /// The last node info timestamp of ours that this entry has seen last_seen_our_node_info_ts: Timestamp, /// Last known node status - node_status: Option, + node_status: Option, } /// The data associated with each bucket entry @@ -170,6 +170,13 @@ impl BucketEntryInner { common_crypto_kinds(&self.validated_node_ids.kinds(), other) } + /// Capability check + pub fn has_capabilities(&self, routing_domain: RoutingDomain, capabilities: &[Capability]) -> bool { + let Some(ni) = self.node_info(routing_domain) else { + return false; + }; + ni.has_capabilities(capabilities) + } // Less is faster pub fn cmp_fastest(e1: &Self, e2: &Self) -> std::cmp::Ordering { @@ -502,13 +509,13 @@ impl BucketEntryInner { &self.peer_stats } - pub fn update_node_status(&mut self, status: NodeStatus) { - match status { - NodeStatus::LocalNetwork(ln) => { - self.local_network.node_status = Some(ln); + pub fn update_node_status(&mut self, routing_domain: RoutingDomain, status: NodeStatus) { + match routing_domain { + RoutingDomain::LocalNetwork => { + self.local_network.node_status = Some(status); } - NodeStatus::PublicInternet(pi) => { - self.public_internet.node_status = Some(pi); + RoutingDomain::PublicInternet => { + self.public_internet.node_status = Some(status); } } } @@ -518,12 +525,12 @@ impl BucketEntryInner { .local_network .node_status .as_ref() - .map(|ln| NodeStatus::LocalNetwork(ln.clone())), + .map(|ns| ns.clone()), RoutingDomain::PublicInternet => self .public_internet .node_status .as_ref() - .map(|pi| NodeStatus::PublicInternet(pi.clone())), + .map(|ns| ns.clone()), } } diff --git a/veilid-core/src/routing_table/find_peers.rs b/veilid-core/src/routing_table/find_peers.rs index e7f289ea..52787196 100644 --- a/veilid-core/src/routing_table/find_peers.rs +++ b/veilid-core/src/routing_table/find_peers.rs @@ -2,7 +2,11 @@ use super::*; impl RoutingTable { /// Utility to find all closest nodes to a particular key, including possibly our own node and nodes further away from the key than our own, returning their peer info - pub fn find_all_closest_peers(&self, key: TypedKey) -> NetworkResult> { + pub fn find_all_closest_peers( + &self, + key: TypedKey, + capabilities: &[Capability], + ) -> NetworkResult> { let Some(own_peer_info) = self.get_own_peer_info(RoutingDomain::PublicInternet) else { // Our own node info is not yet available, drop this request. return NetworkResult::service_unavailable("Not finding closest peers because our peer info is not yet available"); @@ -12,11 +16,27 @@ impl RoutingTable { let filter = Box::new( move |rti: &RoutingTableInner, opt_entry: Option>| { // Ensure only things that are valid/signed in the PublicInternet domain are returned - rti.filter_has_valid_signed_node_info( + if !rti.filter_has_valid_signed_node_info( RoutingDomain::PublicInternet, true, - opt_entry, - ) + opt_entry.clone(), + ) { + return false; + } + // Ensure capabilities are met + match opt_entry { + Some(entry) => entry.with(rti, |_rti, e| { + e.has_capabilities(RoutingDomain::PublicInternet, capabilities) + }), + None => rti + .get_own_peer_info(RoutingDomain::PublicInternet) + .map(|pi| { + pi.signed_node_info() + .node_info() + .has_capabilities(capabilities) + }) + .unwrap_or(false), + } }, ) as RoutingTableEntryFilter; let filters = VecDeque::from([filter]); @@ -40,7 +60,12 @@ impl RoutingTable { } /// Utility to find nodes that are closer to a key than our own node, returning their peer info - pub fn find_peers_closer_to_key(&self, key: TypedKey) -> NetworkResult> { + /// Can filter based on a particular set of capabiltiies + pub fn find_peers_closer_to_key( + &self, + key: TypedKey, + required_capabilities: Vec, + ) -> NetworkResult> { // add node information for the requesting node to our routing table let crypto_kind = key.kind; let own_node_id = self.node_id(crypto_kind); @@ -59,24 +84,29 @@ impl RoutingTable { let Some(entry) = opt_entry else { return false; }; - // Ensure only things that are valid/signed in the PublicInternet domain are returned - if !rti.filter_has_valid_signed_node_info( - RoutingDomain::PublicInternet, - true, - Some(entry.clone()), - ) { - return false; - } - // Ensure things further from the key than our own node are not included - let Some(entry_node_id) = entry.with(rti, |_rti, e| e.node_ids().get(crypto_kind)) else { - return false; - }; - let entry_distance = vcrypto.distance(&entry_node_id.value, &key.value); - if entry_distance >= own_distance { - return false; - } - - true + // Ensure only things that have a minimum set of capabilities are returned + entry.with(rti, |rti, e| { + if !e.has_capabilities(RoutingDomain::PublicInternet, &required_capabilities) { + return false; + } + // Ensure only things that are valid/signed in the PublicInternet domain are returned + if !rti.filter_has_valid_signed_node_info( + RoutingDomain::PublicInternet, + true, + Some(entry.clone()), + ) { + return false; + } + // Ensure things further from the key than our own node are not included + let Some(entry_node_id) = e.node_ids().get(crypto_kind) else { + return false; + }; + let entry_distance = vcrypto.distance(&entry_node_id.value, &key.value); + if entry_distance >= own_distance { + return false; + } + true + }) }, ) as RoutingTableEntryFilter; let filters = VecDeque::from([filter]); diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 618faa77..2d6c29d9 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -1072,7 +1072,7 @@ impl RoutingTable { let res = network_result_try!( rpc_processor .clone() - .rpc_call_find_node(Destination::direct(node_ref), node_id) + .rpc_call_find_node(Destination::direct(node_ref), node_id, vec![]) .await? ); diff --git a/veilid-core/src/routing_table/node_ref.rs b/veilid-core/src/routing_table/node_ref.rs index 826cd662..a10dfecf 100644 --- a/veilid-core/src/routing_table/node_ref.rs +++ b/veilid-core/src/routing_table/node_ref.rs @@ -118,9 +118,9 @@ pub trait NodeRefBase: Sized { fn set_updated_since_last_network_change(&self) { self.operate_mut(|_rti, e| e.set_updated_since_last_network_change(true)); } - fn update_node_status(&self, node_status: NodeStatus) { + fn update_node_status(&self, routing_domain: RoutingDomain, node_status: NodeStatus) { self.operate_mut(|_rti, e| { - e.update_node_status(node_status); + e.update_node_status(routing_domain, node_status); }); } fn envelope_support(&self) -> Vec { diff --git a/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs b/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs index 83167860..f1ae27c4 100644 --- a/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs +++ b/veilid-core/src/routing_table/route_spec_store/route_spec_store.rs @@ -257,20 +257,9 @@ impl RouteSpecStore { // Exclude nodes with no publicinternet nodeinfo, or incompatible nodeinfo or node status won't route entry.with_inner(|e| { - let node_info_ok = - if let Some(sni) = e.signed_node_info(RoutingDomain::PublicInternet) { - sni.has_sequencing_matched_dial_info(sequencing) - } else { - false - }; - let node_status_ok = - if let Some(ns) = e.node_status(RoutingDomain::PublicInternet) { - ns.will_route() - } else { - false - }; - - node_info_ok && node_status_ok + e.signed_node_info(RoutingDomain::PublicInternet).map(|sni| + sni.has_sequencing_matched_dial_info(sequencing) && sni.node_info().has_capability(CAP_ROUTE) + ).unwrap_or(false) }) }, ) as RoutingTableEntryFilter; diff --git a/veilid-core/src/routing_table/routing_domain_editor.rs b/veilid-core/src/routing_table/routing_domain_editor.rs index d1694ec8..01f862ff 100644 --- a/veilid-core/src/routing_table/routing_domain_editor.rs +++ b/veilid-core/src/routing_table/routing_domain_editor.rs @@ -13,6 +13,7 @@ enum RoutingDomainChange { outbound_protocols: ProtocolTypeSet, inbound_protocols: ProtocolTypeSet, address_types: AddressTypeSet, + capabilities: Vec, }, SetNetworkClass { network_class: Option, @@ -79,11 +80,13 @@ impl RoutingDomainEditor { outbound_protocols: ProtocolTypeSet, inbound_protocols: ProtocolTypeSet, address_types: AddressTypeSet, + capabilities: Vec, ) { self.changes.push(RoutingDomainChange::SetupNetwork { outbound_protocols, inbound_protocols, address_types, + capabilities, }) } @@ -142,27 +145,32 @@ impl RoutingDomainEditor { outbound_protocols, inbound_protocols, address_types, + capabilities, } => { let old_outbound_protocols = detail.common().outbound_protocols(); let old_inbound_protocols = detail.common().inbound_protocols(); let old_address_types = detail.common().address_types(); + let old_capabilities = detail.common().capabilities(); let this_changed = old_outbound_protocols != outbound_protocols || old_inbound_protocols != inbound_protocols - || old_address_types != address_types; + || old_address_types != address_types + || old_capabilities != capabilities; debug!( - "[{:?}] setup network: {:?} {:?} {:?}", + "[{:?}] setup network: {:?} {:?} {:?} {:?}", self.routing_domain, outbound_protocols, inbound_protocols, - address_types + address_types, + capabilities ); detail.common_mut().setup_network( outbound_protocols, inbound_protocols, address_types, + capabilities, ); if this_changed { changed = true; diff --git a/veilid-core/src/routing_table/routing_domains.rs b/veilid-core/src/routing_table/routing_domains.rs index e9f96a1b..b1d0b97a 100644 --- a/veilid-core/src/routing_table/routing_domains.rs +++ b/veilid-core/src/routing_table/routing_domains.rs @@ -27,6 +27,7 @@ pub struct RoutingDomainDetailCommon { inbound_protocols: ProtocolTypeSet, address_types: AddressTypeSet, relay_node: Option, + capabilities: Vec, dial_info_details: Vec, // caches cached_peer_info: Mutex>, @@ -41,6 +42,7 @@ impl RoutingDomainDetailCommon { inbound_protocols: Default::default(), address_types: Default::default(), relay_node: Default::default(), + capabilities: Default::default(), dial_info_details: Default::default(), cached_peer_info: Mutex::new(Default::default()), } @@ -52,10 +54,12 @@ impl RoutingDomainDetailCommon { outbound_protocols: ProtocolTypeSet, inbound_protocols: ProtocolTypeSet, address_types: AddressTypeSet, + capabilities: Vec, ) { self.outbound_protocols = outbound_protocols; self.inbound_protocols = inbound_protocols; self.address_types = address_types; + self.capabilities = capabilities; self.clear_cache(); } @@ -75,6 +79,9 @@ impl RoutingDomainDetailCommon { pub fn address_types(&self) -> AddressTypeSet { self.address_types } + pub fn capabilities(&self) -> Vec { + self.capabilities.clone() + } pub fn relay_node(&self) -> Option { self.relay_node.clone() } @@ -108,6 +115,7 @@ impl RoutingDomainDetailCommon { self.address_types, VALID_ENVELOPE_VERSIONS.to_vec(), VALID_CRYPTO_KINDS.to_vec(), + self.capabilities.clone(), self.dial_info_details.clone() ); diff --git a/veilid-core/src/routing_table/tasks/bootstrap.rs b/veilid-core/src/routing_table/tasks/bootstrap.rs index 39db5b0f..1913e5be 100644 --- a/veilid-core/src/routing_table/tasks/bootstrap.rs +++ b/veilid-core/src/routing_table/tasks/bootstrap.rs @@ -344,6 +344,7 @@ impl RoutingTable { AddressTypeSet::all(), // Bootstraps are always IPV4 and IPV6 capable bsrec.envelope_support, // Envelope support is as specified in the bootstrap list crypto_support, // Crypto support is derived from list of node ids + vec![], // Bootstrap needs no capabilities bsrec.dial_info_details, // Dial info is as specified in the bootstrap list ))); diff --git a/veilid-core/src/routing_table/tasks/private_route_management.rs b/veilid-core/src/routing_table/tasks/private_route_management.rs index a5fe37d8..715c25aa 100644 --- a/veilid-core/src/routing_table/tasks/private_route_management.rs +++ b/veilid-core/src/routing_table/tasks/private_route_management.rs @@ -7,6 +7,14 @@ use stop_token::future::FutureExt as StopFutureExt; const BACKGROUND_SAFETY_ROUTE_COUNT: usize = 2; impl RoutingTable { + fn get_background_safety_route_count(&self) -> usize { + let c = self.config.get(); + if c.capabilities.disable.contains(&CAP_ROUTE) { + 0 + } else { + BACKGROUND_SAFETY_ROUTE_COUNT + } + } /// Fastest routes sort fn route_sort_latency_fn(a: &(RouteId, u64), b: &(RouteId, u64)) -> cmp::Ordering { let mut al = a.1; @@ -69,13 +77,14 @@ impl RoutingTable { unpublished_routes.sort_by(Self::route_sort_latency_fn); // Save up to N unpublished routes and test them - for x in 0..(usize::min(BACKGROUND_SAFETY_ROUTE_COUNT, unpublished_routes.len())) { + let background_safety_route_count = self.get_background_safety_route_count(); + for x in 0..(usize::min(background_safety_route_count, unpublished_routes.len())) { must_test_routes.push(unpublished_routes[x].0); } // Kill off all but N unpublished routes rather than testing them - if unpublished_routes.len() > BACKGROUND_SAFETY_ROUTE_COUNT { - for x in &unpublished_routes[BACKGROUND_SAFETY_ROUTE_COUNT..] { + if unpublished_routes.len() > background_safety_route_count { + for x in &unpublished_routes[background_safety_route_count..] { expired_routes.push(x.0); } } @@ -192,8 +201,11 @@ impl RoutingTable { } Option::<()>::None }); - if local_unpublished_route_count < BACKGROUND_SAFETY_ROUTE_COUNT { - let routes_to_allocate = BACKGROUND_SAFETY_ROUTE_COUNT - local_unpublished_route_count; + + let background_safety_route_count = self.get_background_safety_route_count(); + + if local_unpublished_route_count < background_safety_route_count { + let routes_to_allocate = background_safety_route_count - local_unpublished_route_count; // Newly allocated routes let mut newly_allocated_routes = Vec::new(); diff --git a/veilid-core/src/routing_table/tasks/relay_management.rs b/veilid-core/src/routing_table/tasks/relay_management.rs index 37d5ec5f..960c5bc9 100644 --- a/veilid-core/src/routing_table/tasks/relay_management.rs +++ b/veilid-core/src/routing_table/tasks/relay_management.rs @@ -100,6 +100,11 @@ impl RoutingTable { let can_serve_as_relay = e .node_info(RoutingDomain::PublicInternet) .map(|n| { + if !(n.has_capability(CAP_RELAY) && n.is_signal_capable()) { + // Needs to be able to signal and relay + return false; + } + let dids = n.all_filtered_dial_info_details(DialInfoDetail::NO_SORT, |did| { did.matches_filter(&outbound_dif) }); @@ -145,26 +150,23 @@ impl RoutingTable { inner.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, entry| { let entry2 = entry.clone(); entry.with(rti, |rti, e| { - // Ensure we have the node's status - if let Some(node_status) = e.node_status(routing_domain) { - // Ensure the node will relay - if node_status.will_relay() { - // Compare against previous candidate - if let Some(best_inbound_relay) = best_inbound_relay.as_mut() { - // Less is faster - let better = best_inbound_relay.with(rti, |_rti, best| { - // choose low latency stability for relays - BucketEntryInner::cmp_fastest_reliable(cur_ts, e, best) - == std::cmp::Ordering::Less - }); - // Now apply filter function and see if this node should be included - if better && relay_node_filter(e) { - *best_inbound_relay = entry2; - } - } else if relay_node_filter(e) { - // Always store the first candidate - best_inbound_relay = Some(entry2); + // Filter this node + if relay_node_filter(e) { + // Compare against previous candidate + if let Some(best_inbound_relay) = best_inbound_relay.as_mut() { + // Less is faster + let better = best_inbound_relay.with(rti, |_rti, best| { + // choose low latency stability for relays + BucketEntryInner::cmp_fastest_reliable(cur_ts, e, best) + == std::cmp::Ordering::Less + }); + // Now apply filter function and see if this node should be included + if better { + *best_inbound_relay = entry2; } + } else { + // Always store the first candidate + best_inbound_relay = Some(entry2); } } }); diff --git a/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs b/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs index 7a79b1ac..89811612 100644 --- a/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs +++ b/veilid-core/src/routing_table/tests/test_serialize_routing_table.rs @@ -100,6 +100,7 @@ pub async fn test_round_trip_peerinfo() { AddressTypeSet::new(), vec![0], vec![CRYPTO_KIND_VLD0], + PUBLIC_INTERNET_CAPABILITIES.to_vec(), vec![], ), Timestamp::new(0), diff --git a/veilid-core/src/routing_table/types/node_info.rs b/veilid-core/src/routing_table/types/node_info.rs index e67f4711..96c40637 100644 --- a/veilid-core/src/routing_table/types/node_info.rs +++ b/veilid-core/src/routing_table/types/node_info.rs @@ -1,5 +1,54 @@ use super::*; +pub type Capability = FourCC; +pub const CAP_ROUTE: Capability = FourCC(*b"ROUT"); +#[cfg(feature = "unstable-tunnels")] +pub const CAP_TUNNEL: Capability = FourCC(*b"TUNL"); +pub const CAP_SIGNAL: Capability = FourCC(*b"SGNL"); +pub const CAP_RELAY: Capability = FourCC(*b"RLAY"); +pub const CAP_VALIDATE_DIAL_INFO: Capability = FourCC(*b"DIAL"); +pub const CAP_DHT: Capability = FourCC(*b"DHTV"); +pub const CAP_APPMESSAGE: Capability = FourCC(*b"APPM"); +#[cfg(feature = "unstable-blockstore")] +pub const CAP_BLOCKSTORE: Capability = FourCC(*b"BLOC"); + +cfg_if! { + if #[cfg(all(feature = "unstable-blockstore", feature="unstable-tunnels"))] { + const PUBLIC_INTERNET_CAPABILITIES_LEN: usize = 8; + } else if #[cfg(any(feature = "unstable-blockstore", feature="unstable-tunnels"))] { + const PUBLIC_INTERNET_CAPABILITIES_LEN: usize = 7; + } else { + const PUBLIC_INTERNET_CAPABILITIES_LEN: usize = 6; + } +} +pub const PUBLIC_INTERNET_CAPABILITIES: [Capability; PUBLIC_INTERNET_CAPABILITIES_LEN] = [ + CAP_ROUTE, + #[cfg(feature = "unstable-tunnels")] + CAP_TUNNEL, + CAP_SIGNAL, + CAP_RELAY, + CAP_VALIDATE_DIAL_INFO, + CAP_DHT, + CAP_APPMESSAGE, + #[cfg(feature = "unstable-blockstore")] + CAP_BLOCKSTORE, +]; + +#[cfg(feature = "unstable-blockstore")] +const LOCAL_NETWORK_CAPABILITIES_LEN: usize = 4; +#[cfg(not(feature = "unstable-blockstore"))] +const LOCAL_NETWORK_CAPABILITIES_LEN: usize = 3; + +pub const LOCAL_NETWORK_CAPABILITIES: [Capability; LOCAL_NETWORK_CAPABILITIES_LEN] = [ + CAP_RELAY, + CAP_DHT, + CAP_APPMESSAGE, + #[cfg(feature = "unstable-blockstore")] + CAP_BLOCKSTORE, +]; + +pub const MAX_CAPABILITIES: usize = 64; + #[derive( Clone, Default, @@ -21,6 +70,7 @@ pub struct NodeInfo { address_types: AddressTypeSet, envelope_support: Vec, crypto_support: Vec, + capabilities: Vec, dial_info_detail_list: Vec, } @@ -31,6 +81,7 @@ impl NodeInfo { address_types: AddressTypeSet, envelope_support: Vec, crypto_support: Vec, + capabilities: Vec, dial_info_detail_list: Vec, ) -> Self { Self { @@ -39,6 +90,7 @@ impl NodeInfo { address_types, envelope_support, crypto_support, + capabilities, dial_info_detail_list, } } @@ -58,6 +110,9 @@ impl NodeInfo { pub fn crypto_support(&self) -> &[CryptoKind] { &self.crypto_support } + pub fn capabilities(&self) -> &[Capability] { + &self.capabilities + } pub fn dial_info_detail_list(&self) -> &[DialInfoDetail] { &self.dial_info_detail_list } @@ -144,8 +199,27 @@ impl NodeInfo { false } + pub fn has_capability(&self, cap: Capability) -> bool { + self.capabilities.contains(&cap) + } + pub fn has_capabilities(&self, capabilities: &[Capability]) -> bool { + for cap in capabilities { + if !self.has_capability(*cap) { + return false; + } + } + true + } + /// Can this node assist with signalling? Yes but only if it doesn't require signalling, itself. - pub fn can_signal(&self) -> bool { + /// Also used to determine if nodes are capable of validation of dial info, as that operation + /// has the same requirements, inbound capability and a dial info that requires no assistance + pub fn is_signal_capable(&self) -> bool { + // Has capability? + if !self.has_capability(CAP_SIGNAL) { + return false; + } + // Must be inbound capable if !matches!(self.network_class, NetworkClass::InboundCapable) { return false; @@ -158,16 +232,4 @@ impl NodeInfo { } true } - - /// Can this node relay be an inbound relay? - pub fn can_inbound_relay(&self) -> bool { - // For now this is the same - self.can_signal() - } - - /// Is this node capable of validating dial info - pub fn can_validate_dial_info(&self) -> bool { - // For now this is the same - self.can_signal() - } } diff --git a/veilid-core/src/routing_table/types/node_status.rs b/veilid-core/src/routing_table/types/node_status.rs index 11c388d0..2ecb1460 100644 --- a/veilid-core/src/routing_table/types/node_status.rs +++ b/veilid-core/src/routing_table/types/node_status.rs @@ -1,66 +1,9 @@ use super::*; -/// RoutingDomain-specific status for each node -/// is returned by the StatusA call - -/// PublicInternet RoutingDomain Status -#[derive( - Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PublicInternetNodeStatus { - pub will_route: bool, - pub will_tunnel: bool, - pub will_signal: bool, - pub will_relay: bool, - pub will_validate_dial_info: bool, -} - -#[derive( - Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct LocalNetworkNodeStatus { - pub will_relay: bool, - pub will_validate_dial_info: bool, -} +/// Non-nodeinfo status for each node is returned by the StatusA call #[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum NodeStatus { - PublicInternet(PublicInternetNodeStatus), - LocalNetwork(LocalNetworkNodeStatus), -} - -impl NodeStatus { - pub fn will_route(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_route, - NodeStatus::LocalNetwork(_) => false, - } - } - pub fn will_tunnel(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_tunnel, - NodeStatus::LocalNetwork(_) => false, - } - } - pub fn will_signal(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_signal, - NodeStatus::LocalNetwork(_) => false, - } - } - pub fn will_relay(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_relay, - NodeStatus::LocalNetwork(ln) => ln.will_relay, - } - } - pub fn will_validate_dial_info(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_validate_dial_info, - NodeStatus::LocalNetwork(ln) => ln.will_validate_dial_info, - } - } +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct NodeStatus { + // Reserved for expansion } diff --git a/veilid-core/src/rpc_processor/coders/node_info.rs b/veilid-core/src/rpc_processor/coders/node_info.rs index 874a7e6b..288d55d7 100644 --- a/veilid-core/src/rpc_processor/coders/node_info.rs +++ b/veilid-core/src/rpc_processor/coders/node_info.rs @@ -31,6 +31,18 @@ pub fn encode_node_info( s.clone_from_slice(&csvec); } + let mut cap_builder = builder + .reborrow() + .init_capabilities(node_info.capabilities().len() as u32); + if let Some(s) = cap_builder.as_slice() { + let capvec: Vec = node_info + .capabilities() + .iter() + .map(|x| u32::from_be_bytes(x.0)) + .collect(); + + s.clone_from_slice(&capvec); + } let mut didl_builder = builder.reborrow().init_dial_info_detail_list( node_info .dial_info_detail_list() @@ -71,13 +83,11 @@ pub fn decode_node_info(reader: &veilid_capnp::node_info::Reader) -> Result Result = reader + let cs_reader = reader .reborrow() .get_crypto_support() - .map_err(RPCError::protocol)? + .map_err(RPCError::protocol)?; + + if cs_reader.len() as usize > MAX_CRYPTO_KINDS { + return Err(RPCError::protocol("too many crypto kinds")); + } + + let crypto_support: Vec = cs_reader .as_slice() .map(|s| s.iter().map(|x| FourCC::from(x.to_be_bytes())).collect()) .unwrap_or_default(); @@ -117,6 +133,18 @@ pub fn decode_node_info(reader: &veilid_capnp::node_info::Reader) -> Result MAX_CAPABILITIES { + return Err(RPCError::protocol("too many capabilities")); + } + let capabilities = cap_reader + .as_slice() + .map(|s| s.iter().map(|x| FourCC::from(x.to_be_bytes())).collect()) + .unwrap_or_default(); + let didl_reader = reader .reborrow() .get_dial_info_detail_list() @@ -137,6 +165,7 @@ pub fn decode_node_info(reader: &veilid_capnp::node_info::Reader) -> Result Result<(), RPCError> { - builder.set_will_route(public_internet_node_status.will_route); - builder.set_will_tunnel(public_internet_node_status.will_tunnel); - builder.set_will_signal(public_internet_node_status.will_signal); - builder.set_will_relay(public_internet_node_status.will_relay); - builder.set_will_validate_dial_info(public_internet_node_status.will_validate_dial_info); - - Ok(()) -} - -pub fn decode_public_internet_node_status( - reader: &veilid_capnp::public_internet_node_status::Reader, -) -> Result { - Ok(PublicInternetNodeStatus { - will_route: reader.reborrow().get_will_route(), - will_tunnel: reader.reborrow().get_will_tunnel(), - will_signal: reader.reborrow().get_will_signal(), - will_relay: reader.reborrow().get_will_relay(), - will_validate_dial_info: reader.reborrow().get_will_validate_dial_info(), - }) -} - -pub fn encode_local_network_node_status( - local_network_node_status: &LocalNetworkNodeStatus, - builder: &mut veilid_capnp::local_network_node_status::Builder, -) -> Result<(), RPCError> { - builder.set_will_relay(local_network_node_status.will_relay); - builder.set_will_validate_dial_info(local_network_node_status.will_validate_dial_info); - - Ok(()) -} - -pub fn decode_local_network_node_status( - reader: &veilid_capnp::local_network_node_status::Reader, -) -> Result { - Ok(LocalNetworkNodeStatus { - will_relay: reader.reborrow().get_will_relay(), - will_validate_dial_info: reader.reborrow().get_will_validate_dial_info(), - }) -} - pub fn encode_node_status( - node_status: &NodeStatus, - builder: &mut veilid_capnp::node_status::Builder, + _node_status: &NodeStatus, + _builder: &mut veilid_capnp::node_status::Builder, ) -> Result<(), RPCError> { - match node_status { - NodeStatus::PublicInternet(ns) => { - let mut pi_builder = builder.reborrow().init_public_internet(); - encode_public_internet_node_status(&ns, &mut pi_builder) - } - NodeStatus::LocalNetwork(ns) => { - let mut ln_builder = builder.reborrow().init_local_network(); - encode_local_network_node_status(&ns, &mut ln_builder) - } - } + Ok(()) } pub fn decode_node_status( - reader: &veilid_capnp::node_status::Reader, + _reader: &veilid_capnp::node_status::Reader, ) -> Result { - Ok( - match reader - .which() - .map_err(RPCError::map_internal("invalid node status"))? - { - veilid_capnp::node_status::PublicInternet(pi) => { - let r = pi.map_err(RPCError::protocol)?; - let pins = decode_public_internet_node_status(&r)?; - NodeStatus::PublicInternet(pins) - } - veilid_capnp::node_status::LocalNetwork(ln) => { - let r = ln.map_err(RPCError::protocol)?; - let lnns = decode_local_network_node_status(&r)?; - NodeStatus::LocalNetwork(lnns) - } - }, - ) + Ok(NodeStatus {}) } diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_find_node.rs b/veilid-core/src/rpc_processor/coders/operations/operation_find_node.rs index 607dacbc..10848648 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_find_node.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_find_node.rs @@ -5,11 +5,15 @@ const MAX_FIND_NODE_A_PEERS_LEN: usize = 20; #[derive(Debug, Clone)] pub struct RPCOperationFindNodeQ { node_id: TypedKey, + capabilities: Vec, } impl RPCOperationFindNodeQ { - pub fn new(node_id: TypedKey) -> Self { - Self { node_id } + pub fn new(node_id: TypedKey, capabilities: Vec) -> Self { + Self { + node_id, + capabilities, + } } pub fn validate(&mut self, _validate_context: &RPCValidateContext) -> Result<(), RPCError> { Ok(()) @@ -18,15 +22,33 @@ impl RPCOperationFindNodeQ { // pub fn node_id(&self) -> &TypedKey { // &self.node_id // } + // pub fn capabilities(&self) -> &[Capability] { + // &self.capabilities + // } - pub fn destructure(self) -> TypedKey { - self.node_id + pub fn destructure(self) -> (TypedKey, Vec) { + (self.node_id, self.capabilities) } pub fn decode(reader: &veilid_capnp::operation_find_node_q::Reader) -> Result { let ni_reader = reader.get_node_id().map_err(RPCError::protocol)?; let node_id = decode_typed_key(&ni_reader)?; - Ok(Self { node_id }) + let cap_reader = reader + .reborrow() + .get_capabilities() + .map_err(RPCError::protocol)?; + if cap_reader.len() as usize > MAX_CAPABILITIES { + return Err(RPCError::protocol("too many capabilities")); + } + let capabilities = cap_reader + .as_slice() + .map(|s| s.iter().map(|x| FourCC::from(x.to_be_bytes())).collect()) + .unwrap_or_default(); + + Ok(Self { + node_id, + capabilities, + }) } pub fn encode( &self, @@ -34,6 +56,19 @@ impl RPCOperationFindNodeQ { ) -> Result<(), RPCError> { let mut ni_builder = builder.reborrow().init_node_id(); encode_typed_key(&self.node_id, &mut ni_builder); + + let mut cap_builder = builder + .reborrow() + .init_capabilities(self.capabilities.len() as u32); + if let Some(s) = cap_builder.as_slice() { + let capvec: Vec = self + .capabilities + .iter() + .map(|x| u32::from_be_bytes(x.0)) + .collect(); + + s.clone_from_slice(&capvec); + } Ok(()) } } diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index 1e41497c..06931555 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -117,6 +117,13 @@ impl RPCMessageHeader { // RPCMessageHeaderDetail::PrivateRouted(p) => p.direct.peer_noderef.clone(), // } // } + pub fn routing_domain(&self) -> RoutingDomain { + match &self.detail { + RPCMessageHeaderDetail::Direct(d) => d.routing_domain, + RPCMessageHeaderDetail::SafetyRouted(s) => s.direct.routing_domain, + RPCMessageHeaderDetail::PrivateRouted(p) => p.direct.routing_domain, + } + } pub fn direct_sender_node_id(&self) -> TypedKey { match &self.detail { RPCMessageHeaderDetail::Direct(d) => { @@ -435,9 +442,11 @@ impl RPCProcessor { &self, routing_domain: RoutingDomain, signed_node_info: &SignedNodeInfo, + capabilities: &[Capability], ) -> bool { let routing_table = self.routing_table(); routing_table.signed_node_info_is_valid_in_routing_domain(routing_domain, &signed_node_info) + && signed_node_info.node_info().has_capabilities(capabilities) } ////////////////////////////////////////////////////////////////////// @@ -463,6 +472,7 @@ impl RPCProcessor { .rpc_call_find_node( Destination::direct(next_node).with_safety(safety_selection), node_id, + vec![], ) .await { @@ -1467,7 +1477,11 @@ impl RPCProcessor { // Ensure the sender peer info is for the actual sender specified in the envelope // Sender PeerInfo was specified, update our routing table with it - if !self.verify_node_info(routing_domain, sender_peer_info.signed_node_info()) { + if !self.verify_node_info( + routing_domain, + sender_peer_info.signed_node_info(), + &[], + ) { return Ok(NetworkResult::invalid_message( "sender peerinfo has invalid peer scope", )); diff --git a/veilid-core/src/rpc_processor/rpc_app_call.rs b/veilid-core/src/rpc_processor/rpc_app_call.rs index cf0318de..4b54452a 100644 --- a/veilid-core/src/rpc_processor/rpc_app_call.rs +++ b/veilid-core/src/rpc_processor/rpc_app_call.rs @@ -53,6 +53,22 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi + .signed_node_info() + .node_info() + .has_capability(CAP_APPMESSAGE) + { + return Ok(NetworkResult::service_unavailable( + "app call is not available", + )); + } + } + } + // Get the question let (op_id, _, _, kind) = msg.operation.clone().destructure(); let app_call_q = match kind { diff --git a/veilid-core/src/rpc_processor/rpc_app_message.rs b/veilid-core/src/rpc_processor/rpc_app_message.rs index ba49c701..56800099 100644 --- a/veilid-core/src/rpc_processor/rpc_app_message.rs +++ b/veilid-core/src/rpc_processor/rpc_app_message.rs @@ -24,6 +24,22 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi + .signed_node_info() + .node_info() + .has_capability(CAP_APPMESSAGE) + { + return Ok(NetworkResult::service_unavailable( + "app message is not available", + )); + } + } + } + // Get the statement let (_, _, _, kind) = msg.operation.destructure(); let app_message = match kind { diff --git a/veilid-core/src/rpc_processor/rpc_cancel_tunnel.rs b/veilid-core/src/rpc_processor/rpc_cancel_tunnel.rs index 4698234a..2e761488 100644 --- a/veilid-core/src/rpc_processor/rpc_cancel_tunnel.rs +++ b/veilid-core/src/rpc_processor/rpc_cancel_tunnel.rs @@ -6,6 +6,25 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + #[cfg(feature = "unstable-tunnels")] + { + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi + .signed_node_info() + .node_info() + .has_capability(CAP_TUNNEL) + { + return Ok(NetworkResult::service_unavailable( + "tunnel is not available", + )); + } + } + } + } + Err(RPCError::unimplemented("process_cancel_tunnel_q")) } } diff --git a/veilid-core/src/rpc_processor/rpc_complete_tunnel.rs b/veilid-core/src/rpc_processor/rpc_complete_tunnel.rs index 307e659f..c13088c1 100644 --- a/veilid-core/src/rpc_processor/rpc_complete_tunnel.rs +++ b/veilid-core/src/rpc_processor/rpc_complete_tunnel.rs @@ -6,6 +6,24 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + #[cfg(feature = "unstable-tunnels")] + { + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi + .signed_node_info() + .node_info() + .has_capability(CAP_TUNNEL) + { + return Ok(NetworkResult::service_unavailable( + "tunnel is not available", + )); + } + } + } + } Err(RPCError::unimplemented("process_complete_tunnel_q")) } } diff --git a/veilid-core/src/rpc_processor/rpc_find_block.rs b/veilid-core/src/rpc_processor/rpc_find_block.rs index 3bfe81ea..600f7d83 100644 --- a/veilid-core/src/rpc_processor/rpc_find_block.rs +++ b/veilid-core/src/rpc_processor/rpc_find_block.rs @@ -6,6 +6,20 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + #[cfg(feature = "unstable-blockstore")] + { + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(detail.routing_domain) { + if !opi.signed_node_info().node_info().can_blockstore() { + return Ok(NetworkResult::service_unavailable( + "block store is not available", + )); + } + } + } + } Err(RPCError::unimplemented("process_find_block_q")) } } diff --git a/veilid-core/src/rpc_processor/rpc_find_node.rs b/veilid-core/src/rpc_processor/rpc_find_node.rs index afd05e18..6bbcf5fa 100644 --- a/veilid-core/src/rpc_processor/rpc_find_node.rs +++ b/veilid-core/src/rpc_processor/rpc_find_node.rs @@ -15,6 +15,7 @@ impl RPCProcessor { self, dest: Destination, node_id: TypedKey, + capabilities: Vec, ) -> Result>>, RPCError> { // Ensure destination never has a private route if matches!( @@ -29,7 +30,8 @@ impl RPCProcessor { )); } - let find_node_q_detail = RPCQuestionDetail::FindNodeQ(RPCOperationFindNodeQ::new(node_id)); + let find_node_q_detail = + RPCQuestionDetail::FindNodeQ(RPCOperationFindNodeQ::new(node_id, capabilities.clone())); let find_node_q = RPCQuestion::new( network_result_try!(self.get_destination_respond_to(&dest)?), find_node_q_detail, @@ -60,9 +62,13 @@ impl RPCProcessor { let peers = find_node_a.destructure(); for peer_info in &peers { - if !self.verify_node_info(RoutingDomain::PublicInternet, peer_info.signed_node_info()) { + if !self.verify_node_info( + RoutingDomain::PublicInternet, + peer_info.signed_node_info(), + &capabilities, + ) { return Ok(NetworkResult::invalid_message( - "find_node response has invalid peer scope", + "find_node response does not meet peer criteria", )); } } @@ -94,11 +100,12 @@ impl RPCProcessor { }, _ => panic!("not a question"), }; - let node_id = find_node_q.destructure(); + let (node_id, capabilities) = find_node_q.destructure(); // Get a chunk of the routing table near the requested node id let routing_table = self.routing_table(); - let closest_nodes = network_result_try!(routing_table.find_all_closest_peers(node_id)); + let closest_nodes = + network_result_try!(routing_table.find_all_closest_peers(node_id, &capabilities)); // Make FindNode answer let find_node_a = RPCOperationFindNodeA::new(closest_nodes)?; diff --git a/veilid-core/src/rpc_processor/rpc_get_value.rs b/veilid-core/src/rpc_processor/rpc_get_value.rs index 8f419f80..085bb0bc 100644 --- a/veilid-core/src/rpc_processor/rpc_get_value.rs +++ b/veilid-core/src/rpc_processor/rpc_get_value.rs @@ -163,6 +163,7 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ensure this never came over a private route, safety route is okay though match &msg.header.detail { RPCMessageHeaderDetail::Direct(_) | RPCMessageHeaderDetail::SafetyRouted(_) => {} @@ -172,7 +173,17 @@ impl RPCProcessor { )) } } - + // Ignore if disabled + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi.signed_node_info().node_info().has_capability(CAP_DHT) { + return Ok(NetworkResult::service_unavailable( + "dht is not available", + )); + } + } + } // Get the question let kind = msg.operation.kind().clone(); let get_value_q = match kind { @@ -188,7 +199,7 @@ impl RPCProcessor { // Get the nodes that we know about that are closer to the the key than our own node let routing_table = self.routing_table(); - let closer_to_key_peers = network_result_try!(routing_table.find_peers_closer_to_key(key)); + let closer_to_key_peers = network_result_try!(routing_table.find_peers_closer_to_key(key, vec![CAP_DHT])); let debug_string = format!( "IN <=== GetValueQ({} #{}{}) <== {}", diff --git a/veilid-core/src/rpc_processor/rpc_route.rs b/veilid-core/src/rpc_processor/rpc_route.rs index 7e45bccd..0c1b14be 100644 --- a/veilid-core/src/rpc_processor/rpc_route.rs +++ b/veilid-core/src/rpc_processor/rpc_route.rs @@ -365,6 +365,18 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi.signed_node_info().node_info().has_capability(CAP_ROUTE) { + return Ok(NetworkResult::service_unavailable( + "route is not available", + )); + } + } + } + // Get header detail, must be direct and not inside a route itself let detail = match msg.header.detail { RPCMessageHeaderDetail::Direct(detail) => detail, diff --git a/veilid-core/src/rpc_processor/rpc_set_value.rs b/veilid-core/src/rpc_processor/rpc_set_value.rs index 05bc3a23..9973b5ef 100644 --- a/veilid-core/src/rpc_processor/rpc_set_value.rs +++ b/veilid-core/src/rpc_processor/rpc_set_value.rs @@ -175,6 +175,17 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi.signed_node_info().node_info().has_capability(CAP_DHT) { + return Ok(NetworkResult::service_unavailable( + "dht is not available", + )); + } + } + } // Ensure this never came over a private route, safety route is okay though match &msg.header.detail { RPCMessageHeaderDetail::Direct(_) | RPCMessageHeaderDetail::SafetyRouted(_) => {} @@ -200,7 +211,7 @@ impl RPCProcessor { // Get the nodes that we know about that are closer to the the key than our own node let routing_table = self.routing_table(); - let closer_to_key_peers = network_result_try!(routing_table.find_peers_closer_to_key(key)); + let closer_to_key_peers = network_result_try!(routing_table.find_peers_closer_to_key(key, vec![CAP_DHT])); let debug_string = format!( "IN <=== SetValueQ({} #{} len={} seq={} writer={}{}) <== {}", diff --git a/veilid-core/src/rpc_processor/rpc_signal.rs b/veilid-core/src/rpc_processor/rpc_signal.rs index 224a9bac..4e32952a 100644 --- a/veilid-core/src/rpc_processor/rpc_signal.rs +++ b/veilid-core/src/rpc_processor/rpc_signal.rs @@ -37,6 +37,18 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi.signed_node_info().node_info().is_signal_capable() { + return Ok(NetworkResult::service_unavailable( + "signal is not available", + )); + } + } + } + // Can't allow anything other than direct packets here, as handling reverse connections // or anything like via signals over private routes would deanonymize the route match &msg.header.detail { diff --git a/veilid-core/src/rpc_processor/rpc_start_tunnel.rs b/veilid-core/src/rpc_processor/rpc_start_tunnel.rs index ce9bce42..972ba67b 100644 --- a/veilid-core/src/rpc_processor/rpc_start_tunnel.rs +++ b/veilid-core/src/rpc_processor/rpc_start_tunnel.rs @@ -6,6 +6,24 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + #[cfg(feature = "unstable-tunnels")] + { + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi + .signed_node_info() + .node_info() + .has_capability(CAP_TUNNEL) + { + return Ok(NetworkResult::service_unavailable( + "tunnel is not available", + )); + } + } + } + } Err(RPCError::unimplemented("process_start_tunnel_q")) } } diff --git a/veilid-core/src/rpc_processor/rpc_status.rs b/veilid-core/src/rpc_processor/rpc_status.rs index 22bb3817..cf4401ab 100644 --- a/veilid-core/src/rpc_processor/rpc_status.rs +++ b/veilid-core/src/rpc_processor/rpc_status.rs @@ -133,25 +133,8 @@ impl RPCProcessor { // Ensure the returned node status is the kind for the routing domain we asked for if let Some(target_nr) = opt_target_nr { if let Some(a_node_status) = a_node_status { - match routing_domain { - RoutingDomain::PublicInternet => { - if !matches!(a_node_status, NodeStatus::PublicInternet(_)) { - return Ok(NetworkResult::invalid_message( - "node status doesn't match PublicInternet routing domain", - )); - } - } - RoutingDomain::LocalNetwork => { - if !matches!(a_node_status, NodeStatus::LocalNetwork(_)) { - return Ok(NetworkResult::invalid_message( - "node status doesn't match LocalNetwork routing domain", - )); - } - } - } - // Update latest node status in routing table - target_nr.update_node_status(a_node_status.clone()); + target_nr.update_node_status(routing_domain, a_node_status.clone()); } } @@ -236,27 +219,10 @@ impl RPCProcessor { // Ensure the node status from the question is the kind for the routing domain we received the request in if let Some(q_node_status) = q_node_status { - match routing_domain { - RoutingDomain::PublicInternet => { - if !matches!(q_node_status, NodeStatus::PublicInternet(_)) { - return Ok(NetworkResult::invalid_message( - "node status doesn't match PublicInternet routing domain", - )); - } - } - RoutingDomain::LocalNetwork => { - if !matches!(q_node_status, NodeStatus::LocalNetwork(_)) { - return Ok(NetworkResult::invalid_message( - "node status doesn't match LocalNetwork routing domain", - )); - } - } - } - // update node status for the requesting node to our routing table if let Some(sender_nr) = msg.opt_sender_nr.clone() { // Update latest node status in routing table for the statusq sender - sender_nr.update_node_status(q_node_status.clone()); + sender_nr.update_node_status(routing_domain, q_node_status.clone()); } } diff --git a/veilid-core/src/rpc_processor/rpc_supply_block.rs b/veilid-core/src/rpc_processor/rpc_supply_block.rs index 5b27a512..630635eb 100644 --- a/veilid-core/src/rpc_processor/rpc_supply_block.rs +++ b/veilid-core/src/rpc_processor/rpc_supply_block.rs @@ -6,6 +6,20 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + #[cfg(feature = "unstable-blockstore")] + { + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(detail.routing_domain) { + if !opi.signed_node_info().node_info().can_blockstore() { + return Ok(NetworkResult::service_unavailable( + "block store is not available", + )); + } + } + } + } Err(RPCError::unimplemented("process_supply_block_q")) } } diff --git a/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs b/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs index 2ff38e4e..16598b31 100644 --- a/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs +++ b/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs @@ -67,6 +67,19 @@ impl RPCProcessor { } }; + // Ignore if disabled + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(detail.routing_domain) { + let ni = opi.signed_node_info().node_info(); + if !ni.has_capability(CAP_VALIDATE_DIAL_INFO) || !ni.is_signal_capable() { + return Ok(NetworkResult::service_unavailable( + "validate dial info is not available", + )); + } + } + } + // Get the statement let (_, _, _, kind) = msg.operation.destructure(); let (dial_info, receipt, redirect) = match kind { @@ -83,7 +96,6 @@ impl RPCProcessor { // We filter on the -outgoing- protocol capability status not the node's dial info // Use the address type though, to ensure we reach an ipv6 capable node if this is // an ipv6 address - let routing_table = self.routing_table(); let sender_node_id = TypedKey::new( detail.envelope.get_crypto_kind(), detail.envelope.get_sender_id(), @@ -104,11 +116,11 @@ impl RPCProcessor { move |rti: &RoutingTableInner, v: Option>| { let entry = v.unwrap(); entry.with(rti, move |_rti, e| { - if let Some(status) = &e.node_status(routing_domain) { - status.will_validate_dial_info() - } else { - true - } + e.node_info(routing_domain) + .map(|ni| { + ni.has_capability(CAP_VALIDATE_DIAL_INFO) && ni.is_signal_capable() + }) + .unwrap_or(false) }) }, ) as RoutingTableEntryFilter; diff --git a/veilid-core/src/rpc_processor/rpc_value_changed.rs b/veilid-core/src/rpc_processor/rpc_value_changed.rs index 621569e3..fd5b1910 100644 --- a/veilid-core/src/rpc_processor/rpc_value_changed.rs +++ b/veilid-core/src/rpc_processor/rpc_value_changed.rs @@ -6,6 +6,15 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi.signed_node_info().node_info().has_capability(CAP_DHT) { + return Ok(NetworkResult::service_unavailable("dht is not available")); + } + } + } Err(RPCError::unimplemented("process_value_changed")) } } diff --git a/veilid-core/src/rpc_processor/rpc_watch_value.rs b/veilid-core/src/rpc_processor/rpc_watch_value.rs index 0f4d24d1..e6f28474 100644 --- a/veilid-core/src/rpc_processor/rpc_watch_value.rs +++ b/veilid-core/src/rpc_processor/rpc_watch_value.rs @@ -6,6 +6,15 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Ignore if disabled + let routing_table = self.routing_table(); + { + if let Some(opi) = routing_table.get_own_peer_info(msg.header.routing_domain()) { + if !opi.signed_node_info().node_info().has_capability(CAP_DHT) { + return Ok(NetworkResult::service_unavailable("dht is not available")); + } + } + } Err(RPCError::unimplemented("process_watch_value_q")) } } diff --git a/veilid-core/src/tests/common/test_veilid_config.rs b/veilid-core/src/tests/common/test_veilid_config.rs index 1df141f9..9ba054ac 100644 --- a/veilid-core/src/tests/common/test_veilid_config.rs +++ b/veilid-core/src/tests/common/test_veilid_config.rs @@ -168,13 +168,7 @@ fn config_callback(key: String) -> ConfigCallbackReturn { match key.as_str() { "program_name" => Ok(Box::new(String::from("VeilidCoreTests"))), "namespace" => Ok(Box::new(String::from(""))), - "capabilities.protocol_udp" => Ok(Box::new(true)), - "capabilities.protocol_connect_tcp" => Ok(Box::new(true)), - "capabilities.protocol_accept_tcp" => Ok(Box::new(true)), - "capabilities.protocol_connect_ws" => Ok(Box::new(true)), - "capabilities.protocol_accept_ws" => Ok(Box::new(true)), - "capabilities.protocol_connect_wss" => Ok(Box::new(true)), - "capabilities.protocol_accept_wss" => Ok(Box::new(true)), + "capabilities.disable" => Ok(Box::new(Vec::::new())), "table_store.directory" => Ok(Box::new(get_table_store_path())), "table_store.delete" => Ok(Box::new(true)), "block_store.directory" => Ok(Box::new(get_block_store_path())), @@ -299,13 +293,7 @@ pub async fn test_config() { let inner = vc.get(); assert_eq!(inner.program_name, String::from("VeilidCoreTests")); assert_eq!(inner.namespace, String::from("")); - assert_eq!(inner.capabilities.protocol_udp, true); - assert_eq!(inner.capabilities.protocol_connect_tcp, true); - assert_eq!(inner.capabilities.protocol_accept_tcp, true); - assert_eq!(inner.capabilities.protocol_connect_ws, true); - assert_eq!(inner.capabilities.protocol_accept_ws, true); - assert_eq!(inner.capabilities.protocol_connect_wss, true); - assert_eq!(inner.capabilities.protocol_accept_wss, true); + assert_eq!(inner.capabilities.disable, Vec::::new()); assert_eq!(inner.table_store.directory, get_table_store_path()); assert_eq!(inner.table_store.delete, true); assert_eq!(inner.block_store.directory, get_block_store_path()); diff --git a/veilid-core/src/veilid_api/tests/fixtures.rs b/veilid-core/src/veilid_api/tests/fixtures.rs index b2791f68..cb81285d 100644 --- a/veilid-core/src/veilid_api/tests/fixtures.rs +++ b/veilid-core/src/veilid_api/tests/fixtures.rs @@ -76,13 +76,7 @@ pub fn fix_veilidconfiginner() -> VeilidConfigInner { program_name: "Bob".to_string(), namespace: "Internets".to_string(), capabilities: VeilidConfigCapabilities { - protocol_udp: false, - protocol_connect_tcp: true, - protocol_accept_tcp: false, - protocol_connect_ws: true, - protocol_accept_ws: false, - protocol_connect_wss: true, - protocol_accept_wss: false, + disable: Vec::new(), }, protected_store: VeilidConfigProtectedStore { allow_insecure_fallback: true, diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 6934bcca..3d51a0bd 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -468,13 +468,7 @@ pub struct VeilidConfigProtectedStore { JsonSchema, )] pub struct VeilidConfigCapabilities { - pub protocol_udp: bool, - pub protocol_connect_tcp: bool, - pub protocol_accept_tcp: bool, - pub protocol_connect_ws: bool, - pub protocol_accept_ws: bool, - pub protocol_connect_wss: bool, - pub protocol_accept_wss: bool, + pub disable: Vec, } #[derive( @@ -670,13 +664,7 @@ impl VeilidConfig { get_config!(inner.program_name); get_config!(inner.namespace); - get_config!(inner.capabilities.protocol_udp); - get_config!(inner.capabilities.protocol_connect_tcp); - get_config!(inner.capabilities.protocol_accept_tcp); - get_config!(inner.capabilities.protocol_connect_ws); - get_config!(inner.capabilities.protocol_accept_ws); - get_config!(inner.capabilities.protocol_connect_wss); - get_config!(inner.capabilities.protocol_accept_wss); + get_config!(inner.capabilities.disable); get_config!(inner.table_store.directory); get_config!(inner.table_store.delete); get_config!(inner.block_store.directory); diff --git a/veilid-flutter/lib/default_config.dart b/veilid-flutter/lib/default_config.dart index c323b70f..b0ae51f2 100644 --- a/veilid-flutter/lib/default_config.dart +++ b/veilid-flutter/lib/default_config.dart @@ -52,15 +52,7 @@ Future getDefaultVeilidConfig(String programName) async { return VeilidConfig( programName: programName, namespace: "", - capabilities: VeilidConfigCapabilities( - protocolUDP: !kIsWeb, - protocolConnectTCP: !kIsWeb, - protocolAcceptTCP: !kIsWeb, - protocolConnectWS: true, - protocolAcceptWS: !kIsWeb, - protocolConnectWSS: true, - protocolAcceptWSS: false, - ), + capabilities: VeilidConfigCapabilities(disable: []), protectedStore: VeilidConfigProtectedStore( allowInsecureFallback: false, alwaysUseInsecureStorage: false, diff --git a/veilid-flutter/lib/veilid_config.dart b/veilid-flutter/lib/veilid_config.dart index 62fa7e8f..c44e0659 100644 --- a/veilid-flutter/lib/veilid_config.dart +++ b/veilid-flutter/lib/veilid_config.dart @@ -867,44 +867,19 @@ class VeilidConfigProtectedStore { //////////// class VeilidConfigCapabilities { - bool protocolUDP; - bool protocolConnectTCP; - bool protocolAcceptTCP; - bool protocolConnectWS; - bool protocolAcceptWS; - bool protocolConnectWSS; - bool protocolAcceptWSS; + List disable; VeilidConfigCapabilities({ - required this.protocolUDP, - required this.protocolConnectTCP, - required this.protocolAcceptTCP, - required this.protocolConnectWS, - required this.protocolAcceptWS, - required this.protocolConnectWSS, - required this.protocolAcceptWSS, + required this.disable, }); Map toJson() { return { - 'protocol_udp': protocolUDP, - 'protocol_connect_tcp': protocolConnectTCP, - 'protocol_accept_tcp': protocolAcceptTCP, - 'protocol_connect_ws': protocolConnectWS, - 'protocol_accept_ws': protocolAcceptWS, - 'protocol_connect_wss': protocolConnectWSS, - 'protocol_accept_wss': protocolAcceptWSS, + 'disable': disable, }; } - VeilidConfigCapabilities.fromJson(dynamic json) - : protocolUDP = json['protocol_udp'], - protocolConnectTCP = json['protocol_connect_tcp'], - protocolAcceptTCP = json['protocol_accept_tcp'], - protocolConnectWS = json['protocol_connect_ws'], - protocolAcceptWS = json['protocol_accept_ws'], - protocolConnectWSS = json['protocol_connect_wss'], - protocolAcceptWSS = json['protocol_accept_wss']; + VeilidConfigCapabilities.fromJson(dynamic json) : disable = json['disable']; } //////////// diff --git a/veilid-python/veilid/config.py b/veilid-python/veilid/config.py index 9533a64b..255a091f 100644 --- a/veilid-python/veilid/config.py +++ b/veilid-python/veilid/config.py @@ -2,7 +2,7 @@ from dataclasses import dataclass, fields from enum import StrEnum from typing import Optional, Self -from .types import TypedKey, TypedSecret +from .types import TypedKey, TypedSecret, Capability class VeilidConfigLogLevel(StrEnum): @@ -41,13 +41,7 @@ class ConfigBase: @dataclass class VeilidConfigCapabilities(ConfigBase): - protocol_udp: bool - protocol_connect_tcp: bool - protocol_accept_tcp: bool - protocol_connect_ws: bool - protocol_accept_ws: bool - protocol_connect_wss: bool - protocol_accept_wss: bool + disable: list[Capability] @dataclass diff --git a/veilid-python/veilid/types.py b/veilid-python/veilid/types.py index 4f1ea344..eef8cd1d 100644 --- a/veilid-python/veilid/types.py +++ b/veilid-python/veilid/types.py @@ -52,6 +52,17 @@ class CryptoKind(StrEnum): CRYPTO_KIND_VLD0 = "VLD0" +class Capability(StrEnum): + CAP_WILL_ROUTE = "ROUT" + CAP_TUNNEL = "TUNL" + CAP_WILL_SIGNAL = "SGNL" + CAP_WILL_RELAY = "RLAY" + CAP_WILL_VALIDATE_DIAL_INFO = "DIAL" + CAP_WILL_DHT = "DHTV" + CAP_WILL_APPMESSAGE = "APPM" + CAP_BLOCKSTORE = "BLOC" + + class Stability(StrEnum): LOW_LATENCY = "LowLatency" RELIABLE = "Reliable" @@ -67,6 +78,7 @@ class DHTSchemaKind(StrEnum): DFLT = "DFLT" SMPL = "SMPL" + class SafetySelectionKind(StrEnum): UNSAFE = "Unsafe" SAFE = "Safe" @@ -235,11 +247,10 @@ class VeilidVersion: if self._patch < other._patch: return True return False - + def __eq__(self, other): return isinstance(other, VeilidVersion) and self.data == other.data and self.seq == other.seq and self.writer == other.writer - @property def major(self): return self._major @@ -308,7 +319,8 @@ class DHTSchema: if DHTSchemaKind(j["kind"]) == DHTSchemaKind.SMPL: return cls.smpl( j["o_cnt"], - [DHTSchemaSMPLMember.from_json(member) for member in j["members"]], + [DHTSchemaSMPLMember.from_json(member) + for member in j["members"]], ) raise Exception("Unknown DHTSchema kind", j["kind"]) @@ -339,7 +351,8 @@ class DHTRecordDescriptor: return cls( TypedKey(j["key"]), PublicKey(j["owner"]), - None if j["owner_secret"] is None else SecretKey(j["owner_secret"]), + None if j["owner_secret"] is None else SecretKey( + j["owner_secret"]), DHTSchema.from_json(j["schema"]), ) @@ -404,14 +417,15 @@ class SafetySpec: @classmethod def from_json(cls, j: dict) -> Self: - return cls(RouteId(j["preferred_route"]) if "preferred_route" in j else None, - j["hop_count"], - Stability(j["stability"]), - Sequencing(j["sequencing"])) + return cls(RouteId(j["preferred_route"]) if "preferred_route" in j else None, + j["hop_count"], + Stability(j["stability"]), + Sequencing(j["sequencing"])) def to_json(self) -> dict: return self.__dict__ + class SafetySelection: kind: SafetySelectionKind @@ -438,9 +452,8 @@ class SafetySelection: def to_json(self) -> dict: if self.kind == SafetySelectionKind.UNSAFE: - return {"Unsafe": self.sequencing } + return {"Unsafe": self.sequencing} elif self.kind == SafetySelectionKind.SAFE: - return {"Safe": self.safety_spec.to_json() } + return {"Safe": self.safety_spec.to_json()} else: raise Exception("Invalid SafetySelection") - diff --git a/veilid-server/src/settings.rs b/veilid-server/src/settings.rs index 10743c6a..1d7c0b3d 100644 --- a/veilid-server/src/settings.rs +++ b/veilid-server/src/settings.rs @@ -46,6 +46,8 @@ logging: testing: subnode_index: 0 core: + capabilities: + disable: [] protected_store: allow_insecure_fallback: true always_use_insecure_storage: true @@ -70,6 +72,7 @@ core: reverse_connection_receipt_time_ms: 5000 hole_punch_receipt_time_ms: 5000 network_key_password: null + disable_capabilites: [] routing_table: node_id: null node_id_secret: null @@ -622,8 +625,14 @@ pub struct ProtectedStore { pub new_device_encryption_key_password: Option, } +#[derive(Debug, Deserialize, Serialize)] +pub struct Capabilities { + pub disable: Vec, +} + #[derive(Debug, Deserialize, Serialize)] pub struct Core { + pub capabilities: Capabilities, pub protected_store: ProtectedStore, pub table_store: TableStore, pub block_store: BlockStore, @@ -962,6 +971,7 @@ impl Settings { set_config_value!(inner.logging.otlp.grpc_endpoint, value); set_config_value!(inner.logging.console.enabled, value); set_config_value!(inner.testing.subnode_index, value); + set_config_value!(inner.core.capabilities.disable, value); set_config_value!(inner.core.protected_store.allow_insecure_fallback, value); set_config_value!( inner.core.protected_store.always_use_insecure_storage, @@ -1093,13 +1103,14 @@ impl Settings { } else { format!("subnode{}", inner.testing.subnode_index) })), - "capabilities.protocol_udp" => Ok(Box::new(true)), - "capabilities.protocol_connect_tcp" => Ok(Box::new(true)), - "capabilities.protocol_accept_tcp" => Ok(Box::new(true)), - "capabilities.protocol_connect_ws" => Ok(Box::new(true)), - "capabilities.protocol_accept_ws" => Ok(Box::new(true)), - "capabilities.protocol_connect_wss" => Ok(Box::new(true)), - "capabilities.protocol_accept_wss" => Ok(Box::new(true)), + "capabilities.disable" => { + let mut caps = Vec::::new(); + for c in &inner.core.capabilities.disable { + let cap = FourCC::from_str(c.as_str()).map_err(VeilidAPIError::generic)?; + caps.push(cap); + } + Ok(Box::new(caps)) + } "protected_store.allow_insecure_fallback" => { Ok(Box::new(inner.core.protected_store.allow_insecure_fallback)) } diff --git a/veilid-wasm/tests/web.rs b/veilid-wasm/tests/web.rs index 5cfbfd26..8022f2bb 100644 --- a/veilid-wasm/tests/web.rs +++ b/veilid-wasm/tests/web.rs @@ -33,13 +33,7 @@ fn init_callbacks() { window.configCallback = (configKey) => { switch(configKey) { case "namespace": return ""; - case "capabilities.protocol_udp": return false; - case "capabilities.protocol_connect_tcp": return false; - case "capabilities.protocol_accept_tcp": return false; - case "capabilities.protocol_connect_ws": return true; - case "capabilities.protocol_accept_ws": return false; - case "capabilities.protocol_connect_wss": return true; - case "capabilities.protocol_accept_wss": return false; + case "capabilities.disable": return []; case "tablestore.directory": return ""; case "network.routing_table.node_id": return []; case "network.routing_table.node_id_secret": return [];