limit num connections per ip range

This commit is contained in:
Ethan Buchman
2015-07-12 17:54:34 +00:00
parent 9dba060e25
commit 0728539bc1
4 changed files with 225 additions and 32 deletions

View File

@ -1,6 +1,8 @@
package p2p
import (
"net"
"strings"
"sync"
)
@ -14,12 +16,18 @@ type IPeerSet interface {
//-----------------------------------------------------------------------------
var (
maxPeersPerIPRange = [4]int{11, 7, 5, 3} // ...
)
// PeerSet is a special structure for keeping a table of peers.
// Iteration over the peers is super fast and thread-safe.
// We also track how many peers per ip range and avoid too many
type PeerSet struct {
mtx sync.Mutex
lookup map[string]*peerSetItem
list []*Peer
mtx sync.Mutex
lookup map[string]*peerSetItem
list []*Peer
connectedIPs *nestedCounter
}
type peerSetItem struct {
@ -29,24 +37,33 @@ type peerSetItem struct {
func NewPeerSet() *PeerSet {
return &PeerSet{
lookup: make(map[string]*peerSetItem),
list: make([]*Peer, 0, 256),
lookup: make(map[string]*peerSetItem),
list: make([]*Peer, 0, 256),
connectedIPs: NewNestedCounter(),
}
}
// Returns false if peer with key (uuid) is already in set.
func (ps *PeerSet) Add(peer *Peer) bool {
// Returns false if peer with key (uuid) is already in set
// or if we have too many peers from the peer's ip range
func (ps *PeerSet) Add(peer *Peer) error {
ps.mtx.Lock()
defer ps.mtx.Unlock()
if ps.lookup[peer.Key] != nil {
return false
return ErrSwitchDuplicatePeer
}
// ensure we havent maxed out connections for the peer's ip range yet
// and update the ip range counters
if !ps.updateIPRangeCounts(peer.Host) {
return ErrSwitchMaxPeersPerIPRange
}
index := len(ps.list)
// Appending is safe even with other goroutines
// iterating over the ps.list slice.
ps.list = append(ps.list, peer)
ps.lookup[peer.Key] = &peerSetItem{peer, index}
return true
return nil
}
func (ps *PeerSet) Has(peerKey string) bool {
@ -107,3 +124,73 @@ func (ps *PeerSet) List() []*Peer {
defer ps.mtx.Unlock()
return ps.list
}
//-----------------------------------------------------------------------------
// track the number of ips we're connected to for each ip address range
// forms an ip address hierarchy tree with counts
// the struct itself is not thread safe and should always only be accessed with the ps.mtx locked
type nestedCounter struct {
count int
children map[string]*nestedCounter
}
func NewNestedCounter() *nestedCounter {
nc := new(nestedCounter)
nc.children = make(map[string]*nestedCounter)
return nc
}
// Check if we have too many ips in the ip range of the incoming connection
// Thread safe
func (ps *PeerSet) HasMaxForIPRange(conn net.Conn) (ok bool) {
ps.mtx.Lock()
defer ps.mtx.Unlock()
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
spl := strings.Split(ip, ".")
c := ps.connectedIPs
for i, ipByte := range spl {
if c, ok = c.children[ipByte]; !ok {
return false
}
if c.count == maxPeersPerIPRange[i] {
return true
}
}
return false
}
// Update counts for this address' ip range
// Returns false if we already have enough connections
// Not thread safe (only called by ps.Add())
func (ps *PeerSet) updateIPRangeCounts(address string) bool {
spl := strings.Split(address, ".")
c := ps.connectedIPs
return updateNestedCountRecursive(c, spl, 0)
}
// recursively descend the ip hierarchy, checking if we have
// max peers for each range and updating if not
func updateNestedCountRecursive(c *nestedCounter, ipBytes []string, index int) bool {
if index == len(ipBytes) {
return true
}
ipByte := ipBytes[index]
if c2, ok := c.children[ipByte]; !ok {
c2 = NewNestedCounter()
c.children[ipByte] = c2
c = c2
} else {
c = c2
if c.count == maxPeersPerIPRange[index] {
return false
}
}
if !updateNestedCountRecursive(c, ipBytes, index+1) {
return false
}
c.count += 1
return true
}