-
Notifications
You must be signed in to change notification settings - Fork 6
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
IPv6 support #50
Changes from all commits
0518eb4
2f6577f
364a82e
4e7f090
2666855
bac4a0e
c56b8da
137f256
e7dd9a7
a31d868
0fd17f8
3658107
fa8ea51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 { | ||
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 |
---|---|---|
|
@@ -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 = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 It is possible to get a list of interfaces and check if any In my mind, I have been trying to get rid of that startup execution cost. I did exactly what you did in my (various) The difference is that the runtime test means that IPv4 only systems work without effort. With the "check property only So, current code is good enough, IMHO, for scaffolding, There was a problem hiding this comment. Choose a reason for hiding this commentThe 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")) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does parseBoolean do a case-blind comparison? You are probably correct here and I should fix in SN, with There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. Here's what the Javadoc says.
https://docs.oracle.com/javase/8/docs/api/java/lang/Boolean.html#parseBoolean-java.lang.String- There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I am a little confused what we would do with But in our case, we are directly handed an IP address and we have already created a socket according to the value of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kind of punted You can see why I moved I will have to think about and do some design studies on this one. I think the place where 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}") | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am pretty sure this wants to be That is, if you naturally have no IPv6 on the system, do not Gotta love the wide variety of cases we are trying to cover. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 In the specific case you mention, if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the difference is between This is reasoning from the fact that the system property defaults to false, My head is spinning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, I agree. In fact, when we implement this, we should really introduce a new variable There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 My eye is just trained to expect (AI_V4MAPPED | AI_ADDRCONFIG) 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)) | ||
|
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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 ofaccept
. So any improvements should ideally be done in tandem, as we rely on the Scala Native version of this code when callingaccept
on macOS.https://github.com/scala-native/scala-native/blob/2ef67fe81c2a71de7e8a9f57229dbdcb5e178998/posixlib/src/main/resources/scala-native/sys/socket.c#L226-L255
There was a problem hiding this comment.
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..
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.