2022-11-26 21:17:30 +00:00
use super ::* ;
2022-11-25 01:17:54 +00:00
use futures_util ::stream ::{ FuturesUnordered , StreamExt } ;
use stop_token ::future ::FutureExt as StopFutureExt ;
2023-02-13 21:12:46 +00:00
pub const BOOTSTRAP_TXT_VERSION_0 : u8 = 0 ;
2022-11-25 01:17:54 +00:00
#[ derive(Clone, Debug) ]
pub struct BootstrapRecord {
2023-02-13 21:12:46 +00:00
node_ids : TypedKeySet ,
envelope_support : Vec < u8 > ,
2022-11-25 01:17:54 +00:00
dial_info_details : Vec < DialInfoDetail > ,
}
2023-02-13 21:12:46 +00:00
impl BootstrapRecord {
pub fn merge ( & mut self , other : & BootstrapRecord ) {
self . node_ids . add_all ( & other . node_ids ) ;
for x in other . envelope_support {
if ! self . envelope_support . contains ( & x ) {
self . envelope_support . push ( x ) ;
self . envelope_support . sort ( ) ;
}
}
for did in & other . dial_info_details {
if ! self . dial_info_details . contains ( did ) {
self . dial_info_details . push ( did . clone ( ) ) ;
}
}
}
}
2022-11-25 01:17:54 +00:00
impl RoutingTable {
2023-02-13 21:12:46 +00:00
/// Process bootstrap version 0
async fn process_bootstrap_records_v0 (
& self ,
records : Vec < String > ,
) -> EyreResult < Option < BootstrapRecord > > {
// Bootstrap TXT Record Format Version 0:
// txt_version|envelope_support|node_ids|hostname|dialinfoshort*
//
// Split bootstrap node record by '|' and then lists by ','. Example:
// 0|0|VLD0:7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ|bootstrap-1.dev.veilid.net|T5150,U5150,W5150/ws
if records . len ( ) ! = 5 {
bail! ( " invalid number of fields in bootstrap v0 txt record " ) ;
}
// Envelope support
let mut envelope_support = Vec ::new ( ) ;
for ess in records [ 1 ] . split ( " , " ) {
let ess = ess . trim ( ) ;
let es = match records [ 1 ] . parse ::< u8 > ( ) {
Ok ( v ) = > v ,
Err ( e ) = > {
bail! (
" invalid envelope version specified in bootstrap node txt record: {} " ,
e
) ;
}
} ;
envelope_support . push ( es ) ;
}
envelope_support . dedup ( ) ;
envelope_support . sort ( ) ;
// Node Id
let node_ids = TypedKeySet ::new ( ) ;
for node_id_str in records [ 2 ] . split ( " , " ) {
let node_id_str = node_id_str . trim ( ) ;
let node_id = match TypedKey ::from_str ( & node_id_str ) {
Ok ( v ) = > v ,
Err ( e ) = > {
bail! (
" Invalid node id in bootstrap node record {}: {} " ,
node_id_str ,
e
) ;
}
} ;
}
// If this is our own node id, then we skip it for bootstrap, in case we are a bootstrap node
if self . unlocked_inner . matches_own_node_id ( & node_ids ) {
return Ok ( None ) ;
}
// Hostname
let hostname_str = records [ 3 ] . trim ( ) ;
// Resolve each record and store in node dial infos list
let mut dial_info_details = Vec ::new ( ) ;
for rec in records [ 4 ] . split ( " , " ) {
let rec = rec . trim ( ) ;
let dial_infos = match DialInfo ::try_vec_from_short ( rec , hostname_str ) {
Ok ( dis ) = > dis ,
Err ( e ) = > {
warn! ( " Couldn't resolve bootstrap node dial info {}: {} " , rec , e ) ;
continue ;
}
} ;
for di in dial_infos {
dial_info_details . push ( DialInfoDetail {
dial_info : di ,
class : DialInfoClass ::Direct ,
} ) ;
}
}
Ok ( Some ( BootstrapRecord {
node_ids ,
envelope_support ,
dial_info_details ,
} ) )
}
2022-11-25 01:17:54 +00:00
// Bootstrap lookup process
#[ instrument(level = " trace " , skip(self), ret, err) ]
pub ( crate ) async fn resolve_bootstrap (
& self ,
bootstrap : Vec < String > ,
2023-02-13 21:12:46 +00:00
) -> EyreResult < Vec < BootstrapRecord > > {
2022-11-25 01:17:54 +00:00
// Resolve from bootstrap root to bootstrap hostnames
let mut bsnames = Vec ::< String > ::new ( ) ;
for bh in bootstrap {
// Get TXT record for bootstrap (bootstrap.veilid.net, or similar)
let records = intf ::txt_lookup ( & bh ) . await ? ;
for record in records {
// Split the bootstrap name record by commas
for rec in record . split ( ',' ) {
let rec = rec . trim ( ) ;
// If the name specified is fully qualified, go with it
let bsname = if rec . ends_with ( '.' ) {
rec . to_string ( )
}
// If the name is not fully qualified, prepend it to the bootstrap name
else {
format! ( " {} . {} " , rec , bh )
} ;
// Add to the list of bootstrap name to look up
bsnames . push ( bsname ) ;
}
}
}
// Get bootstrap nodes from hostnames concurrently
let mut unord = FuturesUnordered ::new ( ) ;
for bsname in bsnames {
unord . push (
async move {
// look up boostrap node txt records
let bsnirecords = match intf ::txt_lookup ( & bsname ) . await {
Err ( e ) = > {
warn! ( " bootstrap node txt lookup failed for {}: {} " , bsname , e ) ;
return None ;
}
Ok ( v ) = > v ,
} ;
// for each record resolve into key/bootstraprecord pairs
2023-02-13 21:12:46 +00:00
let mut bootstrap_records : Vec < BootstrapRecord > = Vec ::new ( ) ;
2022-11-25 01:17:54 +00:00
for bsnirecord in bsnirecords {
2023-02-13 21:12:46 +00:00
// All formats split on '|' character
2022-11-25 01:17:54 +00:00
let records : Vec < String > = bsnirecord
. trim ( )
2023-02-13 21:12:46 +00:00
. split ( '|' )
2022-11-25 01:17:54 +00:00
. map ( | x | x . trim ( ) . to_owned ( ) )
. collect ( ) ;
// Bootstrap TXT record version
let txt_version : u8 = match records [ 0 ] . parse ::< u8 > ( ) {
Ok ( v ) = > v ,
Err ( e ) = > {
warn! (
" invalid txt_version specified in bootstrap node txt record: {} " ,
e
) ;
continue ;
}
} ;
2023-02-13 21:12:46 +00:00
let bootstrap_record = match txt_version {
BOOTSTRAP_TXT_VERSION_0 = > {
match self . process_bootstrap_records_v0 ( records ) . await {
Err ( e ) = > {
warn! (
" couldn't process v0 bootstrap records from {}: {} " ,
bsname , e
) ;
continue ;
}
Ok ( Some ( v ) ) = > v ,
Ok ( None ) = > {
// skipping
continue ;
}
}
2022-11-25 01:17:54 +00:00
}
2023-02-13 21:12:46 +00:00
_ = > {
warn! ( " unsupported bootstrap txt record version " ) ;
2022-11-25 01:17:54 +00:00
continue ;
}
} ;
2023-02-13 21:12:46 +00:00
bootstrap_records . push ( bootstrap_record ) ;
2022-11-25 01:17:54 +00:00
}
Some ( bootstrap_records )
}
. instrument ( Span ::current ( ) ) ,
) ;
}
2023-02-13 21:12:46 +00:00
let mut merged_bootstrap_records : Vec < BootstrapRecord > = Vec ::new ( ) ;
2022-11-25 01:17:54 +00:00
while let Some ( bootstrap_records ) = unord . next ( ) . await {
2023-02-13 21:12:46 +00:00
let Some ( bootstrap_records ) = bootstrap_records else {
continue ;
} ;
for mut bsrec in bootstrap_records {
let mut mbi = 0 ;
while mbi < merged_bootstrap_records . len ( ) {
let mbr = & mut merged_bootstrap_records [ mbi ] ;
if mbr . node_ids . contains_any ( & bsrec . node_ids ) {
// Merge record, pop this one out
let mbr = merged_bootstrap_records . remove ( mbi ) ;
bsrec . merge ( & mbr ) ;
} else {
// No overlap, go to next record
mbi + = 1 ;
}
2022-11-25 01:17:54 +00:00
}
2023-02-13 21:12:46 +00:00
// Append merged record
merged_bootstrap_records . push ( bsrec ) ;
2022-11-25 01:17:54 +00:00
}
}
2023-02-13 21:12:46 +00:00
// ensure dial infos are sorted
for mbr in & mut merged_bootstrap_records {
mbr . dial_info_details . sort ( ) ;
}
Ok ( merged_bootstrap_records )
2022-11-25 01:17:54 +00:00
}
// 'direct' bootstrap task routine for systems incapable of resolving TXT records, such as browser WASM
#[ instrument(level = " trace " , skip(self), err) ]
pub ( crate ) async fn direct_bootstrap_task_routine (
self ,
stop_token : StopToken ,
bootstrap_dialinfos : Vec < DialInfo > ,
) -> EyreResult < ( ) > {
let mut unord = FuturesUnordered ::new ( ) ;
let network_manager = self . network_manager ( ) ;
for bootstrap_di in bootstrap_dialinfos {
log_rtab! ( debug " direct bootstrap with: {} " , bootstrap_di ) ;
let peer_info = network_manager . boot_request ( bootstrap_di ) . await ? ;
log_rtab! ( debug " direct bootstrap peerinfo: {:?} " , peer_info ) ;
// Got peer info, let's add it to the routing table
for pi in peer_info {
// Register the node
2023-02-13 21:12:46 +00:00
if let Some ( nr ) =
self . register_node_with_peer_info ( RoutingDomain ::PublicInternet , pi , false )
{
2022-11-25 01:17:54 +00:00
// 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 ( ) ) ,
) ;
}
}
}
// Wait for all bootstrap operations to complete before we complete the singlefuture
while let Ok ( Some ( _ ) ) = unord . next ( ) . timeout_at ( stop_token . clone ( ) ) . await { }
Ok ( ( ) )
}
#[ instrument(level = " trace " , skip(self), err) ]
pub ( crate ) async fn bootstrap_task_routine ( self , stop_token : StopToken ) -> EyreResult < ( ) > {
2023-02-13 21:12:46 +00:00
let bootstrap = self
. unlocked_inner
. with_config ( | c | c . network . routing_table . bootstrap . clone ( ) ) ;
// Don't bother if bootstraps aren't configured
if bootstrap . is_empty ( ) {
return Ok ( ( ) ) ;
}
2022-11-25 01:17:54 +00:00
log_rtab! ( debug " --- bootstrap_task " ) ;
// See if we are specifying a direct dialinfo for bootstrap, if so use the direct mechanism
2023-02-13 21:12:46 +00:00
let mut bootstrap_dialinfos = Vec ::< DialInfo > ::new ( ) ;
for b in & bootstrap {
if let Ok ( bootstrap_di_vec ) = DialInfo ::try_vec_from_url ( & b ) {
for bootstrap_di in bootstrap_di_vec {
bootstrap_dialinfos . push ( bootstrap_di ) ;
2022-11-25 01:17:54 +00:00
}
}
2023-02-13 21:12:46 +00:00
}
if bootstrap_dialinfos . len ( ) > 0 {
return self
. direct_bootstrap_task_routine ( stop_token , bootstrap_dialinfos )
. await ;
2022-11-25 01:17:54 +00:00
}
2023-02-13 21:12:46 +00:00
// If not direct, resolve bootstrap servers and recurse their TXT entries
let bsrecs = self . resolve_bootstrap ( bootstrap ) . await ? ;
2022-11-25 01:17:54 +00:00
// Run all bootstrap operations concurrently
let mut unord = FuturesUnordered ::new ( ) ;
2023-02-13 21:12:46 +00:00
for bsrec in bsrecs {
log_rtab! (
" --- bootstrapping {} with {:?} " ,
& bsrec . node_ids ,
& bsrec . dial_info_details
) ;
// Get crypto support from list of node ids
let crypto_support = bsrec . node_ids . kinds ( ) ;
// Make unsigned SignedNodeInfo
let sni = SignedNodeInfo ::Direct ( SignedDirectNodeInfo ::with_no_signature ( NodeInfo {
network_class : NetworkClass ::InboundCapable , // Bootstraps are always inbound capable
outbound_protocols : ProtocolTypeSet ::only ( ProtocolType ::UDP ) , // Bootstraps do not participate in relaying and will not make outbound requests, but will have UDP enabled
address_types : AddressTypeSet ::all ( ) , // Bootstraps are always IPV4 and IPV6 capable
envelope_support : bsrec . envelope_support , // Envelope support is as specified in the bootstrap list
crypto_support , // Crypto support is derived from list of node ids
dial_info_detail_list : bsrec . dial_info_details , // Dial info is as specified in the bootstrap list
} ) ) ;
let pi = PeerInfo ::new ( bsrec . node_ids , sni ) ;
if let Some ( nr ) =
self . register_node_with_peer_info ( RoutingDomain ::PublicInternet , pi , true )
{
2022-11-25 01:17:54 +00:00
// 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 ;
// 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
}
}
. instrument ( Span ::current ( ) ) ,
) ;
}
}
// Wait for all bootstrap operations to complete before we complete the singlefuture
while let Ok ( Some ( _ ) ) = unord . next ( ) . timeout_at ( stop_token . clone ( ) ) . await { }
Ok ( ( ) )
}
}