Use resolveHostName() instead of getaddrinfo() and connectTcpSocket() directly

This commit is contained in:
Cameron Gutman 2020-08-29 18:27:02 -07:00
parent e74fe2b016
commit feaca5b5b0

View File

@ -78,9 +78,8 @@ unsigned short LiGetPortFromPortFlagIndex(int portFlagIndex)
unsigned int LiTestClientConnectivity(const char* testServer, unsigned short referencePort, unsigned int testPortFlags) unsigned int LiTestClientConnectivity(const char* testServer, unsigned short referencePort, unsigned int testPortFlags)
{ {
unsigned int failingPortFlags; unsigned int failingPortFlags;
struct addrinfo* serverAddrs; struct sockaddr_storage address;
struct addrinfo* current; SOCKADDR_LEN address_length;
struct addrinfo hints;
int i; int i;
int err; int err;
SOCKET sockets[PORT_FLAGS_MAX_COUNT]; SOCKET sockets[PORT_FLAGS_MAX_COUNT];
@ -103,215 +102,183 @@ unsigned int LiTestClientConnectivity(const char* testServer, unsigned short ref
return ML_TEST_RESULT_INCONCLUSIVE; return ML_TEST_RESULT_INCONCLUSIVE;
} }
memset(&hints, 0, sizeof(hints)); err = resolveHostName(testServer, AF_UNSPEC, referencePort, &address, &address_length);
hints.ai_family = AF_UNSPEC; if (err != 0) {
hints.ai_flags = AI_ADDRCONFIG; return ML_TEST_RESULT_INCONCLUSIVE;
err = getaddrinfo(testServer, NULL, &hints, &serverAddrs);
if (err != 0 || serverAddrs == NULL) {
Limelog("Failed to resolve test server: %d\n", err);
serverAddrs = NULL;
failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE;
goto Exit;
} }
for (current = serverAddrs; failingPortFlags != 0 && current != NULL; current = current->ai_next) { for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) {
// Test to see if this address is even reachable on a standard port. if (testPortFlags & (1 << i)) {
// This will let us distinguish between port-specific blocks and IP-specific blocks. sockets[i] = socket(address.ss_family,
SOCKET testSocket = connectTcpSocket((struct sockaddr_storage*)current->ai_addr, LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM,
current->ai_addrlen, LiGetProtocolFromPortFlagIndex(i));
referencePort, if (sockets[i] == INVALID_SOCKET) {
TEST_PORT_TIMEOUT_SEC); err = LastSocketFail();
if (testSocket == INVALID_SOCKET) { Limelog("Failed to create socket: %d\n", err);
Limelog("Skipping unavailable test host\n"); failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE;
continue; goto Exit;
} }
else {
closeSocket(testSocket);
}
for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) { #ifdef LC_DARWIN
if (testPortFlags & (1 << i)) { {
sockets[i] = socket(current->ai_family, // Disable SIGPIPE on iOS
LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM, int val = 1;
LiGetProtocolFromPortFlagIndex(i)); setsockopt(sockets[i], SOL_SOCKET, SO_NOSIGPIPE, (char*)&val, sizeof(val));
if (sockets[i] == INVALID_SOCKET) { }
#endif
((struct sockaddr_in6*)&address)->sin6_port = htons(LiGetPortFromPortFlagIndex(i));
if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_TCP) {
// Enable non-blocking I/O for connect timeout support
if (setSocketNonBlocking(sockets[i] , 1) != 0) {
// If non-blocking sockets are not available, TCP tests are not supported
err = LastSocketFail(); err = LastSocketFail();
Limelog("Failed to create socket: %d\n", err); Limelog("Failed to enable non-blocking I/O: %d\n", err);
failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE; failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE;
goto Exit; goto Exit;
} }
#ifdef LC_DARWIN // Initiate an asynchronous connection
{ err = connect(sockets[i], (struct sockaddr*)&address, address_length);
// Disable SIGPIPE on iOS if (err < 0) {
int val = 1; err = (int)LastSocketError();
setsockopt(sockets[i], SOL_SOCKET, SO_NOSIGPIPE, (char*)&val, sizeof(val)); if (err != EWOULDBLOCK && err != EAGAIN && err != EINPROGRESS) {
} Limelog("Failed to start async connect to TCP %u: %d\n", LiGetPortFromPortFlagIndex(i), err);
#endif
((struct sockaddr_in6*)current->ai_addr)->sin6_port = htons(LiGetPortFromPortFlagIndex(i)); // Mask off this bit so we don't try to include it in pollSockets() below
if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_TCP) { testPortFlags &= ~(1 << i);
// Enable non-blocking I/O for connect timeout support
if (setSocketNonBlocking(sockets[i] , 1) != 0) {
// If non-blocking sockets are not available, TCP tests are not supported
err = LastSocketFail();
Limelog("Failed to enable non-blocking I/O: %d\n", err);
failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE;
goto Exit;
} }
}
}
else {
const char buf[] = {'C', 'T', 'E', 'S', 'T'};
int j;
// Initiate an asynchronous connection // Send a few packets since UDP is unreliable
err = connect(sockets[i], current->ai_addr, current->ai_addrlen); for (j = 0; j < 3; j++) {
err = sendto(sockets[i], buf, sizeof(buf), 0, (struct sockaddr*)&address, address_length);
if (err < 0) { if (err < 0) {
err = (int)LastSocketError(); err = (int)LastSocketError();
if (err != EWOULDBLOCK && err != EAGAIN && err != EINPROGRESS) { Limelog("Failed to send test packet to UDP %u: %d\n", LiGetPortFromPortFlagIndex(i), err);
Limelog("Failed to start async connect to TCP %u: %d\n", LiGetPortFromPortFlagIndex(i), err);
// Mask off this bit so we don't try to include it in pollSockets() below // Mask off this bit so we don't try to include it in pollSockets() below
testPortFlags &= ~(1 << i); testPortFlags &= ~(1 << i);
}
break;
}
PltSleepMs(50);
}
}
}
}
// Continue to call pollSockets() until we have no more sockets to wait for,
// or our pollSockets() call times out.
while (testPortFlags != 0) {
int nfds;
struct pollfd pfds[PORT_FLAGS_MAX_COUNT];
nfds = 0;
// Fill out our FD sets
for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) {
if (testPortFlags & (1 << i)) {
pfds[nfds].fd = sockets[i];
if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP) {
// Watch for readability on UDP sockets
pfds[nfds].events = POLLIN;
}
else {
// Watch for writeability on TCP sockets
pfds[nfds].events = POLLOUT;
}
nfds++;
}
}
// Wait for the to complete or the timeout to elapse.
// NB: The timeout resets each time we get a valid response on a port,
// but that's probably fine.
err = pollSockets(pfds, nfds, TEST_PORT_TIMEOUT_SEC * 1000);
if (err < 0) {
// pollSockets() failed
err = LastSocketError();
Limelog("pollSockets() failed: %d\n", err);
failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE;
goto Exit;
}
else if (err == 0) {
// pollSockets() timed out
Limelog("Connection timed out after %d seconds\n", TEST_PORT_TIMEOUT_SEC);
break;
}
// We know something was signalled. Now we just need to find out what.
for (i = 0; i < nfds; i++) {
if (pfds[i].revents != 0) {
int portIndex;
// This socket was signalled. Figure out what port it was.
for (portIndex = 0; portIndex < PORT_FLAGS_MAX_COUNT; portIndex++) {
if (sockets[portIndex] == pfds[i].fd) {
LC_ASSERT(testPortFlags & (1 << portIndex));
break;
}
}
LC_ASSERT(portIndex != PORT_FLAGS_MAX_COUNT);
if (LiGetProtocolFromPortFlagIndex(portIndex) == IPPROTO_UDP) {
char buf[32];
// A UDP socket was signalled. This could be because we got
// a packet from the test server, or it could be because we
// received an ICMP error which will be given to us from
// recvfrom().
testPortFlags &= ~(1 << portIndex);
// Check if the socket can be successfully read now
err = recvfrom(sockets[portIndex], buf, sizeof(buf), 0, NULL, NULL);
if (err >= 0) {
// The UDP test was a success.
failingPortFlags &= ~(1 << portIndex);
Limelog("UDP port %u test successful\n", LiGetPortFromPortFlagIndex(portIndex));
}
else {
err = LastSocketError();
Limelog("UDP port %u test failed: %d\n", LiGetPortFromPortFlagIndex(portIndex), err);
} }
} }
else { else {
const char buf[] = {'C', 'T', 'E', 'S', 'T'}; // A TCP socket was signalled
int j; SOCKADDR_LEN len = sizeof(err);
getsockopt(sockets[portIndex], SOL_SOCKET, SO_ERROR, (char*)&err, &len);
if (err != 0 || (pfds[i].revents & POLLERR)) {
// Get the error code
err = (err != 0) ? err : LastSocketFail();
}
// Send a few packets since UDP is unreliable // The TCP test has completed for this port
for (j = 0; j < 3; j++) { testPortFlags &= ~(1 << portIndex);
err = sendto(sockets[i], buf, sizeof(buf), 0, current->ai_addr, current->ai_addrlen); if (err == 0) {
if (err < 0) { // The TCP test was a success
err = (int)LastSocketError(); failingPortFlags &= ~(1 << portIndex);
Limelog("Failed to send test packet to UDP %u: %d\n", LiGetPortFromPortFlagIndex(i), err);
// Mask off this bit so we don't try to include it in pollSockets() below Limelog("TCP port %u test successful\n", LiGetPortFromPortFlagIndex(portIndex));
testPortFlags &= ~(1 << i); }
else {
break; Limelog("TCP port %u test failed: %d\n", LiGetPortFromPortFlagIndex(portIndex), err);
}
PltSleepMs(50);
} }
} }
} }
} }
// Continue to call pollSockets() until we have no more sockets to wait for, // Next iteration, we'll remove the matching sockets from our FD set and
// or our pollSockets() call times out. // call select() again to wait on the remaining sockets.
while (testPortFlags != 0) {
int nfds;
struct pollfd pfds[PORT_FLAGS_MAX_COUNT];
nfds = 0;
// Fill out our FD sets
for (i = 0; i < PORT_FLAGS_MAX_COUNT; i++) {
if (testPortFlags & (1 << i)) {
pfds[nfds].fd = sockets[i];
if (LiGetProtocolFromPortFlagIndex(i) == IPPROTO_UDP) {
// Watch for readability on UDP sockets
pfds[nfds].events = POLLIN;
}
else {
// Watch for writeability on TCP sockets
pfds[nfds].events = POLLOUT;
}
nfds++;
}
}
// Wait for the to complete or the timeout to elapse.
// NB: The timeout resets each time we get a valid response on a port,
// but that's probably fine.
err = pollSockets(pfds, nfds, TEST_PORT_TIMEOUT_SEC * 1000);
if (err < 0) {
// pollSockets() failed
err = LastSocketError();
Limelog("pollSockets() failed: %d\n", err);
failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE;
goto Exit;
}
else if (err == 0) {
// pollSockets() timed out
Limelog("Connection timed out after %d seconds\n", TEST_PORT_TIMEOUT_SEC);
break;
}
// We know something was signalled. Now we just need to find out what.
for (i = 0; i < nfds; i++) {
if (pfds[i].revents != 0) {
int portIndex;
// This socket was signalled. Figure out what port it was.
for (portIndex = 0; portIndex < PORT_FLAGS_MAX_COUNT; portIndex++) {
if (sockets[portIndex] == pfds[i].fd) {
LC_ASSERT(testPortFlags & (1 << portIndex));
break;
}
}
LC_ASSERT(portIndex != PORT_FLAGS_MAX_COUNT);
if (LiGetProtocolFromPortFlagIndex(portIndex) == IPPROTO_UDP) {
char buf[32];
// A UDP socket was signalled. This could be because we got
// a packet from the test server, or it could be because we
// received an ICMP error which will be given to us from
// recvfrom().
testPortFlags &= ~(1 << portIndex);
// Check if the socket can be successfully read now
err = recvfrom(sockets[portIndex], buf, sizeof(buf), 0, NULL, NULL);
if (err >= 0) {
// The UDP test was a success.
failingPortFlags &= ~(1 << portIndex);
Limelog("UDP port %u test successful\n", LiGetPortFromPortFlagIndex(portIndex));
}
else {
err = LastSocketError();
Limelog("UDP port %u test failed: %d\n", LiGetPortFromPortFlagIndex(portIndex), err);
}
}
else {
// A TCP socket was signalled
SOCKADDR_LEN len = sizeof(err);
getsockopt(sockets[portIndex], SOL_SOCKET, SO_ERROR, (char*)&err, &len);
if (err != 0 || (pfds[i].revents & POLLERR)) {
// Get the error code
err = (err != 0) ? err : LastSocketFail();
}
// The TCP test has completed for this port
testPortFlags &= ~(1 << portIndex);
if (err == 0) {
// The TCP test was a success
failingPortFlags &= ~(1 << portIndex);
Limelog("TCP port %u test successful\n", LiGetPortFromPortFlagIndex(portIndex));
}
else {
Limelog("TCP port %u test failed: %d\n", LiGetPortFromPortFlagIndex(portIndex), err);
}
}
}
}
// Next iteration, we'll remove the matching sockets from our FD set and
// call select() again to wait on the remaining sockets.
}
// We don't need to try another server if we got this far
break;
}
if (current == NULL) {
// None of the addresses we were given worked
failingPortFlags = ML_TEST_RESULT_INCONCLUSIVE;
goto Exit;
} }
Exit: Exit:
@ -321,10 +288,6 @@ Exit:
} }
} }
if (serverAddrs != NULL) {
freeaddrinfo(serverAddrs);
}
cleanupPlatformSockets(); cleanupPlatformSockets();
return failingPortFlags; return failingPortFlags;
} }