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

IPv6 support #50

Merged
merged 13 commits into from
Sep 13, 2022
68 changes: 68 additions & 0 deletions core/src/main/resources/scala-native/socket.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2013-2018 EPFL
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifdef __linux__
#define _GNU_SOURCE
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/socket.h>

typedef unsigned short scalanative_sa_family_t;
struct scalanative_sockaddr {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just dealing with this today. You wonder where my
time goes ;-)

I am not quite catching why this "glue" is necessary, but
that is probably lack of cleverness on my end.

I believe that you want to allocate the largest size possible that
accept4 can possibly fill in. Generally, one would use
a sockaddr_storage, defined specifically for that purpose.
That is not usable on 0.4.n (I have not checked 0.4.7 to
see what the merge elf did). So, I suggest sockaddr_in6
and the adjusted length.

One is supposed to do a check after accept to see if the
addrlen passed back exceeded the addrlen passed in.
In this context, sockaddr_in6 is the largest that can be
passed back, so that check can be skipped. However,
it is good to know about that behavior. (if the return size
is larger, that means the buffer passed in was too small
and got truncated/mangled).

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the mysteries of SN 0.4.x. I believe it is necessary due to the use of an intermediate scalanative_sockaddr?

For what it is worth, this code is entirely cribbed from Scala Native itself except switched to use accept4 instead of accept. So any improvements should ideally be done in tandem, as we rely on the Scala Native version of this code when calling accept on macOS.

https://github.com/scala-native/scala-native/blob/2ef67fe81c2a71de7e8a9f57229dbdcb5e178998/posixlib/src/main/resources/scala-native/sys/socket.c#L226-L255

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After years of sustained effort, I finally eliminated "glue" in straight passthru code in SN 0.5.0.
There is some code there to check, at compile time, that the structures are compatible.
IIRC, accept() was one of the ones which were simplified..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this file can go away completely, with
the corresponding change in socket.scala.

If you want to be extremely conservative,
the "@name" and the code here can go away, and this
file becomes just the _Static_assert statements relative
to struct sockaddr.

The 0.5.0 code which does away with these conversions
has been merged into the SN mainline and seem to
have caused no problems.

Copy link
Owner Author

@armanbilge armanbilge Sep 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, are you sure? That's what I did at first and it was not working actually. I had to add this glue code to fix it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Not working"? in what way?

For the sake of expediency, I suggest the merge proceed
with the file and I will do private builds afterwards, from a
stable baseline, to see why reality is not matching my
expectations. It has a habit of doing that, and pulling
my leash hard.

If there is something wrong with removing the file, there may be
an issue in 0.5.0.

Sorry for taking time.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at all, I would like to get to the bottom of this and would be happy to see the glue code go away. See #53.

scalanative_sa_family_t sa_family;
char sa_data[14];
};

int scalanative_convert_sockaddr(struct scalanative_sockaddr *raw_in,
struct sockaddr **out, socklen_t *size);

int scalanative_convert_scalanative_sockaddr(struct sockaddr *raw_in,
struct scalanative_sockaddr *out,
socklen_t *size);

int epollcat_accept4(int socket, struct scalanative_sockaddr *address,
socklen_t *address_len, int flags) {
struct sockaddr *converted_address;
int convert_result = address != NULL ? // addr and addr_len can be NULL
scalanative_convert_sockaddr(
address, &converted_address, address_len)
: 0;

int result;

if (convert_result == 0) {
result = accept4(socket, converted_address, address_len, flags);
convert_result = address != NULL
? scalanative_convert_scalanative_sockaddr(
converted_address, address, address_len)
: 0;

if (convert_result != 0) {
errno = convert_result;
result = -1;
}
} else {
errno = convert_result;
result = -1;
}

if (address != NULL)
free(converted_address);
return result;
}

#endif // linux
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import java.net.StandardSocketOptions
import java.nio.channels.AsynchronousServerSocketChannel
import java.nio.channels.AsynchronousSocketChannel
import java.nio.channels.CompletionHandler
import java.nio.channels.UnsupportedAddressTypeException
import java.util.concurrent.Future
import scala.scalanative.annotation.stub
import scala.scalanative.libc.errno
Expand Down Expand Up @@ -71,36 +70,13 @@ final class EpollAsyncServerSocketChannel private (fd: Int)
def getOption[T](name: SocketOption[T]): T = ???

