rpc: implement setban, lisnbanned, clearbanned RPCs

This commit is contained in:
Roy Lee 2022-08-14 20:44:32 -07:00
parent ff324e0fdb
commit 98e5771989
7 changed files with 371 additions and 11 deletions

View file

@ -48,6 +48,15 @@ func NewAddNodeCmd(addr string, subCmd AddNodeSubCmd) *AddNodeCmd {
}
}
// ClearBannedCmd defines the clearbanned JSON-RPC command.
type ClearBannedCmd struct{}
// NewClearBannedCmd returns a new instance which can be used to issue an clearbanned
// JSON-RPC command.
func NewClearBannedCmd() *ClearBannedCmd {
return &ClearBannedCmd{}
}
// TransactionInput represents the inputs to a transaction. Specifically a
// transaction hash and output number pair.
type TransactionInput struct {
@ -757,6 +766,15 @@ func NewInvalidateBlockCmd(blockHash string) *InvalidateBlockCmd {
}
}
// ListBannedCmd defines the listbanned JSON-RPC command.
type ListBannedCmd struct{}
// NewListBannedCmd returns a new instance which can be used to issue a listbanned
// JSON-RPC command.
func NewListBannedCmd() *ListBannedCmd {
return &ListBannedCmd{}
}
// PingCmd defines the ping JSON-RPC command.
type PingCmd struct{}
@ -903,6 +921,39 @@ func NewBitcoindSendRawTransactionCmd(hexTx string, maxFeeRate int32) *SendRawTr
}
}
// SetBanSubCmd defines the type used in the setban JSON-RPC command for the
// sub command field.
type SetBanSubCmd string
const (
// SBAdd indicates the specified host should be added as a persistent
// peer.
SBAdd SetBanSubCmd = "add"
// SBRemove indicates the specified peer should be removed.
SBRemove SetBanSubCmd = "remove"
)
// SetBanCmd defines the setban JSON-RPC command.
type SetBanCmd struct {
Addr string
SubCmd SetBanSubCmd `jsonrpcusage:"\"add|remove\""`
BanTime *int `jsonrpcdefault:"0"`
Absolute *bool `jsonrpcdefault:"false"`
}
// NewSetBanCmd returns a new instance which can be used to issue an setban
// JSON-RPC command.
func NewSetBanCmd(addr string, subCmd SetBanSubCmd, banTime *int,
absolute *bool) *SetBanCmd {
return &SetBanCmd{
Addr: addr,
SubCmd: subCmd,
BanTime: banTime,
Absolute: absolute,
}
}
// SetGenerateCmd defines the setgenerate JSON-RPC command.
type SetGenerateCmd struct {
Generate bool
@ -1080,6 +1131,9 @@ func init() {
MustRegisterCmd("getnetworkhashps", (*GetNetworkHashPSCmd)(nil), flags)
MustRegisterCmd("getnodeaddresses", (*GetNodeAddressesCmd)(nil), flags)
MustRegisterCmd("getpeerinfo", (*GetPeerInfoCmd)(nil), flags)
MustRegisterCmd("listbanned", (*ListBannedCmd)(nil), flags)
MustRegisterCmd("setban", (*SetBanCmd)(nil), flags)
MustRegisterCmd("clearbanned", (*ClearBannedCmd)(nil), flags)
MustRegisterCmd("getrawmempool", (*GetRawMempoolCmd)(nil), flags)
MustRegisterCmd("getrawtransaction", (*GetRawTransactionCmd)(nil), flags)
MustRegisterCmd("gettxout", (*GetTxOutCmd)(nil), flags)

View file

@ -721,6 +721,15 @@ type InfoChainResult struct {
Errors string `json:"errors"`
}
// ListBannedResult models the data returned from the listbanned command.
type ListBannedResult struct {
Address string `json:"address"`
BanCreated int64 `json:"ban_created"`
BannedUntil int64 `json:"banned_until"`
BanDuration int64 `json:"ban_duration"`
TimeRemaining int64 `json:"time_remaining"`
}
// TxRawResult models the data from the getrawtransaction command.
type TxRawResult struct {
Hex string `json:"hex"`

View file

@ -6,6 +6,7 @@ package main
import (
"sync/atomic"
"time"
"github.com/lbryio/lbcd/blockchain"
"github.com/lbryio/lbcd/chaincfg/chainhash"
@ -181,6 +182,57 @@ func (cm *rpcConnManager) ConnectedPeers() []rpcserverPeer {
return peers
}
// BannedPeers returns a map consisting of all banned host with banned period.
//
// This function is safe for concurrent access and is part of the
// rpcserverConnManager interface implementation.
func (cm *rpcConnManager) BannedPeers() map[string]bannedPeriod {
replyChan := make(chan map[string]bannedPeriod)
cm.server.query <- listBannedPeersMsg{reply: replyChan}
return <-replyChan
}
// SetBan removes the peer associated with the provided address from the
// list of persistent peers.
//
// This function is safe for concurrent access and is part of the
// rpcserverConnManager interface implementation.
func (cm *rpcConnManager) SetBan(addr string, since, until time.Time) error {
replyChan := make(chan error)
cm.server.query <- setBanMsg{
addr: addr,
since: since,
until: until,
reply: replyChan,
}
return <-replyChan
}
// RemoveBan removes a host from banned list.
//
// This function is safe for concurrent access and is part of the
// rpcserverConnManager interface implementation.
func (cm *rpcConnManager) RemoveBan(addr string) error {
replyChan := make(chan error)
cm.server.query <- removeBanMsg{
addr: addr,
reply: replyChan,
}
return <-replyChan
}
// ClearBanned removes all banned host with banned period.
//
// This function is safe for concurrent access and is part of the
// rpcserverConnManager interface implementation.
func (cm *rpcConnManager) ClearBanned() error {
replyChan := make(chan error)
cm.server.query <- clearBannedMsg{
reply: replyChan,
}
return <-replyChan
}
// PersistentPeers returns an array consisting of all the added persistent
// peers.
//

View file

@ -355,6 +355,75 @@ func (c *Client) GetPeerInfo() ([]btcjson.GetPeerInfoResult, error) {
return c.GetPeerInfoAsync().Receive()
}
// FutureListBannedResult is a future promise to deliver the result of a
// ListBannedAsync RPC invocation (or an applicable error).
type FutureListBannedResult chan *Response
// Receive waits for the Response promised by the future and returns data about
// each connected network peer.
func (r FutureListBannedResult) Receive() ([]btcjson.ListBannedResult, error) {
res, err := ReceiveFuture(r)
if err != nil {
return nil, err
}
// Unmarshal result as an array of ListBanned result objects.
var bannedPeers []btcjson.ListBannedResult
err = json.Unmarshal(res, &bannedPeers)
if err != nil {
return nil, err
}
return bannedPeers, nil
}
// SetBanCommand enumerates the available commands that the SetBanCommand function
// accepts.
type SetBanCommand string
// Constants used to indicate the command for the SetBanCommand function.
const (
// SBAdd indicates the specified host should be added as a banned
// peer.
SBAdd SetBanCommand = "add"
// SBRemove indicates the specified peer should be removed.
SBRemove SetBanCommand = "remove"
)
// String returns the SetBanCommand in human-readable form.
func (cmd SetBanCommand) String() string {
return string(cmd)
}
// FutureSetBanResult is a future promise to deliver the result of an
// SetBanAsync RPC invocation (or an applicable error).
type FutureSetBanResult chan *Response
// Receive waits for the Response promised by the future and returns an error if
// any occurred when performing the specified command.
func (r FutureSetBanResult) Receive() error {
_, err := ReceiveFuture(r)
return err
}
// SetBanAsync returns an instance of a type that can be used to get the result
// of the RPC at some future time by invoking the Receive function on the
// returned instance.
func (c *Client) SetBanAsync(addr string, command string, banTime *int,
absolute *bool) FutureSetBanResult {
cmd := btcjson.NewSetBanCmd(addr, btcjson.SetBanSubCmd(command), banTime,
absolute)
return c.SendCmd(cmd)
}
// SetBan attempts to perform the passed command on the passed persistent peer.
// For example, it can be used to add or a remove a banned peer.
func (c *Client) SetBan(addr string, command string, banTime *int,
absolute *bool) error {
return c.SetBanAsync(addr, command, banTime, absolute).Receive()
}
// FutureGetNetTotalsResult is a future promise to deliver the result of a
// GetNetTotalsAsync RPC invocation (or an applicable error).
type FutureGetNetTotalsResult chan *Response

View file

@ -134,6 +134,7 @@ type commandHandler func(*rpcServer, interface{}, <-chan struct{}) (interface{},
var rpcHandlers map[string]commandHandler
var rpcHandlersBeforeInit = map[string]commandHandler{
"addnode": handleAddNode,
"clearbanned": handleClearBanned,
"createrawtransaction": handleCreateRawTransaction,
"debuglevel": handleDebugLevel,
"decoderawtransaction": handleDecodeRawTransaction,
@ -150,10 +151,10 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"getblockcount": handleGetBlockCount,
"getblockhash": handleGetBlockHash,
"getblockheader": handleGetBlockHeader,
"getchaintips": handleGetChainTips,
"getblocktemplate": handleGetBlockTemplate,
"getcfilter": handleGetCFilter,
"getcfilterheader": handleGetCFilterHeader,
"getchaintips": handleGetChainTips,
"getconnectioncount": handleGetConnectionCount,
"getcurrentnet": handleGetCurrentNet,
"getdifficulty": handleGetDifficulty,
@ -161,8 +162,8 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"gethashespersec": handleGetHashesPerSec,
"getheaders": handleGetHeaders,
"getinfo": handleGetInfo,
"getmempoolinfo": handleGetMempoolInfo,
"getmempoolentry": handleGetMempoolEntry,
"getmempoolinfo": handleGetMempoolInfo,
"getmininginfo": handleGetMiningInfo,
"getnettotals": handleGetNetTotals,
"getnetworkhashps": handleGetNetworkHashPS,
@ -174,11 +175,13 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"gettxout": handleGetTxOut,
"help": handleHelp,
"invalidateblock": handleInvalidateBlock,
"listbanned": handleListBanned,
"node": handleNode,
"ping": handlePing,
"reconsiderblock": handleReconsiderBlock,
"searchrawtransactions": handleSearchRawTransactions,
"sendrawtransaction": handleSendRawTransaction,
"setban": handleSetBan,
"setgenerate": handleSetGenerate,
"signmessagewithprivkey": handleSignMessageWithPrivKey,
"stop": handleStop,
@ -398,6 +401,21 @@ func handleAddNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (in
return nil, nil
}
// handleClearBanned handles clearbanned commands.
func handleClearBanned(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
err := s.cfg.ConnMgr.ClearBanned()
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: err.Error(),
}
}
// no data returned unless an error.
return nil, nil
}
// handleNode handles node commands.
func handleNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*btcjson.NodeCmd)
@ -3146,6 +3164,24 @@ func handleHelp(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (inter
return help, nil
}
// handleListBanned handles the listbanned command.
func handleListBanned(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
banned := s.cfg.ConnMgr.BannedPeers()
reply := make([]*btcjson.ListBannedResult, 0, len(banned))
for address, period := range banned {
since, until := period.since, period.until
r := btcjson.ListBannedResult{
Address: address,
BanCreated: since.Unix(),
BannedUntil: until.Unix(),
BanDuration: int64(until.Sub(since).Seconds()),
TimeRemaining: int64(time.Until(until).Seconds()),
}
reply = append(reply, &r)
}
return reply, nil
}
// handlePing implements the ping command.
func handlePing(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
// Ask server to ping \o_
@ -3749,6 +3785,59 @@ func handleSendRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan st
return tx.Hash().String(), nil
}
// handleSetBan handles the setban command.
func handleSetBan(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*btcjson.SetBanCmd)
addr := net.ParseIP(c.Addr)
if addr == nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: "invalid addr for setban",
}
}
since := time.Now()
until := since.Add(time.Second * time.Duration(*c.BanTime))
if *c.BanTime == 0 {
until = since.Add(defaultBanDuration)
}
if *c.Absolute {
until = time.Unix(int64(*c.BanTime), 0)
}
var err error
switch c.SubCmd {
case "add":
err = s.cfg.ConnMgr.SetBan(addr.String(), since, until)
addr := addr.String()
peers := s.cfg.ConnMgr.ConnectedPeers()
for _, peer := range peers {
p := peer.ToPeer()
if p.NA().IP.String() == addr {
p.Disconnect()
}
}
case "remove":
err = s.cfg.ConnMgr.RemoveBan(addr.String())
default:
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: "invalid subcommand for setban",
}
}
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: err.Error(),
}
}
// no data returned unless an error.
return nil, nil
}
// handleSetGenerate implements the setgenerate command.
func handleSetGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*btcjson.SetGenerateCmd)
@ -4806,6 +4895,18 @@ type rpcserverConnManager interface {
// ConnectedPeers returns an array consisting of all connected peers.
ConnectedPeers() []rpcserverPeer
// BannedPeers returns an array consisting of all Banned peers.
BannedPeers() map[string]bannedPeriod
// SetBan add the addr to the ban list.
SetBan(addr string, since time.Time, until time.Time) error
// RemoveBan remove the subnet from the ban list.
RemoveBan(subnet string) error
// ClearBanned removes all banned IPs.
ClearBanned() error
// PersistentPeers returns an array consisting of all the persistent
// peers.
PersistentPeers() []rpcserverPeer

View file

@ -56,6 +56,9 @@ var helpDescsEnUS = map[string]string{
"createrawtransaction-locktime": "Locktime value; a non-zero value will also locktime-activate the inputs",
"createrawtransaction--result0": "Hex-encoded bytes of the serialized transaction",
// ClearBannedCmd help.
"clearbanned--synopsis": "Clear all banned IPs.",
// ScriptSig help.
"scriptsig-asm": "Disassembly of the script",
"scriptsig-hex": "Hex-encoded bytes of the script",
@ -636,6 +639,16 @@ var helpDescsEnUS = map[string]string{
"ping--synopsis": "Queues a ping to be sent to each connected peer.\n" +
"Ping times are provided by getpeerinfo via the pingtime and pingwait fields.",
// ListBannedCmd help.
"listbanned--synopsis": "List all banned IPs.",
// ListBannedResult help.
"listbannedresult-address": "The IP of the banned node.",
"listbannedresult-ban_created": "The UNIX epoch time the ban was created.",
"listbannedresult-banned_until": "The UNIX epoch time the ban expires.",
"listbannedresult-ban_duration": "The duration of the ban, in seconds.",
"listbannedresult-time_remaining": "The time remaining on the ban, in seconds",
// ReconsiderBlockCmd
"reconsiderblock--synopsis": "Reconsider a block for validation.",
"reconsiderblock-blockhash": "Hash of the block you want to reconsider",
@ -664,6 +677,13 @@ var helpDescsEnUS = map[string]string{
"sendrawtransaction--result0": "The hash of the transaction",
"allowhighfeesormaxfeerate-value": "Either the boolean value for the allowhighfees parameter in bitcoind < v0.19.0 or the numerical value for the maxfeerate field in bitcoind v0.19.0 and later",
// SetBanCmd help.
"setban--synopsis": "Add or remove an IP from the banned list. (Currently, subnet is not supported.)",
"setban-addr": "The IP to ban. (Currently, subnet is not supported.)",
"setban-subcmd": "'add' to add an IP to the list, 'remove' to remove an IP from the list",
"setban-bantime": "Time in seconds the IP is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)",
"setban-absolute": "If set, the bantime must be an absolute timestamp expressed in UNIX epoch time; default to false.",
// SetGenerateCmd help.
"setgenerate--synopsis": "Set the server to generate coins (mine) or not.",
"setgenerate-generate": "Use true to enable generation, false to disable it",
@ -880,6 +900,7 @@ var helpDescsEnUS = map[string]string{
// pointer to the type (or nil to indicate no return value).
var rpcResultTypes = map[string][]interface{}{
"addnode": nil,
"clearbanned": nil,
"createrawtransaction": {(*string)(nil)},
"debuglevel": {(*string)(nil), (*string)(nil)},
"decoderawtransaction": {(*btcjson.TxRawDecodeResult)(nil)},
@ -892,11 +913,11 @@ var rpcResultTypes = map[string][]interface{}{
"getbestblock": {(*btcjson.GetBestBlockResult)(nil)},
"getbestblockhash": {(*string)(nil)},
"getblock": {(*string)(nil), (*btcjson.GetBlockVerboseResult)(nil)},
"getblockchaininfo": {(*btcjson.GetBlockChainInfoResult)(nil)},
"getblockcount": {(*int64)(nil)},
"getblockhash": {(*string)(nil)},
"getblockheader": {(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)},
"getblocktemplate": {(*btcjson.GetBlockTemplateResult)(nil), (*string)(nil), nil},
"getblockchaininfo": {(*btcjson.GetBlockChainInfoResult)(nil)},
"getcfilter": {(*string)(nil)},
"getcfilterheader": {(*string)(nil)},
"getchaintips": {(*[]btcjson.GetChainTipsResult)(nil)},
@ -918,13 +939,15 @@ var rpcResultTypes = map[string][]interface{}{
"getrawmempool": {(*[]string)(nil), (*btcjson.GetRawMempoolVerboseResult)(nil)},
"getrawtransaction": {(*string)(nil), (*btcjson.TxRawResult)(nil)},
"gettxout": {(*btcjson.GetTxOutResult)(nil)},
"node": nil,
"help": {(*string)(nil), (*string)(nil)},
"invalidateblock": nil,
"listbanned": {(*[]btcjson.ListBannedResult)(nil)},
"node": nil,
"ping": nil,
"reconsiderblock": nil,
"searchrawtransactions": {(*string)(nil), (*[]btcjson.SearchRawTransactionsResult)(nil)},
"sendrawtransaction": {(*string)(nil)},
"setban": nil,
"setgenerate": nil,
"signmessagewithprivkey": {(*string)(nil)},
"stop": {(*string)(nil)},

View file

@ -156,13 +156,18 @@ type updatePeerHeightsMsg struct {
originPeer *peer.Peer
}
type bannedPeriod struct {
since time.Time
until time.Time
}
// peerState maintains state of inbound, persistent, outbound peers as well
// as banned peers and outbound groups.
type peerState struct {
inboundPeers map[int32]*serverPeer
outboundPeers map[int32]*serverPeer
persistentPeers map[int32]*serverPeer
banned map[string]time.Time
banned map[string]bannedPeriod
outboundGroups map[string]int
}
@ -1656,10 +1661,10 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool {
sp.Disconnect()
return false
}
if banEnd, ok := state.banned[host]; ok {
if time.Now().Before(banEnd) {
srvrLog.Debugf("Peer %s is banned for another %v - disconnecting",
host, time.Until(banEnd))
if ban, ok := state.banned[host]; ok {
if time.Now().Before(ban.until) {
srvrLog.Infof("Peer %s is banned for another %v - disconnecting",
host, time.Until(ban.until))
sp.Disconnect()
return false
}
@ -1781,7 +1786,12 @@ func (s *server) handleBanPeerMsg(state *peerState, sp *serverPeer) {
direction := directionString(sp.Inbound())
srvrLog.Infof("Banned peer %s (%s) for %v", host, direction,
cfg.BanDuration)
state.banned[host] = time.Now().Add(cfg.BanDuration)
since := time.Now()
state.banned[host] = bannedPeriod{
since: since,
until: since.Add(cfg.BanDuration),
}
}
// handleRelayInvMsg deals with relaying inventory to peers that are not already
@ -1876,6 +1886,25 @@ type getPeersMsg struct {
reply chan []*serverPeer
}
type listBannedPeersMsg struct {
reply chan map[string]bannedPeriod
}
type setBanMsg struct {
addr string
since time.Time
until time.Time
reply chan error
}
type removeBanMsg struct {
addr string
reply chan error
}
type clearBannedMsg struct {
reply chan error
}
type getOutboundGroup struct {
key string
reply chan int
@ -1924,6 +1953,29 @@ func (s *server) handleQuery(state *peerState, querymsg interface{}) {
})
msg.reply <- peers
case listBannedPeersMsg:
banned := map[string]bannedPeriod{}
for host, ban := range state.banned {
banned[host] = ban
}
msg.reply <- banned
case setBanMsg:
ban := bannedPeriod{
since: msg.since,
until: msg.until,
}
state.banned[msg.addr] = ban
msg.reply <- nil
case removeBanMsg:
delete(state.banned, msg.addr)
msg.reply <- nil
case clearBannedMsg:
state.banned = map[string]bannedPeriod{}
msg.reply <- nil
case connectNodeMsg:
// TODO: duplicate oneshots?
// Limit max number of total peers.
@ -2161,7 +2213,7 @@ func (s *server) peerHandler() {
inboundPeers: make(map[int32]*serverPeer),
persistentPeers: make(map[int32]*serverPeer),
outboundPeers: make(map[int32]*serverPeer),
banned: make(map[string]time.Time),
banned: make(map[string]bannedPeriod),
outboundGroups: make(map[string]int),
}