master
cpp 463 lines 16.5 KB
Raw
1 // Copyright (C) Microsoft Corporation. All rights reserved.
2
3 #include <arpa/inet.h>
4 #include <sys/epoll.h>
5 #include <netinet/in.h>
6 #include <sys/socket.h>
7 #include "RuntimeErrorWithSourceLocation.h"
8 #include "DnsServer.h"
9 #include "Syscall.h"
10 #include "util.h"
11
12 // Port used by DNS server
13 constexpr int c_dnsServerPort = 53;
14 // Max number of events to be returned by epoll_wait()
15 constexpr int c_epollWaitMaxEvents = 100;
16 // Maximum size of DNS over UDP requests is 4096 bytes (max size is reached for EDNS UDP requests)
17 constexpr int c_maxUdpDnsBufferSize = 4096;
18 // Maximum time to wait for a tunneled UDP DNS response
19 constexpr auto c_udpRequestTimeout = std::chrono::seconds{60};
20 // Max number of pending connections in the TCP listen queue
21 constexpr int c_maxListenBacklog = 1000;
22
23 DnsServer::DnsServer(DnsTunnelingCallback&& tunnelDnsRequest) : m_tunnelDnsRequest(std::move(tunnelDnsRequest))
24 {
25 }
26
27 DnsServer::~DnsServer() noexcept
28 {
29 Stop();
30 }
31
32 void DnsServer::Start(const std::string& ipAddress) noexcept
33 try
34 {
35 // Create epoll handler fd. 0 represents default flags
36 m_epollFd = Syscall(epoll_create1, 0);
37
38 StartUdpDnsServer(ipAddress);
39 StartTcpDnsServer(ipAddress);
40
41 // Create and register the shutdown pipe with epoll
42 m_shutdownServerLoopPipe = wil::unique_pipe::create(0);
43
44 epoll_event event{};
45 event.events = EPOLLIN;
46 event.data.fd = m_shutdownServerLoopPipe.read().get();
47 Syscall(epoll_ctl, m_epollFd.get(), EPOLL_CTL_ADD, m_shutdownServerLoopPipe.read().get(), &event);
48
49 // Start server loop
50 m_serverThread = std::thread([this]() { ServerLoop(); });
51 }
52 CATCH_LOG()
53
54 void DnsServer::StartUdpDnsServer(const std::string& ipAddress) noexcept
55 try
56 {
57 sockaddr_in serverAddr{};
58
59 serverAddr.sin_family = AF_INET;
60 Syscall(inet_pton, AF_INET, ipAddress.c_str(), &serverAddr.sin_addr);
61 serverAddr.sin_port = htons(c_dnsServerPort);
62
63 // Create IPv4 UDP socket
64 m_udpSocket = Syscall(socket, AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
65
66 // Bind socket
67 Syscall(bind, m_udpSocket.get(), reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr));
68
69 // Configure epoll to track the UDP socket. EPOLLIN is used to get epoll notifications
70 // whenever there is data available to be read from the socket
71 epoll_event event{};
72 event.events = EPOLLIN;
73 event.data.fd = m_udpSocket.get();
74 Syscall(epoll_ctl, m_epollFd.get(), EPOLL_CTL_ADD, m_udpSocket.get(), &event);
75
76 GNS_LOG_INFO("Successfully started UDP server on IP {}", ipAddress.c_str());
77 }
78 CATCH_LOG()
79
80 void DnsServer::StartTcpDnsServer(const std::string& ipAddress) noexcept
81 try
82 {
83 sockaddr_in serverAddr{};
84
85 serverAddr.sin_family = AF_INET;
86 Syscall(inet_pton, AF_INET, ipAddress.c_str(), &serverAddr.sin_addr);
87 serverAddr.sin_port = htons(c_dnsServerPort);
88
89 // Create IPv4 TCP socket
90 m_tcpListenSocket = Syscall(socket, AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
91
92 // Bind socket
93 Syscall(bind, m_tcpListenSocket.get(), reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr));
94
95 // Listen for incoming connections
96 Syscall(listen, m_tcpListenSocket.get(), c_maxListenBacklog);
97
98 // Configure epoll to track the TCP listening socket. EPOLLIN is used to get epoll notifications
99 // whenever there is a new incoming TCP connection.
100 epoll_event event{};
101 event.events = EPOLLIN;
102 event.data.fd = m_tcpListenSocket.get();
103 Syscall(epoll_ctl, m_epollFd.get(), EPOLL_CTL_ADD, m_tcpListenSocket.get(), &event);
104
105 GNS_LOG_INFO("Successfully started TCP server on IP {}", ipAddress.c_str());
106 }
107 CATCH_LOG();
108
109 void DnsServer::HandleUdpDnsResponse(const gsl::span<gsl::byte> dnsBuffer, const LX_GNS_DNS_CLIENT_IDENTIFIER& dnsClientIdentifier) noexcept
110 try
111 {
112 GNS_LOG_INFO("New UDP DNS response DNS buffer size: {}, UDP request id: {}", dnsBuffer.size(), dnsClientIdentifier.DnsClientId);
113
114 std::scoped_lock<std::mutex> lock{m_udpLock};
115
116 auto it = m_udpRequests.find(dnsClientIdentifier.DnsClientId);
117 if (it == m_udpRequests.end())
118 {
119 GNS_LOG_ERROR("Received a response for a UDP request that is not tracked, UDP request id: {}", dnsClientIdentifier.DnsClientId);
120 return;
121 }
122
123 // Stop tracking the request, irrespective of the DNS response being successfully sent
124 const auto removeDnsRequest = wil::scope_exit([&] {
125 m_udpRequestExpirations.erase(it->second.m_expiration);
126 m_udpRequests.erase(it);
127 });
128
129 sockaddr_in& remoteAddr = it->second.m_remoteAddress;
130
131 // Send DNS response buffer back to the Linux DNS client
132 int bufferSize = dnsBuffer.size();
133 int totalBytesSent = 0;
134
135 while (totalBytesSent < bufferSize)
136 {
137 int bytesSent = Syscall(
138 sendto, m_udpSocket.get(), dnsBuffer.data() + totalBytesSent, bufferSize - totalBytesSent, 0, reinterpret_cast<sockaddr*>(&remoteAddr), sizeof(remoteAddr));
139 totalBytesSent += bytesSent;
140 }
141 }
142 CATCH_LOG()
143
144 void DnsServer::HandleTcpDnsResponse(const gsl::span<gsl::byte> dnsBuffer, const LX_GNS_DNS_CLIENT_IDENTIFIER& dnsClientIdentifier) noexcept
145 try
146 {
147 GNS_LOG_INFO(
148 "New TCP DNS response "
149 "DNS buffer size: {}, TCP connection id: {}",
150 dnsBuffer.size(),
151 dnsClientIdentifier.DnsClientId);
152
153 std::scoped_lock<std::mutex> lock{m_tcpLock};
154
155 auto it = m_tcpConnectionContexts.find(dnsClientIdentifier.DnsClientId);
156 if (it == m_tcpConnectionContexts.end())
157 {
158 GNS_LOG_ERROR("Received a response for an untracked TCP connection id: {}", dnsClientIdentifier.DnsClientId);
159 return;
160 }
161
162 auto tcpConnection = it->second->m_tcpConnection.get();
163
164 // Send DNS response buffer back to the Linux DNS client.
165 //
166 // Note: there might be more DNS requests sent on the same TCP connection. The DNS protocol allows sending the responses in a
167 // different order than the order of the corresponding DNS requests.
168 int bufferSize = dnsBuffer.size();
169 int totalBytesSent = 0;
170
171 while (totalBytesSent < bufferSize)
172 {
173 int bytesSent = Syscall(write, tcpConnection, dnsBuffer.data() + totalBytesSent, bufferSize - totalBytesSent);
174 totalBytesSent += bytesSent;
175 }
176 }
177 CATCH_LOG()
178
179 void DnsServer::HandleNewTcpConnection() noexcept
180 try
181 {
182 std::scoped_lock<std::mutex> lock{m_tcpLock};
183
184 // Accept new connection. Mark connection socket as non-blocking
185 wil::unique_fd connectionFd = Syscall(accept4, m_tcpListenSocket.get(), nullptr, nullptr, SOCK_NONBLOCK);
186
187 // Get next connection id. If value reaches UINT_MAX + 1 it will be automatically reset to 0
188 const auto connectionId = m_currentTcpConnectionId++;
189
190 // Track the new connection
191 auto [it, _] = m_tcpConnectionContexts.emplace(
192 connectionId, std::make_unique<DnsServer::TcpConnectionContext>(connectionId, std::move(connectionFd)));
193 auto& localContext = it->second;
194
195 auto removeContextOnError = wil::scope_exit([&] { m_tcpConnectionContexts.erase(connectionId); });
196
197 // Register the new connection with epoll. EPOLLIN is used to get epoll notifications
198 // whenever there is new data on the TCP connection.
199 epoll_event event{};
200 event.events = EPOLLIN;
201 event.data.ptr = localContext.get();
202 Syscall(epoll_ctl, m_epollFd.get(), EPOLL_CTL_ADD, localContext->m_tcpConnection.get(), &event);
203
204 removeContextOnError.release();
205 }
206 CATCH_LOG();
207
208 void DnsServer::HandleNewTcpData(TcpConnectionContext* context) noexcept
209 try
210 {
211 std::vector<gsl::byte> dnsRequest;
212 uint32_t tcpConnectionId{};
213
214 // Scoped m_tcpLock
215 {
216 std::scoped_lock<std::mutex> lock{m_tcpLock};
217
218 // In case of any failure reading data, close the connection and stop tracking it.
219 // Note: Closing the connection automatically unregisters it from epoll.
220 auto removeConnectionOnError = wil::scope_exit([&] { m_tcpConnectionContexts.erase(context->m_connectionId); });
221
222 // Read the remaining bytes of the current DNS request
223 int bytesReceived = Syscall(
224 recv,
225 context->m_tcpConnection.get(),
226 context->m_currentDnsRequest.data() + context->m_currentRequestOffset,
227 context->m_currentDnsRequest.size() - context->m_currentRequestOffset,
228 0);
229
230 // 0 bytes received indicates connection was closed by the TCP client
231 if (bytesReceived == 0)
232 {
233 return;
234 }
235
236 context->m_currentRequestOffset += bytesReceived;
237
238 if (context->m_currentRequestOffset == context->m_currentDnsRequest.size())
239 {
240 // We read the 2 bytes that represent the DNS request length
241 // Resize buffer to fit the entire DNS request (2 bytes storing the request length + the actual DNS request)
242 if (context->m_currentDnsRequest.size() == c_byteCountTcpRequestLength)
243 {
244 uint16_t dnsRequestLength = 0;
245 memcpy(&dnsRequestLength, context->m_currentDnsRequest.data(), c_byteCountTcpRequestLength);
246 // The request length is stored in network byte order
247 dnsRequestLength = ntohs(dnsRequestLength);
248
249 context->m_currentDnsRequest.resize(c_byteCountTcpRequestLength + dnsRequestLength);
250 }
251 // We read a full DNS request
252 else
253 {
254 // Move request to a local variable
255 dnsRequest = std::move(context->m_currentDnsRequest);
256 tcpConnectionId = context->m_connectionId;
257
258 // Reset state to prepare for the next DNS request on the connection (if any)
259 context->m_currentRequestOffset = 0;
260 context->m_currentDnsRequest.resize(c_byteCountTcpRequestLength);
261 }
262 }
263
264 removeConnectionOnError.release();
265 }
266
267 if (!dnsRequest.empty())
268 {
269 // Tunnel request to Windows
270 LX_GNS_DNS_CLIENT_IDENTIFIER dnsClientIdentifier{};
271 dnsClientIdentifier.DnsClientId = tcpConnectionId;
272 dnsClientIdentifier.Protocol = IPPROTO_TCP;
273
274 GNS_LOG_INFO("New TCP DNS request DNS buffer size: {}, TCP connection id: {}", dnsRequest.size(), dnsClientIdentifier.DnsClientId);
275
276 m_tunnelDnsRequest(gsl::make_span(dnsRequest), dnsClientIdentifier);
277 }
278 }
279 CATCH_LOG();
280
281 void DnsServer::HandleDnsResponse(const gsl::span<gsl::byte> dnsBuffer, const LX_GNS_DNS_CLIENT_IDENTIFIER& dnsClientIdentifier) noexcept
282 try
283 {
284 switch (dnsClientIdentifier.Protocol)
285 {
286 case IPPROTO_UDP:
287 {
288 HandleUdpDnsResponse(dnsBuffer, dnsClientIdentifier);
289 break;
290 }
291 case IPPROTO_TCP:
292 {
293 HandleTcpDnsResponse(dnsBuffer, dnsClientIdentifier);
294 break;
295 }
296
297 default:
298 {
299 GNS_LOG_ERROR("Unexpected DNS protocol {}", dnsClientIdentifier.Protocol);
300 break;
301 }
302 }
303 }
304 CATCH_LOG()
305
306 int DnsServer::ExpireUdpRequestsAndGetTimeout() noexcept
307 {
308 std::scoped_lock<std::mutex> lock{m_udpLock};
309 const auto now = std::chrono::steady_clock::now();
310
311 while (!m_udpRequestExpirations.empty() && m_udpRequestExpirations.front().first <= now)
312 {
313 m_udpRequests.erase(m_udpRequestExpirations.front().second);
314 m_udpRequestExpirations.pop_front();
315 }
316
317 if (m_udpRequestExpirations.empty())
318 {
319 return -1;
320 }
321
322 return static_cast<int>(std::chrono::ceil<std::chrono::milliseconds>(m_udpRequestExpirations.front().first - now).count());
323 }
324
325 void DnsServer::ServerLoop() noexcept
326 {
327 UtilSetThreadName("DnsServer");
328
329 epoll_event events[c_epollWaitMaxEvents];
330 memset(events, 0, sizeof(events));
331
332 for (;;)
333 {
334 try
335 {
336 // A fixed number of events is requested from epoll_wait (c_epollWaitMaxEvents). In case the number of ready events is
337 // greater than c_epollWaitMaxEvents, epoll will round-robin through the ready events until we get a notification for all of them.
338 const auto timeout = ExpireUdpRequestsAndGetTimeout();
339 size_t numReadyEvents = Syscall(epoll_wait, m_epollFd.get(), events, c_epollWaitMaxEvents, timeout);
340
341 // No event
342 if (numReadyEvents == 0)
343 {
344 continue;
345 }
346
347 for (size_t index = 0; index < numReadyEvents; index++)
348 {
349 // Notification for the shutdown pipe == the server needs to exit
350 if (events[index].data.fd == m_shutdownServerLoopPipe.read().get())
351 {
352 return;
353 }
354 // Notification for the listen socket == a new incoming TCP connection
355 else if (events[index].data.fd == m_tcpListenSocket.get())
356 {
357 HandleNewTcpConnection();
358 }
359 // Notification for the UDP socket == There is data to be read from the UDP socket, indicating a new DNS request was received
360 else if (events[index].data.fd == m_udpSocket.get())
361 {
362 HandleUdpDnsRequest();
363 }
364 // Other notifications == new data was received on one of the active TCP connections
365 else
366 {
367 HandleNewTcpData(static_cast<TcpConnectionContext*>(events[index].data.ptr));
368 }
369 }
370 }
371 CATCH_LOG()
372 }
373 }
374
375 void DnsServer::HandleUdpDnsRequest() noexcept
376 try
377 {
378 static std::array<gsl::byte, c_maxUdpDnsBufferSize> s_dnsBuffer;
379
380 gsl::span<gsl::byte> dnsRequest;
381 uint32_t udpRequestId{};
382
383 // Scoped m_udpLock
384 {
385 std::scoped_lock<std::mutex> lock{m_udpLock};
386
387 // Since we only configure an IPv4 DNS server in Linux, we expect all Linux DNS clients to use IPv4 addresses
388 sockaddr_in remoteAddr{};
389 socklen_t remoteAddrLen = sizeof(remoteAddr);
390
391 // Read the DNS request
392 int bytesReceived = Syscall(
393 recvfrom, m_udpSocket.get(), s_dnsBuffer.data(), c_maxUdpDnsBufferSize, 0, reinterpret_cast<sockaddr*>(&remoteAddr), &remoteAddrLen);
394
395 if (bytesReceived == 0)
396 {
397 GNS_LOG_ERROR("recvfrom returned 0 bytes");
398 return;
399 }
400
401 // Get next request id. If value reaches UINT_MAX + 1 it will be automatically reset to 0
402 const auto requestId = m_currentUdpRequestId++;
403
404 GNS_LOG_INFO(
405 "New UDP DNS request DNS client IP: {}, DNS client port {}, DNS buffer size: {}, UDP request id: {}",
406 Address::FromBinary(AF_INET, 0, &remoteAddr.sin_addr).Addr().c_str(),
407 ntohs(remoteAddr.sin_port),
408 bytesReceived,
409 requestId);
410
411 // Move request to a local variable
412 dnsRequest = std::move(gsl::make_span(s_dnsBuffer).subspan(0, bytesReceived));
413 udpRequestId = requestId;
414
415 // Track the request
416 const auto expiration = std::chrono::steady_clock::now() + c_udpRequestTimeout;
417 const auto expirationIt = m_udpRequestExpirations.emplace(m_udpRequestExpirations.end(), expiration, requestId);
418 auto removeExpirationOnError = wil::scope_exit([&] { m_udpRequestExpirations.erase(expirationIt); });
419
420 const auto [_, inserted] = m_udpRequests.emplace(requestId, UdpRequestContext{remoteAddr, expirationIt});
421 THROW_UNEXPECTED_IF(!inserted);
422
423 removeExpirationOnError.release();
424 }
425
426 if (!dnsRequest.empty())
427 {
428 auto removeRequestOnError = wil::scope_exit([&] {
429 std::scoped_lock<std::mutex> lock{m_udpLock};
430 const auto it = m_udpRequests.find(udpRequestId);
431 if (it != m_udpRequests.end())
432 {
433 m_udpRequestExpirations.erase(it->second.m_expiration);
434 m_udpRequests.erase(it);
435 }
436 });
437
438 // Tunnel request to Windows
439 LX_GNS_DNS_CLIENT_IDENTIFIER dnsClientIdentifier{};
440 dnsClientIdentifier.Protocol = IPPROTO_UDP;
441 dnsClientIdentifier.DnsClientId = udpRequestId;
442
443 m_tunnelDnsRequest(dnsRequest, dnsClientIdentifier);
444
445 removeRequestOnError.release();
446 }
447 }
448 CATCH_LOG()
449
450 void DnsServer::Stop() noexcept
451 try
452 {
453 GNS_LOG_INFO("stopping DNS server");
454
455 // Signal the server loop to stop by closing the write fd of the pipe
456 m_shutdownServerLoopPipe.write().reset();
457
458 if (m_serverThread.joinable())
459 {
460 m_serverThread.join();
461 }
462 }
463 CATCH_LOG()