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

Eelco Chaudron echaudro at redhat.com
Fri Nov 5 11:33:10 UTC 2021


Thanks for adding a new command to the GDB macros. See some comments below…

//Eelco


On 4 Nov 2021, at 19:03, 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. Currently, it will only extract some basic L2-4
> information from Ethernet frames. However, it could easily be extended
> as needed for future tasks.
>
> Example usage:
> (gdb) break fast_path_processing
> (gdb) commands
>> ovs_dump_packets packets_
>> continue
>> end
> (gdb) continue
>
> Thread 1 "ovs-vswitchd" hit Breakpoint 1, fast_path_processing ...
> 9e:11:bb:29:f3:1b -> 5a:e3:39:9b:b7:e0 ARP: Who has 10.1.1.2, tell
>     9e:11:bb:29:f3:1b
> Thread 1 "ovs-vswitchd" hit Breakpoint 1, fast_path_processing ...
> 5a:e3:39:9b:b7:e0 -> 9e:11:bb:29:f3:1b ARP: 10.1.1.2 is at
>     5a:e3:39:9b:b7:e0
>
>
> Signed-off-by: Mike Pattrick <mkp at redhat.com>
> ---
>  utilities/gdb/ovs_gdb.py | 161 +++++++++++++++++++++++++++++++++++++++
>  1 file changed, 161 insertions(+)
>
> diff --git a/utilities/gdb/ovs_gdb.py b/utilities/gdb/ovs_gdb.py
> index 0b2ecb81b..9e3c59f0e 100644
> --- a/utilities/gdb/ovs_gdb.py
> +++ b/utilities/gdb/ovs_gdb.py
> @@ -34,6 +34,7 @@
>  #    - ovs_dump_udpif_keys {<udpif_name>|<udpif_address>} {short}
>  #    - ovs_show_fdb {[<bridge_name>] {dbg} {hash}}
>  #    - ovs_show_upcall {dbg}
> +#    - ovs_dump_dp_packet <struct dp_packet_batch|struct dp_packet>

Please add in alphabetical order.

>  #
>  #  Example:
>  #    $ gdb $(which ovs-vswitchd) $(pidof ovs-vswitchd)
> @@ -58,6 +59,8 @@
>  import gdb
>  import sys
>  import uuid
> +import socket
> +import struct
>
>
>  #
> @@ -151,6 +154,29 @@ def eth_addr_to_string(eth_addr):
>          int(eth_addr['ea'][5]))
>
>
> +def ipv4_to_string(ip):
> +    return socket.inet_ntop(
> +        socket.AF_INET,
> +        struct.pack(
> +            "I",
> +            ip.cast(gdb.lookup_type('uint32_t'))
> +        )
> +    )
> +

Please run flak8 over you changes as there a quite some errors (you can ignore the over 80 for documentation console output):

 ovs_gdb.py   168  24 warning  E201   whitespace after '(' (python-flake8)
 ovs_gdb.py   168  61 warning  E202   whitespace before ')' (python-flake8)
 ovs_gdb.py  1393  80 warning  E501   line too long (88 > 79 characters) (python-flake8)
 ovs_gdb.py  1403  80 warning  E501   line too long (95 > 79 characters) (python-flake8)
 ovs_gdb.py  1419  80 warning  E501   line too long (89 > 79 characters) (python-flake8)
 ovs_gdb.py  1428  80 warning  E501   line too long (117 > 79 characters) (python-flake8)
 ovs_gdb.py  1430  80 warning  E501   line too long (97 > 79 characters) (python-flake8)
 ovs_gdb.py  1435  80 warning  E501   line too long (101 > 79 characters) (python-flake8)
 ovs_gdb.py  1443  80 warning  E501   line too long (122 > 79 characters) (python-flake8)
 ovs_gdb.py  1445  80 warning  E501   line too long (102 > 79 characters) (python-flake8)
 ovs_gdb.py  1451   9 warning  F841   local variable 'uh' is assigned to but never used (python-flake8)
 ovs_gdb.py  1451  80 warning  E501   line too long (82 > 79 characters) (python-flake8)
 ovs_gdb.py  1452  80 warning  E501   line too long (82 > 79 characters) (python-flake8)

