Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Tor onion services #736

Merged
merged 52 commits into from
Feb 8, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
6deb890
[WIP] Tor hidden service
rorp Oct 17, 2018
1f299ee
wire support for Tor addresses
rorp Oct 19, 2018
fe58f45
Merge branch 'master' into tor_hidden_service
rorp Oct 19, 2018
af401f7
cleanup
rorp Oct 21, 2018
1daca47
SOCKS5
rorp Nov 4, 2018
997db3b
Merge remote-tracking branch 'upstream/master' into tor_hidden_service
rorp Nov 4, 2018
c35a646
docs
rorp Nov 4, 2018
b82dacd
minor changes
rorp Nov 4, 2018
a3663e8
cleanup
rorp Nov 4, 2018
29bbedd
some more changes
rorp Nov 5, 2018
ed5d59d
stream isolation
rorp Nov 18, 2018
a4e3b9d
Merge branch 'master' into tor_hidden_service
rorp Nov 18, 2018
0409347
Merge branch 'master' into tor_hidden_service
rorp Nov 26, 2018
dde1652
Merge branch 'master' into tor_hidden_service
rorp Dec 13, 2018
4d8f37b
Update TOR.md
rorp Dec 13, 2018
f402d91
Merge branch 'master' into tor_hidden_service
rorp Dec 19, 2018
d5b2e9b
more flexible SOCKS5 configuration
rorp Dec 30, 2018
5bda47c
more flexible config
rorp Jan 6, 2019
f3fed21
Merge branch 'master' into tor_hidden_service
rorp Jan 6, 2019
e74a181
Merge branch 'tor_hidden_service' of github.com:rorp/eclair into tor_…
rorp Jan 6, 2019
354150b
Merge branch 'master' into tor_hidden_service
rorp Jan 11, 2019
7c96f0b
fix unit tests
rorp Jan 11, 2019
86ecccd
Merge branch 'master' into tor_hidden_service
rorp Jan 28, 2019
ced223c
fix build
rorp Jan 28, 2019
a20e9f1
Merge branch 'master' into tor_hidden_service
pm47 Jan 31, 2019
cd7ae5d
better doc for virtualPort/targetPorts
pm47 Jan 31, 2019
8893c1e
fixed typo in setPermissions
pm47 Jan 31, 2019
a3d5e48
added link to the tor control protocol spec
pm47 Jan 31, 2019
6a95131
ignore set Permissions errors on windows
pm47 Jan 31, 2019
0a3329a
proper error management
pm47 Jan 31, 2019
fa3c6a7
added windows instructions to TOR.md
pm47 Jan 31, 2019
3cf82a6
renamed file tor_pk->tor.dat for consistency
pm47 Jan 31, 2019
c57eba1
moved constants to companion object
pm47 Jan 31, 2019
bcc3d53
only support v3 hidden service
pm47 Feb 1, 2019
5e8d573
replacing auth SAFECOOKIE->PASSWORD
pm47 Feb 1, 2019
f32334e
use java.nio.Path instead of String
pm47 Feb 1, 2019
5eba5db
put socks proxy parameters in NodeParams
pm47 Feb 1, 2019
0f8d898
rework of socks5 client
pm47 Feb 2, 2019
9602ea4
using TestUtils.BUILD_DIRECTORY everywhere
pm47 Feb 1, 2019
ef7bea2
add back v2 support and cookie auth
pm47 Feb 5, 2019
1fb003b
refactored tor2/tor3 data types and codecs
pm47 Feb 5, 2019
769c318
added missing copyrights
pm47 Feb 5, 2019
a9b5a6e
Merge branch 'master' into tor_hidden_service
pm47 Feb 5, 2019
0592cde
updated tor documentation
pm47 Feb 6, 2019
d2e2964
reworked NodeAddress constructor
pm47 Feb 6, 2019
67baabe
fixed getinfo public addresses
pm47 Feb 6, 2019
c406f8c
better client logs
pm47 Feb 7, 2019
d186033
tor.stream-isolation->socks5.randomize-credentials
pm47 Feb 7, 2019
0c9d3b4
put back InetSocketAddress in Client
pm47 Feb 7, 2019
69b813b
added test to socks5 proxy
pm47 Feb 7, 2019
3567622
reverted changes made to Authenticator
pm47 Feb 7, 2019
517d9ec
typo
rorp Feb 7, 2019
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
19 changes: 8 additions & 11 deletions eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ class Setup(datadir: File,
logger.info(s"hello!")
logger.info(s"version=${getClass.getPackage.getImplementationVersion} commit=${getClass.getPackage.getSpecificationVersion}")
logger.info(s"datadir=${datadir.getCanonicalPath}")

logger.info(s"initializing secure random generator")
// this will force the secure random instance to initialize itself right now, making sure it doesn't hang later (see comment in package.scala)
secureRandom.nextInt()

val config = NodeParams.loadConfiguration(datadir, overrideDefaults)
val seed = seed_opt.getOrElse(NodeParams.getSeed(datadir))
Expand All @@ -73,12 +75,11 @@ class Setup(datadir: File,
implicit val formats = org.json4s.DefaultFormats
implicit val ec = ExecutionContext.Implicits.global

logger.info(s"initializing secure random generator")
// this will force the secure random instance to initialize itself right now, making sure it doesn't hang later (see comment in package.scala)
secureRandom.nextInt()

val nodeParams = NodeParams.makeNodeParams(datadir, config, keyManager, initTor())

logger.info(s"nodeid=${nodeParams.nodeId} alias=${nodeParams.alias}")
logger.info(s"using chain=$chain chainHash=${nodeParams.chainHash}")

// always bind the server to localhost when using Tor
val serverBindingAddress = new InetSocketAddress(
if (config.getBoolean("tor.enabled")) "127.0.0.1" else config.getString("server.binding-ip"),
Expand All @@ -89,9 +90,6 @@ class Setup(datadir: File,
DBCompatChecker.checkNetworkDBCompatibility(nodeParams)
PortChecker.checkAvailable(serverBindingAddress)

logger.info(s"nodeid=${nodeParams.nodeId} alias=${nodeParams.alias}")
logger.info(s"using chain=$chain chainHash=${nodeParams.chainHash}")

val bitcoin = nodeParams.watcherType match {
case BITCOIND =>
val bitcoinClient = new BasicBitcoinJsonRPCClient(
Expand Down Expand Up @@ -201,7 +199,8 @@ class Setup(datadir: File,
relayer = system.actorOf(SimpleSupervisor.props(Relayer.props(nodeParams, register, paymentHandler), "relayer", SupervisorStrategy.Resume))
router = system.actorOf(SimpleSupervisor.props(Router.props(nodeParams, watcher), "router", SupervisorStrategy.Resume))
authenticator = system.actorOf(SimpleSupervisor.props(Authenticator.props(nodeParams), "authenticator", SupervisorStrategy.Resume))
switchboard = system.actorOf(SimpleSupervisor.props(Switchboard.props(nodeParams, authenticator, watcher, router, relayer, wallet), "switchboard", SupervisorStrategy.Resume))
socksProxy = if (config.getBoolean("tor.enabled")) Some(new InetSocketAddress(config.getString("tor.host"), config.getInt("tor.socks-port"))) else None
switchboard = system.actorOf(SimpleSupervisor.props(Switchboard.props(nodeParams, authenticator, watcher, router, relayer, wallet, socksProxy), "switchboard", SupervisorStrategy.Resume))
server = system.actorOf(SimpleSupervisor.props(Server.props(nodeParams, authenticator, serverBindingAddress, Some(tcpBound)), "server", SupervisorStrategy.Restart))
paymentInitiator = system.actorOf(SimpleSupervisor.props(PaymentInitiator.props(nodeParams.nodeId, router, register), "payment-initiator", SupervisorStrategy.Restart))

Expand Down Expand Up @@ -267,8 +266,6 @@ class Setup(datadir: File,

private def initTor(): Option[InetSocketAddress] = {
if (config.getBoolean("tor.enabled")) {
sys.props.put("socksProxyHost", config.getString("tor.host"))
sys.props.put("socksProxyPort", config.getInt("tor.socks-port").toString)
if (config.getString("tor.protocol") != "socks") {
val promiseTorAddress = Promise[OnionAddress]()
val protocolHandler = system.actorOf(TorProtocolHandler.props(
Expand Down
60 changes: 49 additions & 11 deletions eclair-core/src/main/scala/fr/acinq/eclair/io/Client.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,39 +23,77 @@ import akka.event.Logging.MDC
import akka.io.Tcp.SO.KeepAlive
import akka.io.{IO, Tcp}
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.{Logs, NodeParams}
import fr.acinq.eclair.io.Client.ConnectionFailed
import fr.acinq.eclair.tor.Socks5Connection
import fr.acinq.eclair.tor.Socks5Connection.{Socks5Connect, Socks5Connected}
import fr.acinq.eclair.{Logs, NodeParams}

import scala.concurrent.duration._

/**
* Created by PM on 27/10/2015.
*
*/
class Client(nodeParams: NodeParams, authenticator: ActorRef, address: InetSocketAddress, remoteNodeId: PublicKey, origin_opt: Option[ActorRef]) extends Actor with DiagnosticActorLogging {
class Client(nodeParams: NodeParams, authenticator: ActorRef, address: InetSocketAddress, remoteNodeId: PublicKey, origin_opt: Option[ActorRef], socksProxy: Option[InetSocketAddress]) extends Actor with DiagnosticActorLogging {

import Tcp._
import context.system

// we could connect directly here but this allows to take advantage of the automated mdc configuration on message reception
self ! 'connect

private var connection: ActorRef = _

def receive = {

case 'connect =>
log.info(s"connecting to pubkey=$remoteNodeId host=${address.getHostString} port=${address.getPort}")
IO(Tcp) ! Connect(address, timeout = Some(5 seconds), options = KeepAlive(true) :: Nil, pullMode = true)
val addressToConnect = socksProxy match {
case None =>
log.info(s"connecting to pubkey=$remoteNodeId host=${address.getHostString} port=${address.getPort}")
address
case Some(socksProxyAddress) =>
log.info(s"connecting to SOCKS5 proxy $socksProxyAddress")
socksProxyAddress
}
IO(Tcp) ! Connect(addressToConnect, timeout = Some(50 seconds), options = KeepAlive(true) :: Nil, pullMode = true)

case CommandFailed(_: Connect) =>
log.info(s"connection failed to $remoteNodeId@${address.getHostString}:${address.getPort}")
socksProxy match {
case None =>
log.info(s"connection failed to $remoteNodeId@${address.getHostString}:${address.getPort}")
case Some(socksProxyAddress) =>
log.info(s"connection failed to SOCKS5 proxy $socksProxyAddress")
}
origin_opt.map(_ ! Status.Failure(ConnectionFailed(address)))
context stop self

case CommandFailed(_: Socks5Connect) =>
log.info(s"connection failed to $remoteNodeId@${address.getHostString}:${address.getPort} via SOCKS5 ${socksProxy.map(_.toString).getOrElse("")}")
origin_opt.map(_ ! Status.Failure(ConnectionFailed(address)))
context stop self

case Connected(remote, _) =>
log.info(s"connected to pubkey=$remoteNodeId host=${remote.getHostString} port=${remote.getPort}")
val connection = sender
authenticator ! Authenticator.PendingAuth(connection, remoteNodeId_opt = Some(remoteNodeId), address = address, origin_opt = origin_opt)
context watch connection
context become connected(connection)
socksProxy match {
case None =>
connection = sender()
context watch connection
log.info(s"connected to pubkey=$remoteNodeId host=${remote.getHostString} port=${remote.getPort}")
authenticator ! Authenticator.PendingAuth(connection, remoteNodeId_opt = Some(remoteNodeId), address = address, origin_opt = origin_opt)
context become connected(connection)
case Some(_) =>
connection = context.actorOf(Socks5Connection.props(sender()))
context watch connection
connection ! Socks5Connect(address)
}

case Socks5Connected(_) =>
socksProxy match {
case Some(proxyAddress) =>
log.info(s"connected to pubkey=$remoteNodeId host=${address.getHostString} port=${address.getPort} via SOCKS5 proxy $proxyAddress")
authenticator ! Authenticator.PendingAuth(connection, remoteNodeId_opt = Some(remoteNodeId), address = address, origin_opt = origin_opt)
context become connected(connection)
case _ =>
}
}

def connected(connection: ActorRef): Receive = {
Expand All @@ -70,7 +108,7 @@ class Client(nodeParams: NodeParams, authenticator: ActorRef, address: InetSocke

object Client extends App {

def props(nodeParams: NodeParams, authenticator: ActorRef, address: InetSocketAddress, remoteNodeId: PublicKey, origin_opt: Option[ActorRef]): Props = Props(new Client(nodeParams, authenticator, address, remoteNodeId, origin_opt))
def props(nodeParams: NodeParams, authenticator: ActorRef, address: InetSocketAddress, remoteNodeId: PublicKey, origin_opt: Option[ActorRef], socksProxy: Option[InetSocketAddress]): Props = Props(new Client(nodeParams, authenticator, address, remoteNodeId, origin_opt, socksProxy))

case class ConnectionFailed(address: InetSocketAddress) extends RuntimeException(s"connection failed to $address")

Expand Down
8 changes: 4 additions & 4 deletions eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import scala.util.Random
/**
* Created by PM on 26/08/2016.
*/
class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: ActorRef, watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet) extends FSMDiagnosticActorLogging[Peer.State, Peer.Data] {
class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: ActorRef, watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet, socksProxy: Option[InetSocketAddress]) extends FSMDiagnosticActorLogging[Peer.State, Peer.Data] {

import Peer._

Expand All @@ -57,15 +57,15 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor
when(DISCONNECTED) {
case Event(Peer.Connect(NodeURI(_, address)), _) =>
// even if we are in a reconnection loop, we immediately process explicit connection requests
context.actorOf(Client.props(nodeParams, authenticator, new InetSocketAddress(address.getHost, address.getPort), remoteNodeId, origin_opt = Some(sender())))
context.actorOf(Client.props(nodeParams, authenticator, new InetSocketAddress(address.getHost, address.getPort), remoteNodeId, origin_opt = Some(sender()), socksProxy))
stay

case Event(Reconnect, d@DisconnectedData(address_opt, channels, attempts)) =>
address_opt match {
case None => stay // no-op (this peer didn't initiate the connection and doesn't have the ip of the counterparty)
case _ if channels.isEmpty => stay // no-op (no more channels with this peer)
case Some(address) =>
context.actorOf(Client.props(nodeParams, authenticator, address, remoteNodeId, origin_opt = None))
context.actorOf(Client.props(nodeParams, authenticator, address, remoteNodeId, origin_opt = None, socksProxy))
// exponential backoff retry with a finite max
setTimer(RECONNECT_TIMER, Reconnect, Math.min(10 + Math.pow(2, attempts), 60) seconds, repeat = false)
stay using d.copy(attempts = attempts + 1)
Expand Down Expand Up @@ -431,7 +431,7 @@ object Peer {

val IGNORE_NETWORK_ANNOUNCEMENTS_PERIOD = 5 minutes

def props(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: ActorRef, watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet) = Props(new Peer(nodeParams, remoteNodeId, authenticator, watcher, router, relayer, wallet: EclairWallet))
def props(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: ActorRef, watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet, socksProxy: Option[InetSocketAddress]) = Props(new Peer(nodeParams, remoteNodeId, authenticator, watcher, router, relayer, wallet, socksProxy))

// @formatter:off

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package fr.acinq.eclair.io

import java.net.InetSocketAddress
import java.net.{InetAddress, InetSocketAddress}

import akka.actor.{Actor, ActorLogging, ActorRef, OneForOneStrategy, Props, Status, SupervisorStrategy, Terminated}
import fr.acinq.bitcoin.Crypto.PublicKey
Expand All @@ -29,7 +29,7 @@ import fr.acinq.eclair.router.Rebroadcast
* Ties network connections to peers.
* Created by PM on 14/02/2017.
*/
class Switchboard(nodeParams: NodeParams, authenticator: ActorRef, watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet) extends Actor with ActorLogging {
class Switchboard(nodeParams: NodeParams, authenticator: ActorRef, watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet, socksProxy: Option[InetSocketAddress]) extends Actor with ActorLogging {

authenticator ! self

Expand Down Expand Up @@ -101,7 +101,7 @@ class Switchboard(nodeParams: NodeParams, authenticator: ActorRef, watcher: Acto
case Some(peer) => peer
case None =>
log.info(s"creating new peer current=${peers.size}")
val peer = context.actorOf(Peer.props(nodeParams, remoteNodeId, authenticator, watcher, router, relayer, wallet), name = s"peer-$remoteNodeId")
val peer = context.actorOf(Peer.props(nodeParams, remoteNodeId, authenticator, watcher, router, relayer, wallet, socksProxy), name = s"peer-$remoteNodeId")
peer ! Peer.Init(previousKnownAddress, offlineChannels)
context watch (peer)
peer
Expand All @@ -116,6 +116,6 @@ class Switchboard(nodeParams: NodeParams, authenticator: ActorRef, watcher: Acto

object Switchboard {

def props(nodeParams: NodeParams, authenticator: ActorRef, watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet) = Props(new Switchboard(nodeParams, authenticator, watcher, router, relayer, wallet))
def props(nodeParams: NodeParams, authenticator: ActorRef, watcher: ActorRef, router: ActorRef, relayer: ActorRef, wallet: EclairWallet, socksProxy: Option[InetSocketAddress]) = Props(new Switchboard(nodeParams, authenticator, watcher, router, relayer, wallet, socksProxy))

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package fr.acinq.eclair.tor

import java.net.InetSocketAddress

import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import akka.actor.{Actor, ActorLogging, ActorRef, Props, Terminated}
import akka.io.{IO, Tcp}
import akka.util.ByteString

Expand All @@ -29,6 +29,7 @@ class Controller(address: InetSocketAddress, listener: ActorRef)
listener ! c
val connection = sender()
connection ! Register(self)
context watch connection
context become {
case data: ByteString =>
connection ! Write(data)
Expand All @@ -41,6 +42,9 @@ class Controller(address: InetSocketAddress, listener: ActorRef)
case _: ConnectionClosed =>
context stop listener
context stop self
case Terminated(actor) if actor == connection =>
context stop listener
context stop self
}
}

Expand Down
Loading