| /* |
| * Copyright (c) 2017 Cisco and/or its affiliates. |
| * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include <vnet/tcp/tcp.h> |
| |
| #define TCP_TEST_I(_cond, _comment, _args...) \ |
| ({ \ |
| int _evald = (_cond); \ |
| if (!(_evald)) { \ |
| fformat(stderr, "FAIL:%d: " _comment "\n", \ |
| __LINE__, ##_args); \ |
| } else { \ |
| fformat(stderr, "PASS:%d: " _comment "\n", \ |
| __LINE__, ##_args); \ |
| } \ |
| _evald; \ |
| }) |
| |
| #define TCP_TEST(_cond, _comment, _args...) \ |
| { \ |
| if (!TCP_TEST_I(_cond, _comment, ##_args)) { \ |
| return 1; \ |
| } \ |
| } |
| |
| static int |
| tcp_test_sack () |
| { |
| tcp_connection_t _tc, *tc = &_tc; |
| sack_scoreboard_t *sb = &tc->sack_sb; |
| sack_block_t *sacks = 0, block; |
| sack_scoreboard_hole_t *hole; |
| int i; |
| |
| memset (tc, 0, sizeof (*tc)); |
| |
| tc->snd_una = 0; |
| tc->snd_una_max = 1000; |
| tc->snd_nxt = 1000; |
| tc->opt.flags |= TCP_OPTS_FLAG_SACK; |
| scoreboard_init (&tc->sack_sb); |
| |
| for (i = 0; i < 1000 / 100; i++) |
| { |
| block.start = i * 100; |
| block.end = (i + 1) * 100; |
| vec_add1 (sacks, block); |
| } |
| |
| /* |
| * Inject even blocks |
| */ |
| |
| for (i = 0; i < 1000 / 200; i++) |
| { |
| vec_add1 (tc->opt.sacks, sacks[i * 2]); |
| } |
| tc->opt.n_sack_blocks = vec_len (tc->opt.sacks); |
| tcp_rcv_sacks (tc, 0); |
| |
| TCP_TEST ((pool_elts (sb->holes) == 5), |
| "scoreboard has %d elements", pool_elts (sb->holes)); |
| |
| /* First SACK block should be rejected */ |
| hole = scoreboard_first_hole (sb); |
| TCP_TEST ((hole->start == 0 && hole->end == 200), |
| "first hole start %u end %u", hole->start, hole->end); |
| hole = scoreboard_last_hole (sb); |
| TCP_TEST ((hole->start == 900 && hole->end == 1000), |
| "last hole start %u end %u", hole->start, hole->end); |
| TCP_TEST ((sb->sacked_bytes == 400), "sacked bytes %d", sb->sacked_bytes); |
| TCP_TEST ((sb->snd_una_adv == 0), "snd_una_adv %u", sb->snd_una_adv); |
| TCP_TEST ((sb->last_sacked_bytes == 400), |
| "last sacked bytes %d", sb->last_sacked_bytes); |
| |
| /* |
| * Inject odd blocks |
| */ |
| |
| vec_reset_length (tc->opt.sacks); |
| for (i = 0; i < 1000 / 200; i++) |
| { |
| vec_add1 (tc->opt.sacks, sacks[i * 2 + 1]); |
| } |
| tc->opt.n_sack_blocks = vec_len (tc->opt.sacks); |
| tcp_rcv_sacks (tc, 0); |
| |
| hole = scoreboard_first_hole (sb); |
| TCP_TEST ((pool_elts (sb->holes) == 1), |
| "scoreboard has %d holes", pool_elts (sb->holes)); |
| TCP_TEST ((hole->start == 0 && hole->end == 100), |
| "first hole start %u end %u", hole->start, hole->end); |
| TCP_TEST ((sb->sacked_bytes == 900), "sacked bytes %d", sb->sacked_bytes); |
| TCP_TEST ((sb->snd_una_adv == 0), "snd_una_adv %u", sb->snd_una_adv); |
| TCP_TEST ((sb->max_byte_sacked == 1000), |
| "max sacked byte %u", sb->max_byte_sacked); |
| TCP_TEST ((sb->last_sacked_bytes == 500), |
| "last sacked bytes %d", sb->last_sacked_bytes); |
| |
| /* |
| * Ack until byte 100, all bytes are now acked + sacked |
| */ |
| tcp_rcv_sacks (tc, 100); |
| |
| TCP_TEST ((pool_elts (sb->holes) == 0), |
| "scoreboard has %d elements", pool_elts (sb->holes)); |
| TCP_TEST ((sb->snd_una_adv == 900), |
| "snd_una_adv after ack %u", sb->snd_una_adv); |
| TCP_TEST ((sb->max_byte_sacked == 1000), |
| "max sacked byte %u", sb->max_byte_sacked); |
| TCP_TEST ((sb->sacked_bytes == 0), "sacked bytes %d", sb->sacked_bytes); |
| TCP_TEST ((sb->last_sacked_bytes == 0), |
| "last sacked bytes %d", sb->last_sacked_bytes); |
| |
| /* |
| * Add new block |
| */ |
| |
| vec_reset_length (tc->opt.sacks); |
| |
| block.start = 1200; |
| block.end = 1300; |
| vec_add1 (tc->opt.sacks, block); |
| |
| tc->snd_una_max = 1500; |
| tc->snd_una = 1000; |
| tc->snd_nxt = 1500; |
| tcp_rcv_sacks (tc, 1000); |
| |
| TCP_TEST ((sb->snd_una_adv == 0), |
| "snd_una_adv after ack %u", sb->snd_una_adv); |
| TCP_TEST ((pool_elts (sb->holes) == 2), |
| "scoreboard has %d holes", pool_elts (sb->holes)); |
| hole = scoreboard_first_hole (sb); |
| TCP_TEST ((hole->start == 1000 && hole->end == 1200), |
| "first hole start %u end %u", hole->start, hole->end); |
| hole = scoreboard_last_hole (sb); |
| TCP_TEST ((hole->start == 1300 && hole->end == 1500), |
| "last hole start %u end %u", hole->start, hole->end); |
| TCP_TEST ((sb->sacked_bytes == 100), "sacked bytes %d", sb->sacked_bytes); |
| |
| /* |
| * Ack first hole |
| */ |
| |
| vec_reset_length (tc->opt.sacks); |
| tcp_rcv_sacks (tc, 1200); |
| |
| TCP_TEST ((sb->snd_una_adv == 100), |
| "snd_una_adv after ack %u", sb->snd_una_adv); |
| TCP_TEST ((sb->sacked_bytes == 0), "sacked bytes %d", sb->sacked_bytes); |
| TCP_TEST ((pool_elts (sb->holes) == 1), |
| "scoreboard has %d elements", pool_elts (sb->holes)); |
| |
| /* |
| * Remove all |
| */ |
| |
| scoreboard_clear (sb); |
| TCP_TEST ((pool_elts (sb->holes) == 0), |
| "number of holes %d", pool_elts (sb->holes)); |
| return 0; |
| } |
| |
| static int |
| tcp_test_fifo (vlib_main_t * vm, unformat_input_t * input) |
| { |
| svm_fifo_t *f; |
| u32 fifo_size = 1 << 20; |
| u32 *test_data = 0; |
| u32 offset; |
| int i, rv; |
| u32 data_word, test_data_len; |
| |
| /* $$$ parse args */ |
| test_data_len = fifo_size / sizeof (u32); |
| vec_validate (test_data, test_data_len - 1); |
| |
| for (i = 0; i < vec_len (test_data); i++) |
| test_data[i] = i; |
| |
| f = svm_fifo_create (fifo_size); |
| |
| /* Paint fifo data vector with -1's */ |
| memset (f->data, 0xFF, test_data_len); |
| |
| /* Enqueue an initial (un-dequeued) chunk */ |
| rv = svm_fifo_enqueue_nowait (f, 0 /* pid */ , |
| sizeof (u32), (u8 *) test_data); |
| |
| if (rv != sizeof (u32)) |
| { |
| clib_warning ("enqueue returned %d", rv); |
| goto out; |
| } |
| |
| /* |
| * Create 3 chunks in the future. The offsets are relative |
| * to the current fifo tail |
| */ |
| for (i = 0; i < 3; i++) |
| { |
| offset = (2 * i + 1) * sizeof (u32); |
| vlib_cli_output (vm, "add offset %d", offset); |
| |
| rv = svm_fifo_enqueue_with_offset |
| (f, 0 /* pid */ , offset, sizeof (u32), |
| (u8 *) (test_data + ((offset + sizeof (u32)) / sizeof (u32)))); |
| |
| if (rv) |
| { |
| clib_warning ("enqueue returned %d", rv); |
| goto out; |
| } |
| } |
| |
| /* Paint missing data backwards */ |
| for (i = 3; i > 0; i--) |
| { |
| offset = (2 * i + 0) * sizeof (u32); |
| |
| vlib_cli_output (vm, "add offset %d", offset); |
| |
| rv = svm_fifo_enqueue_with_offset |
| (f, 0 /* pid */ , offset, sizeof (u32), |
| (u8 *) (test_data + ((offset + sizeof (u32)) / sizeof (u32)))); |
| |
| if (rv) |
| { |
| clib_warning ("enqueue returned %d", rv); |
| goto out; |
| } |
| } |
| |
| vlib_cli_output (vm, "fifo before missing link: %U", |
| format_svm_fifo, f, 1 /* verbose */ ); |
| |
| /* Enqueue the missing u32 */ |
| rv = svm_fifo_enqueue_nowait (f, 0 /* pid */ , |
| sizeof (u32), (u8 *) (test_data + 1)); |
| if (rv != 7 * sizeof (u32)) |
| { |
| clib_warning ("enqueue returned %d", rv); |
| goto out; |
| } |
| |
| vlib_cli_output (vm, "fifo after missing link: %U", |
| format_svm_fifo, f, 1 /* verbose */ ); |
| |
| /* Collect results */ |
| for (i = 0; i < 7; i++) |
| { |
| rv = svm_fifo_dequeue_nowait (f, 0 /* pid */ , sizeof (u32), |
| (u8 *) & data_word); |
| if (rv != sizeof (u32)) |
| { |
| clib_warning ("dequeue returned %d", rv); |
| goto out; |
| } |
| if (data_word != test_data[i]) |
| { |
| clib_warning ("recovered data %d not %d", data_word, test_data[i]); |
| goto out; |
| } |
| } |
| |
| clib_warning ("test complete..."); |
| |
| out: |
| svm_fifo_free (f); |
| vec_free (test_data); |
| return 0; |
| } |
| |
| |
| |
| static clib_error_t * |
| tcp_test (vlib_main_t * vm, |
| unformat_input_t * input, vlib_cli_command_t * cmd_arg) |
| { |
| int res = 0; |
| |
| while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) |
| { |
| if (unformat (input, "sack")) |
| { |
| res = tcp_test_sack (); |
| } |
| else if (unformat (input, "fifo")) |
| { |
| res = tcp_test_fifo (vm, input); |
| } |
| else |
| { |
| return clib_error_return (0, "unknown input `%U'", |
| format_unformat_error, input); |
| } |
| } |
| |
| if (res) |
| { |
| return clib_error_return (0, "TCP unit test failed"); |
| } |
| else |
| { |
| return 0; |
| } |
| } |
| |
| /* *INDENT-OFF* */ |
| VLIB_CLI_COMMAND (tcp_test_command, static) = |
| { |
| .path = "test tcp", |
| .short_help = "internal tcp unit tests", |
| .function = tcp_test, |
| }; |
| /* *INDENT-ON* */ |
| |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |