diff --git a/cmd/boostd/net.go b/cmd/boostd/net.go index bb30c99af..5b4f529d7 100644 --- a/cmd/boostd/net.go +++ b/cmd/boostd/net.go @@ -1,17 +1,1028 @@ package main import ( - "github.com/filecoin-project/boost/node/repo" - lcli "github.com/filecoin-project/lotus/cli" + "context" + "encoding/json" + "fmt" + "math" + "os" + "sort" + "strings" + "text/tabwriter" + "time" + + "github.com/dustin/go-humanize" + "github.com/fatih/color" + bcli "github.com/filecoin-project/boost/cli" + "github.com/filecoin-project/go-address" + atypes "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/addrutil" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + "github.com/multiformats/go-multiaddr" "github.com/urfave/cli/v2" + "golang.org/x/xerrors" ) var netCmd = &cli.Command{ Name: "net", Usage: "Manage P2P Network", - Before: func(cctx *cli.Context) error { - cctx.App.Metadata["repoType"] = repo.Boost + Subcommands: []*cli.Command{ + NetPeers, + NetPing, + NetConnect, + NetDisconnect, + NetListen, + NetId, + NetFindPeer, + NetScores, + NetReachability, + NetBandwidthCmd, + NetBlockCmd, + NetStatCmd, + NetLimitCmd, + NetProtectAdd, + NetProtectRemove, + NetProtectList, + }, +} + +var NetPeers = &cli.Command{ + Name: "peers", + Usage: "Print peers", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "agent", + Aliases: []string{"a"}, + Usage: "Print agent name", + }, + &cli.BoolFlag{ + Name: "extended", + Aliases: []string{"x"}, + Usage: "Print extended peer information in json", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + peers, err := api.NetPeers(ctx) + if err != nil { + return err + } + + sort.Slice(peers, func(i, j int) bool { + return strings.Compare(string(peers[i].ID), string(peers[j].ID)) > 0 + }) + + if cctx.Bool("extended") { + // deduplicate + seen := make(map[peer.ID]struct{}) + + for _, peer := range peers { + _, dup := seen[peer.ID] + if dup { + continue + } + seen[peer.ID] = struct{}{} + + info, err := api.NetPeerInfo(ctx, peer.ID) + if err != nil { + log.Warnf("error getting extended peer info: %s", err) + } else { + bytes, err := json.Marshal(&info) + if err != nil { + log.Warnf("error marshalling extended peer info: %s", err) + } else { + fmt.Println(string(bytes)) + } + } + } + } else { + for _, peer := range peers { + var agent string + if cctx.Bool("agent") { + agent, err = api.NetAgentVersion(ctx, peer.ID) + if err != nil { + log.Warnf("getting agent version: %s", err) + } else { + agent = ", " + agent + } + } + fmt.Printf("%s, %s%s\n", peer.ID, peer.Addrs, agent) + } + } + + return nil + }, +} + +var NetPing = &cli.Command{ + Name: "ping", + Usage: "Ping peers", + ArgsUsage: "[peerMultiaddr]", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "count", + Value: 10, + Aliases: []string{"c"}, + Usage: "specify the number of times it should ping", + }, + &cli.DurationFlag{ + Name: "interval", + Value: time.Second, + Aliases: []string{"i"}, + Usage: "minimum time between pings", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 1 { + return xerrors.Errorf("at least one argument is required") + } + + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := bcli.ReqContext(cctx) + + pis, err := AddrInfoFromArg(ctx, cctx) + if err != nil { + return err + } + + count := cctx.Int("count") + interval := cctx.Duration("interval") + + for _, pi := range pis { + err := api.NetConnect(ctx, pi) + if err != nil { + return xerrors.Errorf("connect: %w", err) + } + + fmt.Printf("PING %s\n", pi.ID) + var avg time.Duration + var successful int + + for i := 0; i < count && ctx.Err() == nil; i++ { + start := time.Now() + + rtt, err := api.NetPing(ctx, pi.ID) + if err != nil { + if ctx.Err() != nil { + break + } + log.Errorf("Ping failed: error=%v", err) + continue + } + fmt.Printf("Pong received: time=%v\n", rtt) + avg = avg + rtt + successful++ + + wctx, cancel := context.WithTimeout(ctx, time.Until(start.Add(interval))) + <-wctx.Done() + cancel() + } + + if successful > 0 { + fmt.Printf("Average latency: %v\n", avg/time.Duration(successful)) + } + } + return nil + }, +} + +var NetScores = &cli.Command{ + Name: "scores", + Usage: "Print peers' pubsub scores", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "extended", + Aliases: []string{"x"}, + Usage: "print extended peer scores in json", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + scores, err := api.NetPubsubScores(ctx) + if err != nil { + return err + } + + if cctx.Bool("extended") { + enc := json.NewEncoder(os.Stdout) + for _, peer := range scores { + err := enc.Encode(peer) + if err != nil { + return err + } + } + } else { + for _, peer := range scores { + fmt.Printf("%s, %f\n", peer.ID, peer.Score.Score) + } + } + + return nil + }, +} + +var NetListen = &cli.Command{ + Name: "listen", + Usage: "List listen addresses", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.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 NetDisconnect = &cli.Command{ + Name: "disconnect", + Usage: "Disconnect from a peer", + ArgsUsage: "[peerID]", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + ids := cctx.Args().Slice() + for _, id := range ids { + pid, err := peer.Decode(id) + if err != nil { + fmt.Println("failure") + return err + } + fmt.Printf("disconnect %s: ", pid) + err = api.NetDisconnect(ctx, pid) + if err != nil { + fmt.Println("failure") + return err + } + fmt.Println("success") + } + return nil + }, +} + +var NetConnect = &cli.Command{ + Name: "connect", + Usage: "Connect to a peer", + ArgsUsage: "[peerMultiaddr|minerActorAddress]", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + pis, err := AddrInfoFromArg(ctx, cctx) + if err != nil { + return err + } + + for _, pi := range pis { + fmt.Printf("connect %s: ", pi.ID) + err := api.NetConnect(ctx, pi) + if err != nil { + fmt.Println("failure") + return err + } + fmt.Println("success") + } + + return nil + }, +} + +func AddrInfoFromArg(ctx context.Context, cctx *cli.Context) ([]peer.AddrInfo, error) { + pis, err := addrutil.ParseAddresses(ctx, cctx.Args().Slice()) + if err != nil { + a, perr := address.NewFromString(cctx.Args().First()) + if perr != nil { + return nil, err + } + + na, fc, err := bcli.GetFullNodeAPI(cctx) + if err != nil { + return nil, err + } + defer fc() + + mi, err := na.StateMinerInfo(ctx, a, types.EmptyTSK) + if err != nil { + return nil, xerrors.Errorf("getting miner info: %w", err) + } + + if mi.PeerId == nil { + return nil, xerrors.Errorf("no PeerID for miner") + } + multiaddrs := make([]multiaddr.Multiaddr, 0, len(mi.Multiaddrs)) + for i, a := range mi.Multiaddrs { + maddr, err := multiaddr.NewMultiaddrBytes(a) + if err != nil { + log.Warnf("parsing multiaddr %d (%x): %s", i, a, err) + continue + } + multiaddrs = append(multiaddrs, maddr) + } + + pi := peer.AddrInfo{ + ID: *mi.PeerId, + Addrs: multiaddrs, + } + + fmt.Printf("%s -> %s\n", a, pi) + + pis = append(pis, pi) + } + + return pis, nil +} + +var NetId = &cli.Command{ + Name: "id", + Usage: "Get node identity", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := bcli.ReqContext(cctx) + + pid, err := api.ID(ctx) + if err != nil { + return err + } + + fmt.Println(pid) + return nil + }, +} + +var NetFindPeer = &cli.Command{ + Name: "find-peer", + Aliases: []string{"findpeer"}, + Usage: "Find the addresses of a given peerID", + ArgsUsage: "[peerId]", + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 1 { + fmt.Println("Usage: findpeer [peer ID]") + return nil + } + + pid, err := peer.Decode(cctx.Args().First()) + if err != nil { + return err + } + + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := bcli.ReqContext(cctx) + + addrs, err := api.NetFindPeer(ctx, pid) + + if err != nil { + return err + } + + fmt.Println(addrs) + return nil + }, +} + +var NetReachability = &cli.Command{ + Name: "reachability", + Usage: "Print information about reachability from the internet", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := bcli.ReqContext(cctx) + + i, err := api.NetAutoNatStatus(ctx) + if err != nil { + return err + } + + fmt.Println("AutoNAT status: ", i.Reachability.String()) + if len(i.PublicAddrs) > 0 { + fmt.Println("Public address:", i.PublicAddrs) + } + return nil + }, +} + +var NetBandwidthCmd = &cli.Command{ + Name: "bandwidth", + Usage: "Print bandwidth usage information", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "by-peer", + Usage: "list bandwidth usage by peer", + }, + &cli.BoolFlag{ + Name: "by-protocol", + Usage: "list bandwidth usage by protocol", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := bcli.ReqContext(cctx) + + bypeer := cctx.Bool("by-peer") + byproto := cctx.Bool("by-protocol") + + tw := tabwriter.NewWriter(os.Stdout, 4, 4, 2, ' ', 0) + + fmt.Fprintf(tw, "Segment\tTotalIn\tTotalOut\tRateIn\tRateOut\n") + + if bypeer { + bw, err := api.NetBandwidthStatsByPeer(ctx) + if err != nil { + return err + } + + var peers []string + for p := range bw { + peers = append(peers, p) + } + + sort.Slice(peers, func(i, j int) bool { + return peers[i] < peers[j] + }) + + for _, p := range peers { + s := bw[p] + fmt.Fprintf(tw, "%s\t%s\t%s\t%s/s\t%s/s\n", p, humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + } + } else if byproto { + bw, err := api.NetBandwidthStatsByProtocol(ctx) + if err != nil { + return err + } + + var protos []protocol.ID + for p := range bw { + protos = append(protos, p) + } + + sort.Slice(protos, func(i, j int) bool { + return protos[i] < protos[j] + }) + + for _, p := range protos { + s := bw[p] + if p == "" { + p = "" + } + fmt.Fprintf(tw, "%s\t%s\t%s\t%s/s\t%s/s\n", p, humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + } + } else { + + s, err := api.NetBandwidthStats(ctx) + if err != nil { + return err + } + + fmt.Fprintf(tw, "Total\t%s\t%s\t%s/s\t%s/s\n", humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + } + + return tw.Flush() + + }, +} + +var NetBlockCmd = &cli.Command{ + Name: "block", + Usage: "Manage network connection gating rules", + Subcommands: []*cli.Command{ + NetBlockAddCmd, + NetBlockRemoveCmd, + NetBlockListCmd, + }, +} + +var NetBlockAddCmd = &cli.Command{ + Name: "add", + Usage: "Add connection gating rules", + Subcommands: []*cli.Command{ + NetBlockAddPeer, + NetBlockAddIP, + NetBlockAddSubnet, + }, +} + +var NetBlockAddPeer = &cli.Command{ + Name: "peer", + Usage: "Block a peer", + ArgsUsage: " ...", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + var peers []peer.ID + for _, s := range cctx.Args().Slice() { + p, err := peer.Decode(s) + if err != nil { + return err + } + + peers = append(peers, p) + } + + return api.NetBlockAdd(ctx, atypes.NetBlockList{Peers: peers}) + }, +} + +var NetBlockAddIP = &cli.Command{ + Name: "ip", + Usage: "Block an IP address", + ArgsUsage: " ...", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + return api.NetBlockAdd(ctx, atypes.NetBlockList{IPAddrs: cctx.Args().Slice()}) + }, +} + +var NetBlockAddSubnet = &cli.Command{ + Name: "subnet", + Usage: "Block an IP subnet", + ArgsUsage: " ...", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + return api.NetBlockAdd(ctx, atypes.NetBlockList{IPSubnets: cctx.Args().Slice()}) + }, +} + +var NetBlockRemoveCmd = &cli.Command{ + Name: "remove", + Usage: "Remove connection gating rules", + Subcommands: []*cli.Command{ + NetBlockRemovePeer, + NetBlockRemoveIP, + NetBlockRemoveSubnet, + }, +} + +var NetBlockRemovePeer = &cli.Command{ + Name: "peer", + Usage: "Unblock a peer", + ArgsUsage: " ...", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + var peers []peer.ID + for _, s := range cctx.Args().Slice() { + p, err := peer.Decode(s) + if err != nil { + return err + } + + peers = append(peers, p) + } + + return api.NetBlockRemove(ctx, atypes.NetBlockList{Peers: peers}) + }, +} + +var NetBlockRemoveIP = &cli.Command{ + Name: "ip", + Usage: "Unblock an IP address", + ArgsUsage: " ...", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + return api.NetBlockRemove(ctx, atypes.NetBlockList{IPAddrs: cctx.Args().Slice()}) + }, +} + +var NetBlockRemoveSubnet = &cli.Command{ + Name: "subnet", + Usage: "Unblock an IP subnet", + ArgsUsage: " ...", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + return api.NetBlockRemove(ctx, atypes.NetBlockList{IPSubnets: cctx.Args().Slice()}) + }, +} + +var NetBlockListCmd = &cli.Command{ + Name: "list", + Usage: "list connection gating rules", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + acl, err := api.NetBlockList(ctx) + if err != nil { + return err + } + + if len(acl.Peers) != 0 { + sort.Slice(acl.Peers, func(i, j int) bool { + return strings.Compare(string(acl.Peers[i]), string(acl.Peers[j])) > 0 + }) + + fmt.Println("Blocked Peers:") + for _, p := range acl.Peers { + fmt.Printf("\t%s\n", p) + } + } + + if len(acl.IPAddrs) != 0 { + sort.Slice(acl.IPAddrs, func(i, j int) bool { + return strings.Compare(acl.IPAddrs[i], acl.IPAddrs[j]) < 0 + }) + + fmt.Println("Blocked IPs:") + for _, a := range acl.IPAddrs { + fmt.Printf("\t%s\n", a) + } + } + + if len(acl.IPSubnets) != 0 { + sort.Slice(acl.IPSubnets, func(i, j int) bool { + return strings.Compare(acl.IPSubnets[i], acl.IPSubnets[j]) < 0 + }) + + fmt.Println("Blocked Subnets:") + for _, n := range acl.IPSubnets { + fmt.Printf("\t%s\n", n) + } + } + + return nil + }, +} + +var BarCols = float64(64) + +func BarString(total, y, g float64) string { + yBars := int(math.Round(y / total * BarCols)) + gBars := int(math.Round(g / total * BarCols)) + if yBars < 0 { + yBars = 0 + } + if gBars < 0 { + gBars = 0 + } + eBars := int(BarCols) - yBars - gBars + var barString = color.YellowString(strings.Repeat("|", yBars)) + + color.GreenString(strings.Repeat("|", gBars)) + if eBars >= 0 { + barString += strings.Repeat(" ", eBars) + } + return barString +} + +var NetStatCmd = &cli.Command{ + Name: "stat", + Usage: "Report resource usage for a scope", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "json", + }, + }, + ArgsUsage: "scope", + Description: `Report resource usage for a scope. + + The scope can be one of the following: + - system -- reports the system aggregate resource usage. + - transient -- reports the transient resource usage. + - svc: -- reports the resource usage of a specific service. + - proto: -- reports the resource usage of a specific protocol. + - peer: -- reports the resource usage of a specific peer. + - all -- reports the resource usage for all currently active scopes. +`, + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + args := cctx.Args().Slice() + if len(args) != 1 { + return xerrors.Errorf("must specify exactly one scope") + } + scope := args[0] + + result, err := api.NetStat(ctx, scope) + if err != nil { + return xerrors.Errorf("get stat: %w", err) + } + + if cctx.Bool("json") { + enc := json.NewEncoder(os.Stdout) + return enc.Encode(result) + } + + printScope := func(stat *network.ScopeStat, scope string) { + if stat == nil { + return + } + + limit, err := api.NetLimit(ctx, scope) + if err != nil { + fmt.Printf("error: %s\n", color.RedString("%s", err)) + } + + fmt.Printf("%s\n", scope) + fmt.Printf("\tmemory: [%s] %s/%s\n", BarString(float64(limit.Memory), 0, float64(stat.Memory)), + types.SizeStr(types.NewInt(uint64(stat.Memory))), + types.SizeStr(types.NewInt(uint64(limit.Memory)))) + + fmt.Printf("\tstreams in: [%s] %d/%d\n", BarString(float64(limit.StreamsInbound), 0, float64(stat.NumStreamsInbound)), stat.NumStreamsInbound, limit.StreamsInbound) + fmt.Printf("\tstreams out: [%s] %d/%d\n", BarString(float64(limit.StreamsOutbound), 0, float64(stat.NumStreamsOutbound)), stat.NumStreamsOutbound, limit.StreamsOutbound) + fmt.Printf("\tconn in: [%s] %d/%d\n", BarString(float64(limit.ConnsInbound), 0, float64(stat.NumConnsInbound)), stat.NumConnsInbound, limit.ConnsInbound) + fmt.Printf("\tconn out: [%s] %d/%d\n", BarString(float64(limit.ConnsOutbound), 0, float64(stat.NumConnsOutbound)), stat.NumConnsOutbound, limit.ConnsOutbound) + fmt.Printf("\tfile desc: [%s] %d/%d\n", BarString(float64(limit.FD), 0, float64(stat.NumFD)), stat.NumFD, limit.FD) + fmt.Println() + } + + printScope(result.System, "system") + printScope(result.Transient, "transient") + + printScopes := func(name string, st map[string]network.ScopeStat) { + type namedStat struct { + name string + stat network.ScopeStat + } + + stats := make([]namedStat, 0, len(st)) + for n, stat := range st { + stats = append(stats, namedStat{ + name: n, + stat: stat, + }) + } + sort.Slice(stats, func(i, j int) bool { + if stats[i].stat.Memory == stats[j].stat.Memory { + return stats[i].name < stats[j].name + } + + return stats[i].stat.Memory > stats[j].stat.Memory + }) + + for _, stat := range stats { + tmp := stat.stat + printScope(&tmp, name+stat.name) + } + + } + + printScopes("svc:", result.Services) + printScopes("proto:", result.Protocols) + printScopes("peer:", result.Peers) + + return nil + }, +} + +var NetLimitCmd = &cli.Command{ + Name: "limit", + Usage: "Get or set resource limits for a scope", + ArgsUsage: "scope [limit]", + Description: `Get or set resource limits for a scope. + + The scope can be one of the following: + - system -- reports the system aggregate resource usage. + - transient -- reports the transient resource usage. + - svc: -- reports the resource usage of a specific service. + - proto: -- reports the resource usage of a specific protocol. + - peer: -- reports the resource usage of a specific peer. + + The limit is json-formatted, with the same structure as the limits file. +`, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "set", + Usage: "set the limit for a scope", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + args := cctx.Args().Slice() + + if cctx.Bool("set") { + if len(args) != 2 { + return xerrors.Errorf("must specify exactly a scope and a limit") + } + scope := args[0] + limitStr := args[1] + + var limit atypes.NetLimit + err := json.Unmarshal([]byte(limitStr), &limit) + if err != nil { + return xerrors.Errorf("error decoding limit: %w", err) + } + + return api.NetSetLimit(ctx, scope, limit) + + } + + if len(args) != 1 { + return xerrors.Errorf("must specify exactly one scope") + } + scope := args[0] + + result, err := api.NetLimit(ctx, scope) + if err != nil { + return err + } + + enc := json.NewEncoder(os.Stdout) + return enc.Encode(result) + }, +} + +var NetProtectAdd = &cli.Command{ + Name: "protect", + Usage: "Add one or more peer IDs to the list of protected peer connections", + ArgsUsage: " [...]", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + pids, err := decodePeerIDsFromArgs(cctx) + if err != nil { + return err + } + + err = api.NetProtectAdd(ctx, pids) + if err != nil { + return err + } + + fmt.Println("added to protected peers:") + for _, pid := range pids { + fmt.Printf(" %s\n", pid) + } + return nil + }, +} + +var NetProtectRemove = &cli.Command{ + Name: "unprotect", + Usage: "Remove one or more peer IDs from the list of protected peer connections.", + ArgsUsage: " [...]", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + + pids, err := decodePeerIDsFromArgs(cctx) + if err != nil { + return err + } + + err = api.NetProtectRemove(ctx, pids) + if err != nil { + return err + } + + fmt.Printf("removed from protected peers:") + for _, pid := range pids { + fmt.Printf(" %s\n", pid) + } + return nil + }, +} + +// decodePeerIDsFromArgs decodes all the arguments present in cli.Context.Args as peer.ID. +// +// This function requires at least one argument to be present, and arguments must not be empty +// string. Otherwise, an error is returned. +func decodePeerIDsFromArgs(cctx *cli.Context) ([]peer.ID, error) { + pidArgs := cctx.Args().Slice() + if len(pidArgs) == 0 { + return nil, xerrors.Errorf("must specify at least one peer ID as an argument") + } + var pids []peer.ID + for _, pidStr := range pidArgs { + if pidStr == "" { + return nil, xerrors.Errorf("peer ID must not be empty") + } + pid, err := peer.Decode(pidStr) + if err != nil { + return nil, err + } + pids = append(pids, pid) + } + return pids, nil +} + +var NetProtectList = &cli.Command{ + Name: "list-protected", + Usage: "List the peer IDs with protected connection.", + Action: func(cctx *cli.Context) error { + api, closer, err := bcli.GetBoostAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := bcli.ReqContext(cctx) + pids, err := api.NetProtectList(ctx) + if err != nil { + return err + } + + for _, pid := range pids { + fmt.Printf("%s\n", pid) + } return nil }, - Subcommands: lcli.NetCmd.Subcommands, } diff --git a/go.mod b/go.mod index 23e78a46d..6e208da58 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/docker/go-units v0.5.0 github.com/dustin/go-humanize v1.0.1 github.com/etclabscore/go-openrpc-reflect v0.0.36 - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.15.0 github.com/filecoin-project/dagstore v0.7.0 github.com/filecoin-project/go-address v1.1.0 github.com/filecoin-project/go-bitfield v0.2.4