Skip to content

Commit 5589a62

Browse files
committed
Add HTTP-retrieval check to ipfs-check
1 parent ef9fccc commit 5589a62

File tree

3 files changed

+130
-10
lines changed

3 files changed

+130
-10
lines changed

daemon.go

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import (
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+
392512
func 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 {

dht.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99
"github.com/libp2p/go-libp2p/core/network"
1010
"github.com/libp2p/go-libp2p/core/peer"
1111
"github.com/libp2p/go-libp2p/core/protocol"
12-
13-
//lint:ignore SA1019 TODO migrate away from gogo pb
1412
"github.com/libp2p/go-msgio/pbio"
1513
)
1614

main.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,12 @@ func startServer(ctx context.Context, d *daemon, tcpListener, metricsUsername, m
9898
checkHandler := func(w http.ResponseWriter, r *http.Request) {
9999
w.Header().Add("Access-Control-Allow-Origin", "*")
100100

101-
maStr := r.URL.Query().Get("multiaddr")
102-
cidStr := r.URL.Query().Get("cid")
103-
timeoutStr := r.URL.Query().Get("timeoutSeconds")
104-
ipniURL := r.URL.Query().Get("ipniIndexer")
101+
q := r.URL.Query()
102+
maStr := q.Get("multiaddr")
103+
cidStr := q.Get("cid")
104+
timeoutStr := q.Get("timeoutSeconds")
105+
ipniURL := q.Get("ipniIndexer")
106+
httpRetrieval := q.Get("httpRetrieval") != ""
105107

106108
if cidStr == "" {
107109
http.Error(w, "missing 'cid' query parameter", http.StatusBadRequest)
@@ -139,14 +141,14 @@ func startServer(ctx context.Context, d *daemon, tcpListener, metricsUsername, m
139141

140142
var data interface{}
141143
if maStr == "" {
142-
data, err = d.runCidCheck(withTimeout, cidKey, ipniURL)
144+
data, err = d.runCidCheck(withTimeout, cidKey, ipniURL, httpRetrieval)
143145
} else {
144146
ma, ai, err400 := parseMultiaddr(maStr)
145147
if err400 != nil {
146148
http.Error(w, err400.Error(), http.StatusBadRequest)
147149
return
148150
}
149-
data, err = d.runPeerCheck(withTimeout, ma, ai, cidKey, ipniURL)
151+
data, err = d.runPeerCheck(withTimeout, ma, ai, cidKey, ipniURL, httpRetrieval)
150152
}
151153
if err != nil {
152154
http.Error(w, err.Error(), http.StatusInternalServerError)

0 commit comments

Comments
 (0)