lsquic_di_nocopy.c revision bfc7bfd8
1/* Copyright (c) 2017 LiteSpeed Technologies Inc.  See LICENSE. */
2/*
3 * lsquic_di_nocopy.c -- The "no-copy" data in stream.
4 *
5 * Data from packets is not copied: the packets are referenced by stream
6 * frames.  When all data from stream frame is read, the frame is released
7 * and packet reference count is decremented, which possibly results in
8 * packet being released as well.
9 *
10 * This approach works well in regular circumstances; there are two scenarios
11 * when it does not:
12 *
13 *  A.  If we have many out-of-order frames, insertion into the list becomes
14 *      expensive.  In the degenerate case, we'd have to traverse the whole
15 *      list to find appropriate position.
16 *
17 *  B.  Having many frames ties up resources, as each frame keeps a reference
18 *      to the packet that contains it.  This is a possible attack vector:
19 *      send many one-byte packets; a single hole at the beginning will stop
20 *      the server from being able to read the stream, thus tying up resources.
21 *
22 * If we detect that either (A) or (B) is true, we request that the stream
23 * switch to a more robust incoming stream frame handler by setting
24 * DI_SWITCH_IMPL flag.
25 *
26 * For a small number of elements, (A) and (B) do not matter and the checks
27 * are not performed.  This number is defined by EFF_CHECK_THRESH_LOW.  On
28 * the other side of the spectrum, if the number of frames grows very high,
29 * we want to switch to a more memory-efficient implementation even if (A)
30 * and (B) are not true.  EFF_CHECK_THRESH_HIGH defines this threshold.
31 *
32 * Between the low and high thresholds, we detect efficiency problems as
33 * follows.
34 *
35 * To detect (A), we count how many elements we have to traverse during
36 * insertion.  If we have to traverse at least half the list
37 * EFF_FAR_TRAVERSE_COUNT in a row, DI_SWITCH_IMPL is issued.
38 *
39 * If average stream frame size is smaller than EFF_TINY_FRAME_SZ bytes,
40 * (B) condition is true.  In addition, if there are more than EFF_MAX_HOLES
41 * in the stream, this is also indicative of (B).
42 */
43
44
45#include <assert.h>
46#include <inttypes.h>
47#include <stddef.h>
48#include <stdint.h>
49#include <stdlib.h>
50#include <sys/queue.h>
51
52#include "lsquic.h"
53#include "lsquic_types.h"
54#include "lsquic_int_types.h"
55#include "lsquic_conn_flow.h"
56#include "lsquic_packet_common.h"
57#include "lsquic_packet_in.h"
58#include "lsquic_rtt.h"
59#include "lsquic_sfcw.h"
60#include "lsquic_stream.h"
61#include "lsquic_mm.h"
62#include "lsquic_malo.h"
63#include "lsquic_conn.h"
64#include "lsquic_conn_public.h"
65#include "lsquic_data_in_if.h"
66
67
68#define LSQUIC_LOGGER_MODULE LSQLM_DI
69#define LSQUIC_LOG_CONN_ID ncdi->ncdi_conn_pub->lconn->cn_cid
70#define LSQUIC_LOG_STREAM_ID ncdi->ncdi_stream_id
71#include "lsquic_logger.h"
72
73
74/* If number of elements is at or below this number, we do not bother to check
75 * efficiency conditions.
76 */
77#define EFF_CHECK_THRESH_LOW    10
78
79/* If number of elements is higher than this number, efficiency alert
80 * is issued unconditionally.
81 */
82#define EFF_CHECK_THRESH_HIGH   1000
83
84/* Maximum number of consecutive far traversals */
85#define EFF_FAR_TRAVERSE_COUNT  4
86
87/* Maximum number of holes that is not deemed suspicious */
88#define EFF_MAX_HOLES           5
89
90/* What is deemed a tiny frame, in bytes.  If it is a power of two, calculation
91 * is cheaper.
92 */
93#define EFF_TINY_FRAME_SZ       64
94
95
96TAILQ_HEAD(stream_frames_tailq, stream_frame);
97
98
99struct nocopy_data_in
100{
101    struct stream_frames_tailq  ncdi_frames_in;
102    struct data_in              ncdi_data_in;
103    struct lsquic_conn_public  *ncdi_conn_pub;
104    uint64_t                    ncdi_byteage;
105    uint32_t                    ncdi_stream_id;
106    unsigned                    ncdi_n_frames;
107    unsigned                    ncdi_n_holes;
108    unsigned                    ncdi_cons_far;
109};
110
111
112#define NCDI_PTR(data_in) (struct nocopy_data_in *) \
113    ((unsigned char *) (data_in) - offsetof(struct nocopy_data_in, ncdi_data_in))
114
115#define STREAM_FRAME_PTR(data_frame) (struct stream_frame *) \
116    ((unsigned char *) (data_frame) - offsetof(struct stream_frame, data_frame))
117
118
119static const struct data_in_iface di_if_nocopy;
120
121
122struct data_in *
123data_in_nocopy_new (struct lsquic_conn_public *conn_pub, uint32_t stream_id)
124{
125    struct nocopy_data_in *ncdi;
126
127    ncdi = malloc(sizeof(*ncdi));
128    if (!ncdi)
129        return NULL;
130
131    TAILQ_INIT(&ncdi->ncdi_frames_in);
132    ncdi->ncdi_data_in.di_if    = &di_if_nocopy;
133    ncdi->ncdi_data_in.di_flags = 0;
134    ncdi->ncdi_conn_pub         = conn_pub;
135    ncdi->ncdi_stream_id        = stream_id;
136    ncdi->ncdi_byteage          = 0;
137    ncdi->ncdi_n_frames         = 0;
138    ncdi->ncdi_n_holes          = 0;
139    ncdi->ncdi_cons_far         = 0;
140    return &ncdi->ncdi_data_in;
141}
142
143
144static void
145nocopy_di_destroy (struct data_in *data_in)
146{
147    struct nocopy_data_in *const ncdi = NCDI_PTR(data_in);
148    stream_frame_t *frame;
149    while ((frame = TAILQ_FIRST(&ncdi->ncdi_frames_in)))
150    {
151        TAILQ_REMOVE(&ncdi->ncdi_frames_in, frame, next_frame);
152        lsquic_packet_in_put(ncdi->ncdi_conn_pub->mm, frame->packet_in);
153        lsquic_malo_put(frame);
154    }
155    free(ncdi);
156}
157
158
159#if 1
160#define CHECK_ORDER(ncdi)
161#else
162static int
163ordered (const struct nocopy_data_in *ncdi)
164{
165    const stream_frame_t *frame;
166    uint64_t off = 0;
167    int ordered = 1;
168    TAILQ_FOREACH(frame, &ncdi->ncdi_frames_in, next_frame)
169    {
170        ordered &= off <= frame->data_frame.df_offset;
171        off = frame->data_frame.df_offset;
172    }
173    return ordered;
174}
175#define CHECK_ORDER(ncdi) assert(ordered(ncdi))
176#endif
177
178
179/* To reduce the number of conditionals, logical operators have been replaced
180 * with arithmetic operators.  Return value is an integer in range [0, 3].
181 * Bit 0 is set due to FIN in previous frame.  If bit 1 is set, it means that
182 * it's a dup.
183 */
184static int
185insert_frame (struct nocopy_data_in *ncdi, struct stream_frame *new_frame,
186                                    uint64_t read_offset, unsigned *p_n_frames)
187{
188    int ins;
189    unsigned count;
190    stream_frame_t *prev_frame, *next_frame;
191
192    /* Find position in the list, going backwards.  We go backwards because
193     * that is the most likely scenario.
194     */
195    next_frame = TAILQ_LAST(&ncdi->ncdi_frames_in, stream_frames_tailq);
196    if (next_frame && new_frame->data_frame.df_offset < next_frame->data_frame.df_offset)
197    {
198        count = 1;
199        prev_frame = TAILQ_PREV(next_frame, stream_frames_tailq, next_frame);
200        for ( ; prev_frame &&
201                    new_frame->data_frame.df_offset < next_frame->data_frame.df_offset;
202                next_frame = prev_frame,
203                    prev_frame = TAILQ_PREV(prev_frame, stream_frames_tailq, next_frame))
204        {
205            if (new_frame->data_frame.df_offset >= prev_frame->data_frame.df_offset)
206                break;
207            ++count;
208        }
209    }
210    else
211    {
212        count = 0;
213        prev_frame = NULL;
214    }
215
216    if (!prev_frame && next_frame && new_frame->data_frame.df_offset >=
217                                            next_frame->data_frame.df_offset)
218    {
219        prev_frame = next_frame;
220        next_frame = TAILQ_NEXT(next_frame, next_frame);
221    }
222
223    /* Perform checks */
224    if (prev_frame)
225        ins =
226          (((prev_frame->data_frame.df_offset == new_frame->data_frame.df_offset) &
227            (prev_frame->data_frame.df_size   == new_frame->data_frame.df_size)   &
228            (prev_frame->data_frame.df_fin    == new_frame->data_frame.df_fin)) << 1)   /* Duplicate */
229          | prev_frame->data_frame.df_fin                                               /* FIN in the middle or dup */
230          | (prev_frame->data_frame.df_offset + prev_frame->data_frame.df_size
231                                            > new_frame->data_frame.df_offset)          /* Overlap */
232        ;
233    else
234        ins = 0;
235
236    if (next_frame)
237        ins |=
238          (((next_frame->data_frame.df_offset == new_frame->data_frame.df_offset) &
239            (next_frame->data_frame.df_size   == new_frame->data_frame.df_size)   &
240            (next_frame->data_frame.df_fin    == new_frame->data_frame.df_fin)) << 1)   /* Duplicate */
241          | (new_frame->data_frame.df_offset < read_offset) << 1                        /* Duplicate */
242          | new_frame->data_frame.df_fin                                                /* FIN in the middle or dup */
243          | (new_frame->data_frame.df_offset + new_frame->data_frame.df_size
244                                            > next_frame->data_frame.df_offset)         /* Overlap */
245        ;
246    else
247        ins |=
248            (new_frame->data_frame.df_offset < read_offset) << 1                        /* Duplicate */
249        ;
250
251    if (ins)
252        return ins;
253
254    if (prev_frame)
255    {
256        TAILQ_INSERT_AFTER(&ncdi->ncdi_frames_in, prev_frame, new_frame, next_frame);
257        ncdi->ncdi_n_holes += prev_frame->data_frame.df_offset +
258                    prev_frame->data_frame.df_size != new_frame->data_frame.df_offset;
259        if (next_frame)
260        {
261            ncdi->ncdi_n_holes += new_frame->data_frame.df_offset +
262                    new_frame->data_frame.df_size != next_frame->data_frame.df_offset;
263            --ncdi->ncdi_n_holes;
264        }
265    }
266    else
267    {
268        ncdi->ncdi_n_holes += next_frame && new_frame->data_frame.df_offset +
269                    new_frame->data_frame.df_size != next_frame->data_frame.df_offset;
270        TAILQ_INSERT_HEAD(&ncdi->ncdi_frames_in, new_frame, next_frame);
271    }
272    CHECK_ORDER(ncdi);
273
274    ++ncdi->ncdi_n_frames;
275    ncdi->ncdi_byteage += new_frame->data_frame.df_size;
276    *p_n_frames = count;
277
278    return 0;
279}
280
281
282static int
283check_efficiency (struct nocopy_data_in *ncdi, unsigned count)
284{
285    if (ncdi->ncdi_n_frames <= EFF_CHECK_THRESH_LOW)
286    {
287        ncdi->ncdi_cons_far = 0;
288        return 0;
289    }
290    if (ncdi->ncdi_n_frames > EFF_CHECK_THRESH_HIGH)
291        return 1;
292    if (count >= ncdi->ncdi_n_frames / 2)
293    {
294        ++ncdi->ncdi_cons_far;
295        if (ncdi->ncdi_cons_far > EFF_FAR_TRAVERSE_COUNT)
296            return 1;
297    }
298    else
299        ncdi->ncdi_cons_far = 0;
300    if (ncdi->ncdi_n_holes > EFF_MAX_HOLES)
301        return 1;
302    if (ncdi->ncdi_byteage / EFF_TINY_FRAME_SZ < ncdi->ncdi_n_frames)
303        return 1;
304    return 0;
305}
306
307
308static void
309set_eff_alert (struct nocopy_data_in *ncdi)
310{
311    LSQ_DEBUG("low efficiency: n_frames: %u; n_holes: %u; cons_far: %u; "
312              "byteage: %"PRIu64, ncdi->ncdi_n_frames, ncdi->ncdi_n_holes,
313              ncdi->ncdi_cons_far, ncdi->ncdi_byteage);
314    ncdi->ncdi_data_in.di_flags |= DI_SWITCH_IMPL;
315}
316
317
318static enum ins_frame
319nocopy_di_insert_frame (struct data_in *data_in,
320                        struct stream_frame *new_frame, uint64_t read_offset)
321{
322    struct nocopy_data_in *const ncdi = NCDI_PTR(data_in);
323    unsigned count;
324    int ins;
325
326    assert(0 == (new_frame->data_frame.df_fin & ~1));
327    ins = insert_frame(ncdi, new_frame, read_offset, &count);
328    switch (ins)
329    {
330    case 0:
331        if (check_efficiency(ncdi, count))
332            set_eff_alert(ncdi);
333        return INS_FRAME_OK;
334    case 2:
335    case 3:
336        lsquic_packet_in_put(ncdi->ncdi_conn_pub->mm, new_frame->packet_in);
337        lsquic_malo_put(new_frame);
338        return INS_FRAME_DUP;
339    default:
340        assert(1 == ins);
341        lsquic_packet_in_put(ncdi->ncdi_conn_pub->mm, new_frame->packet_in);
342        lsquic_malo_put(new_frame);
343        return INS_FRAME_ERR;
344    }
345}
346
347
348static struct data_frame *
349nocopy_di_get_frame (struct data_in *data_in, uint64_t read_offset)
350{
351    struct nocopy_data_in *const ncdi = NCDI_PTR(data_in);
352    struct stream_frame *frame = TAILQ_FIRST(&ncdi->ncdi_frames_in);
353    if (frame && frame->data_frame.df_offset +
354                                frame->data_frame.df_read_off == read_offset)
355        return &frame->data_frame;
356    else
357        return NULL;
358}
359
360
361static void
362nocopy_di_frame_done (struct data_in *data_in, struct data_frame *data_frame)
363{
364    struct nocopy_data_in *const ncdi = NCDI_PTR(data_in);
365    struct stream_frame *const frame = STREAM_FRAME_PTR(data_frame), *first;
366    assert(data_frame->df_read_off == data_frame->df_size);
367    TAILQ_REMOVE(&ncdi->ncdi_frames_in, frame, next_frame);
368    first = TAILQ_FIRST(&ncdi->ncdi_frames_in);
369    ncdi->ncdi_n_holes -= first && frame->data_frame.df_offset +
370                    frame->data_frame.df_size != first->data_frame.df_offset;
371    --ncdi->ncdi_n_frames;
372    ncdi->ncdi_byteage -= frame->data_frame.df_size;
373    lsquic_packet_in_put(ncdi->ncdi_conn_pub->mm, frame->packet_in);
374    lsquic_malo_put(frame);
375}
376
377
378static int
379nocopy_di_empty (struct data_in *data_in)
380{
381    struct nocopy_data_in *const ncdi = NCDI_PTR(data_in);
382    return TAILQ_EMPTY(&ncdi->ncdi_frames_in);
383}
384
385
386struct data_in *
387nocopy_di_switch_impl (struct data_in *data_in, uint64_t read_offset)
388{
389    struct nocopy_data_in *const ncdi = NCDI_PTR(data_in);
390    struct data_in *new_data_in;
391    stream_frame_t *frame;
392    enum ins_frame ins;
393
394    new_data_in = data_in_hash_new(ncdi->ncdi_conn_pub, ncdi->ncdi_stream_id,
395                                                        ncdi->ncdi_byteage);
396    if (!new_data_in)
397        goto end;
398
399    while ((frame = TAILQ_FIRST(&ncdi->ncdi_frames_in)))
400    {
401        TAILQ_REMOVE(&ncdi->ncdi_frames_in, frame, next_frame);
402        ins = data_in_hash_insert_data_frame(new_data_in, &frame->data_frame,
403                                                                  read_offset);
404        lsquic_packet_in_put(ncdi->ncdi_conn_pub->mm, frame->packet_in);
405        lsquic_malo_put(frame);
406        if (INS_FRAME_ERR == ins)
407        {
408            new_data_in->di_if->di_destroy(new_data_in);
409            new_data_in = NULL;
410            goto end;
411        }
412    }
413
414  end:
415    data_in->di_if->di_destroy(data_in);
416    return new_data_in;
417}
418
419
420/* This function overestimates amount of memory because some packets are
421 * referenced by more than one stream.  In the usual case, however, I
422 * expect the error not to be large.
423 */
424static size_t
425nocopy_di_mem_used (struct data_in *data_in)
426{
427    struct nocopy_data_in *const ncdi = NCDI_PTR(data_in);
428    const stream_frame_t *frame;
429    size_t size;
430
431    size = sizeof(*data_in);
432    TAILQ_FOREACH(frame, &ncdi->ncdi_frames_in, next_frame)
433        size += lsquic_packet_in_mem_used(frame->packet_in);
434
435    return size;
436}
437
438static const struct data_in_iface di_if_nocopy = {
439    .di_destroy      = nocopy_di_destroy,
440    .di_empty        = nocopy_di_empty,
441    .di_frame_done   = nocopy_di_frame_done,
442    .di_get_frame    = nocopy_di_get_frame,
443    .di_insert_frame = nocopy_di_insert_frame,
444    .di_mem_used     = nocopy_di_mem_used,
445    .di_switch_impl  = nocopy_di_switch_impl,
446};
447