Skip to content
This repository was archived by the owner on Apr 13, 2022. It is now read-only.

Commit 49480f0

Browse files
committed
[refer #393] Support periodic random connection eviction
[refer #394] Try to choose candidates from different groups Signed-off-by: Konstantin Knizhnik <knizhnik@garret.ru>
1 parent 30f3bea commit 49480f0

File tree

4 files changed

+43
-6
lines changed

4 files changed

+43
-6
lines changed

src/main/resources/reference.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ scorex {
183183
# Max penalty score peer can accumulate before being banned
184184
penaltyScoreThreshold = 100
185185

186+
# interval of evicting random peer to avoid eclipsing
187+
peerEvictInterval = 1h
186188
}
187189

188190
ntp {

src/main/scala/scorex/core/network/NetworkController.scala

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package scorex.core.network
22

33
import java.net._
4-
54
import akka.actor._
65
import akka.io.Tcp._
76
import akka.io.{IO, Tcp}
@@ -21,7 +20,7 @@ import scorex.util.ScorexLogging
2120
import scala.concurrent.ExecutionContext
2221
import scala.concurrent.duration._
2322
import scala.language.{existentials, postfixOps}
24-
import scala.util.Try
23+
import scala.util.{Random, Try}
2524

2625
/**
2726
* Control all network interaction
@@ -89,6 +88,7 @@ class NetworkController(settings: NetworkSettings,
8988
log.info("Successfully bound to the port " + settings.bindAddress.getPort)
9089
scheduleConnectionToPeer()
9190
scheduleDroppingDeadConnections()
91+
scheduleEvictRandomConnections()
9292

9393
case CommandFailed(_: Bind) =>
9494
log.error("Network port " + settings.bindAddress.getPort + " already in use!")
@@ -231,6 +231,23 @@ class NetworkController(settings: NetworkSettings,
231231
}
232232

233233
/**
234+
* Schedule a periodic eviction of random connection.
235+
* It is needed to prevent eclipsing (https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-heilman.pdf)
236+
*/
237+
private def scheduleEvictRandomConnections(): Unit = {
238+
context.system.scheduler.scheduleWithFixedDelay(settings.peerEvictInterval, settings.peerEvictInterval) {
239+
() =>
240+
val connectedPeers = connections.values.filter(_.peerInfo.nonEmpty).toSeq
241+
if (!connectedPeers.isEmpty) {
242+
val victim = Random.nextInt(connectedPeers.size)
243+
val cp = connectedPeers(victim)
244+
log.info(s"Evict connection to ${cp.peerInfo}")
245+
cp.handlerRef ! CloseConnection
246+
}
247+
}
248+
}
249+
250+
/**
234251
* Schedule a periodic dropping of connections which seem to be inactive
235252
*/
236253
private def scheduleDroppingDeadConnections(): Unit = {

src/main/scala/scorex/core/network/peer/PeerManager.scala

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,32 @@ object PeerManager {
149149
sc: ScorexContext): Map[InetSocketAddress, PeerInfo] = knownPeers
150150
}
151151

152+
// Extract /16 IPv4 prefix (IP group)
153+
private def getIpGroup(addr : InetSocketAddress) : Int = {
154+
val ip = addr.getAddress.getAddress
155+
val group = ((ip(0) & 0xFF) << 8) | (ip(1) & 0xFF)
156+
group
157+
}
158+
159+
/*
160+
* To complicate eclipse attacks, we want to establish connections
161+
* with host belonging to different IP groups (/16 IPv4 prefix).
162+
*/
152163
case class RandomPeerExcluding(excludedPeers: Seq[PeerInfo]) extends GetPeers[Option[PeerInfo]] {
153164

154165
override def choose(knownPeers: Map[InetSocketAddress, PeerInfo],
155166
blacklistedPeers: Seq[InetAddress],
156167
sc: ScorexContext): Option[PeerInfo] = {
157-
val candidates = knownPeers.values.filterNot { p =>
158-
excludedPeers.exists(_.peerSpec.address == p.peerSpec.address) &&
159-
blacklistedPeers.exists(addr => p.peerSpec.address.map(_.getAddress).contains(addr))
168+
// First of all try to establish connections to the hosts from different IP group
169+
// It will complicate eclipse attacks
170+
val excludedGroups = excludedPeers.flatMap(_.peerSpec.address).map(getIpGroup(_)).toSet
171+
val allCandidates = knownPeers.values.filterNot { p =>
172+
excludedPeers.exists(_.peerSpec.address == p.peerSpec.address) ||
173+
blacklistedPeers.exists(addr => p.peerSpec.address.map(_.getAddress).contains(addr))
160174
}.toSeq
175+
val preferredCandidates = allCandidates.filterNot(_.peerSpec.address.fold(true)(addr => excludedGroups.contains(getIpGroup(addr))))
176+
// If it is not possible to connect to the node from different IP group, then try to connect somewhere
177+
val candidates = if (preferredCandidates.nonEmpty) preferredCandidates else allCandidates
161178
if (candidates.nonEmpty) Some(candidates(Random.nextInt(candidates.size)))
162179
else None
163180
}

src/main/scala/scorex/core/settings/Settings.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ case class NetworkSettings(nodeName: String,
5050
maxPeerSpecObjects: Int,
5151
temporalBanDuration: FiniteDuration,
5252
penaltySafeInterval: FiniteDuration,
53-
penaltyScoreThreshold: Int)
53+
penaltyScoreThreshold: Int,
54+
peerEvictInterval: FiniteDuration)
5455

5556
case class ScorexSettings(dataDir: File,
5657
logDir: File,

0 commit comments

Comments
 (0)