diff --git a/akka-actor-tests/src/test/scala/akka/actor/AddressSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/AddressSpec.scala new file mode 100644 index 0000000000..232e545532 --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/actor/AddressSpec.scala @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2009-2016 Typesafe Inc. + */ +package akka.actor + +import org.scalatest.{ WordSpec, ShouldMatchers } + +@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) +class AddressSpec extends WordSpec with ShouldMatchers { + + "The Address factory methods" should { + + "fail if ipv6 address is not wrapped in brackets" in { + intercept[IllegalArgumentException] { + Address("tcp", "system", "0:0:0:0:0:0:0:1", 2551) + }.getMessage should include("must be wrapped with []") + } + + "not fail if ipv6 address is wrapped in brackets" in { + Address("tcp", "system", "[0:0:0:0:0:0:0:1]", 2551) + + } + + "not fail with an ipv4 address" in { + Address("tcp", "system", "192.168.0.1", 2551) + } + + "not fail with a hostname address" in { + Address("tcp", "system", "example.com", 2551) + } + } +} diff --git a/akka-actor/src/main/scala/akka/actor/Address.scala b/akka-actor/src/main/scala/akka/actor/Address.scala index 955f127522..94870a606f 100644 --- a/akka-actor/src/main/scala/akka/actor/Address.scala +++ b/akka-actor/src/main/scala/akka/actor/Address.scala @@ -75,7 +75,11 @@ object Address { /** * Constructs a new Address with the specified protocol, system name, host and port */ - def apply(protocol: String, system: String, host: String, port: Int) = new Address(protocol, system, Some(host), Some(port)) + def apply(protocol: String, system: String, host: String, port: Int) = { + require(!host.contains(":") || host.startsWith("["), s"IPv6 address $host must be wrapped with [] to be safe") + new Address(protocol, system, Some(host), Some(port)) + } + } private[akka] trait PathUtils { diff --git a/akka-remote/src/main/resources/reference.conf b/akka-remote/src/main/resources/reference.conf index 4121c83527..66c8d408d1 100644 --- a/akka-remote/src/main/resources/reference.conf +++ b/akka-remote/src/main/resources/reference.conf @@ -360,6 +360,7 @@ akka { # The hostname or ip clients should connect to. # InetAddress.getLocalHost.getHostAddress is used if empty + # if using an IPv6 address wrap it in brackets, like so: [fe80::a65e:60ff:fee5:ba9f%en0] hostname = "" # Use this setting to bind a network interface to a different port diff --git a/akka-remote/src/main/scala/akka/remote/transport/netty/NettyTransport.scala b/akka-remote/src/main/scala/akka/remote/transport/netty/NettyTransport.scala index 2257c64c7e..775d43788d 100644 --- a/akka-remote/src/main/scala/akka/remote/transport/netty/NettyTransport.scala +++ b/akka-remote/src/main/scala/akka/remote/transport/netty/NettyTransport.scala @@ -238,16 +238,32 @@ private[transport] object NettyTransport { val uniqueIdCounter = new AtomicInteger(0) def addressFromSocketAddress(addr: SocketAddress, schemeIdentifier: String, systemName: String, - hostName: Option[String], port: Option[Int]): Option[Address] = addr match { - case sa: InetSocketAddress ⇒ Some(Address(schemeIdentifier, systemName, - hostName.getOrElse(sa.getAddress.getHostAddress), port.getOrElse(sa.getPort))) // perhaps use getHostString in jdk 1.7 - case _ ⇒ None + hostName: Option[String], port: Option[Int]): Option[Address] = { + + addr match { + case sa: InetSocketAddress ⇒ + Some(Address( + schemeIdentifier, + systemName, + ipv6SafeHostname(hostName.getOrElse(sa.getHostString)), + port.getOrElse(sa.getPort))) + + case _ ⇒ None + } } // Need to do like this for binary compatibility reasons def addressFromSocketAddress(addr: SocketAddress, schemeIdentifier: String, systemName: String, hostName: Option[String]): Option[Address] = addressFromSocketAddress(addr, schemeIdentifier, systemName, hostName, port = None) + + /** + * Wraps an ipv6 address hostname in [] to avoid having it parsed as hostname + port. + */ + private def ipv6SafeHostname(unsafeHostname: String): String = + if (unsafeHostname.contains(":") && !unsafeHostname.startsWith("[")) s"[$unsafeHostname]" + else unsafeHostname + } // FIXME: Split into separate UDP and TCP classes diff --git a/akka-remote/src/test/scala/akka/remote/transport/netty/NettyTransportSpec.scala b/akka-remote/src/test/scala/akka/remote/transport/netty/NettyTransportSpec.scala index 00cb0900b2..47791c0411 100644 --- a/akka-remote/src/test/scala/akka/remote/transport/netty/NettyTransportSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/transport/netty/NettyTransportSpec.scala @@ -116,6 +116,13 @@ class NettyTransportSpec extends WordSpec with Matchers with BindBehaviour { Await.result(sys.terminate(), Duration.Inf) } + + "wrap ipv6 addresses in brackets if missing" in { + val socketAddress = InetSocketAddress.createUnresolved("0:0:0:0:0:0:0:1", 2552) + + val address = NettyTransport.addressFromSocketAddress(socketAddress, "tcp", "example", None, None) + address.flatMap(_.host) should ===(Some("[0:0:0:0:0:0:0:1]")) + } } }