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

Commit

Permalink
[refer #393] Support periodic random connection eviction
Browse files Browse the repository at this point in the history
[refer #394] Try to choose candidates from different groups

Signed-off-by: Konstantin Knizhnik <[email protected]>
  • Loading branch information
knizhnik committed Jan 22, 2021
1 parent 30f3bea commit 49480f0
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ scorex {
# Max penalty score peer can accumulate before being banned
penaltyScoreThreshold = 100

# interval of evicting random peer to avoid eclipsing
peerEvictInterval = 1h
}

ntp {
Expand Down
21 changes: 19 additions & 2 deletions src/main/scala/scorex/core/network/NetworkController.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package scorex.core.network

import java.net._

import akka.actor._
import akka.io.Tcp._
import akka.io.{IO, Tcp}
Expand All @@ -21,7 +20,7 @@ import scorex.util.ScorexLogging
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import scala.language.{existentials, postfixOps}
import scala.util.Try
import scala.util.{Random, Try}

/**
* Control all network interaction
Expand Down Expand Up @@ -89,6 +88,7 @@ class NetworkController(settings: NetworkSettings,
log.info("Successfully bound to the port " + settings.bindAddress.getPort)
scheduleConnectionToPeer()
scheduleDroppingDeadConnections()
scheduleEvictRandomConnections()

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

/**
* Schedule a periodic eviction of random connection.
* It is needed to prevent eclipsing (https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-heilman.pdf)
*/
private def scheduleEvictRandomConnections(): Unit = {
context.system.scheduler.scheduleWithFixedDelay(settings.peerEvictInterval, settings.peerEvictInterval) {
() =>
val connectedPeers = connections.values.filter(_.peerInfo.nonEmpty).toSeq
if (!connectedPeers.isEmpty) {
val victim = Random.nextInt(connectedPeers.size)
val cp = connectedPeers(victim)
log.info(s"Evict connection to ${cp.peerInfo}")
cp.handlerRef ! CloseConnection
}
}
}

/**
* Schedule a periodic dropping of connections which seem to be inactive
*/
private def scheduleDroppingDeadConnections(): Unit = {
Expand Down
23 changes: 20 additions & 3 deletions src/main/scala/scorex/core/network/peer/PeerManager.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,32 @@ object PeerManager {
sc: ScorexContext): Map[InetSocketAddress, PeerInfo] = knownPeers
}

// Extract /16 IPv4 prefix (IP group)
private def getIpGroup(addr : InetSocketAddress) : Int = {
val ip = addr.getAddress.getAddress
val group = ((ip(0) & 0xFF) << 8) | (ip(1) & 0xFF)
group
}

/*
* To complicate eclipse attacks, we want to establish connections
* with host belonging to different IP groups (/16 IPv4 prefix).
*/
case class RandomPeerExcluding(excludedPeers: Seq[PeerInfo]) extends GetPeers[Option[PeerInfo]] {

override def choose(knownPeers: Map[InetSocketAddress, PeerInfo],
blacklistedPeers: Seq[InetAddress],
sc: ScorexContext): Option[PeerInfo] = {
val candidates = knownPeers.values.filterNot { p =>
excludedPeers.exists(_.peerSpec.address == p.peerSpec.address) &&
blacklistedPeers.exists(addr => p.peerSpec.address.map(_.getAddress).contains(addr))
// First of all try to establish connections to the hosts from different IP group
// It will complicate eclipse attacks
val excludedGroups = excludedPeers.flatMap(_.peerSpec.address).map(getIpGroup(_)).toSet
val allCandidates = knownPeers.values.filterNot { p =>
excludedPeers.exists(_.peerSpec.address == p.peerSpec.address) ||
blacklistedPeers.exists(addr => p.peerSpec.address.map(_.getAddress).contains(addr))
}.toSeq
val preferredCandidates = allCandidates.filterNot(_.peerSpec.address.fold(true)(addr => excludedGroups.contains(getIpGroup(addr))))
// If it is not possible to connect to the node from different IP group, then try to connect somewhere
val candidates = if (preferredCandidates.nonEmpty) preferredCandidates else allCandidates
if (candidates.nonEmpty) Some(candidates(Random.nextInt(candidates.size)))
else None
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/scorex/core/settings/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ case class NetworkSettings(nodeName: String,
maxPeerSpecObjects: Int,
temporalBanDuration: FiniteDuration,
penaltySafeInterval: FiniteDuration,
penaltyScoreThreshold: Int)
penaltyScoreThreshold: Int,
peerEvictInterval: FiniteDuration)

case class ScorexSettings(dataDir: File,
logDir: File,
Expand Down

0 comments on commit 49480f0

Please sign in to comment.