summaryrefslogtreecommitdiff
path: root/tools/debugserver/source/RNBSocket.cpp
blob: 80b55b5de3b79178d7c7b2e056cbb933089c5221 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
//===-- RNBSocket.cpp -------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
//  Created by Greg Clayton on 12/12/07.
//
//===----------------------------------------------------------------------===//

#include "RNBSocket.h"
#include "DNBError.h"
#include "DNBLog.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <map>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/event.h>
#include <termios.h>
#include <vector>

#include "lldb/Host/SocketAddress.h"

#ifdef WITH_LOCKDOWN
#include "lockdown.h"
#endif

/* Once we have a RNBSocket object with a port # specified,
   this function is called to wait for an incoming connection.
   This function blocks while waiting for that connection.  */

bool ResolveIPV4HostName(const char *hostname, in_addr_t &addr) {
  if (hostname == NULL || hostname[0] == '\0' ||
      strcmp(hostname, "localhost") == 0 ||
      strcmp(hostname, "127.0.0.1") == 0) {
    addr = htonl(INADDR_LOOPBACK);
    return true;
  } else if (strcmp(hostname, "*") == 0) {
    addr = htonl(INADDR_ANY);
    return true;
  } else {
    // See if an IP address was specified as numbers
    int inet_pton_result = ::inet_pton(AF_INET, hostname, &addr);

    if (inet_pton_result == 1)
      return true;

    struct hostent *host_entry = gethostbyname(hostname);
    if (host_entry) {
      std::string ip_str(
          ::inet_ntoa(*(struct in_addr *)*host_entry->h_addr_list));
      inet_pton_result = ::inet_pton(AF_INET, ip_str.c_str(), &addr);
      if (inet_pton_result == 1)
        return true;
    }
  }
  return false;
}

