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

Fix #2280: Add Unix-only IPv6 TCP support to javalib #2823

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/lib/javalib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -666,4 +666,35 @@ Native's reimplementation, this remains not implemented for now. The added
``getClass().getResourceAsInputStream()`` however is able to be consistent between
the platforms.


Internet Protocol Version 6 (IPv6) Networking
---------------------------------------------

IPv6 provides network features which are more efficient and gradually
replacing its worthy, but venerable, predecessor IPv4.

The Scala Native Java library now supports IPv6 as it is described in the
original `Java Networking IPv6 User Guide <https://docs.oracle.com/javase/8/docs/technotes/guides/net/ipv6_guide/index.html/>`_. The design center is that
a Scala Java Virtual Machine (JVM) program using networking
will run almost identically using Scala Native.

IPv6 will be used if any network interface on a system/node/host, other
than the loopback interface, is configured to enable IPv6. Otherwise,
IPv4 is used as before. Java has been using this approach for decades.

Most people will not be able to determine if IPv6 or IPv4 is in use.
Networks experts will, by using specialist tools.

Scala Native checks and honors the two System Properties described in
the ipv6_guide above: ``java.net.preferIPv4Stack`` and
``java.net.preferIPv6Addresses``. This check is done once, when the
network is first used.

* If there is ever a reason to use only IPv4, a program can
set the ``java.net.preferIPv4Stack`` to ``true`` at runtime
before the first use of the network. There is no way to accomplish
this from the command line or environment.::

System.setProperty("java.net.preferIPv6Addresses", "true")

Continue to :ref:`libc`.
25 changes: 25 additions & 0 deletions javalib/src/main/resources/scala-native/netinet/in6.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef _WIN32
#include <netinet/in.h>
#endif

/* Internet Engineering Task Force (IETF) RFC2553 describes in6.h
* being accessed via netinet/in.h, which includes it, and not directly.
*/

// This file implements only the sole declaration need by java.net.

int scalanative_ipv6_tclass() {
#ifndef IPV6_TCLASS
/* Force a runtime error, probably errno 92: "Protocol not available"
* Do no force link errors for something which is used in the wild
* only by experts, and then rarely.
*/
return 0; // 0 is an invalid socket option.
#else
/* Operating system specific.
* Known values: Linus 67, macOS 36, FreeBSD 61.
* Windows seems to not have it at all, although WSL might.
*/
return IPV6_TCLASS;
#endif
}
157 changes: 146 additions & 11 deletions javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import scala.scalanative.posix.netdb._
import scala.scalanative.posix.netdbOps._
import scala.scalanative.posix.sys.time._
import scala.scalanative.posix.sys.timeOps._
import scala.scalanative.posix.arpa.inet._

import scala.scalanative.meta.LinktimeInfo.isWindows
import java.io.{FileDescriptor, IOException, OutputStream, InputStream}
Expand All @@ -40,6 +41,8 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
protected var timeout = 0
private var listening = false

private final val useIPv4Only = SocketHelpers.getPreferIPv4Stack()

override def getInetAddress: InetAddress = address
override def getFileDescriptor: FileDescriptor = fd
final protected var isClosed: Boolean =
Expand Down Expand Up @@ -91,7 +94,52 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
portOpt.map(inet.ntohs(_).toInt)
}

override def bind(addr: InetAddress, port: Int): Unit = {
/* Fill in the given sockaddr_in6 with the given InetAddress, either
* Inet4Address or Inet6Address, and the given port.
* Set the af_family for IPv6. On return, the sockaddr_in6 should
* be ready to use in bind() or connect().
*
* By contract, all the bytes in sa6 are zero coming in.
*/
private def prepareSockaddrIn6(
inetAddress: InetAddress,
port: Int,
sa6: Ptr[in.sockaddr_in6]
): Unit = {

/* BEWARE: This is Unix-only code.
* Currently (2022-08-27) execution on Windows never get here. IPv4Only
* is forced on. If that ever changes, this method may need
* Windows code.
*
* Having the complexity in one place, it should make adding
* Windows support easier.
*/

sa6.sin6_family = socket.AF_INET6.toUShort
sa6.sin6_port = htons(port.toUShort)

val src = inetAddress.getAddress()

if (inetAddress.isInstanceOf[Inet6Address]) {
sa6.sin6_addr = src.asInstanceOf[in.in6_addr]
} else { // Use IPv4mappedIPv6 address
val dst = sa6.sin6_addr.toPtr.s6_addr

// By contract, the leading bytes are already zero already.
val FF = 255.toUByte
dst(10) = FF // set the IPv4mappedIPv6 indicator bytes
dst(11) = FF

// add the IPv4 trailing bytes, unrolling small loop
dst(12) = src(0).toUByte
dst(13) = src(1).toUByte
dst(14) = src(2).toUByte
dst(15) = src(3).toUByte
}
}

private def bind4(addr: InetAddress, port: Int): Unit = {
val hints = stackalloc[addrinfo]()
val ret = stackalloc[Ptr[addrinfo]]()
hints.ai_family = socket.AF_UNSPEC
Expand Down Expand Up @@ -121,6 +169,36 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
}
}

private def bind6(addr: InetAddress, port: Int): Unit = {
val sa6 = stackalloc[in.sockaddr_in6]()

// By contract, all the bytes in sa6 are zero going in.
prepareSockaddrIn6(addr, port, sa6)

val bindRes = socket.bind(
fd.fd,
sa6.asInstanceOf[Ptr[socket.sockaddr]],
sizeof[in.sockaddr_in6].toUInt
)

if (bindRes < 0)
throwCannotBind(addr)

this.localport = fetchLocalPort(sa6.sin6_family.toInt).getOrElse {
throwCannotBind(addr)
}
}

