diff --git a/veilid-core/src/crypto/types.rs b/veilid-core/src/crypto/types.rs index 49e37f4a..aeb4dcf1 100644 --- a/veilid-core/src/crypto/types.rs +++ b/veilid-core/src/crypto/types.rs @@ -201,8 +201,18 @@ impl TypedKeySet { self.remove(*k); } } + /// Return preferred typed key of our supported crypto kinds pub fn best(&self) -> Option { - self.items.first().copied() + match self.items.first().copied() { + None => None, + Some(k) => { + if !VALID_CRYPTO_KINDS.contains(&k.kind) { + None + } else { + Some(k) + } + } + } } pub fn len(&self) -> usize { self.items.len() @@ -221,6 +231,14 @@ impl TypedKeySet { } false } + pub fn contains_key(&self, key: &PublicKey) -> bool { + for tk in &self.items { + if tk.key == *key { + return true; + } + } + false + } } impl core::ops::Deref for TypedKeySet { @@ -264,6 +282,13 @@ impl FromStr for TypedKeySet { Ok(Self { items }) } } +impl From for TypedKeySet { + fn from(x: TypedKey) -> Self { + let mut tks = TypedKeySet::with_capacity(1); + tks.add(x); + tks + } +} #[derive( Clone, diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 4dc83aae..98f1d04b 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -145,6 +145,15 @@ impl RoutingTableUnlockedInner { false } + pub fn matches_own_node_id_key(&self, node_id_key: &PublicKey) -> bool { + for (ck, v) in &self.node_id_keypairs { + if v.key == *node_id_key { + return true; + } + } + false + } + pub fn calculate_bucket_index(&self, node_id: &TypedKey) -> (CryptoKind, usize) { let crypto = self.crypto(); let self_node_id = self.node_id_keypairs.get(&node_id.kind).unwrap().key; @@ -587,6 +596,13 @@ impl RoutingTable { } } + /// Resolve an existing routing table entry using any crypto kind and return a reference to it + pub fn lookup_any_node_ref(&self, node_id_key: PublicKey) -> Option { + self.inner + .read() + .lookup_any_node_ref(self.clone(), node_id_key) + } + /// Resolve an existing routing table entry and return a reference to it pub fn lookup_node_ref(&self, node_id: TypedKey) -> Option { self.inner.read().lookup_node_ref(self.clone(), node_id) diff --git a/veilid-core/src/routing_table/route_spec_store.rs b/veilid-core/src/routing_table/route_spec_store.rs index 880eb54d..e751e4d0 100644 --- a/veilid-core/src/routing_table/route_spec_store.rs +++ b/veilid-core/src/routing_table/route_spec_store.rs @@ -1780,7 +1780,7 @@ impl RouteSpecStore { /// Import a remote private route for compilation #[instrument(level = "trace", skip(self, blob), ret, err)] - pub fn import_remote_private_route(&self, blob: Vec) -> EyreResult { + pub fn import_remote_private_route(&self, blob: Vec) -> EyreResult { xxx continue here, maybe formalize 'private route set' as having its own non-key identifier for both remote and local routes... just a uuid map to typedkeyset? // decode the pr blob let private_routes = RouteSpecStore::blob_to_private_routes(blob)?; @@ -2006,7 +2006,7 @@ impl RouteSpecStore { let inner = &mut *self.inner.lock(); // Check for stub route - if *key == self.unlocked_inner.routing_table.node_id() { + if self.unlocked_inner.routing_table.matches_own_node_id_key(key) { return None; } // Check for local route @@ -2096,7 +2096,7 @@ impl RouteSpecStore { } /// Convert binary blob to private route - pub fn blob_to_private_routes(blob: Vec) -> EyreResult> { + pub fn blob_to_private_routes(crypto: Crypto, blob: Vec) -> EyreResult> { // Deserialize count if blob.is_empty() { @@ -2123,7 +2123,7 @@ impl RouteSpecStore { .get_root::() .map_err(RPCError::internal) .wrap_err("failed to make reader for private_route")?; - let private_route = decode_private_route(&pr_reader).wrap_err("failed to decode private route")?; + let private_route = decode_private_route(&pr_reader, crypto).wrap_err("failed to decode private route")?; out.push(private_route); } Ok(out) diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index 798102cc..090b32d5 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -678,6 +678,17 @@ impl RoutingTableInner { Some(nr) } + /// Resolve an existing routing table entry using any crypto kind and return a reference to it + pub fn lookup_any_node_ref( + &self, + outer_self: RoutingTable, + node_id_key: PublicKey, + ) -> Option { + VALID_CRYPTO_KINDS + .iter() + .find_map(|ck| self.lookup_node_ref(outer_self, TypedKey::new(*ck, node_id_key))) + } + /// Resolve an existing routing table entry and return a reference to it pub fn lookup_node_ref(&self, outer_self: RoutingTable, node_id: TypedKey) -> Option { if self.unlocked_inner.matches_own_node_id(&[node_id]) { diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index 806a9637..561c648e 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -92,7 +92,7 @@ impl RPCMessageHeader { pub fn crypto_kind(&self) -> CryptoKind { match &self.detail { RPCMessageHeaderDetail::Direct(d) => d.envelope.get_crypto_kind(), - RPCMessageHeaderDetail::SafetyRouted(s) => todo!(), + RPCMessageHeaderDetail::SafetyRouted(s) => s.remote_safety_route., RPCMessageHeaderDetail::PrivateRouted(p) => todo!(), } } @@ -1199,7 +1199,7 @@ impl RPCProcessor { let routing_domain = detail.routing_domain; // Decode the operation - let sender_node_id = detail.envelope.get_sender_id(); + let sender_node_id = TypedKey::new(detail.envelope.get_crypto_kind(), detail.envelope.get_sender_id()); // Decode the RPC message let operation = { @@ -1208,7 +1208,7 @@ impl RPCProcessor { .get_root::() .map_err(RPCError::protocol) .map_err(logthru_rpc!())?; - RPCOperation::decode(&op_reader, Some(&sender_node_id))? + RPCOperation::decode(&op_reader, self.crypto.clone())? }; // Get the sender noderef, incorporating sender's peer info @@ -1217,14 +1217,14 @@ 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.filter_node_info(routing_domain, &sender_peer_info) { + if !self.filter_node_info(routing_domain, &sender_peer_info.signed_node_info) { return Err(RPCError::invalid_format( "sender peerinfo has invalid peer scope", )); } opt_sender_nr = self.routing_table().register_node_with_peer_info( routing_domain, - sender_peer_info, + sender_peer_info.clone(), false, ); } @@ -1255,7 +1255,7 @@ impl RPCProcessor { .get_root::() .map_err(RPCError::protocol) .map_err(logthru_rpc!())?; - RPCOperation::decode(&op_reader, None)? + RPCOperation::decode(&op_reader, self.crypto.clone())? }; // Make the RPC message diff --git a/veilid-core/src/rpc_processor/rpc_find_node.rs b/veilid-core/src/rpc_processor/rpc_find_node.rs index bdc88be0..a3a5af6e 100644 --- a/veilid-core/src/rpc_processor/rpc_find_node.rs +++ b/veilid-core/src/rpc_processor/rpc_find_node.rs @@ -89,13 +89,6 @@ impl RPCProcessor { _ => panic!("not a question"), }; - // Get the crypto kinds the requesting node is capable of - let crypto_kinds = if let Some(sender_nr) = msg.opt_sender_nr { - sender_nr.node_ids().kinds() - } else { - vec![msg.header.crypto_kind()] - }; - // add node information for the requesting node to our routing table let routing_table = self.routing_table(); let Some(own_peer_info) = routing_table.get_own_peer_info(RoutingDomain::PublicInternet) else { @@ -106,12 +99,6 @@ impl RPCProcessor { // find N nodes closest to the target node in our routing table let filter = Box::new( move |rti: &RoutingTableInner, opt_entry: Option>| { - // ensure the returned nodes have at least the crypto kind used to send the findnodeq - if let Some(entry) = opt_entry { - if !entry.with(rti, |_rti, e| e.crypto_kinds().contains(&crypto_kind)) { - return false; - } - } // Ensure only things that are valid/signed in the PublicInternet domain are returned rti.filter_has_valid_signed_node_info( RoutingDomain::PublicInternet, diff --git a/veilid-core/src/tests/common/test_table_store.rs b/veilid-core/src/tests/common/test_table_store.rs index d3f12c1a..42a8694c 100644 --- a/veilid-core/src/tests/common/test_table_store.rs +++ b/veilid-core/src/tests/common/test_table_store.rs @@ -123,18 +123,18 @@ pub async fn test_store_delete_load(ts: TableStore) { assert_eq!(db.load(2, b"baz").unwrap(), Some(b"QWERTY".to_vec())); } -pub async fn test_frozen(ts: TableStore) { +pub async fn test_frozen(vcrypto: CryptoSystemVersion, ts: TableStore) { trace!("test_frozen"); let _ = ts.delete("test"); let db = ts.open("test", 3).await.expect("should have opened"); - let (dht_key, _) = generate_secret(); + let (dht_key, _) = vcrypto.generate_keypair(); assert!(db.store_rkyv(0, b"asdf", &dht_key).await.is_ok()); - assert_eq!(db.load_rkyv::(0, b"qwer").unwrap(), None); + assert_eq!(db.load_rkyv::(0, b"qwer").unwrap(), None); - let d = match db.load_rkyv::(0, b"asdf") { + let d = match db.load_rkyv::(0, b"asdf") { Ok(x) => x, Err(e) => { panic!("couldn't decode: {}", e); @@ -155,12 +155,16 @@ pub async fn test_frozen(ts: TableStore) { pub async fn test_all() { let api = startup().await; + let crypto = api.crypto().unwrap(); let ts = api.table_store().unwrap(); - test_delete_open_delete(ts.clone()).await; - test_store_delete_load(ts.clone()).await; - test_frozen(ts.clone()).await; - let _ = ts.delete("test").await; + for ck in VALID_CRYPTO_KINDS { + let vcrypto = crypto.get(ck).unwrap(); + test_delete_open_delete(ts.clone()).await; + test_store_delete_load(ts.clone()).await; + test_frozen(vcrypto, ts.clone()).await; + let _ = ts.delete("test").await; + } shutdown(api).await; } diff --git a/veilid-core/src/tests/common/test_veilid_config.rs b/veilid-core/src/tests/common/test_veilid_config.rs index 6b175b8b..31af8b1f 100644 --- a/veilid-core/src/tests/common/test_veilid_config.rs +++ b/veilid-core/src/tests/common/test_veilid_config.rs @@ -192,9 +192,18 @@ fn config_callback(key: String) -> ConfigCallbackReturn { "network.client_whitelist_timeout_ms" => Ok(Box::new(300_000u32)), "network.reverse_connection_receipt_time_ms" => Ok(Box::new(5_000u32)), "network.hole_punch_receipt_time_ms" => Ok(Box::new(5_000u32)), - "network.node_id" => Ok(Box::new(Option::::None)), - "network.node_id_secret" => Ok(Box::new(Option::::None)), - "network.bootstrap" => Ok(Box::new(Vec::::new())), + "network.routing_table.node_ids" => { + let mut nids = BTreeMap::::new(); + nids.insert( + CRYPTO_KIND_VLD0, + VeilidConfigNodeId { + node_id: None, + node_id_secret: None, + }, + ); + Ok(Box::new(nids)) + } + "network.routing_table.bootstrap" => Ok(Box::new(Vec::::new())), "network.routing_table.limit_over_attached" => Ok(Box::new(64u32)), "network.routing_table.limit_fully_attached" => Ok(Box::new(32u32)), "network.routing_table.limit_attached_strong" => Ok(Box::new(16u32)), @@ -315,14 +324,13 @@ pub async fn test_config() { assert_eq!(inner.network.client_whitelist_timeout_ms, 300_000u32); assert_eq!(inner.network.reverse_connection_receipt_time_ms, 5_000u32); assert_eq!(inner.network.hole_punch_receipt_time_ms, 5_000u32); - assert!(inner.network.node_id.is_none()); - assert!(inner.network.node_id_secret.is_none()); - assert_eq!(inner.network.bootstrap, Vec::::new()); assert_eq!(inner.network.rpc.concurrency, 2u32); assert_eq!(inner.network.rpc.queue_size, 1024u32); assert_eq!(inner.network.rpc.timeout_ms, 10_000u32); assert_eq!(inner.network.rpc.max_route_hop_count, 4u8); assert_eq!(inner.network.rpc.default_route_hop_count, 1u8); + assert_eq!(inner.network.routing_table.node_ids.len(), 1); + assert_eq!(inner.network.routing_table.bootstrap, Vec::::new()); assert_eq!(inner.network.routing_table.limit_over_attached, 64u32); assert_eq!(inner.network.routing_table.limit_fully_attached, 32u32); assert_eq!(inner.network.routing_table.limit_attached_strong, 16u32); diff --git a/veilid-core/src/tests/common/test_veilid_core.rs b/veilid-core/src/tests/common/test_veilid_core.rs index cce9cdc6..41e670d2 100644 --- a/veilid-core/src/tests/common/test_veilid_core.rs +++ b/veilid-core/src/tests/common/test_veilid_core.rs @@ -50,64 +50,83 @@ pub async fn test_signed_node_info() { .await .expect("startup failed"); - // Test direct - let node_info = NodeInfo { - network_class: NetworkClass::InboundCapable, - outbound_protocols: ProtocolTypeSet::all(), - address_types: AddressTypeSet::all(), - min_version: 0, - max_version: 0, - dial_info_detail_list: vec![DialInfoDetail { - class: DialInfoClass::Mapped, - dial_info: DialInfo::udp(SocketAddress::default()), - }], - }; + let crypto = api.crypto().unwrap(); + for ck in VALID_CRYPTO_KINDS { + let vcrypto = crypto.get(ck).unwrap(); - let (pkey, skey) = generate_secret(); + // 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()), + }], + }; - let sni = - SignedDirectNodeInfo::with_secret(NodeId::new(pkey.clone()), node_info.clone(), &skey) - .unwrap(); - let _ = SignedDirectNodeInfo::new( - NodeId::new(pkey), - node_info.clone(), - sni.timestamp, - sni.signature.unwrap(), - ) - .unwrap(); + let (pkey, skey) = vcrypto.generate_keypair(); - // Test relayed - let node_info2 = NodeInfo { - network_class: NetworkClass::OutboundOnly, - outbound_protocols: ProtocolTypeSet::all(), - address_types: AddressTypeSet::all(), - min_version: 0, - max_version: 0, - dial_info_detail_list: vec![DialInfoDetail { - class: DialInfoClass::Blocked, - dial_info: DialInfo::udp(SocketAddress::default()), - }], - }; + let sni = SignedDirectNodeInfo::make_signatures( + crypto.clone(), + vec![TypedKeyPair::new(ck, pkey, skey)], + node_info.clone(), + ) + .unwrap(); + let mut tks: TypedKeySet = TypedKey::new(ck, pkey).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()); - let (pkey2, skey2) = generate_secret(); + // 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()), + }], + }; - let sni2 = SignedRelayedNodeInfo::make_signatures( - NodeId::new(pkey2.clone()), - node_info2.clone(), - NodeId::new(pkey.clone()), - sni.clone(), - &skey2, - ) - .unwrap(); - let _ = SignedRelayedNodeInfo::new( - NodeId::new(pkey2), - node_info2, - NodeId::new(pkey), - sni, - sni2.timestamp, - sni2.signature, - ) - .unwrap(); + let (pkey2, skey2) = vcrypto.generate_keypair(); + let mut tks2: TypedKeySet = TypedKey::new(ck, pkey2).into(); + let oldtks2len = tks2.len(); + + let sni2 = SignedRelayedNodeInfo::make_signatures( + crypto.clone(), + vec![TypedKeyPair::new(ck, pkey2, skey2)], + node_info2.clone(), + tks.clone(), + sni.clone(), + ) + .unwrap(); + let _ = SignedRelayedNodeInfo::new( + crypto.clone(), + &mut tks2, + node_info2, + tks, + sni, + sni2.timestamp, + sni2.signatures.clone(), + ) + .unwrap(); + + assert_eq!(tks2.len(), oldtks2len); + assert_eq!(tks2.len(), sni2.signatures.len()); + } api.shutdown().await; } diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 1a2a2a63..36b6018e 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -7,7 +7,7 @@ use routing_table::*; #[derive(Default, Debug)] struct DebugCache { - imported_routes: Vec, + imported_routes: Vec, } static DEBUG_CACHE: Mutex = Mutex::new(DebugCache { @@ -30,12 +30,12 @@ fn get_string(text: &str) -> Option { Some(text.to_owned()) } -fn get_route_id(rss: RouteSpecStore) -> impl Fn(&str) -> Option { +fn get_route_id(rss: RouteSpecStore) -> impl Fn(&str) -> Option { return move |text: &str| { if text.is_empty() { return None; } - match TypedKey::from_str(text).ok() { + match PublicKey::from_str(text).ok() { Some(key) => { let routes = rss.list_allocated_routes(|k, _| Some(*k)); if routes.contains(&key) { @@ -128,12 +128,31 @@ fn get_destination(routing_table: RoutingTable) -> impl FnOnce(&str) -> Option 5 { + let fcc = &text[0..4]; + if &text[4..5] != ":" { + return None; + } + let ck = match FourCC::from_str(fcc) { + Ok(v) => v, + Err(_) => { + return None; + } + }; + text = &text[5..]; + Some(ck) + } else { + None + }; let n = get_number(text)?; let mut dc = DEBUG_CACHE.lock(); - let pr_pubkey = dc.imported_routes.get(n)?; + let pr_pubkey = match opt_crypto_kind { + Some(ck) => dc.imported_routes.get(n)?.get(ck)?, + None => dc.imported_routes.get(n)?.best()?, + }; let rss = routing_table.route_spec_store(); - let Some(private_route) = rss.get_remote_private_route(&pr_pubkey) else { + let Some(private_route) = rss.get_remote_private_route(&pr_pubkey.key) else { // Remove imported route dc.imported_routes.remove(n); info!("removed dead imported route {}", n); @@ -150,15 +169,14 @@ fn get_destination(routing_table: RoutingTable) -> impl FnOnce(&str) -> Option impl FnOnce(&str) -> Option Option { fn get_typed_key(text: &str) -> Option { TypedKey::from_str(text).ok() } +fn get_public_key(text: &str) -> Option { + PublicKey::from_str(text).ok() +} fn get_node_ref(routing_table: RoutingTable) -> impl FnOnce(&str) -> Option { move |text| { @@ -198,8 +218,13 @@ fn get_node_ref(routing_table: RoutingTable) -> impl FnOnce(&str) -> Option format!("{}", v.encode()), + let out = match rss.allocate_route( + &VALID_CRYPTO_KINDS, + stability, + sequencing, + hop_count, + directions, + &[], + ) { + Ok(Some(v)) => format!("{}", v), Ok(None) => format!(""), Err(e) => { format!("Route allocation failed: {}", e) @@ -685,7 +717,7 @@ impl VeilidAPI { let routing_table = netman.routing_table(); let rss = routing_table.route_spec_store(); - let route_id = get_debug_argument_at(&args, 1, "debug_route", "route_id", get_typed_key)?; + let route_id = get_debug_argument_at(&args, 1, "debug_route", "route_id", get_public_key)?; // Unpublish route let out = if let Err(e) = rss.mark_route_published(&route_id, false) { @@ -701,7 +733,7 @@ impl VeilidAPI { let routing_table = netman.routing_table(); let rss = routing_table.route_spec_store(); - let route_id = get_debug_argument_at(&args, 1, "debug_route", "route_id", get_typed_key)?; + let route_id = get_debug_argument_at(&args, 1, "debug_route", "route_id", get_public_key)?; match rss.debug_route(&route_id) { Some(s) => Ok(s), diff --git a/veilid-core/src/veilid_api/types.rs b/veilid-core/src/veilid_api/types.rs index 266a09a0..c0f55cee 100644 --- a/veilid-core/src/veilid_api/types.rs +++ b/veilid-core/src/veilid_api/types.rs @@ -1842,7 +1842,7 @@ impl MatchesDialInfoFilter for DialInfo { ////////////////////////////////////////////////////////////////////////// -// Signed NodeInfo that can be passed around amongst peers and verifiable +/// Signed NodeInfo that can be passed around amongst peers and verifiable #[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] #[archive_attr(repr(C), derive(CheckBytes))] pub struct SignedDirectNodeInfo { @@ -1851,6 +1851,8 @@ pub struct SignedDirectNodeInfo { pub signatures: Vec, } impl SignedDirectNodeInfo { + /// Returns a new SignedDirectNodeInfo that has its signatures validated. Will modify the node_ids set to only include node_ids whose signatures validate + /// All signatures are stored however, as this can be passed to other nodes that may be able to validate those signatures. pub fn new( crypto: Crypto, node_ids: &mut TypedKeySet, @@ -1937,6 +1939,8 @@ pub struct SignedRelayedNodeInfo { } impl SignedRelayedNodeInfo { + /// Returns a new SignedRelayedNodeInfo that has its signatures validated. Will modify the node_ids set to only include node_ids whose signatures validate + /// All signatures are stored however, as this can be passed to other nodes that may be able to validate those signatures. pub fn new( crypto: Crypto, node_ids: &mut TypedKeySet,