rnb_err_t RNBSocket::Listen(const char *listen_host, uint16_t port,
                            PortBoundCallback callback,
                            const void *callback_baton) {
  // DNBLogThreadedIf(LOG_RNB_COMM, "%8u RNBSocket::%s called",
  // (uint32_t)m_timer.ElapsedMicroSeconds(true), __FUNCTION__);
  // Disconnect without saving errno
  Disconnect(false);

  DNBError err;
  int queue_id = kqueue();
  if (queue_id < 0) {
    err.SetError(errno, DNBError::MachKernel);
    err.LogThreaded("error: failed to create kqueue.");
    return rnb_err;
  }

  bool any_addr = (strcmp(listen_host, "*") == 0);

  // If the user wants to allow connections from any address we should create
  // sockets on all families that can resolve localhost. This will allow us to
  // listen for IPv6 and IPv4 connections from all addresses if those interfaces
  // are available.
  const char *local_addr = any_addr ? "localhost" : listen_host;

  std::map<int, lldb_private::SocketAddress> sockets;
  auto addresses = lldb_private::SocketAddress::GetAddressInfo(
      local_addr, NULL, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);

  for (auto address : addresses) {
    int sock_fd = ::socket(address.GetFamily(), SOCK_STREAM, IPPROTO_TCP);
    if (sock_fd == -1)
      continue;

    SetSocketOption(sock_fd, SOL_SOCKET, SO_REUSEADDR, 1);

    lldb_private::SocketAddress bind_address = address;

    if(any_addr || !bind_address.IsLocalhost())
      bind_address.SetToAnyAddress(bind_address.GetFamily(), port);
    else
      bind_address.SetPort(port);

    int error =
        ::bind(sock_fd, &bind_address.sockaddr(), bind_address.GetLength());
    if (error == -1) {
      ClosePort(sock_fd, false);
      continue;
    }

    error = ::listen(sock_fd, 5);
    if (error == -1) {
      ClosePort(sock_fd, false);
      continue;
    }

    // We were asked to listen on port zero which means we must now read the
    // actual port that was given to us as port zero is a special code for "find
    // an open port for me". This will only execute on the first socket created,
    // subesquent sockets will reuse this port number.
    if (port == 0) {
      socklen_t sa_len = address.GetLength();
      if (getsockname(sock_fd, &address.sockaddr(), &sa_len) == 0)
        port = address.GetPort();
    }

    sockets[sock_fd] = address;
  }

  if (sockets.size() == 0) {
    err.SetError(errno, DNBError::POSIX);
    err.LogThreaded("::listen or ::bind failed");
    return rnb_err;
  }

  if (callback)
    callback(callback_baton, port);

  std::vector<struct kevent> events;
  events.resize(sockets.size());
  int i = 0;
  for (auto socket : sockets) {
    EV_SET(&events[i++], socket.first, EVFILT_READ, EV_ADD, 0, 0, 0);
  }

  bool accept_connection = false;

  // Loop until we are happy with our connection
  while (!accept_connection) {

    struct kevent event_list[4];
    int num_events =
        kevent(queue_id, events.data(), events.size(), event_list, 4, NULL);

    if (num_events < 0) {
      err.SetError(errno, DNBError::MachKernel);
      err.LogThreaded("error: kevent() failed.");
    }

    for (int i = 0; i < num_events; ++i) {
      auto sock_fd = event_list[i].ident;
      auto socket_pair = sockets.find(sock_fd);
      if (socket_pair == sockets.end())
        continue;

      lldb_private::SocketAddress &addr_in = socket_pair->second;
      lldb_private::SocketAddress accept_addr;
      socklen_t sa_len = accept_addr.GetMaxLength();
      m_fd = ::accept(sock_fd, &accept_addr.sockaddr(), &sa_len);

      if (m_fd == -1) {
        err.SetError(errno, DNBError::POSIX);
        err.LogThreaded("error: Socket accept failed.");
      }

      if (addr_in.IsAnyAddr())
        accept_connection = true;
      else {
        if (accept_addr == addr_in)
          accept_connection = true;
        else {
          ::close(m_fd);
          m_fd = -1;
          ::fprintf(
              stderr,
              "error: rejecting incoming connection from %s (expecting %s)\n",
              accept_addr.GetIPAddress().c_str(),
              addr_in.GetIPAddress().c_str());
          DNBLogThreaded("error: rejecting connection from %s (expecting %s)\n",
                         accept_addr.GetIPAddress().c_str(),
                         addr_in.GetIPAddress().c_str());
          err.Clear();
        }
      }
    }
    if (err.Fail())
      break;
  }
  for (auto socket : sockets) {
    int ListenFd = socket.first;
    ClosePort(ListenFd, false);
  }

  if (err.Fail())
    return rnb_err;

  // Keep our TCP packets coming without any delays.
  SetSocketOption(m_fd, IPPROTO_TCP, TCP_NODELAY, 1);

  return rnb_success;
}

rnb_err_t RNBSocket::Connect(const char *host, uint16_t port) {
  auto result = rnb_err;
  Disconnect(false);

  auto addresses = lldb_private::SocketAddress::GetAddressInfo(
      host, NULL, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);

  for (auto address : addresses) {
    m_fd = ::socket(address.GetFamily(), SOCK_STREAM, IPPROTO_TCP);
    if (m_fd == -1)
      continue;

    // Enable local address reuse
    SetSocketOption(m_fd, SOL_SOCKET, SO_REUSEADDR, 1);

    address.SetPort(port);

    if (-1 == ::connect(m_fd, &address.sockaddr(), address.GetLength())) {
      Disconnect(false);
      continue;
    }
    SetSocketOption(m_fd, IPPROTO_TCP, TCP_NODELAY, 1);

    result = rnb_success;
    break;
  }
  return result;
}

rnb_err_t RNBSocket::useFD(int fd) {
  if (fd < 0) {
    DNBLogThreadedIf(LOG_RNB_COMM, "Bad file descriptor passed in.");
    return rnb_err;
  }

  m_fd = fd;
  return rnb_success;
}

#ifdef WITH_LOCKDOWN
rnb_err_t RNBSocket::ConnectToService() {
  DNBLog("Connecting to com.apple.%s service...", DEBUGSERVER_PROGRAM_NAME);
  // Disconnect from any previous connections
  Disconnect(false);
  if (::secure_lockdown_checkin(&m_ld_conn, NULL, NULL) != kLDESuccess) {
    DNBLogThreadedIf(LOG_RNB_COMM,
                     "::secure_lockdown_checkin(&m_fd, NULL, NULL) failed");
    m_fd = -1;
    return rnb_not_connected;
  }
  m_fd = ::lockdown_get_socket(m_ld_conn);
  if (m_fd == -1) {
    DNBLogThreadedIf(LOG_RNB_COMM, "::lockdown_get_socket() failed");
    return rnb_not_connected;
  }
  m_fd_from_lockdown = true;
  return rnb_success;
}
#endif

