dfilter: add string() function.
authorDario Lombardo <lomato@gmail.com>
Sun, 6 Jan 2019 09:34:32 +0000 (10:34 +0100)
committerPeter Wu <peter@lekensteyn.nl>
Mon, 14 Jan 2019 16:00:29 +0000 (16:00 +0000)
This function can convert non-string fields into strings. This allows the
user to apply string functions (like contains and matches) to non-string fields.

Examples:

string(frame.number) matches "[13579]$" => for odd frames
string(eth.dst) matches "aa\.bb\.cc\.dd\.ee\..." => to match a group of stations
string(snmp.name) matches "^1.2.3.4" => for all OIDs under a specific node

Change-Id: I18173f50ba5314ecdcd1e4b66c7e8ba5b44257ee
Reviewed-on: https://code.wireshark.org/review/31427
Petri-Dish: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
CMakeLists.txt
doc/wireshark-filter.pod
docbook/release-notes.asciidoc
docbook/wsug_src/WSUG_chapter_work.asciidoc
epan/dfilter/dfunctions.c
test/suite_dfilter/group_dfunction_string.py [new file with mode: 0644]

index f0853b9..5f74e5f 100644 (file)
@@ -3131,6 +3131,7 @@ set(_test_group_list
        suite_dfilter.group_bytes_ipv6
        suite_dfilter.group_bytes_type
        suite_dfilter.group_double
+       suite_dfilter.group_dfunction_string
        suite_dfilter.group_integer
        suite_dfilter.group_integer_1byte
        suite_dfilter.group_ipv4
index 98e5c60..b3d7d2c 100644 (file)
@@ -103,6 +103,7 @@ The filter language has the following functions:
     lower(string-field) - converts a string field to lowercase
     len(field)          - returns the byte length of a string or bytes field
     count(field)        - returns the number of field occurrences in a frame
+    string(field)       - converts a non-string field to string
 
 upper() and lower() are useful for performing case-insensitive string
 comparisons. For example:
@@ -110,6 +111,15 @@ comparisons. For example:
     upper(ncp.nds_stream_name) contains "MACRO"
     lower(mount.dump.hostname) == "angel"
 
+string() converts a field value to a string, suitable for use with operators like
+"matches" or "contains". Integer fields are converted to their decimal representation.
+It can be used with IP/Ethernet addresses (as well as others), but not with string or
+byte fields. For example:
+
+    string(frame.number) matches "[13579]$"
+
+gives you all the odd packets.
+
 =head2 Protocol field types
 
 Each protocol field is typed. The types are:
index c4acc98..e66cde2 100644 (file)
@@ -78,6 +78,9 @@ since version 2.6.0:
   Secrets Block (DSB) containing a TLS Key Log (wsbuglink:15252[]).
 * The editcap utility gained a new `--inject-secrets` option to inject an
   existing TLS Key Log file into a pcapng file.
+* A new dfilter function string() has been added. It allows the conversion of
+  non-string fields to strings so string functions (as contains and matches)
+  can be used on them.
 
 === Removed Features and Support
 
index e3b9dba..6bf8c94 100644 (file)
@@ -677,6 +677,7 @@ The display filter language has a number of functions to convert fields, see
 |lower   |Converts a string field to lowercase.
 |len     |Returns the byte length of a string or bytes field.
 |count   |Returns the number of field occurrences in a frame.
+|string  |Converts a non-string field to a string.
 |===============
 
 The `upper` and `lower` functions can used to force case-insensitive matches:
@@ -690,6 +691,21 @@ Usually an IP frame has only two addresses (source and destination), but in case
 of ICMP errors or tunneling, a single packet might contain even more addresses.
 These packets can be found with `count(ip.addr) > 2`.
 
+The `string` function converts a field value to a string, suitable for use with operators
+like "matches" or "contains". Integer fields are converted to their decimal representation.
+It can be used with IP/Ethernet addresses (as well as others), but not with string or
+byte fields.
+
+For example, to match odd frame numbers:
+----
+string(frame.number) matches "[13579]$"
+----
+
+to match IP addresses ending in 255 in a block of subnets (172.16 to 172.31):
+----
+string(ip.dst) matches "^172\.(1[6-9]|2[0-9]|3[0-1])\..{1,3}\.255"
+----
+
 [[ChWorkBuildDisplayFilterMistake]]
 
 ==== A Common Mistake
index 6f71c23..e8cfb33 100644 (file)
@@ -99,6 +99,70 @@ df_func_count(GList* arg1list, GList *arg2junk _U_, GList **retval)
     return TRUE;
 }
 
