| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | conncheckshared.h |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains a simple network connectivity check shared between Windows and Linux. |
| 12 | --*/ |
| 13 | |
| 14 | #pragma once |
| 15 | |
| 16 | #if defined(_MSC_VER) |
| 17 | // Windows |
| 18 | #include <winsock2.h> |
| 19 | #include <windows.h> |
| 20 | #define ConnCheckGetLastError() WSAGetLastError() |
| 21 | #define CONNCHECK_ERROR_PENDING WSAEWOULDBLOCK |
| 22 | #else |
| 23 | // Linux |
| 24 | #include <netdb.h> |
| 25 | #include <errno.h> |
| 26 | #include <fcntl.h> |
| 27 | #include <string.h> |
| 28 | #include <unistd.h> |
| 29 | #include <sys/param.h> |
| 30 | #include <sys/select.h> |
| 31 | #include <sys/socket.h> |
| 32 | #include <sys/time.h> |
| 33 | #include <arpa/inet.h> |
| 34 | #include <wchar.h> |
| 35 | #define ConnCheckGetLastError() errno |
| 36 | #define CONNCHECK_ERROR_PENDING EINPROGRESS |
| 37 | #endif |
| 38 | |
| 39 | namespace wsl::shared::conncheck { |
| 40 | |
| 41 | #if defined(_MSC_VER) |
| 42 | using unique_socket = wil::unique_socket; |
| 43 | #else |
| 44 | using unique_socket = wil::unique_fd; |
| 45 | #endif |
| 46 | |
| 47 | enum class ConnCheckStatus |
| 48 | { |
| 49 | InProgress, |
| 50 | Success, |
| 51 | FailureGetAddrInfo, |
| 52 | FailureConfig, |
| 53 | FailureSocketConnect, |
| 54 | }; |
| 55 | |
| 56 | struct ConnCheckResult |
| 57 | { |
| 58 | ConnCheckStatus Ipv4Status{}; |
| 59 | ConnCheckStatus Ipv6Status{}; |
| 60 | }; |
| 61 | |
| 62 | inline unique_socket ConnCheckConfigureSocket(int family, int socktype, int protocol) |
| 63 | { |
| 64 | unique_socket sock{::socket(family, socktype, protocol)}; |
| 65 | if (!sock) |
| 66 | { |
| 67 | throw std::runtime_error(std::format("CheckConnection: socket() failed: {}", ConnCheckGetLastError())); |
| 68 | } |
| 69 | |
| 70 | #if defined(_MSC_VER) |
| 71 | unsigned long value = 1; |
| 72 | const int status = ::ioctlsocket(sock.get(), FIONBIO, &value); |
| 73 | if (status != 0) |
| 74 | { |
| 75 | throw std::runtime_error(std::format("CheckConnection: ioctlsocket(FIONBIO) failed: {}", ConnCheckGetLastError())); |
| 76 | } |
| 77 | #else |
| 78 | int value = fcntl(sock.get(), F_GETFL, 0); |
| 79 | if (value < 0) |
| 80 | { |
| 81 | throw std::runtime_error(std::format("CheckConnection: fcntl(F_GETFL) failed: {}", ConnCheckGetLastError())); |
| 82 | } |
| 83 | value = fcntl(sock.get(), F_SETFL, value | O_NONBLOCK); |
| 84 | if (value < 0) |
| 85 | { |
| 86 | throw std::runtime_error(std::format("CheckConnection: fcntl(F_SETFL) failed: {}", ConnCheckGetLastError())); |
| 87 | } |
| 88 | #endif |
| 89 | |
| 90 | return sock; |
| 91 | } |
| 92 | |
| 93 | inline unique_socket ConnCheckConnectSocket(int family, const char* hostname, const char* port, ConnCheckResult* resultStatus) noexcept |
| 94 | { |
| 95 | // update the ConnCheckStatus as we attempt to connect |
| 96 | ConnCheckStatus* connCheckStatus = (family == AF_INET) ? &(resultStatus->Ipv4Status) : &(resultStatus->Ipv6Status); |
| 97 | unique_socket sock; |
| 98 | try |
| 99 | { |
| 100 | // first step, try to resolve the name |
| 101 | *connCheckStatus = ConnCheckStatus::FailureGetAddrInfo; |
| 102 | |
| 103 | printf("CheckConnection: resolving the name %s [%s]\n", hostname, (family == AF_INET) ? "AF_INET" : "AF_INET6"); |
| 104 | addrinfo* servinfo = nullptr; |
| 105 | const auto freeAddrInfoOnExit = wil::scope_exit([&] { |
| 106 | if (servinfo) |
| 107 | { |
| 108 | freeaddrinfo(servinfo); |
| 109 | } |
| 110 | }); |
| 111 | |
| 112 | addrinfo hints{}; |
| 113 | hints.ai_family = family; |
| 114 | hints.ai_socktype = SOCK_STREAM; |
| 115 | hints.ai_flags = AI_NUMERICSERV; |
| 116 | auto status = getaddrinfo(hostname, port, &hints, &servinfo); |
| 117 | if (status != 0) |
| 118 | { |
| 119 | throw std::runtime_error(std::format("CheckConnection: getaddrinfo() failed: {}", status)); |
| 120 | } |
| 121 | |
| 122 | // next configure the socket |
| 123 | *connCheckStatus = ConnCheckStatus::FailureConfig; |
| 124 | sock = ConnCheckConfigureSocket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol); |
| 125 | |
| 126 | const void* pAddr = (AF_INET == family) |
| 127 | ? static_cast<const void*>(&(reinterpret_cast<sockaddr_in*>(servinfo->ai_addr))->sin_addr) |
| 128 | : static_cast<const void*>(&(reinterpret_cast<sockaddr_in6*>(servinfo->ai_addr))->sin6_addr); |
| 129 | char dst[INET6_ADDRSTRLEN * 2]{}; |
| 130 | printf("CheckConnection: connecting to %s\n", inet_ntop(servinfo->ai_family, pAddr, dst, sizeof(dst))); |
| 131 | |
| 132 | // next connect the socket |
| 133 | *connCheckStatus = ConnCheckStatus::FailureSocketConnect; |
| 134 | status = connect(sock.get(), servinfo->ai_addr, static_cast<int>(servinfo->ai_addrlen)); |
| 135 | if (status != 0 && ConnCheckGetLastError() != CONNCHECK_ERROR_PENDING) |
| 136 | { |
| 137 | throw std::runtime_error(std::format("CheckConnection: connect() failed: {}", ConnCheckGetLastError())); |
| 138 | } |
| 139 | |
| 140 | // success |
| 141 | *connCheckStatus = ConnCheckStatus::InProgress; |
| 142 | } |
| 143 | CATCH_LOG() |
| 144 | |
| 145 | return sock; |
| 146 | } |
| 147 | |
| 148 | // Attempts to establish a TCPv4 and a TCPv6 connection to a port on a host. |
| 149 | // ipv6hostname is an optional parameter in case the IPv6 equivalent hostname is different. |
| 150 | // example: www.msftconnecttest.com and ipv6.msftconnecttest.com |
| 151 | // This API is blocking/synchronous. |
| 152 | inline ConnCheckResult CheckConnection(const char* hostname, const char* ipv6hostname, const char* port) |
| 153 | { |
| 154 | ConnCheckResult result{}; |
| 155 | result.Ipv4Status = ConnCheckStatus::InProgress; |
| 156 | result.Ipv6Status = ConnCheckStatus::InProgress; |
| 157 | |
| 158 | if (ipv6hostname == nullptr) |
| 159 | { |
| 160 | ipv6hostname = hostname; |
| 161 | } |
| 162 | |
| 163 | const auto v4sock = ConnCheckConnectSocket(AF_INET, hostname, port, &result); |
| 164 | const auto v6sock = ConnCheckConnectSocket(AF_INET6, ipv6hostname, port, &result); |
| 165 | const auto startTime = std::chrono::steady_clock::now(); |
| 166 | |
| 167 | while (result.Ipv4Status == ConnCheckStatus::InProgress || result.Ipv6Status == ConnCheckStatus::InProgress) |
| 168 | { |
| 169 | constexpr long maxMillisecondsElapsed = 5000; |
| 170 | const auto currentTime = std::chrono::steady_clock::now(); |
| 171 | const auto elapsedTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime); |
| 172 | if (elapsedTimeMs.count() >= maxMillisecondsElapsed) |
| 173 | { |
| 174 | if (result.Ipv4Status == ConnCheckStatus::InProgress) |
| 175 | { |
| 176 | wprintf(L"CheckConnection: result.Ipv4Status = ConnCheckStatus::FailureSocketConnect\n"); |
| 177 | result.Ipv4Status = ConnCheckStatus::FailureSocketConnect; |
| 178 | } |
| 179 | if (result.Ipv6Status == ConnCheckStatus::InProgress) |
| 180 | { |
| 181 | wprintf(L"CheckConnection: result.Ipv6Status = ConnCheckStatus::FailureSocketConnect\n"); |
| 182 | result.Ipv6Status = ConnCheckStatus::FailureSocketConnect; |
| 183 | } |
| 184 | // bailing if we have timed out |
| 185 | break; |
| 186 | } |
| 187 | |
| 188 | fd_set writeSocketSet{}; |
| 189 | if (result.Ipv4Status == ConnCheckStatus::InProgress) |
| 190 | { |
| 191 | FD_SET(v4sock.get(), &writeSocketSet); |
| 192 | } |
| 193 | if (result.Ipv6Status == ConnCheckStatus::InProgress) |
| 194 | { |
| 195 | FD_SET(v6sock.get(), &writeSocketSet); |
| 196 | } |
| 197 | |
| 198 | const int maxsocket = std::max( |
| 199 | (result.Ipv4Status == ConnCheckStatus::InProgress) ? (int)v4sock.get() : -1, |
| 200 | (result.Ipv6Status == ConnCheckStatus::InProgress) ? (int)v6sock.get() : -1); |
| 201 | if (maxsocket != -1) |
| 202 | { |
| 203 | timeval timeout{}; |
| 204 | timeout.tv_sec = static_cast<long>(maxMillisecondsElapsed - elapsedTimeMs.count()) / 1000; |
| 205 | wprintf(L"CheckConnection: arming select for %d seconds\n", timeout.tv_sec); |
| 206 | const auto status = select(maxsocket + 1, nullptr, &writeSocketSet, nullptr, &timeout); |
| 207 | if (status == 0) |
| 208 | { |
| 209 | if (result.Ipv4Status == ConnCheckStatus::InProgress) |
| 210 | { |
| 211 | wprintf(L"CheckConnection: result.Ipv4Status = ConnCheckStatus::FailureSocketConnect (timeout)\n"); |
| 212 | result.Ipv4Status = ConnCheckStatus::FailureSocketConnect; |
| 213 | } |
| 214 | if (result.Ipv6Status == ConnCheckStatus::InProgress) |
| 215 | { |
| 216 | wprintf(L"CheckConnection: result.Ipv6Status = ConnCheckStatus::FailureSocketConnect (timeout)\n"); |
| 217 | result.Ipv6Status = ConnCheckStatus::FailureSocketConnect; |
| 218 | } |
| 219 | } |
| 220 | else if (status < 0) |
| 221 | { |
| 222 | const auto error = errno; |
| 223 | if (result.Ipv4Status == ConnCheckStatus::InProgress) |
| 224 | { |
| 225 | wprintf(L"CheckConnection: result.Ipv4Status = ConnCheckStatus::FailureSocketConnect (%d)\n", error); |
| 226 | result.Ipv4Status = ConnCheckStatus::FailureSocketConnect; |
| 227 | } |
| 228 | if (result.Ipv6Status == ConnCheckStatus::InProgress) |
| 229 | { |
| 230 | wprintf(L"CheckConnection: result.Ipv6Status = ConnCheckStatus::FailureSocketConnect (%d)\n", error); |
| 231 | result.Ipv6Status = ConnCheckStatus::FailureSocketConnect; |
| 232 | } |
| 233 | } |
| 234 | else |
| 235 | { |
| 236 | // Success. |
| 237 | if (v4sock) |
| 238 | { |
| 239 | if (FD_ISSET(v4sock.get(), &writeSocketSet)) |
| 240 | { |
| 241 | wprintf(L"CheckConnection: v4 succeeded\n"); |
| 242 | result.Ipv4Status = ConnCheckStatus::Success; |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | if (v6sock) |
| 247 | { |
| 248 | if (FD_ISSET(v6sock.get(), &writeSocketSet)) |
| 249 | { |
| 250 | wprintf(L"CheckConnection: v6 succeeded\n"); |
| 251 | result.Ipv6Status = ConnCheckStatus::Success; |
| 252 | } |
| 253 | } |
| 254 | } |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | wprintf(L"CheckConnection: returning v4 (%d) v6 (%d)\n", result.Ipv4Status, result.Ipv6Status); |
| 259 | return result; |
| 260 | } |
| 261 | } // namespace wsl::shared::conncheck |