88 "time"
99
1010 vole "github.com/ipfs-shipyard/vole/lib"
11+ bsmsg "github.com/ipfs/boxo/bitswap/message"
12+ "github.com/ipfs/boxo/bitswap/message/pb"
13+ "github.com/ipfs/boxo/bitswap/network/httpnet"
1114 "github.com/ipfs/boxo/ipns"
1215 "github.com/ipfs/boxo/routing/http/client"
1316 "github.com/ipfs/boxo/routing/http/contentrouter"
@@ -19,6 +22,7 @@ import (
1922 record "github.com/libp2p/go-libp2p-record"
2023 "github.com/libp2p/go-libp2p/core/host"
2124 "github.com/libp2p/go-libp2p/core/peer"
25+ "github.com/libp2p/go-libp2p/core/peerstore"
2226 "github.com/libp2p/go-libp2p/core/routing"
2327 "github.com/libp2p/go-libp2p/p2p/net/connmgr"
2428 "github.com/multiformats/go-multiaddr"
@@ -140,13 +144,14 @@ type providerOutput struct {
140144 Addrs []string
141145 ConnectionMaddrs []string
142146 DataAvailableOverBitswap BitswapCheckOutput
147+ DataAvailableOverHTTP HTTPCheckOutput
143148 Source string
144149}
145150
146151// runCidCheck finds providers of a given CID, using the DHT and IPNI
147152// concurrently. A check of connectivity and Bitswap availability is performed
148153// for each provider found.
149- func (d * daemon ) runCidCheck (ctx context.Context , cidKey cid.Cid , ipniURL string ) (cidCheckOutput , error ) {
154+ func (d * daemon ) runCidCheck (ctx context.Context , cidKey cid.Cid , ipniURL string , httpRetrieval bool ) (cidCheckOutput , error ) {
150155 crClient , err := client .New (ipniURL ,
151156 client .WithStreamResultsRequired (), // // https://specs.ipfs.tech/routing/http-routing-v1/#streaming
152157 client .WithProtocolFilter (defaultProtocolFilter ), // IPIP-484
@@ -236,6 +241,7 @@ func (d *daemon) runCidCheck(ctx context.Context, cidKey cid.Cid, ipniURL string
236241 ID : provider .ID .String (),
237242 Addrs : outputAddrs ,
238243 DataAvailableOverBitswap : BitswapCheckOutput {},
244+ DataAvailableOverHTTP : HTTPCheckOutput {},
239245 Source : src ,
240246 }
241247
@@ -261,6 +267,10 @@ func (d *daemon) runCidCheck(ctx context.Context, cidKey cid.Cid, ipniURL string
261267 p2pAddr , _ := multiaddr .NewMultiaddr ("/p2p/" + provider .ID .String ())
262268 provOutput .DataAvailableOverBitswap = checkBitswapCID (ctx , testHost , cidKey , p2pAddr )
263269
270+ if httpRetrieval {
271+ provOutput .DataAvailableOverHTTP = checkHTTPRetrieval (ctx , testHost , cidKey , p2pAddr )
272+ }
273+
264274 for _ , c := range testHost .Network ().ConnsToPeer (provider .ID ) {
265275 provOutput .ConnectionMaddrs = append (provOutput .ConnectionMaddrs , c .RemoteMultiaddr ().String ())
266276 }
@@ -286,10 +296,11 @@ type peerCheckOutput struct {
286296 ProviderRecordFromPeerInIPNI bool
287297 ConnectionMaddrs []string
288298 DataAvailableOverBitswap BitswapCheckOutput
299+ DataAvailableOverHTTP HTTPCheckOutput
289300}
290301
291302// runPeerCheck checks the connectivity and Bitswap availability of a CID from a given peer (either with just peer ID or specific multiaddr)
292- func (d * daemon ) runPeerCheck (ctx context.Context , ma multiaddr.Multiaddr , ai * peer.AddrInfo , c cid.Cid , ipniURL string ) (* peerCheckOutput , error ) {
303+ func (d * daemon ) runPeerCheck (ctx context.Context , ma multiaddr.Multiaddr , ai * peer.AddrInfo , c cid.Cid , ipniURL string , httpRetrieval bool ) (* peerCheckOutput , error ) {
293304 addrMap , peerAddrDHTErr := peerAddrsInDHT (ctx , d .dht , d .dhtMessenger , ai .ID )
294305
295306 var inDHT , inIPNI bool
@@ -353,6 +364,10 @@ func (d *daemon) runPeerCheck(ctx context.Context, ma multiaddr.Multiaddr, ai *p
353364 // If so is the data available over Bitswap?
354365 out .DataAvailableOverBitswap = checkBitswapCID (ctx , testHost , c , ma )
355366
367+ if httpRetrieval {
368+ out .DataAvailableOverHTTP = checkHTTPRetrieval (ctx , testHost , c , ma )
369+ }
370+
356371 // Get all connection maddrs to the peer (in case we hole punched, there will usually be two: limited relay and direct)
357372 for _ , c := range testHost .Network ().ConnsToPeer (ai .ID ) {
358373 out .ConnectionMaddrs = append (out .ConnectionMaddrs , c .RemoteMultiaddr ().String ())
@@ -389,6 +404,111 @@ func checkBitswapCID(ctx context.Context, host host.Host, c cid.Cid, ma multiadd
389404 return out
390405}
391406
407+ type HTTPCheckOutput struct {
408+ Duration time.Duration
409+ Connected bool
410+ Requested bool
411+ HasCID bool
412+ Error string
413+ }
414+
415+ type httpReceiver struct {
416+ msgCh chan bsmsg.BitSwapMessage
417+ errorCh chan error
418+ }
419+
420+ func (recv * httpReceiver ) ReceiveMessage (ctx context.Context , sender peer.ID , incoming bsmsg.BitSwapMessage ) {
421+ recv .msgCh <- incoming
422+ }
423+
424+ func (recv * httpReceiver ) ReceiveError (err error ) {
425+ recv .errorCh <- err
426+ }
427+
428+ func (recv * httpReceiver ) PeerConnected (p peer.ID ) { // nop
429+ }
430+
431+ func (recv * httpReceiver ) PeerDisconnected (p peer.ID ) { // nop
432+ }
433+
434+ // FIXME: could expose this directly in Boxo.
435+ func supportsHEAD (pstore peerstore.Peerstore , p peer.ID ) bool {
436+ v , err := pstore .Get (p , "http-retrieval-head-support" )
437+ if err != nil {
438+ return false
439+ }
440+
441+ b , ok := v .(bool )
442+ return ok && b
443+ }
444+
445+ func checkHTTPRetrieval (ctx context.Context , host host.Host , c cid.Cid , ma multiaddr.Multiaddr ) HTTPCheckOutput {
446+ infos , err := peer .AddrInfosFromP2pAddrs (ma )
447+ if err != nil {
448+ return HTTPCheckOutput {
449+ Error : err .Error (),
450+ }
451+ }
452+
453+ htnet := httpnet .New (host ,
454+ httpnet .WithUserAgent (userAgent ),
455+ httpnet .WithResponseHeaderTimeout (5 * time .Second ), // default: 10
456+ httpnet .WithHTTPWorkers (1 ),
457+ )
458+ defer htnet .Stop ()
459+
460+ recv := httpReceiver {
461+ msgCh : make (chan bsmsg.BitSwapMessage ),
462+ errorCh : make (chan error ),
463+ }
464+ htnet .Start (& recv )
465+
466+ pid := infos [0 ].ID
467+ err = htnet .Connect (ctx , infos [0 ])
468+ if err != nil {
469+ return HTTPCheckOutput {
470+ Error : err .Error (),
471+ }
472+ }
473+ if ! supportsHEAD (host .Peerstore (), pid ) {
474+ return HTTPCheckOutput {
475+ Error : "HTTP endpoint does not support HEAD requests" ,
476+ }
477+ }
478+
479+ // Now we are in a position of sending a HEAD request.
480+ msg := bsmsg .New (true )
481+ msg .AddEntry (c , 0 , pb .Message_Wantlist_Have , true )
482+ start := time .Now ()
483+ err = htnet .SendMessage (ctx , pid , msg )
484+ if err != nil {
485+ return HTTPCheckOutput {
486+ Connected : true ,
487+ Error : err .Error (),
488+ }
489+ }
490+
491+ resp := HTTPCheckOutput {
492+ Connected : true ,
493+ Requested : true ,
494+ }
495+ waitCtx , cancel := context .WithTimeout (ctx , 5 * time .Second )
496+ defer cancel ()
497+ select {
498+ case <- waitCtx .Done ():
499+ case msg := <- recv .msgCh :
500+ if len (msg .Haves ()) > 0 {
501+ resp .HasCID = true
502+ }
503+
504+ case err = <- recv .errorCh :
505+ resp .Error = err .Error ()
506+ }
507+
508+ resp .Duration = time .Since (start )
509+ return resp
510+ }
511+
392512func peerAddrsInDHT (ctx context.Context , d kademlia , messenger * dhtpb.ProtocolMessenger , p peer.ID ) (map [string ]int , error ) {
393513 closestPeers , err := d .GetClosestPeers (ctx , string (p ))
394514 if err != nil {
0 commit comments