diff mbox series

[ovs-dev,v2] utilities: Add another GDB macro for ovs-vswitchd

Message ID 8839ca84209fb4b28501af5a956b2a2d0e0ee17d.camel@redhat.com
State Superseded
Headers show
Series [ovs-dev,v2] utilities: Add another GDB macro for ovs-vswitchd | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test fail github build: failed

Commit Message

Mike Pattrick Nov. 5, 2021, 5:26 p.m. UTC
This commit adds a basic packet metadata macro to the already existing
macros in ovs_gdb.py, ovs_dump_packets will print out information about
one or more packets. It feeds packets into tcpdump, and the user can
pass in tcpdump options to modify how packets are parsed or even write
out packets to a pcap file.

Example usage:
(gdb) break fast_path_processing
(gdb) commands
>ovs_dump_packets packets_
>continue
>end
(gdb) continue

Thread 1 "ovs-vswitchd" hit Breakpoint 2, fast_path_processing ...
12:01:05.962485 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 10.1.1.1 tell 10.1.1.2, length 28
Thread 1 "ovs-vswitchd" hit Breakpoint 1, fast_path_processing ...
12:01:05.981214 ARP, Ethernet (len 6), IPv4 (len 4), Reply 10.1.1.1 is-at a6:0f:c3:f0:5f:bd (oui Unknown), length 28

Signed-off-by: Mike Pattrick <mkp@redhat.com>
---
 utilities/gdb/ovs_gdb.py | 102 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 100 insertions(+), 2 deletions(-)

Comments

Eelco Chaudron Nov. 12, 2021, 10:27 a.m. UTC | #1
Some small remaining comments, rest looks good!

On 5 Nov 2021, at 18:26, Mike Pattrick wrote:

> This commit adds a basic packet metadata macro to the already existing
> macros in ovs_gdb.py, ovs_dump_packets will print out information about
> one or more packets. It feeds packets into tcpdump, and the user can
> pass in tcpdump options to modify how packets are parsed or even write
> out packets to a pcap file.
>
> Example usage:
> (gdb) break fast_path_processing
> (gdb) commands
>> ovs_dump_packets packets_
>> continue
>> end
> (gdb) continue
>
> Thread 1 "ovs-vswitchd" hit Breakpoint 2, fast_path_processing ...
> 12:01:05.962485 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 10.1.1.1 tell 10.1.1.2, length 28
> Thread 1 "ovs-vswitchd" hit Breakpoint 1, fast_path_processing ...
> 12:01:05.981214 ARP, Ethernet (len 6), IPv4 (len 4), Reply 10.1.1.1 is-at a6:0f:c3:f0:5f:bd (oui Unknown), length 28
>
> Signed-off-by: Mike Pattrick <mkp@redhat.com>
> ---
>  utilities/gdb/ovs_gdb.py | 102 ++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 100 insertions(+), 2 deletions(-)
>
> diff --git a/utilities/gdb/ovs_gdb.py b/utilities/gdb/ovs_gdb.py
> index 0b2ecb81b..ee9160607 100644
> --- a/utilities/gdb/ovs_gdb.py
> +++ b/utilities/gdb/ovs_gdb.py
> @@ -29,6 +29,7 @@
>  #    - ovs_dump_netdev
>  #    - ovs_dump_netdev_provider
>  #    - ovs_dump_ovs_list <struct ovs_list *> {[<structure>] [<member>] {dump}]}
> +#    - ovs_dump_packets <struct dp_packet_batch|struct dp_packet> [tcpdump options]
>  #    - ovs_dump_simap <struct simap *>
>  #    - ovs_dump_smap <struct smap *>
>  #    - ovs_dump_udpif_keys {<udpif_name>|<udpif_address>} {short}
> @@ -58,6 +59,9 @@
>  import gdb
>  import sys
>  import uuid
> +import struct

Please keep imports in alphabetical order.


> +from scapy.layers.l2 import Ether
> +from scapy.utils import tcpdump

Not everybody might have/need scapy support. Maybe we can change it to something like:

try:
    from scapy.layers.l2 import Ether
    from scapy.utils import tcpdump
except ModuleNotFoundError:
    print("WARNING: Can't find the Scapy Python module!")
    print("         This is required for the ovs_dump_packets command.")

