diff options
author | Felix Lange <fjl@twurst.com> | 2016-11-22 01:11:54 +0800 |
---|---|---|
committer | Felix Lange <fjl@twurst.com> | 2016-11-23 05:21:18 +0800 |
commit | a98d1d67d6192df0bd57f608921a82cc508eee18 (patch) | |
tree | 49eb8ce8813e8f53cf7d949b2d7dd0ef575713e8 /p2p/discv5 | |
parent | ba2884f3431312c616e21f57deeb03a7c4374d57 (diff) | |
download | go-tangerine-a98d1d67d6192df0bd57f608921a82cc508eee18.tar go-tangerine-a98d1d67d6192df0bd57f608921a82cc508eee18.tar.gz go-tangerine-a98d1d67d6192df0bd57f608921a82cc508eee18.tar.bz2 go-tangerine-a98d1d67d6192df0bd57f608921a82cc508eee18.tar.lz go-tangerine-a98d1d67d6192df0bd57f608921a82cc508eee18.tar.xz go-tangerine-a98d1d67d6192df0bd57f608921a82cc508eee18.tar.zst go-tangerine-a98d1d67d6192df0bd57f608921a82cc508eee18.zip |
p2p/discover, p2p/discv5: prevent relay of invalid IPs and low ports
The discovery DHT contains a number of hosts with LAN and loopback IPs.
These get relayed because some implementations do not perform any checks
on the IP.
go-ethereum already prevented relay in most cases because it verifies
that the host actually exists before adding it to the local table. But
this verification causes other issues. We have received several reports
where people's VPSs got shut down by hosting providers because sending
packets to random LAN hosts is indistinguishable from a slow port scan.
The new check prevents sending random packets to LAN by discarding LAN
IPs sent by Internet hosts (and loopback IPs from LAN and Internet
hosts). The new check also blacklists almost all currently registered
special-purpose networks assigned by IANA to avoid inciting random
responses from services in the LAN.
As another precaution against abuse of the DHT, ports below 1024 are now
considered invalid.
Diffstat (limited to 'p2p/discv5')
-rw-r--r-- | p2p/discv5/net.go | 19 | ||||
-rw-r--r-- | p2p/discv5/net_test.go | 27 | ||||
-rw-r--r-- | p2p/discv5/udp.go | 10 |
3 files changed, 35 insertions, 21 deletions
diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go index 7ad6f1e5b..b7e4a0bee 100644 --- a/p2p/discv5/net.go +++ b/p2p/discv5/net.go @@ -45,6 +45,7 @@ const ( bucketRefreshInterval = 1 * time.Minute seedCount = 30 seedMaxAge = 5 * 24 * time.Hour + lowPort = 1024 ) const testTopic = "foo" @@ -684,16 +685,19 @@ func (net *Network) internNodeFromDB(dbn *Node) *Node { return n } -func (net *Network) internNodeFromNeighbours(rn rpcNode) (n *Node, err error) { +func (net *Network) internNodeFromNeighbours(sender *net.UDPAddr, rn rpcNode) (n *Node, err error) { if rn.ID == net.tab.self.ID { return nil, errors.New("is self") } + if rn.UDP <= lowPort { + return nil, errors.New("low port") + } n = net.nodes[rn.ID] if n == nil { // We haven't seen this node before. - n, err = nodeFromRPC(rn) - n.state = unknown + n, err = nodeFromRPC(sender, rn) if err == nil { + n.state = unknown net.nodes[n.ID] = n } return n, err @@ -1095,7 +1099,7 @@ func (net *Network) handleQueryEvent(n *Node, ev nodeEvent, pkt *ingressPacket) net.conn.sendNeighbours(n, results) return n.state, nil case neighborsPacket: - err := net.handleNeighboursPacket(n, pkt.data.(*neighbors)) + err := net.handleNeighboursPacket(n, pkt) return n.state, err case neighboursTimeout: if n.pendingNeighbours != nil { @@ -1182,17 +1186,18 @@ func rlpHash(x interface{}) (h common.Hash) { return h } -func (net *Network) handleNeighboursPacket(n *Node, req *neighbors) error { +func (net *Network) handleNeighboursPacket(n *Node, pkt *ingressPacket) error { if n.pendingNeighbours == nil { return errNoQuery } net.abortTimedEvent(n, neighboursTimeout) + req := pkt.data.(*neighbors) nodes := make([]*Node, len(req.Nodes)) for i, rn := range req.Nodes { - nn, err := net.internNodeFromNeighbours(rn) + nn, err := net.internNodeFromNeighbours(pkt.remoteAddr, rn) if err != nil { - glog.V(logger.Debug).Infof("invalid neighbour from %x: %v", n.ID[:8], err) + glog.V(logger.Debug).Infof("invalid neighbour (%v) from %x@%v: %v", rn.IP, n.ID[:8], pkt.remoteAddr, err) continue } nodes[i] = nn diff --git a/p2p/discv5/net_test.go b/p2p/discv5/net_test.go index 422daa33b..c8d2558f1 100644 --- a/p2p/discv5/net_test.go +++ b/p2p/discv5/net_test.go @@ -40,7 +40,7 @@ func TestNetwork_Lookup(t *testing.T) { // t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) // } // seed table with initial node (otherwise lookup will terminate immediately) - seeds := []*Node{NewNode(lookupTestnet.dists[256][0], net.IP{}, 256, 999)} + seeds := []*Node{NewNode(lookupTestnet.dists[256][0], net.IP{10, 0, 2, 99}, lowPort+256, 999)} if err := network.SetFallbackNodes(seeds); err != nil { t.Fatal(err) } @@ -272,13 +272,13 @@ func (tn *preminedTestnet) sendFindnode(to *Node, target NodeID) { func (tn *preminedTestnet) sendFindnodeHash(to *Node, target common.Hash) { // current log distance is encoded in port number // fmt.Println("findnode query at dist", toaddr.Port) - if to.UDP == 0 { - panic("query to node at distance 0") + if to.UDP <= lowPort { + panic("query to node at or below distance 0") } next := to.UDP - 1 var result []rpcNode - for i, id := range tn.dists[to.UDP] { - result = append(result, nodeToRPC(NewNode(id, net.ParseIP("127.0.0.1"), next, uint16(i)+1))) + for i, id := range tn.dists[to.UDP-lowPort] { + result = append(result, nodeToRPC(NewNode(id, net.ParseIP("10.0.2.99"), next, uint16(i)+1+lowPort))) } injectResponse(tn.net, to, neighborsPacket, &neighbors{Nodes: result}) } @@ -296,14 +296,14 @@ func (tn *preminedTestnet) send(to *Node, ptype nodeEvent, data interface{}) (ha // ignored case findnodeHashPacket: // current log distance is encoded in port number - // fmt.Println("findnode query at dist", toaddr.Port) - if to.UDP == 0 { - panic("query to node at distance 0") + // fmt.Println("findnode query at dist", toaddr.Port-lowPort) + if to.UDP <= lowPort { + panic("query to node at or below distance 0") } next := to.UDP - 1 var result []rpcNode - for i, id := range tn.dists[to.UDP] { - result = append(result, nodeToRPC(NewNode(id, net.ParseIP("127.0.0.1"), next, uint16(i)+1))) + for i, id := range tn.dists[to.UDP-lowPort] { + result = append(result, nodeToRPC(NewNode(id, net.ParseIP("10.0.2.99"), next, uint16(i)+1+lowPort))) } injectResponse(tn.net, to, neighborsPacket, &neighbors{Nodes: result}) default: @@ -328,8 +328,11 @@ func (tn *preminedTestnet) sendTopicRegister(to *Node, topics []Topic, idx int, panic("sendTopicRegister called") } -func (*preminedTestnet) Close() {} -func (*preminedTestnet) localAddr() *net.UDPAddr { return new(net.UDPAddr) } +func (*preminedTestnet) Close() {} + +func (*preminedTestnet) localAddr() *net.UDPAddr { + return &net.UDPAddr{IP: net.ParseIP("10.0.1.1"), Port: 40000} +} // mine generates a testnet struct literal with nodes at // various distances to the given target. diff --git a/p2p/discv5/udp.go b/p2p/discv5/udp.go index f86fb4d81..396f438a2 100644 --- a/p2p/discv5/udp.go +++ b/p2p/discv5/udp.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/rlp" ) @@ -198,8 +199,10 @@ func (e1 rpcEndpoint) equal(e2 rpcEndpoint) bool { return e1.UDP == e2.UDP && e1.TCP == e2.TCP && bytes.Equal(e1.IP, e2.IP) } -func nodeFromRPC(rn rpcNode) (*Node, error) { - // TODO: don't accept localhost, LAN addresses from internet hosts +func nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) { + if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil { + return nil, err + } n := NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP) err := n.validateComplete() return n, err @@ -327,6 +330,9 @@ func (t *udp) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) return } for i, result := range nodes { + if netutil.CheckRelayIP(remote.IP, result.IP) != nil { + continue + } p.Nodes = append(p.Nodes, nodeToRPC(result)) if len(p.Nodes) == maxTopicNodes || i == len(nodes)-1 { t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p) |