mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
Add Basic Support for IP Tracker (#7844)
* add basic support for ip tracker * clean up * check for it * fix * Update beacon-chain/p2p/peers/status.go * fix
This commit is contained in:
@@ -18,6 +18,7 @@ go_library(
|
|||||||
"@com_github_libp2p_go_libp2p_core//network:go_default_library",
|
"@com_github_libp2p_go_libp2p_core//network:go_default_library",
|
||||||
"@com_github_libp2p_go_libp2p_core//peer:go_default_library",
|
"@com_github_libp2p_go_libp2p_core//peer:go_default_library",
|
||||||
"@com_github_multiformats_go_multiaddr//:go_default_library",
|
"@com_github_multiformats_go_multiaddr//:go_default_library",
|
||||||
|
"@com_github_multiformats_go_multiaddr//net:go_default_library",
|
||||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/libp2p/go-libp2p-core/network"
|
"github.com/libp2p/go-libp2p-core/network"
|
||||||
"github.com/libp2p/go-libp2p-core/peer"
|
"github.com/libp2p/go-libp2p-core/peer"
|
||||||
ma "github.com/multiformats/go-multiaddr"
|
ma "github.com/multiformats/go-multiaddr"
|
||||||
|
manet "github.com/multiformats/go-multiaddr/net"
|
||||||
"github.com/prysmaticlabs/go-bitfield"
|
"github.com/prysmaticlabs/go-bitfield"
|
||||||
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
||||||
"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers/peerdata"
|
"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers/peerdata"
|
||||||
@@ -52,14 +53,20 @@ const (
|
|||||||
PeerConnecting
|
PeerConnecting
|
||||||
)
|
)
|
||||||
|
|
||||||
// Additional buffer beyond current peer limit, from which we can store the relevant peer statuses.
|
const (
|
||||||
const maxLimitBuffer = 150
|
// ColocationLimit restricts how many peer identities we can see from a single ip or ipv6 subnet.
|
||||||
|
ColocationLimit = 5
|
||||||
|
|
||||||
|
// Additional buffer beyond current peer limit, from which we can store the relevant peer statuses.
|
||||||
|
maxLimitBuffer = 150
|
||||||
|
)
|
||||||
|
|
||||||
// Status is the structure holding the peer status information.
|
// Status is the structure holding the peer status information.
|
||||||
type Status struct {
|
type Status struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
scorers *scorers.Service
|
scorers *scorers.Service
|
||||||
store *peerdata.Store
|
store *peerdata.Store
|
||||||
|
ipTracker map[string]uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatusConfig represents peer status service params.
|
// StatusConfig represents peer status service params.
|
||||||
@@ -76,9 +83,10 @@ func NewStatus(ctx context.Context, config *StatusConfig) *Status {
|
|||||||
MaxPeers: maxLimitBuffer + config.PeerLimit,
|
MaxPeers: maxLimitBuffer + config.PeerLimit,
|
||||||
})
|
})
|
||||||
return &Status{
|
return &Status{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
store: store,
|
store: store,
|
||||||
scorers: scorers.NewService(ctx, store, config.ScorerParams),
|
scorers: scorers.NewService(ctx, store, config.ScorerParams),
|
||||||
|
ipTracker: map[string]uint64{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,11 +108,15 @@ func (p *Status) Add(record *enr.Record, pid peer.ID, address ma.Multiaddr, dire
|
|||||||
|
|
||||||
if peerData, ok := p.store.PeerData(pid); ok {
|
if peerData, ok := p.store.PeerData(pid); ok {
|
||||||
// Peer already exists, just update its address info.
|
// Peer already exists, just update its address info.
|
||||||
|
prevAddress := peerData.Address
|
||||||
peerData.Address = address
|
peerData.Address = address
|
||||||
peerData.Direction = direction
|
peerData.Direction = direction
|
||||||
if record != nil {
|
if record != nil {
|
||||||
peerData.Enr = record
|
peerData.Enr = record
|
||||||
}
|
}
|
||||||
|
if !sameIP(prevAddress, address) {
|
||||||
|
p.addIpToTracker(pid)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
peerData := &peerdata.PeerData{
|
peerData := &peerdata.PeerData{
|
||||||
@@ -117,6 +129,7 @@ func (p *Status) Add(record *enr.Record, pid peer.ID, address ma.Multiaddr, dire
|
|||||||
peerData.Enr = record
|
peerData.Enr = record
|
||||||
}
|
}
|
||||||
p.store.SetPeerData(pid, peerData)
|
p.store.SetPeerData(pid, peerData)
|
||||||
|
p.addIpToTracker(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address returns the multiaddress of the given remote peer.
|
// Address returns the multiaddress of the given remote peer.
|
||||||
@@ -269,7 +282,7 @@ func (p *Status) ChainStateLastUpdated(pid peer.ID) (time.Time, error) {
|
|||||||
// IsBad states if the peer is to be considered bad (by *any* of the registered scorers).
|
// IsBad states if the peer is to be considered bad (by *any* of the registered scorers).
|
||||||
// If the peer is unknown this will return `false`, which makes using this function easier than returning an error.
|
// If the peer is unknown this will return `false`, which makes using this function easier than returning an error.
|
||||||
func (p *Status) IsBad(pid peer.ID) bool {
|
func (p *Status) IsBad(pid peer.ID) bool {
|
||||||
return p.scorers.IsBadPeer(pid)
|
return p.isfromBadIP(pid) || p.scorers.IsBadPeer(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextValidTime gets the earliest possible time it is to contact/dial
|
// NextValidTime gets the earliest possible time it is to contact/dial
|
||||||
@@ -452,6 +465,7 @@ func (p *Status) Prune() {
|
|||||||
for _, peerData := range peersToPrune {
|
for _, peerData := range peersToPrune {
|
||||||
p.store.DeletePeerData(peerData.pid)
|
p.store.DeletePeerData(peerData.pid)
|
||||||
}
|
}
|
||||||
|
p.tallyIPTracker()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BestFinalized returns the highest finalized epoch equal to or higher than ours that is agreed
|
// BestFinalized returns the highest finalized epoch equal to or higher than ours that is agreed
|
||||||
@@ -568,6 +582,88 @@ func (p *Status) HighestEpoch() uint64 {
|
|||||||
return helpers.SlotToEpoch(highestSlot)
|
return helpers.SlotToEpoch(highestSlot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Status) isfromBadIP(pid peer.ID) bool {
|
||||||
|
p.store.RLock()
|
||||||
|
defer p.store.RUnlock()
|
||||||
|
|
||||||
|
peerData, ok := p.store.PeerData(pid)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if peerData.Address == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ip, err := manet.ToIP(peerData.Address)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if val, ok := p.ipTracker[ip.String()]; ok {
|
||||||
|
if val > ColocationLimit {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Status) addIpToTracker(pid peer.ID) {
|
||||||
|
data, ok := p.store.PeerData(pid)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if data.Address == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ip, err := manet.ToIP(data.Address)
|
||||||
|
if err != nil {
|
||||||
|
// Should never happen, it is
|
||||||
|
// assumed every IP coming in
|
||||||
|
// is a valid ip.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Ignore loopback addresses.
|
||||||
|
if ip.IsLoopback() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stringIP := ip.String()
|
||||||
|
p.ipTracker[stringIP] += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Status) tallyIPTracker() {
|
||||||
|
tracker := map[string]uint64{}
|
||||||
|
// Iterate through all peers.
|
||||||
|
for _, peerData := range p.store.Peers() {
|
||||||
|
if peerData.Address == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip, err := manet.ToIP(peerData.Address)
|
||||||
|
if err != nil {
|
||||||
|
// Should never happen, it is
|
||||||
|
// assumed every IP coming in
|
||||||
|
// is a valid ip.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stringIP := ip.String()
|
||||||
|
tracker[stringIP] += 1
|
||||||
|
}
|
||||||
|
p.ipTracker = tracker
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameIP(firstAddr, secondAddr ma.Multiaddr) bool {
|
||||||
|
// Exit early if we do get nil multiaddresses
|
||||||
|
if firstAddr == nil || secondAddr == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
firstIP, err := manet.ToIP(firstAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
secondIP, err := manet.ToIP(secondAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return firstIP.Equal(secondIP)
|
||||||
|
}
|
||||||
|
|
||||||
func retrieveIndicesFromBitfield(bitV bitfield.Bitvector64) []uint64 {
|
func retrieveIndicesFromBitfield(bitV bitfield.Bitvector64) []uint64 {
|
||||||
committeeIdxs := make([]uint64, 0, bitV.Count())
|
committeeIdxs := make([]uint64, 0, bitV.Count())
|
||||||
for i := uint64(0); i < 64; i++ {
|
for i := uint64(0); i < 64; i++ {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package peers_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -517,6 +518,45 @@ func TestPrune(t *testing.T) {
|
|||||||
assert.ErrorContains(t, "peer unknown", err)
|
assert.ErrorContains(t, "peer unknown", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPeerIPTracker(t *testing.T) {
|
||||||
|
maxBadResponses := 2
|
||||||
|
p := peers.NewStatus(context.Background(), &peers.StatusConfig{
|
||||||
|
PeerLimit: 30,
|
||||||
|
ScorerParams: &scorers.Config{
|
||||||
|
BadResponsesScorerConfig: &scorers.BadResponsesScorerConfig{
|
||||||
|
Threshold: maxBadResponses,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
badIP := "211.227.218.116"
|
||||||
|
badPeers := []peer.ID{}
|
||||||
|
for i := 0; i < peers.ColocationLimit+10; i++ {
|
||||||
|
port := strconv.Itoa(3000 + i)
|
||||||
|
addr, err := ma.NewMultiaddr("/ip4/" + badIP + "/tcp/" + port)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
badPeers = append(badPeers, createPeer(t, p, addr))
|
||||||
|
}
|
||||||
|
for _, pr := range badPeers {
|
||||||
|
assert.Equal(t, true, p.IsBad(pr), "peer with bad ip is not bad")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add in bad peers, so that our records are trimmed out
|
||||||
|
// from the peer store.
|
||||||
|
for i := 0; i < p.MaxPeerLimit()+100; i++ {
|
||||||
|
// Peer added to peer handler.
|
||||||
|
pid := addPeer(t, p, peers.PeerConnected)
|
||||||
|
p.Scorers().BadResponsesScorer().Increment(pid)
|
||||||
|
}
|
||||||
|
p.Prune()
|
||||||
|
|
||||||
|
for _, pr := range badPeers {
|
||||||
|
assert.Equal(t, false, p.IsBad(pr), "peer with good ip is regarded as bad")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTrimmedOrderedPeers(t *testing.T) {
|
func TestTrimmedOrderedPeers(t *testing.T) {
|
||||||
p := peers.NewStatus(context.Background(), &peers.StatusConfig{
|
p := peers.NewStatus(context.Background(), &peers.StatusConfig{
|
||||||
PeerLimit: 30,
|
PeerLimit: 30,
|
||||||
@@ -833,3 +873,15 @@ func addPeer(t *testing.T, p *peers.Status, state peerdata.PeerConnectionState)
|
|||||||
})
|
})
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createPeer(t *testing.T, p *peers.Status, addr ma.Multiaddr) peer.ID {
|
||||||
|
mhBytes := []byte{0x11, 0x04}
|
||||||
|
idBytes := make([]byte, 4)
|
||||||
|
_, err := rand.Read(idBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
mhBytes = append(mhBytes, idBytes...)
|
||||||
|
id, err := peer.IDFromBytes(mhBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
p.Add(new(enr.Record), id, addr, network.DirUnknown)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user