def bind(local: SocketAddress, backlog: Int): AsynchronousServerSocketChannel = {
val addrinfo = stackalloc[Ptr[posix.netdb.addrinfo]]()
Zone { implicit z =>
val addr = local.asInstanceOf[InetSocketAddress]
val hints = stackalloc[posix.netdb.addrinfo]()
hints.ai_family = posix.sys.socket.AF_INET
hints.ai_flags = posix.netdb.AI_NUMERICHOST | posix.netdb.AI_NUMERICSERV
hints.ai_socktype = posix.sys.socket.SOCK_STREAM
val rtn = posix
.netdb
.getaddrinfo(
toCString(addr.getAddress().getHostAddress()),
toCString(addr.getPort.toString),
hints,
addrinfo
)

if (rtn != 0) {
val ex = if (rtn == posix.netdb.EAI_FAMILY) {
new UnsupportedAddressTypeException()
} else {
val msg = s"getaddrinfo: ${SocketHelpers.getGaiErrorMessage(rtn)}"
new IOException(msg)
}

throw ex
}
val addrinfo = SocketHelpers.toAddrinfo(local.asInstanceOf[InetSocketAddress]) match {
case Left(ex) => throw ex
case Right(addrinfo) => addrinfo
}

val bindRet = posix.sys.socket.bind(fd, (!addrinfo).ai_addr, (!addrinfo).ai_addrlen)
posix.netdb.freeaddrinfo(!addrinfo)
val bindRet = posix.sys.socket.bind(fd, addrinfo.ai_addr, addrinfo.ai_addrlen)
posix.netdb.freeaddrinfo(addrinfo)

// posix.errno.EADDRNOTAVAIL becomes available in Scala Native 0.5.0
val EADDRNOTAVAIL =
Expand Down Expand Up @@ -157,9 +133,10 @@ final class EpollAsyncServerSocketChannel private (fd: Int)
handler: CompletionHandler[AsynchronousSocketChannel, _ >: A]
): Unit = {
if (readReady) {
val addr = stackalloc[posix.netinet.in.sockaddr_in]()
val addr = // allocate enough for an IPv6
stackalloc[posix.netinet.in.sockaddr_in6]().asInstanceOf[Ptr[posix.sys.socket.sockaddr]]
val addrlen = stackalloc[posix.sys.socket.socklen_t]()
!addrlen = sizeof[posix.netinet.in.sockaddr_in].toUInt
!addrlen = sizeof[posix.netinet.in.sockaddr_in6].toUInt
val clientFd =
if (LinktimeInfo.isLinux)
socket.accept4(
Expand All @@ -169,10 +146,7 @@ final class EpollAsyncServerSocketChannel private (fd: Int)
SOCK_NONBLOCK
)
else {
posix
.sys
.socket
.accept(fd, addr.asInstanceOf[Ptr[posix.sys.socket.sockaddr]], addrlen)
posix.sys.socket.accept(fd, addr, addrlen)
}
if (clientFd == -1) {
if (errno.errno == posix.errno.EAGAIN || errno.errno == posix.errno.EWOULDBLOCK) {
Expand All @@ -188,7 +162,16 @@ final class EpollAsyncServerSocketChannel private (fd: Int)
try {
if (!LinktimeInfo.isLinux)
SocketHelpers.setNonBlocking(clientFd)
val ch = EpollAsyncSocketChannel(clientFd, SocketHelpers.toInetSocketAddress(addr))
val inetAddr =
if (SocketHelpers.preferIPv4Stack)
SocketHelpers.toInet4SocketAddress(
addr.asInstanceOf[Ptr[posix.netinet.in.sockaddr_in]]
)
else
SocketHelpers.toInet6SocketAddress(
addr.asInstanceOf[Ptr[posix.netinet.in.sockaddr_in6]]
)
val ch = EpollAsyncSocketChannel(clientFd, inetAddr)
handler.completed(ch, attachment)
} catch {
case NonFatal(ex) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import java.nio.ByteBuffer
import java.nio.channels.AsynchronousSocketChannel
import java.nio.channels.ClosedChannelException
import java.nio.channels.CompletionHandler
import java.nio.channels.UnsupportedAddressTypeException
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import scala.annotation.tailrec
Expand Down Expand Up @@ -165,41 +164,15 @@ final class EpollAsyncSocketChannel private (
attachment: A,
handler: CompletionHandler[Void, _ >: A]
): Unit = {
val addrinfo = stackalloc[Ptr[posix.netdb.addrinfo]]()

val continue = Zone { implicit z =>
val addr = remote.asInstanceOf[InetSocketAddress]
val hints = stackalloc[posix.netdb.addrinfo]()
hints.ai_family = posix.sys.socket.AF_INET
hints.ai_flags = posix.netdb.AI_NUMERICHOST | posix.netdb.AI_NUMERICSERV
hints.ai_socktype = posix.sys.socket.SOCK_STREAM
val rtn = posix
.netdb
.getaddrinfo(
toCString(addr.getAddress().getHostAddress()),
toCString(addr.getPort.toString),
hints,
addrinfo
)
if (rtn == 0) {
true
} else {
val ex = if (rtn == posix.netdb.EAI_FAMILY) {
new UnsupportedAddressTypeException()
} else {
val msg = s"getaddrinfo: ${SocketHelpers.getGaiErrorMessage(rtn)}"
new IOException(msg)
}
handler.failed(ex, attachment)
false
}
val addrinfo = SocketHelpers.toAddrinfo(remote.asInstanceOf[InetSocketAddress]) match {
case Left(ex) =>
return handler.failed(ex, attachment)
case Right(addrinfo) => addrinfo
}

if (!continue)
return ()
val conRet = posix.sys.socket.connect(fd, addrinfo.ai_addr, addrinfo.ai_addrlen)
posix.netdb.freeaddrinfo(addrinfo)

val conRet = posix.sys.socket.connect(fd, (!addrinfo).ai_addr, (!addrinfo).ai_addrlen)
posix.netdb.freeaddrinfo(!addrinfo)
if (conRet == -1 && errno.errno != posix.errno.EINPROGRESS) {
val ex = errno.errno match {
case e if e == posix.errno.ECONNREFUSED =>
Expand Down
85 changes: 72 additions & 13 deletions core/src/main/scala/epollcat/internal/ch/SocketHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,32 @@ import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.SocketAddress
import java.nio.channels.UnsupportedAddressTypeException
import scala.scalanative.libc.errno
import scala.scalanative.meta.LinktimeInfo
import scala.scalanative.posix
import scala.scalanative.posix.netdbOps._
import scala.scalanative.posix.netinet.inOps._
import scala.scalanative.unsafe._

private[ch] object SocketHelpers {

lazy val preferIPv4Stack =
Copy link
Collaborator

@LeeTibbert LeeTibbert Sep 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SN 0.5.0 does a getaddrinfo of a known IPv6 site in a crude
attempt to ascertain if IPv6 support is available.

It is possible to get a list of interfaces and check if any
of them are configured for IPv6. The getaddrinfo() is
an implementation felicity avoid that the hairy code
dealing directly with interfaces.

In my mind, I have been trying to get rid of that startup execution cost. I did exactly what you did in my (various)
epoll IPv6 studies. Thinking of them and reviewing this
now makes me think the initial execution test is worthwhile/necessary.
It also affects preferIPv6Addresses.

The difference is that the runtime test means that IPv4 only systems work without effort. With the "check property only
approach", a developer, not an end user, has to set the
property. The land of ugly.

So, current code is good enough, IMHO, for scaffolding,
but it carries a "due bill" for a runtime check (or better solution)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. This sounds like a due bill and one to chew on as well. I've opened a followup issue.

java.lang.Boolean.parseBoolean(System.getProperty("java.net.preferIPv4Stack", "false"))

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does parseBoolean do a case-blind comparison?
The java docs are pretty unclear about if the property is
Boolean or String. They say
However, in the case an application would rather use IPv4 only sockets, then this property can be set to true. "true" is bolded
but not in double quotes.

You are probably correct here and I should fix in SN, with
your approval.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably needs a placeholder that java.net.preferIPv6Addresses is known, but not implemented this evolution.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Here's what the Javadoc says.

Parses the string argument as a boolean. The boolean returned represents the value true if the string argument is not null and is equal, ignoring case, to the string "true".

Example: Boolean.parseBoolean("True") returns true.
Example: Boolean.parseBoolean("yes") returns false.

https://docs.oracle.com/javase/8/docs/api/java/lang/Boolean.html#parseBoolean-java.lang.String-

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know, thank you for the snippet. So, presumably 0 and 1 would both be Boolean:False?
Sometimes on unix command lines (perhaps bash?) those are also taken as boolean. There
are a bunch of quirks about the case of true/false, where something work and others (True?)
that one would expect to work do not. Like black flies in Maine, just an annoyance one
learn to live with (or pretend to live with).

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably needs a placeholder that java.net.preferIPv6Addresses is known, but not implemented this evolution.

I am a little confused what we would do with preferIPv6Addresses. It seems like this is a useful option for getByName, which has the choice to resolve either an IPv4 address or an IPv6 address.

But in our case, we are directly handed an IP address and we have already created a socket according to the value of preferIPv4Stack. So it's not obvious to me how we should modify our behavior based on this setting.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of punted preferIPv6Addresses in SN 0.5.0 and realized doing this work that I had forgotten that
I had done so. Oops!

You can see why I moved peferIPv6Addresses to the rear echelons. It is a complex feature which
will probably never be used but some reasonable answer must be made for API compatibility.

I will have to think about and do some design studies on this one. I think the place where
I would start is if a host/machine has both IPv4 and IPv6 addresses, preferIPv6Addresses, is
set, and an IPv4 address is presented to getadderinfo. The "pure" IPv6 address should be
used. I just do not know if addrinfo will order the list that way. I suspect it must be done
manually. I believe this a live concern for SN. I am not convinced that such an IPv4
can sneak thru to epoll getadderinfo. I suspect it can.

Again, when this becomes our biggest & most urgent problem, it will be a good day.

def mkNonBlocking(): CInt = {
val SOCK_NONBLOCK =
if (LinktimeInfo.isLinux)
socket.SOCK_NONBLOCK
else 0

val fd = posix
.sys
.socket
.socket(posix.sys.socket.AF_INET, posix.sys.socket.SOCK_STREAM | SOCK_NONBLOCK, 0)
val domain =
if (preferIPv4Stack)
posix.sys.socket.AF_INET
else
posix.sys.socket.AF_INET6

val fd = posix.sys.socket.socket(domain, posix.sys.socket.SOCK_STREAM | SOCK_NONBLOCK, 0)

if (fd == -1)
throw new RuntimeException(s"socket: ${errno.errno}")
Expand Down Expand Up @@ -98,28 +106,79 @@ private[ch] object SocketHelpers {
}

def getLocalAddress(fd: CInt): SocketAddress = {
val addr = stackalloc[posix.netinet.in.sockaddr_in]()
val addr = // allocate enough for an IPv6
stackalloc[posix.netinet.in.sockaddr_in6]().asInstanceOf[Ptr[posix.sys.socket.sockaddr]]
val len = stackalloc[posix.sys.socket.socklen_t]()
!len = sizeof[posix.sys.socket.sockaddr].toUInt
if (posix
.sys
.socket
.getsockname(fd, addr.asInstanceOf[Ptr[posix.sys.socket.sockaddr]], len) == -1)
!len = sizeof[posix.netinet.in.sockaddr_in6].toUInt
if (posix.sys.socket.getsockname(fd, addr, len) == -1)
throw new IOException(s"getsockname: ${errno.errno}")
toInetSocketAddress(addr)
if (preferIPv4Stack)
toInet4SocketAddress(addr.asInstanceOf[Ptr[posix.netinet.in.sockaddr_in]])
else
toInet6SocketAddress(addr.asInstanceOf[Ptr[posix.netinet.in.sockaddr_in6]])
}

def toInetSocketAddress(
def toInet4SocketAddress(
addr: Ptr[posix.netinet.in.sockaddr_in]
): InetSocketAddress = {
val port = posix.arpa.inet.htons(addr.sin_port).toInt
val port = posix.arpa.inet.ntohs(addr.sin_port).toInt
val addrBytes = addr.sin_addr.at1.asInstanceOf[Ptr[Byte]]
val inetAddr = InetAddress.getByAddress(
Array(addrBytes(0), addrBytes(1), addrBytes(2), addrBytes(3))
)
new InetSocketAddress(inetAddr, port)
}

def toInet6SocketAddress(
addr: Ptr[posix.netinet.in.sockaddr_in6]
): InetSocketAddress = {
val port = posix.arpa.inet.ntohs(addr.sin6_port).toInt
val addrBytes = addr.sin6_addr.at1.asInstanceOf[Ptr[Byte]]
val inetAddr = InetAddress.getByAddress {
LeeTibbert marked this conversation as resolved.
Show resolved Hide resolved
val addr = new Array[Byte](16)
LeeTibbert marked this conversation as resolved.
Show resolved Hide resolved
var i = 0
while (i < addr.length) {
addr(i) = addrBytes(i.toLong)
i += 1
}
addr
}
new InetSocketAddress(inetAddr, port)
}

def toAddrinfo(addr: InetSocketAddress): Either[Throwable, Ptr[posix.netdb.addrinfo]] = Zone {
implicit z =>
val addrinfo = stackalloc[Ptr[posix.netdb.addrinfo]]()
val hints = stackalloc[posix.netdb.addrinfo]()
hints.ai_family =
if (preferIPv4Stack)
posix.sys.socket.AF_INET
else
posix.sys.socket.AF_INET6
hints.ai_flags = posix.netdb.AI_NUMERICHOST | posix.netdb.AI_NUMERICSERV
if (!preferIPv4Stack) hints.ai_flags |= posix.netdb.AI_V4MAPPED
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pretty sure this wants to be
(posix.netdb.AI_V4MAPPED | posix.netdb.AI_ADDRCONFIG)

That is, if you naturally have no IPv6 on the system, do not
take a prefectly good IPv4 address and map it to a useless
IPv4mappedIPv6 address 😄

Gotta love the wide variety of cases we are trying to cover.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another one that I'm confused about. The problem is, we've already created the socket according to the value of preferIPv4Stack. So it seems to me that using AI_ADDRCONFIG could get us into trouble?

In the specific case you mention, if preferIPv4Stack = false and the system has no IPv6, then we are already hosed regardless of our choice here 😅

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the difference is between java.net.preferIPv4Stack which can and usually is false,
and the variable preferIPv4Stack. The latter is the reason for the startup runtime test
to see if IPv6 is available. If there is no IPv6, I believe that would set the variable
to true, even if the property is false. The property is "prefer", like a hint.

This is reasoning from the fact that the system property defaults to false,
yet Java appears to run just fine on IPv4 only systems (if you can find one).

My head is spinning.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no IPv6, I believe that would set the variable
to true, even if the property is false.

Yes, I agree. In fact, when we implement this, we should really introduce a new variable useIPv4Stack that is derived from the combination of preferIPv4Stack with the startup runtime test.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I thought of that for SN 0.5.0 and do not remember what I did. If I left it as preferIPv4 it is
becoming evident that was a mistake.

Copy link
Collaborator

@LeeTibbert LeeTibbert Sep 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For historical reference, as we move towards closure:

In tracing this yet again, the line
if (!preferIPv4Stack) hints.ai_flags |= posix.netdb.AI_V4MAPPED
is directly equivalent to OR'in in AI_ADDRCONFIG. The mapping
only gets done when IPv6 is available.

My eye is just trained to expect (AI_V4MAPPED | AI_ADDRCONFIG)
and alerted.

No need for the OR in this case.

hints.ai_socktype = posix.sys.socket.SOCK_STREAM
val rtn = posix
.netdb
.getaddrinfo(
toCString(addr.getAddress().getHostAddress()),
toCString(addr.getPort.toString),
hints,
addrinfo
)
if (rtn == 0) {
Right(!addrinfo)
} else {
val ex = if (rtn == posix.netdb.EAI_FAMILY) {
new UnsupportedAddressTypeException()
} else {
val msg = s"getaddrinfo: ${SocketHelpers.getGaiErrorMessage(rtn)}"
new IOException(msg)
}
Left(ex)
}
}

// Return text translation of getaddrinfo (gai) error code.
def getGaiErrorMessage(gaiErrorCode: CInt): String = {
fromCString(posix.netdb.gai_strerror(gaiErrorCode))
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/epollcat/internal/ch/socket.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ private[ch] object socket {
final val SOCK_NONBLOCK = 2048 // only in Linux and FreeBSD, but not macOS

// only supported on Linux and FreeBSD, but not macOS
@name("epollcat_accept4") // can remove glue code in SN 0.5
def accept4(sockfd: CInt, addr: Ptr[sockaddr], addrlen: Ptr[socklen_t], flags: CInt): CInt =
extern
}
Loading