Skip to content

Commit

Permalink
Send remote address in init
Browse files Browse the repository at this point in the history
This adds the option to report a remote IP address back to a connecting
peer using the init message. A node can decide to use that information
to discover a potential update to its public IP address (NAT) and use
that for a `node_announcement` update message containg the new address.

See lightning/bolts#917
  • Loading branch information
t-bast committed Sep 28, 2021
1 parent 467a0bc commit a9e9da5
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[ByteVector], co

val wireLog = new BusLogging(context.system.eventStream, "", classOf[Diagnostics], context.system.asInstanceOf[ExtendedActorSystem].logFilter) with DiagnosticLoggingAdapter

def diag(message: T, direction: String) = {
def diag(message: T, direction: String): Unit = {
require(direction == "IN" || direction == "OUT")
val channelId_opt = Logs.channelId(message)
wireLog.mdc(Logs.mdc(LogCategory(message), remoteNodeId_opt, channelId_opt))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
d.transport ! TransportHandler.Listener(self)
Metrics.PeerConnectionsConnecting.withTag(Tags.ConnectionState, Tags.ConnectionStates.Initializing).increment()
log.info(s"using features=$localFeatures")
val localInit = protocol.Init(localFeatures, TlvStream(InitTlv.Networks(chainHash :: Nil)))
val localInit = NodeAddress.extractPublicIPAddress(d.pendingAuth.address) match {
case Some(remoteAddress) if !d.pendingAuth.outgoing => protocol.Init(localFeatures, TlvStream(InitTlv.Networks(chainHash :: Nil), InitTlv.RemoteAddress(remoteAddress)))
case None => protocol.Init(localFeatures, TlvStream(InitTlv.Networks(chainHash :: Nil)))
}
d.transport ! localInit
startSingleTimer(INIT_TIMER, InitTimeout, conf.initTimeout)
goto(INITIALIZING) using InitializingData(chainHash, d.pendingAuth, d.remoteNodeId, d.transport, peer, localInit, doSync)
Expand All @@ -114,6 +117,7 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A
d.transport ! TransportHandler.ReadAck(remoteInit)

log.info(s"peer is using features=${remoteInit.features}, networks=${remoteInit.networks.mkString(",")}")
remoteInit.remoteAddress_opt.foreach(address => log.info("peer reports that our public address is {}", address.socketAddress.toString))

val featureGraphErr_opt = Features.validateFeatureGraph(remoteInit.features)
if (remoteInit.networks.nonEmpty && remoteInit.networks.intersect(d.localInit.networks).isEmpty) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ sealed trait HtlcSettlementMessage extends UpdateMessage { def id: Long } // <-

case class Init(features: Features, tlvStream: TlvStream[InitTlv] = TlvStream.empty) extends SetupMessage {
val networks = tlvStream.get[InitTlv.Networks].map(_.chainHashes).getOrElse(Nil)
val remoteAddress_opt = tlvStream.get[InitTlv.RemoteAddress].map(_.address)
}

case class Warning(channelId: ByteVector32, data: ByteVector, tlvStream: TlvStream[WarningTlv] = TlvStream.empty) extends SetupMessage with HasChannelId {
Expand Down Expand Up @@ -215,28 +216,47 @@ case class Color(r: Byte, g: Byte, b: Byte) {
// @formatter:off
sealed trait NodeAddress { def socketAddress: InetSocketAddress }
sealed trait OnionAddress extends NodeAddress
sealed trait IPAddress extends NodeAddress
// @formatter:on

object NodeAddress {
/**
* Creates a NodeAddress from a host and port.
*
* Note that non-onion hosts will 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.
*/
* Creates a NodeAddress from a host and port.
*
* Note that non-onion hosts will 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.
*/
def fromParts(host: String, port: Int): Try[NodeAddress] = Try {
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 _ => InetAddress.getByName(host) match {
case _ => InetAddress.getByName(host) match {
case a: Inet4Address => IPv4(a, port)
case a: Inet6Address => IPv6(a, port)
}
}
}

/**
* Extract the public IP address from an incoming connection, when possible. This information can be sent back to our
* peer to allow them to discover their public IP address from within their local network.
*/
def extractPublicIPAddress(socketAddress: InetSocketAddress): Option[IPAddress] = {
val address = socketAddress.getAddress
val isPrivate = address.isAnyLocalAddress || address.isLoopbackAddress || address.isLinkLocalAddress || address.isSiteLocalAddress
address match {
case address: Inet4Address if !isPrivate => Some(IPv4(address, socketAddress.getPort))
case address: Inet6Address if !isPrivate => Some(IPv6(address, socketAddress.getPort))
case _ => None
}
}
}
case class IPv4(ipv4: Inet4Address, port: Int) extends NodeAddress { override def socketAddress = new InetSocketAddress(ipv4, port) }
case class IPv6(ipv6: Inet6Address, port: Int) extends NodeAddress { override def socketAddress = new InetSocketAddress(ipv6, port) }

// @formatter:off
case class IPv4(ipv4: Inet4Address, port: Int) extends IPAddress { override def socketAddress = new InetSocketAddress(ipv4, port) }
case class IPv6(ipv6: Inet6Address, port: Int) extends IPAddress { override def socketAddress = new InetSocketAddress(ipv6, port) }
case class Tor2(tor2: String, port: Int) extends OnionAddress { override def socketAddress = InetSocketAddress.createUnresolved(tor2 + ".onion", port) }
case class Tor3(tor3: String, port: Int) extends OnionAddress { override def socketAddress = InetSocketAddress.createUnresolved(tor3 + ".onion", port) }
// @formatter:on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,24 @@ object InitTlv {
/** The chains the node is interested in. */
case class Networks(chainHashes: List[ByteVector32]) extends InitTlv

/**
* When receiving an incoming connection, we can send back the public address our peer is connecting from.
* This lets our peer discover if its public IP has changed from within its local network.
*/
case class RemoteAddress(address: NodeAddress) extends InitTlv

}

object InitTlvCodecs {

import InitTlv._

private val networks: Codec[Networks] = variableSizeBytesLong(varintoverflow, list(bytes32)).as[Networks]
private val remoteAddress: Codec[RemoteAddress] = variableSizeBytesLong(varintoverflow, nodeaddress).as[RemoteAddress]

val initTlvCodec = tlvStream(discriminated[InitTlv].by(varint)
.typecase(UInt64(1), networks)
.typecase(UInt64(3), remoteAddress)
)

}
Expand Down

0 comments on commit a9e9da5

Please sign in to comment.