diff --git a/veilid-core/proto/veilid.capnp b/veilid-core/proto/veilid.capnp index dfec750e..c7940275 100644 --- a/veilid-core/proto/veilid.capnp +++ b/veilid-core/proto/veilid.capnp @@ -288,7 +288,7 @@ struct OperationReturnReceipt @0xeb0fb5b5a9160eeb { } struct OperationFindNodeQ @0xfdef788fe9623bcd { - nodeId @0 :TypedKey; # node id to locate + nodeId @0 :TypedKey; # node id to locate } struct OperationFindNodeA @0xa84cf2fb40c77089 { diff --git a/veilid-core/src/crypto/byte_array_types.rs b/veilid-core/src/crypto/byte_array_types.rs index a16b92e3..7880a695 100644 --- a/veilid-core/src/crypto/byte_array_types.rs +++ b/veilid-core/src/crypto/byte_array_types.rs @@ -217,6 +217,14 @@ macro_rules! byte_array_type { } } + impl FromStr for $name { + type Err = VeilidAPIError; + + fn from_str(s: &str) -> Result { + $name::try_from(s) + } + } + impl TryFrom for $name { type Error = VeilidAPIError; fn try_from(value: String) -> Result { diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index dc25da2e..2a3e47fd 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -634,7 +634,7 @@ impl NetworkManager { pub async fn handle_private_receipt>( &self, receipt_data: R, - private_route: TypedKey, + private_route: PublicKey, ) -> NetworkResult<()> { let receipt_manager = self.receipt_manager(); diff --git a/veilid-core/src/receipt_manager.rs b/veilid-core/src/receipt_manager.rs index 427a5fcf..0df15aa1 100644 --- a/veilid-core/src/receipt_manager.rs +++ b/veilid-core/src/receipt_manager.rs @@ -11,7 +11,7 @@ pub enum ReceiptEvent { ReturnedOutOfBand, ReturnedInBand { inbound_noderef: NodeRef }, ReturnedSafety, - ReturnedPrivate { private_route: TypedKey }, + ReturnedPrivate { private_route: PublicKey }, Expired, Cancelled, } @@ -21,7 +21,7 @@ pub enum ReceiptReturned { OutOfBand, InBand { inbound_noderef: NodeRef }, Safety, - Private { private_route: TypedKey }, + Private { private_route: PublicKey }, } pub trait ReceiptCallback: Send + 'static { diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 48c17100..d0b180f0 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -891,11 +891,20 @@ impl RoutingTable { } #[instrument(level = "trace", skip(self), ret)] - pub fn register_find_node_answer(&self, peers: Vec) -> Vec { - // register nodes we'd found + pub fn register_find_node_answer( + &self, + crypto_kind: CryptoKind, + peers: Vec, + ) -> Vec { + // Register nodes we'd found let mut out = Vec::::with_capacity(peers.len()); for p in peers { - // register the node if it's new + // Ensure we're getting back nodes we asked for + if !p.node_ids.kinds().contains(&crypto_kind) { + continue; + } + + // Register the node if it's new if let Some(nr) = self.register_node_with_peer_info(RoutingDomain::PublicInternet, p, false) { @@ -922,29 +931,41 @@ impl RoutingTable { // register nodes we'd found Ok(NetworkResult::value( - self.register_find_node_answer(res.answer), + self.register_find_node_answer(node_id.kind, res.answer), )) } + /// Ask a remote node to list the nodes it has around the current node #[instrument(level = "trace", skip(self), ret, err)] - pub async fn find_self(&self, node_ref: NodeRef) -> EyreResult>> { - let self_node_id = self.node_id(); + pub async fn find_self( + &self, + crypto_kind: CryptoKind, + node_ref: NodeRef, + ) -> EyreResult>> { + let self_node_id = self.node_id(crypto_kind); self.find_node(node_ref, self_node_id).await } + /// Ask a remote node to list the nodes it has around itself #[instrument(level = "trace", skip(self), ret, err)] - pub async fn find_target(&self, node_ref: NodeRef) -> EyreResult>> { - let target_node_id = node_ref.node_id(); + pub async fn find_target( + &self, + crypto_kind: CryptoKind, + node_ref: NodeRef, + ) -> EyreResult>> { + let Some(target_node_id) = node_ref.node_ids().get(crypto_kind) else { + bail!("no target node ids for this crypto kind"); + }; self.find_node(node_ref, target_node_id).await } #[instrument(level = "trace", skip(self))] - pub async fn reverse_find_node(&self, node_ref: NodeRef, wide: bool) { - // Ask bootstrap node to 'find' our own node so we can get some more nodes near ourselves + pub async fn reverse_find_node(&self, crypto_kind: CryptoKind, node_ref: NodeRef, wide: bool) { + // Ask node to 'find node' on own node so we can get some more nodes near ourselves // and then contact those nodes to inform -them- that we exist - // Ask bootstrap server for nodes closest to our own node - let closest_nodes = network_result_value_or_log!(match self.find_self(node_ref.clone()).await { + // Ask node for nodes closest to our own node + let closest_nodes = network_result_value_or_log!(match self.find_self(crypto_kind, node_ref.clone()).await { Err(e) => { log_rtab!(error "find_self failed for {:?}: {:?}", @@ -960,7 +981,7 @@ impl RoutingTable { // Ask each node near us to find us as well if wide { for closest_nr in closest_nodes { - network_result_value_or_log!(match self.find_self(closest_nr.clone()).await { + network_result_value_or_log!(match self.find_self(crypto_kind, closest_nr.clone()).await { Err(e) => { log_rtab!(error "find_self failed for {:?}: {:?}", diff --git a/veilid-core/src/routing_table/route_spec_store.rs b/veilid-core/src/routing_table/route_spec_store.rs index cc03a7d9..880eb54d 100644 --- a/veilid-core/src/routing_table/route_spec_store.rs +++ b/veilid-core/src/routing_table/route_spec_store.rs @@ -168,7 +168,7 @@ pub struct RouteSpecDetail { /// Secret key #[with(Skip)] secret_key: SecretKey, - /// Route hops + /// Route hops (node id keys) hops: Vec, /// Route noderefs #[with(Skip)] @@ -444,7 +444,7 @@ impl RouteSpecStore { let mut dead_keys = Vec::new(); for (k, rsd) in &mut content.details { for h in &rsd.hops { - let Some(nr) = routing_table.lookup_node_ref(*h) else { + let Some(nr) = routing_table.lookup_node_ref(TypedKey::new(rsd.crypto_kind, *h)) else { dead_keys.push(*k); break; }; @@ -582,13 +582,13 @@ impl RouteSpecStore { fn detail<'a>( inner: &'a RouteSpecStoreInner, - route_spec_key: &TypedKey, + route_spec_key: &PublicKey, ) -> Option<&'a RouteSpecDetail> { inner.content.details.get(route_spec_key) } fn detail_mut<'a>( inner: &'a mut RouteSpecStoreInner, - route_spec_key: &TypedKey, + route_spec_key: &PublicKey, ) -> Option<&'a mut RouteSpecDetail> { inner.content.details.get_mut(route_spec_key) } @@ -1594,9 +1594,10 @@ impl RouteSpecStore { &self, inner: &mut RouteSpecStoreInner, rti: &RoutingTableInner, + crypto_kind: CryptoKind, safety_spec: &SafetySpec, direction: DirectionSet, - avoid_nodes: &[PublicKey], + avoid_nodes: &[TypedKey], ) -> EyreResult> { // Ensure the total hop count isn't too long for our config let max_route_hop_count = self.unlocked_inner.max_route_hop_count; @@ -1610,9 +1611,12 @@ impl RouteSpecStore { // See if the preferred route is here if let Some(preferred_route) = safety_spec.preferred_route { if let Some(preferred_rsd) = inner.content.details.get(&preferred_route) { - // Only use the preferred route if it doesn't end with the avoid nodes - if !avoid_node_ids.contains(preferred_rsd.hops.last().unwrap()) { - return Ok(Some(preferred_route)); + // Only use the preferred route if it has the desire crypto kind + if preferred_rsd.crypto_kind == crypto_kind { + // Only use the preferred route if it doesn't end with the avoid nodes + if !avoid_nodes.contains(&TypedKey::new(crypto_kind, preferred_rsd.hops.last().cloned().unwrap())) { + return Ok(Some(preferred_route)); + } } } } @@ -1657,7 +1661,7 @@ impl RouteSpecStore { &self, crypto_kind: CryptoKind, safety_spec: &SafetySpec, - avoid_nodes: &[PublicKey], + avoid_nodes: &[TypedKey], ) -> EyreResult> { let inner = &mut *self.inner.lock(); let routing_table = self.unlocked_inner.routing_table.clone(); @@ -1666,6 +1670,7 @@ impl RouteSpecStore { Ok(self.get_route_for_safety_spec_inner( inner, rti, + crypto_kind, safety_spec, Direction::Inbound.into(), avoid_nodes, @@ -1673,6 +1678,7 @@ impl RouteSpecStore { } /// Assemble private route for publication + /// Returns a PrivateRoute object for an allocated private route #[instrument(level = "trace", skip(self), err)] pub fn assemble_private_route( &self, @@ -1683,8 +1689,15 @@ impl RouteSpecStore { let routing_table = self.unlocked_inner.routing_table.clone(); let rti = &*routing_table.inner.read(); + // Get the route spec detail for the requested private route let rsd = Self::detail(inner, key).ok_or_else(|| eyre!("route does not exist"))?; + // Ensure we get the crypto for it + let crypto = routing_table.network_manager().crypto(); + let Some(vcrypto) = crypto.get(rsd.crypto_kind) else { + bail!("crypto not supported for route"); + }; + // See if we can optimize this compilation yet // We don't want to include full nodeinfo if we don't have to let optimized = optimized @@ -1696,7 +1709,10 @@ impl RouteSpecStore { if !rti.has_valid_own_node_info(RoutingDomain::PublicInternet) { bail!("can't make private routes until our node info is valid"); } - RouteNode::NodeId(NodeId::new(routing_table.node_id())) + let Some(node_id) = routing_table.node_ids().get(rsd.crypto_kind) else { + bail!("missing node id for crypto kind"); + }; + RouteNode::NodeId(node_id.key) } else { let Some(pi) = rti.get_own_peer_info(RoutingDomain::PublicInternet) else { bail!("can't make private routes until our node info is valid"); @@ -1706,12 +1722,11 @@ impl RouteSpecStore { next_hop: None, }; - let crypto = routing_table.network_manager().crypto(); // Loop for each hop let hop_count = rsd.hops.len(); // iterate hops in private route order (reverse, but inside out) for h in 0..hop_count { - let nonce = Crypto::get_random_nonce(); + let nonce = vcrypto.random_nonce(); let blob_data = { let mut rh_message = ::capnp::message::Builder::new_default(); @@ -1721,10 +1736,10 @@ impl RouteSpecStore { }; // Encrypt the previous blob ENC(nonce, DH(PKhop,SKpr)) - let dh_secret = crypto + let dh_secret = vcrypto .cached_dh(&rsd.hops[h], &rsd.secret_key) .wrap_err("dh failed")?; - let enc_msg_data = Crypto::encrypt_aead(blob_data.as_slice(), &nonce, &dh_secret, None) + let enc_msg_data = vcrypto.encrypt_aead(blob_data.as_slice(), &nonce, &dh_secret, None) .wrap_err("encryption failed")?; let route_hop_data = RouteHopData { nonce, @@ -1734,14 +1749,14 @@ impl RouteSpecStore { route_hop = RouteHop { node: if optimized { // Optimized, no peer info, just the dht key - RouteNode::NodeId(NodeId::new(rsd.hops[h])) + RouteNode::NodeId(rsd.hops[h]) } else { // Full peer info, required until we are sure the route has been fully established - let node_id = rsd.hops[h]; + let node_id = TypedKey::new(rsd.crypto_kind, rsd.hops[h]); let pi = rti .with_node_entry(node_id, |entry| { entry.with(rti, |_rti, e| { - e.make_peer_info(node_id, RoutingDomain::PublicInternet) + e.make_peer_info(RoutingDomain::PublicInternet) }) }) .flatten(); @@ -1755,7 +1770,7 @@ impl RouteSpecStore { } let private_route = PrivateRoute { - public_key: key.clone(), + public_key: TypedKey::new(rsd.crypto_kind, key.clone()), // add hop for 'FirstHop' hop_count: (hop_count + 1).try_into().unwrap(), hops: PrivateRouteHops::FirstHop(route_hop), @@ -1765,27 +1780,35 @@ 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 { // decode the pr blob - let private_route = RouteSpecStore::blob_to_private_route(blob)?; + let private_routes = RouteSpecStore::blob_to_private_routes(blob)?; - // ensure private route has first hop - if !matches!(private_route.hops, PrivateRouteHops::FirstHop(_)) { - bail!("private route must have first hop"); - } - - // ensure this isn't also an allocated route + let mut out = TypedKeySet::new(); let inner = &mut *self.inner.lock(); - if Self::detail(inner, &private_route.public_key).is_some() { - bail!("should not import allocated route"); + + for private_route in private_routes { + + // ensure private route has first hop + if !matches!(private_route.hops, PrivateRouteHops::FirstHop(_)) { + bail!("private route must have first hop"); + } + + // ensure this isn't also an allocated route + if Self::detail(inner, &private_route.public_key.key).is_some() { + bail!("should not import allocated route"); + } + + // store the private route in our cache + let cur_ts = get_aligned_timestamp(); + let key = Self::with_create_remote_private_route(inner, cur_ts, private_route, |r| { + r.private_route.as_ref().unwrap().public_key.clone() + }); + + out.add(key); } - // store the private route in our cache - let cur_ts = get_aligned_timestamp(); - let key = Self::with_create_remote_private_route(inner, cur_ts, private_route, |r| { - r.private_route.as_ref().unwrap().public_key.clone() - }); - Ok(key) + Ok(out) } /// Release a remote private route that is no longer in use @@ -1829,7 +1852,7 @@ impl RouteSpecStore { where F: FnOnce(&mut RemotePrivateRouteInfo) -> R, { - let pr_pubkey = private_route.public_key; + let pr_pubkey = private_route.public_key.key; let rpr = inner .cache @@ -2045,33 +2068,64 @@ impl RouteSpecStore { } } - /// Convert private route to binary blob - pub fn private_route_to_blob(private_route: &PrivateRoute) -> EyreResult> { + /// Convert private route list to binary blob + pub fn private_routes_to_blob(private_routes: &[PrivateRoute]) -> EyreResult> { let mut pr_message = ::capnp::message::Builder::new_default(); let mut pr_builder = pr_message.init_root::(); - encode_private_route(&private_route, &mut pr_builder) - .wrap_err("failed to encode private route")?; - + let mut buffer = vec![]; - capnp::serialize_packed::write_message(&mut buffer, &pr_message) - .map_err(RPCError::internal) - .wrap_err("failed to convert builder to vec")?; + + // Serialize count + let pr_count = private_routes.len(); + if pr_count > MAX_CRYPTO_KINDS { + bail!("too many crypto kinds to encode blob"); + } + let pr_count = pr_count as u8; + buffer.push(pr_count); + + // Serialize stream of private routes + for private_route in private_routes { + encode_private_route(private_route, &mut pr_builder) + .wrap_err("failed to encode private route")?; + + capnp::serialize_packed::write_message(&mut buffer, &pr_message) + .map_err(RPCError::internal) + .wrap_err("failed to convert builder to vec")?; + } Ok(buffer) } /// Convert binary blob to private route - pub fn blob_to_private_route(blob: Vec) -> EyreResult { - let reader = capnp::serialize_packed::read_message( - blob.as_slice(), - capnp::message::ReaderOptions::new(), - ) - .map_err(RPCError::internal) - .wrap_err("failed to make message reader")?; + pub fn blob_to_private_routes(blob: Vec) -> EyreResult> { - let pr_reader = reader - .get_root::() + // Deserialize count + if blob.is_empty() { + bail!("not deserializing empty private route blob"); + } + + let pr_count = blob[0] as usize; + if pr_count > MAX_CRYPTO_KINDS { + bail!("too many crypto kinds to decode blob"); + } + + // Deserialize stream of private routes + let pr_slice = &blob[1..]; + let mut out = Vec::with_capacity(pr_count); + for _ in 0..pr_count { + let reader = capnp::serialize_packed::read_message( + &mut pr_slice, + capnp::message::ReaderOptions::new(), + ) .map_err(RPCError::internal) - .wrap_err("failed to make reader for private_route")?; - decode_private_route(&pr_reader).wrap_err("failed to decode private route") + .wrap_err("failed to make message reader")?; + + let pr_reader = reader + .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")?; + 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 fb895efe..de60b8b0 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -413,6 +413,7 @@ impl RoutingTableInner { &self, routing_domain_set: RoutingDomainSet, min_state: BucketEntryState, + crypto_kinds: &[CryptoKind], xxx finish this and peer minimum refresh and bootstrap tick, then both routines ) -> usize { let mut count = 0usize; let cur_ts = get_aligned_timestamp(); @@ -451,7 +452,7 @@ impl RoutingTableInner { mut f: F, ) -> Option { for entry in self.all_entries { - if entry.with(self, |_rti, e| e.state(cur_ts) >= min_state) { + if entry.with_inner(|e| e.state(cur_ts) >= min_state) { if let Some(out) = f(self, entry) { return Some(out); } @@ -474,7 +475,7 @@ impl RoutingTableInner { // Collect all entries that are 'needs_ping' and have some node info making them reachable somehow let mut node_refs = Vec::::with_capacity(self.bucket_entry_count()); self.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, entry| { - if entry.with(rti, |rti, e| { + if entry.with_inner(|e| { // If this isn't in the routing domain we are checking, don't include it if !e.exists_in_routing_domain(rti, routing_domain) { return false; @@ -757,7 +758,7 @@ impl RoutingTableInner { let cur_ts = get_aligned_timestamp(); for entry in self.all_entries { - match entry.with(self, |_rti, e| e.state(cur_ts)) { + match entry.with_inner(|e| e.state(cur_ts)) { BucketEntryState::Reliable => { reliable_entry_count += 1; } @@ -807,7 +808,7 @@ impl RoutingTableInner { ) -> Vec { let public_node_filter = Box::new(|rti: &RoutingTableInner, v: Option>| { let entry = v.unwrap(); - entry.with(rti, |_rti, e| { + entry.with_inner(|e| { // skip nodes on local network if e.node_info(RoutingDomain::LocalNetwork).is_some() { return false; @@ -838,7 +839,7 @@ impl RoutingTableInner { ) -> bool { match entry { None => has_valid_own_node_info, - Some(entry) => entry.with(self, |_rti, e| { + Some(entry) => entry.with_inner(|e| { e.signed_node_info(routing_domain.into()) .map(|sni| sni.has_any_signature()) .unwrap_or(false) @@ -854,7 +855,7 @@ impl RoutingTableInner { ) -> PeerInfo { match entry { None => own_peer_info.clone(), - Some(entry) => entry.with(self, |_rti, e| e.make_peer_info(routing_domain).unwrap()), + Some(entry) => entry.with_inner(|e| e.make_peer_info(routing_domain).unwrap()), } } @@ -932,7 +933,7 @@ impl RoutingTableInner { move |rti: &RoutingTableInner, v: Option>| { if let Some(entry) = &v { // always filter out dead nodes - if entry.with(rti, |_rti, e| e.state(cur_ts) == BucketEntryState::Dead) { + if entry.with_inner(|e| e.state(cur_ts) == BucketEntryState::Dead) { false } else { true @@ -970,8 +971,8 @@ impl RoutingTableInner { // reliable nodes come first let ae = a_entry.as_ref().unwrap(); let be = b_entry.as_ref().unwrap(); - ae.with(rti, |rti, ae| { - be.with(rti, |_rti, be| { + ae.with_inner(|ae| { + be.with_inner(|be| { let ra = ae.check_reliable(cur_ts); let rb = be.check_reliable(cur_ts); if ra != rb { @@ -1020,7 +1021,25 @@ impl RoutingTableInner { { let cur_ts = get_aligned_timestamp(); - // closest sort + // Get the crypto kind + let crypto_kind = node_id.kind; + let vcrypto = self.unlocked_inner.crypto().get(crypto_kind).unwrap(); + + // Filter to ensure entries support the crypto kind in use + + let filter = Box::new( + move |rti: &RoutingTableInner, opt_entry: Option>| { + if let Some(entry) = opt_entry { + entry.with_inner(|e| e.crypto_kinds().contains(&crypto_kind)) + } else { + VALID_CRYPTO_KINDS.contains(&crypto_kind) + } + }, + ) as RoutingTableEntryFilter; + filters.push_front(filter); + + // Closest sort + // Distance is done using the node id's distance metric which may vary based on crypto system let sort = |rti: &RoutingTableInner, a_entry: &Option>, b_entry: &Option>| { @@ -1038,10 +1057,10 @@ impl RoutingTableInner { // reliable nodes come first, pessimistically treating our own node as unreliable let ra = a_entry .as_ref() - .map_or(false, |x| x.with(rti, |_rti, x| x.check_reliable(cur_ts))); + .map_or(false, |x| x.with_inner(|x| x.check_reliable(cur_ts))); let rb = b_entry .as_ref() - .map_or(false, |x| x.with(rti, |_rti, x| x.check_reliable(cur_ts))); + .map_or(false, |x| x.with_inner(|x| x.check_reliable(cur_ts))); if ra != rb { if ra { return core::cmp::Ordering::Less; @@ -1050,9 +1069,24 @@ impl RoutingTableInner { } } + // get keys + let a_key = if let Some(a_entry) = a_entry { + a_entry.with_inner(|e| e.node_ids().get(crypto_kind).unwrap()) + } else { + self.unlocked_inner.node_id(crypto_kind) + }; + let b_key = if let Some(b_entry) = b_entry { + b_entry.with_inner(|e| e.node_ids().get(crypto_kind).unwrap()) + } else { + self.unlocked_inner.node_id(crypto_kind) + }; + // distance is the next metric, closer nodes first - let da = distance(a_key, &node_id); - let db = distance(b_key, &node_id); + // since multiple cryptosystems are in use, the distance for a key is the shortest + // distance to that key over all supported cryptosystems + + let da = vcrypto.distance(&a_key.key, &node_id.key); + let db = vcrypto.distance(&b_key.key, &node_id.key); da.cmp(&db) }; diff --git a/veilid-core/src/routing_table/tasks/bootstrap.rs b/veilid-core/src/routing_table/tasks/bootstrap.rs index 5f1f4d4c..ccdf948d 100644 --- a/veilid-core/src/routing_table/tasks/bootstrap.rs +++ b/veilid-core/src/routing_table/tasks/bootstrap.rs @@ -262,12 +262,15 @@ impl RoutingTable { self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, false) { // Add this our futures to process in parallel - let routing_table = self.clone(); - unord.push( - // lets ask bootstrap to find ourselves now - async move { routing_table.reverse_find_node(nr, true).await } - .instrument(Span::current()), - ); + for crypto_kind in VALID_CRYPTO_KINDS { + let routing_table = self.clone(); + let nr = nr.clone(); + unord.push( + // lets ask bootstrap to find ourselves now + async move { routing_table.reverse_find_node(crypto_kind, nr, true).await } + .instrument(Span::current()), + ); + } } } } @@ -337,27 +340,32 @@ impl RoutingTable { self.register_node_with_peer_info(RoutingDomain::PublicInternet, pi, true) { // Add this our futures to process in parallel - let routing_table = self.clone(); - unord.push( - async move { - // Need VALID signed peer info, so ask bootstrap to find_node of itself - // which will ensure it has the bootstrap's signed peer info as part of the response - let _ = routing_table.find_target(nr.clone()).await; + for crypto_kind in VALID_CRYPTO_KINDS { + let nr = nr.clone(); + let routing_table = self.clone(); + unord.push( + async move { + // Need VALID signed peer info, so ask bootstrap to find_node of itself + // which will ensure it has the bootstrap's signed peer info as part of the response + let _ = routing_table.find_target(crypto_kind, nr.clone()).await; - // Ensure we got the signed peer info - if !nr.signed_node_info_has_valid_signature(RoutingDomain::PublicInternet) { - log_rtab!(warn - "bootstrap at {:?} did not return valid signed node info", - nr - ); - // If this node info is invalid, it will time out after being unpingable - } else { - // otherwise this bootstrap is valid, lets ask it to find ourselves now - routing_table.reverse_find_node(nr, true).await + // Ensure we got the signed peer info + if !nr + .signed_node_info_has_valid_signature(RoutingDomain::PublicInternet) + { + log_rtab!(warn + "bootstrap at {:?} did not return valid signed node info", + nr + ); + // If this node info is invalid, it will time out after being unpingable + } else { + // otherwise this bootstrap is valid, lets ask it to find ourselves now + routing_table.reverse_find_node(crypto_kind, nr, true).await + } } - } - .instrument(Span::current()), - ); + .instrument(Span::current()), + ); + } } } diff --git a/veilid-core/src/routing_table/tasks/peer_minimum_refresh.rs b/veilid-core/src/routing_table/tasks/peer_minimum_refresh.rs index e6f028d6..71c6deea 100644 --- a/veilid-core/src/routing_table/tasks/peer_minimum_refresh.rs +++ b/veilid-core/src/routing_table/tasks/peer_minimum_refresh.rs @@ -20,22 +20,39 @@ impl RoutingTable { // For the PublicInternet routing domain, get list of all peers we know about // even the unreliable ones, and ask them to find nodes close to our node too - let routing_table = self.clone(); - let noderefs = routing_table.find_fastest_nodes( - min_peer_count, - VecDeque::new(), - |_rti, entry: Option>| { - NodeRef::new(routing_table.clone(), entry.unwrap().clone(), None) - }, - ); let mut ord = FuturesOrdered::new(); - for nr in noderefs { + + for crypto_kind in VALID_CRYPTO_KINDS { let routing_table = self.clone(); - ord.push_back( - async move { routing_table.reverse_find_node(nr, false).await } - .instrument(Span::current()), + + let mut filters = VecDeque::new(); + let filter = Box::new( + move |rti: &RoutingTableInner, opt_entry: Option>| { + if let Some(entry) = opt_entry { + entry.with_inner(|e| e.crypto_kinds().contains(&crypto_kind)) + } else { + VALID_CRYPTO_KINDS.contains(&crypto_kind) + } + }, + ) as RoutingTableEntryFilter; + filters.push_front(filter); + + let noderefs = routing_table.find_fastest_nodes( + min_peer_count, + filters, + |_rti, entry: Option>| { + NodeRef::new(routing_table.clone(), entry.unwrap().clone(), None) + }, ); + + for nr in noderefs { + let routing_table = self.clone(); + ord.push_back( + async move { routing_table.reverse_find_node(nr, false).await } + .instrument(Span::current()), + ); + } } // do peer minimum search in order from fastest to slowest diff --git a/veilid-core/src/rpc_processor/coders/operations/answer.rs b/veilid-core/src/rpc_processor/coders/operations/answer.rs index 7dd429a2..7d068adf 100644 --- a/veilid-core/src/rpc_processor/coders/operations/answer.rs +++ b/veilid-core/src/rpc_processor/coders/operations/answer.rs @@ -15,9 +15,12 @@ impl RPCAnswer { pub fn desc(&self) -> &'static str { self.detail.desc() } - pub fn decode(reader: &veilid_capnp::answer::Reader) -> Result { + pub fn decode( + reader: &veilid_capnp::answer::Reader, + crypto: Crypto, + ) -> Result { let d_reader = reader.get_detail(); - let detail = RPCAnswerDetail::decode(&d_reader)?; + let detail = RPCAnswerDetail::decode(&d_reader, crypto)?; Ok(RPCAnswer { detail }) } pub fn encode(&self, builder: &mut veilid_capnp::answer::Builder) -> Result<(), RPCError> { @@ -60,6 +63,7 @@ impl RPCAnswerDetail { pub fn decode( reader: &veilid_capnp::answer::detail::Reader, + crypto: Crypto, ) -> Result { let which_reader = reader.which().map_err(RPCError::protocol)?; let out = match which_reader { @@ -70,7 +74,7 @@ impl RPCAnswerDetail { } veilid_capnp::answer::detail::FindNodeA(r) => { let op_reader = r.map_err(RPCError::protocol)?; - let out = RPCOperationFindNodeA::decode(&op_reader)?; + let out = RPCOperationFindNodeA::decode(&op_reader, crypto)?; RPCAnswerDetail::FindNodeA(out) } veilid_capnp::answer::detail::AppCallA(r) => { @@ -100,7 +104,7 @@ impl RPCAnswerDetail { } veilid_capnp::answer::detail::FindBlockA(r) => { let op_reader = r.map_err(RPCError::protocol)?; - let out = RPCOperationFindBlockA::decode(&op_reader)?; + let out = RPCOperationFindBlockA::decode(&op_reader, crypto)?; RPCAnswerDetail::FindBlockA(out) } veilid_capnp::answer::detail::StartTunnelA(r) => { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation.rs b/veilid-core/src/rpc_processor/coders/operations/operation.rs index 8f8790bd..d27b4d88 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation.rs @@ -29,12 +29,12 @@ impl RPCOperationKind { } veilid_capnp::operation::kind::Which::Statement(r) => { let q_reader = r.map_err(RPCError::protocol)?; - let out = RPCStatement::decode(&q_reader)?; + let out = RPCStatement::decode(&q_reader, crypto)?; RPCOperationKind::Statement(out) } veilid_capnp::operation::kind::Which::Answer(r) => { let q_reader = r.map_err(RPCError::protocol)?; - let out = RPCAnswer::decode(&q_reader)?; + let out = RPCAnswer::decode(&q_reader, crypto)?; RPCOperationKind::Answer(out) } }; diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_find_block.rs b/veilid-core/src/rpc_processor/coders/operations/operation_find_block.rs index 61256e46..4f15e6f0 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_find_block.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_find_block.rs @@ -35,6 +35,7 @@ pub struct RPCOperationFindBlockA { impl RPCOperationFindBlockA { pub fn decode( reader: &veilid_capnp::operation_find_block_a::Reader, + crypto: Crypto, ) -> Result { let data = reader.get_data().map_err(RPCError::protocol)?.to_vec(); @@ -46,7 +47,7 @@ impl RPCOperationFindBlockA { .map_err(RPCError::map_internal("too many suppliers"))?, ); for s in suppliers_reader.iter() { - let peer_info = decode_peer_info(&s)?; + let peer_info = decode_peer_info(&s, crypto.clone())?; suppliers.push(peer_info); } @@ -58,7 +59,7 @@ impl RPCOperationFindBlockA { .map_err(RPCError::map_internal("too many peers"))?, ); for p in peers_reader.iter() { - let peer_info = decode_peer_info(&p)?; + let peer_info = decode_peer_info(&p, crypto.clone())?; peers.push(peer_info); } 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 98f34d9e..c3511efa 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 @@ -10,7 +10,7 @@ impl RPCOperationFindNodeQ { reader: &veilid_capnp::operation_find_node_q::Reader, ) -> Result { let ni_reader = reader.get_node_id().map_err(RPCError::protocol)?; - let node_id = decode_key256(&ni_reader); + let node_id = decode_typed_key(&ni_reader)?; Ok(RPCOperationFindNodeQ { node_id }) } pub fn encode( @@ -18,7 +18,7 @@ impl RPCOperationFindNodeQ { builder: &mut veilid_capnp::operation_find_node_q::Builder, ) -> Result<(), RPCError> { let mut ni_builder = builder.reborrow().init_node_id(); - encode_key256(&self.node_id, &mut ni_builder)?; + encode_typed_key(&self.node_id, &mut ni_builder); Ok(()) } } @@ -31,6 +31,7 @@ pub struct RPCOperationFindNodeA { impl RPCOperationFindNodeA { pub fn decode( reader: &veilid_capnp::operation_find_node_a::Reader, + crypto: Crypto, ) -> Result { let peers_reader = reader.get_peers().map_err(RPCError::protocol)?; let mut peers = Vec::::with_capacity( @@ -40,7 +41,7 @@ impl RPCOperationFindNodeA { .map_err(RPCError::map_internal("too many peers"))?, ); for p in peers_reader.iter() { - let peer_info = decode_peer_info(&p)?; + let peer_info = decode_peer_info(&p, crypto.clone())?; peers.push(peer_info); } diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_route.rs b/veilid-core/src/rpc_processor/coders/operations/operation_route.rs index 6485bcac..e98c16de 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_route.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_route.rs @@ -29,7 +29,7 @@ impl RoutedOperation { .map_err(RPCError::map_internal("too many signatures"))?, ); for s in sigs_reader.iter() { - let sig = decode_typed_signature(&s); + let sig = decode_typed_signature(&s)?; signatures.push(sig); } @@ -80,9 +80,10 @@ pub struct RPCOperationRoute { impl RPCOperationRoute { pub fn decode( reader: &veilid_capnp::operation_route::Reader, + crypto: Crypto, ) -> Result { let sr_reader = reader.get_safety_route().map_err(RPCError::protocol)?; - let safety_route = decode_safety_route(&sr_reader)?; + let safety_route = decode_safety_route(&sr_reader, crypto)?; let o_reader = reader.get_operation().map_err(RPCError::protocol)?; let operation = RoutedOperation::decode(&o_reader)?; diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_signal.rs b/veilid-core/src/rpc_processor/coders/operations/operation_signal.rs index 4b8a6fd3..a414e300 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_signal.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_signal.rs @@ -8,8 +8,9 @@ pub struct RPCOperationSignal { impl RPCOperationSignal { pub fn decode( reader: &veilid_capnp::operation_signal::Reader, + crypto: Crypto, ) -> Result { - let signal_info = decode_signal_info(reader)?; + let signal_info = decode_signal_info(reader, crypto)?; Ok(RPCOperationSignal { signal_info }) } pub fn encode( diff --git a/veilid-core/src/rpc_processor/coders/operations/statement.rs b/veilid-core/src/rpc_processor/coders/operations/statement.rs index 1c2ddf59..2108b373 100644 --- a/veilid-core/src/rpc_processor/coders/operations/statement.rs +++ b/veilid-core/src/rpc_processor/coders/operations/statement.rs @@ -18,9 +18,12 @@ impl RPCStatement { pub fn desc(&self) -> &'static str { self.detail.desc() } - pub fn decode(reader: &veilid_capnp::statement::Reader) -> Result { + pub fn decode( + reader: &veilid_capnp::statement::Reader, + crypto: Crypto, + ) -> Result { let d_reader = reader.get_detail(); - let detail = RPCStatementDetail::decode(&d_reader)?; + let detail = RPCStatementDetail::decode(&d_reader, crypto)?; Ok(RPCStatement { detail }) } pub fn encode(&self, builder: &mut veilid_capnp::statement::Builder) -> Result<(), RPCError> { @@ -52,6 +55,7 @@ impl RPCStatementDetail { } pub fn decode( reader: &veilid_capnp::statement::detail::Reader, + crypto: Crypto, ) -> Result { let which_reader = reader.which().map_err(RPCError::protocol)?; let out = match which_reader { @@ -62,7 +66,7 @@ impl RPCStatementDetail { } veilid_capnp::statement::detail::Route(r) => { let op_reader = r.map_err(RPCError::protocol)?; - let out = RPCOperationRoute::decode(&op_reader)?; + let out = RPCOperationRoute::decode(&op_reader, crypto)?; RPCStatementDetail::Route(out) } veilid_capnp::statement::detail::ValueChanged(r) => { @@ -72,7 +76,7 @@ impl RPCStatementDetail { } veilid_capnp::statement::detail::Signal(r) => { let op_reader = r.map_err(RPCError::protocol)?; - let out = RPCOperationSignal::decode(&op_reader)?; + let out = RPCOperationSignal::decode(&op_reader, crypto)?; RPCStatementDetail::Signal(out) } veilid_capnp::statement::detail::ReturnReceipt(r) => { diff --git a/veilid-core/src/rpc_processor/destination.rs b/veilid-core/src/rpc_processor/destination.rs index 9161de40..98dffccc 100644 --- a/veilid-core/src/rpc_processor/destination.rs +++ b/veilid-core/src/rpc_processor/destination.rs @@ -164,7 +164,7 @@ impl RPCProcessor { // Sent directly but with a safety route, respond to private route let ck = target.best_node_id().kind; let Some(pr_key) = rss - .get_private_route_for_safety_spec(ck, safety_spec, &target.node_ids().keys()) + .get_private_route_for_safety_spec(ck, safety_spec, &target.node_ids()) .map_err(RPCError::internal)? else { return Ok(NetworkResult::no_connection_other("no private route for response at this time")); }; @@ -193,7 +193,7 @@ impl RPCProcessor { let mut avoid_nodes = relay.node_ids(); avoid_nodes.add_all(&target.node_ids()); let Some(pr_key) = rss - .get_private_route_for_safety_spec(ck, safety_spec, &avoid_nodes.keys()) + .get_private_route_for_safety_spec(ck, safety_spec, &avoid_nodes) .map_err(RPCError::internal)? else { return Ok(NetworkResult::no_connection_other("no private route for response at this time")); }; diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index 7f625d8a..806a9637 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -87,7 +87,16 @@ struct RPCMessageHeader { detail: RPCMessageHeaderDetail, } -impl RPCMessageHeader {} +impl RPCMessageHeader { + /// The crypto kind used on the RPC + pub fn crypto_kind(&self) -> CryptoKind { + match &self.detail { + RPCMessageHeaderDetail::Direct(d) => d.envelope.get_crypto_kind(), + RPCMessageHeaderDetail::SafetyRouted(s) => todo!(), + RPCMessageHeaderDetail::PrivateRouted(p) => todo!(), + } + } +} #[derive(Debug)] pub struct RPCMessageData { diff --git a/veilid-core/src/rpc_processor/rpc_app_call.rs b/veilid-core/src/rpc_processor/rpc_app_call.rs index b0506be9..d0887981 100644 --- a/veilid-core/src/rpc_processor/rpc_app_call.rs +++ b/veilid-core/src/rpc_processor/rpc_app_call.rs @@ -44,6 +44,9 @@ impl RPCProcessor { &self, msg: RPCMessage, ) -> Result, RPCError> { + // Get the crypto kind used to send this question + let crypto_kind = msg.header.crypto_kind(); + // Get the question let app_call_q = match msg.operation.kind() { RPCOperationKind::Question(q) => match q.detail() { @@ -61,7 +64,7 @@ impl RPCProcessor { let sender = msg .opt_sender_nr .as_ref() - .map(|nr| NodeId::new(nr.node_id())); + .map(|nr| nr.node_ids().get(crypto_kind).unwrap().key); let message = app_call_q.message.clone(); (self.unlocked_inner.update_callback)(VeilidUpdate::AppCall(VeilidAppCall { sender, diff --git a/veilid-core/src/rpc_processor/rpc_app_message.rs b/veilid-core/src/rpc_processor/rpc_app_message.rs index 6793803f..d690e02e 100644 --- a/veilid-core/src/rpc_processor/rpc_app_message.rs +++ b/veilid-core/src/rpc_processor/rpc_app_message.rs @@ -30,8 +30,16 @@ impl RPCProcessor { _ => panic!("not a statement"), }; + // Get the crypto kind used to send this question + let crypto_kind = msg.header.crypto_kind(); + + // Get the sender node id this came from + let sender = msg + .opt_sender_nr + .as_ref() + .map(|nr| nr.node_ids().get(crypto_kind).unwrap().key); + // Pass the message up through the update callback - let sender = msg.opt_sender_nr.map(|nr| NodeId::new(nr.node_id())); let message = app_message.message; (self.unlocked_inner.update_callback)(VeilidUpdate::AppMessage(VeilidAppMessage { sender, diff --git a/veilid-core/src/rpc_processor/rpc_find_node.rs b/veilid-core/src/rpc_processor/rpc_find_node.rs index 566f1e7a..bdc88be0 100644 --- a/veilid-core/src/rpc_processor/rpc_find_node.rs +++ b/veilid-core/src/rpc_processor/rpc_find_node.rs @@ -11,7 +11,7 @@ impl RPCProcessor { pub async fn rpc_call_find_node( self, dest: Destination, - key: PublicKey, + node_id: TypedKey, ) -> Result>>, RPCError> { // Ensure destination never has a private route if matches!( @@ -26,8 +26,7 @@ impl RPCProcessor { )); } - let find_node_q_detail = - RPCQuestionDetail::FindNodeQ(RPCOperationFindNodeQ { node_id: key }); + let find_node_q_detail = RPCQuestionDetail::FindNodeQ(RPCOperationFindNodeQ { node_id }); let find_node_q = RPCQuestion::new( network_result_try!(self.get_destination_respond_to(&dest)?), find_node_q_detail, @@ -90,6 +89,13 @@ 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 { @@ -98,10 +104,20 @@ impl RPCProcessor { }; // find N nodes closest to the target node in our routing table - let filter = Box::new( - move |rti: &RoutingTableInner, entry: Option>| { - rti.filter_has_valid_signed_node_info(RoutingDomain::PublicInternet, true, entry) + 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, + true, + opt_entry, + ) }, ) as RoutingTableEntryFilter; let filters = VecDeque::from([filter]); diff --git a/veilid-core/src/rpc_processor/rpc_status.rs b/veilid-core/src/rpc_processor/rpc_status.rs index ef03f631..2335100a 100644 --- a/veilid-core/src/rpc_processor/rpc_status.rs +++ b/veilid-core/src/rpc_processor/rpc_status.rs @@ -42,7 +42,6 @@ impl RPCProcessor { target, safety_selection: _, } => { - let opt_target_nr = self.routing_table.lookup_node_ref(*target); let routing_domain = match relay.best_routing_domain() { Some(rd) => rd, None => { @@ -51,7 +50,7 @@ impl RPCProcessor { )) } }; - (opt_target_nr, routing_domain) + (Some(target.clone()), routing_domain) } Destination::PrivateRoute { private_route: _, 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 adde09ab..7535779e 100644 --- a/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs +++ b/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs @@ -88,7 +88,10 @@ impl RPCProcessor { // 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_id = detail.envelope.get_sender_id(); + let sender_node_id = TypedKey::new( + detail.envelope.get_crypto_kind(), + detail.envelope.get_sender_id(), + ); let routing_domain = detail.routing_domain; let node_count = { let c = self.config.get(); @@ -102,7 +105,7 @@ impl RPCProcessor { dial_info.clone(), ); let will_validate_dial_info_filter = Box::new( - move |rti: &RoutingTableInner, _k: TypedKey, v: Option>| { + move |rti: &RoutingTableInner, v: Option>| { let entry = v.unwrap(); entry.with(rti, move |_rti, e| { if let Some(status) = &e.node_status(routing_domain) { @@ -129,7 +132,7 @@ impl RPCProcessor { } for peer in peers { // Ensure the peer is not the one asking for the validation - if peer.node_id() == sender_id { + if peer.node_ids().contains(&sender_node_id) { continue; } diff --git a/veilid-core/src/veilid_api/api.rs b/veilid-core/src/veilid_api/api.rs index 0231349e..5719a0c8 100644 --- a/veilid-core/src/veilid_api/api.rs +++ b/veilid-core/src/veilid_api/api.rs @@ -165,11 +165,10 @@ impl VeilidAPI { // Private route allocation /// Allocate a new private route set with default cryptography and network options - /// Returns a list of the public key and published 'blob' pairs. Publishing as many of these - /// pairs as possible to the network is desirable as support for multiple cryptography - /// systems will require choosing a compatible route + /// Returns a set of keys and a publishable 'blob' with the route encrypted with each crypto kind + /// Those nodes importing the blob will have their choice of which crypto kind to use #[instrument(level = "debug", skip(self))] - pub async fn new_private_route(&self) -> Result)>, VeilidAPIError> { + pub async fn new_private_route(&self) -> Result<(TypedKeySet, Vec), VeilidAPIError> { self.new_custom_private_route( &VALID_CRYPTO_KINDS, Stability::default(), @@ -185,7 +184,7 @@ impl VeilidAPI { crypto_kinds: &[CryptoKind], stability: Stability, sequencing: Sequencing, - ) -> Result)>, VeilidAPIError> { + ) -> Result<(TypedKeySet, Vec), VeilidAPIError> { let default_route_hop_count: usize = { let config = self.config()?; let c = config.get(); @@ -232,7 +231,10 @@ impl VeilidAPI { } #[instrument(level = "debug", skip(self))] - pub fn import_remote_private_route(&self, blob: Vec) -> Result { + pub fn import_remote_private_route( + &self, + blob: Vec, + ) -> Result { let rss = self.routing_table()?.route_spec_store(); rss.import_remote_private_route(blob) .map_err(|e| VeilidAPIError::invalid_argument(e, "blob", "private route blob")) diff --git a/veilid-core/src/veilid_api/error.rs b/veilid-core/src/veilid_api/error.rs index 5da01f9e..d7b57e5c 100644 --- a/veilid-core/src/veilid_api/error.rs +++ b/veilid-core/src/veilid_api/error.rs @@ -108,7 +108,7 @@ pub enum VeilidAPIError { #[error("Shutdown")] Shutdown, #[error("Key not found: {key}")] - KeyNotFound { key: TypedKey }, + KeyNotFound { key: PublicKey }, #[error("No connection: {message}")] NoConnection { message: String }, #[error("No peer info: {node_id}")]