1 /*
2 * testcode/unittcpreuse.c - unit test for tcp_reuse.
3 *
4 * Copyright (c) 2021, NLnet Labs. All rights reserved.
5 *
6 * This software is open source.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 *
15 * Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
18 *
19 * Neither the name of the NLNET LABS nor the names of its contributors may
20 * be used to endorse or promote products derived from this software without
21 * specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 */
36 /**
37 * \file
38 * Tests the tcp_reuse functionality.
39 */
40
41 #include "config.h"
42 #include "testcode/unitmain.h"
43 #include "util/log.h"
44 #include "util/random.h"
45 #include "services/outside_network.h"
46
47 #define MAX_TCP_WAITING_NODES 5
48
49 /** add number of new IDs to the reuse tree, randomly chosen */
tcpid_addmore(struct reuse_tcp * reuse,struct outside_network * outnet,unsigned int addnum)50 static void tcpid_addmore(struct reuse_tcp* reuse,
51 struct outside_network* outnet, unsigned int addnum)
52 {
53 unsigned int i;
54 struct waiting_tcp* w;
55 for(i=0; i<addnum; i++) {
56 uint16_t id = reuse_tcp_select_id(reuse, outnet);
57 unit_assert(!reuse_tcp_by_id_find(reuse, id));
58 w = calloc(1, sizeof(*w));
59 unit_assert(w);
60 w->id = id;
61 w->outnet = outnet;
62 w->next_waiting = (void*)reuse->pending;
63 reuse_tree_by_id_insert(reuse, w);
64 }
65 }
66
67 /** fill up the reuse ID tree and test assertions */
tcpid_fillup(struct reuse_tcp * reuse,struct outside_network * outnet)68 static void tcpid_fillup(struct reuse_tcp* reuse,
69 struct outside_network* outnet)
70 {
71 int t, numtest=3;
72 for(t=0; t<numtest; t++) {
73 rbtree_init(&reuse->tree_by_id, reuse_id_cmp);
74 tcpid_addmore(reuse, outnet, 65535);
75 reuse_del_readwait(&reuse->tree_by_id);
76 }
77 }
78
79 /** test TCP ID selection */
tcpid_test(void)80 static void tcpid_test(void)
81 {
82 struct pending_tcp pend;
83 struct outside_network outnet;
84 unit_show_func("services/outside_network.c", "reuse_tcp_select_id");
85 memset(&pend, 0, sizeof(pend));
86 pend.reuse.pending = &pend;
87 memset(&outnet, 0, sizeof(outnet));
88 outnet.rnd = ub_initstate(NULL);
89 rbtree_init(&pend.reuse.tree_by_id, reuse_id_cmp);
90 tcpid_fillup(&pend.reuse, &outnet);
91 ub_randfree(outnet.rnd);
92 }
93
94 /** check that the tree has present number of nodes and the LRU is linked
95 * properly. */
check_tree_and_list(struct outside_network * outnet,int present)96 static void check_tree_and_list(struct outside_network* outnet, int present)
97 {
98 int i;
99 struct reuse_tcp *reuse, *next_reuse;
100 unit_assert(present == (int)outnet->tcp_reuse.count);
101 if(present < 1) {
102 unit_assert(outnet->tcp_reuse_first == NULL);
103 unit_assert(outnet->tcp_reuse_last == NULL);
104 return;
105 }
106 unit_assert(outnet->tcp_reuse_first->item_on_lru_list);
107 unit_assert(!outnet->tcp_reuse_first->lru_prev);
108 reuse = outnet->tcp_reuse_first;
109 for(i=0; i<present-1; i++) {
110 unit_assert(reuse->item_on_lru_list);
111 unit_assert(reuse->lru_next);
112 unit_assert(reuse->lru_next != reuse);
113 next_reuse = reuse->lru_next;
114 unit_assert(next_reuse->lru_prev == reuse);
115 reuse = next_reuse;
116 }
117 unit_assert(!reuse->lru_next);
118 unit_assert(outnet->tcp_reuse_last->item_on_lru_list);
119 unit_assert(outnet->tcp_reuse_last == reuse);
120 }
121
122 /** creates pending_tcp. Copy of outside_network.c:create_pending_tcp without
123 * the comm_point creation */
create_pending_tcp(struct outside_network * outnet)124 static int create_pending_tcp(struct outside_network* outnet)
125 {
126 size_t i;
127 if(outnet->num_tcp == 0)
128 return 1; /* no tcp needed, nothing to do */
129 if(!(outnet->tcp_conns = (struct pending_tcp **)calloc(
130 outnet->num_tcp, sizeof(struct pending_tcp*))))
131 return 0;
132 for(i=0; i<outnet->num_tcp; i++) {
133 if(!(outnet->tcp_conns[i] = (struct pending_tcp*)calloc(1,
134 sizeof(struct pending_tcp))))
135 return 0;
136 outnet->tcp_conns[i]->next_free = outnet->tcp_free;
137 outnet->tcp_free = outnet->tcp_conns[i];
138 }
139 return 1;
140 }
141
142 /** empty the tcp_reuse tree and LRU list */
empty_tree(struct outside_network * outnet)143 static void empty_tree(struct outside_network* outnet)
144 {
145 size_t i;
146 struct reuse_tcp* reuse;
147 reuse = outnet->tcp_reuse_first;
148 i = outnet->tcp_reuse.count;
149 while(reuse) {
150 reuse_tcp_remove_tree_list(outnet, reuse);
151 check_tree_and_list(outnet, --i);
152 reuse = outnet->tcp_reuse_first;
153 }
154 }
155
156 /** check removal of the LRU element on the given position of total elements */
check_removal(struct outside_network * outnet,int position,int total)157 static void check_removal(struct outside_network* outnet, int position, int total)
158 {
159 int i;
160 struct reuse_tcp* reuse;
161 empty_tree(outnet);
162 for(i=0; i<total; i++) {
163 reuse_tcp_insert(outnet, outnet->tcp_conns[i]);
164 }
165 check_tree_and_list(outnet, total);
166 reuse = outnet->tcp_reuse_first;
167 for(i=0; i<position; i++) reuse = reuse->lru_next;
168 reuse_tcp_remove_tree_list(outnet, reuse);
169 check_tree_and_list(outnet, total-1);
170 }
171
172 /** check snipping off the last element of the LRU with total elements */
check_snip(struct outside_network * outnet,int total)173 static void check_snip(struct outside_network* outnet, int total)
174 {
175 int i;
176 struct reuse_tcp* reuse;
177 empty_tree(outnet);
178 for(i=0; i<total; i++) {
179 reuse_tcp_insert(outnet, outnet->tcp_conns[i]);
180 }
181 check_tree_and_list(outnet, total);
182 reuse = reuse_tcp_lru_snip(outnet);
183 while(reuse) {
184 reuse_tcp_remove_tree_list(outnet, reuse);
185 check_tree_and_list(outnet, --total);
186 reuse = reuse_tcp_lru_snip(outnet);
187 }
188 unit_assert(outnet->tcp_reuse_first == NULL);
189 unit_assert(outnet->tcp_reuse_last == NULL);
190 unit_assert(outnet->tcp_reuse.count == 0);
191 }
192
193 /** test tcp_reuse tree and LRU list functions */
tcp_reuse_tree_list_test(void)194 static void tcp_reuse_tree_list_test(void)
195 {
196 size_t i;
197 struct outside_network outnet;
198 struct reuse_tcp* reuse;
199 memset(&outnet, 0, sizeof(outnet));
200 rbtree_init(&outnet.tcp_reuse, reuse_cmp);
201 outnet.num_tcp = 5;
202 outnet.tcp_reuse_max = outnet.num_tcp;
203 if(!create_pending_tcp(&outnet)) fatal_exit("out of memory");
204 /* add all to the tree */
205 unit_show_func("services/outside_network.c", "reuse_tcp_insert");
206 for(i=0; i<outnet.num_tcp; i++) {
207 reuse_tcp_insert(&outnet, outnet.tcp_conns[i]);
208 check_tree_and_list(&outnet, i+1);
209 }
210 /* check touching */
211 unit_show_func("services/outside_network.c", "reuse_tcp_lru_touch");
212 for(i=0; i<outnet.tcp_reuse.count; i++) {
213 for(reuse = outnet.tcp_reuse_first; reuse->lru_next; reuse = reuse->lru_next);
214 reuse_tcp_lru_touch(&outnet, reuse);
215 check_tree_and_list(&outnet, outnet.num_tcp);
216 }
217 /* check removal */
218 unit_show_func("services/outside_network.c", "reuse_tcp_remove_tree_list");
219 check_removal(&outnet, 2, 5);
220 check_removal(&outnet, 1, 3);
221 check_removal(&outnet, 1, 2);
222 /* check snip */
223 unit_show_func("services/outside_network.c", "reuse_tcp_lru_snip");
224 check_snip(&outnet, 4);
225
226 for(i=0; i<outnet.num_tcp; i++)
227 if(outnet.tcp_conns[i]) {
228 free(outnet.tcp_conns[i]);
229 }
230 free(outnet.tcp_conns);
231 }
232
check_waiting_tcp_list(struct outside_network * outnet,struct waiting_tcp * first,struct waiting_tcp * last,size_t total)233 static void check_waiting_tcp_list(struct outside_network* outnet,
234 struct waiting_tcp* first, struct waiting_tcp* last, size_t total)
235 {
236 size_t i, j;
237 struct waiting_tcp* w = outnet->tcp_wait_first;
238 struct waiting_tcp* n = NULL;
239 if(first) unit_assert(outnet->tcp_wait_first == first);
240 if(last) unit_assert(outnet->tcp_wait_last == last && !last->next_waiting);
241 for(i=0; w; i++) {
242 unit_assert(i<total); /* otherwise we are looping */
243 unit_assert(w->on_tcp_waiting_list);
244 n = w->next_waiting;
245 for(j=0; n; j++) {
246 unit_assert(j<total-i-1); /* otherwise we are looping */
247 unit_assert(n != w);
248 n = n->next_waiting;
249 }
250 w = w->next_waiting;
251 }
252 }
253
254 /** clear the tcp waiting list */
waiting_tcp_list_clear(struct outside_network * outnet)255 static void waiting_tcp_list_clear(struct outside_network* outnet)
256 {
257 struct waiting_tcp* w = outnet->tcp_wait_first, *n = NULL;
258 if(!w) return;
259 unit_assert(outnet->tcp_wait_first);
260 unit_assert(outnet->tcp_wait_last);
261 while(w) {
262 n = w->next_waiting;
263 w->on_tcp_waiting_list = 0;
264 w->next_waiting = (struct waiting_tcp*)1; /* In purpose faux value */
265 w = n;
266 }
267 outnet->tcp_wait_first = NULL;
268 outnet->tcp_wait_last = NULL;
269 }
270
271 /** check removal of the waiting_tcp element on the given position of total
272 * elements */
check_waiting_tcp_removal(int is_pop,struct outside_network * outnet,struct waiting_tcp * store,size_t position,size_t total)273 static void check_waiting_tcp_removal(int is_pop,
274 struct outside_network* outnet, struct waiting_tcp* store,
275 size_t position, size_t total)
276 {
277 size_t i;
278 struct waiting_tcp* w;
279 waiting_tcp_list_clear(outnet);
280 for(i=0; i<total; i++) {
281 outnet_waiting_tcp_list_add(outnet, &store[i], 0);
282 }
283 check_waiting_tcp_list(outnet, &store[0], &store[total-1], total);
284
285 if(is_pop) {
286 w = outnet_waiting_tcp_list_pop(outnet);
287 unit_assert(w); /* please clang-analyser */
288 } else {
289 w = outnet->tcp_wait_first;
290 for(i=0; i<position; i++) {
291 unit_assert(w); /* please clang-analyser */
292 w = w->next_waiting;
293 }
294 unit_assert(w); /* please clang-analyser */
295 outnet_waiting_tcp_list_remove(outnet, w);
296 }
297 unit_assert(!(w->on_tcp_waiting_list || w->next_waiting));
298
299 if(position == 0 && total == 1) {
300 /* the list should be empty */
301 check_waiting_tcp_list(outnet, NULL, NULL, total-1);
302 } else if(position == 0) {
303 /* first element should be gone */
304 check_waiting_tcp_list(outnet, &store[1], &store[total-1], total-1);
305 } else if(position == total - 1) {
306 /* last element should be gone */
307 check_waiting_tcp_list(outnet, &store[0], &store[total-2], total-1);
308 } else {
309 /* an element should be gone */
310 check_waiting_tcp_list(outnet, &store[0], &store[total-1], total-1);
311 }
312 }
313
waiting_tcp_list_test(void)314 static void waiting_tcp_list_test(void)
315 {
316 size_t i = 0;
317 struct outside_network outnet;
318 struct waiting_tcp* w, *t = NULL;
319 struct waiting_tcp store[MAX_TCP_WAITING_NODES];
320 memset(&outnet, 0, sizeof(outnet));
321 memset(&store, 0, sizeof(store));
322
323 /* Check add first on empty list */
324 unit_show_func("services/outside_network.c", "outnet_waiting_tcp_list_add_first");
325 t = &store[i];
326 outnet_waiting_tcp_list_add_first(&outnet, t, 0);
327 check_waiting_tcp_list(&outnet, t, t, 1);
328
329 /* Check add */
330 unit_show_func("services/outside_network.c", "outnet_waiting_tcp_list_add");
331 for(i=1; i<MAX_TCP_WAITING_NODES-1; i++) {
332 w = &store[i];
333 outnet_waiting_tcp_list_add(&outnet, w, 0);
334 }
335 check_waiting_tcp_list(&outnet, t, w, MAX_TCP_WAITING_NODES-1);
336
337 /* Check add first on populated list */
338 unit_show_func("services/outside_network.c", "outnet_waiting_tcp_list_add_first");
339 w = &store[i];
340 t = outnet.tcp_wait_last;
341 outnet_waiting_tcp_list_add_first(&outnet, w, 0);
342 check_waiting_tcp_list(&outnet, w, t, MAX_TCP_WAITING_NODES);
343
344 /* Check removal */
345 unit_show_func("services/outside_network.c", "outnet_waiting_tcp_list_remove");
346 check_waiting_tcp_removal(0, &outnet, store, 2, 5);
347 check_waiting_tcp_removal(0, &outnet, store, 1, 3);
348 check_waiting_tcp_removal(0, &outnet, store, 0, 2);
349 check_waiting_tcp_removal(0, &outnet, store, 1, 2);
350 check_waiting_tcp_removal(0, &outnet, store, 0, 1);
351
352 /* Check pop */
353 unit_show_func("services/outside_network.c", "outnet_waiting_tcp_list_pop");
354 check_waiting_tcp_removal(1, &outnet, store, 0, 3);
355 check_waiting_tcp_removal(1, &outnet, store, 0, 2);
356 check_waiting_tcp_removal(1, &outnet, store, 0, 1);
357 }
358
check_reuse_write_wait(struct reuse_tcp * reuse,struct waiting_tcp * first,struct waiting_tcp * last,size_t total)359 static void check_reuse_write_wait(struct reuse_tcp* reuse,
360 struct waiting_tcp* first, struct waiting_tcp* last, size_t total)
361 {
362 size_t i, j;
363 struct waiting_tcp* w = reuse->write_wait_first;
364 struct waiting_tcp* n = NULL;
365 if(first) unit_assert(reuse->write_wait_first == first && !first->write_wait_prev);
366 if(last) unit_assert(reuse->write_wait_last == last && !last->write_wait_next);
367 /* check one way */
368 for(i=0; w; i++) {
369 unit_assert(i<total); /* otherwise we are looping */
370 unit_assert(w->write_wait_queued);
371 n = w->write_wait_next;
372 for(j=0; n; j++) {
373 unit_assert(j<total-i-1); /* otherwise we are looping */
374 unit_assert(n != w);
375 n = n->write_wait_next;
376 }
377 w = w->write_wait_next;
378 }
379 /* check the other way */
380 w = reuse->write_wait_last;
381 for(i=0; w; i++) {
382 unit_assert(i<total); /* otherwise we are looping */
383 unit_assert(w->write_wait_queued);
384 n = w->write_wait_prev;
385 for(j=0; n; j++) {
386 unit_assert(j<total-i-1); /* otherwise we are looping */
387 unit_assert(n != w);
388 n = n->write_wait_prev;
389 }
390 w = w->write_wait_prev;
391 }
392 }
393
394 /** clear the tcp waiting list */
reuse_write_wait_clear(struct reuse_tcp * reuse)395 static void reuse_write_wait_clear(struct reuse_tcp* reuse)
396 {
397 struct waiting_tcp* w = reuse->write_wait_first, *n = NULL;
398 if(!w) return;
399 unit_assert(reuse->write_wait_first);
400 unit_assert(reuse->write_wait_last);
401 while(w) {
402 n = w->write_wait_next;
403 w->write_wait_queued = 0;
404 w->write_wait_next = (struct waiting_tcp*)1; /* In purpose faux value */
405 w->write_wait_prev = (struct waiting_tcp*)1; /* In purpose faux value */
406 w = n;
407 }
408 reuse->write_wait_first = NULL;
409 reuse->write_wait_last = NULL;
410 }
411
412 /** check removal of the reuse_write_wait element on the given position of total
413 * elements */
check_reuse_write_wait_removal(int is_pop,struct reuse_tcp * reuse,struct waiting_tcp * store,size_t position,size_t total)414 static void check_reuse_write_wait_removal(int is_pop,
415 struct reuse_tcp* reuse, struct waiting_tcp* store,
416 size_t position, size_t total)
417 {
418 size_t i;
419 struct waiting_tcp* w;
420 reuse_write_wait_clear(reuse);
421 for(i=0; i<total; i++) {
422 reuse_write_wait_push_back(reuse, &store[i]);
423 }
424 check_reuse_write_wait(reuse, &store[0], &store[total-1], total);
425
426 if(is_pop) {
427 w = reuse_write_wait_pop(reuse);
428 } else {
429 w = reuse->write_wait_first;
430 for(i=0; i<position; i++) w = w->write_wait_next;
431 reuse_write_wait_remove(reuse, w);
432 }
433 unit_assert(!(w->write_wait_queued || w->write_wait_next || w->write_wait_prev));
434
435 if(position == 0 && total == 1) {
436 /* the list should be empty */
437 check_reuse_write_wait(reuse, NULL, NULL, total-1);
438 } else if(position == 0) {
439 /* first element should be gone */
440 check_reuse_write_wait(reuse, &store[1], &store[total-1], total-1);
441 } else if(position == total - 1) {
442 /* last element should be gone */
443 check_reuse_write_wait(reuse, &store[0], &store[total-2], total-1);
444 } else {
445 /* an element should be gone */
446 check_reuse_write_wait(reuse, &store[0], &store[total-1], total-1);
447 }
448 }
449
reuse_write_wait_test(void)450 static void reuse_write_wait_test(void)
451 {
452 size_t i;
453 struct reuse_tcp reuse;
454 struct waiting_tcp store[MAX_TCP_WAITING_NODES];
455 struct waiting_tcp* w;
456 memset(&reuse, 0, sizeof(reuse));
457 memset(&store, 0, sizeof(store));
458
459 /* Check adding */
460 unit_show_func("services/outside_network.c", "reuse_write_wait_push_back");
461 for(i=0; i<MAX_TCP_WAITING_NODES; i++) {
462 w = &store[i];
463 reuse_write_wait_push_back(&reuse, w);
464 }
465 check_reuse_write_wait(&reuse, &store[0], w, MAX_TCP_WAITING_NODES);
466
467 /* Check removal */
468 unit_show_func("services/outside_network.c", "reuse_write_wait_remove");
469 check_reuse_write_wait_removal(0, &reuse, store, 2, 5);
470 check_reuse_write_wait_removal(0, &reuse, store, 1, 3);
471 check_reuse_write_wait_removal(0, &reuse, store, 0, 2);
472 check_reuse_write_wait_removal(0, &reuse, store, 1, 2);
473 check_reuse_write_wait_removal(0, &reuse, store, 0, 1);
474
475 /* Check pop */
476 unit_show_func("services/outside_network.c", "reuse_write_wait_pop");
477 check_reuse_write_wait_removal(1, &reuse, store, 0, 3);
478 check_reuse_write_wait_removal(1, &reuse, store, 0, 2);
479 check_reuse_write_wait_removal(1, &reuse, store, 0, 1);
480 }
481
tcpreuse_test(void)482 void tcpreuse_test(void)
483 {
484 unit_show_feature("tcp_reuse");
485 tcpid_test();
486 tcp_reuse_tree_list_test();
487 waiting_tcp_list_test();
488 reuse_write_wait_test();
489 }
490