aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2018-04-14 18:07:22 +0200
committerAnders Broman <a.broman58@gmail.com>2018-04-18 03:47:02 +0000
commit1ff82572ca62096520d0c6529fcc0ecee518206d (patch)
tree51d0894d456188ceca615cb677d4f7996abe8f3f
parent4a156da068269aae75d79cd08e579754c52a0c43 (diff)
dfilter: add range support to set membership operator ("f in {x .. y}")
Allow "tcp.srcport in {1662 1663 1664}" to be abbreviated to "tcp.srcport in {1662 .. 1664}". The range operator is supported for any field value which supports the "<=" and "=>" operators and thus works for integers, IP addresses, etc. The naive mapping "tcp.srcport >= 1662 and tcp.srcport <= 1664" is not used because it does not have the intended effect with fields that have multiple occurrences (e.g. tcp.port). Each condition could be satisfied by an other value. Therefore a new DVFM instruction (ANY_IN_RANGE) is added to test the range condition against each individual field value. Bug: 14180 Change-Id: I53c2d0f9bc9d4f0ffaabde9a83442122965c95f7 Reviewed-on: https://code.wireshark.org/review/26945 Petri-Dish: Peter Wu <peter@lekensteyn.nl> Tested-by: Petri Dish Buildbot Reviewed-by: Anders Broman <a.broman58@gmail.com>
-rw-r--r--doc/wireshark-filter.pod10
-rw-r--r--docbook/release-notes.asciidoc4
-rw-r--r--docbook/wsug_src/WSUG_chapter_work.asciidoc17
-rw-r--r--epan/dfilter/dfvm.c46
-rw-r--r--epan/dfilter/dfvm.h3
-rw-r--r--epan/dfilter/gencode.c84
-rw-r--r--epan/dfilter/grammar.lemon15
-rw-r--r--epan/dfilter/scanner.l2
-rw-r--r--epan/dfilter/semcheck.c28
-rw-r--r--epan/dfilter/sttype-set.c13
-rw-r--r--epan/dfilter/sttype-set.h3
-rwxr-xr-xtools/dfilter-test.py1
-rw-r--r--tools/dftestlib/membership.py42
13 files changed, 235 insertions, 33 deletions
diff --git a/doc/wireshark-filter.pod b/doc/wireshark-filter.pod
index a6ef64f..84754ec 100644
--- a/doc/wireshark-filter.pod
+++ b/doc/wireshark-filter.pod
@@ -288,6 +288,16 @@ as opposed to the more verbose:
tcp.port == 80 or tcp.port == 443 or tcp.port == 8080
+To find HTTP requests using the HEAD or GET methods:
+
+ http.request.method in {"HEAD" "GET"}
+
+The set of values can also contain ranges:
+
+ tcp.port in {443 4430 .. 4434}
+ ip.addr in {10.0.0.5 .. 10.0.0.9 192.168.1.1 .. 192.168.1.9}
+ frame.time_delta in {10 .. 10.5}
+
=head2 Type conversions
If a field is a text string or a byte array, it can be expressed in whichever
diff --git a/docbook/release-notes.asciidoc b/docbook/release-notes.asciidoc
index 7ba15f5..0c80e1b 100644
--- a/docbook/release-notes.asciidoc
+++ b/docbook/release-notes.asciidoc
@@ -39,7 +39,9 @@ Dumpcap might not quit if Wireshark or TShark crashes.
The following features are new (or have been significantly updated)
since version 2.6.0:
-* Watch this space.
+* The membership operator now supports ranges, allowing display filters such as
+ `tcp.port in {4430 .. 4434}` to be expressed. See the User's Guide, chapter
+ _Building display filter expressions_ for details.
//=== Removed Dissectors
diff --git a/docbook/wsug_src/WSUG_chapter_work.asciidoc b/docbook/wsug_src/WSUG_chapter_work.asciidoc
index 6e91a42..57268b1 100644
--- a/docbook/wsug_src/WSUG_chapter_work.asciidoc
+++ b/docbook/wsug_src/WSUG_chapter_work.asciidoc
@@ -440,6 +440,23 @@ have been expressed as:
tcp.port == 80 || tcp.port == 443 || tcp.port == 8080
----
+The set of values can also contain ranges:
+----
+tcp.port in {443 4430 .. 4434}
+----
+This is not merely a shortcut for `tcp.port == 443 || (tcp.port >= 4430 &&
+tcp.port <= 4434)`. Comparison operators are usually satisfied when any field
+matches the filter, and thus a packet with ports 80 and 56789 would match this
+alternative display filter since `56789 >= 4430 && 80 <= 4434` is true. The
+membership operator instead tests the same field against the range condition.
+
+Sets are not just limited to numbers, other types can be used as well:
+----
+http.request.method in {"HEAD" "GET"}
+ip.addr in {10.0.0.5 .. 10.0.0.9 192.168.1.1 .. 192.168.1.9}
+frame.time_delta in {10 .. 10.5}
+----
+
[[ChWorkBuildDisplayFilterMistake]]
==== A Common Mistake
diff --git a/epan/dfilter/dfvm.c b/epan/dfilter/dfvm.c
index cf7c036..f93fd50 100644
--- a/epan/dfilter/dfvm.c
+++ b/epan/dfilter/dfvm.c
@@ -118,6 +118,7 @@ dfvm_dump(FILE *f, dfilter_t *df)
case ANY_BITWISE_AND:
case ANY_CONTAINS:
case ANY_MATCHES:
+ case ANY_IN_RANGE:
case NOT:
case RETURN:
case IF_TRUE_GOTO:
@@ -252,6 +253,12 @@ dfvm_dump(FILE *f, dfilter_t *df)
id, arg1->value.numeric, arg2->value.numeric);
break;
+ case ANY_IN_RANGE:
+ fprintf(f, "%05d ANY_IN_RANGE\treg#%u in range reg#%u,reg#%u\n",
+ id, arg1->value.numeric, arg2->value.numeric,
+ arg3->value.numeric);
+ break;
+
case NOT:
fprintf(f, "%05d NOT\n", id);
break;
@@ -357,6 +364,37 @@ any_test(dfilter_t *df, FvalueCmpFunc cmp, int reg1, int reg2)
return FALSE;
}
+static gboolean
+any_in_range(dfilter_t *df, int reg1, int reg2, int reg3)
+{
+ GList *list1, *list_low, *list_high;
+ fvalue_t *low, *high;
+
+ list1 = df->registers[reg1];
+ list_low = df->registers[reg2];
+ list_high = df->registers[reg3];
+
+ /* The first register contains the values associated with a field, the
+ * second and third arguments are expected to be a single value for the
+ * lower and upper bound respectively. These cannot be fields and thus
+ * the list length MUST be one. This should have been enforced by
+ * grammar.lemon.
+ */
+ g_assert(list_low && !g_list_next(list_low));
+ g_assert(list_high && !g_list_next(list_high));
+ low = (fvalue_t *)list_low->data;
+ high = (fvalue_t *)list_high->data;
+
+ while (list1) {
+ fvalue_t *value = (fvalue_t *)list1->data;
+ if (fvalue_ge(value, low) && fvalue_le(value, high)) {
+ return TRUE;
+ }
+ list1 = g_list_next(list1);
+ }
+ return FALSE;
+}
+
/* Free the list nodes w/o freeing the memory that each
* list node points to. */
@@ -515,6 +553,13 @@ dfvm_apply(dfilter_t *df, proto_tree *tree)
arg1->value.numeric, arg2->value.numeric);
break;
+ case ANY_IN_RANGE:
+ arg3 = insn->arg3;
+ accum = any_in_range(df, arg1->value.numeric,
+ arg2->value.numeric,
+ arg3->value.numeric);
+ break;
+
case NOT:
accum = !accum;
break;
@@ -589,6 +634,7 @@ dfvm_init_const(dfilter_t *df)
case ANY_BITWISE_AND:
case ANY_CONTAINS:
case ANY_MATCHES:
+ case ANY_IN_RANGE:
case NOT:
case RETURN:
case IF_TRUE_GOTO:
diff --git a/epan/dfilter/dfvm.h b/epan/dfilter/dfvm.h
index 4014c87..68a65dd 100644
--- a/epan/dfilter/dfvm.h
+++ b/epan/dfilter/dfvm.h
@@ -59,7 +59,8 @@ typedef enum {
ANY_CONTAINS,
ANY_MATCHES,
MK_RANGE,
- CALL_FUNCTION
+ CALL_FUNCTION,
+ ANY_IN_RANGE
} dfvm_opcode_t;
diff --git a/epan/dfilter/gencode.c b/epan/dfilter/gencode.c
index 769fcc3..dc00314 100644
--- a/epan/dfilter/gencode.c
+++ b/epan/dfilter/gencode.c
@@ -232,19 +232,16 @@ dfw_append_function(dfwork_t *dfw, stnode_t *node, dfvm_value_t **p_jmp)
}
+/**
+ * Adds an instruction for a relation operator where the values are already
+ * loaded in registers.
+ */
static void
-gen_relation(dfwork_t *dfw, dfvm_opcode_t op, stnode_t *st_arg1, stnode_t *st_arg2)
+gen_relation_regs(dfwork_t *dfw, dfvm_opcode_t op, int reg1, int reg2)
{
dfvm_insn_t *insn;
dfvm_value_t *val1, *val2;
- dfvm_value_t *jmp1 = NULL, *jmp2 = NULL;
- int reg1 = -1, reg2 = -1;
- /* Create code for the LHS and RHS of the relation */
- reg1 = gen_entity(dfw, st_arg1, &jmp1);
- reg2 = gen_entity(dfw, st_arg2, &jmp2);
-
- /* Then combine them in a DFVM insruction */
insn = dfvm_insn_new(op);
val1 = dfvm_value_new(REGISTER);
val1->value.numeric = reg1;
@@ -253,9 +250,23 @@ gen_relation(dfwork_t *dfw, dfvm_opcode_t op, stnode_t *st_arg1, stnode_t *st_ar
insn->arg1 = val1;
insn->arg2 = val2;
dfw_append_insn(dfw, insn);
+}
+
+static void
+gen_relation(dfwork_t *dfw, dfvm_opcode_t op, stnode_t *st_arg1, stnode_t *st_arg2)
+{
+ dfvm_value_t *jmp1 = NULL, *jmp2 = NULL;
+ int reg1 = -1, reg2 = -1;
+
+ /* Create code for the LHS and RHS of the relation */
+ reg1 = gen_entity(dfw, st_arg1, &jmp1);
+ reg2 = gen_entity(dfw, st_arg2, &jmp2);
- /* If either of the relation argumnents need an "exit" instruction
- * to jump to (on failure), mark them */
+ /* Then combine them in a DFVM insruction */
+ gen_relation_regs(dfw, op, reg1, reg2);
+
+ /* If either of the relation arguments need an "exit" instruction
+ * to jump to (on failure), mark them */
if (jmp1) {
jmp1->value.numeric = dfw->next_insn_id;
}
@@ -282,10 +293,10 @@ static void
gen_relation_in(dfwork_t *dfw, stnode_t *st_arg1, stnode_t *st_arg2)
{
dfvm_insn_t *insn;
- dfvm_value_t *val1, *val2;
- dfvm_value_t *jmp1 = NULL, *jmp2 = NULL;
- int reg1 = -1, reg2 = -1;
- stnode_t *node;
+ dfvm_value_t *val1, *val2, *val3;
+ dfvm_value_t *jmp1 = NULL, *jmp2 = NULL, *jmp3 = NULL;
+ int reg1 = -1, reg2 = -1, reg3 = -1;
+ stnode_t *node1, *node2;
GSList *nodelist;
GSList *jumplist = NULL;
@@ -295,21 +306,36 @@ gen_relation_in(dfwork_t *dfw, stnode_t *st_arg1, stnode_t *st_arg2)
/* Create code for the set on the RHS of the relation */
nodelist = (GSList*)stnode_data(st_arg2);
while (nodelist) {
- node = (stnode_t*)nodelist->data;
- reg2 = gen_entity(dfw, node, &jmp2);
-
- /* Add test to see if the item matches */
- insn = dfvm_insn_new(ANY_EQ);
- val1 = dfvm_value_new(REGISTER);
- val1->value.numeric = reg1;
- val2 = dfvm_value_new(REGISTER);
- val2->value.numeric = reg2;
- insn->arg1 = val1;
- insn->arg2 = val2;
- dfw_append_insn(dfw, insn);
-
+ node1 = (stnode_t*)nodelist->data;
+ nodelist = g_slist_next(nodelist);
+ node2 = (stnode_t*)nodelist->data;
nodelist = g_slist_next(nodelist);
+ if (node2) {
+ /* Range element: add lower/upper bound test. */
+ reg2 = gen_entity(dfw, node1, &jmp2);
+ reg3 = gen_entity(dfw, node2, &jmp3);
+
+ /* Add test to see if the item is in range. */
+ insn = dfvm_insn_new(ANY_IN_RANGE);
+ val1 = dfvm_value_new(REGISTER);
+ val1->value.numeric = reg1;
+ val2 = dfvm_value_new(REGISTER);
+ val2->value.numeric = reg2;
+ val3 = dfvm_value_new(REGISTER);
+ val3->value.numeric = reg3;
+ insn->arg1 = val1;
+ insn->arg2 = val2;
+ insn->arg3 = val3;
+ dfw_append_insn(dfw, insn);
+ } else {
+ /* Normal element: add equality test. */
+ reg2 = gen_entity(dfw, node1, &jmp2);
+
+ /* Add test to see if the item matches */
+ gen_relation_regs(dfw, ANY_EQ, reg1, reg2);
+ }
+
/* Exit as soon as we find a match */
if (nodelist) {
insn = dfvm_insn_new(IF_TRUE_GOTO);
@@ -324,6 +350,10 @@ gen_relation_in(dfwork_t *dfw, stnode_t *st_arg1, stnode_t *st_arg2)
jmp2->value.numeric = dfw->next_insn_id;
jmp2 = NULL;
}
+ if (jmp3) {
+ jmp3->value.numeric = dfw->next_insn_id;
+ jmp3 = NULL;
+ }
}
/* Jump here if the LHS entity was not present */
diff --git a/epan/dfilter/grammar.lemon b/epan/dfilter/grammar.lemon
index 703876e..647816e 100644
--- a/epan/dfilter/grammar.lemon
+++ b/epan/dfilter/grammar.lemon
@@ -310,11 +310,26 @@ relation_test(T) ::= entity(E) TEST_IN LBRACE setnode_list(L) RBRACE.
setnode_list(L) ::= entity(E).
{
L = g_slist_append(NULL, E);
+ L = g_slist_append(L, NULL);
}
setnode_list(L) ::= setnode_list(P) entity(E).
{
L = g_slist_append(P, E);
+ L = g_slist_append(L, NULL);
+}
+
+/* Range elements. */
+setnode_list(L) ::= entity(X) DOTDOT entity(Y).
+{
+ L = g_slist_append(NULL, X);
+ L = g_slist_append(L, Y);
+}
+
+setnode_list(L) ::= setnode_list(P) entity(X) DOTDOT entity(Y).
+{
+ L = g_slist_append(P, X);
+ L = g_slist_append(L, Y);
}
/* Functions */
diff --git a/epan/dfilter/scanner.l b/epan/dfilter/scanner.l
index 93aaa19..744615a 100644
--- a/epan/dfilter/scanner.l
+++ b/epan/dfilter/scanner.l
@@ -119,6 +119,7 @@ static void mark_lval_deprecated(const char *s);
")" return simple(TOKEN_RPAREN);
"," return simple(TOKEN_COMMA);
"{" return simple(TOKEN_LBRACE);
+".." return simple(TOKEN_DOTDOT);
"}" return simple(TOKEN_RBRACE);
"==" return simple(TOKEN_TEST_EQ);
@@ -387,6 +388,7 @@ simple(int token)
case TOKEN_RBRACE:
case TOKEN_COLON:
case TOKEN_COMMA:
+ case TOKEN_DOTDOT:
case TOKEN_HYPHEN:
case TOKEN_TEST_EQ:
case TOKEN_TEST_NE:
diff --git a/epan/dfilter/semcheck.c b/epan/dfilter/semcheck.c
index 326adb8..7baf011 100644
--- a/epan/dfilter/semcheck.c
+++ b/epan/dfilter/semcheck.c
@@ -725,6 +725,7 @@ check_relation_LHS_FIELD(dfwork_t *dfw, const char *relation_string,
if (stnode_type_id(st_node) == STTYPE_TEST) {
sttype_test_set2_args(st_node, st_arg1, new_st);
} else {
+ /* Replace STTYPE_UNPARSED element by resolved value. */
sttype_set_replace_element(st_node, st_arg2, new_st);
}
stnode_free(st_arg2);
@@ -770,7 +771,10 @@ check_relation_LHS_FIELD(dfwork_t *dfw, const char *relation_string,
if (strcmp(relation_string, "in") != 0) {
g_assert_not_reached();
}
- /* Attempt to interpret one element of the set at a time */
+ /* Attempt to interpret one element of the set at a time. Each
+ * element is represented by two items in the list, the element
+ * value and NULL. Both will be replaced by a lower and upper
+ * value if the element is a range. */
nodelist = (GSList*)stnode_data(st_arg2);
while (nodelist) {
stnode_t *node = (stnode_t*)nodelist->data;
@@ -780,8 +784,26 @@ check_relation_LHS_FIELD(dfwork_t *dfw, const char *relation_string,
THROW(TypeError);
break;
}
- check_relation_LHS_FIELD(dfw, "==", can_func,
- allow_partial_value, st_arg2, st_arg1, node);
+
+ nodelist = g_slist_next(nodelist);
+ g_assert(nodelist);
+ stnode_t *node_right = (stnode_t *)nodelist->data;
+ if (node_right) {
+ /* range type, check if comparison is possible. */
+ if (!ftype_can_ge(ftype1)) {
+ dfilter_fail(dfw, "%s (type=%s) cannot participate in '%s' comparison.",
+ hfinfo1->abbrev, ftype_pretty_name(ftype1),
+ ">=");
+ THROW(TypeError);
+ }
+ check_relation_LHS_FIELD(dfw, ">=", ftype_can_ge,
+ allow_partial_value, st_arg2, st_arg1, node);
+ check_relation_LHS_FIELD(dfw, "<=", ftype_can_le,
+ allow_partial_value, st_arg2, st_arg1, node_right);
+ } else {
+ check_relation_LHS_FIELD(dfw, "==", can_func,
+ allow_partial_value, st_arg2, st_arg1, node);
+ }
nodelist = g_slist_next(nodelist);
}
}
diff --git a/epan/dfilter/sttype-set.c b/epan/dfilter/sttype-set.c
index 8b4634a..6705c42 100644
--- a/epan/dfilter/sttype-set.c
+++ b/epan/dfilter/sttype-set.c
@@ -11,10 +11,18 @@
#include "syntax-tree.h"
#include "sttype-set.h"
+/*
+ * The GSList stores a list of elements of the set. Each element is represented
+ * by two list items: (lower, upper) in case of a value range or (value, NULL)
+ * if the element is not a range value.
+ */
+
static void
slist_stnode_free(gpointer data)
{
- stnode_free((stnode_t *)data);
+ if (data) {
+ stnode_free((stnode_t *)data);
+ }
}
void
@@ -28,6 +36,9 @@ sttype_set_replace_element(stnode_t *node, stnode_t *oldnode, stnode_t *newnode)
{
GSList *nodelist = (GSList*)stnode_data(node);
+ /* This deliberately checks both the left and right nodes, covering both
+ * the lower and upper bound for ranges. NULL right nodes (in case of
+ * normal, non-range elements) will usually not match "oldnode". */
while (nodelist) {
if (nodelist->data == oldnode) {
nodelist->data = newnode;
diff --git a/epan/dfilter/sttype-set.h b/epan/dfilter/sttype-set.h
index cec2bdb..8faad34 100644
--- a/epan/dfilter/sttype-set.h
+++ b/epan/dfilter/sttype-set.h
@@ -17,6 +17,9 @@
void
sttype_set_replace_element(stnode_t *node, stnode_t *oldnode, stnode_t *newnode);
+gboolean
+sttype_set_convert_to_range(stnode_t **node_left, stnode_t **node_right);
+
void
set_nodelist_free(GSList *params);
diff --git a/tools/dfilter-test.py b/tools/dfilter-test.py
index 8c2ab12..61cae97 100755
--- a/tools/dfilter-test.py
+++ b/tools/dfilter-test.py
@@ -21,6 +21,7 @@ from dftestlib.double import testDouble
from dftestlib.integer import testInteger
from dftestlib.integer_1byte import testInteger1Byte
from dftestlib.ipv4 import testIPv4
+from dftestlib.membership import testMembership
from dftestlib.range_method import testRange
from dftestlib.scanner import testScanner
from dftestlib.string_type import testString
diff --git a/tools/dftestlib/membership.py b/tools/dftestlib/membership.py
new file mode 100644
index 0000000..f61bdd5
--- /dev/null
+++ b/tools/dftestlib/membership.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2018 Peter Wu <peter@lekensteyn.nl>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+
+from dftestlib import dftest
+
+class testMembership(dftest.DFTest):
+ trace_file = "http.pcap"
+
+ def test_membership_1_match(self):
+ dfilter = 'tcp.port in {80 3267}'
+ self.assertDFilterCount(dfilter, 1)
+
+ def test_membership_2_range_match(self):
+ dfilter = 'tcp.port in {80 .. 81}'
+ self.assertDFilterCount(dfilter, 1)
+
+ def test_membership_3_range_no_match(self):
+ dfilter = 'tcp.dstport in {1 .. 79 81 .. 65535}'
+ self.assertDFilterCount(dfilter, 0)
+
+ def test_membership_4_range_no_match_multiple(self):
+ # Verifies that multiple fields cannot satisfy different conditions.
+ dfilter = 'tcp.port in {1 .. 79 81 .. 3266 3268 .. 65535}'
+ self.assertDFilterCount(dfilter, 0)
+
+ def test_membership_5_negative_range_float(self):
+ dfilter = 'frame.time_delta in {-2.0 .. 0.0}'
+ self.assertDFilterCount(dfilter, 1)
+
+ def test_membership_6_both_negative_range_float(self):
+ dfilter = 'frame.time_delta in {-20 .. -.7}'
+ self.assertDFilterCount(dfilter, 0)
+
+ def test_membership_7_string(self):
+ dfilter = 'http.request.method in {"GET" "HEAD"}'
+ self.assertDFilterCount(dfilter, 1)
+
+ def test_membership_8_ip_range(self):
+ dfilter = 'ip.addr in { 10.0.0.5 .. 10.0.0.9 }'
+ self.assertDFilterCount(dfilter, 1)