aboutsummaryrefslogblamecommitdiff
path: root/tools/debugserver/source/RNBSocket.cpp
blob: ce4886ab9df32093078fcd50ee5b9bb9068efb99 (plain) (tree)
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
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421




































































































































































































































































































































































































































                                                                                                                                                
//===-- RNBSocket.cpp -------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//  Created by Greg Clayton on 12/12/07.
//
//===----------------------------------------------------------------------===//

#include "RNBSocket.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <termios.h>
#include "DNBLog.h"
#include "DNBError.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);

    // Now figure out the hostname that will be attaching and palce it into
    struct sockaddr_in listen_addr;
    ::memset (&listen_addr, 0, sizeof listen_addr);
    listen_addr.sin_len = sizeof listen_addr;
    listen_addr.sin_family = AF_INET;
    listen_addr.sin_port = htons (port);
    listen_addr.sin_addr.s_addr = INADDR_ANY;
    
    if (!ResolveIPV4HostName(listen_host, listen_addr.sin_addr.s_addr))
    {
        DNBLogThreaded("error: failed to resolve connecting host '%s'", listen_host);
        return rnb_err;
    }
    
    DNBError err;
    int listen_fd = ::socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listen_fd == -1)
        err.SetError(errno, DNBError::POSIX);

    if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
        err.LogThreaded("::socket ( domain = AF_INET, type = SOCK_STREAM, protocol = IPPROTO_TCP ) => socket = %i", listen_fd);

    if (err.Fail())
        return rnb_err;

    // enable local address reuse
    SetSocketOption (listen_fd, SOL_SOCKET, SO_REUSEADDR, 1);

    struct sockaddr_in sa;
    ::memset (&sa, 0, sizeof sa);
    sa.sin_len = sizeof sa;
    sa.sin_family = AF_INET;
    sa.sin_port = htons (port);
    sa.sin_addr.s_addr = INADDR_ANY; // Let incoming connections bind to any host network interface (this is NOT who can connect to us)
    int error = ::bind (listen_fd, (struct sockaddr *) &sa, sizeof(sa));
    if (error == -1)
        err.SetError(errno, DNBError::POSIX);

    if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
        err.LogThreaded("::bind ( socket = %i, (struct sockaddr *) &sa, sizeof(sa)) )", listen_fd);

    if (err.Fail())
    {
        ClosePort (listen_fd, false);
        return rnb_err;
    }

    error = ::listen (listen_fd, 5);
    if (error == -1)
        err.SetError(errno, DNBError::POSIX);

    if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
        err.LogThreaded("::listen ( socket = %i, backlog = 1 )", listen_fd);

    if (err.Fail())
    {
        ClosePort (listen_fd, false);
        return rnb_err;
    }
    
    if (callback)
    {
        // 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".
        if (port == 0)
        {
            socklen_t sa_len = sizeof (sa);
            if (getsockname(listen_fd, (struct sockaddr *)&sa, &sa_len) == 0)
            {
                port = ntohs (sa.sin_port);
                callback (callback_baton, port);
            }
        }
        else
        {
            callback (callback_baton, port);
        }
    }

    struct sockaddr_in accept_addr;
    ::memset (&accept_addr, 0, sizeof accept_addr);
    accept_addr.sin_len = sizeof accept_addr;

    bool accept_connection = false;

    // Loop until we are happy with our connection
    while (!accept_connection)
    {
        socklen_t accept_addr_len = sizeof accept_addr;
        m_fd = ::accept (listen_fd, (struct sockaddr *)&accept_addr, &accept_addr_len);

        if (m_fd == -1)
            err.SetError(errno, DNBError::POSIX);
        
        if (err.Fail() || DNBLogCheckLogBit(LOG_RNB_COMM))
            err.LogThreaded("::accept ( socket = %i, address = %p, address_len = %u )", listen_fd, &accept_addr, accept_addr_len);

        if (err.Fail())
            break;

        if (listen_addr.sin_addr.s_addr == INADDR_ANY)
            accept_connection = true;
        else
        {
            if (accept_addr_len == listen_addr.sin_len &&
                accept_addr.sin_addr.s_addr == listen_addr.sin_addr.s_addr)
            {
                accept_connection = true;
            }
            else
            {
                ::close (m_fd);
                m_fd = -1;
                const uint8_t *accept_ip = (const uint8_t *)&accept_addr.sin_addr.s_addr;
                const uint8_t *listen_ip = (const uint8_t *)&listen_addr.sin_addr.s_addr;
                ::fprintf (stderr,
                           "error: rejecting incoming connection from %u.%u.%u.%u (expecting %u.%u.%u.%u)\n",
                           accept_ip[0], accept_ip[1], accept_ip[2], accept_ip[3],
                           listen_ip[0], listen_ip[1], listen_ip[2], listen_ip[3]);
                DNBLogThreaded ("error: rejecting connection from %u.%u.%u.%u (expecting %u.%u.%u.%u)",
                                accept_ip[0], accept_ip[1], accept_ip[2], accept_ip[3],
                                listen_ip[0], listen_ip[1], listen_ip[2], listen_ip[3]);
            }
        }
    }

    ClosePort (listen_fd, false);

    if (err.Fail())
    {
        return rnb_err;
    }
    else
    {
        // 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)
{
    Disconnect (false);

    // Create the socket
    m_fd = ::socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_fd == -1)
        return rnb_err;
    
    // Enable local address reuse
    SetSocketOption (m_fd, SOL_SOCKET, SO_REUSEADDR, 1);
    
    struct sockaddr_in sa;
    ::memset (&sa, 0, sizeof (sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons (port);
    
    if (!ResolveIPV4HostName(host, sa.sin_addr.s_addr))
    {
        DNBLogThreaded("error: failed to resolve host '%s'", host);
        Disconnect (false);
        return rnb_err;
    }
    
    if (-1 == ::connect (m_fd, (const struct sockaddr *)&sa, sizeof(sa)))
    {
        Disconnect (false);
        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::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, (char *)buffer);   // All data is string based in debugserver, so this is safe
    DNBLogThreadedIf(LOG_RNB_COMM, "sent: %*s", (int)length, (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;
}