Skip to content

Commit 701f229

Browse files
authored
More flexible mixing of clearnet addresses and tor proxy (#3054)
We improve the connection logic when a proxy is configured, but Tor shouldn't be used for IPv4 or IPv6 remote addresses.
1 parent 9771b2d commit 701f229

File tree

2 files changed

+123
-16
lines changed

2 files changed

+123
-16
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/io/ReconnectionTask.scala

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -187,27 +187,32 @@ object ReconnectionTask {
187187
// @formatter:on
188188

189189
def selectNodeAddress(nodeParams: NodeParams, nodeAddresses: Seq[NodeAddress]): Option[NodeAddress] = {
190-
// it doesn't make sense to mix tor and clearnet addresses, so we separate them and decide whether we use one or the other
190+
val selectedAddresses = selectNodeAddresses(nodeParams, nodeAddresses)
191+
// we pick an address at random
192+
if (selectedAddresses.nonEmpty) {
193+
Some(selectedAddresses(Random.nextInt(selectedAddresses.size)))
194+
} else {
195+
None
196+
}
197+
}
198+
199+
private[io] def selectNodeAddresses(nodeParams: NodeParams, nodeAddresses: Seq[NodeAddress]): Seq[NodeAddress] = {
191200
val torAddresses = nodeAddresses.collect { case o: OnionAddress => o }
192201
val clearnetAddresses = nodeAddresses diff torAddresses
193202
val selectedAddresses = nodeParams.socksProxy_opt match {
194-
case Some(params) if clearnetAddresses.nonEmpty && params.useForTor && (!params.useForIPv4 || !params.useForIPv6) =>
195-
// Remote has clearnet (and possibly tor addresses), and we support tor, but we have configured it to only use
196-
// tor when strictly necessary. In this case we will only connect over clearnet.
203+
case Some(params) if clearnetAddresses.nonEmpty && (!params.useForIPv4 || !params.useForIPv6) =>
204+
// Remote has clearnet and possibly tor addresses, but we have configured our proxy to only be used
205+
// for tor. In this case we will only connect over clearnet.
197206
clearnetAddresses
198-
case Some(params) if torAddresses.nonEmpty && params.useForTor =>
199-
// In all other cases, if they have a tor address and we support tor, we use tor.
200-
torAddresses
207+
case Some(params) if params.useForTor =>
208+
// The SOCKS5 proxy is enabled, and specifically configured to handle tor addresses.
209+
// This is the only case when we can connect to both tor and clearnet.
210+
nodeAddresses
201211
case _ =>
202-
// Otherwise, if we don't support tor or they don't have a tor address, we use clearnet.
212+
// Otherwise, if we don't support tor or remote doesn't have a tor address, we use clearnet.
203213
clearnetAddresses
204214
}
205-
// finally, we pick an address at random
206-
if (selectedAddresses.nonEmpty) {
207-
Some(selectedAddresses(Random.nextInt(selectedAddresses.size)))
208-
} else {
209-
None
210-
}
215+
selectedAddresses
211216
}
212217

213218
def getPeerAddressFromDb(nodeParams: NodeParams, remoteNodeId: PublicKey): Option[NodeAddress] = {

eclair-core/src/test/scala/fr/acinq/eclair/io/ReconnectionTaskSpec.scala

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,15 +250,117 @@ class ReconnectionTaskSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
250250
assert(ReconnectionTask.selectNodeAddress(nodeParams, List(clearnet, tor)).contains(clearnet))
251251
}
252252
{
253-
// tor supported and enabled for clearnet addresses: return tor addresses when available
253+
// tor supported and enabled for clearnet addresses: return both tor and clearnet addresses when available
254254
val socksParams = mock[Socks5ProxyParams]
255255
socksParams.useForTor returns true
256256
socksParams.useForIPv4 returns true
257257
socksParams.useForIPv6 returns true
258258
nodeParams.socksProxy_opt returns Some(socksParams)
259259
assert(ReconnectionTask.selectNodeAddress(nodeParams, List(clearnet)).contains(clearnet))
260260
assert(ReconnectionTask.selectNodeAddress(nodeParams, List(tor)).contains(tor))
261-
assert(ReconnectionTask.selectNodeAddress(nodeParams, List(clearnet, tor)).contains(tor))
261+
assert(ReconnectionTask.selectNodeAddress(nodeParams, List(clearnet, tor)).exists(Set(clearnet, tor)(_)))
262+
}
263+
{
264+
// tor supported and enabled for clearnet addresses, but disabled for tor: return clearnet addresses when available
265+
val socksParams = mock[Socks5ProxyParams]
266+
socksParams.useForTor returns false
267+
socksParams.useForIPv4 returns true
268+
socksParams.useForIPv6 returns true
269+
nodeParams.socksProxy_opt returns Some(socksParams)
270+
assert(ReconnectionTask.selectNodeAddress(nodeParams, List(clearnet)).contains(clearnet))
271+
assert(ReconnectionTask.selectNodeAddress(nodeParams, List(tor)).isEmpty)
272+
assert(ReconnectionTask.selectNodeAddress(nodeParams, List(clearnet, tor)).contains(clearnet))
273+
}
274+
}
275+
276+
test("select node addresses for reconnection") { () =>
277+
val nodeParams = mock[NodeParams]
278+
val clearnetIPv4 = NodeAddress.fromParts("1.2.3.4", 9735).get
279+
val clearnetIPv6 = NodeAddress.fromParts("2001:db8::1", 9735).get
280+
val tor = NodeAddress.fromParts("iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", 9735).get
281+
val dnsHostname = NodeAddress.fromParts("example.com", 9735).get
282+
283+
{
284+
// no proxy configured: only return clearnet addresses
285+
nodeParams.socksProxy_opt returns None
286+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4)) == List(clearnetIPv4))
287+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(tor)) == List.empty)
288+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, tor)) == List(clearnetIPv4))
289+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, clearnetIPv6, tor)) == List(clearnetIPv4, clearnetIPv6))
290+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(dnsHostname, tor)) == List(dnsHostname))
291+
}
292+
{
293+
// proxy configured but not for tor: only return clearnet addresses
294+
val socksParams = mock[Socks5ProxyParams]
295+
socksParams.useForTor returns false
296+
socksParams.useForIPv4 returns true
297+
socksParams.useForIPv6 returns true
298+
nodeParams.socksProxy_opt returns Some(socksParams)
299+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4)) == List(clearnetIPv4))
300+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(tor)) == List.empty)
301+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, tor)) == List(clearnetIPv4))
302+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, clearnetIPv6, tor)) == List(clearnetIPv4, clearnetIPv6))
303+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(dnsHostname, tor)) == List(dnsHostname))
304+
}
305+
{
306+
// proxy configured for tor but not for IPv4: return tor addresses only if there ar no clearnet addresses, otherwise return clearnet addresses
307+
val socksParams = mock[Socks5ProxyParams]
308+
socksParams.useForTor returns true
309+
socksParams.useForIPv4 returns false
310+
socksParams.useForIPv6 returns true
311+
nodeParams.socksProxy_opt returns Some(socksParams)
312+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4)) == List(clearnetIPv4))
313+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv6)) == List(clearnetIPv6))
314+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(tor)) == List(tor))
315+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, tor)) == List(clearnetIPv4))
316+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv6, tor)) == List(clearnetIPv6))
317+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, clearnetIPv6, tor)) == List(clearnetIPv4, clearnetIPv6))
318+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(dnsHostname, tor)) == List(dnsHostname))
319+
}
320+
{
321+
// proxy configured for tor but not for IPv6: return tor addresses only if there ar no clearnet addresses, otherwise return clearnet addresses
322+
val socksParams = mock[Socks5ProxyParams]
323+
socksParams.useForTor returns true
324+
socksParams.useForIPv4 returns true
325+
socksParams.useForIPv6 returns false
326+
nodeParams.socksProxy_opt returns Some(socksParams)
327+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4)) == List(clearnetIPv4))
328+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv6)) == List(clearnetIPv6))
329+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(tor)) == List(tor))
330+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, tor)) == List(clearnetIPv4))
331+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv6, tor)) == List(clearnetIPv6))
332+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, clearnetIPv6, tor)) == List(clearnetIPv4, clearnetIPv6))
333+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(dnsHostname, tor)) == List(dnsHostname))
334+
}
335+
{
336+
// proxy configured for tor but not for IPv4 and IPv6: return tor addresses only if there ar no clearnet addresses, otherwise return clearnet addresses
337+
val socksParams = mock[Socks5ProxyParams]
338+
socksParams.useForTor returns true
339+
socksParams.useForIPv4 returns false
340+
socksParams.useForIPv6 returns false
341+
nodeParams.socksProxy_opt returns Some(socksParams)
342+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4)) == List(clearnetIPv4))
343+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv6)) == List(clearnetIPv6))
344+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(tor)) == List(tor))
345+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, tor)) == List(clearnetIPv4))
346+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv6, tor)) == List(clearnetIPv6))
347+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, clearnetIPv6, tor)) == List(clearnetIPv4, clearnetIPv6))
348+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(dnsHostname, tor)) == List(dnsHostname))
349+
}
350+
{
351+
// proxy configured for tor and both IPv4/IPv6: return all addresses
352+
val socksParams = mock[Socks5ProxyParams]
353+
socksParams.useForTor returns true
354+
socksParams.useForIPv4 returns true
355+
socksParams.useForIPv6 returns true
356+
nodeParams.socksProxy_opt returns Some(socksParams)
357+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4)) == List(clearnetIPv4))
358+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv6)) == List(clearnetIPv6))
359+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(tor)) == List(tor))
360+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, tor)) == List(clearnetIPv4, tor))
361+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv6, tor)) == List(clearnetIPv6, tor))
362+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(clearnetIPv4, clearnetIPv6, tor)) == List(clearnetIPv4, clearnetIPv6, tor))
363+
assert(ReconnectionTask.selectNodeAddresses(nodeParams, List(dnsHostname, tor)) == List(dnsHostname, tor))
262364
}
263365
}
264366

0 commit comments

Comments
 (0)