1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-05-20 00:18:12 +08:00
Files
kubo/core/commands/ping.go
Icarus9913 9fdb085605 refactor: stop using go-libp2p deprecated peer.ID.Pretty
Signed-off-by: Icarus9913 <icaruswu66@qq.com>
2023-09-18 15:58:15 +02:00

223 lines
4.9 KiB
Go

package commands
import (
"context"
"errors"
"fmt"
"io"
"strings"
"time"
"github.com/ipfs/kubo/core/commands/cmdenv"
cmds "github.com/ipfs/go-ipfs-cmds"
peer "github.com/libp2p/go-libp2p/core/peer"
pstore "github.com/libp2p/go-libp2p/core/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: cmds.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: []cmds.Argument{
cmds.StringArg("peer ID", true, true, "ID of peer to be pinged.").EnableStdin(),
},
Options: []cmds.Option{
cmds.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("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),
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 fmt.Errorf("peer lookup failed: %s", err)
}
n.Peerstore.AddAddrs(p.ID, p.Addrs, pstore.TempAddrTTL)
}
if err := res.Emit(&PingResult{
Text: fmt.Sprintf("PING %s.", pid),
Success: true,
}); err != nil {
return err
}
ctx, cancel := context.WithTimeout(req.Context, kPingTimeout*time.Duration(numPings))
defer cancel()
pings := ping.Ping(ctx, n.PeerHost, pid)
var (
count int
total time.Duration
)
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for i := 0; i < numPings; i++ {
r, ok := <-pings
if !ok {
break
}
if r.Error != nil {
err = res.Emit(&PingResult{
Success: false,
Text: fmt.Sprintf("Ping error: %s", r.Error),
})
} else {
count++
total += r.RTT
err = res.Emit(&PingResult{
Success: true,
Time: r.RTT,
})
}
if err != nil {
return err
}
select {
case <-ticker.C:
case <-ctx.Done():
return ctx.Err()
}
}
if count == 0 {
return fmt.Errorf("ping failed")
}
averagems := total.Seconds() * 1000 / float64(count)
return res.Emit(&PingResult{
Success: true,
Text: fmt.Sprintf("Average latency: %.2fms", averagems),
})
},
Type: PingResult{},
PostRun: cmds.PostRunMap{
cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error {
var (
total time.Duration
count int
)
for {
event, err := res.Next()
switch err {
case nil:
case io.EOF:
return nil
case context.Canceled, context.DeadlineExceeded:
if count == 0 {
return err
}
averagems := total.Seconds() * 1000 / float64(count)
return re.Emit(&PingResult{
Success: true,
Text: fmt.Sprintf("Average latency: %.2fms", averagems),
})
default:
return err
}
pr := event.(*PingResult)
if pr.Success && pr.Text == "" {
total += pr.Time
count++
}
err = re.Emit(event)
if err != nil {
return err
}
}
},
},
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, "/") {
maddr, err := ma.NewMultiaddr(text)
if err != nil {
return nil, "", err
}
transport, id := peer.SplitAddr(maddr)
if id == "" {
return nil, "", peer.ErrInvalidAddr
}
return transport, id, nil
}
// Raw peer ID
p, err := peer.Decode(text)
return nil, p, err
}