From 776a436337dbc47c62a09115647ed25e244494d1 Mon Sep 17 00:00:00 2001 From: Adrian Moreno Date: Wed, 25 Sep 2024 12:52:14 +0200 Subject: [PATCH] documentation: Document ovs-flowviz. Add a man page for ovs-flowviz as well as a topic page with some more detailed examples. Signed-off-by: Adrian Moreno Signed-off-by: 0-day Robot --- Documentation/automake.mk | 4 +- Documentation/conf.py | 4 + Documentation/ref/index.rst | 1 + Documentation/ref/ovs-flowviz.8.rst | 539 ++++++++++++++++++++ Documentation/topics/flow-visualization.rst | 313 ++++++++++++ Documentation/topics/index.rst | 1 + NEWS | 5 +- rhel/openvswitch-fedora.spec.in | 1 + rhel/openvswitch.spec.in | 1 + 9 files changed, 867 insertions(+), 2 deletions(-) create mode 100644 Documentation/ref/ovs-flowviz.8.rst create mode 100644 Documentation/topics/flow-visualization.rst diff --git a/Documentation/automake.mk b/Documentation/automake.mk index 47d2e336a0b..539870aa29e 100644 --- a/Documentation/automake.mk +++ b/Documentation/automake.mk @@ -45,7 +45,7 @@ DOC_SOURCE = \ Documentation/topics/fuzzing/ovs-fuzzing-infrastructure.rst \ Documentation/topics/fuzzing/ovs-fuzzers.rst \ Documentation/topics/fuzzing/security-analysis-of-ovs-fuzzers.rst \ - Documentation/topics/testing.rst \ + Documentation/topics/flow-visualization.rst \ Documentation/topics/integration.rst \ Documentation/topics/language-bindings.rst \ Documentation/topics/networking-namespaces.rst \ @@ -55,6 +55,7 @@ DOC_SOURCE = \ Documentation/topics/ovsdb-replication.rst \ Documentation/topics/porting.rst \ Documentation/topics/record-replay.rst \ + Documentation/topics/testing.rst \ Documentation/topics/tracing.rst \ Documentation/topics/usdt-probes.rst \ Documentation/topics/userspace-checksum-offloading.rst \ @@ -162,6 +163,7 @@ RST_MANPAGES = \ ovs-actions.7.rst \ ovs-appctl.8.rst \ ovs-ctl.8.rst \ + ovs-flowviz.8.rst \ ovs-l3ping.8.rst \ ovs-parse-backtrace.8.rst \ ovs-pki.8.rst \ diff --git a/Documentation/conf.py b/Documentation/conf.py index 2364405ade8..303973fc93a 100644 --- a/Documentation/conf.py +++ b/Documentation/conf.py @@ -112,6 +112,8 @@ # Define the canonical URL for our domain configured on Read the Docs. html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") +option_emphasise_placeholders = True + # Tell Jinja2 templates the build is running on Read the Docs. html_context = {} if os.environ.get("READTHEDOCS", "") == "True": @@ -128,6 +130,8 @@ u'utility for configuring running Open vSwitch daemons'), ('ovs-ctl.8', u'OVS startup helper script'), + ('ovs-flowviz.8', + u'utility for visualizing OpenFlow and datapath flows'), ('ovs-l3ping.8', u'check network deployment for L3 tunneling problems'), ('ovs-parse-backtrace.8', diff --git a/Documentation/ref/index.rst b/Documentation/ref/index.rst index 03ada932f56..7f2fe617736 100644 --- a/Documentation/ref/index.rst +++ b/Documentation/ref/index.rst @@ -42,6 +42,7 @@ time: ovs-actions.7 ovs-appctl.8 ovs-ctl.8 + ovs-flowviz.8 ovs-l3ping.8 ovs-pki.8 ovs-sim.1 diff --git a/Documentation/ref/ovs-flowviz.8.rst b/Documentation/ref/ovs-flowviz.8.rst new file mode 100644 index 00000000000..28172047d6a --- /dev/null +++ b/Documentation/ref/ovs-flowviz.8.rst @@ -0,0 +1,539 @@ +.. + Licensed under the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain + a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations + under the License. + + Convention for heading levels in Open vSwitch documentation: + + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + + Avoid deeper levels because they do not render well. + +=========== +ovs-flowviz +=========== + +Synopsis +======== + +``ovs-flowviz`` +[``-i`` *[alias,]file* | ``--input`` *[alias,]file*] +[``-c`` *file* | ``--config`` *file*] +[``-f`` *filter* | ``--filter`` *filter*] +[``-h`` *filter* | ``--highlight`` *filter*] +[``--style`` *style*] +*flow_type* *format* [*args*...] + +``ovs-flowviz --help`` + +Description +=========== + +``ovs-flowviz`` helps visualize OpenFlow and datapath flow dumps in different +formats in order to make them more easily understood. + +``ovs-flowviz`` reads flows from ``stdin`` or from a file specified by the +``--input`` option, filters them, highlights them, and finally outputs +them in one of the predefined formats. + + +Options +======= + +.. program: ovs-flowviz + +.. option:: -h, --help + + Print a brief help message to the console. + +.. option:: -i [alias,]file, --input [alias,]file + + File to read flows from. If not provided, ``ovs-flowviz`` + will read flows from stdin. + + This option can be specified multiple times. + The file path can prepended by an alias that will be shown in the output. + For example: ``--input node1,/path/to/file1 --input node2,/path/to/file2`` + +.. option:: -c file, --config file + + Style configuration file to use, overriding the default one. + Styles defined in the style configuration file can be selected using + the ``--style`` option. + + For more details on the style configuration file, see the + `Style Configuration File`_ section below. + +.. option:: -f filter, --filter filter + + Flow filter expression. Only those flows matching the expression will be + shown (although some formats implement filtering differently, see the + `Datapath tree format`_ section below). + + The filtering syntax is detailed in `Filtering Syntax`_. + +.. option:: -h filter, --highlight filter + + Highlight the flows that match the provided filter expression. + + The filtering syntax is detailed in `Filtering Syntax`_. + +.. option:: --style style + + Style. The selected style must be defined in the style configuration file. + +.. option:: flow_type + + "openflow" or "datapath". + +.. option:: format + + See the `Supported formats`_ section. + + +Supported formats +================= + +``ovs-flowviz`` supports several visualization formats for both OpenFlow and +datapath flows: + +.. list-table:: + :widths: 20 10 70 + :align: center + :header-rows: 1 + + * - Flow Type + - Format + - Description + * - Both + - console + - Prints the flows in a configurable, colorful style in the console. + * - Both + - json + - Prints the flows in JSON format. + * - Both + - html + - Prints the flows in an HTML list. + * - Openflow + - cookie + - Prints the flows in the console sorted by cookie. + * - Openflow + - logic + - Prints the logical structure of flows in the console. + * - Datapath + - tree + - Prints the flows as a tree structure arranged by `recirc_id` and `in_port`. + * - Datapath + - graph + - Prints a graphviz graph of the flows arranged by `recirc_id` and + `in_port`. + + +Console format +~~~~~~~~~~~~~~ + +The ``console`` format works for both OpenFlow and datapath flow types, and +prints flows in the terminal using the style determined by the ``--style`` +option. + +Arguments: + +.. option:: -h, --heat-map + + Color of the packet and byte counters to reflect their relative size. + The color gradient goes through the following colors: + blue (coldest, lowest), cyan, green, yellow, red (hottest, highest) + + Note filtering is applied before the range is calculated. + + +JSON format +~~~~~~~~~~~ + +The ``json`` format works for both OpenFlow and datapath flow types, and prints +flows in JSON format. See the `JSON Syntax`_ section for more details. + + +HTML format +~~~~~~~~~~~ + +The ``html`` format works for both OpenFlow and datapath flows, and prints +flows in an HTML table that offers some basic interactivity. OpenFlow flows +are sorted in tables and datapath flows are arranged in flow trees +(see `Datapath tree format`_ for more details). + +Styles defined via Style Configuration File and selected via ``--style`` option +also apply to the ``html`` format. + + +OpenFlow cookie format +~~~~~~~~~~~~~~~~~~~~~~ + +The OpenFlow ``cookie`` format is similar to the ``console`` format but +instead of arranging the flows by table, it arranges the flows by cookie. + + +Openflow logic format +~~~~~~~~~~~~~~~~~~~~~ + +The OpenFlow ``logic`` format helps visualize the logic structure of OpenFlow +pipelines by arranging flows into *logical blocks*. +A logical block is a set of flows that have: + +* Same ``priority``. +* Match on the same fields (regardless of the match value and mask). +* Execute the same actions (regardless of the actions' arguments, + except for resubmit and output). +* Optionally, the ``cookie`` can be included as part of the logical flow. + +Arguments: + +.. option:: -s, --show-flows + + Show all the flows under each logical block. + +.. option:: -d, --ovn-detrace + + Use ovn-detrace.py script to extract cookie information (implies '-c'). + +.. option:: -c, --cookie + + Consider the cookie in the logical block. + +.. option:: --ovn-detrace-path path + + Use an alternative path to search for ovn_detrace.py. + +.. option:: --ovnnb-db text + + OVN NB database string (implies '-d'). + Default: "unix:/var/run/ovn/ovnnb_db.sock". + +.. option:: --ovnsb-db text + + OVN SB database string (implies '-d'). + Default: "unix:/var/run/ovn/ovnsb_db.sock". + +.. option:: --o text, --ovn-filter text + + Specify the filter to be run on the ovn-detrace information. + Syntax: python regular expression + (See https://docs.python.org/3/library/re.html). + +.. option:: -h, --heat-map + + Change the color of the packet and byte counters to reflect their relative + size. The color gradient goes through the following colors: + blue (coldest, lowest), cyan, green, yellow, red (hottest, highest) + + Note filtering is applied before the range is calculated. + + +Datapath tree format +~~~~~~~~~~~~~~~~~~~~ + +The datapath ``tree`` format arranges datapath flows in a hierarchical tree. +The tree is comprised of blocks with the same `recirc_id` and `in_port`. +Within those blocks, flows with the same action are combined. And matches +which are the same are omitted to reduce the visual noise. + +When a flow's actions includes the `recirc()` action with a specific +`recirc_id`, flows matching on that `recirc_id` and the same `in_port` are +listed below. This is done recursively for all actions. + +The result is a hierarchical representation that shows how actions are related +to each other via recirculation. Note that flows with a specific non-zero +`recirc_id` are listed below each group of flows that have a corresponding +`recirc()` action. Therefore, the output contains duplicated flows and can be +verbose. + +Filtering works in a slightly different way for datapath flow trees. +Unlike other formats where a filter simply removes non-matching flows, +the output of a filtered datapath flow tree will show full sub-trees +that contain at least one flow that satisfies the filter. + +The ``html`` format prints this same tree as an interactive HTML table and +the ``graph`` format shows the same tree as a graphviz graph. + + +Datapath graph format +~~~~~~~~~~~~~~~~~~~~~ + +The datapath ``graph`` generates a graphviz visual representation of the +same tree-like flow hierarchy that the ``tree`` format prints. + +Arguments: + +.. option:: -h, --html + + Print the graphviz format as an svg image alongside an interactive HTML + table of flows. + + +JSON Syntax +=========== + +Printing a single-file OpenFlow or datapath dump without pmd thread blocks in +``json`` format results in a list of JSON objects, each representing a flow. + +This list can be found inside one or more levels of JSON dictionaries +if multiple files are processed (filename used as key) or if pmd thread blocks +are found in datapath flows (name of the thread used as key). + +Each flow object includes the following keys: + +**orig** + Original flow string. + + +**info** + Object with the flow information such as: cookie, duration, table, + n_packets, n_bytes, etc. + + +**match** + Object with the flow match. + For each match, the object contains a key-value where the key is the name + of the match as defined in ovs-fields(7) and ovs-ofctl(8), and the value + represents the match value. The way each value is represented depends on its + type. See `Value representation`_. + + +**actions** + List of action objects. + Each action is represented by an JSON object that has one key and one value. + The key corresponds to the action name. The value represents the arguments + of the key. See `Action representation`_. + + +**ufid** + The UFID (datapath flows only). + + +Value representation +~~~~~~~~~~~~~~~~~~~~ + +Values are represented differently depending on their type: + +* Flags: The value of flags is "true". + +* Decimal / Hexadecimal: Represented by their integer value. + If they support masking, represented by a dictionary with two keys: + ``value`` contains the field value and ``mask`` contains the mask. + Both are integers. + +* Ethernet: Represented by a string: "{address}[/{mask}]" + +* IPv4 / IPv6: Represented by a string "{address}[/{mask]}" + +* Registers: Represented by a dictionary with three keys: + ``field``` contains the field value (string), ``start``, and ``end`` + represent the first and last bit of the register value. + +For example, the register +:: + + + NXM_NX_REG10[0..15] + + +is represented as +:: + + + { + "field": "NXM_NX_REG10", + "start": 0, + "end": 15 + }, + + +Action representation +~~~~~~~~~~~~~~~~~~~~~ + +Actions are generally represented by an object that has a single key and +value. The key is the action name as defined ovs-actions(7). + +The value of actions that have no arguments (such as ``drop``) is +(boolean) ``true``. + +The value of actions that have a list of arguments (e.g: +``resubmit([port],[table],[ct])``) is an object that has the name of the +argument as key. The argument names for each action is defined in +ovs-actions. For example, the action +:: + + resubmit(,10) + +is represented as +:: + + { + "redirect": { + "port": "", + "table": 10 + } + } + +The value of actions that have a key-word list as arguments +(e.g: ``ct([argument])``) is an object whose keys correspond to the keys +defined in ``ovs-actions(7)``. The way values are represented depends +on the type of the argument. +For example, the action +:: + + ct(table=14,zone=NXM_NX_REG12[0..15],nat) + +is represented as +:: + + { + "ct": { + "table": 14, + "zone": { + "field": "NXM_NX_REG12", + "start": 0, + "end": 15 + }, + "nat": true + } + } + + +Style Configuration File +======================== + +The style configuration file is selected via the ``--config`` option +and has INI syntax. It can define any number of styles to be used by both +``console`` and ``html`` formats. Once defined in the configuration file, +formats are selected using the ``--style`` option. + +INI sections are used to define styles, ``[styles.mystyle]`` defines a style +called `mystle`. Within a section styles can be defined as: + +:: + + [FORMAT].[PORTION].[SELECTOR].[ELEMENT] = [VALUE] + + +**FORMAT** + Either ``console`` or ``html`` + +**PORTION** + Part of the key-value the style applies to: + ``key`` to indicate the key part of a key-value, ``value`` to indicate + the value part of a key-value, ``flag`` to indicate a single flag + or ``delim`` to indicate delimiters such as parentheses, brackets, etc. + +**SELECTOR** + Select the key-value the style applies to: + ``highlighted`` to indicate highlighted key-values, ``type.`` + to indicate certain types such as `IPAddress` or `EthMask` or `` + to select a particular key name. + +**ELEMENT** + Select select the style element to modify: + **color** or **underline** (only for ``console`` format). + +**VALUE** + Ether a color hex, other color names defined in the rich python + library (https://rich.readthedocs.io/en/stable/appendix/colors.html) or + **true** if the element is ``underline``. + +A default configuration file is shipped with `ovs-flowviz` and its path is +printed in the ``--help`` output. A detailed description of the syntax +alongside some examples are available there. + + +Filtering syntax +================ + +``ovs-flowviz`` provides rich highlighting and filtering. The special command +``ovs-flowviz filter`` dumps the filtering syntax: + +:: + + $ ovs-flowviz filter + Filter Syntax + ************* + + [! | not ] {key}[[.subkey]...] [OPERATOR] {value})] [LOGICAL OPERATOR] ... + + Comparison operators: + = equality + < less than + > more than + ~= masking (valid for IP and Ethernet fields) + + Logical operators: + !{expr}: NOT + {expr} && {expr}: AND + {expr} || {expr}: OR + + Matches and flow metadata: + To compare against a match or info field, use the field directly, e.g: + priority=100 + n_bytes>10 + Use simple keywords for flags: + tcp and ip_src=192.168.1.1 + + Actions: + Actions values might be dictionaries, use subkeys to access individual + values, e.g: + output.port=3 + Use simple keywords for flags + drop + + Examples of valid filters: + nw_addr~=192.168.1.1 && (tcp_dst=80 || tcp_dst=443) + arp=true && !arp_tsa=192.168.1.1 + n_bytes>0 && drop=true + + +Example expressions: +:: + + n_bytes > 0 and drop + nw_src~=192.168.1.1 or arp.tsa=192.168.1.1 + ! tcp && output.port=2 + + +Examples +======== + +Print OpenFlow flows sorted by cookie adding OVN data to each one: +:: + + $ ovs-flowviz -i flows.txt openflow cookie --ovn-detrace + +Print OpenFlow logical structure, showing the flows and heat-map: +:: + + $ ovs-flowviz -i flows.txt openflow logic --show-flows --heat-map + +Display OpenFlow flows in HTML format with "light" style and highlight drops: +:: + + $ ovs-flowviz -i flows.txt --style "light" --highlight "n_packets > 0 and drop" openflow html > flows.html + +Display the datapath flows in an interactive graphviz + HTML view: +:: + + $ ovs-flowviz -i flows.txt datapath graph --html > flows.html + +Display the datapath flow trees that lead to packets being sent to port 10: +:: + + $ ovs-flowviz -i flows.txt --filter "output.port=10" datapath tree diff --git a/Documentation/topics/flow-visualization.rst b/Documentation/topics/flow-visualization.rst new file mode 100644 index 00000000000..3165f796fb6 --- /dev/null +++ b/Documentation/topics/flow-visualization.rst @@ -0,0 +1,313 @@ +.. + Licensed under the Apache License, Version 2.0 (the "License"); you may + not use this file except in compliance with the License. You may obtain + a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + License for the specific language governing permissions and limitations + under the License. + + Convention for heading levels in Open vSwitch documentation: + + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + + Avoid deeper levels because they do not render well. + +================================== +Visualizing flows with ovs-flowviz +================================== + +When troubleshooting networking issues with OVS, it's common to end up looking +at OpenFlow or datapath flow dumps. These dumps tend to be quite dense and +difficult to reason about. + +``ovs-flowviz`` is a utility script that helps visualizing OpenFlow and +datapath flows to make it easier to understand what is going on. + +The `ovs-flowviz(8)`_ manpage describes its basic usage. In this document a few +of its advanced visualization formats will be expanded. + + +Installing ovs-flowviz +---------------------- + +``ovs-flowviz`` is part of the openvswitch python package but its +extra dependencies have to be installed explicitly by running: +:: + + $ pip install openvswitch[flowviz] + +Or, if you are working with the OVS tree: +:: + + $ cd python && pip install .[flowviz] + +Visualizing OpenFlow logical block +---------------------------------- + +When controllers such as OVN write OpenFlow flows, they typically organize +flows in functional blocks. These blocks can expand to multiple flows that +"look similar", in the sense that they match on the same fields and have +similar actions. + +However, looking at a flow dump the number of flows can make it difficult +to perceive this logical functionality that the controller is trying to +implement using OpenFlow. + +``ovs-flowviz openflow logic`` visualization can be used to understand an OVN +flow dump a bit better. + +On a particular flow dump table 0 contains 23 flows: +:: + + $ grep -c "table=0" flows.txt + 23 + +Looking at the first few lines, the amount of information can be +overwhelming and difficult our analysis: + +:: + + $ head flows.txt + cookie=0xf76b4b20, duration=765.107s, table=0, n_packets=0, n_bytes=0, priority=180,vlan_tci=0x0000/0x1000 actions=conjunction(100,2/2) + cookie=0xf76b4b20, duration=765.107s, table=0, n_packets=0, n_bytes=0, priority=180,conj_id=100,in_port="patch-br-int-to",vlan_tci=0x0000/0x1000 actions=load:0xa->NXM_NX_REG13[],load:0xc->NXM_NX_REG11[],load:0xb->NXM_NX_REG12[],load:0xb->OXM_OF_METADATA[],load:0x1->NXM_NX_REG14[],mod_dl_src:02:42:ac:12:00:03,resubmit(,8) + cookie=0x0, duration=765.388s, table=0, n_packets=0, n_bytes=0, priority=100,in_port="ovn-6bb3b3-0" actions=move:NXM_NX_TUN_ID[0..23]->OXM_OF_METADATA[0..23],move:NXM_NX_TUN_METADATA0[16..30]->NXM_NX_REG14[0..14],move:NXM_NX_TUN_METADATA0[0..15]->NXM_NX_REG15[0..15],resubmit(,40) + cookie=0x0, duration=765.388s, table=0, n_packets=0, n_bytes=0, priority=100,in_port="ovn-a6ff98-0" actions=move:NXM_NX_TUN_ID[0..23]->OXM_OF_METADATA[0..23],move:NXM_NX_TUN_METADATA0[16..30]->NXM_NX_REG14[0..14],move:NXM_NX_TUN_METADATA0[0..15]->NXM_NX_REG15[0..15],resubmit(,40) + cookie=0xf2ca6195, duration=765.107s, table=0, n_packets=6, n_bytes=636, priority=100,in_port="ovn-k8s-mp0" actions=load:0x1->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x2->NXM_NX_REG14[],resubmit(,8) + cookie=0x236e941d, duration=408.874s, table=0, n_packets=11, n_bytes=846, priority=100,in_port=aceac9829941d11 actions=load:0x11->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x3->NXM_NX_REG14[],resubmit(,8) + cookie=0x3facf689, duration=405.581s, table=0, n_packets=11, n_bytes=846, priority=100,in_port="363ba22029cd92b" actions=load:0x12->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x4->NXM_NX_REG14[],resubmit(,8) + cookie=0xe7c8c4bb, duration=405.570s, table=0, n_packets=11, n_bytes=846, priority=100,in_port="6a62cde0d50ef44" actions=load:0x13->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x5->NXM_NX_REG14[],resubmit(,8) + cookie=0x99a0ffc1, duration=59.391s, table=0, n_packets=8, n_bytes=636, priority=100,in_port="5ff3bfaaa4eb622" actions=load:0x14->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x6->NXM_NX_REG14[],resubmit(,8) + cookie=0xe1b5c263, duration=59.365s, table=0, n_packets=8, n_bytes=636, priority=100,in_port="8d9e0bc76347e59" actions=load:0x15->NXM_NX_REG13[],load:0x2->NXM_NX_REG11[],load:0x7->NXM_NX_REG12[],load:0x4->OXM_OF_METADATA[],load:0x7->NXM_NX_REG14[],resubmit(,8) + + +However, table 0 can be better understood by looking at its logical +representation: +:: + + $ ovs-flowviz -i flows.txt -f "table=0" openflow logic + Ofproto Flows (logical) + └── ** TABLE 0 ** + ├── priority=180 priority,vlan_tci ---> conjunction ( x 1 ) + ├── priority=180 priority,conj_id,in_port,vlan_tci ---> load,load,load,load,load,mod_dl_src resubmit(,8), ( x 1 ) + ├── priority=100 priority,in_port ---> move,move,move resubmit(,40), ( x 2 ) + ├── priority=100 priority,in_port ---> load,load,load,load,load resubmit(,8), ( x 16 ) + ├── priority=100 priority,in_port,vlan_tci ---> load,load,load,load,load resubmit(,8), ( x 1 ) + ├── priority=100 priority,in_port,dl_vlan ---> strip_vlan,load,load,load,load,load resubmit(,8), ( x 1 ) + └── priority=0 priority ---> drop, ( x 1 ) + + +In only a few logical blocks, there is a good overview of what this table is +doing. It looks like it's adding metadata based on input ports and vlan +IDs and mainly sending traffic to table 8. + +A possible next step might be to look at table 8, and in this case, filter out +the flows that have not been hit by actual traffic. +This is quite easy to do with the arithmetic filtering expressions: +:: + + $ ovs-flowviz -i flows.txt -f "table=8 and n_packets>0" openflow logic + + Ofproto Flows (logical) + └── ** TABLE 8 ** + ├── priority=50 priority,reg14,metadata,dl_dst ---> load resubmit(,9), ( x 3 ) + └── priority=50 priority,metadata ---> load,move resubmit(,73),resubmit(,9), ( x 2 ) + +At this point, understanding the output might be difficult without relating it +to the matadata OVN stored in the previous table. This is where +``ovs-flowviz``'s OVN integration is useful: +:: + + $ export OVN_NB_DB=tcp:172.18.0.4:6641 + $ export OVN_SB_DB=tcp:172.18.0.4:6642 + $ ovs-flowviz -i flows.txt -f "table=8 and n_packets>0" openflow logic --ovn-detrace + Ofproto Flows (logical) + └── ** TABLE 8 ** + ├── cookie=0xe10c34ee priority=50 priority,reg14,metadata,dl_dst ---> load resubmit(,9), ( x 1 ) + │ └── OVN Info + │ ├── * Logical datapaths: + │ ├── * "ovn_cluster_router" (366e1c41-0f3d-4420-b796-10692b64e3e4) + │ ├── * Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.mcast && inport == "rtos-ovn-worker2), actions=(xreg0[0..47] = 0a:58:0a:f4:01:01; next;) + │ └── * Logical Router Port: rtos-ovn-worker2 mac 0a:58:0a:f4:01:01 networks ['10.244.1.1/24'] ipv6_ra_configs {} + ├── cookie=0x11e1adbc priority=50 priority,reg14,metadata,dl_dst ---> load resubmit(,9), ( x 1 ) + │ └── OVN Info + │ ├── * Logical datapaths: + │ ├── * "GR_ovn-worker2" (c07f8387-6479-4e81-9304-9f8e54f81c56) + │ ├── * Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.mcast && inport == "rtoe-GR_ovn-worker2), actions=(xreg0[0..47] = 02:42:ac:12:00:03; next;) + │ └── * Logical Router Port: rtoe-GR_ovn-worker2 mac 02:42:ac:12:00:03 networks ['172.18.0.3/16'] ipv6_ra_configs {} + ├── cookie=0xf42133f priority=50 priority,reg14,metadata,dl_dst ---> load resubmit(,9), ( x 1 ) + │ └── OVN Info + │ ├── * Logical datapaths: + │ ├── * "GR_ovn-worker2" (c07f8387-6479-4e81-9304-9f8e54f81c56) + │ ├── * Logical flow: table=0 (lr_in_admission), priority=50, match=(eth.dst == 02:42:ac:12:00:03 && inport == "rtoe-GR_ovn-worker2), actions=(xreg0[0..47] = 02:42:ac:12:00:03; next;) + │ └── * Logical Router Port: rtoe-GR_ovn-worker2 mac 02:42:ac:12:00:03 networks ['172.18.0.3/16'] ipv6_ra_configs {} + └── cookie=0x43a0327 priority=50 priority,metadata ---> load,move resubmit(,73),resubmit(,9), ( x 2 ) + └── OVN Info + ├── * Logical datapaths: + ├── * "ovn-worker" (24280d0b-fee0-4f8e-ba4f-036a9b9af921) + ├── * "ovn-control-plane" (3262a782-8961-416b-805e-08233e8fda72) + ├── * "ext_ovn-worker2" (3f88dcd2-c56d-478f-a3b1-c7aee2efe967) + ├── * "ext_ovn-worker" (5facbaf0-485d-4cf5-8940-eff9678ef7bb) + ├── * "ext_ovn-control-plane" (8b0aecb6-b05a-48a7-ad09-72524bb91d40) + ├── * "join" (e2dc230e-2f2a-4b93-93fa-0fe495163514) + ├── * "ovn-worker2" (f7709fbf-d728-4cff-9b9b-150461cc75d2) + └── * Logical flow: table=0 (ls_in_check_port_sec), priority=50, match=(1), actions=(reg0[15] = check_in_port_sec(); next;) + +``ovs-flowviz`` has automatically added the `cookie` to the logical block key +so more blocks have been printed. In exchange, it has looked up each cookie on +the running OVN databases and inserted the known information on each +block. + +The logical flow that generated each OpenFlow flow and the logical datapath +it belongs to are now printed, making OVN's pipeline clearer. + +Visualizing datapath flow trees +------------------------------- + +Another typical usecase that can lead to eyestrain is understanding datapath +conntrack recirculations. + +OVS makes heavy use of connection tracking and the ``recirc()`` action +to build complex datapaths. Typically, OVS will insert a flow that, +when matched, will send the packet through conntrack (using the ``ct`` action) +and recirculate it with a particular recirculation id (``recirc_id``). Then, +flows matching on that ``recirc_id`` will be matched and further process the +packet. This can happen more than once for a given packet. + +This sequential set of events is, however, difficult to visualize when you +look at a datapath flow dump. Flows are unordered so recirculations need to +be followed manually (typically, with heavy use of "grep"). + +For this use-case, ``ovs-flowviz datapath tree`` format can be extremely +useful. It builds a hierarchical tree based on the ``recirc_id``, ``in_port`` +and ``recirc()`` actions. + +Furthermore, it is common to end up with multiple flows that have the same +list of actions. An example of this is a number flows that perform mac/vlan +checks for a given port and send the traffic though the same conntrack zone. +In order to better visualize this and reduce the amount of duplicated flows +that are printed in this view, these flows are combined into a block, and the +match keys that are equal for all flows are removed. + +For example: +:: + + Datapath Flows (logical) + └── ╭────────────────────────────────╮ + │ [recirc_id(0x0) in_port(eth0)] │ + ╰────────────────────────────────╯ + └── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ recirc_id(0),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0/0),ct_zone(0/0),ct_mark(0/0),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=22:a1:5d:dc:95:50),eth_type(0x0800),ipv4(src=10.132.0.7,dst=1 │ + │ 0.128.0.0/255.128.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:4924, bytes:468961, │ + │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:01),eth_type(......),ipv4(src=10.132.0.7,dst=1 │ + │ 0.0.0.0/255.255.128.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=32768/0x8000,dst=0/0), packets:711, bytes:114236, │ + │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:14),eth_type(......),ipv4(src=10.132.0.7,dst=1 │ + │ 0.128.0.0/255.128.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:140, bytes:114660, │ + │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:22),eth_type(......),ipv4(src=10.132.0.7,dst=1 │ + │ 0.128.0.0/255.128.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:1, bytes:66, │ + │ recirc_id(0),dp_hash(...),skb_priority(...),in_port(eth0),skb_mark(...),ct_state(...),ct_zone(...),ct_mark(...),ct_label(...),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:09),eth_type(......),ipv4(src=10.132.0.7,dst=1 │ + │ 0.128.0.0/255.128.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:0, bytes:0, │ + ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + └── ╭───────────────────────────────────────╮ + │ actions: ct(zone=32,nat),recirc(0xc1) │ + ╰───────────────────────────────────────╯ + └── ╭─────────────────────────────────╮ + │ [recirc_id(0xc1) in_port(eth0)] │ + ╰─────────────────────────────────╯ + ├── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ recirc_id(0xc1),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0xf),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=22:a1:5d:dc:95:50),eth_type(0x0800),ip │ + │ │ v4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=6,tos=0/0,ttl=0/0,frag=no),tcp(src=0/0,dst=0/0),tcp_flags(0/0), packets:4924, bytes:468961, │ + │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ └── ╭───────────────────────────────────────╮ + │ │ actions: ct(zone=14,nat),recirc(0xc2) │ + │ ╰───────────────────────────────────────╯ + │ └── ╭─────────────────────────────────╮ + │ │ [recirc_id(0xc2) in_port(eth0)] │ + │ ╰─────────────────────────────────╯ + │ └── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ recirc_id(0xc2),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0x1),ct_label(0/0),eth(src=00:00:00:00:00:00/00:00:00:00:00:00,dst=00:00:00 │ + │ │ :00:00:00/01:00:00:00:00:00),eth_type(0x0800),ipv4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=0/0,tos=0/0,ttl=0/0,frag=no), packets:4924, bytes:468961, │ + │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + │ └── ╭──────────────────────╮ + │ │ actions: ovn-k8s-mp0 │ + │ ╰──────────────────────╯ + ├── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ recirc_id(0xc1),dp_hash(0/0),skb_priority(0/0),in_port(eth0),skb_mark(0/0),ct_state(0x2a/0x3f),ct_zone(0/0),ct_mark(0/0xf),ct_label(0/0),eth(src=0a:58:0a:84:00:07,dst=0a:58:0a:84:00:14),eth_type(0x0800),ip │ + │ │ v4(src=0.0.0.0/0.0.0.0,dst=0.0.0.0/0.0.0.0,proto=17,tos=0/0,ttl=0/0,frag=no),udp(src=4096/0xf000,dst=0/0), packets:140, bytes:114660 │ + │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ + + +The above shows a part of a bigger tree with an initial block of flows +at ``recirc_id(0)`` which match on different destination Ethernet +addresses and protocols, and send traffic through conntrack (zone 32). + +Then some additional flows at ``recirc_id(0xc1)`` process each +connection independently. One of them, shown in the example, sends packets +through conntrack zone 14, and after another recirculation the packet is +ultimately sent through a port. + +This is a truly complex multi-zone conntrack pipeline that is now significantly +clearer thanks to this visualization. + +Also note, the flows in the block are conveniently sorted by sent packets. + +This example shows only a single "subtree". Even though the combination of +flows with the same action helps, if we use this command to display a large +dump, the output can be verbose. There are two, combinable, mechanisms that +can help. + + +Plotting datapath trees +~~~~~~~~~~~~~~~~~~~~~~~ + +By using the ``ovs-flowviz datapath html`` format, long datapath trees can +be displayed in an interactive HTML table. The resulting web page allows +subtrees to be expanded and collapsed, allowing focus on the desired +information. + +The ``ovs-flowviz datapath graph`` format generates a graphviz +graph definition where blocks of flows with the same ``recirc_id`` match +are arranged together, and edges are created to represent recirculations. +This format comes with further features such as displaying the conntrack +zones, which are key to understanding what the datapath is really doing with a +packet. + +The ``html`` and ``graph`` can also be combined. +``ovs-flowviz datapath graph --html`` command will output an interactive +HTML table alongside a SVG graphical representation of the flows. Flows in the +SVG representation link to the corresponding entry in the HTML table. + + +Filtering +~~~~~~~~~ + +As well as allowing expanding and collapsing subtrees, filtering can be used. + +However, filtering works in a slightly different way than it does with OpenFlow +flows. Instead of just removing non-matching flows, the output of a filtered +datapath flow tree will show full sub-trees containing at least one flow that +satisfies the filter. + +For example, the following command allows understanding the flows in the above +example in the context of traffic going out on port ``ovn-k8s-mp0``: +:: + + $ ovs-appctl dpctl/dump-flows | ovs-flowviz -f "output.port=ovn-k8s-mp0" datapath tree + +The resulting flow tree will contain all of the flows above, including those +with ``recirc_id(0)`` and ``recirc_id(0xc1)`` that don't actually output +traffic to port ``ovn-k8s-mp0``. This is because they are part of a subtree +that contains flows that output packets on port ``ovn-k8s-mp0`` + +This provides a "full picture" of how traffic, ending up in a particular +port, is being processed. + +.. _ovs-flowviz(8): https://docs.openvswitch.org/en/latest/ref/ovs-flowviz.8 diff --git a/Documentation/topics/index.rst b/Documentation/topics/index.rst index f239fcf83f8..9ddb145dd4d 100644 --- a/Documentation/topics/index.rst +++ b/Documentation/topics/index.rst @@ -58,3 +58,4 @@ OVS userspace-checksum-offloading userspace-tx-steering usdt-probes + flow-visualization diff --git a/NEWS b/NEWS index 7a9626bf4ee..8672655d71e 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ Post-v3.4.0 -------------------- - + - Python: + * Added tool called "ovs-flowviz" capable of parsing OpenFlow + and datapath flow dumps and displaying them in several different + formats. v3.4.0 - 15 Aug 2024 -------------------- diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in index f129bc64625..00412020aa5 100644 --- a/rhel/openvswitch-fedora.spec.in +++ b/rhel/openvswitch-fedora.spec.in @@ -500,6 +500,7 @@ fi %{_mandir}/man8/ovs-ctl.8* %{_mandir}/man8/ovs-dpctl.8* %{_mandir}/man8/ovs-dpctl-top.8* +%{_mandir}/man8/ovs-flowviz.8* %{_mandir}/man8/ovs-kmod-ctl.8* %{_mandir}/man8/ovs-ofctl.8* %{_mandir}/man8/ovs-pki.8* diff --git a/rhel/openvswitch.spec.in b/rhel/openvswitch.spec.in index 9903dd10a87..437e01ee12a 100644 --- a/rhel/openvswitch.spec.in +++ b/rhel/openvswitch.spec.in @@ -223,6 +223,7 @@ exit 0 /usr/share/man/man8/ovs-ctl.8.gz /usr/share/man/man8/ovs-dpctl.8.gz /usr/share/man/man8/ovs-dpctl-top.8.gz +/usr/share/man/man8/ovs-flowviz.8.gz /usr/share/man/man8/ovs-kmod-ctl.8.gz /usr/share/man/man8/ovs-ofctl.8.gz /usr/share/man/man8/ovs-parse-backtrace.8.gz