#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 # Controls the openvswitch module. Part of the kselftest suite, but # can be used for some diagnostic purpose as well. import argparse import errno import ipaddress import logging import multiprocessing import struct import sys import time try: from pyroute2 import NDB from pyroute2.netlink import NLA_F_NESTED from pyroute2.netlink import NLM_F_ACK from pyroute2.netlink import NLM_F_DUMP from pyroute2.netlink import NLM_F_REQUEST from pyroute2.netlink import genlmsg from pyroute2.netlink import nla from pyroute2.netlink import nlmsg_atoms from pyroute2.netlink.exceptions import NetlinkError from pyroute2.netlink.generic import GenericNetlinkSocket except ModuleNotFoundError: print("Need to install the python pyroute2 package.") sys.exit(0) OVS_DATAPATH_FAMILY = "ovs_datapath" OVS_VPORT_FAMILY = "ovs_vport" OVS_FLOW_FAMILY = "ovs_flow" OVS_PACKET_FAMILY = "ovs_packet" OVS_METER_FAMILY = "ovs_meter" OVS_CT_LIMIT_FAMILY = "ovs_ct_limit" OVS_DATAPATH_VERSION = 2 OVS_DP_CMD_NEW = 1 OVS_DP_CMD_DEL = 2 OVS_DP_CMD_GET = 3 OVS_DP_CMD_SET = 4 OVS_VPORT_CMD_NEW = 1 OVS_VPORT_CMD_DEL = 2 OVS_VPORT_CMD_GET = 3 OVS_VPORT_CMD_SET = 4 OVS_FLOW_CMD_NEW = 1 OVS_FLOW_CMD_DEL = 2 OVS_FLOW_CMD_GET = 3 OVS_FLOW_CMD_SET = 4 def macstr(mac): outstr = ":".join(["%02X" % i for i in mac]) return outstr def convert_mac(mac_str, mask=False): if mac_str is None or mac_str == "": mac_str = "00:00:00:00:00:00" if mask is True and mac_str != "00:00:00:00:00:00": mac_str = "FF:FF:FF:FF:FF:FF" mac_split = mac_str.split(":") ret = bytearray([int(i, 16) for i in mac_split]) return bytes(ret) def convert_ipv4(ip, mask=False): if ip is None: ip = 0 if mask is True: if ip != 0: ip = int(ipaddress.IPv4Address(ip)) & 0xFFFFFFFF return int(ipaddress.IPv4Address(ip)) class ovs_dp_msg(genlmsg): # include the OVS version # We need a custom header rather than just being able to rely on # genlmsg because fields ends up not expressing everything correctly # if we use the canonical example of setting fields = (('customfield',),) fields = genlmsg.fields + (("dpifindex", "I"),) class ovsactions(nla): nla_flags = NLA_F_NESTED nla_map = ( ("OVS_ACTION_ATTR_UNSPEC", "none"), ("OVS_ACTION_ATTR_OUTPUT", "uint32"), ("OVS_ACTION_ATTR_USERSPACE", "userspace"), ("OVS_ACTION_ATTR_SET", "none"), ("OVS_ACTION_ATTR_PUSH_VLAN", "none"), ("OVS_ACTION_ATTR_POP_VLAN", "flag"), ("OVS_ACTION_ATTR_SAMPLE", "none"), ("OVS_ACTION_ATTR_RECIRC", "uint32"), ("OVS_ACTION_ATTR_HASH", "none"), ("OVS_ACTION_ATTR_PUSH_MPLS", "none"), ("OVS_ACTION_ATTR_POP_MPLS", "flag"), ("OVS_ACTION_ATTR_SET_MASKED", "none"), ("OVS_ACTION_ATTR_CT", "ctact"), ("OVS_ACTION_ATTR_TRUNC", "uint32"), ("OVS_ACTION_ATTR_PUSH_ETH", "none"), ("OVS_ACTION_ATTR_POP_ETH", "flag"), ("OVS_ACTION_ATTR_CT_CLEAR", "flag"), ("OVS_ACTION_ATTR_PUSH_NSH", "none"), ("OVS_ACTION_ATTR_POP_NSH", "flag"), ("OVS_ACTION_ATTR_METER", "none"), ("OVS_ACTION_ATTR_CLONE", "none"), ("OVS_ACTION_ATTR_CHECK_PKT_LEN", "none"), ("OVS_ACTION_ATTR_ADD_MPLS", "none"), ("OVS_ACTION_ATTR_DEC_TTL", "none"), ) class ctact(nla): nla_flags = NLA_F_NESTED nla_map = ( ("OVS_CT_ATTR_NONE", "none"), ("OVS_CT_ATTR_COMMIT", "flag"), ("OVS_CT_ATTR_ZONE", "uint16"), ("OVS_CT_ATTR_MARK", "none"), ("OVS_CT_ATTR_LABELS", "none"), ("OVS_CT_ATTR_HELPER", "asciiz"), ("OVS_CT_ATTR_NAT", "natattr"), ("OVS_CT_ATTR_FORCE_COMMIT", "flag"), ("OVS_CT_ATTR_EVENTMASK", "uint32"), ("OVS_CT_ATTR_TIMEOUT", "asciiz"), ) class natattr(nla): nla_flags = NLA_F_NESTED nla_map = ( ("OVS_NAT_ATTR_NONE", "none"), ("OVS_NAT_ATTR_SRC", "flag"), ("OVS_NAT_ATTR_DST", "flag"), ("OVS_NAT_ATTR_IP_MIN", "ipaddr"), ("OVS_NAT_ATTR_IP_MAX", "ipaddr"), ("OVS_NAT_ATTR_PROTO_MIN", "uint16"), ("OVS_NAT_ATTR_PROTO_MAX", "uint16"), ("OVS_NAT_ATTR_PERSISTENT", "flag"), ("OVS_NAT_ATTR_PROTO_HASH", "flag"), ("OVS_NAT_ATTR_PROTO_RANDOM", "flag"), ) def dpstr(self, more=False): print_str = "nat(" if self.get_attr("OVS_NAT_ATTR_SRC"): print_str += "src" elif self.get_attr("OVS_NAT_ATTR_DST"): print_str += "dst" else: print_str += "XXX-unknown-nat" if self.get_attr("OVS_NAT_ATTR_IP_MIN") or self.get_attr( "OVS_NAT_ATTR_IP_MAX" ): if self.get_attr("OVS_NAT_ATTR_IP_MIN"): print_str += "=%s," % str( self.get_attr("OVS_NAT_ATTR_IP_MIN") ) if self.get_attr("OVS_NAT_ATTR_IP_MAX"): print_str += "-%s," % str( self.get_attr("OVS_NAT_ATTR_IP_MAX") ) else: print_str += "," if self.get_attr("OVS_NAT_ATTR_PROTO_MIN"): print_str += "proto_min=%d," % self.get_attr( "OVS_NAT_ATTR_PROTO_MIN" ) if self.get_attr("OVS_NAT_ATTR_PROTO_MAX"): print_str += "proto_max=%d," % self.get_attr( "OVS_NAT_ATTR_PROTO_MAX" ) if self.get_attr("OVS_NAT_ATTR_PERSISTENT"): print_str += "persistent," if self.get_attr("OVS_NAT_ATTR_HASH"): print_str += "hash," if self.get_attr("OVS_NAT_ATTR_RANDOM"): print_str += "random" print_str += ")" return print_str def dpstr(self, more=False): print_str = "ct(" if self.get_attr("OVS_CT_ATTR_COMMIT") is not None: print_str += "commit," if self.get_attr("OVS_CT_ATTR_ZONE") is not None: print_str += "zone=%d," % self.get_attr("OVS_CT_ATTR_ZONE") if self.get_attr("OVS_CT_ATTR_HELPER") is not None: print_str += "helper=%s," % self.get_attr("OVS_CT_ATTR_HELPER") if self.get_attr("OVS_CT_ATTR_NAT") is not None: print_str += self.get_attr("OVS_CT_ATTR_NAT").dpstr(more) print_str += "," if self.get_attr("OVS_CT_ATTR_FORCE_COMMIT") is not None: print_str += "force," if self.get_attr("OVS_CT_ATTR_EVENTMASK") is not None: print_str += "emask=0x%X," % self.get_attr( "OVS_CT_ATTR_EVENTMASK" ) if self.get_attr("OVS_CT_ATTR_TIMEOUT") is not None: print_str += "timeout=%s" % self.get_attr( "OVS_CT_ATTR_TIMEOUT" ) print_str += ")" return print_str class userspace(nla): nla_flags = NLA_F_NESTED nla_map = ( ("OVS_USERSPACE_ATTR_UNUSED", "none"), ("OVS_USERSPACE_ATTR_PID", "uint32"), ("OVS_USERSPACE_ATTR_USERDATA", "array(uint8)"), ("OVS_USERSPACE_ATTR_EGRESS_TUN_PORT", "uint32"), ) def dpstr(self, more=False): print_str = "userspace(" if self.get_attr("OVS_USERSPACE_ATTR_PID") is not None: print_str += "pid=%d," % self.get_attr( "OVS_USERSPACE_ATTR_PID" ) if self.get_attr("OVS_USERSPACE_ATTR_USERDATA") is not None: print_str += "userdata=" for f in self.get_attr("OVS_USERSPACE_ATTR_USERDATA"): print_str += "%x." % f if self.get_attr("OVS_USERSPACE_ATTR_TUN_PORT") is not None: print_str += "egress_tun_port=%d" % self.get_attr( "OVS_USERSPACE_ATTR_TUN_PORT" ) print_str += ")" return print_str def dpstr(self, more=False): print_str = "" for field in self.nla_map: if field[1] == "none" or self.get_attr(field[0]) is None: continue if print_str != "": print_str += "," if field[1] == "uint32": if field[0] == "OVS_ACTION_ATTR_OUTPUT": print_str += "%d" % int(self.get_attr(field[0])) elif field[0] == "OVS_ACTION_ATTR_RECIRC": print_str += "recirc(0x%x)" % int(self.get_attr(field[0])) elif field[0] == "OVS_ACTION_ATTR_TRUNC": print_str += "trunc(%d)" % int(self.get_attr(field[0])) elif field[1] == "flag": if field[0] == "OVS_ACTION_ATTR_CT_CLEAR": print_str += "ct_clear" elif field[0] == "OVS_ACTION_ATTR_POP_VLAN": print_str += "pop_vlan" elif field[0] == "OVS_ACTION_ATTR_POP_ETH": print_str += "pop_eth" elif field[0] == "OVS_ACTION_ATTR_POP_NSH": print_str += "pop_nsh" elif field[0] == "OVS_ACTION_ATTR_POP_MPLS": print_str += "pop_mpls" else: datum = self.get_attr(field[0]) print_str += datum.dpstr(more) return print_str class ovskey(nla): nla_flags = NLA_F_NESTED nla_map = ( ("OVS_KEY_ATTR_UNSPEC", "none"), ("OVS_KEY_ATTR_ENCAP", "none"), ("OVS_KEY_ATTR_PRIORITY", "uint32"), ("OVS_KEY_ATTR_IN_PORT", "uint32"), ("OVS_KEY_ATTR_ETHERNET", "ethaddr"), ("OVS_KEY_ATTR_VLAN", "uint16"), ("OVS_KEY_ATTR_ETHERTYPE", "be16"), ("OVS_KEY_ATTR_IPV4", "ovs_key_ipv4"), ("OVS_KEY_ATTR_IPV6", "ovs_key_ipv6"), ("OVS_KEY_ATTR_TCP", "ovs_key_tcp"), ("OVS_KEY_ATTR_UDP", "ovs_key_udp"), ("OVS_KEY_ATTR_ICMP", "ovs_key_icmp"), ("OVS_KEY_ATTR_ICMPV6", "ovs_key_icmpv6"), ("OVS_KEY_ATTR_ARP", "ovs_key_arp"), ("OVS_KEY_ATTR_ND", "ovs_key_nd"), ("OVS_KEY_ATTR_SKB_MARK", "uint32"), ("OVS_KEY_ATTR_TUNNEL", "none"), ("OVS_KEY_ATTR_SCTP", "ovs_key_sctp"), ("OVS_KEY_ATTR_TCP_FLAGS", "be16"), ("OVS_KEY_ATTR_DP_HASH", "uint32"), ("OVS_KEY_ATTR_RECIRC_ID", "uint32"), ("OVS_KEY_ATTR_MPLS", "array(ovs_key_mpls)"), ("OVS_KEY_ATTR_CT_STATE", "uint32"), ("OVS_KEY_ATTR_CT_ZONE", "uint16"), ("OVS_KEY_ATTR_CT_MARK", "uint32"), ("OVS_KEY_ATTR_CT_LABELS", "none"), ("OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4", "ovs_key_ct_tuple_ipv4"), ("OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6", "ovs_key_ct_tuple_ipv6"), ("OVS_KEY_ATTR_NSH", "none"), ("OVS_KEY_ATTR_PACKET_TYPE", "none"), ("OVS_KEY_ATTR_ND_EXTENSIONS", "none"), ("OVS_KEY_ATTR_TUNNEL_INFO", "none"), ("OVS_KEY_ATTR_IPV6_EXTENSIONS", "none"), ) class ovs_key_proto(nla): fields = ( ("src", "!H"), ("dst", "!H"), ) fields_map = ( ("src", "src", "%d", lambda x: int(x) if x is not None else 0), ("dst", "dst", "%d", lambda x: int(x) if x is not None else 0), ) def __init__( self, protostr, data=None, offset=None, parent=None, length=None, init=None, ): self.proto_str = protostr nla.__init__( self, data=data, offset=offset, parent=parent, length=length, init=init, ) def dpstr(self, masked=None, more=False): outstr = self.proto_str + "(" first = False for f in self.fields_map: if first: outstr += "," if masked is None: outstr += "%s=" % f[0] if isinstance(f[2], str): outstr += f[2] % self[f[1]] else: outstr += f[2](self[f[1]]) first = True elif more or f[3](masked[f[1]]) != 0: outstr += "%s=" % f[0] if isinstance(f[2], str): outstr += f[2] % self[f[1]] else: outstr += f[2](self[f[1]]) outstr += "/" if isinstance(f[2], str): outstr += f[2] % masked[f[1]] else: outstr += f[2](masked[f[1]]) first = True outstr += ")" return outstr class ethaddr(ovs_key_proto): fields = ( ("src", "!6s"), ("dst", "!6s"), ) fields_map = ( ( "src", "src", macstr, lambda x: int.from_bytes(x, "big"), convert_mac, ), ( "dst", "dst", macstr, lambda x: int.from_bytes(x, "big"), convert_mac, ), ) def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "eth", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_ipv4(ovs_key_proto): fields = ( ("src", "!I"), ("dst", "!I"), ("proto", "B"), ("tos", "B"), ("ttl", "B"), ("frag", "B"), ) fields_map = ( ( "src", "src", lambda x: str(ipaddress.IPv4Address(x)), int, convert_ipv4, ), ( "dst", "dst", lambda x: str(ipaddress.IPv4Address(x)), int, convert_ipv4, ), ("proto", "proto", "%d", lambda x: int(x) if x is not None else 0), ("tos", "tos", "%d", lambda x: int(x) if x is not None else 0), ("ttl", "ttl", "%d", lambda x: int(x) if x is not None else 0), ("frag", "frag", "%d", lambda x: int(x) if x is not None else 0), ) def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "ipv4", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_ipv6(ovs_key_proto): fields = ( ("src", "!16s"), ("dst", "!16s"), ("label", "!I"), ("proto", "B"), ("tclass", "B"), ("hlimit", "B"), ("frag", "B"), ) fields_map = ( ( "src", "src", lambda x: str(ipaddress.IPv6Address(x)), lambda x: int.from_bytes(x, "big"), lambda x: ipaddress.IPv6Address(x), ), ( "dst", "dst", lambda x: str(ipaddress.IPv6Address(x)), lambda x: int.from_bytes(x, "big"), lambda x: ipaddress.IPv6Address(x), ), ("label", "label", "%d", int), ("proto", "proto", "%d", int), ("tclass", "tclass", "%d", int), ("hlimit", "hlimit", "%d", int), ("frag", "frag", "%d", int), ) def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "ipv6", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_tcp(ovs_key_proto): def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "tcp", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_udp(ovs_key_proto): def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "udp", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_sctp(ovs_key_proto): def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "sctp", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_icmp(ovs_key_proto): fields = ( ("type", "B"), ("code", "B"), ) fields_map = ( ("type", "type", "%d", int), ("code", "code", "%d", int), ) def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "icmp", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_icmpv6(ovs_key_icmp): def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "icmpv6", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_arp(ovs_key_proto): fields = ( ("sip", "!I"), ("tip", "!I"), ("op", "!H"), ("sha", "!6s"), ("tha", "!6s"), ("pad", "xx"), ) fields_map = ( ( "sip", "sip", lambda x: str(ipaddress.IPv4Address(x)), int, convert_ipv4, ), ( "tip", "tip", lambda x: str(ipaddress.IPv4Address(x)), int, convert_ipv4, ), ("op", "op", "%d", lambda x: int(x) if x is not None else 0), ( "sha", "sha", macstr, lambda x: int.from_bytes(x, "big"), convert_mac, ), ( "tha", "tha", macstr, lambda x: int.from_bytes(x, "big"), convert_mac, ), ) def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "arp", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_nd(ovs_key_proto): fields = ( ("target", "!16s"), ("sll", "!6s"), ("tll", "!6s"), ) fields_map = ( ( "target", "target", lambda x: str(ipaddress.IPv6Address(x)), lambda x: int.from_bytes(x, "big"), ), ("sll", "sll", macstr, lambda x: int.from_bytes(x, "big")), ("tll", "tll", macstr, lambda x: int.from_bytes(x, "big")), ) def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "nd", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_ct_tuple_ipv4(ovs_key_proto): fields = ( ("src", "!I"), ("dst", "!I"), ("tp_src", "!H"), ("tp_dst", "!H"), ("proto", "B"), ) fields_map = ( ( "src", "src", lambda x: str(ipaddress.IPv4Address(x)), int, ), ( "dst", "dst", lambda x: str(ipaddress.IPv6Address(x)), int, ), ("tp_src", "tp_src", "%d", int), ("tp_dst", "tp_dst", "%d", int), ("proto", "proto", "%d", int), ) def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "ct_tuple4", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_ct_tuple_ipv6(nla): fields = ( ("src", "!16s"), ("dst", "!16s"), ("tp_src", "!H"), ("tp_dst", "!H"), ("proto", "B"), ) fields_map = ( ( "src", "src", lambda x: str(ipaddress.IPv6Address(x)), lambda x: int.from_bytes(x, "big", convertmac), ), ( "dst", "dst", lambda x: str(ipaddress.IPv6Address(x)), lambda x: int.from_bytes(x, "big"), ), ("tp_src", "tp_src", "%d", int), ("tp_dst", "tp_dst", "%d", int), ("proto", "proto", "%d", int), ) def __init__( self, data=None, offset=None, parent=None, length=None, init=None, ): ovskey.ovs_key_proto.__init__( self, "ct_tuple6", data=data, offset=offset, parent=parent, length=length, init=init, ) class ovs_key_mpls(nla): fields = (("lse", ">I"),) def dpstr(self, mask=None, more=False): print_str = "" for field in ( ( "OVS_KEY_ATTR_PRIORITY", "skb_priority", "%d", lambda x: False, True, ), ( "OVS_KEY_ATTR_SKB_MARK", "skb_mark", "%d", lambda x: False, True, ), ( "OVS_KEY_ATTR_RECIRC_ID", "recirc_id", "0x%08X", lambda x: False, True, ), ( "OVS_KEY_ATTR_DP_HASH", "dp_hash", "0x%08X", lambda x: False, True, ), ( "OVS_KEY_ATTR_CT_STATE", "ct_state", "0x%04x", lambda x: False, True, ), ( "OVS_KEY_ATTR_CT_ZONE", "ct_zone", "0x%04x", lambda x: False, True, ), ( "OVS_KEY_ATTR_CT_MARK", "ct_mark", "0x%08x", lambda x: False, True, ), ( "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV4", None, None, False, False, ), ( "OVS_KEY_ATTR_CT_ORIG_TUPLE_IPV6", None, None, False, False, ), ( "OVS_KEY_ATTR_IN_PORT", "in_port", "%d", lambda x: True, True, ), ("OVS_KEY_ATTR_ETHERNET", None, None, False, False), ( "OVS_KEY_ATTR_ETHERTYPE", "eth_type", "0x%04x", lambda x: int(x) == 0xFFFF, True, ), ("OVS_KEY_ATTR_IPV4", None, None, False, False), ("OVS_KEY_ATTR_IPV6", None, None, False, False), ("OVS_KEY_ATTR_ARP", None, None, False, False), ("OVS_KEY_ATTR_TCP", None, None, False, False), ( "OVS_KEY_ATTR_TCP_FLAGS", "tcp_flags", "0x%04x", lambda x: False, True, ), ("OVS_KEY_ATTR_UDP", None, None, False, False), ("OVS_KEY_ATTR_SCTP", None, None, False, False), ("OVS_KEY_ATTR_ICMP", None, None, False, False), ("OVS_KEY_ATTR_ICMPV6", None, None, False, False), ("OVS_KEY_ATTR_ND", None, None, False, False), ): v = self.get_attr(field[0]) if v is not None: m = None if mask is None else mask.get_attr(field[0]) if field[4] is False: print_str += v.dpstr(m, more) print_str += "," else: if m is None or field[3](m): print_str += field[1] + "(" print_str += field[2] % v print_str += ")," elif more or m != 0: print_str += field[1] + "(" print_str += (field[2] % v) + "/" + (field[2] % m) print_str += ")," return print_str class OvsPacket(GenericNetlinkSocket): OVS_PACKET_CMD_MISS = 1 # Flow table miss OVS_PACKET_CMD_ACTION = 2 # USERSPACE action OVS_PACKET_CMD_EXECUTE = 3 # Apply actions to packet class ovs_packet_msg(ovs_dp_msg): nla_map = ( ("OVS_PACKET_ATTR_UNSPEC", "none"), ("OVS_PACKET_ATTR_PACKET", "array(uint8)"), ("OVS_PACKET_ATTR_KEY", "ovskey"), ("OVS_PACKET_ATTR_ACTIONS", "ovsactions"), ("OVS_PACKET_ATTR_USERDATA", "none"), ("OVS_PACKET_ATTR_EGRESS_TUN_KEY", "none"), ("OVS_PACKET_ATTR_UNUSED1", "none"), ("OVS_PACKET_ATTR_UNUSED2", "none"), ("OVS_PACKET_ATTR_PROBE", "none"), ("OVS_PACKET_ATTR_MRU", "uint16"), ("OVS_PACKET_ATTR_LEN", "uint32"), ("OVS_PACKET_ATTR_HASH", "uint64"), ) def __init__(self): GenericNetlinkSocket.__init__(self) self.bind(OVS_PACKET_FAMILY, OvsPacket.ovs_packet_msg) def upcall_handler(self, up=None): print("listening on upcall packet handler:", self.epid) while True: try: msgs = self.get() for msg in msgs: if not up: continue if msg["cmd"] == OvsPacket.OVS_PACKET_CMD_MISS: up.miss(msg) elif msg["cmd"] == OvsPacket.OVS_PACKET_CMD_ACTION: up.action(msg) elif msg["cmd"] == OvsPacket.OVS_PACKET_CMD_EXECUTE: up.execute(msg) else: print("Unkonwn cmd: %d" % msg["cmd"]) except NetlinkError as ne: raise ne class OvsDatapath(GenericNetlinkSocket): OVS_DP_F_VPORT_PIDS = 1 << 1 OVS_DP_F_DISPATCH_UPCALL_PER_CPU = 1 << 3 class dp_cmd_msg(ovs_dp_msg): """ Message class that will be used to communicate with the kernel module. """ nla_map = ( ("OVS_DP_ATTR_UNSPEC", "none"), ("OVS_DP_ATTR_NAME", "asciiz"), ("OVS_DP_ATTR_UPCALL_PID", "array(uint32)"), ("OVS_DP_ATTR_STATS", "dpstats"), ("OVS_DP_ATTR_MEGAFLOW_STATS", "megaflowstats"), ("OVS_DP_ATTR_USER_FEATURES", "uint32"), ("OVS_DP_ATTR_PAD", "none"), ("OVS_DP_ATTR_MASKS_CACHE_SIZE", "uint32"), ("OVS_DP_ATTR_PER_CPU_PIDS", "array(uint32)"), ) class dpstats(nla): fields = ( ("hit", "=Q"), ("missed", "=Q"), ("lost", "=Q"), ("flows", "=Q"), ) class megaflowstats(nla): fields = ( ("mask_hit", "=Q"), ("masks", "=I"), ("padding", "=I"), ("cache_hits", "=Q"), ("pad1", "=Q"), ) def __init__(self): GenericNetlinkSocket.__init__(self) self.bind(OVS_DATAPATH_FAMILY, OvsDatapath.dp_cmd_msg) def info(self, dpname, ifindex=0): msg = OvsDatapath.dp_cmd_msg() msg["cmd"] = OVS_DP_CMD_GET msg["version"] = OVS_DATAPATH_VERSION msg["reserved"] = 0 msg["dpifindex"] = ifindex msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) try: reply = self.nlm_request( msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST ) reply = reply[0] except NetlinkError as ne: if ne.code == errno.ENODEV: reply = None else: raise ne return reply def create( self, dpname, shouldUpcall=False, versionStr=None, p=OvsPacket() ): msg = OvsDatapath.dp_cmd_msg() msg["cmd"] = OVS_DP_CMD_NEW if versionStr is None: msg["version"] = OVS_DATAPATH_VERSION else: msg["version"] = int(versionStr.split(":")[0], 0) msg["reserved"] = 0 msg["dpifindex"] = 0 msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) dpfeatures = 0 if versionStr is not None and versionStr.find(":") != -1: dpfeatures = int(versionStr.split(":")[1], 0) else: if versionStr is None or versionStr.find(":") == -1: dpfeatures |= OvsDatapath.OVS_DP_F_DISPATCH_UPCALL_PER_CPU dpfeatures &= ~OvsDatapath.OVS_DP_F_VPORT_PIDS nproc = multiprocessing.cpu_count() procarray = [] for i in range(1, nproc): procarray += [int(p.epid)] msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", procarray]) msg["attrs"].append(["OVS_DP_ATTR_USER_FEATURES", dpfeatures]) if not shouldUpcall: msg["attrs"].append(["OVS_DP_ATTR_UPCALL_PID", [0]]) try: reply = self.nlm_request( msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK ) reply = reply[0] except NetlinkError as ne: if ne.code == errno.EEXIST: reply = None else: raise ne return reply def destroy(self, dpname): msg = OvsDatapath.dp_cmd_msg() msg["cmd"] = OVS_DP_CMD_DEL msg["version"] = OVS_DATAPATH_VERSION msg["reserved"] = 0 msg["dpifindex"] = 0 msg["attrs"].append(["OVS_DP_ATTR_NAME", dpname]) try: reply = self.nlm_request( msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK ) reply = reply[0] except NetlinkError as ne: if ne.code == errno.ENODEV: reply = None else: raise ne return reply class OvsVport(GenericNetlinkSocket): OVS_VPORT_TYPE_NETDEV = 1 OVS_VPORT_TYPE_INTERNAL = 2 OVS_VPORT_TYPE_GRE = 3 OVS_VPORT_TYPE_VXLAN = 4 OVS_VPORT_TYPE_GENEVE = 5 class ovs_vport_msg(ovs_dp_msg): nla_map = ( ("OVS_VPORT_ATTR_UNSPEC", "none"), ("OVS_VPORT_ATTR_PORT_NO", "uint32"), ("OVS_VPORT_ATTR_TYPE", "uint32"), ("OVS_VPORT_ATTR_NAME", "asciiz"), ("OVS_VPORT_ATTR_OPTIONS", "none"), ("OVS_VPORT_ATTR_UPCALL_PID", "array(uint32)"), ("OVS_VPORT_ATTR_STATS", "vportstats"), ("OVS_VPORT_ATTR_PAD", "none"), ("OVS_VPORT_ATTR_IFINDEX", "uint32"), ("OVS_VPORT_ATTR_NETNSID", "uint32"), ) class vportstats(nla): fields = ( ("rx_packets", "=Q"), ("tx_packets", "=Q"), ("rx_bytes", "=Q"), ("tx_bytes", "=Q"), ("rx_errors", "=Q"), ("tx_errors", "=Q"), ("rx_dropped", "=Q"), ("tx_dropped", "=Q"), ) def type_to_str(vport_type): if vport_type == OvsVport.OVS_VPORT_TYPE_NETDEV: return "netdev" elif vport_type == OvsVport.OVS_VPORT_TYPE_INTERNAL: return "internal" elif vport_type == OvsVport.OVS_VPORT_TYPE_GRE: return "gre" elif vport_type == OvsVport.OVS_VPORT_TYPE_VXLAN: return "vxlan" elif vport_type == OvsVport.OVS_VPORT_TYPE_GENEVE: return "geneve" raise ValueError("Unknown vport type:%d" % vport_type) def str_to_type(vport_type): if vport_type == "netdev": return OvsVport.OVS_VPORT_TYPE_NETDEV elif vport_type == "internal": return OvsVport.OVS_VPORT_TYPE_INTERNAL elif vport_type == "gre": return OvsVport.OVS_VPORT_TYPE_INTERNAL elif vport_type == "vxlan": return OvsVport.OVS_VPORT_TYPE_VXLAN elif vport_type == "geneve": return OvsVport.OVS_VPORT_TYPE_GENEVE raise ValueError("Unknown vport type: '%s'" % vport_type) def __init__(self, packet=OvsPacket()): GenericNetlinkSocket.__init__(self) self.bind(OVS_VPORT_FAMILY, OvsVport.ovs_vport_msg) self.upcall_packet = packet def info(self, vport_name, dpifindex=0, portno=None): msg = OvsVport.ovs_vport_msg() msg["cmd"] = OVS_VPORT_CMD_GET msg["version"] = OVS_DATAPATH_VERSION msg["reserved"] = 0 msg["dpifindex"] = dpifindex if portno is None: msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_name]) else: msg["attrs"].append(["OVS_VPORT_ATTR_PORT_NO", portno]) try: reply = self.nlm_request( msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST ) reply = reply[0] except NetlinkError as ne: if ne.code == errno.ENODEV: reply = None else: raise ne return reply def attach(self, dpindex, vport_ifname, ptype): msg = OvsVport.ovs_vport_msg() msg["cmd"] = OVS_VPORT_CMD_NEW msg["version"] = OVS_DATAPATH_VERSION msg["reserved"] = 0 msg["dpifindex"] = dpindex port_type = OvsVport.str_to_type(ptype) msg["attrs"].append(["OVS_VPORT_ATTR_TYPE", port_type]) msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) msg["attrs"].append( ["OVS_VPORT_ATTR_UPCALL_PID", [self.upcall_packet.epid]] ) try: reply = self.nlm_request( msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK ) reply = reply[0] except NetlinkError as ne: if ne.code == errno.EEXIST: reply = None else: raise ne return reply def reset_upcall(self, dpindex, vport_ifname, p=None): msg = OvsVport.ovs_vport_msg() msg["cmd"] = OVS_VPORT_CMD_SET msg["version"] = OVS_DATAPATH_VERSION msg["reserved"] = 0 msg["dpifindex"] = dpindex msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) if p == None: p = self.upcall_packet else: self.upcall_packet = p msg["attrs"].append(["OVS_VPORT_ATTR_UPCALL_PID", [p.epid]]) try: reply = self.nlm_request( msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK ) reply = reply[0] except NetlinkError as ne: raise ne return reply def detach(self, dpindex, vport_ifname): msg = OvsVport.ovs_vport_msg() msg["cmd"] = OVS_VPORT_CMD_DEL msg["version"] = OVS_DATAPATH_VERSION msg["reserved"] = 0 msg["dpifindex"] = dpindex msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) try: reply = self.nlm_request( msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK ) reply = reply[0] except NetlinkError as ne: if ne.code == errno.ENODEV: reply = None else: raise ne return reply def upcall_handler(self, handler=None): self.upcall_packet.upcall_handler(handler) class OvsFlow(GenericNetlinkSocket): class ovs_flow_msg(ovs_dp_msg): nla_map = ( ("OVS_FLOW_ATTR_UNSPEC", "none"), ("OVS_FLOW_ATTR_KEY", "ovskey"), ("OVS_FLOW_ATTR_ACTIONS", "ovsactions"), ("OVS_FLOW_ATTR_STATS", "flowstats"), ("OVS_FLOW_ATTR_TCP_FLAGS", "uint8"), ("OVS_FLOW_ATTR_USED", "uint64"), ("OVS_FLOW_ATTR_CLEAR", "none"), ("OVS_FLOW_ATTR_MASK", "ovskey"), ("OVS_FLOW_ATTR_PROBE", "none"), ("OVS_FLOW_ATTR_UFID", "array(uint32)"), ("OVS_FLOW_ATTR_UFID_FLAGS", "uint32"), ) class flowstats(nla): fields = ( ("packets", "=Q"), ("bytes", "=Q"), ) def dpstr(self, more=False): ufid = self.get_attr("OVS_FLOW_ATTR_UFID") ufid_str = "" if ufid is not None: ufid_str = ( "ufid:{:08x}-{:04x}-{:04x}-{:04x}-{:04x}{:08x}".format( ufid[0], ufid[1] >> 16, ufid[1] & 0xFFFF, ufid[2] >> 16, ufid[2] & 0, ufid[3], ) ) key_field = self.get_attr("OVS_FLOW_ATTR_KEY") keymsg = None if key_field is not None: keymsg = key_field mask_field = self.get_attr("OVS_FLOW_ATTR_MASK") maskmsg = None if mask_field is not None: maskmsg = mask_field acts_field = self.get_attr("OVS_FLOW_ATTR_ACTIONS") actsmsg = None if acts_field is not None: actsmsg = acts_field print_str = "" if more: print_str += ufid_str + "," if keymsg is not None: print_str += keymsg.dpstr(maskmsg, more) stats = self.get_attr("OVS_FLOW_ATTR_STATS") if stats is None: print_str += " packets:0, bytes:0," else: print_str += " packets:%d, bytes:%d," % ( stats["packets"], stats["bytes"], ) used = self.get_attr("OVS_FLOW_ATTR_USED") print_str += " used:" if used is None: print_str += "never," else: used_time = int(used) cur_time_sec = time.clock_gettime(time.CLOCK_MONOTONIC) used_time = (cur_time_sec * 1000) - used_time print_str += "{}s,".format(used_time / 1000) print_str += " actions:" if ( actsmsg is None or "attrs" not in actsmsg or len(actsmsg["attrs"]) == 0 ): print_str += "drop" else: print_str += actsmsg.dpstr(more) return print_str def __init__(self): GenericNetlinkSocket.__init__(self) self.bind(OVS_FLOW_FAMILY, OvsFlow.ovs_flow_msg) def dump(self, dpifindex, flowspec=None): """ Returns a list of messages containing flows. dpifindex should be a valid datapath obtained by calling into the OvsDatapath lookup flowpsec is a string which represents a flow in the dpctl format. """ msg = OvsFlow.ovs_flow_msg() msg["cmd"] = OVS_FLOW_CMD_GET msg["version"] = OVS_DATAPATH_VERSION msg["reserved"] = 0 msg["dpifindex"] = dpifindex msg_flags = NLM_F_REQUEST | NLM_F_ACK if flowspec is None: msg_flags |= NLM_F_DUMP rep = None try: rep = self.nlm_request( msg, msg_type=self.prid, msg_flags=msg_flags, ) except NetlinkError as ne: raise ne return rep def miss(self, packetmsg): seq = packetmsg["header"]["sequence_number"] keystr = "(none)" key_field = packetmsg.get_attr("OVS_PACKET_ATTR_KEY") if key_field is not None: keystr = key_field.dpstr(None, True) pktdata = packetmsg.get_attr("OVS_PACKET_ATTR_PACKET") pktpres = "yes" if pktdata is not None else "no" print("MISS upcall[%d/%s]: %s" % (seq, pktpres, keystr), flush=True) def execute(self, packetmsg): print("userspace execute command") def action(self, packetmsg): print("userspace action command") def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()): dp_name = dp_lookup_rep.get_attr("OVS_DP_ATTR_NAME") base_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_STATS") megaflow_stats = dp_lookup_rep.get_attr("OVS_DP_ATTR_MEGAFLOW_STATS") user_features = dp_lookup_rep.get_attr("OVS_DP_ATTR_USER_FEATURES") masks_cache_size = dp_lookup_rep.get_attr("OVS_DP_ATTR_MASKS_CACHE_SIZE") print("%s:" % dp_name) print( " lookups: hit:%d missed:%d lost:%d" % (base_stats["hit"], base_stats["missed"], base_stats["lost"]) ) print(" flows:%d" % base_stats["flows"]) pkts = base_stats["hit"] + base_stats["missed"] avg = (megaflow_stats["mask_hit"] / pkts) if pkts != 0 else 0.0 print( " masks: hit:%d total:%d hit/pkt:%f" % (megaflow_stats["mask_hit"], megaflow_stats["masks"], avg) ) print(" caches:") print(" masks-cache: size:%d" % masks_cache_size) if user_features is not None: print(" features: 0x%X" % user_features) # port print out for iface in ndb.interfaces: rep = vpl.info(iface.ifname, ifindex) if rep is not None: print( " port %d: %s (%s)" % ( rep.get_attr("OVS_VPORT_ATTR_PORT_NO"), rep.get_attr("OVS_VPORT_ATTR_NAME"), OvsVport.type_to_str(rep.get_attr("OVS_VPORT_ATTR_TYPE")), ) ) def main(argv): nlmsg_atoms.ovskey = ovskey nlmsg_atoms.ovsactions = ovsactions parser = argparse.ArgumentParser() parser.add_argument( "-v", "--verbose", action="count", help="Increment 'verbose' output counter.", default=0, ) subparsers = parser.add_subparsers() showdpcmd = subparsers.add_parser("show") showdpcmd.add_argument( "showdp", metavar="N", type=str, nargs="?", help="Datapath Name" ) adddpcmd = subparsers.add_parser("add-dp") adddpcmd.add_argument("adddp", help="Datapath Name") adddpcmd.add_argument( "-u", "--upcall", action="store_true", help="Leave open a reader for upcalls", ) adddpcmd.add_argument( "-V", "--versioning", required=False, help="Specify a custom version / feature string", ) deldpcmd = subparsers.add_parser("del-dp") deldpcmd.add_argument("deldp", help="Datapath Name") addifcmd = subparsers.add_parser("add-if") addifcmd.add_argument("dpname", help="Datapath Name") addifcmd.add_argument("addif", help="Interface name for adding") addifcmd.add_argument( "-u", "--upcall", action="store_true", help="Leave open a reader for upcalls", ) addifcmd.add_argument( "-t", "--ptype", type=str, default="netdev", choices=["netdev", "internal"], help="Interface type (default netdev)", ) delifcmd = subparsers.add_parser("del-if") delifcmd.add_argument("dpname", help="Datapath Name") delifcmd.add_argument("delif", help="Interface name for adding") dumpflcmd = subparsers.add_parser("dump-flows") dumpflcmd.add_argument("dumpdp", help="Datapath Name") args = parser.parse_args() if args.verbose > 0: if args.verbose > 1: logging.basicConfig(level=logging.DEBUG) ovspk = OvsPacket() ovsdp = OvsDatapath() ovsvp = OvsVport(ovspk) ovsflow = OvsFlow() ndb = NDB() if hasattr(args, "showdp"): found = False for iface in ndb.interfaces: rep = None if args.showdp is None: rep = ovsdp.info(iface.ifname, 0) elif args.showdp == iface.ifname: rep = ovsdp.info(iface.ifname, 0) if rep is not None: found = True print_ovsdp_full(rep, iface.index, ndb, ovsvp) if not found: msg = "No DP found" if args.showdp is not None: msg += ":'%s'" % args.showdp print(msg) elif hasattr(args, "adddp"): rep = ovsdp.create(args.adddp, args.upcall, args.versioning, ovspk) if rep is None: print("DP '%s' already exists" % args.adddp) else: print("DP '%s' added" % args.adddp) if args.upcall: ovspk.upcall_handler(ovsflow) elif hasattr(args, "deldp"): ovsdp.destroy(args.deldp) elif hasattr(args, "addif"): rep = ovsdp.info(args.dpname, 0) if rep is None: print("DP '%s' not found." % args.dpname) return 1 dpindex = rep["dpifindex"] rep = ovsvp.attach(rep["dpifindex"], args.addif, args.ptype) msg = "vport '%s'" % args.addif if rep and rep["header"]["error"] is None: msg += " added." else: msg += " failed to add." if args.upcall: if rep is None: rep = ovsvp.reset_upcall(dpindex, args.addif, ovspk) ovsvp.upcall_handler(ovsflow) elif hasattr(args, "delif"): rep = ovsdp.info(args.dpname, 0) if rep is None: print("DP '%s' not found." % args.dpname) return 1 rep = ovsvp.detach(rep["dpifindex"], args.delif) msg = "vport '%s'" % args.delif if rep and rep["header"]["error"] is None: msg += " removed." else: msg += " failed to remove." elif hasattr(args, "dumpdp"): rep = ovsdp.info(args.dumpdp, 0) if rep is None: print("DP '%s' not found." % args.dumpdp) return 1 rep = ovsflow.dump(rep["dpifindex"]) for flow in rep: print(flow.dpstr(True if args.verbose > 0 else False)) return 0 if __name__ == "__main__": sys.exit(main(sys.argv))