[ovs-dev] [PATCH v1 00/18] python: add flow parsing library

Adrian Moreno amorenoz at redhat.com
Mon Nov 22 11:22:38 UTC 2021

While troubleshooting or developing new features in OVS, a considerable
amount of time is spent analyzing flows (whether that's Openflow flows
or datapath flows). Currently, OVS has tools to dump flows with
different levels of verbosity as well as to filter flows prior to
dumping them, e.g: 'ovs-ofctl dump-flows', 'ovs-appctl
dpctl/dump-flows', etc.

The output of these commands is considered stable so it should be
possible to write more layered tools that enable advanced flow analysis.
However, the way flows are formatted into strings is not trivial to

This series introduces the a flow parsing library capable of parsing
both Openflow and DPIF flows.

The library is based on generic key-value and list parsers and a number
of common decoders. Based on that, an Openflow flow parser and a DPIF
flow parser are introduced by defining a way to decode each possible
field and action they might contain.

The library has the following features:
- Parsed key-value pairs keep some metadata that refer to the original
  strings they were extracted from. That way the flows can be printed
  and formatted in flexible ways.
- It includes a basic flow filtering mechanism. A filter can be defined
  combining logical (||, &&, !), arithmetical (<, >, =) or mask (~=)
- It supports IPAddress and Ethernet masking (based on netaddr)
- The decoder to use for each key (match or action) is set explicitly to
  avoid expensive runtime type-guessing.
- The decoders to use for Openflow fields is automatically generated
  based on meta-flow.h
- Additional dependencies:
  - netaddr: For IP and Ethernet Address management
  - pyparsing: For filtering syntax
  - pytest: For unit tests:v

One key goal challenge of including this library is avoiding diversion
between the C code that prints/parses the flows and the python parsing
code. To that effect, the series introduces the following mechanisms:
- Decoding information of openflow fields is automatically generated
  based on meta-flow.h
- The calls to ovs-ofctl made from tests/ofp-actions.at are wrapped by a
  python script that also runs the python parsers. If an exception is
  raised by the python code (meaning it was not capable of parsing the
  flow string), the test will fail
- The calls to the test-odp made from tests/odp.at are wrapped by a
  python script that also runs the python parsers. If an exception is
  raised by the python code (meaning it was not capable of parsing the
  flow string), the test will fail.
- A python unit test target ensures python code works and it's easy to
  add more flow examples to it
- A dependency check is introduced. The python parsing code mainly
  depends on lib/ofp-actions.c and lib/odp-util.c. This series stores
  the md5sum of such files and adds a build target that ensures the
  files have not been changed. That way, anyone who modifies those files
  will get a warning the fist time they build the project. Dependency
  digests are easily updated using a string so hopefully this warning
  would not be too inconvenient.

Library usage
>>> from ovs.flows.ofp import OFPFlowFactory
>>> flow = OFPFlowFactory().from_string("cookie=0x2b32ab4d, table=41, n_packets=11, n_bytes=462, priority=33,ip,reg15=0x2/0x2,nw_src= actions=move:NXM_OF_TCP_DST[]->NXM_NX_XXREG0[32..47],ct(table=16,zone=NXM_NX_REG13[0..15],nat)")
>>> flow.info
{'cookie': 724740941, 'table': 41, 'n_packets': 11, 'n_bytes': 462}
>>> flow.match
{'priority': 33, 'ip': True, 'reg15': Mask32('0x2/0x2'), 'nw_src': IPMask('')}
>>> flow.actions
[{'move': {'src': {'field': 'NXM_OF_TCP_DST'}, 'dst': {'field': 'NXM_NX_XXREG0', 'start': 32, 'end': 47}}}, {'ct': {'table': 16, 'zone': {'field': 'NXM_NX_REG13', 'start': 0, 'end': 15}, 'nat': True}}]
>>> from ovs.flows.filter import OFFilter
>>> filt = OFFilter("nw_src ~= and (table = 42 or n_packets > 0)")
>>> filt.evaluate(flow)

Another way to try the library would be with a utility called
ovs-ofparse that I'll send to the list as RFC soon

RFC -> V1:
- filters: created a class to represent the filtering result. That way
  we can store more information such as what key actually triggered the
  match. This enables functionality such as highlighting of keys based
  on an expression
