package cli import ( "context" "fmt" "sync" "time" "github.com/libp2p/go-libp2p-core/peer" ma "github.com/multiformats/go-multiaddr" madns "github.com/multiformats/go-multiaddr-dns" "gopkg.in/urfave/cli.v2" ) var netCmd = &cli.Command{ Name: "net", Usage: "Manage P2P Network", Subcommands: []*cli.Command{ netPeers, netConnect, netListen, netId, }, } var netPeers = &cli.Command{ Name: "peers", Usage: "Print peers", Action: func(cctx *cli.Context) error { api, err := getAPI(cctx) if err != nil { return err } ctx := reqContext(cctx) peers, err := api.NetPeers(ctx) if err != nil { return err } for _, peer := range peers { fmt.Println(peer) } return nil }, } var netListen = &cli.Command{ Name: "listen", Usage: "List listen addresses", Action: func(cctx *cli.Context) error { api, err := getAPI(cctx) if err != nil { return err } ctx := reqContext(cctx) addrs, err := api.NetAddrsListen(ctx) if err != nil { return err } for _, peer := range addrs.Addrs { fmt.Printf("%s/p2p/%s\n", peer, addrs.ID) } return nil }, } var netConnect = &cli.Command{ Name: "connect", Usage: "Connect to a peer", Action: func(cctx *cli.Context) error { api, err := getAPI(cctx) if err != nil { return err } ctx := reqContext(cctx) pis, err := parseAddresses(ctx, cctx.Args().Slice()) if err != nil { return err } for _, pi := range pis { fmt.Printf("connect %s: ", pi.ID.Pretty()) err := api.NetConnect(ctx, pi) if err != nil { fmt.Println("failure") return err } fmt.Println("success") } return nil }, } var netId = &cli.Command{ Name: "id", Usage: "Get node identity", Action: func(cctx *cli.Context) error { api, err := getAPI(cctx) if err != nil { return err } ctx := reqContext(cctx) pid, err := api.ID(ctx) if err != nil { return err } fmt.Println(pid) return nil }, } // parseAddresses is a function that takes in a slice of string peer addresses // (multiaddr + peerid) and returns a slice of properly constructed peers func parseAddresses(ctx context.Context, addrs []string) ([]peer.AddrInfo, error) { // resolve addresses maddrs, err := resolveAddresses(ctx, addrs) if err != nil { return nil, err } return peer.AddrInfosFromP2pAddrs(maddrs...) } const ( dnsResolveTimeout = 10 * time.Second ) // resolveAddresses resolves addresses parallelly func resolveAddresses(ctx context.Context, addrs []string) ([]ma.Multiaddr, error) { ctx, cancel := context.WithTimeout(ctx, dnsResolveTimeout) defer cancel() var maddrs []ma.Multiaddr var wg sync.WaitGroup resolveErrC := make(chan error, len(addrs)) maddrC := make(chan ma.Multiaddr) for _, addr := range addrs { maddr, err := ma.NewMultiaddr(addr) if err != nil { return nil, err } // check whether address ends in `ipfs/Qm...` if _, last := ma.SplitLast(maddr); last.Protocol().Code == ma.P_IPFS { maddrs = append(maddrs, maddr) continue } wg.Add(1) go func(maddr ma.Multiaddr) { defer wg.Done() raddrs, err := madns.Resolve(ctx, maddr) if err != nil { resolveErrC <- err return } // filter out addresses that still doesn't end in `ipfs/Qm...` found := 0 for _, raddr := range raddrs { if _, last := ma.SplitLast(raddr); last != nil && last.Protocol().Code == ma.P_IPFS { maddrC <- raddr found++ } } if found == 0 { resolveErrC <- fmt.Errorf("found no ipfs peers at %s", maddr) } }(maddr) } go func() { wg.Wait() close(maddrC) }() for maddr := range maddrC { maddrs = append(maddrs, maddr) } select { case err := <-resolveErrC: return nil, err default: } return maddrs, nil }