cmd, node, p2p: implement whitelist and blacklist for peers (#1331)

This commit is contained in:
Daniel Liu 2025-08-20 15:14:56 +08:00 committed by GitHub
parent 0ceeb24fb3
commit 4ec6e8cd58
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 133 additions and 5 deletions

View file

@ -61,6 +61,8 @@ var (
utils.IdentityFlag,
utils.UnlockedAccountFlag,
utils.PasswordFileFlag,
utils.PeersWhitelistFlag,
utils.PeersBlacklistFlag,
utils.BootnodesFlag,
utils.BootnodesV4Flag,
utils.BootnodesV5Flag,

View file

@ -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) {

View file

@ -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
}

View file

@ -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)

View file

@ -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
}