- Formatted python code according to flake8 requirements
- Split ofp actions in ofp_act.py
- drop ofparse utility (will send a RFC to the mail list soon)
- Moved the initialization of the decoders objects to a factory so they
  are cached. This significantly decreases parsing time of large dumps.

Adrian Moreno (18):
  python: add generic Key-Value parser
  python: add mask, ip and eth decoders
  python: add list parser
  build-aux: split extract-ofp-fields
  build-aux: generate ofp field decoders
  python: add flow base class
  python: introduce OpenFlow Flow parsing
  python: add ovs datapath flow parsing
  python: add flow filtering syntax
  python: add a json encoder to flow fields
  tests: wrap ovs-ofctl calls to test python parser
  tests: Wrap test-odp to also run python parsers
  python: detect changes in flow formatting code
  python: introduce unit tests
  python: add unit tests for list
  python: add unit tests for openflow parsing
  python: add unit tests to datapath parsing
  python: add unit tests for filtering engine

 .ci/linux-prepare.sh                    |   2 +-
 .gitignore                              |   2 +
 Documentation/intro/install/general.rst |   2 +
 Makefile.am                             |   3 +-
 Vagrantfile                             |   2 +-
 build-aux/automake.mk                   |   3 +-
 build-aux/extract-ofp-fields            | 393 +------------
 build-aux/gen_ofp_field_decoders        |  73 +++
 configure.ac                            |   1 +
 m4/openvswitch.m4                       |  12 +
 python/.gitignore                       |   1 +
 python/automake.mk                      |  51 +-
 python/build/extract_ofp_fields.py      | 386 +++++++++++++
 python/build/flow-parse-deps.py         | 101 ++++
 python/ovs/flows/__init__.py            |   0
 python/ovs/flows/decoders.py            | 468 +++++++++++++++
 python/ovs/flows/deps.py                |   5 +
 python/ovs/flows/filter.py              | 225 ++++++++
 python/ovs/flows/flow.py                |  94 +++
 python/ovs/flows/kv.py                  | 282 +++++++++
 python/ovs/flows/list.py                | 123 ++++
 python/ovs/flows/odp.py                 | 723 ++++++++++++++++++++++++
 python/ovs/flows/ofp.py                 | 400 +++++++++++++
 python/ovs/flows/ofp_act.py             | 233 ++++++++
 python/ovs/tests/test_filter.py         | 225 ++++++++
 python/ovs/tests/test_kv.py             |  74 +++
 python/ovs/tests/test_list.py           |  67 +++
 python/ovs/tests/test_odp.py            | 527 +++++++++++++++++
 python/ovs/tests/test_ofp.py            | 524 +++++++++++++++++
 python/setup.py                         |   4 +-
 tests/automake.mk                       |   5 +-
 tests/odp.at                            |  36 +-
 tests/ofp-actions.at                    |  46 +-
 tests/ovs-test-dpparse.py               |  83 +++
 tests/ovs-test-ofparse.py               | 103 ++++
 35 files changed, 4844 insertions(+), 435 deletions(-)
 create mode 100755 build-aux/gen_ofp_field_decoders
 create mode 100644 python/build/extract_ofp_fields.py
 create mode 100755 python/build/flow-parse-deps.py
 create mode 100644 python/ovs/flows/__init__.py
 create mode 100644 python/ovs/flows/decoders.py
 create mode 100644 python/ovs/flows/deps.py
 create mode 100644 python/ovs/flows/filter.py
 create mode 100644 python/ovs/flows/flow.py
 create mode 100644 python/ovs/flows/kv.py
 create mode 100644 python/ovs/flows/list.py
 create mode 100644 python/ovs/flows/odp.py
 create mode 100644 python/ovs/flows/ofp.py
 create mode 100644 python/ovs/flows/ofp_act.py
 create mode 100644 python/ovs/tests/test_filter.py
 create mode 100644 python/ovs/tests/test_kv.py
 create mode 100644 python/ovs/tests/test_list.py
 create mode 100644 python/ovs/tests/test_odp.py
 create mode 100644 python/ovs/tests/test_ofp.py
 create mode 100755 tests/ovs-test-dpparse.py
 create mode 100755 tests/ovs-test-ofparse.py


More information about the dev mailing list