+/* dfilter function: string() */
+static gboolean
+df_func_string(GList* arg1list, GList *arg2junk _U_, GList **retval)
+{
+    GList    *arg1 = arg1list;
+    fvalue_t *arg_fvalue;
+    fvalue_t *new_ft_string;
+    char     *s;
+
+    while (arg1) {
+        arg_fvalue = (fvalue_t *)arg1->data;
+        switch (fvalue_type_ftenum(arg_fvalue))
+        {
+        case FT_UINT8:
+        case FT_UINT16:
+        case FT_UINT24:
+        case FT_UINT32:
+        case FT_UINT40:
+        case FT_UINT48:
+        case FT_UINT56:
+        case FT_UINT64:
+        case FT_INT8:
+        case FT_INT16:
+        case FT_INT32:
+        case FT_INT40:
+        case FT_INT48:
+        case FT_INT56:
+        case FT_INT64:
+        case FT_IPv4:
+        case FT_IPv6:
+        case FT_FLOAT:
+        case FT_DOUBLE:
+        case FT_ETHER:
+        case FT_FRAMENUM:
+        case FT_AX25:
+        case FT_IPXNET:
+        case FT_GUID:
+        case FT_OID:
+        case FT_EUI64:
+        case FT_VINES:
+        case FT_REL_OID:
+        case FT_SYSTEM_ID:
+        case FT_FCWWN:
+        case FT_IEEE_11073_SFLOAT:
+        case FT_IEEE_11073_FLOAT:
+            s = fvalue_to_string_repr(NULL, arg_fvalue, FTREPR_DFILTER, BASE_NONE);
+            /* Ensure we have an allocated string here */
+            if (!s)
+                s = wmem_strdup(NULL, "");
+            break;
+        default:
+            return TRUE;
+        }
+
+        new_ft_string = fvalue_new(FT_STRING);
+        fvalue_set_string(new_ft_string, s);
+        wmem_free(NULL, s);
+        *retval = g_list_append(*retval, new_ft_string);
+
+        arg1 = arg1->next;
+    }
+
+    return TRUE;
+}
 
 /* For upper() and lower() checks that the parameter passed to
  * it is an FT_STRING */
@@ -174,13 +238,80 @@ ul_semcheck_field_param(dfwork_t *dfw, int param_num, stnode_t *st_node)
     }
 }
 
