~ubuntu-branches/debian/stretch/libnice/stretch

« back to all changes in this revision

Viewing changes to tests/test-pseudotcp-fuzzy.c

  • Committer: Package Import Robot
  • Author(s): Simon McVittie
  • Date: 2014-05-14 12:00:13 UTC
  • mfrom: (1.2.9) (5.1.13 sid)
  • Revision ID: package-import@ubuntu.com-20140514120013-fi5mh9bexrjnwnd8
Tags: 0.1.7-1
* New upstream release 0.1.6, 0.1.7
  - fixes various compiler warnings that were mistakenly fatal in 0.1.5
    (Closes: #743232, #743233)
  - update symbols file for new API
* Explicitly disable -Werror, even if we package a non-release in future
* Don't run tests during the build. We were ignoring failures already,
  and they sometimes hang until the buildd terminates them.
  Upstream (Olivier Crête) says they are stable enough to be useful
  for developers, but not for integration testing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* vim: et ts=2 sw=2 tw=80: */
 
2
/*
 
3
 * This file is part of the Nice GLib ICE library.
 
4
 *
 
5
 * (C) 2010, 2014 Collabora Ltd.
 
6
 *  Contact: Philip Withnall
 
7
 *
 
8
 * The contents of this file are subject to the Mozilla Public License Version
 
9
 * 1.1 (the "License"); you may not use this file except in compliance with
 
10
 * the License. You may obtain a copy of the License at
 
11
 * http://www.mozilla.org/MPL/
 
12
 *
 
13
 * Software distributed under the License is distributed on an "AS IS" basis,
 
14
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 
15
 * for the specific language governing rights and limitations under the
 
16
 * License.
 
17
 *
 
18
 * The Original Code is the Nice GLib ICE library.
 
19
 *
 
20
 * The Initial Developers of the Original Code are Collabora Ltd and Nokia
 
21
 * Corporation. All Rights Reserved.
 
22
 *
 
23
 * Contributors:
 
24
 *   Philip Withnall, Collabora Ltd.
 
25
 *   Youness Alaoui, Collabora Ltd.
 
26
 *
 
27
 * Alternatively, the contents of this file may be used under the terms of the
 
28
 * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
 
29
 * case the provisions of LGPL are applicable instead of those above. If you
 
30
 * wish to allow use of your version of this file only under the terms of the
 
31
 * LGPL and not to allow others to use your version of this file under the
 
32
 * MPL, indicate your decision by deleting the provisions above and replace
 
33
 * them with the notice and other provisions required by the LGPL. If you do
 
34
 * not delete the provisions above, a recipient may use your version of this
 
35
 * file under either the MPL or the LGPL.
 
36
 */
 
37
 
 
38
#ifdef HAVE_CONFIG_H
 
39
#include "config.h"
 
40
#endif
 
41
 
 
42
#include <locale.h>
 
43
#include <string.h>
 
44
#include <stdio.h>
 
45
#include <stdlib.h>
 
46
#include <errno.h>
 
47
#include <math.h>
 
48
 
 
49
#include "pseudotcp.h"
 
50
 
 
51
 
 
52
/**
 
53
 * A fuzzing test for the pseudotcp socket. This connects two sockets in a
 
54
 * loopback arrangement, with the packet output from one being fed to the other,
 
55
 * and vice-versa. Fuzzing happens on the packet interface between the two,
 
56
 * mutating the packets slightly and seeing what happens.
 
57
 *
 
58
 * The input data to the left-most socket is read from a file. The output data
 
59
 * from the loopback is written to another file, although this probably isn’t
 
60
 * very useful. If no files are provided, a small amount of dummy data is sent
 
61
 * through the sockets instead. This almost certainly won’t catch any bugs, and
 
62
 * is just present to allow this test to be run as part of `make check` so it
 
63
 * doesn’t bit rot.
 
64
 *
 
65
 * A good command to generate an input file is:
 
66
 *     dd if=/dev/urandom of=rand count=10000 ibs=1024
 
67
 *
 
68
 * None of the data is validated, and the test results are effectively the 1-bit
 
69
 * value of ‘did it crash?’. In particular, the output file is not validated,
 
70
 * and the TCP packets emitted by both sockets are not checked for validity.
 
71
 *
 
72
 * It is suggested that this test is run under GDB and Valgrind. Any crashes or
 
73
 * errors which are detected can be reproduced by providing the same input file
 
74
 * and seed (using the --seed option). The seed is printed out at the beginning
 
75
 * of each test run.
 
76
 */
 
77
 
 
78
 
 
79
PseudoTcpSocket *left;
 
80
PseudoTcpSocket *right;
 
81
GMainLoop *main_loop = NULL;
 
82
GRand *prng = NULL;
 
83
gint retval = 0;
 
84
FILE *in = NULL;
 
85
FILE *out = NULL;
 
86
int total_read = 0;
 
87
int total_wrote = 0;
 
88
guint left_clock = 0;
 
89
guint right_clock = 0;
 
90
gboolean left_closed = FALSE;
 
91
gboolean right_closed = FALSE;
 
92
gboolean reading_done = FALSE;
 
93
 
 
94
/* Number of bytes of payload each socket has received so far. */
 
95
guint32 left_stream_pos = 0;
 
96
guint32 right_stream_pos = 0;
 
97
 
 
98
/* Configuration options. */
 
99
gint64 seed = 0;
 
100
guint32 fuzz_start_pos = 1;  /* bytes into stream payload; after the SYN-ACKs */
 
101
guint n_changes_lambda = 2;  /* lambda parameter for a Poisson distribution
 
102
                              * controlling the number of mutations made to each
 
103
                              * packet */
 
104
 
 
105
 
 
106
static void
 
107
adjust_clock (PseudoTcpSocket *sock);
 
108
 
 
109
 
 
110
static void
 
111
write_to_sock (PseudoTcpSocket *sock)
 
112
{
 
113
  gchar buf[1024];
 
114
  gsize len;
 
115
  gint wlen;
 
116
  guint total = 0;
 
117
 
 
118
  while (TRUE) {
 
119
    len = fread (buf, 1, sizeof(buf), in);
 
120
    if (len == 0) {
 
121
      g_debug ("Done reading data from file");
 
122
      g_assert (feof (in));
 
123
      reading_done = TRUE;
 
124
      pseudo_tcp_socket_close (sock, FALSE);
 
125
      break;
 
126
    } else {
 
127
      wlen = pseudo_tcp_socket_send (sock, buf, len);
 
128
      g_debug ("Sending %" G_GSIZE_FORMAT " bytes : %d", len, wlen);
 
129
      total += wlen;
 
130
      total_read += wlen;
 
131
      if (wlen < (gint) len) {
 
132
        g_debug ("seeking  %ld from %lu", wlen - len, ftell (in));
 
133
        fseek (in, wlen - len, SEEK_CUR);
 
134
        g_assert (!feof (in));
 
135
        g_debug ("Socket queue full after %d bytes written", total);
 
136
        break;
 
137
      }
 
138
    }
 
139
  }
 
140
  adjust_clock (sock);
 
141
}
 
142
 
 
143
static void
 
144
opened (PseudoTcpSocket *sock, gpointer data)
 
145
{
 
146
  g_debug ("Socket %p Opened", sock);
 
147
  if (sock == left) {
 
148
    if (in)
 
149
      write_to_sock (sock);
 
150
    else {
 
151
      pseudo_tcp_socket_send (sock, "abcdefghijklmnopqrstuvwxyz", 26);
 
152
      reading_done = TRUE;
 
153
      pseudo_tcp_socket_close (sock, FALSE);
 
154
    }
 
155
  }
 
156
}
 
157
 
 
158
static void
 
159
readable (PseudoTcpSocket *sock, gpointer data)
 
160
{
 
161
  gchar buf[1024];
 
162
  gint len;
 
163
  g_debug ("Socket %p Readable", sock);
 
164
 
 
165
  do {
 
166
    len = pseudo_tcp_socket_recv (sock, buf, sizeof(buf));
 
167
 
 
168
    if (len > 0) {
 
169
      g_debug ("Read %d bytes", len);
 
170
      if (out) {
 
171
        if (fwrite (buf, len, 1, out) == 0)
 
172
          g_debug ("Error writing to output file");
 
173
        else {
 
174
          total_wrote += len;
 
175
 
 
176
          g_assert (total_wrote <= total_read);
 
177
          g_debug ("Written %d bytes, need %d bytes", total_wrote, total_read);
 
178
          if (total_wrote == total_read && feof (in)) {
 
179
            g_assert (reading_done);
 
180
            pseudo_tcp_socket_close (sock, FALSE);
 
181
          }
 
182
        }
 
183
      } else {
 
184
        pseudo_tcp_socket_close (sock, FALSE);
 
185
      }
 
186
    }
 
187
  } while (len > 0);
 
188
 
 
189
  if (len == -1 &&
 
190
      pseudo_tcp_socket_get_error (sock) != EWOULDBLOCK) {
 
191
    g_printerr ("Error reading from socket: error code %d.\n",
 
192
        pseudo_tcp_socket_get_error (sock));
 
193
 
 
194
    retval = -1;
 
195
    g_main_loop_quit (main_loop);
 
196
    return;
 
197
  }
 
198
}
 
199
 
 
200
static void
 
201
writable (PseudoTcpSocket *sock, gpointer data)
 
202
{
 
203
  g_debug ("Socket %p Writable", sock);
 
204
  if (in && sock == left)
 
205
    write_to_sock (sock);
 
206
}
 
207
 
 
208
static void
 
209
closed (PseudoTcpSocket *sock, guint32 err, gpointer data)
 
210
{
 
211
  /* Don’t treat this as an error, since we’re throwing rubbish into the
 
212
   * socket and can hardly expect it to complete successfully. */
 
213
  g_debug ("Socket %p Closed : %d", sock, err);
 
214
  retval = 0;
 
215
  g_main_loop_quit (main_loop);
 
216
}
 
217
 
 
218
struct notify_data {
 
219
  PseudoTcpSocket *sock;
 
220
  guint32 len;
 
221
  guint32 stream_pos;
 
222
  guint8 buffer[];
 
223
};
 
224
 
 
225
/**
 
226
 * random_int_poisson:
 
227
 * @lambda: Lambda parameter for the distribution function, which must be
 
228
 * non-zero
 
229
 *
 
230
 * Generate a random variable from a Poisson distribution with parameter
 
231
 * @lambda. This consumes one %gdouble’s worth of randomness from the global
 
232
 * @prng.
 
233
 *
 
234
 * This is implemented using the inverse transform of the Poisson CDF, and is
 
235
 * guaranteed to return in time linearly proportional to @lambda.
 
236
 *
 
237
 * Returns: Poisson-distributed pseudo-random variable
 
238
 */
 
239
static guint32
 
240
random_int_poisson (guint lambda)
 
241
{
 
242
  gdouble U;
 
243
  guint32 i;
 
244
  gdouble p, F;
 
245
 
 
246
  g_return_val_if_fail (lambda > 0, 0);
 
247
 
 
248
  /*
 
249
   * Reference: http://www.cs.bgu.ac.il/~mps042/invtransnote.htm,
 
250
   * §Simulating a Poisson random variable.
 
251
   */
 
252
  U = g_rand_double (prng);  /* step 1 */
 
253
  i = 0;
 
254
  p = exp (0.0 - (gdouble) lambda);
 
255
  F = p;  /* step 2 */
 
256
 
 
257
  while (U >= F) {  /* step 3 */
 
258
    p = (lambda * p) / (i + 1);
 
259
    F += p;
 
260
    i += 1;  /* step 4 and 5 */
 
261
  }
 
262
 
 
263
  return i;
 
264
}
 
265
 
 
266
static guint32
 
267
fuzz_packet (guint8 *buf, guint32 len, guint32 stream_pos)
 
268
{
 
269
  guint32 i;
 
270
  guint n_changes;
 
271
#define TCP_HEADER_LENGTH 32 /* bytes; or thereabouts (include some options) */
 
272
 
 
273
  /* Do we want to fuzz this packet? */
 
274
  if (stream_pos < fuzz_start_pos) {
 
275
    return len;
 
276
  }
 
277
 
 
278
  /* Get fuzzing. Only bother fuzzing the header; fuzzing the payload is
 
279
   * pointless. Weight the number of changes towards having only a few changes,
 
280
   * since that makes them less likely to be summarily rejected. */
 
281
  n_changes = random_int_poisson (n_changes_lambda);
 
282
  g_debug ("Making %u changes for bytes at stream position %u:",
 
283
      n_changes, stream_pos);
 
284
 
 
285
  for (i = 0; i < n_changes; i++) {
 
286
    guint32 pos = g_rand_int_range (prng, 0, MIN (len, TCP_HEADER_LENGTH));
 
287
    g_debug (" • Changing byte %u.", stream_pos + pos);
 
288
    buf[pos] = g_rand_int_range (prng, 0, G_MAXUINT8 + 1);
 
289
  }
 
290
 
 
291
  return len;
 
292
}
 
293
 
 
294
static gboolean
 
295
notify_packet (gpointer user_data)
 
296
{
 
297
  struct notify_data *data = (struct notify_data*) user_data;
 
298
 
 
299
  /* Fuzz the packet. */
 
300
  data->len = fuzz_packet (data->buffer, data->len, data->stream_pos);
 
301
 
 
302
  pseudo_tcp_socket_notify_packet (data->sock,
 
303
      (gchar *) data->buffer, data->len);
 
304
  adjust_clock (data->sock);
 
305
 
 
306
  g_free (data);
 
307
  return FALSE;
 
308
}
 
309
 
 
310
static PseudoTcpWriteResult
 
311
write_packet (PseudoTcpSocket *sock, const gchar *buffer, guint32 len,
 
312
    gpointer user_data)
 
313
{
 
314
  struct notify_data *data;
 
315
  PseudoTcpState state;
 
316
  g_object_get (sock, "state", &state, NULL);
 
317
 
 
318
  data = g_malloc (sizeof(struct notify_data) + len);
 
319
 
 
320
  g_debug ("Socket %p(%d) Writing : %d bytes", sock, state, len);
 
321
 
 
322
  memcpy (data->buffer, buffer, len);
 
323
  data->len = len;
 
324
 
 
325
  if (sock == left) {
 
326
    data->stream_pos = left_stream_pos;
 
327
    left_stream_pos += len;
 
328
    data->sock = right;
 
329
  } else {
 
330
    data->stream_pos = right_stream_pos;
 
331
    right_stream_pos += len;
 
332
    data->sock = left;
 
333
  }
 
334
 
 
335
  g_idle_add (notify_packet, data);
 
336
 
 
337
  return WR_SUCCESS;
 
338
}
 
339
 
 
340
 
 
341
static gboolean notify_clock (gpointer data)
 
342
{
 
343
  PseudoTcpSocket *sock = (PseudoTcpSocket *)data;
 
344
  //g_debug ("Socket %p: Notifying clock", sock);
 
345
  pseudo_tcp_socket_notify_clock (sock);
 
346
  adjust_clock (sock);
 
347
  return FALSE;
 
348
}
 
349
 
 
350
static void adjust_clock (PseudoTcpSocket *sock)
 
351
{
 
352
  guint64 timeout = 0;
 
353
 
 
354
  if (pseudo_tcp_socket_get_next_clock (sock, &timeout)) {
 
355
    timeout -= g_get_monotonic_time () / 1000;
 
356
    g_debug ("Socket %p: Adjusting clock to %ld ms", sock, timeout);
 
357
    if (sock == left) {
 
358
      if (left_clock != 0)
 
359
         g_source_remove (left_clock);
 
360
      left_clock = g_timeout_add (timeout, notify_clock, sock);
 
361
    } else {
 
362
      if (right_clock != 0)
 
363
         g_source_remove (right_clock);
 
364
      right_clock = g_timeout_add (timeout, notify_clock, sock);
 
365
    }
 
366
  } else {
 
367
    g_debug ("Socket %p should be destroyed, it's done", sock);
 
368
    if (sock == left)
 
369
      left_closed = TRUE;
 
370
    else
 
371
      right_closed = TRUE;
 
372
    if (left_closed && right_closed)
 
373
      g_main_loop_quit (main_loop);
 
374
  }
 
375
}
 
376
 
 
377
static GOptionEntry entries[] = {
 
378
  { "seed", 's', 0, G_OPTION_ARG_INT64, &seed, "PRNG seed", "N" },
 
379
  { "fuzz-start-position", 'p', 0, G_OPTION_ARG_INT, &fuzz_start_pos,
 
380
    "Number of bytes into the stream to start fuzzing after", "B" },
 
381
  { "fuzz-n-changes-lambda", 'l', 0, G_OPTION_ARG_INT, &n_changes_lambda,
 
382
    "Lambda value for the Poisson distribution controlling the number of "
 
383
    "changes made to each packet", "λ" },
 
384
  { NULL }
 
385
};
 
386
 
 
387
int main (int argc, char *argv[])
 
388
{
 
389
  PseudoTcpCallbacks cbs = {
 
390
    NULL, opened, readable, writable, closed, write_packet
 
391
  };
 
392
  GOptionContext *context;
 
393
  GError *error = NULL;
 
394
 
 
395
  setlocale (LC_ALL, "");
 
396
  g_type_init ();
 
397
 
 
398
  /* Configuration. */
 
399
  context = g_option_context_new ("— fuzz-test the pseudotcp socket");
 
400
  g_option_context_add_main_entries (context, entries, NULL);
 
401
 
 
402
  if (!g_option_context_parse (context, &argc, &argv, &error)) {
 
403
    g_printerr ("Option parsing failed: %s\n", error->message);
 
404
    goto context_error;
 
405
  }
 
406
 
 
407
  if (n_changes_lambda == 0) {
 
408
    g_printerr ("Option parsing failed: %s\n",
 
409
        "Lambda values must be positive.");
 
410
    goto context_error;
 
411
  }
 
412
 
 
413
  g_option_context_free (context);
 
414
 
 
415
  /* Tweak the configuration. */
 
416
  if (seed == 0) {
 
417
    seed = g_get_real_time ();
 
418
  }
 
419
 
 
420
  /* Open the input and output files */
 
421
  if (argc >= 3) {
 
422
    in = fopen (argv[1], "r");
 
423
    out = fopen (argv[2], "w");
 
424
  }
 
425
 
 
426
  /* Set up the main loop and sockets. */
 
427
  main_loop = g_main_loop_new (NULL, FALSE);
 
428
 
 
429
  g_print ("Using seed: %" G_GINT64_FORMAT ", start position: %u, λ: %u\n",
 
430
      seed, fuzz_start_pos, n_changes_lambda);
 
431
  prng = g_rand_new_with_seed (seed);
 
432
 
 
433
  pseudo_tcp_set_debug_level (PSEUDO_TCP_DEBUG_VERBOSE);
 
434
 
 
435
  left = pseudo_tcp_socket_new (0, &cbs);
 
436
  right = pseudo_tcp_socket_new (0, &cbs);
 
437
  g_debug ("Left: %p. Right: %p", left, right);
 
438
 
 
439
  pseudo_tcp_socket_notify_mtu (left, 1496);
 
440
  pseudo_tcp_socket_notify_mtu (right, 1496);
 
441
 
 
442
  pseudo_tcp_socket_connect (left);
 
443
  adjust_clock (left);
 
444
  adjust_clock (right);
 
445
 
 
446
  /* Run the main loop. */
 
447
  g_main_loop_run (main_loop);
 
448
  g_main_loop_unref (main_loop);
 
449
 
 
450
  g_object_unref (left);
 
451
  g_object_unref (right);
 
452
 
 
453
  g_rand_free (prng);
 
454
 
 
455
  if (in != NULL)
 
456
    fclose (in);
 
457
  if (out != NULL)
 
458
    fclose (out);
 
459
 
 
460
  return retval;
 
461
 
 
462
context_error:
 
463
  g_printerr ("\n%s\n", g_option_context_get_help (context, TRUE, NULL));
 
464
  g_option_context_free (context);
 
465
 
 
466
  return 1;
 
467
}