>
>  #
> @@ -138,7 +142,7 @@ def get_time_msec():
>
>  def get_time_now():
>      # See get_time_msec() above
> -    return int(get_global_variable("coverage_run_time"))/1000, -5
> +    return int(get_global_variable("coverage_run_time")) / 1000, -5
>
>
>  def eth_addr_to_string(eth_addr):
> @@ -156,7 +160,7 @@ def eth_addr_to_string(eth_addr):
>  #
>  class ProgressIndicator(object):
>      def __init__(self, message=None):
> -        self.spinner = "/-\|"
> +        self.spinner = "/-\\|"
>          self.spinner_index = 0
>          self.message = message
>
> @@ -1306,6 +1310,99 @@ class CmdDumpOfpacts(gdb.Command):
>              print("(struct ofpact *) {}: {}".format(node, node.dereference()))
>
>
> +#
> +# Implements the GDB "ovs_dump_packets" command
> +#
> +class CmdDumpPackets(gdb.Command):
> +    """Dump metadata about dp_packets
> +    Usage: ovs_dump_packets <struct dp_packet_batch|struct dp_packet> [tcpdump options]
> +
> +    This command can take either a dp_packet_batch struct and print out metadata
> +    about all packets in this batch, or a single dp_packet struct and print out
> +    metadata about a single packet.
> +
> +    Everything after the struct reference is passed into tcpdump.
> +
> +    (gdb) ovs_dump_packets packets_
> +    12:01:05.981214 ARP, Ethernet (len 6), IPv4 (len 4), Reply 10.1.1.1 is-at a6:0f:c3:f0:5f:bd (oui Unknown), length 28
> +    """
> +    def __init__(self):
> +        super().__init__("ovs_dump_packets", gdb.COMMAND_DATA)
> +
> +    def invoke(self, arg, from_tty):
> +        arg_list = gdb.string_to_argv(arg)
> +        if len(arg_list) == 0:
> +            print("Usage: ovs_dump_packets <struct dp_packet_batch|"
> +                    "struct dp_packet> [tcpdump options]")

Indent is not correct here.

> +            return
> +
> +        symb_name = arg_list[0]
> +        tcpdump_args = arg_list[1:]
> +
> +        val = gdb.parse_and_eval(symb_name)
> +        while val.type.code == gdb.TYPE_CODE_PTR:
> +            val = val.dereference()
> +
> +        pkt_list = []
> +        if str(val.type).startswith("struct dp_packet_batch"):
> +            for idx in range(val['count']):
> +                pkt_struct = val['packets'][idx].dereference()
> +                pkt = self.extract_pkt(pkt_struct)
> +                if pkt is None:
> +                    continue
> +                pkt_list.append(pkt)
> +        elif str(val.type) == "struct dp_packet":
> +            pkt = self.extract_pkt(val)
> +            if pkt is None:
> +                return
> +            pkt_list.append(pkt)
> +        else:
> +            print("Error, unsupported argument type:", str(val.type))

