github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/core/commands/ping.go (about)

     1  package commands
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"reflect"
     8  	"strings"
     9  	"time"
    10  
    11  	cmds "github.com/ipfs/go-ipfs/commands"
    12  	core "github.com/ipfs/go-ipfs/core"
    13  	peer "github.com/ipfs/go-ipfs/p2p/peer"
    14  	u "github.com/ipfs/go-ipfs/util"
    15  
    16  	ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
    17  	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
    18  )
    19  
    20  const kPingTimeout = 10 * time.Second
    21  
    22  type PingResult struct {
    23  	Success bool
    24  	Time    time.Duration
    25  	Text    string
    26  }
    27  
    28  var PingCmd = &cmds.Command{
    29  	Helptext: cmds.HelpText{
    30  		Tagline: "send echo request packets to IPFS hosts",
    31  		Synopsis: `
    32  Send pings to a peer using the routing system to discover its address
    33  		`,
    34  		ShortDescription: `
    35  ipfs ping is a tool to test sending data to other nodes. It finds nodes
    36  via the routing system, send pings, wait for pongs, and print out round-
    37  trip latency information.
    38  		`,
    39  	},
    40  	Arguments: []cmds.Argument{
    41  		cmds.StringArg("peer ID", true, true, "ID of peer to be pinged").EnableStdin(),
    42  	},
    43  	Options: []cmds.Option{
    44  		cmds.IntOption("count", "n", "number of ping messages to send"),
    45  	},
    46  	Marshalers: cmds.MarshalerMap{
    47  		cmds.Text: func(res cmds.Response) (io.Reader, error) {
    48  			outChan, ok := res.Output().(<-chan interface{})
    49  			if !ok {
    50  				fmt.Println(reflect.TypeOf(res.Output()))
    51  				return nil, u.ErrCast()
    52  			}
    53  
    54  			marshal := func(v interface{}) (io.Reader, error) {
    55  				obj, ok := v.(*PingResult)
    56  				if !ok {
    57  					return nil, u.ErrCast()
    58  				}
    59  
    60  				buf := new(bytes.Buffer)
    61  				if len(obj.Text) > 0 {
    62  					buf = bytes.NewBufferString(obj.Text + "\n")
    63  				} else if obj.Success {
    64  					fmt.Fprintf(buf, "Pong received: time=%.2f ms\n", obj.Time.Seconds()*1000)
    65  				} else {
    66  					fmt.Fprintf(buf, "Pong failed\n")
    67  				}
    68  				return buf, nil
    69  			}
    70  
    71  			return &cmds.ChannelMarshaler{
    72  				Channel:   outChan,
    73  				Marshaler: marshal,
    74  				Res:       res,
    75  			}, nil
    76  		},
    77  	},
    78  	Run: func(req cmds.Request, res cmds.Response) {
    79  		ctx := req.Context()
    80  		n, err := req.InvocContext().GetNode()
    81  		if err != nil {
    82  			res.SetError(err, cmds.ErrNormal)
    83  			return
    84  		}
    85  
    86  		// Must be online!
    87  		if !n.OnlineMode() {
    88  			res.SetError(errNotOnline, cmds.ErrClient)
    89  			return
    90  		}
    91  
    92  		addr, peerID, err := ParsePeerParam(req.Arguments()[0])
    93  		if err != nil {
    94  			res.SetError(err, cmds.ErrNormal)
    95  			return
    96  		}
    97  
    98  		if addr != nil {
    99  			n.Peerstore.AddAddr(peerID, addr, peer.TempAddrTTL) // temporary
   100  		}
   101  
   102  		// Set up number of pings
   103  		numPings := 10
   104  		val, found, err := req.Option("count").Int()
   105  		if err != nil {
   106  			res.SetError(err, cmds.ErrNormal)
   107  			return
   108  		}
   109  		if found {
   110  			numPings = val
   111  		}
   112  
   113  		outChan := pingPeer(ctx, n, peerID, numPings)
   114  		res.SetOutput(outChan)
   115  	},
   116  	Type: PingResult{},
   117  }
   118  
   119  func pingPeer(ctx context.Context, n *core.IpfsNode, pid peer.ID, numPings int) <-chan interface{} {
   120  	outChan := make(chan interface{})
   121  	go func() {
   122  		defer close(outChan)
   123  
   124  		if len(n.Peerstore.Addrs(pid)) == 0 {
   125  			// Make sure we can find the node in question
   126  			outChan <- &PingResult{
   127  				Text: fmt.Sprintf("Looking up peer %s", pid.Pretty()),
   128  			}
   129  
   130  			ctx, cancel := context.WithTimeout(ctx, kPingTimeout)
   131  			defer cancel()
   132  			p, err := n.Routing.FindPeer(ctx, pid)
   133  			if err != nil {
   134  				outChan <- &PingResult{Text: fmt.Sprintf("Peer lookup error: %s", err)}
   135  				return
   136  			}
   137  			n.Peerstore.AddAddrs(p.ID, p.Addrs, peer.TempAddrTTL)
   138  		}
   139  
   140  		outChan <- &PingResult{Text: fmt.Sprintf("PING %s.", pid.Pretty())}
   141  
   142  		ctx, cancel := context.WithTimeout(ctx, kPingTimeout*time.Duration(numPings))
   143  		defer cancel()
   144  		pings, err := n.Ping.Ping(ctx, pid)
   145  		if err != nil {
   146  			log.Debugf("Ping error: %s", err)
   147  			outChan <- &PingResult{Text: fmt.Sprintf("Ping error: %s", err)}
   148  			return
   149  		}
   150  
   151  		var done bool
   152  		var total time.Duration
   153  		for i := 0; i < numPings && !done; i++ {
   154  			select {
   155  			case <-ctx.Done():
   156  				done = true
   157  				break
   158  			case t, ok := <-pings:
   159  				if !ok {
   160  					done = true
   161  					break
   162  				}
   163  
   164  				outChan <- &PingResult{
   165  					Success: true,
   166  					Time:    t,
   167  				}
   168  				total += t
   169  				time.Sleep(time.Second)
   170  			}
   171  		}
   172  		averagems := total.Seconds() * 1000 / float64(numPings)
   173  		outChan <- &PingResult{
   174  			Text: fmt.Sprintf("Average latency: %.2fms", averagems),
   175  		}
   176  	}()
   177  	return outChan
   178  }
   179  
   180  func ParsePeerParam(text string) (ma.Multiaddr, peer.ID, error) {
   181  	// to be replaced with just multiaddr parsing, once ptp is a multiaddr protocol
   182  	idx := strings.LastIndex(text, "/")
   183  	if idx == -1 {
   184  		pid, err := peer.IDB58Decode(text)
   185  		if err != nil {
   186  			return nil, "", err
   187  		}
   188  
   189  		return nil, pid, nil
   190  	}
   191  
   192  	addrS := text[:idx]
   193  	peeridS := text[idx+1:]
   194  
   195  	var maddr ma.Multiaddr
   196  	var pid peer.ID
   197  
   198  	// make sure addrS parses as a multiaddr.
   199  	if len(addrS) > 0 {
   200  		var err error
   201  		maddr, err = ma.NewMultiaddr(addrS)
   202  		if err != nil {
   203  			return nil, "", err
   204  		}
   205  	}
   206  
   207  	// make sure idS parses as a peer.ID
   208  	var err error
   209  	pid, err = peer.IDB58Decode(peeridS)
   210  	if err != nil {
   211  		return nil, "", err
   212  	}
   213  
   214  	return maddr, pid, nil
   215  }