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 #3672: Improve javalib java.net handling of NetworkInterface indices #3702

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
Original file line number Diff line number Diff line change
Expand Up @@ -119,53 +119,6 @@ private[net] abstract class AbstractPlainDatagramSocketImpl
}
}

/* 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 = posix.sys.socket.AF_INET6.toUShort
sa6.sin6_port = inet.htons(port.toUShort)

val src = inetAddress.getAddress()

if (inetAddress.isInstanceOf[Inet6Address]) {
val from = src.asInstanceOf[scala.scalanative.runtime.Array[Byte]].at(0)
val dst = sa6.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]]
memcpy(dst, from, 16.toUInt)
} 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 sa4Ptr = stackalloc[Ptr[addrinfo]]()
Expand Down Expand Up @@ -197,7 +150,7 @@ private[net] abstract class AbstractPlainDatagramSocketImpl
val sa6Len = sizeof[in.sockaddr_in6].toUInt

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

val bindRes = posix.sys.socket.bind(
fd.fd,
Expand Down Expand Up @@ -256,7 +209,7 @@ private[net] abstract class AbstractPlainDatagramSocketImpl
val sa6Len = sizeof[in.sockaddr_in6].toUInt

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

val buffer = p.getData()
val cArr = buffer.at(p.getOffset())
Expand Down Expand Up @@ -303,7 +256,7 @@ private[net] abstract class AbstractPlainDatagramSocketImpl
val sa6Len = sizeof[in.sockaddr_in6].toUInt

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

val connectRet = posix.sys.socket.connect(
fd.fd,
Expand Down
51 changes: 2 additions & 49 deletions javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,53 +93,6 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
portOpt.map(inet.ntohs(_).toInt)
}

/* 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 = inet.htons(port.toUShort)

val src = inetAddress.getAddress()

if (inetAddress.isInstanceOf[Inet6Address]) {
val from = src.asInstanceOf[scala.scalanative.runtime.Array[Byte]].at(0)
val dst = sa6.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]]
memcpy(dst, from, 16.toUInt)
} 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]]()
Expand Down Expand Up @@ -174,7 +127,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
val sa6 = stackalloc[in.sockaddr_in6]()

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

val bindRes = socket.bind(
fd.fd,
Expand Down Expand Up @@ -305,7 +258,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
val sa6 = stackalloc[in.sockaddr_in6]()

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

if (timeout != 0)
setSocketFdBlocking(fd, blocking = false)
Expand Down
56 changes: 2 additions & 54 deletions javalib/src/main/scala/java/net/InetAddress.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import scala.scalanative.posix.netdb._
import scala.scalanative.posix.netdbOps._
import scala.scalanative.posix.string.{memcpy, strerror}
import scala.scalanative.posix.sys.socket._
import scala.scalanative.posix.sys.socketOps._
import scala.scalanative.posix.time.{time_t, time, difftime}
import scala.scalanative.posix.unistd

Expand Down Expand Up @@ -215,61 +216,8 @@ object InetAddress {
* to leave the host field blank/empty.
*/
val effectiveHost = if (isNumeric) null else host
SocketHelpers.sockaddrToInetAddress(addrinfoP.ai_addr, effectiveHost)

if (addrinfoP.ai_family == AF_INET) {
new Inet4Address(addrinfoToByteArray(addrinfoP), effectiveHost)
} else if (addrinfoP.ai_family == AF_INET6) {
val addr = addrinfoP.ai_addr.asInstanceOf[Ptr[sockaddr_in6]]
val addrBytes = addr.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]]

// Scala JVM down-converts even when preferIPv6Addresses is "true"
if (isIPv4MappedAddress(addrBytes)) {
new Inet4Address(extractIP4Bytes(addrBytes), effectiveHost)
} else {
/* Yes, Java specifies Int for scope_id in a way which disallows
* some values POSIX/IEEE/IETF allows.
*/

val scope_id = addr.sin6_scope_id.toInt

/* Be aware some trickiness here.
* Java treats a 0 scope_id (qua NetworkInterface index)
* as having been not supplied.
* Exactly the same 0 scope_id explicitly passed to
* Inet6Address.getByAddress() is considered supplied and
* displayed as such.
*/

if (scope_id == 0)
Inet6Address(addrinfoToByteArray(addrinfoP), effectiveHost)
else
Inet6Address.getByAddress(
effectiveHost,
addrinfoToByteArray(addrinfoP),
scope_id
)
}
} else {
val af = addrinfoP.ai_family
throw new IOException(
s"The requested address family is not supported: ${af}."
)
}
}

private def addrinfoToByteArray(
addrinfoP: Ptr[addrinfo]
): Array[Byte] = {
SocketHelpers.sockaddrToByteArray(addrinfoP.ai_addr)
}

private def extractIP4Bytes(pb: Ptr[Byte]): Array[Byte] = {
val buf = new Array[Byte](4)
buf(0) = pb(12)
buf(1) = pb(13)
buf(2) = pb(14)
buf(3) = pb(15)
buf
}