All errors are in the format print(“ERROR: unsupported argument type: {}”.format(str(val.type))

> +            return
> +
> +        tcpdump(pkt_list, args=tcpdump_args)

Wondering if we should supply the “-n” option if no options are given to speed up the actual display. But I’ll leave it up to you.

> +
> +    def extract_pkt(self, pkt):
> +        pkt_fields = pkt.type.keys()
> +        if pkt['packet_type'] != 0:
> +            return
> +        if pkt['l3_ofs'] == 0xFFFF:
> +            return
> +
> +        if "mbuf" in pkt_fields:
> +            if pkt['mbuf']['data_off'] == 0xFFFF:
> +                return
> +            eth_ptr = pkt['mbuf']['buf_addr']
> +            eth_off = int(pkt['mbuf']['data_off'])
> +            eth_len = int(pkt['mbuf']['pkt_len'])
> +        else:
> +            if pkt['data_ofs'] == 0xFFFF:
> +                return
> +            eth_ptr = pkt['base_']
> +            eth_off = int(pkt['data_ofs'])
> +            eth_len = int(pkt['size_'])
> +

Thanks for doing the above, so it will also work with core dumps!

> +        if eth_ptr == 0 or eth_len < 1:
> +            return
> +
> +        # Extract packet
> +        pkt_ptr = eth_ptr.cast(
> +                gdb.lookup_type('uint8_t').pointer()
> +            )
> +        pkt_ptr += eth_off
> +
> +        pkt_data = []
> +        for idx in range(eth_len):
> +            pkt_data.append(int(pkt_ptr[idx]))
> +
> +        pkt_data = struct.pack("{}B".format(eth_len), *pkt_data)
> +
> +        packet = Ether(pkt_data)
> +        packet.len = int(eth_len)
> +
> +        return packet
> +
> +
>  #
>  # Initialize all GDB commands
>  #
> @@ -1319,6 +1416,7 @@ CmdDumpNetdev()
>  CmdDumpNetdevProvider()
>  CmdDumpOfpacts()
>  CmdDumpOvsList()
> +CmdDumpPackets()
>  CmdDumpSimap()
>  CmdDumpSmap()
>  CmdDumpUdpifKeys()
> -- 
> 2.30.2
>
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
diff mbox series

Patch

diff --git a/utilities/gdb/ovs_gdb.py b/utilities/gdb/ovs_gdb.py
index 0b2ecb81b..ee9160607 100644
--- a/utilities/gdb/ovs_gdb.py
+++ b/utilities/gdb/ovs_gdb.py
@@ -29,6 +29,7 @@ 
 #    - ovs_dump_netdev
 #    - ovs_dump_netdev_provider
 #    - ovs_dump_ovs_list <struct ovs_list *> {[<structure>] [<member>] {dump}]}
+#    - ovs_dump_packets <struct dp_packet_batch|struct dp_packet> [tcpdump options]
 #    - ovs_dump_simap <struct simap *>
 #    - ovs_dump_smap <struct smap *>
 #    - ovs_dump_udpif_keys {<udpif_name>|<udpif_address>} {short}
@@ -58,6 +59,9 @@ 
 import gdb
 import sys
 import uuid
+import struct
+from scapy.layers.l2 import Ether
+from scapy.utils import tcpdump
 
 
 #
@@ -138,7 +142,7 @@  def get_time_msec():
 
 def get_time_now():
     # See get_time_msec() above
-    return int(get_global_variable("coverage_run_time"))/1000, -5
+    return int(get_global_variable("coverage_run_time")) / 1000, -5
 
 
 def eth_addr_to_string(eth_addr):
@@ -156,7 +160,7 @@  def eth_addr_to_string(eth_addr):
 #
 class ProgressIndicator(object):
     def __init__(self, message=None):
-        self.spinner = "/-\|"
+        self.spinner = "/-\\|"
         self.spinner_index = 0
         self.message = message
 
@@ -1306,6 +1310,99 @@  class CmdDumpOfpacts(gdb.Command):
             print("(struct ofpact *) {}: {}".format(node, node.dereference()))
 
 
+#
+# Implements the GDB "ovs_dump_packets" command
+#
+class CmdDumpPackets(gdb.Command):
+    """Dump metadata about dp_packets
+    Usage: ovs_dump_packets <struct dp_packet_batch|struct dp_packet> [tcpdump options]
+
+    This command can take either a dp_packet_batch struct and print out metadata
+    about all packets in this batch, or a single dp_packet struct and print out
+    metadata about a single packet.
+
+    Everything after the struct reference is passed into tcpdump.
+
+    (gdb) ovs_dump_packets packets_
+    12:01:05.981214 ARP, Ethernet (len 6), IPv4 (len 4), Reply 10.1.1.1 is-at a6:0f:c3:f0:5f:bd (oui Unknown), length 28
+    """
+    def __init__(self):
+        super().__init__("ovs_dump_packets", gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        arg_list = gdb.string_to_argv(arg)
+        if len(arg_list) == 0:
+            print("Usage: ovs_dump_packets <struct dp_packet_batch|"
+                    "struct dp_packet> [tcpdump options]")
+            return
+
+        symb_name = arg_list[0]
+        tcpdump_args = arg_list[1:]
+
+        val = gdb.parse_and_eval(symb_name)
+        while val.type.code == gdb.TYPE_CODE_PTR:
+            val = val.dereference()
+
+        pkt_list = []
+        if str(val.type).startswith("struct dp_packet_batch"):
+            for idx in range(val['count']):
+                pkt_struct = val['packets'][idx].dereference()
+                pkt = self.extract_pkt(pkt_struct)
+                if pkt is None:
+                    continue
+                pkt_list.append(pkt)
+        elif str(val.type) == "struct dp_packet":
+            pkt = self.extract_pkt(val)
+            if pkt is None:
+                return
+            pkt_list.append(pkt)
+        else:
+            print("Error, unsupported argument type:", str(val.type))
+            return
+
+        tcpdump(pkt_list, args=tcpdump_args)
+
+    def extract_pkt(self, pkt):
+        pkt_fields = pkt.type.keys()
+        if pkt['packet_type'] != 0:
+            return
+        if pkt['l3_ofs'] == 0xFFFF:
+            return
+
+        if "mbuf" in pkt_fields:
+            if pkt['mbuf']['data_off'] == 0xFFFF:
+                return
+            eth_ptr = pkt['mbuf']['buf_addr']
+            eth_off = int(pkt['mbuf']['data_off'])
+            eth_len = int(pkt['mbuf']['pkt_len'])
+        else:
+            if pkt['data_ofs'] == 0xFFFF:
+                return
+            eth_ptr = pkt['base_']
+            eth_off = int(pkt['data_ofs'])
+            eth_len = int(pkt['size_'])
+
+        if eth_ptr == 0 or eth_len < 1:
+            return
+
+        # Extract packet
+        pkt_ptr = eth_ptr.cast(
+                gdb.lookup_type('uint8_t').pointer()
+            )
+        pkt_ptr += eth_off
+
+        pkt_data = []
+        for idx in range(eth_len):
+            pkt_data.append(int(pkt_ptr[idx]))
+
+        pkt_data = struct.pack("{}B".format(eth_len), *pkt_data)
+
+        packet = Ether(pkt_data)
+        packet.len = int(eth_len)
+
+        return packet
+
+
 #
 # Initialize all GDB commands
 #
@@ -1319,6 +1416,7 @@  CmdDumpNetdev()
 CmdDumpNetdevProvider()
 CmdDumpOfpacts()
 CmdDumpOvsList()
+CmdDumpPackets()
 CmdDumpSimap()
 CmdDumpSmap()
 CmdDumpUdpifKeys()