private lazy val bindFunc =
if (useIPv4Only) bind4(_: InetAddress, _: Int)
else bind6(_: InetAddress, _: Int)

override def bind(addr: InetAddress, port: Int): Unit = {
throwIfClosed("bind")

bindFunc(addr, port)
}

override def listen(backlog: Int): Unit = {
if (socket.listen(fd.fd, backlog) == -1) {
throw new SocketException("Listen failed")
Expand All @@ -131,9 +209,8 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
override def accept(s: SocketImpl): Unit = {
throwIfClosed("accept") // Do not send negative fd.fd to poll()

if (timeout > 0) {
if (timeout > 0)
tryPollOnAccept()
}

val storage: Ptr[Byte] = stackalloc[Byte](sizeof[in.sockaddr_in6])
val len = stackalloc[socket.socklen_t]()
Expand Down Expand Up @@ -168,7 +245,9 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
s.port = inet.ntohs(sa.sin6_port).toInt
}

Zone { implicit z => s.address = InetAddress.getByName(fromCString(ipstr)) }
Zone { implicit z =>
s.address = InetAddress.getByName(fromCString(ipstr))
}

s.fd = new FileDescriptor(newFd)
s.localport = this.localport
Expand All @@ -183,10 +262,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
connect(new InetSocketAddress(address, port), 0)
}

override def connect(address: SocketAddress, timeout: Int): Unit = {

throwIfClosed("connect") // Do not send negative fd.fd to poll()

private def connect4(address: SocketAddress, timeout: Int): Unit = {
val inetAddr = address.asInstanceOf[InetSocketAddress]
val hints = stackalloc[addrinfo]()
val ret = stackalloc[Ptr[addrinfo]]()
Expand Down Expand Up @@ -246,6 +322,63 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
}
}

private def connect6(address: SocketAddress, timeout: Int): Unit = {
val insAddr = address.asInstanceOf[InetSocketAddress]

val sa6 = stackalloc[in.sockaddr_in6]()

// By contract, all the bytes in sa6 are zero going in.
prepareSockaddrIn6(insAddr.getAddress, insAddr.getPort, sa6)

if (timeout != 0)
setSocketFdBlocking(fd, blocking = false)

val connectRet = socket.connect(
fd.fd,
sa6.asInstanceOf[Ptr[socket.sockaddr]],
sizeof[in.sockaddr_in6].toUInt
)

if (connectRet < 0) {
def inProgress = mapLastError(
onUnix = _ == EINPROGRESS,
onWindows = {
case WSAEINPROGRESS | WSAEWOULDBLOCK => true
case _ => false
}
)

if (timeout > 0 && inProgress) {
tryPollOnConnect(timeout)
} else {
val ra = insAddr.getAddress.getHostAddress()
throw new ConnectException(
s"Could not connect to address: ${ra}"
+ s" on port: ${insAddr.getPort}"
+ s", errno: ${lastError()}"
)
}
}

this.address = insAddr.getAddress
this.port = insAddr.getPort
this.localport = fetchLocalPort(sa6.sin6_family.toInt).getOrElse {
throw new ConnectException(
"Could not resolve a local port when connecting"
)
}
}

private lazy val connectFunc =
if (useIPv4Only) connect4(_: SocketAddress, _: Int)
else connect6(_: SocketAddress, _: Int)

override def connect(address: SocketAddress, timeout: Int): Unit = {
throwIfClosed("connect") // Do not send negative fd.fd to poll()

connectFunc(address, timeout)
}

override def close(): Unit = {
if (!isClosed) {
if (isWindows) WinSocketApi.closeSocket(fd.handle)
Expand Down Expand Up @@ -358,7 +491,8 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
// because some of them have the same value, but require different levels
// for example IP_TOS and TCP_NODELAY have the same value on my machine
private def nativeValueFromOption(option: Int) = option match {
case SocketOptions.IP_TOS => in.IP_TOS
case SocketOptions.IP_TOS =>
SocketHelpers.getTrafficClassSocketOption()
case SocketOptions.SO_KEEPALIVE => socket.SO_KEEPALIVE
case SocketOptions.SO_LINGER => socket.SO_LINGER
case SocketOptions.SO_TIMEOUT => socket.SO_RCVTIMEO
Expand All @@ -381,7 +515,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {

val level = optID match {
case SocketOptions.TCP_NODELAY => in.IPPROTO_TCP
case SocketOptions.IP_TOS => in.IPPROTO_IP
case SocketOptions.IP_TOS => SocketHelpers.getIPPROTO()
case _ => socket.SOL_SOCKET
}

Expand Down Expand Up @@ -436,7 +570,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
}

val level = optID match {
case SocketOptions.IP_TOS => in.IPPROTO_IP
case SocketOptions.IP_TOS => SocketHelpers.getIPPROTO()
case SocketOptions.TCP_NODELAY => in.IPPROTO_TCP
case _ => socket.SOL_SOCKET
}
Expand Down Expand Up @@ -486,6 +620,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {

ptr.asInstanceOf[Ptr[Byte]]
}

case _ =>
val ptr = stackalloc[CInt]()
!ptr = value.asInstanceOf[Int]
Expand Down