aboutsummaryrefslogtreecommitdiffstats
path: root/p2p/discover/node.go
diff options
context:
space:
mode:
authorFelix Lange <fjl@twurst.com>2015-03-25 23:45:53 +0800
committerFelix Lange <fjl@twurst.com>2015-04-01 23:00:12 +0800
commitde7af720d6bb10b93d716fb0c6cf3ee0e51dc71a (patch)
treeb4ece2cb300a989ec349ff30ea84e8906131fdaf /p2p/discover/node.go
parent92928309b2f0e274a6103b21a650eb07e78f88ea (diff)
downloaddexon-de7af720d6bb10b93d716fb0c6cf3ee0e51dc71a.tar
dexon-de7af720d6bb10b93d716fb0c6cf3ee0e51dc71a.tar.gz
dexon-de7af720d6bb10b93d716fb0c6cf3ee0e51dc71a.tar.bz2
dexon-de7af720d6bb10b93d716fb0c6cf3ee0e51dc71a.tar.lz
dexon-de7af720d6bb10b93d716fb0c6cf3ee0e51dc71a.tar.xz
dexon-de7af720d6bb10b93d716fb0c6cf3ee0e51dc71a.tar.zst
dexon-de7af720d6bb10b93d716fb0c6cf3ee0e51dc71a.zip
p2p/discover: implement node bonding
This a fix for an attack vector where the discovery protocol could be used to amplify traffic in a DDOS attack. A malicious actor would send a findnode request with the IP address and UDP port of the target as the source address. The recipient of the findnode packet would then send a neighbors packet (which is 16x the size of findnode) to the victim. Our solution is to require a 'bond' with the sender of findnode. If no bond exists, the findnode packet is not processed. A bond between nodes α and β is created when α replies to a ping from β. This (initial) version of the bonding implementation might still be vulnerable against replay attacks during the expiration time window. We will add stricter source address validation later.
Diffstat (limited to 'p2p/discover/node.go')
-rw-r--r--p2p/discover/node.go43
1 files changed, 41 insertions, 2 deletions
diff --git a/p2p/discover/node.go b/p2p/discover/node.go
index e1130e0b5..99cb549a5 100644
--- a/p2p/discover/node.go
+++ b/p2p/discover/node.go
@@ -13,6 +13,8 @@ import (
"net/url"
"strconv"
"strings"
+ "sync"
+ "sync/atomic"
"time"
"github.com/ethereum/go-ethereum/crypto"
@@ -30,7 +32,8 @@ type Node struct {
DiscPort int // UDP listening port for discovery protocol
TCPPort int // TCP listening port for RLPx
- active time.Time
+ // this must be set/read using atomic load and store.
+ activeStamp int64
}
func newNode(id NodeID, addr *net.UDPAddr) *Node {
@@ -39,7 +42,6 @@ func newNode(id NodeID, addr *net.UDPAddr) *Node {
IP: addr.IP,
DiscPort: addr.Port,
TCPPort: addr.Port,
- active: time.Now(),
}
}
@@ -48,6 +50,20 @@ func (n *Node) isValid() bool {
return !n.IP.IsMulticast() && !n.IP.IsUnspecified() && n.TCPPort != 0 && n.DiscPort != 0
}
+func (n *Node) bumpActive() {
+ stamp := time.Now().Unix()
+ atomic.StoreInt64(&n.activeStamp, stamp)
+}
+
+func (n *Node) active() time.Time {
+ stamp := atomic.LoadInt64(&n.activeStamp)
+ return time.Unix(stamp, 0)
+}
+
+func (n *Node) addr() *net.UDPAddr {
+ return &net.UDPAddr{IP: n.IP, Port: n.DiscPort}
+}
+
// The string representation of a Node is a URL.
// Please see ParseNode for a description of the format.
func (n *Node) String() string {
@@ -304,3 +320,26 @@ func randomID(a NodeID, n int) (b NodeID) {
}
return b
}
+
+// nodeDB stores all nodes we know about.
+type nodeDB struct {
+ mu sync.RWMutex
+ byID map[NodeID]*Node
+}
+
+func (db *nodeDB) get(id NodeID) *Node {
+ db.mu.RLock()
+ defer db.mu.RUnlock()
+ return db.byID[id]
+}
+
+func (db *nodeDB) add(id NodeID, addr *net.UDPAddr, tcpPort uint16) *Node {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+ if db.byID == nil {
+ db.byID = make(map[NodeID]*Node)
+ }
+ n := &Node{ID: id, IP: addr.IP, DiscPort: addr.Port, TCPPort: int(tcpPort)}
+ db.byID[n.ID] = n
+ return n
+}