1 | //===-- RNBSocket.cpp -------------------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // Created by Greg Clayton on 12/12/07. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "RNBSocket.h" |
14 | #include "DNBError.h" |
15 | #include "DNBLog.h" |
16 | #include <arpa/inet.h> |
17 | #include <cerrno> |
18 | #include <fcntl.h> |
19 | #include <map> |
20 | #include <netdb.h> |
21 | #include <netinet/in.h> |
22 | #include <netinet/tcp.h> |
23 | #include <sys/event.h> |
24 | #include <termios.h> |
25 | #include <vector> |
26 | |
27 | #include "lldb/Host/SocketAddress.h" |
28 | |
29 | #ifdef WITH_LOCKDOWN |
30 | #include "lockdown.h" |
31 | #endif |
32 | |
33 | rnb_err_t RNBSocket::Listen(const char *listen_host, uint16_t port, |
34 | PortBoundCallback callback, |
35 | const void *callback_baton) { |
36 | // DNBLogThreadedIf(LOG_RNB_COMM, "%8u RNBSocket::%s called", |
37 | // (uint32_t)m_timer.ElapsedMicroSeconds(true), __FUNCTION__); |
38 | // Disconnect without saving errno |
39 | Disconnect(save_errno: false); |
40 | |
41 | DNBError err; |
42 | int queue_id = kqueue(); |
43 | if (queue_id < 0) { |
44 | err.SetError(errno, flavor: DNBError::MachKernel); |
45 | err.LogThreaded(format: "error: failed to create kqueue." ); |
46 | return rnb_err; |
47 | } |
48 | |
49 | bool any_addr = (strcmp(listen_host, "*" ) == 0); |
50 | |
51 | // If the user wants to allow connections from any address we should create |
52 | // sockets on all families that can resolve localhost. This will allow us to |
53 | // listen for IPv6 and IPv4 connections from all addresses if those interfaces |
54 | // are available. |
55 | const char *local_addr = any_addr ? "localhost" : listen_host; |
56 | |
57 | std::map<int, lldb_private::SocketAddress> sockets; |
58 | auto addresses = lldb_private::SocketAddress::GetAddressInfo( |
59 | hostname: local_addr, NULL, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP); |
60 | |
61 | for (auto address : addresses) { |
62 | int sock_fd = ::socket(address.GetFamily(), SOCK_STREAM, IPPROTO_TCP); |
63 | if (sock_fd == -1) |
64 | continue; |
65 | |
66 | SetSocketOption(sock_fd, SOL_SOCKET, SO_REUSEADDR, 1); |
67 | |
68 | lldb_private::SocketAddress bind_address = address; |
69 | |
70 | if(any_addr || !bind_address.IsLocalhost()) |
71 | bind_address.SetToAnyAddress(bind_address.GetFamily(), port); |
72 | else |
73 | bind_address.SetPort(port); |
74 | |
75 | int error = |
76 | ::bind(sock_fd, &bind_address.sockaddr(), bind_address.GetLength()); |
77 | if (error == -1) { |
78 | ClosePort(sock_fd, false); |
79 | continue; |
80 | } |
81 | |
82 | error = ::listen(sock_fd, 5); |
83 | if (error == -1) { |
84 | ClosePort(sock_fd, false); |
85 | continue; |
86 | } |
87 | |
88 | // We were asked to listen on port zero which means we must now read the |
89 | // actual port that was given to us as port zero is a special code for "find |
90 | // an open port for me". This will only execute on the first socket created, |
91 | // subesquent sockets will reuse this port number. |
92 | if (port == 0) { |
93 | socklen_t sa_len = address.GetLength(); |
94 | if (getsockname(sock_fd, &address.sockaddr(), &sa_len) == 0) |
95 | port = address.GetPort(); |
96 | } |
97 | |
98 | sockets[sock_fd] = address; |
99 | } |
100 | |
101 | if (sockets.size() == 0) { |
102 | err.SetError(errno, flavor: DNBError::POSIX); |
103 | err.LogThreaded(format: "::listen or ::bind failed" ); |
104 | return rnb_err; |
105 | } |
106 | |
107 | if (callback) |
108 | callback(callback_baton, port); |
109 | |
110 | std::vector<struct kevent> events; |
111 | events.resize(new_size: sockets.size()); |
112 | int i = 0; |
113 | for (auto socket : sockets) { |
114 | EV_SET(&events[i++], socket.first, EVFILT_READ, EV_ADD, 0, 0, 0); |
115 | } |
116 | |
117 | bool accept_connection = false; |
118 | |
119 | // Loop until we are happy with our connection |
120 | while (!accept_connection) { |
121 | |
122 | struct kevent event_list[4]; |
123 | int num_events; |
124 | do { |
125 | errno = 0; |
126 | num_events = |
127 | kevent(queue_id, events.data(), events.size(), event_list, 4, NULL); |
128 | } while (num_events == -1 && |
129 | (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)); |
130 | |
131 | if (num_events < 0) { |
132 | err.SetError(errno, flavor: DNBError::MachKernel); |
133 | err.LogThreaded(format: "error: kevent() failed." ); |
134 | } |
135 | |
136 | for (int i = 0; i < num_events; ++i) { |
137 | auto sock_fd = event_list[i].ident; |
138 | auto socket_pair = sockets.find(sock_fd); |
139 | if (socket_pair == sockets.end()) |
140 | continue; |
141 | |
142 | lldb_private::SocketAddress &addr_in = socket_pair->second; |
143 | lldb_private::SocketAddress accept_addr; |
144 | socklen_t sa_len = accept_addr.GetMaxLength(); |
145 | m_fd = ::accept(fd: sock_fd, addr: &accept_addr.sockaddr(), addr_len: &sa_len); |
146 | |
147 | if (m_fd == -1) { |
148 | err.SetError(errno, flavor: DNBError::POSIX); |
149 | err.LogThreaded(format: "error: Socket accept failed." ); |
150 | } |
151 | |
152 | if (addr_in.IsAnyAddr()) |
153 | accept_connection = true; |
154 | else { |
155 | if (accept_addr == addr_in) |
156 | accept_connection = true; |
157 | else { |
158 | ::close(fd: m_fd); |
159 | m_fd = -1; |
160 | ::fprintf( |
161 | stderr, |
162 | format: "error: rejecting incoming connection from %s (expecting %s)\n" , |
163 | accept_addr.GetIPAddress().c_str(), |
164 | addr_in.GetIPAddress().c_str()); |
165 | DNBLogThreaded("error: rejecting connection from %s (expecting %s)\n" , |
166 | accept_addr.GetIPAddress().c_str(), |
167 | addr_in.GetIPAddress().c_str()); |
168 | err.Clear(); |
169 | } |
170 | } |
171 | } |
172 | if (err.Fail()) |
173 | break; |
174 | } |
175 | for (auto socket : sockets) { |
176 | int ListenFd = socket.first; |
177 | ClosePort(fd&: ListenFd, save_errno: false); |
178 | } |
179 | |
180 | if (err.Fail()) |
181 | return rnb_err; |
182 | |
183 | // Keep our TCP packets coming without any delays. |
184 | SetSocketOption(fd: m_fd, IPPROTO_TCP, TCP_NODELAY, option_value: 1); |
185 | |
186 | return rnb_success; |
187 | } |
188 | |
189 | rnb_err_t RNBSocket::Connect(const char *host, uint16_t port) { |
190 | auto result = rnb_err; |
191 | Disconnect(save_errno: false); |
192 | |
193 | auto addresses = lldb_private::SocketAddress::GetAddressInfo( |
194 | hostname: host, NULL, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP); |
195 | |
196 | for (auto address : addresses) { |
197 | m_fd = ::socket(domain: address.GetFamily(), SOCK_STREAM, IPPROTO_TCP); |
198 | if (m_fd == -1) |
199 | continue; |
200 | |
201 | // Enable local address reuse |
202 | SetSocketOption(fd: m_fd, SOL_SOCKET, SO_REUSEADDR, option_value: 1); |
203 | |
204 | address.SetPort(port); |
205 | |
206 | if (-1 == ::connect(fd: m_fd, addr: &address.sockaddr(), len: address.GetLength())) { |
207 | Disconnect(save_errno: false); |
208 | continue; |
209 | } |
210 | SetSocketOption(fd: m_fd, IPPROTO_TCP, TCP_NODELAY, option_value: 1); |
211 | |
212 | result = rnb_success; |
213 | break; |
214 | } |
215 | return result; |
216 | } |
217 | |
218 | rnb_err_t RNBSocket::useFD(int fd) { |
219 | if (fd < 0) { |
220 | DNBLogThreadedIf(LOG_RNB_COMM, "Bad file descriptor passed in." ); |
221 | return rnb_err; |
222 | } |
223 | |
224 | m_fd = fd; |
225 | return rnb_success; |
226 | } |
227 | |
228 | #ifdef WITH_LOCKDOWN |
229 | rnb_err_t RNBSocket::ConnectToService() { |
230 | DNBLog("Connecting to com.apple.%s service..." , DEBUGSERVER_PROGRAM_NAME); |
231 | // Disconnect from any previous connections |
232 | Disconnect(false); |
233 | if (::secure_lockdown_checkin(&m_ld_conn, NULL, NULL) != kLDESuccess) { |
234 | DNBLogThreadedIf(LOG_RNB_COMM, |
235 | "::secure_lockdown_checkin(&m_fd, NULL, NULL) failed" ); |
236 | m_fd = -1; |
237 | return rnb_not_connected; |
238 | } |
239 | m_fd = ::lockdown_get_socket(m_ld_conn); |
240 | if (m_fd == -1) { |
241 | DNBLogThreadedIf(LOG_RNB_COMM, "::lockdown_get_socket() failed" ); |
242 | return rnb_not_connected; |
243 | } |
244 | m_fd_from_lockdown = true; |
245 | return rnb_success; |
246 | } |
247 | #endif |
248 | |
249 | rnb_err_t RNBSocket::OpenFile(const char *path) { |
250 | DNBError err; |
251 | m_fd = open(file: path, O_RDWR); |
252 | if (m_fd == -1) { |
253 | err.SetError(errno, flavor: DNBError::POSIX); |
254 | err.LogThreaded(format: "can't open file '%s'" , path); |
255 | return rnb_not_connected; |
256 | } else { |
257 | struct termios stdin_termios; |
258 | |
259 | if (::tcgetattr(fd: m_fd, termios_p: &stdin_termios) == 0) { |
260 | stdin_termios.c_lflag &= ~ECHO; // Turn off echoing |
261 | stdin_termios.c_lflag &= ~ICANON; // Get one char at a time |
262 | ::tcsetattr(fd: m_fd, TCSANOW, termios_p: &stdin_termios); |
263 | } |
264 | } |
265 | return rnb_success; |
266 | } |
267 | |
268 | int RNBSocket::SetSocketOption(int fd, int level, int option_name, |
269 | int option_value) { |
270 | return ::setsockopt(fd: fd, level: level, optname: option_name, optval: &option_value, |
271 | optlen: sizeof(option_value)); |
272 | } |
273 | |
274 | rnb_err_t RNBSocket::Disconnect(bool save_errno) { |
275 | #ifdef WITH_LOCKDOWN |
276 | if (m_fd_from_lockdown) { |
277 | m_fd_from_lockdown = false; |
278 | m_fd = -1; |
279 | lockdown_disconnect(m_ld_conn); |
280 | return rnb_success; |
281 | } |
282 | #endif |
283 | return ClosePort(fd&: m_fd, save_errno); |
284 | } |
285 | |
286 | rnb_err_t RNBSocket::Read(std::string &p) { |
287 | char buf[1024]; |
288 | p.clear(); |
289 | |
290 | // Note that BUF is on the stack so we must be careful to keep any |
291 | // writes to BUF from overflowing or we'll have security issues. |
292 | |
293 | if (m_fd == -1) |
294 | return rnb_err; |
295 | |
296 | // DNBLogThreadedIf(LOG_RNB_COMM, "%8u RNBSocket::%s calling read()", |
297 | // (uint32_t)m_timer.ElapsedMicroSeconds(true), __FUNCTION__); |
298 | DNBError err; |
299 | ssize_t bytesread; |
300 | do { |
301 | errno = 0; |
302 | bytesread = read(fd: m_fd, buf: buf, nbytes: sizeof(buf)); |
303 | } while (bytesread == -1 && |
304 | (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)); |
305 | if (bytesread <= 0) |
306 | err.SetError(errno, flavor: DNBError::POSIX); |
307 | else |
308 | p.append(s: buf, n: bytesread); |
309 | |
310 | if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM)) |
311 | err.LogThreaded(format: "::read ( %i, %p, %llu ) => %i" , m_fd, buf, sizeof(buf), |
312 | (uint64_t)bytesread); |
313 | |
314 | // Our port went away - we have to mark this so IsConnected will return the |
315 | // truth. |
316 | if (bytesread == 0) { |
317 | m_fd = -1; |
318 | return rnb_not_connected; |
319 | } else if (bytesread == -1) { |
320 | m_fd = -1; |
321 | return rnb_err; |
322 | } |
323 | // Strip spaces from the end of the buffer |
324 | while (!p.empty() && isspace(p[p.size() - 1])) |
325 | p.erase(pos: p.size() - 1); |
326 | |
327 | // Most data in the debugserver packets valid printable characters... |
328 | DNBLogThreadedIf(LOG_RNB_COMM, "read: %s" , p.c_str()); |
329 | return rnb_success; |
330 | } |
331 | |
332 | rnb_err_t RNBSocket::Write(const void *buffer, size_t length) { |
333 | if (m_fd == -1) |
334 | return rnb_err; |
335 | |
336 | DNBError err; |
337 | ssize_t bytessent = write(fd: m_fd, buf: buffer, n: length); |
338 | if (bytessent < 0) |
339 | err.SetError(errno, flavor: DNBError::POSIX); |
340 | |
341 | if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM)) |
342 | err.LogThreaded(format: "::write ( socket = %i, buffer = %p, length = %llu) => %i" , |
343 | m_fd, buffer, length, (uint64_t)bytessent); |
344 | |
345 | if (bytessent < 0) |
346 | return rnb_err; |
347 | |
348 | if ((size_t)bytessent != length) |
349 | return rnb_err; |
350 | |
351 | DNBLogThreadedIf( |
352 | LOG_RNB_PACKETS, "putpkt: %*s" , (int)length, |
353 | (const char *) |
354 | buffer); // All data is string based in debugserver, so this is safe |
355 | DNBLogThreadedIf(LOG_RNB_COMM, "sent: %*s" , (int)length, |
356 | (const char *)buffer); |
357 | |
358 | return rnb_success; |
359 | } |
360 | |
361 | rnb_err_t RNBSocket::ClosePort(int &fd, bool save_errno) { |
362 | int close_err = 0; |
363 | if (fd > 0) { |
364 | errno = 0; |
365 | close_err = close(fd: fd); |
366 | fd = -1; |
367 | } |
368 | return close_err != 0 ? rnb_err : rnb_success; |
369 | } |
370 | |