Skip to content

Commit

Permalink
Fix #3672: Improve javalib java.net handling of NetworkInterface indi…
Browse files Browse the repository at this point in the history
…ces (#3702)
  • Loading branch information
LeeTibbert committed Jan 23, 2024
1 parent 73a8971 commit 834f6e3
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 154 deletions.
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

0 comments on commit 834f6e3

Please sign in to comment.