Permalink
Cannot retrieve contributors at this time
222 lines (198 sloc)
4.93 KB
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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.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 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.Pretty()), | |
| 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 | |
| } |