mirror of
https://github.com/ipfs/kubo.git
synced 2025-08-06 19:44:01 +08:00
174 lines
4.1 KiB
Go
174 lines
4.1 KiB
Go
package commands
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ipfs/go-ipfs/core/commands/cmdenv"
|
|
|
|
iaddr "github.com/ipfs/go-ipfs-addr"
|
|
cmdkit "github.com/ipfs/go-ipfs-cmdkit"
|
|
cmds "github.com/ipfs/go-ipfs-cmds"
|
|
"github.com/libp2p/go-libp2p-peer"
|
|
pstore "github.com/libp2p/go-libp2p-peerstore"
|
|
ping "github.com/libp2p/go-libp2p/p2p/protocol/ping"
|
|
ma "github.com/multiformats/go-multiaddr"
|
|
)
|
|
|
|
const kPingTimeout = 10 * time.Second
|
|
|
|
type PingResult struct {
|
|
Success bool
|
|
Time time.Duration
|
|
Text string
|
|
}
|
|
|
|
const (
|
|
pingCountOptionName = "count"
|
|
)
|
|
|
|
// ErrPingSelf is returned when the user attempts to ping themself.
|
|
var ErrPingSelf = errors.New("error: can't ping self")
|
|
|
|
var PingCmd = &cmds.Command{
|
|
Helptext: cmdkit.HelpText{
|
|
Tagline: "Send echo request packets to IPFS hosts.",
|
|
ShortDescription: `
|
|
'ipfs ping' is a tool to test sending data to other nodes. It finds nodes
|
|
via the routing system, sends pings, waits for pongs, and prints out round-
|
|
trip latency information.
|
|
`,
|
|
},
|
|
Arguments: []cmdkit.Argument{
|
|
cmdkit.StringArg("peer ID", true, true, "ID of peer to be pinged.").EnableStdin(),
|
|
},
|
|
Options: []cmdkit.Option{
|
|
cmdkit.IntOption(pingCountOptionName, "n", "Number of ping messages to send.").WithDefault(10),
|
|
},
|
|
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
|
|
n, err := cmdenv.GetNode(env)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Must be online!
|
|
if !n.IsOnline {
|
|
return ErrNotOnline
|
|
}
|
|
|
|
addr, pid, err := ParsePeerParam(req.Arguments[0])
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse peer address '%s': %s", req.Arguments[0], err)
|
|
}
|
|
|
|
if pid == n.Identity {
|
|
return ErrPingSelf
|
|
}
|
|
|
|
if addr != nil {
|
|
n.Peerstore.AddAddr(pid, addr, pstore.TempAddrTTL) // temporary
|
|
}
|
|
|
|
numPings, _ := req.Options[pingCountOptionName].(int)
|
|
if numPings <= 0 {
|
|
return fmt.Errorf("error: ping count must be greater than 0, was %d", numPings)
|
|
}
|
|
|
|
if len(n.Peerstore.Addrs(pid)) == 0 {
|
|
// Make sure we can find the node in question
|
|
if err := res.Emit(&PingResult{
|
|
Text: fmt.Sprintf("Looking up peer %s", pid.Pretty()),
|
|
Success: true,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(req.Context, kPingTimeout)
|
|
p, err := n.Routing.FindPeer(ctx, pid)
|
|
cancel()
|
|
if err != nil {
|
|
return res.Emit(&PingResult{Text: fmt.Sprintf("Peer lookup error: %s", err)})
|
|
}
|
|
n.Peerstore.AddAddrs(p.ID, p.Addrs, pstore.TempAddrTTL)
|
|
}
|
|
|
|
if err := res.Emit(&PingResult{
|
|
Text: fmt.Sprintf("PING %s.", pid.Pretty()),
|
|
Success: true,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(req.Context, kPingTimeout*time.Duration(numPings))
|
|
defer cancel()
|
|
pings, err := ping.Ping(ctx, n.PeerHost, pid)
|
|
if err != nil {
|
|
return res.Emit(&PingResult{
|
|
Success: false,
|
|
Text: fmt.Sprintf("Ping error: %s", err),
|
|
})
|
|
}
|
|
|
|
var total time.Duration
|
|
ticker := time.NewTicker(time.Second)
|
|
defer ticker.Stop()
|
|
for i := 0; i < numPings; i++ {
|
|
t, ok := <-pings
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
if err := res.Emit(&PingResult{
|
|
Success: true,
|
|
Time: t,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
total += t
|
|
|
|
select {
|
|
case <-ticker.C:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
}
|
|
}
|
|
averagems := total.Seconds() * 1000 / float64(numPings)
|
|
return res.Emit(&PingResult{
|
|
Success: true,
|
|
Text: fmt.Sprintf("Average latency: %.2fms", averagems),
|
|
})
|
|
},
|
|
Type: PingResult{},
|
|
Encoders: cmds.EncoderMap{
|
|
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *PingResult) error {
|
|
if len(out.Text) > 0 {
|
|
fmt.Fprintln(w, out.Text)
|
|
} else if out.Success {
|
|
fmt.Fprintf(w, "Pong received: time=%.2f ms\n", out.Time.Seconds()*1000)
|
|
} else {
|
|
fmt.Fprintf(w, "Pong failed\n")
|
|
}
|
|
return nil
|
|
}),
|
|
},
|
|
}
|
|
|
|
func ParsePeerParam(text string) (ma.Multiaddr, peer.ID, error) {
|
|
// Multiaddr
|
|
if strings.HasPrefix(text, "/") {
|
|
a, err := iaddr.ParseString(text)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
return a.Transport(), a.ID(), nil
|
|
}
|
|
// Raw peer ID
|
|
p, err := peer.IDB58Decode(text)
|
|
return nil, p, err
|
|
}
|