[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