aboutsummaryrefslogtreecommitdiffstats
path: root/test/packetdrill/tcp_options_iterator.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetdrill/tcp_options_iterator.c')
-rw-r--r--test/packetdrill/tcp_options_iterator.c169
1 files changed, 169 insertions, 0 deletions
diff --git a/test/packetdrill/tcp_options_iterator.c b/test/packetdrill/tcp_options_iterator.c
new file mode 100644
index 0000000..6123387
--- /dev/null
+++ b/test/packetdrill/tcp_options_iterator.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+/*
+ * Author: ncardwell@google.com (Neal Cardwell)
+ *
+ * Implementation for module to allow iteration over TCP options in
+ * wire format.
+ */
+
+#include "tcp_options_iterator.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include "packet.h"
+#include "tcp.h"
+#include "tcp_options.h"
+
+/* Return the length (in bytes) we expect to see for the TCP option of
+ * the given kind, or 0 if the option is variable-length. Returns
+ * STATUS_OK on success; on failure returns STATUS_ERR and sets
+ * error message.
+ */
+static int get_expected_tcp_option_length(u8 kind, u8 *expected_length,
+ char **error)
+{
+ switch (kind) {
+ case TCPOPT_EOL:
+ case TCPOPT_NOP:
+ *expected_length = 1; /* no length byte or data */
+ break;
+
+ case TCPOPT_MAXSEG:
+ *expected_length = TCPOLEN_MAXSEG;
+ break;
+
+ case TCPOPT_WINDOW:
+ *expected_length = TCPOLEN_WINDOW;
+ break;
+
+ case TCPOPT_SACK_PERMITTED:
+ *expected_length = TCPOLEN_SACK_PERMITTED;
+ break;
+
+ case TCPOPT_TIMESTAMP:
+ *expected_length = TCPOLEN_TIMESTAMP;
+ break;
+
+ case TCPOPT_SACK:
+ case TCPOPT_MD5SIG:
+ case TCPOPT_FASTOPEN:
+ case TCPOPT_EXP:
+ *expected_length = 0; /* variable-length option */
+ break;
+
+ default:
+ asprintf(error, "unexpected TCP option kind: %u", kind);
+ return STATUS_ERR;
+ }
+ return STATUS_OK;
+}
+
+/* Calculate the length of the TCP option at 'opt', in a block of TCP
+ * options that ends at 'end'. If 'expected_length' is non-zero,
+ * verify that length matches the expectation. Return length of
+ * option in bytes in *length. Returns STATUS_OK on success; on
+ * failure returns STATUS_ERR and sets error message.
+ */
+static int get_tcp_option_length(const u8 *option, const u8 *end,
+ u8 expected_length, u8 *length, char **error)
+{
+ int result = STATUS_ERR;
+ if (option + 1 >= end) {
+ asprintf(error, "TCP option length byte extends too far");
+ goto out;
+ }
+ *length = *(option + 1);
+ if (*length < 2) {
+ asprintf(error, "TCP option with length byte is too short");
+ goto out;
+ }
+
+ if (option + (*length) > end) {
+ asprintf(error, "TCP option data extends too far");
+ goto out;
+ }
+ if (expected_length && (*length != expected_length)) {
+ asprintf(error,
+ "bad TCP option length: was %u but expected %u",
+ *length, expected_length);
+ goto out;
+ }
+ result = STATUS_OK;
+
+out:
+ return result;
+}
+
+static struct tcp_option *get_current_option(
+ struct tcp_options_iterator *iter)
+{
+ assert(iter->current_option <= iter->options_end);
+ if (iter->current_option >= iter->options_end)
+ iter->current_option = NULL;
+ return (struct tcp_option *)iter->current_option;
+}
+
+struct tcp_option *tcp_options_begin(
+ struct packet *packet,
+ struct tcp_options_iterator *iter)
+{
+ memset(iter, 0, sizeof(*iter));
+ iter->current_option = packet_tcp_options(packet);
+ iter->options_end = packet_payload(packet);
+ return get_current_option(iter);
+}
+
+struct tcp_option *tcp_options_next(
+ struct tcp_options_iterator *iter, char **error)
+{
+ /* Ensure we haven't hit the end. */
+ assert(iter->current_option < iter->options_end);
+ assert(iter->current_option != NULL);
+
+ /* Find the length we expect for this kind of option. */
+ u8 length = 0; /* length of this option in bytes */
+ u8 expected_length = 0; /* expected length for this kind */
+ struct tcp_option *option = (struct tcp_option *)iter->current_option;
+ if (get_expected_tcp_option_length(
+ option->kind, &expected_length, error))
+ goto out;
+
+ /* Calculate and validate the actual length of the option. */
+ if (expected_length == 1) {
+ /* 1 byte length means no length byte, so real length is 1. */
+ length = 1;
+ } else {
+ /* Parse and validate length byte. */
+ if (get_tcp_option_length(iter->current_option,
+ iter->options_end,
+ expected_length, &length, error))
+ goto out;
+ }
+
+ /* Advance to the next TCP option. */
+ assert(length > 0);
+ iter->current_option += length;
+ assert(iter->current_option <= iter->options_end);
+ return get_current_option(iter);
+
+out:
+ return NULL;
+
+}