tcp-turn.c 10.6 KB
Newer Older
Youness Alaoui's avatar
Youness Alaoui committed
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
/*
 * This file is part of the Nice GLib ICE library.
 *
 * (C) 2006-2008 Collabora Ltd.
 *  Contact: Dafydd Harries
 *  Contact: Olivier Crete
 * (C) 2006, 2007 Nokia Corporation. All rights reserved.
 *  Contact: Kai Vehmanen
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Nice GLib ICE library.
 *
 * The Initial Developers of the Original Code are Collabora Ltd and Nokia
 * Corporation. All Rights Reserved.
 *
 * Contributors:
 *   Dafydd Harries, Collabora Ltd.
 *   Olivier Crete, Collabora Ltd.
 *   Rémi Denis-Courmont, Nokia
 *   Kai Vehmanen
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
 * case the provisions of LGPL are applicable instead of those above. If you
 * wish to allow use of your version of this file only under the terms of the
 * LGPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replace
 * them with the notice and other provisions required by the LGPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the LGPL.
 */

/*
43
 * Implementation of TCP relay socket interface using TCP Berkeley sockets. (See
Youness Alaoui's avatar
Youness Alaoui committed
44
45
46
47
48
49
50
51
52
53
54
55
56
 * http://en.wikipedia.org/wiki/Berkeley_sockets.)
 */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

#include "tcp-turn.h"

57
58
59
60
#ifdef G_OS_WIN32
#define EINPROGRESS WSAEINPROGRESS
#endif

Youness Alaoui's avatar
Youness Alaoui committed
61
62
63
64
65
66
typedef struct {
  NiceUdpTurnSocketCompatibility compatibility;
  GQueue send_queue;
  gchar recv_buf[65536];
  guint recv_buf_len;
  guint expecting_len;
67
  NiceAddress server_addr;
68
69
70
  GMainContext *context;
  GIOChannel *io_channel;
  GSource *io_source;
Youness Alaoui's avatar
Youness Alaoui committed
71
72
73
74
75
76
77
} TurnTcpPriv;

struct to_be_sent {
  guint length;
  gchar *buf;
};

78
/*** NiceSocket ***/
Youness Alaoui's avatar
Youness Alaoui committed
79
80
81

static gint
socket_recv (
82
  NiceSocket *sock,
Youness Alaoui's avatar
Youness Alaoui committed
83
84
85
86
87
88
  NiceAddress *from,
  guint len,
  gchar *buf)
{
  TurnTcpPriv *priv = sock->priv;
  int ret;
89
  guint padlen;
Youness Alaoui's avatar
Youness Alaoui committed
90
91

  if (priv->expecting_len == 0) {
92
    guint headerlen = 0;
Youness Alaoui's avatar
Youness Alaoui committed
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

    if (priv->compatibility == NICE_UDP_TURN_SOCKET_COMPATIBILITY_DRAFT9)
      headerlen = 4;
    else if (priv->compatibility == NICE_UDP_TURN_SOCKET_COMPATIBILITY_GOOGLE)
      headerlen = 2;
    else
      g_assert_not_reached();

    ret = read (sock->fileno, priv->recv_buf + priv->recv_buf_len,
        headerlen - priv->recv_buf_len);
    if (ret < 0) {
      if (errno == EAGAIN)
        return 0;
      else
        return ret;
    }

    priv->recv_buf_len += ret;

112
    if (priv->recv_buf_len < headerlen)
Youness Alaoui's avatar
Youness Alaoui committed
113
114
115
116
117
118
      return 0;

    if (priv->compatibility == NICE_UDP_TURN_SOCKET_COMPATIBILITY_DRAFT9) {
      guint16 magic = ntohs (*(guint16*)priv->recv_buf);
      guint16 packetlen = ntohs (*(guint16*)(priv->recv_buf + 2));

119
120
      if (magic < 0x4000) {
        /* Its STUN */
Youness Alaoui's avatar
Youness Alaoui committed
121
        priv->expecting_len = 20 + packetlen;
122
123
      } else {
        /* Channel data */
Youness Alaoui's avatar
Youness Alaoui committed
124
125
126
127
128
129
130
131
132
133
        priv->expecting_len = 4 + packetlen;
      }
    }
    else if (priv->compatibility == NICE_UDP_TURN_SOCKET_COMPATIBILITY_GOOGLE) {
      guint len = ntohs (*(guint16*)priv->recv_buf);
      priv->expecting_len = len;
      priv->recv_buf_len = 0;
    }
  }

134
135
136
137
  if (priv->compatibility == NICE_UDP_TURN_SOCKET_COMPATIBILITY_DRAFT9)
    padlen = (priv->expecting_len % 4) ?  4 - (priv->expecting_len % 4) : 0;
  else
    padlen = 0;
Youness Alaoui's avatar
Youness Alaoui committed
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

  ret = read (sock->fileno, priv->recv_buf + priv->recv_buf_len,
      priv->expecting_len + padlen - priv->recv_buf_len);
  if (ret < 0) {
    if (errno == EAGAIN)
      return 0;
    else
      return ret;
  }

  priv->recv_buf_len += ret;

  if (priv->recv_buf_len == priv->expecting_len + padlen) {
    guint copy_len = MIN (len, priv->recv_buf_len);
    memcpy (buf, priv->recv_buf, copy_len);
    priv->expecting_len = 0;
    priv->recv_buf_len = 0;
155
156
    if (from)
      *from = priv->server_addr;
Youness Alaoui's avatar
Youness Alaoui committed
157
158
159
160
161
162
    return copy_len;
  }

  return 0;
}
static void
163
add_to_be_sent (NiceSocket *sock, const gchar *buf, guint len, gboolean head);
Youness Alaoui's avatar
Youness Alaoui committed
164
165
166
167
168
169
170
171
172
173



