mirror of
https://gitlab.com/veilid/veilid.git
synced 2025-07-29 01:38:40 -04:00
make peer info creation fallible
This commit is contained in:
parent
87708d4b3e
commit
3dba776c91
10 changed files with 178 additions and 90 deletions
|
@ -1,6 +1,9 @@
|
||||||
**UNRELEASED**
|
**UNRELEASED**
|
||||||
|
|
||||||
|
- veilid-core:
|
||||||
- Add private route example
|
- Add private route example
|
||||||
|
- Add `require_inbound_relay` option in VeilidConfig. Default is false, but if enabled, forces OutboundOnly/InboundRelay mode. Can be used as an extra layer of IP address obscurity for some threat models. (@neequ57)
|
||||||
|
- Fix crash when peer info has missing or unsupported node ids
|
||||||
|
|
||||||
**Changed in Veilid 0.4.7**
|
**Changed in Veilid 0.4.7**
|
||||||
|
|
||||||
|
|
|
@ -135,12 +135,19 @@ impl NetworkManager {
|
||||||
bsrec.dial_info_details().to_vec(), // Dial info is as specified in the bootstrap list
|
bsrec.dial_info_details().to_vec(), // Dial info is as specified in the bootstrap list
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
let bspi = match PeerInfo::new(
|
||||||
Some(Arc::new(PeerInfo::new(
|
|
||||||
RoutingDomain::PublicInternet,
|
RoutingDomain::PublicInternet,
|
||||||
bsrec.node_ids().clone(),
|
bsrec.node_ids().clone(),
|
||||||
sni,
|
sni,
|
||||||
)))
|
) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
veilid_log!(self error "Bootstrap has invalid peer info: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Arc::new(bspi))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
@ -144,11 +144,19 @@ impl NetworkManager {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
Some(Arc::new(PeerInfo::new(
|
let bspi = match PeerInfo::new(
|
||||||
RoutingDomain::PublicInternet,
|
RoutingDomain::PublicInternet,
|
||||||
bsrec.node_ids().clone(),
|
bsrec.node_ids().clone(),
|
||||||
sni,
|
sni,
|
||||||
)))
|
) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
veilid_log!(self error "Bootstrap has invalid peer info: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Arc::new(bspi))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
@ -588,9 +588,13 @@ impl BucketEntryInner {
|
||||||
};
|
};
|
||||||
// Peer info includes all node ids, even unvalidated ones
|
// Peer info includes all node ids, even unvalidated ones
|
||||||
let node_ids = self.node_ids();
|
let node_ids = self.node_ids();
|
||||||
let opt_pi = opt_current_sni
|
let opt_pi = match opt_current_sni {
|
||||||
.as_ref()
|
Some(s) => match PeerInfo::new(routing_domain, node_ids, *s.clone()) {
|
||||||
.map(|s| Arc::new(PeerInfo::new(routing_domain, node_ids, *s.clone())));
|
Ok(v) => Some(Arc::new(v)),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
// Cache the peerinfo
|
// Cache the peerinfo
|
||||||
pi_cache.insert(routing_domain, opt_pi.clone());
|
pi_cache.insert(routing_domain, opt_pi.clone());
|
||||||
|
|
|
@ -752,6 +752,9 @@ impl RoutingTableInner {
|
||||||
F: FnOnce(&mut RoutingTableInner, &mut BucketEntryInner),
|
F: FnOnce(&mut RoutingTableInner, &mut BucketEntryInner),
|
||||||
{
|
{
|
||||||
let routing_table = self.routing_table();
|
let routing_table = self.routing_table();
|
||||||
|
if node_ids.is_empty() {
|
||||||
|
bail!("Can't create node with no node id");
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure someone isn't trying register this node itself
|
// Ensure someone isn't trying register this node itself
|
||||||
if routing_table.matches_own_node_id(node_ids) {
|
if routing_table.matches_own_node_id(node_ids) {
|
||||||
|
@ -761,11 +764,14 @@ impl RoutingTableInner {
|
||||||
// Look up all bucket entries and make sure we only have zero or one
|
// Look up all bucket entries and make sure we only have zero or one
|
||||||
// If we have more than one, pick the one with the best cryptokind to add node ids to
|
// If we have more than one, pick the one with the best cryptokind to add node ids to
|
||||||
let mut best_entry: Option<Arc<BucketEntry>> = None;
|
let mut best_entry: Option<Arc<BucketEntry>> = None;
|
||||||
|
let mut supported_node_ids = TypedNodeIdGroup::new();
|
||||||
for node_id in node_ids.iter() {
|
for node_id in node_ids.iter() {
|
||||||
// Ignore node ids we don't support
|
// Ignore node ids we don't support
|
||||||
if !VALID_CRYPTO_KINDS.contains(&node_id.kind) {
|
if !VALID_CRYPTO_KINDS.contains(&node_id.kind) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
supported_node_ids.add(*node_id);
|
||||||
|
|
||||||
// Find the first in crypto sort order
|
// Find the first in crypto sort order
|
||||||
let bucket_index = routing_table.calculate_bucket_index(node_id);
|
let bucket_index = routing_table.calculate_bucket_index(node_id);
|
||||||
let bucket = self.get_bucket(bucket_index);
|
let bucket = self.get_bucket(bucket_index);
|
||||||
|
@ -798,8 +804,13 @@ impl RoutingTableInner {
|
||||||
return Ok(nr);
|
return Ok(nr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fail out if we can't handle this node
|
||||||
|
if supported_node_ids.is_empty() {
|
||||||
|
bail!("Not registering node with no supported node ids");
|
||||||
|
}
|
||||||
|
|
||||||
// If no entry exists yet, add the first entry to a bucket, possibly evicting a bucket member
|
// If no entry exists yet, add the first entry to a bucket, possibly evicting a bucket member
|
||||||
let first_node_id = node_ids[0];
|
let first_node_id = supported_node_ids[0];
|
||||||
let bucket_entry = routing_table.calculate_bucket_index(&first_node_id);
|
let bucket_entry = routing_table.calculate_bucket_index(&first_node_id);
|
||||||
let bucket = self.get_bucket_mut(bucket_entry);
|
let bucket = self.get_bucket_mut(bucket_entry);
|
||||||
let new_entry = bucket.add_new_entry(first_node_id.value);
|
let new_entry = bucket.add_new_entry(first_node_id.value);
|
||||||
|
|
|
@ -419,6 +419,7 @@ impl RoutingDomainDetailCommon {
|
||||||
routing_table.node_ids(),
|
routing_table.node_ids(),
|
||||||
signed_node_info,
|
signed_node_info,
|
||||||
)
|
)
|
||||||
|
.expect("our own peerinfo should never fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_cache(&self) {
|
fn clear_cache(&self) {
|
||||||
|
|
|
@ -1,74 +1,31 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{routing_table::*, RegisteredComponents};
|
use crate::{routing_table::*, RegisteredComponents};
|
||||||
|
|
||||||
pub async fn test_routingtable_buckets_round_trip() {
|
fn make_mock_typed_node_id(kind: CryptoKind, idx: u8) -> TypedNodeId {
|
||||||
let original_registry = mock_registry::init("a").await;
|
TypedNodeId::new(
|
||||||
let copy_registry = mock_registry::init("b").await;
|
kind,
|
||||||
|
NodeId::new([
|
||||||
// Wrap to close lifetime of 'inner' which is borrowed here so terminate() can succeed
|
idx, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
// (it also .write() locks routing table inner)
|
0, 0, 0, 0,
|
||||||
{
|
|
||||||
let original = original_registry.routing_table();
|
|
||||||
let copy = copy_registry.routing_table();
|
|
||||||
|
|
||||||
// Add lots of routes to `original` here to exercise all various types.
|
|
||||||
|
|
||||||
let (serialized_bucket_map, all_entry_bytes) = original.serialized_buckets();
|
|
||||||
|
|
||||||
RoutingTable::populate_routing_table_inner(
|
|
||||||
&mut copy.inner.write(),
|
|
||||||
serialized_bucket_map,
|
|
||||||
all_entry_bytes,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let original_inner = &*original.inner.read();
|
|
||||||
let copy_inner = &*copy.inner.read();
|
|
||||||
|
|
||||||
let routing_table_keys: Vec<_> = original_inner.buckets.keys().clone().collect();
|
|
||||||
let copy_keys: Vec<_> = copy_inner.buckets.keys().clone().collect();
|
|
||||||
|
|
||||||
assert_eq!(routing_table_keys.len(), copy_keys.len());
|
|
||||||
|
|
||||||
for crypto in routing_table_keys {
|
|
||||||
// The same keys are present in the original and copy RoutingTables.
|
|
||||||
let original_buckets = original_inner.buckets.get(crypto).unwrap();
|
|
||||||
let copy_buckets = copy_inner.buckets.get(crypto).unwrap();
|
|
||||||
|
|
||||||
// Recurse into RoutingTable.inner.buckets
|
|
||||||
for (left_buckets, right_buckets) in original_buckets.iter().zip(copy_buckets.iter()) {
|
|
||||||
// Recurse into RoutingTable.inner.buckets.entries
|
|
||||||
for ((left_crypto, left_entries), (right_crypto, right_entries)) in
|
|
||||||
left_buckets.entries().zip(right_buckets.entries())
|
|
||||||
{
|
|
||||||
assert_eq!(left_crypto, right_crypto);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
format!("{:?}", left_entries),
|
|
||||||
format!("{:?}", right_entries)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Even if these are mocks, we should still practice good hygiene.
|
|
||||||
mock_registry::terminate(original_registry).await;
|
|
||||||
mock_registry::terminate(copy_registry).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn test_round_trip_peerinfo() {
|
|
||||||
let mut tks = TypedPublicKeyGroup::new();
|
|
||||||
tks.add(TypedPublicKey::new(
|
|
||||||
CRYPTO_KIND_VLD0,
|
|
||||||
PublicKey::new([
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0,
|
|
||||||
]),
|
]),
|
||||||
));
|
)
|
||||||
let pi: PeerInfo = PeerInfo::new(
|
}
|
||||||
|
|
||||||
|
fn make_mock_typed_node_id_group(vld0: bool, unknown: bool) -> TypedNodeIdGroup {
|
||||||
|
let mut tks = TypedNodeIdGroup::new();
|
||||||
|
if vld0 {
|
||||||
|
tks.add(make_mock_typed_node_id(CRYPTO_KIND_VLD0, 0));
|
||||||
|
}
|
||||||
|
if unknown {
|
||||||
|
tks.add(make_mock_typed_node_id(CryptoKind([1, 2, 3, 4]), 0));
|
||||||
|
}
|
||||||
|
tks
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_mock_peer_info(node_ids: TypedNodeIdGroup) -> EyreResult<PeerInfo> {
|
||||||
|
PeerInfo::new(
|
||||||
RoutingDomain::PublicInternet,
|
RoutingDomain::PublicInternet,
|
||||||
tks.into(),
|
node_ids,
|
||||||
SignedNodeInfo::Direct(SignedDirectNodeInfo::new(
|
SignedNodeInfo::Direct(SignedDirectNodeInfo::new(
|
||||||
NodeInfo::new(
|
NodeInfo::new(
|
||||||
NetworkClass::OutboundOnly,
|
NetworkClass::OutboundOnly,
|
||||||
|
@ -82,7 +39,91 @@ pub fn test_round_trip_peerinfo() {
|
||||||
Timestamp::new(0),
|
Timestamp::new(0),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
)),
|
)),
|
||||||
);
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_mock_data(routing_table: &VeilidComponentGuard<'_, RoutingTable>) {
|
||||||
|
let pi =
|
||||||
|
make_mock_peer_info(make_mock_typed_node_id_group(true, false)).expect("should be valid");
|
||||||
|
routing_table
|
||||||
|
.register_node_with_peer_info(Arc::new(pi), true)
|
||||||
|
.expect("should register");
|
||||||
|
let pi2 =
|
||||||
|
make_mock_peer_info(make_mock_typed_node_id_group(true, true)).expect("should be valid");
|
||||||
|
routing_table
|
||||||
|
.register_node_with_peer_info(Arc::new(pi2), true)
|
||||||
|
.expect("should register");
|
||||||
|
|
||||||
|
let _ = make_mock_peer_info(make_mock_typed_node_id_group(false, false))
|
||||||
|
.expect_err("should fail with no node ids");
|
||||||
|
|
||||||
|
let pi3 =
|
||||||
|
make_mock_peer_info(make_mock_typed_node_id_group(false, true)).expect("should be valid");
|
||||||
|
|
||||||
|
let _ = routing_table
|
||||||
|
.register_node_with_peer_info(Arc::new(pi3), true)
|
||||||
|
.expect_err("should fail with only unsupported node ids");
|
||||||
|
}
|
||||||
|
pub async fn test_routingtable_buckets_round_trip() {
|
||||||
|
let original_registry = mock_registry::init("a").await;
|
||||||
|
let copy_registry = mock_registry::init("b").await;
|
||||||
|
|
||||||
|
// Wrap to close lifetime of 'inner' which is borrowed here so terminate() can succeed
|
||||||
|
// (it also .write() locks routing table inner)
|
||||||
|
{
|
||||||
|
let original = original_registry.routing_table();
|
||||||
|
let copy = copy_registry.routing_table();
|
||||||
|
|
||||||
|
add_mock_data(&original);
|
||||||
|
|
||||||
|
let (serialized_bucket_map, all_entry_bytes) = original.serialized_buckets();
|
||||||
|
|
||||||
|
RoutingTable::populate_routing_table_inner(
|
||||||
|
&mut copy.inner.write(),
|
||||||
|
serialized_bucket_map,
|
||||||
|
all_entry_bytes,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let original_inner = &*original.inner.read();
|
||||||
|
let copy_inner = &*copy.inner.read();
|
||||||
|
|
||||||
|
let original_crypto_kinds: Vec<_> = original_inner.buckets.keys().clone().collect();
|
||||||
|
let copy_crypto_kinds: Vec<_> = copy_inner.buckets.keys().clone().collect();
|
||||||
|
|
||||||
|
assert_eq!(original_crypto_kinds.len(), copy_crypto_kinds.len());
|
||||||
|
|
||||||
|
for crypto in original_crypto_kinds {
|
||||||
|
// The same keys are present in the original and copy RoutingTables.
|
||||||
|
let original_buckets = original_inner.buckets.get(crypto).unwrap();
|
||||||
|
let copy_buckets = copy_inner.buckets.get(crypto).unwrap();
|
||||||
|
|
||||||
|
// Recurse into RoutingTable.inner.buckets
|
||||||
|
for (left_bucket, right_bucket) in original_buckets.iter().zip(copy_buckets.iter()) {
|
||||||
|
// Recurse into RoutingTable.inner.buckets.entries
|
||||||
|
for ((left_node_id, left_entry), (right_node_id, right_entry)) in
|
||||||
|
left_bucket.entries().zip(right_bucket.entries())
|
||||||
|
{
|
||||||
|
assert_eq!(left_node_id, right_node_id);
|
||||||
|
|
||||||
|
let s = left_entry.with(original_inner, |_rti, e| serialize_json(e));
|
||||||
|
let s2 = right_entry.with(copy_inner, |_rti, e| serialize_json(e));
|
||||||
|
|
||||||
|
assert_eq!(s, s2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even if these are mocks, we should still practice good hygiene.
|
||||||
|
mock_registry::terminate(original_registry).await;
|
||||||
|
mock_registry::terminate(copy_registry).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_round_trip_peerinfo() {
|
||||||
|
let pi =
|
||||||
|
make_mock_peer_info(make_mock_typed_node_id_group(true, true)).expect("should be valid");
|
||||||
|
|
||||||
let s = serialize_json(&pi);
|
let s = serialize_json(&pi);
|
||||||
let pi2 = deserialize_json(&s).expect("Should deserialize");
|
let pi2 = deserialize_json(&s).expect("Should deserialize");
|
||||||
let s2 = serialize_json(&pi2);
|
let s2 = serialize_json(&pi2);
|
||||||
|
|
|
@ -34,13 +34,26 @@ impl PeerInfo {
|
||||||
routing_domain: RoutingDomain,
|
routing_domain: RoutingDomain,
|
||||||
node_ids: TypedNodeIdGroup,
|
node_ids: TypedNodeIdGroup,
|
||||||
signed_node_info: SignedNodeInfo,
|
signed_node_info: SignedNodeInfo,
|
||||||
) -> Self {
|
) -> EyreResult<Self> {
|
||||||
assert!(!node_ids.is_empty() && node_ids.len() <= MAX_CRYPTO_KINDS);
|
if node_ids.is_empty() {
|
||||||
Self {
|
bail!(
|
||||||
|
"no node ids for peer info ({:?})\n{:#?}",
|
||||||
|
routing_domain,
|
||||||
|
signed_node_info
|
||||||
|
);
|
||||||
|
} else if node_ids.len() > MAX_CRYPTO_KINDS {
|
||||||
|
bail!(
|
||||||
|
"too many node ids for peer info ({:?}): {:?}\n{:#?}",
|
||||||
|
routing_domain,
|
||||||
|
node_ids,
|
||||||
|
signed_node_info
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
routing_domain,
|
routing_domain,
|
||||||
node_ids,
|
node_ids,
|
||||||
signed_node_info,
|
signed_node_info,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self, crypto: &Crypto) -> VeilidAPIResult<()> {
|
pub fn validate(&self, crypto: &Crypto) -> VeilidAPIResult<()> {
|
||||||
|
|
|
@ -69,11 +69,14 @@ impl SignedNodeInfo {
|
||||||
pub fn relay_peer_info(&self, routing_domain: RoutingDomain) -> Option<Arc<PeerInfo>> {
|
pub fn relay_peer_info(&self, routing_domain: RoutingDomain) -> Option<Arc<PeerInfo>> {
|
||||||
match self {
|
match self {
|
||||||
SignedNodeInfo::Direct(_) => None,
|
SignedNodeInfo::Direct(_) => None,
|
||||||
SignedNodeInfo::Relayed(r) => Some(Arc::new(PeerInfo::new(
|
SignedNodeInfo::Relayed(r) => Some(Arc::new(
|
||||||
|
PeerInfo::new(
|
||||||
routing_domain,
|
routing_domain,
|
||||||
r.relay_ids().clone(),
|
r.relay_ids().clone(),
|
||||||
SignedNodeInfo::Direct(r.relay_info().clone()),
|
SignedNodeInfo::Direct(r.relay_info().clone()),
|
||||||
))),
|
)
|
||||||
|
.unwrap(), // validate() above, should have ensured this succeeds
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn has_any_dial_info(&self) -> bool {
|
pub fn has_any_dial_info(&self) -> bool {
|
||||||
|
|
|
@ -47,9 +47,6 @@ pub fn decode_peer_info(
|
||||||
if node_ids.is_empty() {
|
if node_ids.is_empty() {
|
||||||
return Err(RPCError::protocol("no verified node ids"));
|
return Err(RPCError::protocol("no verified node ids"));
|
||||||
}
|
}
|
||||||
Ok(PeerInfo::new(
|
PeerInfo::new(decode_context.routing_domain, node_ids, signed_node_info)
|
||||||
decode_context.routing_domain,
|
.map_err(RPCError::map_invalid_format("invalid peer info"))
|
||||||
node_ids,
|
|
||||||
signed_node_info,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue