#!/usr/bin/env python3 # # Copyright (c) 2022 Red Hat, Inc. # # 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. # # Script information: # ------------------- # dpif_nl_exec_monitor.py uses the dpif_netlink_operate__:op_flow_execute USDT # probe to receive all DPIF_OP_EXECUTE operations that are queued for # transmission over the netlink socket. It will do some basic decoding, and if # requested a packet dump. Note that there are also options to obtain # additional information for the DPIF_OP_FLOW_* operations. # # Here is an example: # # # ./dpif_op_nl_monitor.py --packet-decode decode --trace-exec-op # Display DPIF_OP_EXECUTE operations being queued for transmission... # TIME CPU COMM PID NL_SIZE # Display DPIF operations being queued for transmission onto the netlink... # TIME CPU COMM PID NL_SIZE DPIF_... # 2162030.641541513 5 handler4 2951007 164 DPIF_... # nlmsghdr : len = 0, type = 36, flags = 1, seq = 0, pid = 0 # genlmsghdr: cmd = DPIF_OP_EXECUTE, version = 1, reserved = 0 # ovs_header: dp_ifindex = 21 # > Decode OVS_PACKET_ATTR_* TLVs: # nla_len 46, nla_type OVS_PACKET_ATTR_PACKET[1], data: 00 00 00... # nla_len 20, nla_type OVS_PACKET_ATTR_KEY[2], data: 08 00 02 00... # > Decode OVS_KEY_ATTR_* TLVs: # nla_len 8, nla_type OVS_KEY_ATTR_PRIORITY[2], data: 00 00... # nla_len 8, nla_type OVS_KEY_ATTR_SKB_MARK[15], data: 00 00... # nla_len 88, nla_type OVS_PACKET_ATTR_ACTIONS[3], data: 4c 00 03... # > Decode OVS_ACTION_ATTR_* TLVs: # nla_len 76, nla_type OVS_ACTION_ATTR_SET[3], data: 48 00... # > Decode OVS_TUNNEL_KEY_ATTR_* TLVs: # nla_len 12, nla_type OVS_TUNNEL_KEY_ATTR_ID[0], data:... # nla_len 20, nla_type OVS_TUNNEL_KEY_ATTR_IPV6_DST[13], ... # nla_len 5, nla_type OVS_TUNNEL_KEY_ATTR_TTL[4], data: 40 # nla_len 4, nla_type OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT... # nla_len 4, nla_type OVS_TUNNEL_KEY_ATTR_CSUM[6], data: # nla_len 6, nla_type OVS_TUNNEL_KEY_ATTR_TP_DST[10],... # nla_len 12, nla_type OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS... # nla_len 8, nla_type OVS_ACTION_ATTR_OUTPUT[1], data: 02 00 00 00 # - Dumping OVS_PACKET_ATR_PACKET data: # ###[ Ethernet ]### # dst = 00:00:00:00:ec:01 # src = 04:f4:bc:28:57:00 # type = IPv4 # ###[ IP ]### # version = 4 # ihl = 5 # tos = 0x0 # len = 50 # id = 0 # flags = # frag = 0 # ttl = 127 # proto = icmp # chksum = 0x2767 # src = 10.0.0.1 # dst = 10.0.0.100 # \options \ # ###[ ICMP ]### # type = echo-request # code = 0 # chksum = 0xf7f3 # id = 0x0 # seq = 0xc # # The example above dumps the full netlink and packet decode. However options # exist to disable this. For a complete list of options, please use the # '--help' or '-h' argument. # from bcc import BPF, USDT, USDTException from os.path import exists from scapy.all import hexdump, wrpcap from scapy.layers.l2 import Ether import argparse import psutil import re import struct import sys # # Actual eBPF source code # ebpf_source = """ #include #define MAX_NLMSG struct event_t { u32 cpu; u32 pid; u64 ts; u32 nl_size; char comm[TASK_COMM_LEN]; u8 nl_msg[MAX_NLMSG]; }; struct ofpbuf { void *base; void *data; uint32_t size; /* The actual structure is longer, but we are only interested in the * first couple of entries. */ }; BPF_RINGBUF_OUTPUT(events, ); BPF_TABLE("percpu_array", uint32_t, uint64_t, dropcnt, 1); static int trace_event(struct ofpbuf *nlbuf) { uint32_t size; struct event_t *event = events.ringbuf_reserve(sizeof(struct event_t)); if (!event) { uint32_t type = 0; uint64_t *value = dropcnt.lookup(&type); if (value) __sync_fetch_and_add(value, 1); return 1; } event->ts = bpf_ktime_get_ns(); event->cpu = bpf_get_smp_processor_id(); event->pid = bpf_get_current_pid_tgid(); bpf_get_current_comm(&event->comm, sizeof(event->comm)); event->nl_size = nlbuf->size; if (event->nl_size > MAX_NLMSG) size = MAX_NLMSG; else size = event->nl_size; bpf_probe_read(&event->nl_msg, size, nlbuf->data); events.ringbuf_submit(event, 0); return 0; } #if int trace__op_execute(struct pt_regs *ctx) { struct ofpbuf nlbuf; bpf_usdt_readarg_p(5, ctx, &nlbuf, sizeof(nlbuf)); return trace_event(&nlbuf); }; #endif #if int trace__op_flow_put(struct pt_regs *ctx) { struct ofpbuf nlbuf; bpf_usdt_readarg_p(4, ctx, &nlbuf, sizeof(nlbuf)); return trace_event(&nlbuf); }; #endif #if int trace__op_flow_del(struct pt_regs *ctx) { struct ofpbuf nlbuf; bpf_usdt_readarg_p(4, ctx, &nlbuf, sizeof(nlbuf)); return trace_event(&nlbuf); }; #endif #if int trace__op_flow_get(struct pt_regs *ctx) { struct ofpbuf nlbuf; bpf_usdt_readarg_p(4, ctx, &nlbuf, sizeof(nlbuf)); return trace_event(&nlbuf); }; #endif """ # # print_event() # def print_event(ctx, data, size): event = b["events"].event(data) if event.nl_size < options.nlmsg_size: nl_size = event.nl_size else: nl_size = options.nlmsg_size print("{:<18.9f} {:<4} {:<16} {:<10} {:<10} {}". format(event.ts / 1000000000, event.cpu, event.comm.decode("utf-8"), event.pid, event.nl_size, get_ovs_dpif_op_str(get_cmd_type_from_nlm( bytes(event.nl_msg)[:nl_size])))) # # Dumping the netlink message data if requested. # if options.nlmsg_decode == "hex": # # Abuse scapy's hex dump to dump flow key # print(re.sub("^", " " * 4, hexdump(Ether(bytes(event.nl_msg)[:nl_size]), dump=True), flags=re.MULTILINE)) if options.nlmsg_decode == "nlraw": decode_result = decode_nlm(bytes(event.nl_msg)[:nl_size], dump=True) else: decode_result = decode_nlm(bytes(event.nl_msg)[:nl_size], dump=False) # # Decode packet only if there is data # if "OVS_PACKET_ATTR_PACKET" not in decode_result: return pkt_data = decode_result["OVS_PACKET_ATTR_PACKET"] indent = 4 if options.nlmsg_decode != "nlraw" else 6 if options.packet_decode != "none": print("{}- Dumping OVS_PACKET_ATR_PACKET data:".format(" " * indent)) if options.packet_decode == "hex": print(re.sub("^", " " * indent, hexdump(pkt_data, dump=True), flags=re.MULTILINE)) packet = Ether(pkt_data) if options.packet_decode == "decode": print(re.sub("^", " " * indent, packet.show(dump=True), flags=re.MULTILINE)) if options.pcap is not None: wrpcap(options.pcap, packet, append=True) # # decode_nlm_tlvs() # def decode_nlm_tlvs(tlvs, header=None, indent=4, dump=True, attr_to_str_func=None, decode_tree=None): bytes_left = len(tlvs) result = {} if dump: print("{}{}".format(" " * indent, header)) while bytes_left: if bytes_left < 4: if dump: print("{}WARN: decode truncated; can't read header".format( " " * indent)) break nla_len, nla_type = struct.unpack("=HH", tlvs[:4]) if nla_len < 4: if dump: print("{}WARN: decode truncated; nla_len < 4".format( " " * indent)) break nla_data = tlvs[4:nla_len] trunc = "" if attr_to_str_func is None: nla_type_name = "type_{}".format(nla_type) else: nla_type_name = attr_to_str_func(nla_type) if nla_len > bytes_left: trunc = "..." nla_data = nla_data[:(bytes_left - 4)] else: result[nla_type_name] = nla_data if dump: print("{}nla_len {}, nla_type {}[{}], data: {}{}".format( " " * indent, nla_len, nla_type_name, nla_type, "".join("{:02x} ".format(b) for b in nla_data), trunc)) # # If we have the full data, try to decode further # if trunc == "" and decode_tree is not None \ and nla_type_name in decode_tree: node = decode_tree[nla_type_name] decode_nlm_tlvs(nla_data, header=node["header"], indent=indent + node["indent"], dump=True, attr_to_str_func=node["attr_str_func"], decode_tree=node["decode_tree"]) if trunc != "": if dump: print("{}WARN: decode truncated; nla_len > msg_len[{}] ". format(" " * indent, bytes_left)) break # update next offset, but make sure it's aligned correctly next_offset = (nla_len + 3) & ~(3) tlvs = tlvs[next_offset:] bytes_left -= next_offset return result # # get_cmd_type_from_nlm() # def get_cmd_type_from_nlm(nlm): # The netlink message consists of at least a 'struct nlmsghdr' (16-bytes) # followed by a 'struct genlmsghdr'. The first byte of the genlmsghdr # structure contains the command. Which is what we will extract here. if len(nlm) < 17: return -1 return nlm[16] # # decode_nlm() # def decode_nlm(msg, indent=4, dump=True): result = {} # # Decode 'struct nlmsghdr' # if dump: print("{}nlmsghdr : len = {}, type = {}, flags = {}, seq = {}, " "pid = {}".format(" " * indent, *struct.unpack("=IHHII", msg[:16]))) msg = msg[16:] # # Decode 'struct genlmsghdr' # cmd, version, reserved = struct.unpack("=BBH", msg[:4]) if dump: print("{}genlmsghdr: cmd = {}, version = {}, reserved = {}".format( " " * indent, get_ovs_dpif_op_str(cmd), version, reserved)) msg = msg[4:] # # Decode 'struct ovs_header' # if dump: print("{}ovs_header: dp_ifindex = {}".format( " " * indent, *struct.unpack("=I", msg[:4]))) msg = msg[4:] # # Decode TLVs # nl_key_attr = { "header": "> Decode OVS_KEY_ATTR_* TLVs:", "indent": 4, "attr_str_func": get_ovs_key_attr_str, "decode_tree": { "OVS_KEY_ATTR_ENCAP": { "header": "> Decode OVS_KEY_ATTR_* TLVs:", "indent": 4, "attr_str_func": get_ovs_key_attr_str, "decode_tree": { "OVS_KEY_ATTR_ENCAP": { "header": "> Decode OVS_KEY_ATTR_* TLVs:", "indent": 4, "attr_str_func": get_ovs_key_attr_str, "decode_tree": None, }, }, }, } } nl_action_attr = { "header": "> Decode OVS_ACTION_ATTR_* TLVs:", "indent": 4, "attr_str_func": get_ovs_action_attr_str, "decode_tree": { "OVS_ACTION_ATTR_SET": { "header": "> Decode OVS_KEY_ATTR_* TLVs:", "indent": 4, "attr_str_func": get_ovs_key_attr_str, "decode_tree": { "OVS_KEY_ATTR_TUNNEL": { "header": "> Decode OVS_TUNNEL_KEY_ATTR_* TLVs:", "indent": 4, "attr_str_func": get_ovs_tunnel_key_attr_str, "decode_tree": None, }, }, }, }, } nl_attr_tree_exec = { "OVS_PACKET_ATTR_KEY": nl_key_attr, "OVS_PACKET_ATTR_ACTIONS": nl_action_attr, } nl_attr_tree_put = { "OVS_FLOW_ATTR_KEY": nl_key_attr, "OVS_FLOW_ATTR_MASK": nl_key_attr, "OVS_FLOW_ATTR_ACTIONS": nl_action_attr, } if get_ovs_dpif_op_str(cmd) == "DPIF_OP_EXECUTE": result = decode_nlm_tlvs(msg, indent=indent + 2, dump=dump, header="> Decode OVS_PACKET_ATTR_* TLVs:", attr_to_str_func=get_ovs_pkt_attr_str, decode_tree=nl_attr_tree_exec) else: result = decode_nlm_tlvs(msg, indent=indent + 2, dump=dump, header="> Decode OVS_FLOW_ATTR_* TLVs:", attr_to_str_func=get_ovs_flow_attr_str, decode_tree=nl_attr_tree_put) return result # # get_ovs_flow_attr_str() # def get_ovs_flow_attr_str(attr): ovs_flow_attr = ["OVS_FLOW_ATTR_UNSPEC", "OVS_FLOW_ATTR_KEY", "OVS_FLOW_ATTR_ACTIONS", "OVS_FLOW_ATTR_STATS", "OVS_FLOW_ATTR_TCP_FLAGS", "OVS_FLOW_ATTR_USED", "OVS_FLOW_ATTR_CLEAR", "OVS_FLOW_ATTR_MASK", "OVS_FLOW_ATTR_PROBE", "OVS_FLOW_ATTR_UFID", "OVS_FLOW_ATTR_UFID_FLAGS", "OVS_FLOW_ATTR_PAD"] if attr < 0 or attr >= len(ovs_flow_attr): return "".format(attr) return ovs_flow_attr[attr] # # get_ovs_pkt_attr_str() # def get_ovs_pkt_attr_str(attr): ovs_pkt_attr = ["OVS_PACKET_ATTR_UNSPEC", "OVS_PACKET_ATTR_PACKET", "OVS_PACKET_ATTR_KEY", "OVS_PACKET_ATTR_ACTIONS", "OVS_PACKET_ATTR_USERDATA", "OVS_PACKET_ATTR_EGRESS_TUN_KEY", "OVS_PACKET_ATTR_UNUSED1", "OVS_PACKET_ATTR_UNUSED2", "OVS_PACKET_ATTR_PROBE", "OVS_PACKET_ATTR_MRU", "OVS_PACKET_ATTR_LEN", "OVS_PACKET_ATTR_HASH"] if attr < 0 or attr >= len(ovs_pkt_attr): return "".format(attr) return ovs_pkt_attr[attr] # # get_ovs_key_attr_str() # def get_ovs_key_attr_str(attr): ovs_key_attr = ["OVS_KEY_ATTR_UNSPEC", "OVS_KEY_ATTR_ENCAP", "OVS_KEY_ATTR_PRIORITY", "OVS_KEY_ATTR_IN_PORT", "OVS_KEY_ATTR_ETHERNET", "OVS_KEY_ATTR_VLAN", "OVS_KEY_ATTR_ETHERTYPE", "OVS_KEY_ATTR_IPV4", "OVS_KEY_ATTR_IPV6", "OVS_KEY_ATTR_TCP", "OVS_KEY_ATTR_UDP", "OVS_KEY_ATTR_ICMP", "OVS_KEY_ATTR_ICMPV6", "OVS_KEY_ATTR_ARP", "OVS_KEY_ATTR_ND", "OVS_KEY_ATTR_SKB_MARK", "OVS_KEY_ATTR_TUNNEL", "OVS_KEY_ATTR_SCTP", "OVS_KEY_ATTR_TCP_FLAGS", "OVS_KEY_ATTR_DP_HASH", "OVS_KEY_ATTR_RECIRC_ID", "OVS_KEY_ATTR_MPLS", "OVS_KEY_ATTR_CT_STATE", "OVS_KEY_ATTR_CT_ZONE", "OVS_KEY_ATTR_CT_MARK", "OVS_KEY_ATTR_CT_LABELS", "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4", "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6", "OVS_KEY_ATTR_NSH"] if attr < 0 or attr >= len(ovs_key_attr): return "".format(attr) return ovs_key_attr[attr] # # get_ovs_action_attr_str() # def get_ovs_action_attr_str(attr): ovs_action_attr = ["OVS_ACTION_ATTR_UNSPEC", "OVS_ACTION_ATTR_OUTPUT", "OVS_ACTION_ATTR_USERSPACE", "OVS_ACTION_ATTR_SET", "OVS_ACTION_ATTR_PUSH_VLAN", "OVS_ACTION_ATTR_POP_VLAN", "OVS_ACTION_ATTR_SAMPLE", "OVS_ACTION_ATTR_RECIRC", "OVS_ACTION_ATTR_HASH", "OVS_ACTION_ATTR_PUSH_MPLS", "OVS_ACTION_ATTR_POP_MPLS", "OVS_ACTION_ATTR_SET_MASKED", "OVS_ACTION_ATTR_CT", "OVS_ACTION_ATTR_TRUNC", "OVS_ACTION_ATTR_PUSH_ETH", "OVS_ACTION_ATTR_POP_ETH", "OVS_ACTION_ATTR_CT_CLEAR", "OVS_ACTION_ATTR_PUSH_NSH", "OVS_ACTION_ATTR_POP_NSH", "OVS_ACTION_ATTR_METER", "OVS_ACTION_ATTR_CLONE", "OVS_ACTION_ATTR_CHECK_PKT_LEN", "OVS_ACTION_ATTR_ADD_MPLS", "OVS_ACTION_ATTR_DEC_TTL", "OVS_ACTION_ATTR_DROP", "OVS_ACTION_ATTR_PSAMPLE", "OVS_ACTION_ATTR_TUNNEL_PUSH", "OVS_ACTION_ATTR_TUNNEL_POP", "OVS_ACTION_ATTR_LB_OUTPUT"] if attr < 0 or attr >= len(ovs_action_attr): return "".format(attr) return ovs_action_attr[attr] # # get_ovs_tunnel_key_attr_str() # def get_ovs_tunnel_key_attr_str(attr): ovs_tunnel_key_attr = ["OVS_TUNNEL_KEY_ATTR_ID", "OVS_TUNNEL_KEY_ATTR_IPV4_SRC", "OVS_TUNNEL_KEY_ATTR_IPV4_DST", "OVS_TUNNEL_KEY_ATTR_TOS", "OVS_TUNNEL_KEY_ATTR_TTL", "OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT", "OVS_TUNNEL_KEY_ATTR_CSUM", "OVS_TUNNEL_KEY_ATTR_OAM", "OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS", "OVS_TUNNEL_KEY_ATTR_TP_SRC", "OVS_TUNNEL_KEY_ATTR_TP_DST", "OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS", "OVS_TUNNEL_KEY_ATTR_IPV6_SRC", "OVS_TUNNEL_KEY_ATTR_IPV6_DST", "OVS_TUNNEL_KEY_ATTR_PAD", "OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS", "OVS_TUNNEL_KEY_ATTR_GTPU_OPTS"] if attr < 0 or attr >= len(ovs_tunnel_key_attr): return "".format(attr) return ovs_tunnel_key_attr[attr] # # get_ovs_dpif_op_str() # def get_ovs_dpif_op_str(op): ovs_dpif_ops = ["DPIF_OP_UNSPEC", "DPIF_OP_FLOW_PUT", "DPIF_OP_FLOW_DEL", "DPIF_OP_EXECUTE", "DPIF_OP_FLOW_GET"] if op < 0 or op >= len(ovs_dpif_ops): return "".format(op) return ovs_dpif_ops[op] # # buffer_size_type() # def buffer_size_type(astr, min=64, max=2048): value = int(astr) if min <= value <= max: return value else: raise argparse.ArgumentTypeError( "value not in range {}-{}".format(min, max)) # # next_power_of_two() # def next_power_of_two(val): np = 1 while np < val: np *= 2 return np # # main() # def main(): # # Don't like these globals, but ctx passing does not seem to work with the # existing open_ring_buffer() API :( # global b global options # # Argument parsing # parser = argparse.ArgumentParser() parser.add_argument("--buffer-page-count", help="Number of BPF ring buffer pages, default 1024", type=int, default=1024, metavar="NUMBER") parser.add_argument("-D", "--debug", help="Enable eBPF debugging", type=int, const=0x3f, default=0, nargs="?") parser.add_argument("-d", "--packet-decode", help="Display packet content in selected mode, " "default none", choices=["none", "hex", "decode"], default="none") parser.add_argument("-n", "--nlmsg-decode", help="Display netlink message content in selected mode" ", default nlraw", choices=["none", "hex", "nlraw"], default="nlraw") parser.add_argument("-p", "--pid", metavar="VSWITCHD_PID", help="ovs-vswitch's PID", type=int, default=None) parser.add_argument("-s", "--nlmsg-size", help="Set maximum netlink message size to capture, " "default 512", type=buffer_size_type, default=512, metavar="[64-2048]") parser.add_argument("--trace-del-op", help="Monitor DPIF_OP_FLOW_DEL messages", action="store_true") parser.add_argument("--trace-exec-op", help="Monitor DPIF_OP_EXECUTE messages", action="store_true") parser.add_argument("--trace-get-op", help="Monitor DPIF_OP_FLOW_GET messages", action="store_true") parser.add_argument("--trace-put-op", help="Monitor DPIF_OP_FLOW_PUT messages", action="store_true") parser.add_argument("-w", "--pcap", metavar="PCAP_FILE", help="Write execute packets to specified pcap file", type=str, default=None) options = parser.parse_args() # # Verify arguments. # if (not options.trace_del_op and not options.trace_exec_op and not options.trace_get_op and not options.trace_put_op): print("ERROR: At least on of the '--trace-*-op' arguments should be " "specified!") sys.exit(-1) # # Find the PID of the ovs-vswitchd daemon if not specified. # if options.pid is None: for proc in psutil.process_iter(): if "ovs-vswitchd" in proc.name(): if options.pid is not None: print("ERROR: Multiple ovs-vswitchd daemons running, " "use the -p option!") sys.exit(-1) options.pid = proc.pid # # Error checking on input parameters # if options.pid is None: print("ERROR: Failed to find ovs-vswitchd's PID!") sys.exit(-1) if options.pcap is not None: if exists(options.pcap): print("ERROR: Destination capture file \"{}\" already exists!". format(options.pcap)) sys.exit(-1) options.buffer_page_count = next_power_of_two(options.buffer_page_count) # # Attach the usdt probe # u = USDT(pid=int(options.pid)) try: if options.trace_exec_op: u.enable_probe(probe="dpif_netlink_operate__:op_flow_execute", fn_name="trace__op_execute") if options.trace_put_op: u.enable_probe(probe="dpif_netlink_operate__:op_flow_put", fn_name="trace__op_flow_put") if options.trace_del_op: u.enable_probe(probe="dpif_netlink_operate__:op_flow_del", fn_name="trace__op_flow_del") if options.trace_get_op: u.enable_probe(probe="dpif_netlink_operate__:op_flow_get", fn_name="trace__op_flow_get") except USDTException as e: print("ERROR: {}".format( (re.sub("^", " " * 7, str(e), flags=re.MULTILINE)).strip(). replace("--with-dtrace or --enable-dtrace", "--enable-usdt-probes"))) sys.exit(-1) # # Uncomment to see how arguments are decoded. # print(u.get_text()) # # # Attach probe to running process # source = ebpf_source.replace("", str(options.nlmsg_size)) source = source.replace("", str(options.buffer_page_count)) source = source.replace("", "1" if options.trace_del_op else "0") source = source.replace("", "1" if options.trace_exec_op else "0") source = source.replace("", "1" if options.trace_get_op else "0") source = source.replace("", "1" if options.trace_put_op else "0") b = BPF(text=source, usdt_contexts=[u], debug=options.debug) # # Print header # print("Display DPIF operations being queued for transmission onto the " "netlink socket.") print("{:<18} {:<4} {:<16} {:<10} {:<10} {}".format( "TIME", "CPU", "COMM", "PID", "NL_SIZE", "DPIF_OPERATION")) # # Dump out all events # b["events"].open_ring_buffer(print_event) while 1: try: b.ring_buffer_poll() except KeyboardInterrupt: break dropcnt = b.get_table("dropcnt") for k in dropcnt.keys(): count = dropcnt.sum(k).value if k.value == 0 and count > 0: print("\nWARNING: Not all upcalls were captured, {} were dropped!" "\n Increase the BPF ring buffer size with the " "--buffer-page-count option.".format(count)) # # Start main() as the default entry point... # if __name__ == "__main__": main()