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.IdentityFlag,
utils.UnlockedAccountFlag, utils.UnlockedAccountFlag,
utils.PasswordFileFlag, utils.PasswordFileFlag,
utils.PeersWhitelistFlag,
utils.PeersBlacklistFlag,
utils.BootnodesFlag, utils.BootnodesFlag,
utils.BootnodesV4Flag, utils.BootnodesV4Flag,
utils.BootnodesV5Flag, utils.BootnodesV5Flag,

View file

@ -639,6 +639,18 @@ var (
Value: 30303, Value: 30303,
Category: flags.NetworkingCategory, 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{ BootnodesFlag = &cli.StringFlag{
Name: "bootnodes", Name: "bootnodes",
Usage: "Comma separated enode URLs for P2P discovery bootstrap (set v4+v5 instead for light servers)", 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 // setBootstrapNodes creates a list of bootstrap nodes from the command line
// flags, reverting to pre-configured ones if none have been specified. // flags, reverting to pre-configured ones if none have been specified.
// Priority order for bootnodes configuration: // Priority order for bootnodes configuration:
@ -1249,6 +1328,8 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
setNAT(ctx, cfg) setNAT(ctx, cfg)
setListenAddress(ctx, cfg) setListenAddress(ctx, cfg)
setBootstrapNodes(ctx, cfg) setBootstrapNodes(ctx, cfg)
setWhiteBlackListPeers(ctx, cfg)
removeBlackPeers(cfg)
// setBootstrapNodesV5(ctx, cfg) // setBootstrapNodesV5(ctx, cfg)
if ctx.IsSet(MaxPeersFlag.Name) { if ctx.IsSet(MaxPeersFlag.Name) {

View file

@ -65,6 +65,18 @@ func (api *adminAPI) AddPeer(url string) (bool, error) {
if err != nil { if err != nil {
return false, fmt.Errorf("invalid enode: %v", err) 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) server.AddPeer(node)
return true, nil return true, nil
} }
@ -96,6 +108,18 @@ func (api *adminAPI) AddTrustedPeer(url string) (bool, error) {
if err != nil { if err != nil {
return false, fmt.Errorf("invalid enode: %v", err) 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) server.AddTrustedPeer(node)
return true, nil return true, nil
} }

View file

@ -72,7 +72,9 @@ const (
DiscSelf DiscSelf
DiscReadTimeout DiscReadTimeout
DiscPairPeerStop DiscPairPeerStop
DiscSubprotocolError = 0x10 DiscNonWhitelistedPeer
DiscBlacklistedPeer
DiscSubprotocolError = DiscReason(0x10)
) )
var discReasonToString = [...]string{ var discReasonToString = [...]string{
@ -89,11 +91,13 @@ var discReasonToString = [...]string{
DiscSelf: "connected to self", DiscSelf: "connected to self",
DiscReadTimeout: "read timeout", DiscReadTimeout: "read timeout",
DiscPairPeerStop: "pair peer connection stop", DiscPairPeerStop: "pair peer connection stop",
DiscNonWhitelistedPeer: "disconnect non-whitelisted peer",
DiscBlacklistedPeer: "disconnect blacklisted peer",
DiscSubprotocolError: "subprotocol error", DiscSubprotocolError: "subprotocol error",
} }
func (d DiscReason) String() string { 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 fmt.Sprintf("unknown disconnect reason %d", d)
} }
return discReasonToString[int(d)] return discReasonToString[int(d)]
@ -107,7 +111,7 @@ func discReasonForError(err error) DiscReason {
if reason, ok := err.(DiscReason); ok { if reason, ok := err.(DiscReason); ok {
return reason return reason
} }
if err == errProtocolReturned { if errors.Is(err, errProtocolReturned) {
return DiscQuitting return DiscQuitting
} }
peerError, ok := err.(*peerError) peerError, ok := err.(*peerError)

View file

@ -87,6 +87,11 @@ type Config struct {
// Name sets the node name of this server. // Name sets the node name of this server.
Name string `toml:"-"` 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 // BootstrapNodes are used to establish connectivity
// with the rest of the network. // with the rest of the network.
BootstrapNodes []*discover.Node 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) srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)
return 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. // For dialed connections, check that the remote public key matches.
if dialDest != nil && c.id != dialDest.ID { if dialDest != nil && c.id != dialDest.ID {
clog.Trace("Dialed identity mismatch", "want", c, 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 // If the checks completed successfully, runPeer has now been
// launched by run. // launched by run.
clog.Trace("connection set up", "inbound", dialDest == nil) clog.Debug("Setup connection")
return nil return nil
} }