> +
> +def ipv6_to_string(ip):
> +    ip_array = ip.cast( gdb.lookup_type('uint64_t').array(1) )
> +
> +    return socket.inet_ntop(
> +        socket.AF_INET6,
> +        struct.pack(
> +            "QQ",
> +            ip_array[0],
> +            ip_array[1]
> +        )
> +    )
> +
> +
>  #
>  # Simple class to print a spinner on the console
>  #
> @@ -1306,6 +1332,140 @@ 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>
> +
> +    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.
> +
> +    (gdb) ovs_dump_packets packets_
> +    9e:11:bb:29:f3:1b -> ff:ff:ff:ff:ff:ff ARP: Who has 00:00:00:00:00:00, tell 9e:11:bb:29:f3:1b
> +    """
> +    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) != 1:
> +            print("Usage: ovs_dump_packets <dp_packet_struct>")

Here you should also print both options, and do a return

> +
> +        val = gdb.parse_and_eval(arg_list[0])
> +        while val.type.code == gdb.TYPE_CODE_PTR:
> +            val = val.dereference()
> +
> +        if str(val.type).startswith("struct dp_packet_batch"):
> +            for idx in range(val['count']):
> +                pkt = val['packets'][idx].dereference()
> +                parsed = self.fmt_pkt(pkt)
> +                if parsed is not None:
> +                    print(parsed['format'] % parsed)
> +        elif str(val.type) == "struct dp_packet":
> +            parsed = self.fmt_pkt(val)
> +            if parsed is not None:
> +                print(parsed['format'] % parsed)
> +        else:
> +            print("Unhandled type", str(val.type))

I would change the text to “ERROR: Unsupported argument type!”

> +
> +    def fmt_pkt(self, pkt):
> +        eth = gdb.parse_and_eval('dp_packet_eth(%s)' % pkt.address)

For the gdb macros we try to use .format() as much as possible, so please try to convert them.

> +        # todo, check for is NULL
> +        if eth == 0:
> +            return None

What is the remaining todo?

> +
> +        res_d = {
> +            "e_src": None,
> +            "e_dst": None,
> +            "e_proto": None,
> +            "i_src": None,
> +            "i_dst": None,
> +            "i_proto": None,
> +            "t_src": "",
> +            "t_dst": "",
> +            "proto_msg": None,
> +            "format": "Can not parse ethernet protocol %(e_proto)s"
> +        }

Is there a specific reason for not just returning the string we need to display?
Or do the actual displaying, so no None checks are needed above?

Maybe we should also do a “hex” flag, so it will dump the packet content in hex.
This ways we can convert it to a pcap file, which I do a lot, for example:

  HEXA=$(echo $@ | tr -d \\n | sed 's/ //g; s/-//g; s/0x//g' | sed -n 's/\([0-9a-fA-F][0-9a-fA-F]\)/\1 /pg');
  echo 00000000 $HEXA | text2pcap - - > $FILE

Or if you want to remove all decoding logic, you could import scapy, and do something like:

    from scapy.layers.l2 import Ether

    packet = Ether(pkt_data)
    packet.wirelen = event.pkt_size
    packet.show(dump=True)

Output will look something like:

      ###[ Ethernet ]###
        dst       = 3c:fd:fe:9e:7f:68
        src       = 04:f4:bc:28:57:01
        type      = IPv4
      ###[ IP ]###
           version   = 4
           ihl       = 5
           tos       = 0x0
           len       = 84
           id        = 41404
           flags     = DF
           frag      = 0
           ttl       = 64
          proto     = icmp
           chksum    = 0x940c
           src       = 1.1.1.100
           dst       = 1.1.1.123
           \options   \
      ###[ ICMP ]###
              type      = echo-reply
              code      = 0
              chksum    = 0x2f55
              id        = 0x90e6
              seq       = 0x1
      ###[ Raw ]###
                 load      = 'GBTa\x00\x00\x00\x00\xd8L\r\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567’#


> +        eth_hdr = eth.cast(gdb.lookup_type('struct eth_header').pointer()).dereference()
> +        res_d['e_dst'] = eth_addr_to_string(eth_hdr['eth_dst'])
> +        res_d['e_src'] = eth_addr_to_string(eth_hdr['eth_src'])
> +        eth_type = socket.ntohs(eth_hdr['eth_type'])
> +        res_d['e_proto'] = eth_type
> +
> +        l3 = gdb.parse_and_eval('dp_packet_l3(%s)' % pkt.address)
> +        if eth_type == 0x0806:
> +            res_d['e_proto'] = "ARP"
> +
> +            arp_pkt = l3.cast(gdb.lookup_type('struct arp_eth_header').pointer()).dereference()
> +            ar_op = socket.ntohs(arp_pkt['ar_op'])
> +            if ar_op == 1:
> +                res_d['proto_msg'] = "Who has %s, tell %s" % (
> +                    ipv4_to_string(arp_pkt['ar_tpa']),
> +                    eth_addr_to_string(arp_pkt['ar_sha']),
> +                )
> +            elif ar_op == 2:
> +                res_d['proto_msg'] = "%s is at %s" % (
> +                    ipv4_to_string(arp_pkt['ar_spa']),
> +                    eth_addr_to_string(arp_pkt['ar_sha']),
> +                )
> +
> +            res_d['format'] = "%(e_src)s -> %(e_dst)s ARP: %(proto_msg)s"
> +
> +        elif eth_type == 0x0800:
> +            ip_pkt = l3.cast(gdb.lookup_type('struct ip_header').pointer()).dereference()
> +            res_d['e_proto'] = "IP"
> +            res_d['i_src'] = ipv4_to_string(ip_pkt['ip_src'])
> +            res_d['i_dst'] = ipv4_to_string(ip_pkt['ip_dst'])
> +
> +            ip_proto = ip_pkt['ip_proto']
> +            l4 = gdb.parse_and_eval('dp_packet_l4(%s)' % pkt.address)
> +            self.parse_l4(ip_proto, l4, res_d)
> +            if res_d['t_dst']:
> +                res_d['format'] = "%(e_src)s -> %(e_dst)s IP/%(i_proto)s: %(i_src)s:%(t_src)s -> %(i_dst)s:%(t_dst)s"
> +            else:
> +                res_d['format'] = "%(e_src)s -> %(e_dst)s IP/%(i_proto)s: %(i_src)s -> %(i_dst)s"
> +
> +        elif eth_type == 0x86dd:
> +            res_d['e_proto'] = "IPv6"
> +
> +            ip_pkt = l3.cast(gdb.lookup_type('struct ovs_16aligned_ip6_hdr').pointer()).dereference()
> +            res_d['i_src'] = ipv6_to_string(ip_pkt['ip6_src'])
> +            res_d['i_dst'] = ipv6_to_string(ip_pkt['ip6_dst'])
> +
> +            l4 = gdb.parse_and_eval('dp_packet_l4(%s)' % pkt.address)
> +            ip_proto = ip_pkt['ip6_ctlun']['ip6_un1']['ip6_un1_nxt']
> +            self.parse_l4(ip_proto, l4, res_d)
> +            if res_d['t_dst']:
> +                res_d['format'] = "%(e_src)s -> %(e_dst)s IP6/%(i_proto)s: [%(i_src)s]:%(t_src)s -> [%(i_dst)s]:%(t_dst)s"
> +            else:
> +                res_d['format'] = "%(e_src)s -> %(e_dst)s IP6/%(i_proto)s: [%(i_src)s] -> [%(i_dst)s]"
> +
> +        return res_d
> +
> +    @staticmethod
> +    def parse_l4(ip_proto, l4, res_d):
> +        uh = l4.cast(gdb.lookup_type('struct udp_header').pointer()).dereference()
> +        th = l4.cast(gdb.lookup_type('struct tcp_header').pointer()).dereference()
> +        if ip_proto == 58:
> +            res_d['i_proto'] = "ICMPv6"
> +        elif ip_proto == 11:
> +            res_d['i_proto'] = "UDP"
> +            res_d['t_src'] = socket.ntohs(th['udp_src'])
> +            res_d['t_dst'] = socket.ntohs(th['udp_dst'])

Guess this needs to one uh instead of th? This should fail you tests.

> +        elif ip_proto == 6:
> +            res_d['i_proto'] = "TCP"
> +            res_d['t_src'] = socket.ntohs(th['tcp_src'])
> +            res_d['t_dst'] = socket.ntohs(th['tcp_dst'])
> +        elif ip_proto == 1:
> +            res_d['i_proto'] = "ICMP"
> +        else:
> +            res_d['i_proto'] = str(ip_proto)
> +
> +
>  #
>  # Initialize all GDB commands
>  #
> @@ -1324,3 +1484,4 @@ CmdDumpSmap()
>  CmdDumpUdpifKeys()
>  CmdShowFDB()
>  CmdShowUpcall()
> +CmdDumpPackets()

Add in alphabetical order

> -- 
> 2.30.2
>
>
>
> _______________________________________________
> dev mailing list
> dev at openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev



More information about the dev mailing list