-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Try to connect to both IPv4 and IPv6 addresses #430
Conversation
…ferred one first and the other one after a delay, and keep the first one to succeed.
Wow, great job! Would you mind adding some small unit tests? There are some example tests with a simple server socket / client socket that you can repurpose to test ipv4-only server sockets / ipv6-only server sockets against the new client behavior. |
Hmm my last comment disappeared. We need to also translate IPv4 to IPv6 when using raw addresses according to Use System APIs to Synthesize IPv6 Addresses:
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <err.h>
uint8_t ipv4[4] = {192, 0, 2, 1};
struct addrinfo hints, *res, *res0;
int error, s;
const char *cause = NULL;
char ipv4_str_buf[INET_ADDRSTRLEN] = { 0 };
const char *ipv4_str = inet_ntop(AF_INET, &ipv4, ipv4_str_buf, sizeof(ipv4_str_buf));
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
error = getaddrinfo(ipv4_str, "http", &hints, &res0);
if (error) {
errx(1, "%s", gai_strerror(error));
/*NOTREACHED*/
}
s = -1;
for (res = res0; res; res = res->ai_next) {
s = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (s < 0) {
cause = "socket";
continue;
}
if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
cause = "connect";
close(s);
s = -1;
continue;
}
break; /* okay we got one */
}
if (s < 0) {
err(1, "%s", cause);
/*NOTREACHED*/
}
freeaddrinfo(res0); |
Couple of quick comments!
|
@chrisballinger I'm working on adding a few unit tests to test ipv4-only server sockets / ipv6-only server sockets. |
|
…reduced the alternateAddressDelay.
@polmum So the last hesitation I have is, what happens when network lag causes the (correct) protocol chosen first to timeout after 300ms, and then it falls back to the incorrect protocol and fails. For instance, on NAT64 networks like how Apple recommends, getaddrinfo can return both an IPv4 and a synthesized IPv4->IPv6 address. Although the IPv4 address is unreachable, for whatever reason both addresses are returned. We can prefer connection to IPv6, but if network lag causes the IPv6 connection to take longer than 300ms, we will fall back to IPv4 and fail. |
if (alternateAddress) | ||
{ | ||
NSTimeInterval alternateAddressDelay = 0.3; | ||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ |
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.
@polmum This should probably be dispatched on the socketQueue right?
Okay I've taken a second look and my initial assumptions were wrong and it does connect to both protocols and picks the winner. How important is the connection success/failure cache? For most applications I imagine it would be of negligible difference, but it should probably be implemented at some point. |
I made alternateAddressDelay configurable in the ipv6 branch, use socketQueue for dispatch_after, and merges another fix regarding incorrect IPv6 ports: https://github.com/robbiehanson/CocoaAsyncSocket/tree/ipv6 Will probably make a release with this soon assuming no other issues come up. |
@chrisballinger @polmum We've recently shipped an update to our app with the
Digging around into |
@awmwong @chrisballinger @polmum We found the closeSocket: trying to close the same socket twice, because
fired after the the alternateSocketFD had already been closed. We added a check to make sure the socket was either socket6FD or socket4FD before we closed it. An attempt to close the socket the second time will skip the close() because it was set to SOCKET_NULL the first time.
|
@awmwong @jpickering Thanks for the feedback! Good thing we caught this before shipping it out to thousands of other apps. |
Pushed the fix to ipv6 branch. |
Thanks everyone! I plan to make a new release soon that includes this PR. |
If a host has both IPv4 and IPv6 addresses, try to connect to the preferred one first and the other one after a delay. Keep the first connection to succeed and discard the other one.
This pull request is a possible solution to the issue described in https://github.com/robbiehanson/XMPPFramework/issues/718. It is based on the idea of the Happy Eyeballs algorithm, although it is not a complete implementation of it, as it does not cache connection successes and failures.
I tried to modify as few lines of code as possible, although I had to extract a few methods to be able to reuse them for the connection to both addresses.