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

[refer #393] Support periodic random connection eviction #394

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
19 changes: 16 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,28 @@ 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please comment non-trivial logic like this

group
}

case class RandomPeerExcluding(excludedPeers: Seq[PeerInfo]) extends GetPeers[Option[PeerInfo]] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do a ScalaDoc describing implemented strategy details


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