1- use std:: { net:: SocketAddr , sync:: Arc , time:: Duration } ;
1+ use genawaiter:: sync:: Gen ;
2+ use std:: {
3+ collections:: { BTreeSet , HashSet } ,
4+ net:: { Ipv4Addr , SocketAddr , SocketAddrV4 } ,
5+ sync:: Arc ,
6+ time:: Duration ,
7+ } ;
28
39use anyhow:: Context ;
10+ use futures:: { future:: BoxFuture , FutureExt , Stream , StreamExt } ;
11+ use iroh_bytes:: HashAndFormat ;
412use iroh_net:: {
513 magic_endpoint:: { get_alpn, get_remote_node_id} ,
614 MagicEndpoint , NodeId ,
715} ;
816use iroh_pkarr_node_discovery:: PkarrNodeDiscovery ;
17+ use mainline:: common:: { GetPeerResponse , StoreQueryMetdata } ;
918use pkarr:: PkarrClient ;
1019use protocol:: QueryResponse ;
1120use tokio:: io:: AsyncWriteExt ;
@@ -18,36 +27,6 @@ pub mod options;
1827pub mod protocol;
1928pub mod tracker;
2029
21- /// A tracker id for queries - either a node id or an address.
22- #[ derive( Debug , Clone , PartialEq , Eq , Hash , PartialOrd , Ord ) ]
23- pub enum TrackerId {
24- NodeId ( NodeId ) ,
25- Addr ( std:: net:: SocketAddr ) ,
26- }
27-
28- impl std:: fmt:: Display for TrackerId {
29- fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
30- match self {
31- TrackerId :: NodeId ( node_id) => write ! ( f, "{}" , node_id) ,
32- TrackerId :: Addr ( addr) => write ! ( f, "{}" , addr) ,
33- }
34- }
35- }
36-
37- impl std:: str:: FromStr for TrackerId {
38- type Err = anyhow:: Error ;
39-
40- fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
41- if let Ok ( node_id) = s. parse ( ) {
42- return Ok ( TrackerId :: NodeId ( node_id) ) ;
43- }
44- if let Ok ( addr) = s. parse ( ) {
45- return Ok ( TrackerId :: Addr ( addr) ) ;
46- }
47- anyhow:: bail!( "invalid tracker id" )
48- }
49- }
50-
5130/// Accept an incoming connection and extract the client-provided [`NodeId`] and ALPN protocol.
5231pub ( crate ) async fn accept_conn (
5332 mut conn : quinn:: Connecting ,
@@ -78,6 +57,100 @@ pub async fn announce(connection: quinn::Connection, args: Announce) -> anyhow::
7857 Ok ( ( ) )
7958}
8059
60+ fn to_infohash ( haf : HashAndFormat ) -> mainline:: Id {
61+ let mut data = [ 0u8 ; 20 ] ;
62+ data. copy_from_slice ( & haf. hash . as_bytes ( ) [ ..20 ] ) ;
63+ mainline:: Id :: from_bytes ( data) . unwrap ( )
64+ }
65+
66+ fn unique_tracker_addrs (
67+ mut response : mainline:: common:: Response < GetPeerResponse > ,
68+ ) -> impl Stream < Item = SocketAddr > {
69+ Gen :: new ( |co| async move {
70+ let mut found = HashSet :: new ( ) ;
71+ while let Some ( response) = response. next_async ( ) . await {
72+ let tracker = response. peer ;
73+ if !found. insert ( tracker) {
74+ continue ;
75+ }
76+ co. yield_ ( tracker) . await ;
77+ }
78+ } )
79+ }
80+
81+ async fn query_one (
82+ endpoint : impl ConnectionProvider ,
83+ addr : SocketAddr ,
84+ args : Query ,
85+ ) -> anyhow:: Result < Vec < NodeId > > {
86+ let connection = endpoint. connect ( addr) . await ?;
87+ let result = query ( connection, args) . await ?;
88+ Ok ( result. hosts )
89+ }
90+
91+ /// A connection provider that can be used to connect to a tracker.
92+ ///
93+ /// This can either be a [`quinn::Endpoint`] where connections are created on demand,
94+ /// or some sort of connection pool.
95+ pub trait ConnectionProvider : Clone {
96+ fn connect ( & self , addr : SocketAddr ) -> BoxFuture < anyhow:: Result < quinn:: Connection > > ;
97+ }
98+
99+ impl ConnectionProvider for quinn:: Endpoint {
100+ fn connect ( & self , addr : SocketAddr ) -> BoxFuture < anyhow:: Result < quinn:: Connection > > {
101+ async move { Ok ( self . connect ( addr, "localhost" ) ?. await ?) } . boxed ( )
102+ }
103+ }
104+
105+ /// Query the mainline DHT for trackers for the given content, then query each tracker for peers.
106+ pub async fn query_dht (
107+ endpoint : impl ConnectionProvider ,
108+ dht : mainline:: dht:: Dht ,
109+ args : Query ,
110+ query_parallelism : usize ,
111+ ) -> impl Stream < Item = anyhow:: Result < NodeId > > {
112+ let dht = dht. as_async ( ) ;
113+ let info_hash = to_infohash ( args. content ) ;
114+ let response: mainline:: common:: Response < GetPeerResponse > = dht. get_peers ( info_hash) ;
115+ let unique_tracker_addrs = unique_tracker_addrs ( response) ;
116+ unique_tracker_addrs
117+ . map ( move |addr| {
118+ let endpoint = endpoint. clone ( ) ;
119+ async move {
120+ let hosts = match query_one ( endpoint, addr, args) . await {
121+ Ok ( hosts) => hosts. into_iter ( ) . map ( anyhow:: Ok ) . collect ( ) ,
122+ Err ( cause) => vec ! [ Err ( cause) ] ,
123+ } ;
124+ futures:: stream:: iter ( hosts)
125+ }
126+ } )
127+ . buffer_unordered ( query_parallelism)
128+ . flatten ( )
129+ }
130+
131+ /// Announce to the mainline DHT in parallel.
132+ ///
133+ /// Note that this should only be called from a publicly reachable node, where port is the port
134+ /// on which the tracker protocol is reachable.
135+ pub fn announce_dht (
136+ dht : mainline:: dht:: Dht ,
137+ content : BTreeSet < HashAndFormat > ,
138+ port : u16 ,
139+ announce_parallelism : usize ,
140+ ) -> impl Stream < Item = ( HashAndFormat , mainline:: Result < StoreQueryMetdata > ) > {
141+ let dht = dht. as_async ( ) ;
142+ futures:: stream:: iter ( content)
143+ . map ( move |content| {
144+ let dht = dht. clone ( ) ;
145+ async move {
146+ let info_hash = to_infohash ( content) ;
147+ let res = dht. announce_peer ( info_hash, Some ( port) ) . await ;
148+ ( content, res)
149+ }
150+ } )
151+ . buffer_unordered ( announce_parallelism)
152+ }
153+
81154/// Assume an existing connection to a tracker and query it for peers for some content.
82155pub async fn query ( connection : quinn:: Connection , args : Query ) -> anyhow:: Result < QueryResponse > {
83156 tracing:: info!( "connected to {:?}" , connection. remote_address( ) ) ;
@@ -193,3 +266,69 @@ async fn create_endpoint(
193266 . bind ( port)
194267 . await
195268}
269+
270+ /// A tracker id for queries - either a node id or an address.
271+ #[ derive( Debug , Clone , PartialEq , Eq , Hash , PartialOrd , Ord ) ]
272+ pub enum TrackerId {
273+ NodeId ( NodeId ) ,
274+ Addr ( std:: net:: SocketAddr ) ,
275+ }
276+
277+ impl std:: fmt:: Display for TrackerId {
278+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
279+ match self {
280+ TrackerId :: NodeId ( node_id) => write ! ( f, "{}" , node_id) ,
281+ TrackerId :: Addr ( addr) => write ! ( f, "{}" , addr) ,
282+ }
283+ }
284+ }
285+
286+ impl std:: str:: FromStr for TrackerId {
287+ type Err = anyhow:: Error ;
288+
289+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
290+ if let Ok ( node_id) = s. parse ( ) {
291+ return Ok ( TrackerId :: NodeId ( node_id) ) ;
292+ }
293+ if let Ok ( addr) = s. parse ( ) {
294+ return Ok ( TrackerId :: Addr ( addr) ) ;
295+ }
296+ anyhow:: bail!( "invalid tracker id" )
297+ }
298+ }
299+
300+ /// Connect to a tracker using the [protocol::TRACKER_ALPN] protocol, using either
301+ /// a node id or an address.
302+ ///
303+ /// Note that this is less efficient than using an existing endpoint when doing multiple requests.
304+ /// It is provided as a convenience function for short lived utilities.
305+ pub async fn connect ( tracker : & TrackerId , local_port : u16 ) -> anyhow:: Result < quinn:: Connection > {
306+ match tracker {
307+ TrackerId :: Addr ( tracker) => connect_socket ( * tracker, local_port) . await ,
308+ TrackerId :: NodeId ( tracker) => connect_magic ( & tracker, local_port) . await ,
309+ }
310+ }
311+
312+ /// Create a magic endpoint and connect to a tracker using the [protocol::TRACKER_ALPN] protocol.
313+ pub async fn connect_magic ( tracker : & NodeId , local_port : u16 ) -> anyhow:: Result < quinn:: Connection > {
314+ // todo: uncomment once the connection problems are fixed
315+ // for now, a random node id is more reliable.
316+ // let key = load_secret_key(tracker_path(CLIENT_KEY)?).await?;
317+ let key = iroh_net:: key:: SecretKey :: generate ( ) ;
318+ let endpoint = create_endpoint ( key, local_port, false ) . await ?;
319+ tracing:: info!( "trying to connect to tracker at {:?}" , tracker) ;
320+ let connection = endpoint. connect_by_node_id ( tracker, TRACKER_ALPN ) . await ?;
321+ Ok ( connection)
322+ }
323+
324+ /// Create a quinn endpoint and connect to a tracker using the [protocol::TRACKER_ALPN] protocol.
325+ pub async fn connect_socket (
326+ tracker : SocketAddr ,
327+ local_port : u16 ,
328+ ) -> anyhow:: Result < quinn:: Connection > {
329+ let bind_addr = SocketAddr :: V4 ( SocketAddrV4 :: new ( Ipv4Addr :: UNSPECIFIED , local_port) ) ;
330+ let endpoint = create_quinn_client ( bind_addr, vec ! [ TRACKER_ALPN . to_vec( ) ] , false ) ?;
331+ tracing:: info!( "trying to connect to tracker at {:?}" , tracker) ;
332+ let connection = endpoint. connect ( tracker, "localhost" ) ?. await ?;
333+ Ok ( connection)
334+ }
0 commit comments