rnb_err_t RNBSocket::OpenFile(const char *path) {
  DNBError err;
  m_fd = open(path, O_RDWR);
  if (m_fd == -1) {
    err.SetError(errno, DNBError::POSIX);
    err.LogThreaded("can't open file '%s'", path);
    return rnb_not_connected;
  } else {
    struct termios stdin_termios;

    if (::tcgetattr(m_fd, &stdin_termios) == 0) {
      stdin_termios.c_lflag &= ~ECHO;   // Turn off echoing
      stdin_termios.c_lflag &= ~ICANON; // Get one char at a time
      ::tcsetattr(m_fd, TCSANOW, &stdin_termios);
    }
  }
  return rnb_success;
}

int RNBSocket::SetSocketOption(int fd, int level, int option_name,
                               int option_value) {
  return ::setsockopt(fd, level, option_name, &option_value,
                      sizeof(option_value));
}

rnb_err_t RNBSocket::Disconnect(bool save_errno) {
#ifdef WITH_LOCKDOWN
  if (m_fd_from_lockdown) {
    m_fd_from_lockdown = false;
    m_fd = -1;
    lockdown_disconnect(m_ld_conn);
    return rnb_success;
  }
#endif
  return ClosePort(m_fd, save_errno);
}

rnb_err_t RNBSocket::Read(std::string &p) {
  char buf[1024];
  p.clear();

  // Note that BUF is on the stack so we must be careful to keep any
  // writes to BUF from overflowing or we'll have security issues.

  if (m_fd == -1)
    return rnb_err;

  // DNBLogThreadedIf(LOG_RNB_COMM, "%8u RNBSocket::%s calling read()",
  // (uint32_t)m_timer.ElapsedMicroSeconds(true), __FUNCTION__);
  DNBError err;
  ssize_t bytesread = read(m_fd, buf, sizeof(buf));
  if (bytesread <= 0)
    err.SetError(errno, DNBError::POSIX);
  else
    p.append(buf, bytesread);

  if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
    err.LogThreaded("::read ( %i, %p, %llu ) => %i", m_fd, buf, sizeof(buf),
                    (uint64_t)bytesread);

  // Our port went away - we have to mark this so IsConnected will return the
  // truth.
  if (bytesread == 0) {
    m_fd = -1;
    return rnb_not_connected;
  } else if (bytesread == -1) {
    m_fd = -1;
    return rnb_err;
  }
  // Strip spaces from the end of the buffer
  while (!p.empty() && isspace(p[p.size() - 1]))
    p.erase(p.size() - 1);

  // Most data in the debugserver packets valid printable characters...
  DNBLogThreadedIf(LOG_RNB_COMM, "read: %s", p.c_str());
  return rnb_success;
}

rnb_err_t RNBSocket::Write(const void *buffer, size_t length) {
  if (m_fd == -1)
    return rnb_err;

  DNBError err;
  ssize_t bytessent = write(m_fd, buffer, length);
  if (bytessent < 0)
    err.SetError(errno, DNBError::POSIX);

  if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
    err.LogThreaded("::write ( socket = %i, buffer = %p, length = %llu) => %i",
                    m_fd, buffer, length, (uint64_t)bytessent);

  if (bytessent < 0)
    return rnb_err;

  if ((size_t)bytessent != length)
    return rnb_err;

  DNBLogThreadedIf(
      LOG_RNB_PACKETS, "putpkt: %*s", (int)length,
      (const char *)
          buffer); // All data is string based in debugserver, so this is safe
  DNBLogThreadedIf(LOG_RNB_COMM, "sent: %*s", (int)length,
                   (const char *)buffer);

  return rnb_success;
}

rnb_err_t RNBSocket::ClosePort(int &fd, bool save_errno) {
  int close_err = 0;
  if (fd > 0) {
    errno = 0;
    close_err = close(fd);
    fd = -1;
  }
  return close_err != 0 ? rnb_err : rnb_success;
}