From 8ca3aeb2a8e23d1d67953eb706dd520dd47c28fb Mon Sep 17 00:00:00 2001 From: RECKEL Roland Date: Fri, 2 Jan 2026 14:52:26 +0100 Subject: [PATCH 1/6] Add getOptOriginalDest --- .../scala/fs2/io/internal/SocketHelpers.scala | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala index 84156e4d82..f08086fcd3 100644 --- a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala +++ b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala @@ -43,6 +43,9 @@ import scala.scalanative.posix.netinet.tcp._ import scala.scalanative.posix.string._ import scala.scalanative.posix.sys.socket._ import scala.scalanative.posix.unistd._ +import scala.scalanative.posix.sys.socket._ +import scala.scalanative.posix.netinet.in._ +import scala.scalanative.posix.arpa.inet._ import scala.scalanative.unsafe._ import scala.scalanative.unsigned._ @@ -52,6 +55,7 @@ import netinetinOps._ import syssocket._ import sysun._ import sysunOps._ +import com.comcast.ip4s.Host private[io] object SocketHelpers { @@ -99,6 +103,8 @@ private[io] object SocketHelpers { getOptionBool(fd, SO_KEEPALIVE) case StandardSocketOptions.TCP_NODELAY => getTcpOptionBool(fd, TCP_NODELAY) + case _ if name.name() == "SO_ORIGINAL_DST" => + getOptOriginalDest(fd) case _ => Sync[F].pure(None) }).asInstanceOf[F[Option[A]]] @@ -114,6 +120,41 @@ private[io] object SocketHelpers { def getTcpOptionInt[F[_]: Sync](fd: CInt, option: CInt): F[Option[Int]] = getOptionImpl(fd, IPPROTO_TCP /* aka SOL_TCP */, option) + def getOptOriginalDest[F[_]](fd: CInt)(implicit F: Sync[F]): F[Option[SocketAddress[IpAddress]]] = { + F.delay { + val SOL_IP = 0 + val SO_ORIGINAL_DST = 80 + val size = sizeOf[sockaddr_storage] + val ptr = stackalloc[Byte](size) + val szPtr = stackalloc[UInt]() + !szPtr = size.toUInt + val ret = guardMask( + getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, ptr, szPtr) + )(_ == ENOPROTOOPT) + if (ret == ENOPROTOOPT) None else { + val sockaddr = ptr.asInstanceOf[Ptr[sockaddr_storage]] + if(sockaddr._1 == AF_INET) { + val dstStr = stackalloc[Byte](INET_ADDRSTRLEN) + val addr = ptr.asInstanceOf[Ptr[sockaddr_in]] + val addr_in = addr.sin_addr + val port = htons(addr.sin_port).toInt + inet_ntop(AF_INET, addr_in.toPtr.asInstanceOf[CVoidPtr], dstStr, INET_ADDRSTRLEN.toUInt) + SocketAddress.fromString4(s"${fromCString(dstStr)}:$port") + } else if(sockaddr._1 == AF_INET6) { + val dstStr = stackalloc[Byte](INET6_ADDRSTRLEN) + val addr = ptr.asInstanceOf[Ptr[sockaddr_in6]] + val addr_in = addr.sin6_addr + val port = htons(addr.sin6_port).toInt + inet_ntop(AF_INET6, addr_in.toPtr.asInstanceOf[CVoidPtr], dstStr, INET6_ADDRSTRLEN.toUInt) + SocketAddress.fromString6(s"${fromCString(dstStr)}:$port") + } else { + println("Something went wrong during getsockopt") + None + } + } + } + } + def getOptionImpl[F[_]](fd: CInt, level: CInt, option: CInt)(implicit F: Sync[F] ): F[Option[Int]] = From 31e5286d1bbfcf4da50bfe87497b98a93b309179 Mon Sep 17 00:00:00 2001 From: RECKEL Roland Date: Fri, 9 Jan 2026 10:32:42 +0100 Subject: [PATCH 2/6] Cleanup getOriginalDestination socket option --- .../scala/fs2/io/net/SocketOptionPlatform.scala | 7 +++++++ .../scala/fs2/io/internal/SocketHelpers.scala | 16 +++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/io/jvm-native/src/main/scala/fs2/io/net/SocketOptionPlatform.scala b/io/jvm-native/src/main/scala/fs2/io/net/SocketOptionPlatform.scala index d188bf0937..80e66362b1 100644 --- a/io/jvm-native/src/main/scala/fs2/io/net/SocketOptionPlatform.scala +++ b/io/jvm-native/src/main/scala/fs2/io/net/SocketOptionPlatform.scala @@ -28,6 +28,8 @@ import java.lang.{Boolean => JBoolean, Integer => JInt} import java.net.{NetworkInterface => JNetworkInterface} import com.comcast.ip4s.NetworkInterface +import com.comcast.ip4s.SocketAddress +import com.comcast.ip4s.IpAddress private[net] trait SocketOptionCompanionPlatform { type Key[A] = JSocketOption[A] @@ -90,6 +92,11 @@ private[net] trait SocketOptionCompanionPlatform { def noDelay(value: Boolean): SocketOption = boolean(NoDelay, value) + val OriginalDestination: Key[SocketAddress[IpAddress]] = new Key[SocketAddress[IpAddress]] { + def name() = "SO_ORIGINAL_DST" + def `type`() = classOf[SocketAddress[IpAddress]] + } + val UnixSocketDeleteIfExists: Key[JBoolean] = new Key[JBoolean] { def name() = "FS2_UNIX_DELETE_IF_EXISTS" def `type`() = classOf[JBoolean] diff --git a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala index f08086fcd3..6c42a3130b 100644 --- a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala +++ b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala @@ -55,7 +55,6 @@ import netinetinOps._ import syssocket._ import sysun._ import sysunOps._ -import com.comcast.ip4s.Host private[io] object SocketHelpers { @@ -86,7 +85,8 @@ private[io] object SocketHelpers { StandardSocketOptions.SO_REUSEADDR, StandardSocketOptions.SO_REUSEPORT, StandardSocketOptions.SO_KEEPALIVE, - StandardSocketOptions.TCP_NODELAY + StandardSocketOptions.TCP_NODELAY, + fs2.io.net.SocketOption.OriginalDestination ) ) @@ -103,8 +103,9 @@ private[io] object SocketHelpers { getOptionBool(fd, SO_KEEPALIVE) case StandardSocketOptions.TCP_NODELAY => getTcpOptionBool(fd, TCP_NODELAY) - case _ if name.name() == "SO_ORIGINAL_DST" => - getOptOriginalDest(fd) + case fs2.io.net.SocketOption.OriginalDestination => + val SO_ORIGINAL_DST = 80 // linux kernel option: https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter_ipv4.h#L52 + getIpOptSocketAddress(fd, SO_ORIGINAL_DST) case _ => Sync[F].pure(None) }).asInstanceOf[F[Option[A]]] @@ -120,16 +121,14 @@ private[io] object SocketHelpers { def getTcpOptionInt[F[_]: Sync](fd: CInt, option: CInt): F[Option[Int]] = getOptionImpl(fd, IPPROTO_TCP /* aka SOL_TCP */, option) - def getOptOriginalDest[F[_]](fd: CInt)(implicit F: Sync[F]): F[Option[SocketAddress[IpAddress]]] = { + def getIpOptSocketAddress[F[_]](fd: CInt, option: CInt)(implicit F: Sync[F]): F[Option[SocketAddress[IpAddress]]] = { F.delay { - val SOL_IP = 0 - val SO_ORIGINAL_DST = 80 val size = sizeOf[sockaddr_storage] val ptr = stackalloc[Byte](size) val szPtr = stackalloc[UInt]() !szPtr = size.toUInt val ret = guardMask( - getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, ptr, szPtr) + getsockopt(fd, IPPROTO_IP, option, ptr, szPtr) )(_ == ENOPROTOOPT) if (ret == ENOPROTOOPT) None else { val sockaddr = ptr.asInstanceOf[Ptr[sockaddr_storage]] @@ -148,7 +147,6 @@ private[io] object SocketHelpers { inet_ntop(AF_INET6, addr_in.toPtr.asInstanceOf[CVoidPtr], dstStr, INET6_ADDRSTRLEN.toUInt) SocketAddress.fromString6(s"${fromCString(dstStr)}:$port") } else { - println("Something went wrong during getsockopt") None } } From bd47884e48e00744db233e65bd48e1bc7ab36b09 Mon Sep 17 00:00:00 2001 From: RECKEL Roland Date: Fri, 9 Jan 2026 10:55:09 +0100 Subject: [PATCH 3/6] Formatting --- .../scala/fs2/io/internal/SocketHelpers.scala | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala index 6c42a3130b..18aa2e743e 100644 --- a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala +++ b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala @@ -104,7 +104,8 @@ private[io] object SocketHelpers { case StandardSocketOptions.TCP_NODELAY => getTcpOptionBool(fd, TCP_NODELAY) case fs2.io.net.SocketOption.OriginalDestination => - val SO_ORIGINAL_DST = 80 // linux kernel option: https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter_ipv4.h#L52 + // linux kernel option: https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter_ipv4.h#L52 + val SO_ORIGINAL_DST = 80 getIpOptSocketAddress(fd, SO_ORIGINAL_DST) case _ => Sync[F].pure(None) }).asInstanceOf[F[Option[A]]] @@ -121,7 +122,9 @@ private[io] object SocketHelpers { def getTcpOptionInt[F[_]: Sync](fd: CInt, option: CInt): F[Option[Int]] = getOptionImpl(fd, IPPROTO_TCP /* aka SOL_TCP */, option) - def getIpOptSocketAddress[F[_]](fd: CInt, option: CInt)(implicit F: Sync[F]): F[Option[SocketAddress[IpAddress]]] = { + def getIpOptSocketAddress[F[_]](fd: CInt, option: CInt)(implicit + F: Sync[F] + ): F[Option[SocketAddress[IpAddress]]] = F.delay { val size = sizeOf[sockaddr_storage] val ptr = stackalloc[Byte](size) @@ -129,17 +132,18 @@ private[io] object SocketHelpers { !szPtr = size.toUInt val ret = guardMask( getsockopt(fd, IPPROTO_IP, option, ptr, szPtr) - )(_ == ENOPROTOOPT) - if (ret == ENOPROTOOPT) None else { + )(_ == ENOPROTOOPT) + if (ret == ENOPROTOOPT) None + else { val sockaddr = ptr.asInstanceOf[Ptr[sockaddr_storage]] - if(sockaddr._1 == AF_INET) { + if (sockaddr._1 == AF_INET) { val dstStr = stackalloc[Byte](INET_ADDRSTRLEN) val addr = ptr.asInstanceOf[Ptr[sockaddr_in]] val addr_in = addr.sin_addr val port = htons(addr.sin_port).toInt inet_ntop(AF_INET, addr_in.toPtr.asInstanceOf[CVoidPtr], dstStr, INET_ADDRSTRLEN.toUInt) SocketAddress.fromString4(s"${fromCString(dstStr)}:$port") - } else if(sockaddr._1 == AF_INET6) { + } else if (sockaddr._1 == AF_INET6) { val dstStr = stackalloc[Byte](INET6_ADDRSTRLEN) val addr = ptr.asInstanceOf[Ptr[sockaddr_in6]] val addr_in = addr.sin6_addr @@ -151,7 +155,6 @@ private[io] object SocketHelpers { } } } - } def getOptionImpl[F[_]](fd: CInt, level: CInt, option: CInt)(implicit F: Sync[F] From 3be9fcf411d2731c07d5cf4876d9f60b8d83657a Mon Sep 17 00:00:00 2001 From: RECKEL Roland Date: Fri, 9 Jan 2026 11:14:06 +0100 Subject: [PATCH 4/6] Cleanup imports --- io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala index 18aa2e743e..3f6c964681 100644 --- a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala +++ b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala @@ -36,12 +36,10 @@ import com.comcast.ip4s.{ import java.net.SocketOption import java.net.StandardSocketOptions import scala.scalanative.meta.LinktimeInfo -import scala.scalanative.posix.arpa.inet._ import scala.scalanative.posix.errno.ENOPROTOOPT import scala.scalanative.posix.netinet.in.IPPROTO_TCP import scala.scalanative.posix.netinet.tcp._ import scala.scalanative.posix.string._ -import scala.scalanative.posix.sys.socket._ import scala.scalanative.posix.unistd._ import scala.scalanative.posix.sys.socket._ import scala.scalanative.posix.netinet.in._ From 8e1b26d5402c3cead92442d909db63d99e391852 Mon Sep 17 00:00:00 2001 From: RECKEL Roland Date: Mon, 26 Jan 2026 09:18:04 +0100 Subject: [PATCH 5/6] Use toSocketAddress to convert the address returned by getsockopt --- .../scala/fs2/io/internal/SocketHelpers.scala | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala index 3f6c964681..052cb17f80 100644 --- a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala +++ b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala @@ -42,6 +42,7 @@ import scala.scalanative.posix.netinet.tcp._ import scala.scalanative.posix.string._ import scala.scalanative.posix.unistd._ import scala.scalanative.posix.sys.socket._ +import scala.scalanative.posix.sys.socketOps._ import scala.scalanative.posix.netinet.in._ import scala.scalanative.posix.arpa.inet._ import scala.scalanative.unsafe._ @@ -133,24 +134,8 @@ private[io] object SocketHelpers { )(_ == ENOPROTOOPT) if (ret == ENOPROTOOPT) None else { - val sockaddr = ptr.asInstanceOf[Ptr[sockaddr_storage]] - if (sockaddr._1 == AF_INET) { - val dstStr = stackalloc[Byte](INET_ADDRSTRLEN) - val addr = ptr.asInstanceOf[Ptr[sockaddr_in]] - val addr_in = addr.sin_addr - val port = htons(addr.sin_port).toInt - inet_ntop(AF_INET, addr_in.toPtr.asInstanceOf[CVoidPtr], dstStr, INET_ADDRSTRLEN.toUInt) - SocketAddress.fromString4(s"${fromCString(dstStr)}:$port") - } else if (sockaddr._1 == AF_INET6) { - val dstStr = stackalloc[Byte](INET6_ADDRSTRLEN) - val addr = ptr.asInstanceOf[Ptr[sockaddr_in6]] - val addr_in = addr.sin6_addr - val port = htons(addr.sin6_port).toInt - inet_ntop(AF_INET6, addr_in.toPtr.asInstanceOf[CVoidPtr], dstStr, INET6_ADDRSTRLEN.toUInt) - SocketAddress.fromString6(s"${fromCString(dstStr)}:$port") - } else { - None - } + val sa = ptr.asInstanceOf[Ptr[sockaddr]] + Some(toSocketAddress(sa, sa.sa_family.toInt).asIpUnsafe) } } From 1b3a5629eaa0da4b9a00eba49d10a667d02c5cfb Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Wed, 28 Jan 2026 07:45:43 -0500 Subject: [PATCH 6/6] Remove unused import --- io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala index 052cb17f80..d4d08a917e 100644 --- a/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala +++ b/io/native/src/main/scala/fs2/io/internal/SocketHelpers.scala @@ -49,7 +49,6 @@ import scala.scalanative.unsafe._ import scala.scalanative.unsigned._ import NativeUtil._ -import netinetin._ import netinetinOps._ import syssocket._ import sysun._