/*
 * Returns:
 * -1 = error
 * 0 = have more to send
 * 1 = sent everything
 */

174
175
176
177
178
179
static gboolean
socket_send_more (
  GIOChannel *source,
  G_GNUC_UNUSED
  GIOCondition condition,
  gpointer data)
Youness Alaoui's avatar
Youness Alaoui committed
180
{
181
  NiceSocket *sock = (NiceSocket *) data;
Youness Alaoui's avatar
Youness Alaoui committed
182
183
184
185
186
187
188
189
  TurnTcpPriv *priv = sock->priv;
  struct to_be_sent *tbs = NULL;

  while ((tbs = g_queue_pop_head (&priv->send_queue))) {
    int ret;

    ret = write (sock->fileno, tbs->buf, tbs->length);

190
191
192
    if (ret < 0) {
      if (errno == EAGAIN) {
        add_to_be_sent (sock, tbs->buf, tbs->length, TRUE);
193
194
195
        g_free (tbs->buf);
        g_slice_free (struct to_be_sent, tbs);
        break;
196
197
198
      }
    } else if (ret < (int) tbs->length) {
      add_to_be_sent (sock, tbs->buf + ret, tbs->length - ret, TRUE);
199
200
201
      g_free (tbs->buf);
      g_slice_free (struct to_be_sent, tbs);
      break;
Youness Alaoui's avatar
Youness Alaoui committed
202
203
    }

204
    g_free (tbs->buf);
Youness Alaoui's avatar
Youness Alaoui committed
205
206
207
    g_slice_free (struct to_be_sent, tbs);
  }

208
209
210
211
212
213
214
215
216
217
  if (g_queue_is_empty (&priv->send_queue)) {
    g_io_channel_unref (priv->io_channel);
    priv->io_channel = NULL;
    g_source_destroy (priv->io_source);
    g_source_unref (priv->io_source);
    priv->io_source = NULL;
    return FALSE;
  }

  return TRUE;
Youness Alaoui's avatar
Youness Alaoui committed
218
219
}

220
221
222
223
224
225
226

static void
add_to_be_sent (NiceSocket *sock, const gchar *buf, guint len, gboolean head)
{
  TurnTcpPriv *priv = sock->priv;
  struct to_be_sent *tbs = g_slice_new (struct to_be_sent);

227
228
229
  if (len <= 0)
    return;

230
231
232
233
234
235
236
  tbs->buf = g_memdup (buf, len);
  tbs->length = len;
  if (head)
    g_queue_push_head (&priv->send_queue, tbs);
  else
    g_queue_push_tail (&priv->send_queue, tbs);

237
238
239
240
241
242
243
  if (priv->io_channel == NULL) {
    priv->io_channel = g_io_channel_unix_new (sock->fileno);
    priv->io_source = g_io_create_watch (priv->io_channel, G_IO_OUT);
    g_source_set_callback (priv->io_source, (GSourceFunc) socket_send_more,
        sock, NULL);
    g_source_attach (priv->io_source, priv->context);
  }
244
245
246
}


