diff options
-rw-r--r-- | doc/wireshark-filter.pod | 10 | ||||
-rw-r--r-- | docbook/release-notes.asciidoc | 4 | ||||
-rw-r--r-- | docbook/wsug_src/WSUG_chapter_work.asciidoc | 17 | ||||
-rw-r--r-- | epan/dfilter/dfvm.c | 46 | ||||
-rw-r--r-- | epan/dfilter/dfvm.h | 3 | ||||
-rw-r--r-- | epan/dfilter/gencode.c | 84 | ||||
-rw-r--r-- | epan/dfilter/grammar.lemon | 15 | ||||
-rw-r--r-- | epan/dfilter/scanner.l | 2 | ||||
-rw-r--r-- | epan/dfilter/semcheck.c | 28 | ||||
-rw-r--r-- | epan/dfilter/sttype-set.c | 13 | ||||
-rw-r--r-- | epan/dfilter/sttype-set.h | 3 | ||||
-rwxr-xr-x | tools/dfilter-test.py | 1 | ||||
-rw-r--r-- | tools/dftestlib/membership.py | 42 |
13 files changed, 235 insertions, 33 deletions
diff --git a/doc/wireshark-filter.pod b/doc/wireshark-filter.pod index a6ef64f58a..84754ec173 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 7ba15f5ee2..0c80e1bcee 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 6e91a4258b..57268b1502 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 cf7c0362f0..f93fd50edf 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 4014c8784d..68a65dd734 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 769fcc3143..dc003141ae 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 703876ebcb..647816e7a1 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 93aaa195fc..744615a0ec 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 326adb8563..7baf011940 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 8b4634a2c3..6705c42f13 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 cec2bdb773..8faad34b8f 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 8c2ab1209c..61cae97b58 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 0000000000..f61bdd5cf8 --- /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) |