private def formatIn4Addr(pb: Ptr[Byte]): String = {
Expand Down
140 changes: 139 additions & 1 deletion javalib/src/main/scala/java/net/SocketHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package java.net
import scala.scalanative.unsigned._
import scala.scalanative.unsafe._

import java.io.IOException

import scala.scalanative.posix.arpa.inet
import scala.scalanative.posix.{netdb, netdbOps}, netdb._, netdbOps._
import scala.scalanative.posix.netinet.{in, inOps}, in._, inOps._
Expand Down Expand Up @@ -137,6 +139,60 @@ object SocketHelpers {
(ptrInt(2) == 0xffff0000) && (ptrLong(0) == 0x0L)
}

/* 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[net] 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 = AF_INET6.toUShort
sa6.sin6_port = inet.htons(port.toUShort)

val src = inetAddress.getAddress()

if (inetAddress.isInstanceOf[Inet6Address]) {
val from = src.asInstanceOf[scala.scalanative.runtime.Array[Byte]].at(0)
val dst = sa6.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]]
memcpy(dst, from, 16.toUInt)

sa6.sin6_scope_id = inetAddress
.asInstanceOf[Inet6Address]
.getScopeId()
.toUShort
} else { // Use IPv4mappedIPv6 address
// IPv4 addresses do not have a scope_id, so leave at current value 0

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[net] def sockaddrToByteArray(sockAddr: Ptr[sockaddr]): Array[Byte] = {
val af = sockAddr.sa_family.toInt
val (src, size) = if (af == AF_INET6) {
Expand Down Expand Up @@ -174,10 +230,92 @@ object SocketHelpers {
inet.ntohs(inPort).toInt
}

private def extractIP4Bytes(pb: Ptr[Byte]): Array[Byte] = {
val buf = new Array[Byte](4)
buf(0) = pb(12)
buf(1) = pb(13)
buf(2) = pb(14)
buf(3) = pb(15)
buf
}

/* The goal is to have a single implementation of InetAddress class &
* subclass creation that can be used by InetAddress.scala and
* NetworkInterface.scala, by way of sockaddrStorageToInetSocketAddress().
*
* One would expect such a routine to be in InetAddress.scala
* to make the creation of Inet4Address & Inet6Address instances
* simpler and have better performance.
*
* test-runtime compiles & executes across many versions using that
* scheme. Unfortunately, test-scripted on Scala 2.12 (and possibly
* other versions) fails to compile the java-net-socket test.
* Good design wrecked upon the rocks of hard reality.
*/

private[net] def sockaddrToInetAddress(
sin: Ptr[sockaddr],
host: String
): InetAddress = {

if (sin.sa_family == AF_INET) {
InetAddress.getByAddress(host, SocketHelpers.sockaddrToByteArray(sin))
} else if (sin.sa_family == AF_INET6) {
val sin6 = sin.asInstanceOf[Ptr[sockaddr_in6]]
val addrBytes = sin6.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]]

// Scala JVM down-converts even when preferIPv6Addresses is "true"
/* 2024-01-21 10:16 -0500
* Yes this is still astonishing but true. Not just a trick of
* output formatting.
*
* Using scala.cli
*
* scala> import
* scala> val ia1 = InetAddress.getByName("::FFFF:127.0.0.1")
* val ia1: java.net.InetAddress = /127.0.0.1
* scala> ia1.isInstanceOf[Inet4Address]
* val res0: Boolean = true
*/
if (isIPv4MappedAddress(addrBytes)) {
InetAddress.getByAddress(host, extractIP4Bytes(addrBytes))
} else {
/* Yes, Java specifies Int for scope_id in a way which disallows
* some values POSIX/IEEE/IETF allows.
*/

val scope_id = sin6.sin6_scope_id.toInt

/* Be aware some trickiness here.
* Java treats a 0 scope_id (qua NetworkInterface index)
* as having been not supplied.
* Exactly the same 0 scope_id explicitly passed to
* Inet6Address.getByAddress() is considered supplied and
* displayed as such.
*/

// Keep address bytes passed in immutable, get new Array.
val clonedBytes = SocketHelpers.sockaddrToByteArray(sin)
if (scope_id == 0)
InetAddress.getByAddress(host, clonedBytes)
else
Inet6Address.getByAddress(
host,
clonedBytes,
scope_id
)
}
} else {
throw new IOException(
s"The requested address family is not supported: ${sin.sa_family}."
)
}
}

private[net] def sockaddrStorageToInetSocketAddress(
sockAddr: Ptr[sockaddr]
): InetSocketAddress = {
val addr = InetAddress.getByAddress(sockaddrToByteArray(sockAddr))
val addr = sockaddrToInetAddress(sockAddr, "")
val port = sockddrToPort(sockAddr)
new InetSocketAddress(addr, port)
}
Expand Down