以太坊是如何解决日蚀攻击(eclipse attack)的

1,什么是日蚀攻击 首先简单介绍一下日蚀攻击         以太坊的节点发现机制基于Kademlia,但其目的却不同,Kademlia旨在成为在分布式对等...

本文原创,首发于区块链兄弟,转载请注明出处:https://www.blockchainbrother.com/article/314911


1,什么是日蚀攻击

首先简单介绍一下日蚀攻击

        以太坊的节点发现机制基于Kademlia,但其目的却不同,Kademlia旨在成为在分布式对等网络中存储和查找内容的手段,而以太坊仅用于发现新的节点。由于以太坊的节点是由其公钥表示的,并且不受IP限制,因此在一个机器上可以同时存在很多节点。攻击者在很少的服务器上制作出很多的节点,并积极的ping受害者的服务器。通过Kademlia协议,攻击者的节点信息将存储并填充在受害者节点列表中。下一步就是让受害者重启机器,手段包括断电、ddos攻击等等。重启后,攻击者再不停的ping被害者的节点以建立tcp连接,一旦被害者所有的tcp连接都是攻击者制造的,那么就达到了把被害者与正常的网络隔离的目的,当然最大的目的应该还是为了双花。有一篇论文是专门介绍日蚀攻击的,大家可以找来看看。

网上有很多关于日蚀攻击的详细介绍,在这里不做赘述。

2,以太坊是如何防止日蚀攻击的

在刚才提到的论文中,提到了以太坊的geth1.8.0解决了日蚀攻击,于是作者拿1.8.0和1.7.3做对比,理清了以太坊解决这个问题的做法。

直接看代码。

以太坊启动时加载p2p网络的流程如下,

cmd/geth/main.go init方法-> geth -> startNode() -> utils.StartNode() ->  stack.Start() ->  running.Start()

这个running.Start()调用的即是p2p/server.go中的Start()方法,看看这个方法做了什么:

// Start starts running the server.
// Servers can not be re-used after stopping.
func (srv *Server) Start() (err error) {
......
srv.loopWG.Add(1)
go srv.run(dialer)
srv.running = true
return nil
}

这篇文章主要关注解决日蚀攻击相关代码,其他的不做介绍。

上面的go srv.run(dialer)连接池管理协程,负责维护TCP连接的列表,监听各种信号,处理peer的增删改

func (srv *Server) run(dialstate dialer) {
...

running:
for {
scheduleTasks()

select {
...
case c := <-srv.posthandshake:
// A connection has passed the encryption handshake so
// the remote identity is known (but hasn't been verified yet).
if trusted[c.id] {
// Ensure that the trusted flag is set before checking against MaxPeers.
c.flags |= trustedConn
}
// TODO: track in-progress inbound node IDs (pre-Peer) to avoid dialing them.
select {
case c.cont <- srv.encHandshakeChecks(peers, inboundCount, c):
case <-srv.quit:
break running
}
case c := <-srv.addpeer:
// At this point the connection is past the protocol handshake.
// Its capabilities are known and the remote identity is verified.  
err := srv.protoHandshakeChecks(peers, inboundCount, c)
if err == nil {
// The handshakes are done and it passed all checks.
p := newPeer(c, srv.Protocols)
// If message events are enabled, pass the peerFeed
// to the peer
if srv.EnableMsgEvents {
p.events = &srv.peerFeed
}
name := truncateName(c.name)
srv.log.Debug("Adding p2p peer", "name", name, "addr", c.fd.RemoteAddr(), "peers", len(peers)+1)
go srv.runPeer(p)
peers[c.id] = p
if p.Inbound() {
inboundCount++
}
}
...
case pd := <-srv.delpeer:
// A peer disconnected.
d := common.PrettyDuration(mclock.Now() - pd.created)
pd.log.Debug("Removing p2p peer", "duration", d, "peers", len(peers)-1, "req", pd.requested, "err", pd.err)
delete(peers, pd.ID())
if pd.Inbound() {
inboundCount--
}
}
}
...
}

注意加粗的代码,有一个针对inboundCount的操作,当有posthandshake、addpeer消息的时候,会先去check,如果add或del了一个peer,则有对应的inboundCount++或者inboundCount--。看看到底check了什么:

protoHandshakeChecks最终也是调用encHandshakeChecks:

func (srv *Server) encHandshakeChecks(peers map[discover.NodeID]*Peer, inboundCount int, c *conn) error {
switch {
case !c.is(trustedConn|staticDialedConn) && len(peers) >= srv.MaxPeers:
return DiscTooManyPeers
case !c.is(trustedConn) && c.is(inboundConn) && inboundCount >= srv.maxInboundConns():
return DiscTooManyPeers

case peers[c.id] != nil:
return DiscAlreadyConnected
case c.id == srv.Self().ID:
return DiscSelf
default:
return nil
}
}

inboundConn表示连接类型为主动连接过来。

看加粗的这段逻辑:如果该连接是信任的,且是主动连接过来的,且主动连接过来的节点数量大于srv.maxInboundConns()时,则拒绝此连接。

可以看出来,以太坊是通过限制主动连接过来的数量来阻止日蚀攻击的。我们顺便看下这个数量是多少:

func (srv *Server) maxInboundConns() int {
return srv.MaxPeers - srv.maxDialedConns()
}

func (srv *Server) maxDialedConns() int {
if srv.NoDiscovery || srv.NoDial {
return 0
}
r := srv.DialRatio
if r == 0 {
r = defaultDialRatio
}
return srv.MaxPeers / r
}

MaxPeers默认是25,defaultDialRatio表示能够接受主动连接的比例,默认是3,所以最多允许传入的tcp连接数量就是25/3 = 8个


  • 发表于 2019-08-09 17:36
  • 阅读 ( 113 )
  • 分类:以太坊

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
不写代码的码农
曹欢

区块链开发

2 篇文章

作家榜 »

  1. 社区运营-小以 623 文章
  2. 社区运营-小链 243 文章
  3. 涂晶 82 文章
  4. 于中阳Mercina-zy 79 文章
  5. 李晓琼 44 文章
  6. 兄弟连区块链培训 42 文章
  7. 吴寿鹤 38 文章
  8. John-smith 25 文章