master
h 261 lines 9.5 KB
Raw
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