diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go index 07446a82e3..1017311b5c 100644 --- a/cmd/XDC/main.go +++ b/cmd/XDC/main.go @@ -61,6 +61,8 @@ var ( utils.IdentityFlag, utils.UnlockedAccountFlag, utils.PasswordFileFlag, + utils.PeersWhitelistFlag, + utils.PeersBlacklistFlag, utils.BootnodesFlag, utils.BootnodesV4Flag, utils.BootnodesV5Flag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7cca28bd83..419ca477b8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -639,6 +639,18 @@ var ( Value: 30303, Category: flags.NetworkingCategory, } + PeersWhitelistFlag = &cli.StringFlag{ + Name: "peers-whitelist", + Usage: "Comma separated NodeID or enode URLs for peer whitelist (only connect to them)", + Value: "", + Category: flags.NetworkingCategory, + } + PeersBlacklistFlag = &cli.StringFlag{ + Name: "peers-blacklist", + Usage: "Comma separated NodeID or enode URLs for peer blacklist (will not connect to them)", + Value: "", + Category: flags.NetworkingCategory, + } BootnodesFlag = &cli.StringFlag{ Name: "bootnodes", Usage: "Comma separated enode URLs for P2P discovery bootstrap (set v4+v5 instead for light servers)", @@ -954,6 +966,73 @@ func setNodeUserIdent(ctx *cli.Context, cfg *node.Config) { } } +func setWhiteBlackListPeers(ctx *cli.Context, cfg *p2p.Config) { + CheckExclusive(ctx, PeersWhitelistFlag, PeersBlacklistFlag) + + // setup whitelist for peers + if ctx.IsSet(PeersWhitelistFlag.Name) { + urls := SplitAndTrim(ctx.String(PeersWhitelistFlag.Name)) + cfg.WhitePeers = make(map[discover.NodeID]struct{}, len(urls)) + for _, url := range urls { + if url != "" { + node1, err1 := discover.HexID(url) + if err1 == nil { + cfg.WhitePeers[node1] = struct{}{} + log.Info("Add peer to whitelist", "id", node1.String()) + continue + } + node2, err2 := discover.ParseNode(url) + if err2 == nil { + cfg.WhitePeers[node2.ID] = struct{}{} + log.Info("Add peer to whitelist", "enode", url, "id", node2.ID.String()) + continue + } + log.Crit("Invalid peer id for whitelist", "url", url, "err1", err1, "err2", err2) + } + } + } + + // setup blacklist for peers + if ctx.IsSet(PeersBlacklistFlag.Name) { + urls := SplitAndTrim(ctx.String(PeersBlacklistFlag.Name)) + cfg.BlackPeers = make(map[discover.NodeID]struct{}, len(urls)) + for _, url := range urls { + if url != "" { + node1, err1 := discover.HexID(url) + if err1 == nil { + cfg.BlackPeers[node1] = struct{}{} + log.Info("Add peer to blacklsit", "id", node1.String()) + continue + } + node2, err2 := discover.ParseNode(url) + if err2 == nil { + cfg.BlackPeers[node2.ID] = struct{}{} + log.Info("Add peer to blacklsit", "enode", url, "id", node2.ID.String()) + continue + } + log.Crit("Invalid peer id for blacklist", "url", url, "err1", err1, "err2", err2) + } + } + } +} + +// removeBlackPeers removes bootstrap nodes which is in peers blacklist +func removeBlackPeers(cfg *p2p.Config) { + if len(cfg.BlackPeers) == 0 { + return + } + + filteredNodes := make([]*discover.Node, 0, len(cfg.BootstrapNodes)) + for _, node := range cfg.BootstrapNodes { + if _, ok := cfg.BlackPeers[node.ID]; ok { + log.Info("Remove black peer", "enode", node.String(), "id", node.ID) + continue + } + filteredNodes = append(filteredNodes, node) + } + cfg.BootstrapNodes = filteredNodes +} + // setBootstrapNodes creates a list of bootstrap nodes from the command line // flags, reverting to pre-configured ones if none have been specified. // Priority order for bootnodes configuration: @@ -1249,6 +1328,8 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { setNAT(ctx, cfg) setListenAddress(ctx, cfg) setBootstrapNodes(ctx, cfg) + setWhiteBlackListPeers(ctx, cfg) + removeBlackPeers(cfg) // setBootstrapNodesV5(ctx, cfg) if ctx.IsSet(MaxPeersFlag.Name) { diff --git a/node/api.go b/node/api.go index b2ea06f4a7..e944bec1b3 100644 --- a/node/api.go +++ b/node/api.go @@ -65,6 +65,18 @@ func (api *adminAPI) AddPeer(url string) (bool, error) { if err != nil { return false, fmt.Errorf("invalid enode: %v", err) } + // only accept the node which is in peer whitelist if the list is not empty + if len(server.WhitePeers) > 0 { + if _, ok := server.WhitePeers[node.ID]; !ok { + return false, fmt.Errorf("peer is not in whitelist: %v, ID: %s", url, node.ID) + } + } + // reject the node which is in peer blacklist + if len(server.BlackPeers) > 0 { + if _, ok := server.BlackPeers[node.ID]; ok { + return false, fmt.Errorf("peer is in blacklist: %v, ID: %s", url, node.ID) + } + } server.AddPeer(node) return true, nil } @@ -96,6 +108,18 @@ func (api *adminAPI) AddTrustedPeer(url string) (bool, error) { if err != nil { return false, fmt.Errorf("invalid enode: %v", err) } + // only accept the node which is in peer whitelist if the list is not empty + if len(server.WhitePeers) > 0 { + if _, ok := server.WhitePeers[node.ID]; !ok { + return false, fmt.Errorf("trusted peer is not in whitelist: %v, ID: %s", url, node.ID) + } + } + // reject the node which is in peer blacklist + if len(server.BlackPeers) > 0 { + if _, ok := server.BlackPeers[node.ID]; ok { + return false, fmt.Errorf("trusted peer is in blacklist: %v, ID: %s", url, node.ID) + } + } server.AddTrustedPeer(node) return true, nil } diff --git a/p2p/peer_error.go b/p2p/peer_error.go index 3ee1e1ab25..7e4379d2aa 100644 --- a/p2p/peer_error.go +++ b/p2p/peer_error.go @@ -72,7 +72,9 @@ const ( DiscSelf DiscReadTimeout DiscPairPeerStop - DiscSubprotocolError = 0x10 + DiscNonWhitelistedPeer + DiscBlacklistedPeer + DiscSubprotocolError = DiscReason(0x10) ) var discReasonToString = [...]string{ @@ -89,11 +91,13 @@ var discReasonToString = [...]string{ DiscSelf: "connected to self", DiscReadTimeout: "read timeout", DiscPairPeerStop: "pair peer connection stop", + DiscNonWhitelistedPeer: "disconnect non-whitelisted peer", + DiscBlacklistedPeer: "disconnect blacklisted peer", DiscSubprotocolError: "subprotocol error", } func (d DiscReason) String() string { - if len(discReasonToString) <= int(d) || int(d) < 0 { + if len(discReasonToString) <= int(d) || int(d) < 0 || discReasonToString[int(d)] == "" { return fmt.Sprintf("unknown disconnect reason %d", d) } return discReasonToString[int(d)] @@ -107,7 +111,7 @@ func discReasonForError(err error) DiscReason { if reason, ok := err.(DiscReason); ok { return reason } - if err == errProtocolReturned { + if errors.Is(err, errProtocolReturned) { return DiscQuitting } peerError, ok := err.(*peerError) diff --git a/p2p/server.go b/p2p/server.go index e113530ecc..e95a6cc1f0 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -87,6 +87,11 @@ type Config struct { // Name sets the node name of this server. Name string `toml:"-"` + // Whitelist for peers + WhitePeers map[discover.NodeID]struct{} + // Blacklist for peers. + BlackPeers map[discover.NodeID]struct{} + // BootstrapNodes are used to establish connectivity // with the rest of the network. BootstrapNodes []*discover.Node @@ -892,7 +897,19 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) e srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) return err } - clog := srv.log.New("id", c.id, "addr", c.fd.RemoteAddr(), "conn", c.flags) + clog := srv.log.New("id", c.id.String(), "addr", c.fd.RemoteAddr(), "conn", c.flags) + if len(srv.WhitePeers) > 0 { + if _, ok := srv.WhitePeers[c.id]; !ok { + clog.Debug("Reject non-whitelisted peer") + return DiscNonWhitelistedPeer + } + } + if len(srv.BlackPeers) > 0 { + if _, ok := srv.BlackPeers[c.id]; ok { + clog.Debug("Reject blacklisted peer") + return DiscBlacklistedPeer + } + } // For dialed connections, check that the remote public key matches. if dialDest != nil && c.id != dialDest.ID { clog.Trace("Dialed identity mismatch", "want", c, dialDest.ID) @@ -921,7 +938,7 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *discover.Node) e } // If the checks completed successfully, runPeer has now been // launched by run. - clog.Trace("connection set up", "inbound", dialDest == nil) + clog.Debug("Setup connection") return nil }