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 <m...@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 > d...@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev