From 092d7760a6aeb7ff9d466e21f2398654bb35388f Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Tue, 12 Apr 2022 17:48:44 +0200 Subject: [PATCH 01/15] Support DNS hostnames and deprecate Torv2 addresses in node announcements --- docs/release-notes/eclair-vnext.md | 6 +++ .../scala/fr/acinq/eclair/NodeParams.scala | 9 +++- .../scala/fr/acinq/eclair/io/Client.scala | 3 +- .../fr/acinq/eclair/io/ReconnectionTask.scala | 2 +- .../acinq/eclair/router/Announcements.scala | 13 ++++++ .../fr/acinq/eclair/router/Validation.scala | 8 +++- .../acinq/eclair/tor/Socks5Connection.scala | 5 +++ .../eclair/wire/protocol/CommonCodecs.scala | 3 ++ .../wire/protocol/LightningMessageTypes.scala | 26 +++++++++-- .../scala/fr/acinq/eclair/StartupSpec.scala | 22 +++++++++ .../fr/acinq/eclair/db/NetworkDbSpec.scala | 4 +- .../fr/acinq/eclair/io/NodeURISpec.scala | 4 +- .../scala/fr/acinq/eclair/io/PeerSpec.scala | 14 ++++++ .../eclair/json/JsonSerializersSpec.scala | 2 + .../eclair/router/AnnouncementsSpec.scala | 34 ++++++++++++-- .../fr/acinq/eclair/router/RouterSpec.scala | 45 +++++++++++++++++++ .../eclair/tor/Socks5ConnectionSpec.scala | 10 ++++- .../wire/protocol/CommonCodecsSpec.scala | 7 +++ 18 files changed, 201 insertions(+), 16 deletions(-) diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md index 97bd4cc0e1..3ce006850b 100644 --- a/docs/release-notes/eclair-vnext.md +++ b/docs/release-notes/eclair-vnext.md @@ -38,6 +38,12 @@ Expired incoming invoices that are unpaid will be searched for and purged from t * `eclair.purge-expired-invoices.enabled = true * `eclair.purge-expired-invoices.interval = 24 hours` +#### Public IP addresses can be DNS host names, but not Tor v2 addresses + +You can now specify a DNS host name as one of your `server.public-ips` addresses (see PR [#911](https://github.com/lightning/bolts/pull/911)). Note: you can not specify more than one DNS host name. + +Tor v2 addresses are no longer supported as a `server.public-ips` address and will be ignored in gossip messages (see PR [#940](https://github.com/lightning/bolts/pull/940]). + ## Verifying signatures You will need `gpg` and our release signing key 7A73FE77DE2C4027. Note that you can get it: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index ae01b21058..fb888ad9a0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -32,8 +32,8 @@ import fr.acinq.eclair.io.PeerConnection import fr.acinq.eclair.message.OnionMessages.OnionMessageConfig import fr.acinq.eclair.payment.relay.Relayer.{RelayFees, RelayParams} import fr.acinq.eclair.router.Graph.{HeuristicsConstants, WeightRatios} -import fr.acinq.eclair.router.PathFindingExperimentConf import fr.acinq.eclair.router.Router.{MultiPartParams, PathFindingConf, RouterConf, SearchBoundaries} +import fr.acinq.eclair.router.{Announcements, PathFindingExperimentConf} import fr.acinq.eclair.tor.Socks5ProxyParams import fr.acinq.eclair.wire.protocol.{Color, EncodingType, NodeAddress} import grizzled.slf4j.Logging @@ -297,6 +297,11 @@ object NodeParams extends Logging { require(features.hasFeature(Features.ChannelType), s"${Features.ChannelType.rfcName} must be enabled") } + def validateAddresses(addresses: List[NodeAddress]): Unit = { + val addressesError = Announcements.validateAddresses(addresses) + require(addressesError.isEmpty, addressesError.map(_.message)) + } + val pluginMessageParams = pluginParams.collect { case p: CustomFeaturePlugin => p } val features = Features.fromConfiguration(config.getConfig("features")) validateFeatures(features) @@ -328,6 +333,8 @@ object NodeParams extends Logging { .toList .map(ip => NodeAddress.fromParts(ip, config.getInt("server.port")).get) ++ publicTorAddress_opt + validateAddresses(addresses) + val feeTargets = FeeTargets( fundingBlockTarget = config.getInt("on-chain-fees.target-blocks.funding"), commitmentBlockTarget = config.getInt("on-chain-fees.target-blocks.commitment"), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Client.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Client.scala index 3219d6f76f..1448d1df92 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Client.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Client.scala @@ -44,12 +44,13 @@ class Client(keyPair: KeyPair, socks5ProxyParams_opt: Option[Socks5ProxyParams], def receive: Receive = { case Symbol("connect") => - // note that there is no resolution here, it's either plain ip addresses, or unresolved tor hostnames + // note that only DNS host names are resolved here; plain ip addresses and tor hostnames are not resolved val remoteAddress = remoteNodeAddress match { case addr: IPv4 => new InetSocketAddress(addr.ipv4, addr.port) case addr: IPv6 => new InetSocketAddress(addr.ipv6, addr.port) case addr: Tor2 => InetSocketAddress.createUnresolved(addr.host, addr.port) case addr: Tor3 => InetSocketAddress.createUnresolved(addr.host, addr.port) + case addr: DnsHostname => new InetSocketAddress(addr.host, addr.port) } val (peerOrProxyAddress, proxyParams_opt) = socks5ProxyParams_opt.map(proxyParams => (proxyParams, Socks5ProxyParams.proxyAddress(remoteNodeAddress, proxyParams))) match { case Some((proxyParams, Some(proxyAddress))) => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/ReconnectionTask.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/ReconnectionTask.scala index 8946afeb54..f550e1b0e0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/ReconnectionTask.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/ReconnectionTask.scala @@ -211,7 +211,7 @@ object ReconnectionTask { } def getPeerAddressFromDb(nodeParams: NodeParams, remoteNodeId: PublicKey): Option[NodeAddress] = { - val nodeAddresses = nodeParams.db.peers.getPeer(remoteNodeId).toSeq ++ nodeParams.db.network.getNode(remoteNodeId).toSeq.flatMap(_.addresses) + val nodeAddresses = nodeParams.db.peers.getPeer(remoteNodeId).toSeq ++ nodeParams.db.network.getNode(remoteNodeId).toList.flatMap(_.validAddresses) selectNodeAddress(nodeParams, nodeAddresses) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala index 83b8930183..edc5a698fc 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala @@ -70,11 +70,13 @@ object Announcements { def makeNodeAnnouncement(nodeSecret: PrivateKey, alias: String, color: Color, nodeAddresses: List[NodeAddress], features: Features[NodeFeature], timestamp: TimestampSecond = TimestampSecond.now()): NodeAnnouncement = { require(alias.length <= 32) + // sort addresses by ascending address descriptor type; do not reorder addresses within the same descriptor type val sortedAddresses = nodeAddresses.map { case address@(_: IPv4) => (1, address) case address@(_: IPv6) => (2, address) case address@(_: Tor2) => (3, address) case address@(_: Tor3) => (4, address) + case address@(_: DnsHostname) => (5, address) }.sortBy(_._1).map(_._2) val witness = nodeAnnouncementWitnessEncode(timestamp, nodeSecret.publicKey, color, alias, features.unscoped(), sortedAddresses, TlvStream.empty) val sig = Crypto.sign(witness, nodeSecret) @@ -89,6 +91,17 @@ object Announcements { ) } + case class AddressException(message: String) extends IllegalArgumentException(message) + + def validateAddresses(addresses: List[NodeAddress]): Option[AddressException] = { + if (addresses.count(_.isInstanceOf[DnsHostname]) > 1) + Some(AddressException(s"Invalid server.public-ip addresses: can not have more than one DNS host name.")) + else addresses.collectFirst { + case address if address.isInstanceOf[Tor2] => AddressException(s"invalid server.public-ip address `$address`: Tor v2 is deprecated.") + case address if address.port == 0 && !address.isInstanceOf[Tor3] => AddressException(s"invalid server.public-ip address `$address`: A non-Tor address can not use port 0.") + } + } + /** * BOLT 7: * The creating node MUST set node-id-1 and node-id-2 to the public keys of the diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index 5f4d66c73c..8389cff596 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -206,6 +206,10 @@ object Validation { log.debug("received node announcement from {}", ctx.sender()) None } + val rebroadcastNode = if (n.shouldRebroadcast) Some(n -> origins) else { + log.debug("will not rebroadcast {}", n) + None + } if (d.stash.nodes.contains(n)) { log.debug("ignoring {} (already stashed)", n) val origins1 = d.stash.nodes(n) ++ origins @@ -228,13 +232,13 @@ object Validation { remoteOrigins.foreach(sendDecision(_, GossipDecision.Accepted(n))) ctx.system.eventStream.publish(NodeUpdated(n)) db.updateNode(n) - d.copy(nodes = d.nodes + (n.nodeId -> n), rebroadcast = d.rebroadcast.copy(nodes = d.rebroadcast.nodes + (n -> origins))) + d.copy(nodes = d.nodes + (n.nodeId -> n), rebroadcast = d.rebroadcast.copy(nodes = d.rebroadcast.nodes ++ rebroadcastNode)) } else if (d.channels.values.exists(c => isRelatedTo(c.ann, n.nodeId))) { log.debug("added node nodeId={}", n.nodeId) remoteOrigins.foreach(sendDecision(_, GossipDecision.Accepted(n))) ctx.system.eventStream.publish(NodesDiscovered(n :: Nil)) db.addNode(n) - d.copy(nodes = d.nodes + (n.nodeId -> n), rebroadcast = d.rebroadcast.copy(nodes = d.rebroadcast.nodes + (n -> origins))) + d.copy(nodes = d.nodes + (n.nodeId -> n), rebroadcast = d.rebroadcast.copy(nodes = d.rebroadcast.nodes ++ rebroadcastNode)) } else if (d.awaiting.keys.exists(c => isRelatedTo(c, n.nodeId))) { log.debug("stashing {}", n) d.copy(stash = d.stash.copy(nodes = d.stash.nodes + (n -> origins))) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/tor/Socks5Connection.scala b/eclair-core/src/main/scala/fr/acinq/eclair/tor/Socks5Connection.scala index 0f27c104e7..7cf8fbf83f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/tor/Socks5Connection.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/tor/Socks5Connection.scala @@ -237,6 +237,11 @@ object Socks5ProxyParams { case _: IPv6 if proxyParams.useForIPv6 => Some(proxyParams.address) case _: Tor2 if proxyParams.useForTor => Some(proxyParams.address) case _: Tor3 if proxyParams.useForTor => Some(proxyParams.address) + case _: DnsHostname => InetAddress.getByName(address.host) match { + case _: Inet4Address if proxyParams.useForIPv4 => Some(proxyParams.address) + case _: Inet6Address if proxyParams.useForIPv6 => Some(proxyParams.address) + case _ => None + } case _ => None } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala index 7e37e80478..9306ad5df8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala @@ -126,12 +126,15 @@ object CommonCodecs { def base32(size: Int): Codec[String] = bytes(size).xmap(b => new Base32().encodeAsString(b.toArray).toLowerCase, a => ByteVector(new Base32().decode(a.toUpperCase()))) + val punycode: Codec[String] = variableSizeBytes(uint8, ascii) + val nodeaddress: Codec[NodeAddress] = discriminated[NodeAddress].by(uint8) .typecase(1, (ipv4address :: uint16).as[IPv4]) .typecase(2, (ipv6address :: uint16).as[IPv6]) .typecase(3, (base32(10) :: uint16).as[Tor2]) .typecase(4, (base32(35) :: uint16).as[Tor3]) + .typecase( 5, (punycode :: uint16).as[DnsHostname]) // this one is a bit different from most other codecs: the first 'len' element is *not* the number of items // in the list but rather the number of bytes of the encoded list. The rationale is once we've read this diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index 6510657c48..ccd3143bdd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -312,16 +312,22 @@ object NodeAddress { /** * Creates a NodeAddress from a host and port. * - * Note that non-onion hosts will be resolved. + * Note that only IP v4 and v6 hosts will be resolved, onion and DNS hosts names will not be resolved. * * We don't attempt to resolve onion addresses (it will be done by the tor proxy), so we just recognize them based on * the .onion TLD and rely on their length to separate v2/v3. + * + * We resolve host names comprised of only numbers and periods (IPv4) or that contain a colon (IPv6). + * Other host names are assumed to be a DNS name and are not immediately resolved. + * */ def fromParts(host: String, port: Int): Try[NodeAddress] = Try { + val ipv4v6 = "^([0-9.]*)?$|(:)".r host match { case _ if host.endsWith(".onion") && host.length == 22 => Tor2(host.dropRight(6), port) case _ if host.endsWith(".onion") && host.length == 62 => Tor3(host.dropRight(6), port) - case _ => IPAddress(InetAddress.getByName(host), port) + case _ if ipv4v6.findFirstIn(host).isDefined => IPAddress(InetAddress.getByName(host), port) + case _ => DnsHostname(host, port) } } @@ -348,6 +354,7 @@ case class IPv4(ipv4: Inet4Address, port: Int) extends IPAddress { override def case class IPv6(ipv6: Inet6Address, port: Int) extends IPAddress { override def host: String = InetAddresses.toUriString(ipv6) } case class Tor2(tor2: String, port: Int) extends OnionAddress { override def host: String = tor2 + ".onion" } case class Tor3(tor3: String, port: Int) extends OnionAddress { override def host: String = tor3 + ".onion" } +case class DnsHostname(dnsHostname: String, port: Int) extends IPAddress {override def host: String = dnsHostname} // @formatter:on case class NodeAnnouncement(signature: ByteVector64, @@ -357,7 +364,20 @@ case class NodeAnnouncement(signature: ByteVector64, rgbColor: Color, alias: String, addresses: List[NodeAddress], - tlvStream: TlvStream[NodeAnnouncementTlv] = TlvStream.empty) extends RoutingMessage with AnnouncementMessage with HasTimestamp + tlvStream: TlvStream[NodeAnnouncementTlv] = TlvStream.empty) extends RoutingMessage with AnnouncementMessage with HasTimestamp { + + def validAddresses: List[NodeAddress] = { + // if port is equal to 0, SHOULD ignore ipv6_addr OR ipv4_addr OR hostname; SHOULD ignore Tor v2 onion services. + val validAddresses = addresses.filter(address => address.port != 0 || address.isInstanceOf[Tor3]).filterNot( address => address.isInstanceOf[Tor2]) + // if more than one type 5 address is announced, SHOULD ignore the additional data. + validAddresses.filter(!_.isInstanceOf[DnsHostname]) ++ validAddresses.filter(_.isInstanceOf[DnsHostname]).take(1) + } + + def shouldRebroadcast: Boolean = { + // if more than one type 5 address is announced, MUST not forward the node_announcement. + addresses.count(address => address.isInstanceOf[DnsHostname]) <= 1 + } +} case class ChannelUpdate(signature: ByteVector64, chainHash: ByteVector32, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala index fb7a86121f..31979d9902 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala @@ -273,4 +273,26 @@ class StartupSpec extends AnyFunSuite { assert(nodeParamsAttempt2.isSuccess) } + test("NodeParams should fail when server.public-ips addresses or server.port are invalid") { + case class TestCase(publicIps: Seq[String], port: String, error: Option[String] = None, errorIp: Option[String] = None) + val testCases = Seq[TestCase]( + TestCase(Seq("0.0.0.0", "140.82.121.4", "2620:1ec:c11:0:0:0:0:200", "2620:1ec:c11:0:0:0:0:201", "iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", "of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad.onion", "acinq.co"), "9735"), + TestCase(Seq("140.82.121.4", "2620:1ec:c11:0:0:0:0:200", "acinq.fr", "iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion"), "0", Some("port 0"),Some("140.82.121.4")), + TestCase(Seq("hsmithsxurybd7uh.onion", "iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion"), "9735", Some("Tor v2"), Some("hsmithsxurybd7uh.onion")), + TestCase(Seq("acinq.co", "acinq.fr"), "9735", Some("DNS host name")), + ) + testCases.foreach( test => { + val serverConf = ConfigFactory.parseMap(Map( + s"server.public-ips" -> test.publicIps.asJava, + s"server.port" -> test.port, + ).asJava).withFallback(defaultConf) + val attempt = Try(makeNodeParamsWithDefaults(serverConf)) + if (test.error.isEmpty) + assert(attempt.isSuccess) + else + assert(attempt.isFailure && attempt.failed.get.getMessage.contains(test.error.get) && + (test.errorIp.isEmpty || attempt.failed.get.getMessage.contains(test.errorIp.get))) + }) + } + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala index bdc551e04b..2115045fb9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala @@ -58,7 +58,7 @@ class NetworkDbSpec extends AnyFunSuite { val node_1 = Announcements.makeNodeAnnouncement(randomKey(), "node-alice", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features.empty) val node_2 = Announcements.makeNodeAnnouncement(randomKey(), "node-bob", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(VariableLengthOnion -> Optional)) val node_3 = Announcements.makeNodeAnnouncement(randomKey(), "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(VariableLengthOnion -> Optional)) - val node_4 = Announcements.makeNodeAnnouncement(randomKey(), "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), Tor2("aaaqeayeaudaocaj", 42000) :: Nil, Features.empty) + val node_4 = Announcements.makeNodeAnnouncement(randomKey(), "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), Tor3("of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad", 42000) :: Nil, Features.empty) assert(db.listNodes().toSet === Set.empty) db.addNode(node_1) @@ -73,7 +73,7 @@ class NetworkDbSpec extends AnyFunSuite { assert(db.listNodes().toSet === Set(node_1, node_3, node_4)) db.updateNode(node_1) - assert(node_4.addresses == List(Tor2("aaaqeayeaudaocaj", 42000))) + assert(node_4.addresses == List(Tor3("of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad", 42000))) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/NodeURISpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/NodeURISpec.scala index 2fb1a4ade9..abe231d873 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/NodeURISpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/NodeURISpec.scala @@ -37,8 +37,8 @@ class NodeURISpec extends AnyFunSuite { val testCases = List( TestCase(s"$PUBKEY@$IPV4_ENDURANCE:9737", IPV4_ENDURANCE, 9737), TestCase(s"$PUBKEY@$IPV4_ENDURANCE", IPV4_ENDURANCE, 9735), - TestCase(s"$PUBKEY@$NAME_ENDURANCE:9737", "13.248.222.197", 9737), - TestCase(s"$PUBKEY@$NAME_ENDURANCE", "13.248.222.197", 9735), + TestCase(s"$PUBKEY@$NAME_ENDURANCE:9737", NAME_ENDURANCE, 9737), + TestCase(s"$PUBKEY@$NAME_ENDURANCE", NAME_ENDURANCE, 9735), TestCase(s"$PUBKEY@$IPV6:9737", "[2405:204:66a9:536c:873f:dc4a:f055:a298]", 9737), TestCase(s"$PUBKEY@$IPV6", "[2405:204:66a9:536c:873f:dc4a:f055:a298]", 9735), ) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 8f998f0a34..cd20cf2f98 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -136,6 +136,20 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle mockServer.close() } + test("return connection failure for a peer with an invalid dns host name") { f => + import f._ + + // this actor listens to connection requests and creates connections + system.actorOf(ClientSpawner.props(nodeParams.keyPair, nodeParams.socksProxy_opt, nodeParams.peerConnectionConf, TestProbe().ref, TestProbe().ref)) + + val invalidDnsHostname_opt = NodeAddress.fromParts("eclair.invalid", 9735).toOption + + val probe = TestProbe() + probe.send(peer, Peer.Init(Set.empty)) + probe.send(peer, Peer.Connect(remoteNodeId, invalidDnsHostname_opt, probe.ref, isPersistent = true)) + probe.expectMsgType[PeerConnection.ConnectionResult.ConnectionFailed] + } + test("successfully reconnect to peer at startup when there are existing channels", Tag("auto_reconnect")) { f => import f._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index 1b3708bd4c..eb81a39e7b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -95,12 +95,14 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { val ipv6LocalHost = IPAddress(InetAddress.getByAddress(Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)), 9735) val tor2 = Tor2("aaaqeayeaudaocaj", 7777) val tor3 = Tor3("aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc", 9999) + val dnsHostName = DnsHostname("acinq.co", 8888) JsonSerializers.serialization.write(ipv4)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""10.0.0.1:8888"""" JsonSerializers.serialization.write(ipv6)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""[2405:204:66a9:536c:873f:dc4a:f055:a298]:9737"""" JsonSerializers.serialization.write(ipv6LocalHost)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""[::1]:9735"""" JsonSerializers.serialization.write(tor2)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocaj.onion:7777"""" JsonSerializers.serialization.write(tor3)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""aaaqeayeaudaocajbifqydiob4ibceqtcqkrmfyydenbwha5dypsaijc.onion:9999"""" + JsonSerializers.serialization.write(dnsHostName)(org.json4s.DefaultFormats + NodeAddressSerializer) shouldBe s""""acinq.co:8888"""" } test("PeerInfo serialization") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala index b92e5f022e..4196a3d817 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala @@ -23,7 +23,7 @@ import fr.acinq.eclair._ import fr.acinq.eclair.router.Announcements._ import fr.acinq.eclair.wire.protocol.ChannelUpdate.ChannelFlags import fr.acinq.eclair.wire.protocol.LightningMessageCodecs.nodeAnnouncementCodec -import fr.acinq.eclair.wire.protocol.{Color, NodeAddress} +import fr.acinq.eclair.wire.protocol.NodeAddress import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -80,18 +80,46 @@ class AnnouncementsSpec extends AnyFunSuite { test("sort node announcement addresses") { val addresses = List( + NodeAddress.fromParts("acinq.co", 9735).get, NodeAddress.fromParts("iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", 9735).get, NodeAddress.fromParts("2620:1ec:c11:0:0:0:0:200", 9735).get, NodeAddress.fromParts("140.82.121.4", 9735).get, - NodeAddress.fromParts("hsmithsxurybd7uh.onion", 9735).get, ) val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, addresses, Alice.nodeParams.features.nodeAnnouncementFeatures()) assert(checkSig(ann)) assert(ann.addresses === List( NodeAddress.fromParts("140.82.121.4", 9735).get, NodeAddress.fromParts("2620:1ec:c11:0:0:0:0:200", 9735).get, - NodeAddress.fromParts("hsmithsxurybd7uh.onion", 9735).get, NodeAddress.fromParts("iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", 9735).get, + NodeAddress.fromParts("acinq.co", 9735).get, + )) + } + + test("filter invalid and deprecated node announcement addresses") { + val addresses = List( + NodeAddress.fromParts("140.82.121.5", 9735).get, + NodeAddress.fromParts("140.82.121.4", 0).get, // ignore IPv4 with port 0 + NodeAddress.fromParts("140.82.121.4", 9735).get, // more than one IPv4 is OK + NodeAddress.fromParts("2620:1ec:c11:0:0:0:0:201", 9735).get, + NodeAddress.fromParts("2620:1ec:c11:0:0:0:0:200", 9735).get, // more than one IPv6 is OK + NodeAddress.fromParts("2620:1ec:c11:0:0:0:0:200", 0).get, // ignore IPv6 with port 0 + NodeAddress.fromParts("hsmithsxurybd7uh.onion", 9735).get, // deprecate Torv2 addresses + NodeAddress.fromParts("iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", 0).get, // port zero for Tor is OK + NodeAddress.fromParts("of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad.onion", 9735).get, // more than one Tor is OK + NodeAddress.fromParts("acinq.co", 0).get, // ignore DNS hostname with port 0 + NodeAddress.fromParts("acinq.co", 9735).get, + NodeAddress.fromParts("acinq.fr", 9735).get, // ignore more than one DNS hostnames + ) + val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, addresses, Alice.nodeParams.features.nodeAnnouncementFeatures()) + assert(checkSig(ann)) + assert(ann.validAddresses === List( + NodeAddress.fromParts("140.82.121.5", 9735).get, + NodeAddress.fromParts("140.82.121.4", 9735).get, + NodeAddress.fromParts("2620:1ec:c11:0:0:0:0:201", 9735).get, + NodeAddress.fromParts("2620:1ec:c11:0:0:0:0:200", 9735).get, + NodeAddress.fromParts("iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", 0).get, + NodeAddress.fromParts("of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad.onion", 9735).get, + NodeAddress.fromParts("acinq.co", 9735).get )) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index a5fc1bb368..0af321437f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -737,4 +737,49 @@ class RouterSpec extends BaseRouterSpec { } } + test("properly announce valid new nodes announcements and ignore invalid ones") { fixture => + import fixture._ + val eventListener = TestProbe() + system.eventStream.subscribe(eventListener.ref, classOf[NetworkEvent]) + system.eventStream.subscribe(eventListener.ref, classOf[Rebroadcast]) + val peerConnection = TestProbe() + + { + // continue to rebroadcast node updates with deprecated Torv2 addresses + val torv2Address = List(NodeAddress.fromParts("hsmithsxurybd7uh.onion", 9735).get) + val node_c_torv2 = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), torv2Address, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), timestamp = TimestampSecond.now() + 1) + peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, node_c_torv2)) + peerConnection.expectMsg(TransportHandler.ReadAck(node_c_torv2)) + peerConnection.expectMsg(GossipDecision.Accepted(node_c_torv2)) + eventListener.expectMsg(NodeUpdated(node_c_torv2)) + router ! Router.TickBroadcast + val rebroadcast = eventListener.expectMsgType[Rebroadcast] + assert(rebroadcast.nodes.contains(node_c_torv2)) + } + + { + // rebroadcast node updates with a single DNS hostname addresses + val hostname = List(NodeAddress.fromParts("acinq.co", 9735).get) + val node_c_hostname = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), hostname, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), timestamp = TimestampSecond.now() + 10) + peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, node_c_hostname)) + peerConnection.expectMsg(TransportHandler.ReadAck(node_c_hostname)) + peerConnection.expectMsg(GossipDecision.Accepted(node_c_hostname)) + eventListener.expectMsg(NodeUpdated(node_c_hostname)) + router ! Router.TickBroadcast + val rebroadcast = eventListener.expectMsgType[Rebroadcast] + assert(rebroadcast.nodes.contains(node_c_hostname)) + } + + { + // do NOT rebroadcast node updates with more than one DNS hostname addresses + val multiHostnames = List(NodeAddress.fromParts("acinq.co", 9735).get, NodeAddress.fromParts("acinq.fr", 9735).get) + val node_c_noForward = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), multiHostnames, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), timestamp = TimestampSecond.now() + 20) + peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, node_c_noForward)) + peerConnection.expectMsg(TransportHandler.ReadAck(node_c_noForward)) + peerConnection.expectMsg(GossipDecision.Accepted(node_c_noForward)) + eventListener.expectMsg(NodeUpdated(node_c_noForward)) + router ! Router.TickBroadcast + eventListener.expectNoMessage(100 millis) + } + } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/tor/Socks5ConnectionSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/tor/Socks5ConnectionSpec.scala index a35cfb2b1f..5b3c02da68 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/tor/Socks5ConnectionSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/tor/Socks5ConnectionSpec.scala @@ -17,9 +17,9 @@ package fr.acinq.eclair.tor import fr.acinq.eclair.wire.protocol.NodeAddress +import org.scalatest.funsuite.AnyFunSuite import java.net.InetSocketAddress -import org.scalatest.funsuite.AnyFunSuite /** * Created by PM on 27/01/2017. @@ -54,6 +54,14 @@ class Socks5ConnectionSpec extends AnyFunSuite { address = NodeAddress.fromParts("iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", 9735).get, proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = false, useForWatchdogs = true)).isEmpty) + // DnsHostname "localhost" resolves to an IPv4 address + assert(Socks5ProxyParams.proxyAddress( + address = NodeAddress.fromParts("localhost", 9735).get, + proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = true, useForWatchdogs = true)).contains(proxyAddress)) + + assert(Socks5ProxyParams.proxyAddress( + address = NodeAddress.fromParts("localhost", 9735).get, + proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = false, useForIPv6 = true, useForTor = true, useForWatchdogs = true)).isEmpty) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/CommonCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/CommonCodecsSpec.scala index dbc6b4e11e..c2bc7ca3ca 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/CommonCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/CommonCodecsSpec.scala @@ -210,6 +210,13 @@ class CommonCodecsSpec extends AnyFunSuite { val nodeaddr2 = nodeaddress.decode(bin).require.value assert(nodeaddr === nodeaddr2) } + { + val nodeaddr = DnsHostname("acinq.co", 4231) + val bin = nodeaddress.encode(nodeaddr).require + assert(bin === hex"05 086163696e712e636f 1087".toBitVector) + val nodeaddr2 = nodeaddress.decode(bin).require.value + assert(nodeaddr === nodeaddr2) + } } test("encode/decode bytevector32") { From 9d91c5a4b7daf9a611e0b73a62e34f59c19a0d27 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Wed, 27 Jul 2022 09:36:09 -0700 Subject: [PATCH 02/15] Removed Tor v2 deprecation note now in another PR --- docs/release-notes/eclair-vnext.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md index 3ce006850b..7631cf5f5b 100644 --- a/docs/release-notes/eclair-vnext.md +++ b/docs/release-notes/eclair-vnext.md @@ -38,12 +38,10 @@ Expired incoming invoices that are unpaid will be searched for and purged from t * `eclair.purge-expired-invoices.enabled = true * `eclair.purge-expired-invoices.interval = 24 hours` -#### Public IP addresses can be DNS host names, but not Tor v2 addresses +#### Public IP addresses can be DNS host names You can now specify a DNS host name as one of your `server.public-ips` addresses (see PR [#911](https://github.com/lightning/bolts/pull/911)). Note: you can not specify more than one DNS host name. -Tor v2 addresses are no longer supported as a `server.public-ips` address and will be ignored in gossip messages (see PR [#940](https://github.com/lightning/bolts/pull/940]). - ## Verifying signatures You will need `gpg` and our release signing key 7A73FE77DE2C4027. Note that you can get it: From a9429bad747bf61566a1618bcfc75b0de1767c4b Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Wed, 27 Jul 2022 09:53:19 -0700 Subject: [PATCH 03/15] Fixed terse brackets of if statements --- .../scala/fr/acinq/eclair/router/Announcements.scala | 10 ++++++---- .../main/scala/fr/acinq/eclair/router/Validation.scala | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala index edc5a698fc..1f43ff3afd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala @@ -94,11 +94,13 @@ object Announcements { case class AddressException(message: String) extends IllegalArgumentException(message) def validateAddresses(addresses: List[NodeAddress]): Option[AddressException] = { - if (addresses.count(_.isInstanceOf[DnsHostname]) > 1) + if (addresses.count(_.isInstanceOf[DnsHostname]) > 1) { Some(AddressException(s"Invalid server.public-ip addresses: can not have more than one DNS host name.")) - else addresses.collectFirst { - case address if address.isInstanceOf[Tor2] => AddressException(s"invalid server.public-ip address `$address`: Tor v2 is deprecated.") - case address if address.port == 0 && !address.isInstanceOf[Tor3] => AddressException(s"invalid server.public-ip address `$address`: A non-Tor address can not use port 0.") + } else { + addresses.collectFirst { + case address if address.isInstanceOf[Tor2] => AddressException(s"invalid server.public-ip address `$address`: Tor v2 is deprecated.") + case address if address.port == 0 && !address.isInstanceOf[Tor3] => AddressException(s"invalid server.public-ip address `$address`: A non-Tor address can not use port 0.") + } } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index 8389cff596..26dddac8e5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -206,7 +206,9 @@ object Validation { log.debug("received node announcement from {}", ctx.sender()) None } - val rebroadcastNode = if (n.shouldRebroadcast) Some(n -> origins) else { + val rebroadcastNode = if (n.shouldRebroadcast) { + Some(n -> origins) + } else { log.debug("will not rebroadcast {}", n) None } From 34e6befa476f84efec66559567b7e6442bf23c65 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Wed, 27 Jul 2022 10:18:07 -0700 Subject: [PATCH 04/15] Move address validateAddresses logic to NodeParams --- .../main/scala/fr/acinq/eclair/NodeParams.scala | 15 ++++++++++++--- .../fr/acinq/eclair/router/Announcements.scala | 11 ----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index fb888ad9a0..6d09c38645 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -31,11 +31,12 @@ import fr.acinq.eclair.io.MessageRelay.{NoRelay, RelayAll, RelayChannelsOnly, Re import fr.acinq.eclair.io.PeerConnection import fr.acinq.eclair.message.OnionMessages.OnionMessageConfig import fr.acinq.eclair.payment.relay.Relayer.{RelayFees, RelayParams} +import fr.acinq.eclair.router.Announcements.AddressException import fr.acinq.eclair.router.Graph.{HeuristicsConstants, WeightRatios} +import fr.acinq.eclair.router.PathFindingExperimentConf import fr.acinq.eclair.router.Router.{MultiPartParams, PathFindingConf, RouterConf, SearchBoundaries} -import fr.acinq.eclair.router.{Announcements, PathFindingExperimentConf} import fr.acinq.eclair.tor.Socks5ProxyParams -import fr.acinq.eclair.wire.protocol.{Color, EncodingType, NodeAddress} +import fr.acinq.eclair.wire.protocol._ import grizzled.slf4j.Logging import scodec.bits.ByteVector @@ -298,7 +299,15 @@ object NodeParams extends Logging { } def validateAddresses(addresses: List[NodeAddress]): Unit = { - val addressesError = Announcements.validateAddresses(addresses) + val addressesError = if (addresses.count(_.isInstanceOf[DnsHostname]) > 1) { + Some(AddressException(s"Invalid server.public-ip addresses: can not have more than one DNS host name.")) + } else { + addresses.collectFirst { + case address if address.isInstanceOf[Tor2] => AddressException(s"invalid server.public-ip address `$address`: Tor v2 is deprecated.") + case address if address.port == 0 && !address.isInstanceOf[Tor3] => AddressException(s"invalid server.public-ip address `$address`: A non-Tor address can not use port 0.") + } + } + require(addressesError.isEmpty, addressesError.map(_.message)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala index 1f43ff3afd..0a24edf48d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala @@ -93,17 +93,6 @@ object Announcements { case class AddressException(message: String) extends IllegalArgumentException(message) - def validateAddresses(addresses: List[NodeAddress]): Option[AddressException] = { - if (addresses.count(_.isInstanceOf[DnsHostname]) > 1) { - Some(AddressException(s"Invalid server.public-ip addresses: can not have more than one DNS host name.")) - } else { - addresses.collectFirst { - case address if address.isInstanceOf[Tor2] => AddressException(s"invalid server.public-ip address `$address`: Tor v2 is deprecated.") - case address if address.port == 0 && !address.isInstanceOf[Tor3] => AddressException(s"invalid server.public-ip address `$address`: A non-Tor address can not use port 0.") - } - } - } - /** * BOLT 7: * The creating node MUST set node-id-1 and node-id-2 to the public keys of the From ec79f2e581f34af9a41356c0aade3e5e7640b4ed Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Fri, 12 Aug 2022 10:22:47 +0200 Subject: [PATCH 05/15] Added socks5.use-for-dnshostnames param --- eclair-core/src/main/resources/reference.conf | 1 + .../main/scala/fr/acinq/eclair/NodeParams.scala | 1 + .../fr/acinq/eclair/tor/Socks5Connection.scala | 8 ++------ .../watchdogs/BlockchainWatchdogSpec.scala | 3 ++- .../acinq/eclair/tor/Socks5ConnectionSpec.scala | 16 ++++++++-------- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 295ba8a30b..f533669545 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -299,6 +299,7 @@ eclair { use-for-ipv6 = true use-for-tor = true use-for-watchdogs = true + use-for-dnshostnames = true randomize-credentials = false // this allows tor stream isolation } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 6d09c38645..0c904cefc0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -178,6 +178,7 @@ object NodeParams extends Logging { useForIPv6 = config.getBoolean("socks5.use-for-ipv6"), useForTor = config.getBoolean("socks5.use-for-tor"), useForWatchdogs = config.getBoolean("socks5.use-for-watchdogs"), + useForDnsHostnames = config.getBoolean("socks5.use-for-dnshostnames"), )) } else { None diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/tor/Socks5Connection.scala b/eclair-core/src/main/scala/fr/acinq/eclair/tor/Socks5Connection.scala index 7cf8fbf83f..3a0e1aba14 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/tor/Socks5Connection.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/tor/Socks5Connection.scala @@ -217,7 +217,7 @@ object Socks5Connection { def portToByteString(port: Int): ByteString = ByteString((port & 0x0000ff00) >> 8, port & 0x000000ff) } -case class Socks5ProxyParams(address: InetSocketAddress, credentials_opt: Option[Credentials], randomizeCredentials: Boolean, useForIPv4: Boolean, useForIPv6: Boolean, useForTor: Boolean, useForWatchdogs: Boolean) +case class Socks5ProxyParams(address: InetSocketAddress, credentials_opt: Option[Credentials], randomizeCredentials: Boolean, useForIPv4: Boolean, useForIPv6: Boolean, useForTor: Boolean, useForWatchdogs: Boolean, useForDnsHostnames: Boolean) object Socks5ProxyParams { @@ -237,11 +237,7 @@ object Socks5ProxyParams { case _: IPv6 if proxyParams.useForIPv6 => Some(proxyParams.address) case _: Tor2 if proxyParams.useForTor => Some(proxyParams.address) case _: Tor3 if proxyParams.useForTor => Some(proxyParams.address) - case _: DnsHostname => InetAddress.getByName(address.host) match { - case _: Inet4Address if proxyParams.useForIPv4 => Some(proxyParams.address) - case _: Inet6Address if proxyParams.useForIPv6 => Some(proxyParams.address) - case _ => None - } + case _: DnsHostname if proxyParams.useForDnsHostnames => Some(proxyParams.address) case _ => None } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/watchdogs/BlockchainWatchdogSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/watchdogs/BlockchainWatchdogSpec.scala index 24b73080d3..ee7316a644 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/watchdogs/BlockchainWatchdogSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/watchdogs/BlockchainWatchdogSpec.scala @@ -107,7 +107,8 @@ class BlockchainWatchdogSpec extends ScalaTestWithActorTestKit(ConfigFactory.loa useForIPv4 = true, useForIPv6 = true, useForTor = true, - useForWatchdogs = true) + useForWatchdogs = true, + useForDnsHostnames = true) if (proxyAcceptsConnections(proxyParams)) { val eventListener = TestProbe[DangerousBlocksSkew]() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/tor/Socks5ConnectionSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/tor/Socks5ConnectionSpec.scala index 5b3c02da68..6925b737a7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/tor/Socks5ConnectionSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/tor/Socks5ConnectionSpec.scala @@ -32,36 +32,36 @@ class Socks5ConnectionSpec extends AnyFunSuite { assert(Socks5ProxyParams.proxyAddress( address = NodeAddress.fromParts("1.2.3.4", 9735).get, - proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = true, useForWatchdogs = true)).contains(proxyAddress)) + proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = true, useForWatchdogs = true, useForDnsHostnames = true)).contains(proxyAddress)) assert(Socks5ProxyParams.proxyAddress( address = NodeAddress.fromParts("1.2.3.4", 9735).get, - proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = false, useForIPv6 = true, useForTor = true, useForWatchdogs = true)).isEmpty) + proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = false, useForIPv6 = true, useForTor = true, useForWatchdogs = true, useForDnsHostnames = true)).isEmpty) assert(Socks5ProxyParams.proxyAddress( address = NodeAddress.fromParts("[fc92:97a3:e057:b290:abd8:9bd6:135d:7e7]", 9735).get, - proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = true, useForWatchdogs = true)).contains(proxyAddress)) + proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = true, useForWatchdogs = true, useForDnsHostnames = true)).contains(proxyAddress)) assert(Socks5ProxyParams.proxyAddress( address = NodeAddress.fromParts("[fc92:97a3:e057:b290:abd8:9bd6:135d:7e7]", 9735).get, - proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = false, useForTor = true, useForWatchdogs = true)).isEmpty) + proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = false, useForTor = true, useForWatchdogs = true, useForDnsHostnames = true)).isEmpty) assert(Socks5ProxyParams.proxyAddress( address = NodeAddress.fromParts("iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", 9735).get, - proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = true, useForWatchdogs = true)).contains(proxyAddress)) + proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = true, useForWatchdogs = true, useForDnsHostnames = true)).contains(proxyAddress)) assert(Socks5ProxyParams.proxyAddress( address = NodeAddress.fromParts("iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", 9735).get, - proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = false, useForWatchdogs = true)).isEmpty) + proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = false, useForWatchdogs = true, useForDnsHostnames = true)).isEmpty) // DnsHostname "localhost" resolves to an IPv4 address assert(Socks5ProxyParams.proxyAddress( address = NodeAddress.fromParts("localhost", 9735).get, - proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = true, useForWatchdogs = true)).contains(proxyAddress)) + proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = true, useForWatchdogs = true, useForDnsHostnames = true)).contains(proxyAddress)) assert(Socks5ProxyParams.proxyAddress( address = NodeAddress.fromParts("localhost", 9735).get, - proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = false, useForIPv6 = true, useForTor = true, useForWatchdogs = true)).isEmpty) + proxyParams = Socks5ProxyParams(address = proxyAddress, credentials_opt = None, randomizeCredentials = false, useForIPv4 = true, useForIPv6 = true, useForTor = true, useForWatchdogs = true, useForDnsHostnames = false)).isEmpty) } } From 160db3729f5f44fb04200be27d05de2609bd17c0 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Fri, 12 Aug 2022 10:35:47 +0200 Subject: [PATCH 06/15] Inline dnshostname ascii codec instead of misnamed punycode codec --- .../scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala index 9306ad5df8..74f0114654 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala @@ -126,15 +126,13 @@ object CommonCodecs { def base32(size: Int): Codec[String] = bytes(size).xmap(b => new Base32().encodeAsString(b.toArray).toLowerCase, a => ByteVector(new Base32().decode(a.toUpperCase()))) - val punycode: Codec[String] = variableSizeBytes(uint8, ascii) - val nodeaddress: Codec[NodeAddress] = discriminated[NodeAddress].by(uint8) .typecase(1, (ipv4address :: uint16).as[IPv4]) .typecase(2, (ipv6address :: uint16).as[IPv6]) .typecase(3, (base32(10) :: uint16).as[Tor2]) .typecase(4, (base32(35) :: uint16).as[Tor3]) - .typecase( 5, (punycode :: uint16).as[DnsHostname]) + .typecase( 5, (variableSizeBytes(uint8, ascii) :: uint16).as[DnsHostname]) // this one is a bit different from most other codecs: the first 'len' element is *not* the number of items // in the list but rather the number of bytes of the encoded list. The rationale is once we've read this From fd32764cf3f01be1f6e35832f365250de252c2aa Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Fri, 12 Aug 2022 11:10:16 +0200 Subject: [PATCH 07/15] Use standard Java functions to detect IPv4/v6 addresses --- .../fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index ccd3143bdd..076b7ea7db 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -24,6 +24,7 @@ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.{ChannelFlags, ChannelType} import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Feature, Features, InitFeature, MilliSatoshi, ShortChannelId, TimestampSecond, UInt64} import scodec.bits.ByteVector +import sun.net.util.IPAddressUtil.{isIPv4LiteralAddress, isIPv6LiteralAddress} import java.net.{Inet4Address, Inet6Address, InetAddress} import java.nio.charset.StandardCharsets @@ -322,11 +323,10 @@ object NodeAddress { * */ def fromParts(host: String, port: Int): Try[NodeAddress] = Try { - val ipv4v6 = "^([0-9.]*)?$|(:)".r host match { case _ if host.endsWith(".onion") && host.length == 22 => Tor2(host.dropRight(6), port) case _ if host.endsWith(".onion") && host.length == 62 => Tor3(host.dropRight(6), port) - case _ if ipv4v6.findFirstIn(host).isDefined => IPAddress(InetAddress.getByName(host), port) + case _ if isIPv4LiteralAddress(host) || isIPv6LiteralAddress(host) => IPAddress(InetAddress.getByName(host), port) case _ => DnsHostname(host, port) } } From ff2ac0419ff96c9dabc00e68add94c35e0ad8344 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Fri, 12 Aug 2022 11:25:26 +0200 Subject: [PATCH 08/15] Fixed some code/formatting nits --- .../wire/protocol/LightningMessageTypes.scala | 6 +++--- .../test/scala/fr/acinq/eclair/StartupSpec.scala | 14 ++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index 076b7ea7db..7b8f740e97 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -366,14 +366,14 @@ case class NodeAnnouncement(signature: ByteVector64, addresses: List[NodeAddress], tlvStream: TlvStream[NodeAnnouncementTlv] = TlvStream.empty) extends RoutingMessage with AnnouncementMessage with HasTimestamp { - def validAddresses: List[NodeAddress] = { + val validAddresses: List[NodeAddress] = { // if port is equal to 0, SHOULD ignore ipv6_addr OR ipv4_addr OR hostname; SHOULD ignore Tor v2 onion services. val validAddresses = addresses.filter(address => address.port != 0 || address.isInstanceOf[Tor3]).filterNot( address => address.isInstanceOf[Tor2]) // if more than one type 5 address is announced, SHOULD ignore the additional data. - validAddresses.filter(!_.isInstanceOf[DnsHostname]) ++ validAddresses.filter(_.isInstanceOf[DnsHostname]).take(1) + validAddresses.filter(!_.isInstanceOf[DnsHostname]) ++ validAddresses.find(_.isInstanceOf[DnsHostname]) } - def shouldRebroadcast: Boolean = { + val shouldRebroadcast: Boolean = { // if more than one type 5 address is announced, MUST not forward the node_announcement. addresses.count(address => address.isInstanceOf[DnsHostname]) <= 1 } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala index 31979d9902..c90a98d37d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/StartupSpec.scala @@ -277,21 +277,23 @@ class StartupSpec extends AnyFunSuite { case class TestCase(publicIps: Seq[String], port: String, error: Option[String] = None, errorIp: Option[String] = None) val testCases = Seq[TestCase]( TestCase(Seq("0.0.0.0", "140.82.121.4", "2620:1ec:c11:0:0:0:0:200", "2620:1ec:c11:0:0:0:0:201", "iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion", "of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad.onion", "acinq.co"), "9735"), - TestCase(Seq("140.82.121.4", "2620:1ec:c11:0:0:0:0:200", "acinq.fr", "iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion"), "0", Some("port 0"),Some("140.82.121.4")), + TestCase(Seq("140.82.121.4", "2620:1ec:c11:0:0:0:0:200", "acinq.fr", "iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion"), "0", Some("port 0"), Some("140.82.121.4")), TestCase(Seq("hsmithsxurybd7uh.onion", "iq7zhmhck54vcax2vlrdcavq2m32wao7ekh6jyeglmnuuvv3js57r4id.onion"), "9735", Some("Tor v2"), Some("hsmithsxurybd7uh.onion")), TestCase(Seq("acinq.co", "acinq.fr"), "9735", Some("DNS host name")), ) - testCases.foreach( test => { + testCases.foreach(test => { val serverConf = ConfigFactory.parseMap(Map( s"server.public-ips" -> test.publicIps.asJava, s"server.port" -> test.port, ).asJava).withFallback(defaultConf) val attempt = Try(makeNodeParamsWithDefaults(serverConf)) - if (test.error.isEmpty) + if (test.error.isEmpty) { assert(attempt.isSuccess) - else - assert(attempt.isFailure && attempt.failed.get.getMessage.contains(test.error.get) && - (test.errorIp.isEmpty || attempt.failed.get.getMessage.contains(test.errorIp.get))) + } else { + assert(attempt.isFailure) + assert(attempt.failed.get.getMessage.contains(test.error.get)) + assert(test.errorIp.isEmpty || attempt.failed.get.getMessage.contains(test.errorIp.get)) + } }) } From fbf5529f2d2a9785c94d5f31abee07f004d8b510 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Fri, 12 Aug 2022 11:43:01 +0200 Subject: [PATCH 09/15] List and verify a dnshostname node added to the db --- .../test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala index 2115045fb9..5f5a0c94a1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala @@ -58,7 +58,8 @@ class NetworkDbSpec extends AnyFunSuite { val node_1 = Announcements.makeNodeAnnouncement(randomKey(), "node-alice", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features.empty) val node_2 = Announcements.makeNodeAnnouncement(randomKey(), "node-bob", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(VariableLengthOnion -> Optional)) val node_3 = Announcements.makeNodeAnnouncement(randomKey(), "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(VariableLengthOnion -> Optional)) - val node_4 = Announcements.makeNodeAnnouncement(randomKey(), "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), Tor3("of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad", 42000) :: Nil, Features.empty) + val node_4 = Announcements.makeNodeAnnouncement(randomKey(), "node-eve", Color(100.toByte, 200.toByte, 300.toByte), Tor3("of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad", 42000) :: Nil, Features.empty) + val node_5 = Announcements.makeNodeAnnouncement(randomKey(), "node-frank", Color(100.toByte, 200.toByte, 300.toByte), DnsHostname("eclair.invalid", 42000) :: Nil, Features.empty) assert(db.listNodes().toSet === Set.empty) db.addNode(node_1) @@ -68,12 +69,14 @@ class NetworkDbSpec extends AnyFunSuite { db.addNode(node_2) db.addNode(node_3) db.addNode(node_4) - assert(db.listNodes().toSet === Set(node_1, node_2, node_3, node_4)) + db.addNode(node_5) + assert(db.listNodes().toSet === Set(node_1, node_2, node_3, node_4, node_5)) db.removeNode(node_2.nodeId) - assert(db.listNodes().toSet === Set(node_1, node_3, node_4)) + assert(db.listNodes().toSet === Set(node_1, node_3, node_4, node_5)) db.updateNode(node_1) assert(node_4.addresses == List(Tor3("of7husrflx7sforh3fw6yqlpwstee3wg5imvvmkp4bz6rbjxtg5nljad", 42000))) + assert(node_5.addresses == List(DnsHostname("eclair.invalid", 42000))) } } From 76d24d2513bddcb0fd7de5930bbb32b4198d0005 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Fri, 12 Aug 2022 12:01:07 +0200 Subject: [PATCH 10/15] fixup! Use standard Java functions to detect IPv4/v6 addresses --- .../fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index 7b8f740e97..8af4b4d317 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -318,8 +318,7 @@ object NodeAddress { * We don't attempt to resolve onion addresses (it will be done by the tor proxy), so we just recognize them based on * the .onion TLD and rely on their length to separate v2/v3. * - * We resolve host names comprised of only numbers and periods (IPv4) or that contain a colon (IPv6). - * Other host names are assumed to be a DNS name and are not immediately resolved. + * Host names that are not Tor, IPv4 or IPv6 are assumed to be a DNS name and are not immediately resolved. * */ def fromParts(host: String, port: Int): Try[NodeAddress] = Try { From 505c7cb0442093fb134d503dde7689dfee5f8692 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Fri, 12 Aug 2022 12:05:21 +0200 Subject: [PATCH 11/15] Add assertions to verify that dnshostname is correctly parsed --- eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index cd20cf2f98..b89a0f20e0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -143,6 +143,8 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle system.actorOf(ClientSpawner.props(nodeParams.keyPair, nodeParams.socksProxy_opt, nodeParams.peerConnectionConf, TestProbe().ref, TestProbe().ref)) val invalidDnsHostname_opt = NodeAddress.fromParts("eclair.invalid", 9735).toOption + assert(invalidDnsHostname_opt.nonEmpty) + assert(invalidDnsHostname_opt.get == DnsHostname("eclair.invalid", 9735)) val probe = TestProbe() probe.send(peer, Peer.Init(Set.empty)) From a8e58ee76529776b56c432575da0f63a540d872a Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Mon, 15 Aug 2022 09:21:14 +0200 Subject: [PATCH 12/15] fixup! Use standard Java functions to detect IPv4/v6 addresses --- .../fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index 8af4b4d317..a221f6fc12 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -24,7 +24,6 @@ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.{ChannelFlags, ChannelType} import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Feature, Features, InitFeature, MilliSatoshi, ShortChannelId, TimestampSecond, UInt64} import scodec.bits.ByteVector -import sun.net.util.IPAddressUtil.{isIPv4LiteralAddress, isIPv6LiteralAddress} import java.net.{Inet4Address, Inet6Address, InetAddress} import java.nio.charset.StandardCharsets @@ -325,7 +324,7 @@ object NodeAddress { host match { case _ if host.endsWith(".onion") && host.length == 22 => Tor2(host.dropRight(6), port) case _ if host.endsWith(".onion") && host.length == 62 => Tor3(host.dropRight(6), port) - case _ if isIPv4LiteralAddress(host) || isIPv6LiteralAddress(host) => IPAddress(InetAddress.getByName(host), port) + case _ if InetAddresses.isInetAddress(host) => IPAddress(InetAddress.getByName(host), port) case _ => DnsHostname(host, port) } } From e248b7c043ffa4e74f61e3827264a9740ad612c4 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Mon, 15 Aug 2022 09:29:20 +0200 Subject: [PATCH 13/15] Fixed a couple of more formatting nits --- .../main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala | 2 +- .../fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala index 74f0114654..9d98560c35 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala @@ -132,7 +132,7 @@ object CommonCodecs { .typecase(2, (ipv6address :: uint16).as[IPv6]) .typecase(3, (base32(10) :: uint16).as[Tor2]) .typecase(4, (base32(35) :: uint16).as[Tor3]) - .typecase( 5, (variableSizeBytes(uint8, ascii) :: uint16).as[DnsHostname]) + .typecase(5, (variableSizeBytes(uint8, ascii) :: uint16).as[DnsHostname]) // this one is a bit different from most other codecs: the first 'len' element is *not* the number of items // in the list but rather the number of bytes of the encoded list. The rationale is once we've read this diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index a221f6fc12..80e4f1d57e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -366,7 +366,7 @@ case class NodeAnnouncement(signature: ByteVector64, val validAddresses: List[NodeAddress] = { // if port is equal to 0, SHOULD ignore ipv6_addr OR ipv4_addr OR hostname; SHOULD ignore Tor v2 onion services. - val validAddresses = addresses.filter(address => address.port != 0 || address.isInstanceOf[Tor3]).filterNot( address => address.isInstanceOf[Tor2]) + val validAddresses = addresses.filter(address => address.port != 0 || address.isInstanceOf[Tor3]).filterNot(address => address.isInstanceOf[Tor2]) // if more than one type 5 address is announced, SHOULD ignore the additional data. validAddresses.filter(!_.isInstanceOf[DnsHostname]) ++ validAddresses.find(_.isInstanceOf[DnsHostname]) } From ad966d02ac00393b7ce8d431ed42281b97c406b4 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Mon, 15 Aug 2022 17:28:57 +0200 Subject: [PATCH 14/15] Fix "get proxy address" test failure because of brackets around IPv6 address --- .../fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index e2fe4330ae..4c82527d7b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -363,7 +363,7 @@ object NodeAddress { host match { case _ if host.endsWith(".onion") && host.length == 22 => Tor2(host.dropRight(6), port) case _ if host.endsWith(".onion") && host.length == 62 => Tor3(host.dropRight(6), port) - case _ if InetAddresses.isInetAddress(host) => IPAddress(InetAddress.getByName(host), port) + case _ if InetAddresses.isInetAddress(host.filterNot(Set('[', ']'))) => IPAddress(InetAddress.getByName(host), port) case _ => DnsHostname(host, port) } } From c2df6d2fbe710738efae018815d6c3bf71c4ba04 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Mon, 15 Aug 2022 17:32:42 +0200 Subject: [PATCH 15/15] Fix test failures caused by fromParts() no longer resolving "localhost" to 127.0.0.1 --- .../src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index 1e88e1adcc..ff8a479a84 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -169,7 +169,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM ActorRef.noSender, nodeId = aliceNodeId, state = Peer.CONNECTED, - address = Some(NodeAddress.fromParts("localhost", 9731).get), + address = Some(NodeAddress.fromParts("127.0.0.1", 9731).get), channels = 1), PeerInfo( ActorRef.noSender, @@ -254,7 +254,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM chainHash = ByteVector32(hex"06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f"), network = "regtest", blockHeight = 9999, - publicAddresses = NodeAddress.fromParts("localhost", 9731).get :: Nil, + publicAddresses = NodeAddress.fromParts("127.0.0.1", 9731).get :: Nil, onionAddress = None, instanceId = "01234567-0123-4567-89ab-0123456789ab" ))