+static void
+ul_semcheck_string_param(dfwork_t *dfw, int param_num, stnode_t *st_node)
+{
+    sttype_id_t type;
+    ftenum_t    ftype;
+    header_field_info *hfinfo;
+    const char* msg = "To string conversion for this field is not supported";
+
+    type = stnode_type_id(st_node);
+
+    if (param_num == 0) {
+        switch(type) {
+            case STTYPE_FIELD:
+                hfinfo = (header_field_info *)stnode_data(st_node);
+                ftype = hfinfo->type;
+                switch (ftype)
+                {
+                case FT_UINT8:
+                case FT_UINT16:
+                case FT_UINT24:
+                case FT_UINT32:
+                case FT_UINT40:
+                case FT_UINT48:
+                case FT_UINT56:
+                case FT_UINT64:
+                case FT_INT8:
+                case FT_INT16:
+                case FT_INT32:
+                case FT_INT40:
+                case FT_INT48:
+                case FT_INT56:
+                case FT_INT64:
+                case FT_IPv4:
+                case FT_IPv6:
+                case FT_FLOAT:
+                case FT_DOUBLE:
+                case FT_ETHER:
+                case FT_FRAMENUM:
+                case FT_AX25:
+                case FT_IPXNET:
+                case FT_GUID:
+                case FT_OID:
+                case FT_EUI64:
+                case FT_VINES:
+                case FT_REL_OID:
+                case FT_SYSTEM_ID:
+                case FT_FCWWN:
+                case FT_IEEE_11073_SFLOAT:
+                case FT_IEEE_11073_FLOAT:
+                    break;
+                default:
+                    dfilter_fail(dfw, "%s", msg);
+                    THROW(TypeError);
+                    break;
+                }
+                break;
+            default:
+                dfilter_fail(dfw, "%s", msg);
+                THROW(TypeError);
+        }
+    }
+    else {
+        g_assert_not_reached();
+    }
+}
+
 /* The table of all display-filter functions */
 static df_func_def_t
 df_functions[] = {
-    { "lower", df_func_lower, FT_STRING, 1, 1, ul_semcheck_params },
-    { "upper", df_func_upper, FT_STRING, 1, 1, ul_semcheck_params },
-    { "len",   df_func_len,   FT_UINT32, 1, 1, ul_semcheck_len_params },
-    { "count", df_func_count, FT_UINT32, 1, 1, ul_semcheck_field_param },
+    { "lower",  df_func_lower,  FT_STRING, 1, 1, ul_semcheck_params },
+    { "upper",  df_func_upper,  FT_STRING, 1, 1, ul_semcheck_params },
+    { "len",    df_func_len,    FT_UINT32, 1, 1, ul_semcheck_len_params },
+    { "count",  df_func_count,  FT_UINT32, 1, 1, ul_semcheck_field_param },
+    { "string", df_func_string, FT_STRING, 1, 1, ul_semcheck_string_param },
     { NULL, NULL, FT_NONE, 0, 0, NULL }
 };
 
diff --git a/test/suite_dfilter/group_dfunction_string.py b/test/suite_dfilter/group_dfunction_string.py
new file mode 100644 (file)
index 0000000..c1bd173
--- /dev/null
@@ -0,0 +1,39 @@
+# Copyright (c) 2019 by Dario Lombardo <lomato@gmail.com>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import unittest
+import fixtures
+from suite_dfilter.dfiltertest import *
+
+@fixtures.uses_fixtures
+class case_dfunction_string(unittest.TestCase):
+    trace_file = "dhcp.pcap"
+
+    def test_matches_1(self, checkDFilterCount):
+        dfilter = "string(frame.number) matches \"[13579]$\""
+        checkDFilterCount(dfilter, 2)
+
+    def test_contains_1(self, checkDFilterCount):
+        dfilter = "string(eth.src) contains \"00:08:74\""
+        checkDFilterCount(dfilter, 2)
+
+    def test_fail_1(self, checkDFilterFail):
+        # Invalid filter (only non-string fields are supported)
+        dfilter = "string(dhcp.server) == hostname"
+        checkDFilterFail(dfilter)
+
+    def test_fail_2(self, checkDFilterFail):
+        # Invalid field: value
+        dfilter = "string(123) == \"123\""
+        checkDFilterFail(dfilter)
+
+    def test_fail_3(self, checkDFilterFail):
+        # Invalid field: protocol
+        dfilter = "string(dhcp) == hostname"
+        checkDFilterFail(dfilter)
+
+    def test_fail_4(self, checkDFilterFail):
+        # Invalid field: bytes
+        dfilter = "string(dhcp.option.value) == \"hostname\""
+        checkDFilterFail(dfilter)