Youness Alaoui's avatar
Youness Alaoui committed
247
248
static gboolean
socket_send (
249
  NiceSocket *sock,
Youness Alaoui's avatar
Youness Alaoui committed
250
251
252
253
254
255
  const NiceAddress *to,
  guint len,
  const gchar *buf)
{
  int ret;
  TurnTcpPriv *priv = sock->priv;
256
257
258
259
260
  gchar padbuf[3] = {0, 0, 0};
  int padlen = (len%4) ? 4 - (len%4) : 0;

  if (priv->compatibility != NICE_UDP_TURN_SOCKET_COMPATIBILITY_DRAFT9)
    padlen = 0;
Youness Alaoui's avatar
Youness Alaoui committed
261

262
263
  /* First try to send the data, don't send it later if it can be sent now
     this way we avoid allocating memory on every send */
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
  if (g_queue_is_empty (&priv->send_queue)) {
    if (priv->compatibility == NICE_UDP_TURN_SOCKET_COMPATIBILITY_GOOGLE) {
      guint16 tmpbuf = htons (len);
      ret = write (sock->fileno, &tmpbuf, sizeof(guint16));

      if (ret < 0) {
        if (errno == EAGAIN) {
          add_to_be_sent (sock, (gchar*) &tmpbuf, sizeof(guint16), FALSE);
          add_to_be_sent (sock, buf, len, FALSE);
          return TRUE;
        } else {
          return FALSE;
        }
      } else if ((guint)ret < sizeof(guint16)) {
        add_to_be_sent (sock, ((gchar*) &tmpbuf) + ret,
            sizeof(guint16) - ret, FALSE);
280
        add_to_be_sent (sock, buf, len, FALSE);
Youness Alaoui's avatar
Youness Alaoui committed
281
282
283
284
        return TRUE;
      }
    }

285
    ret = write (sock->fileno, buf, len);
Youness Alaoui's avatar
Youness Alaoui committed
286

287
    if (ret < 0) {
Youness Alaoui's avatar
Youness Alaoui committed
288
      if (errno == EAGAIN) {
289
        add_to_be_sent (sock, buf, len, FALSE);
290
        add_to_be_sent (sock, padbuf, padlen, FALSE);
Youness Alaoui's avatar
Youness Alaoui committed
291
292
293
294
        return TRUE;
      } else {
        return FALSE;
      }
295
296
    } else if ((guint)ret < len) {
      add_to_be_sent (sock, buf + ret, len - ret, FALSE);
297
      add_to_be_sent (sock, padbuf, padlen, FALSE);
298
      return TRUE;
Youness Alaoui's avatar
Youness Alaoui committed
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

    if (priv->compatibility == NICE_UDP_TURN_SOCKET_COMPATIBILITY_DRAFT9 &&
        len % 4) {

      ret = write (sock->fileno, padbuf, padlen);

      if (ret < 0) {
        if (errno == EAGAIN) {
          add_to_be_sent (sock, padbuf, padlen, FALSE);
          return TRUE;
        } else {
          return FALSE;
        }
      } else if (ret < padlen) {
        add_to_be_sent (sock, padbuf, padlen - ret, FALSE);
        return TRUE;
      }
    }
  } else {
    if (priv->compatibility == NICE_UDP_TURN_SOCKET_COMPATIBILITY_GOOGLE) {
      guint16 tmpbuf = htons (len);
      add_to_be_sent (sock, (gchar*) &tmpbuf, sizeof(guint16), FALSE);
    }
    add_to_be_sent (sock, buf, len, FALSE);
324
    add_to_be_sent (sock, padbuf, padlen, FALSE);
Youness Alaoui's avatar
Youness Alaoui committed
325
326
327
328
329
330
331
332
333
334
335
336
337
  }

  return TRUE;
}

static void
free_to_be_sent (struct to_be_sent *tbs)
{
  g_free (tbs->buf);
  g_slice_free (struct to_be_sent, tbs);
}

static void
338
socket_close (NiceSocket *sock)
Youness Alaoui's avatar
Youness Alaoui committed
339
340
341
342
343
{
  TurnTcpPriv *priv = sock->priv;
  close (sock->fileno);
  g_queue_foreach (&priv->send_queue, (GFunc) free_to_be_sent, NULL);
  g_queue_clear (&priv->send_queue);
344
345
346
  g_io_channel_unref (priv->io_channel);
  g_source_destroy (priv->io_source);
  g_source_unref (priv->io_source);
Youness Alaoui's avatar
Youness Alaoui committed
347
348
349
  g_slice_free(TurnTcpPriv, sock->priv);
}

350
351
352
353
354
355
static gboolean
socket_is_reliable (NiceSocket *sock)
{
  return TRUE;
}

Youness Alaoui's avatar
Youness Alaoui committed
356

357
358
359
NiceSocket *
nice_tcp_turn_socket_new (
    NiceAgent *agent,
360
    GMainContext *ctx,
361
362
    NiceAddress *addr,
    NiceUdpTurnSocketCompatibility compatibility)
Youness Alaoui's avatar
Youness Alaoui committed
363
364
{
  int sockfd = -1;
365
  int ret;
Youness Alaoui's avatar
Youness Alaoui committed
366
367
  struct sockaddr_storage name;
  guint name_len = sizeof (name);
368
  NiceSocket *sock = g_slice_new0 (NiceSocket);
Youness Alaoui's avatar
Youness Alaoui committed
369
370
  TurnTcpPriv *priv;

371
372
373
374
375
376
  if (addr != NULL) {
    nice_address_copy_to_sockaddr(addr, (struct sockaddr *)&name);
  } else {
    memset (&name, 0, sizeof (name));
    name.ss_family = AF_UNSPEC;
  }
Youness Alaoui's avatar
Youness Alaoui committed
377

378
379
380
381
382
  if ((sockfd == -1) &&
      ((name.ss_family == AF_UNSPEC) ||
          (name.ss_family == AF_INET))) {
    sockfd = socket (PF_INET, SOCK_STREAM, 0);
    name.ss_family = AF_INET;
Youness Alaoui's avatar
Youness Alaoui committed
383
#ifdef HAVE_SA_LEN
384
    name.ss_len = sizeof (struct sockaddr_in);
Youness Alaoui's avatar
Youness Alaoui committed
385
#endif
386
  }
Youness Alaoui's avatar
Youness Alaoui committed
387

388
389
390
391
  if (sockfd == -1) {
    g_slice_free (NiceSocket, sock);
    return NULL;
  }
Youness Alaoui's avatar
Youness Alaoui committed
392
393
394
395
396
397
398
399
400

#ifdef FD_CLOEXEC
  fcntl (sockfd, F_SETFD, fcntl (sockfd, F_GETFD) | FD_CLOEXEC);
#endif
#ifdef O_NONBLOCK
  fcntl (sockfd, F_SETFL, fcntl (sockfd, F_GETFL) | O_NONBLOCK);
#endif


401
  ret = connect (sockfd, (const struct sockaddr *)&name, name_len);
Youness Alaoui's avatar
Youness Alaoui committed
402
403
404

  if (ret < 0 && errno != EINPROGRESS) {
    close (sockfd);
405
406
    g_slice_free (NiceSocket, sock);
    return NULL;
Youness Alaoui's avatar
Youness Alaoui committed
407
408
409
410
411
  }

  sock->priv = priv = g_slice_new0 (TurnTcpPriv);

  priv->compatibility = compatibility;
412
  priv->server_addr = *addr;
413
  priv->context = ctx;
Youness Alaoui's avatar
Youness Alaoui committed
414
415
416
417

  sock->fileno = sockfd;
  sock->send = socket_send;
  sock->recv = socket_recv;
418
  sock->is_reliable = socket_is_reliable;
Youness Alaoui's avatar
Youness Alaoui committed
419
420
  sock->close = socket_close;

421
  return sock;
Youness Alaoui's avatar
Youness Alaoui committed
422
}