| #!/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 math | |
| import multiprocessing | |
| import re | |
| import struct | |
| import sys | |
| import time | |
| import types | |
| import uuid | |
| 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.event import EventSocket | |
| from pyroute2.netlink.exceptions import NetlinkError | |
| from pyroute2.netlink.generic import GenericNetlinkSocket | |
| from pyroute2.netlink.nlsocket import Marshal | |
| import pyroute2 | |
| import pyroute2.iproute | |
| except ModuleNotFoundError: | |
| print("Need to install the python pyroute2 package >= 0.6.") | |
| sys.exit(1) | |
| 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 | |
| UINT32_MAX = 0xFFFFFFFF | |
| def macstr(mac): | |
| outstr = ":".join(["%02X" % i for i in mac]) | |
| return outstr | |
| def strcspn(str1, str2): | |
| tot = 0 | |
| for char in str1: | |
| if str2.find(char) != -1: | |
| return tot | |
| tot += 1 | |
| return tot | |
| def strspn(str1, str2): | |
| tot = 0 | |
| for char in str1: | |
| if str2.find(char) == -1: | |
| return tot | |
| tot += 1 | |
| return tot | |
| def intparse(statestr, defmask="0xffffffff"): | |
| totalparse = strspn(statestr, "0123456789abcdefABCDEFx/") | |
| # scan until "/" | |
| count = strspn(statestr, "x0123456789abcdefABCDEF") | |
| firstnum = statestr[:count] | |
| if firstnum[-1] == "/": | |
| firstnum = firstnum[:-1] | |
| k = int(firstnum, 0) | |
| m = None | |
| if defmask is not None: | |
| secondnum = defmask | |
| if statestr[count] == "/": | |
| secondnum = statestr[count + 1 :] # this is wrong... | |
| m = int(secondnum, 0) | |
| return statestr[totalparse + 1 :], k, m | |
| def parse_flags(flag_str, flag_vals): | |
| bitResult = 0 | |
| maskResult = 0 | |
| if len(flag_str) == 0: | |
| return flag_str, bitResult, maskResult | |
| if flag_str[0].isdigit(): | |
| idx = 0 | |
| while flag_str[idx].isdigit() or flag_str[idx] == "x": | |
| idx += 1 | |
| digits = flag_str[:idx] | |
| flag_str = flag_str[idx:] | |
| bitResult = int(digits, 0) | |
| maskResult = int(digits, 0) | |
| while len(flag_str) > 0 and (flag_str[0] == "+" or flag_str[0] == "-"): | |
| if flag_str[0] == "+": | |
| setFlag = True | |
| elif flag_str[0] == "-": | |
| setFlag = False | |
| flag_str = flag_str[1:] | |
| flag_len = 0 | |
| while ( | |
| flag_str[flag_len] != "+" | |
| and flag_str[flag_len] != "-" | |
| and flag_str[flag_len] != "," | |
| and flag_str[flag_len] != ")" | |
| ): | |
| flag_len += 1 | |
| flag = flag_str[0:flag_len] | |
| if flag in flag_vals: | |
| if maskResult & flag_vals[flag]: | |
| raise KeyError( | |
| "Flag %s set once, cannot be set in multiples" % flag | |
| ) | |
| if setFlag: | |
| bitResult |= flag_vals[flag] | |
| maskResult |= flag_vals[flag] | |
| else: | |
| raise KeyError("Missing flag value: %s" % flag) | |
| flag_str = flag_str[flag_len:] | |
| return flag_str, bitResult, maskResult | |
| def parse_ct_state(statestr): | |
| ct_flags = { | |
| "new": 1 << 0, | |
| "est": 1 << 1, | |
| "rel": 1 << 2, | |
| "rpl": 1 << 3, | |
| "inv": 1 << 4, | |
| "trk": 1 << 5, | |
| "snat": 1 << 6, | |
| "dnat": 1 << 7, | |
| } | |
| return parse_flags(statestr, ct_flags) | |
| def convert_mac(data): | |
| def to_bytes(mac): | |
| mac_split = mac.split(":") | |
| ret = bytearray([int(i, 16) for i in mac_split]) | |
| return bytes(ret) | |
| mac_str, _, mask_str = data.partition('/') | |
| if not mac_str: | |
| mac_str = mask_str = "00:00:00:00:00:00" | |
| elif not mask_str: | |
| mask_str = "FF:FF:FF:FF:FF:FF" | |
| return to_bytes(mac_str), to_bytes(mask_str) | |
| def convert_ipv4(data): | |
| ip, _, mask = data.partition('/') | |
| if not ip: | |
| ip = mask = 0 | |
| elif not mask: | |
| mask = 0xFFFFFFFF | |
| elif mask.isdigit(): | |
| mask = (0xFFFFFFFF << (32 - int(mask))) & 0xFFFFFFFF | |
| return int(ipaddress.IPv4Address(ip)), int(ipaddress.IPv4Address(mask)) | |
| def convert_ipv6(data): | |
| ip, _, mask = data.partition('/') | |
| if not ip: | |
| ip = mask = 0 | |
| elif not mask: | |
| mask = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' | |
| elif mask.isdigit(): | |
| mask = ipaddress.IPv6Network("::/" + mask).hostmask | |
| return ipaddress.IPv6Address(ip).packed, ipaddress.IPv6Address(mask).packed | |
| def convert_int(size): | |
| def convert_int_sized(data): | |
| value, _, mask = data.partition('/') | |
| if not value: | |
| return 0, 0 | |
| elif not mask: | |
| return int(value, 0), pow(2, size) - 1 | |
| else: | |
| return int(value, 0), int(mask, 0) | |
| return convert_int_sized | |
| def parse_starts_block(block_str, scanstr, returnskipped, scanregex=False): | |
| if scanregex: | |
| m = re.search(scanstr, block_str) | |
| if m is None: | |
| if returnskipped: | |
| return block_str | |
| return False | |
| if returnskipped: | |
| block_str = block_str[len(m.group(0)) :] | |
| return block_str | |
| return True | |
| if block_str.startswith(scanstr): | |
| if returnskipped: | |
| block_str = block_str[len(scanstr) :] | |
| else: | |
| return True | |
| if returnskipped: | |
| return block_str | |
| return False | |
| def parse_extract_field( | |
| block_str, fieldstr, scanfmt, convert, masked=False, defval=None | |
| ): | |
| if fieldstr and not block_str.startswith(fieldstr): | |
| return block_str, defval | |
| if fieldstr: | |
| str_skiplen = len(fieldstr) | |
| str_skipped = block_str[str_skiplen:] | |
| if str_skiplen == 0: | |
| return str_skipped, defval | |
| else: | |
| str_skiplen = 0 | |
| str_skipped = block_str | |
| m = re.search(scanfmt, str_skipped) | |
| if m is None: | |
| raise ValueError("Bad fmt string") | |
| data = m.group(0) | |
| if convert: | |
| data = convert(m.group(0)) | |
| str_skipped = str_skipped[len(m.group(0)) :] | |
| if masked: | |
| if str_skipped[0] == "/": | |
| raise ValueError("Masking support TBD...") | |
| str_skipped = str_skipped[strspn(str_skipped, ", ") :] | |
| return str_skipped, data | |
| def parse_attrs(actstr, attr_desc): | |
| """Parses the given action string and returns a list of netlink | |
| attributes based on a list of attribute descriptions. | |
| Each element in the attribute description list is a tuple such as: | |
| (name, attr_name, parse_func) | |
| where: | |
| name: is the string representing the attribute | |
| attr_name: is the name of the attribute as defined in the uAPI. | |
| parse_func: is a callable accepting a string and returning either | |
| a single object (the parsed attribute value) or a tuple of | |
| two values (the parsed attribute value and the remaining string) | |
| Returns a list of attributes and the remaining string. | |
| """ | |
| def parse_attr(actstr, key, func): | |
| actstr = actstr[len(key) :] | |
| if not func: | |
| return None, actstr | |
| delim = actstr[0] | |
| actstr = actstr[1:] | |
| if delim == "=": | |
| pos = strcspn(actstr, ",)") | |
| ret = func(actstr[:pos]) | |
| else: | |
| ret = func(actstr) | |
| if isinstance(ret, tuple): | |
| (datum, actstr) = ret | |
| else: | |
| datum = ret | |
| actstr = actstr[strcspn(actstr, ",)"):] | |
| if delim == "(": | |
| if not actstr or actstr[0] != ")": | |
| raise ValueError("Action contains unbalanced parentheses") | |
| actstr = actstr[1:] | |
| actstr = actstr[strspn(actstr, ", ") :] | |
| return datum, actstr | |
| attrs = [] | |
| attr_desc = list(attr_desc) | |
| while actstr and actstr[0] != ")" and attr_desc: | |
| found = False | |
| for i, (key, attr, func) in enumerate(attr_desc): | |
| if actstr.startswith(key): | |
| datum, actstr = parse_attr(actstr, key, func) | |
| attrs.append([attr, datum]) | |
| found = True | |
| del attr_desc[i] | |
| if not found: | |
| raise ValueError("Unknown attribute: '%s'" % actstr) | |
| actstr = actstr[strspn(actstr, ", ") :] | |
| if actstr[0] != ")": | |
| raise ValueError("Action string contains extra garbage or has " | |
| "unbalanced parenthesis: '%s'" % actstr) | |
| return attrs, actstr[1:] | |
| 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", "ovskey"), | |
| ("OVS_ACTION_ATTR_PUSH_VLAN", "push_vlan"), | |
| ("OVS_ACTION_ATTR_POP_VLAN", "flag"), | |
| ("OVS_ACTION_ATTR_SAMPLE", "sample"), | |
| ("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", "ovskey"), | |
| ("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", "recursive"), | |
| ("OVS_ACTION_ATTR_CHECK_PKT_LEN", "none"), | |
| ("OVS_ACTION_ATTR_ADD_MPLS", "none"), | |
| ("OVS_ACTION_ATTR_DEC_TTL", "dec_ttl"), | |
| ("OVS_ACTION_ATTR_DROP", "uint32"), | |
| ("OVS_ACTION_ATTR_PSAMPLE", "psample"), | |
| ) | |
| class dec_ttl(nla): # pylint: disable=invalid-name | |
| """Nested OVS_DEC_TTL_ATTR_* sub-attributes.""" | |
| nla_flags = NLA_F_NESTED | |
| nla_map = ( | |
| ("OVS_DEC_TTL_ATTR_UNSPEC", "none"), | |
| ("OVS_DEC_TTL_ATTR_ACTION", "actions"), | |
| ) | |
| class psample(nla): | |
| nla_flags = NLA_F_NESTED | |
| nla_map = ( | |
| ("OVS_PSAMPLE_ATTR_UNSPEC", "none"), | |
| ("OVS_PSAMPLE_ATTR_GROUP", "uint32"), | |
| ("OVS_PSAMPLE_ATTR_COOKIE", "array(uint8)"), | |
| ) | |
| def dpstr(self, more=False): | |
| args = "group=%d" % self.get_attr("OVS_PSAMPLE_ATTR_GROUP") | |
| cookie = self.get_attr("OVS_PSAMPLE_ATTR_COOKIE") | |
| if cookie: | |
| args += ",cookie(%s)" % \ | |
| "".join(format(x, "02x") for x in cookie) | |
| return "psample(%s)" % args | |
| def parse(self, actstr): | |
| desc = ( | |
| ("group", "OVS_PSAMPLE_ATTR_GROUP", int), | |
| ("cookie", "OVS_PSAMPLE_ATTR_COOKIE", | |
| lambda x: list(bytearray.fromhex(x))) | |
| ) | |
| attrs, actstr = parse_attrs(actstr, desc) | |
| for attr in attrs: | |
| self["attrs"].append(attr) | |
| return actstr | |
| class push_vlan(nla): | |
| fields = (("vlan_tpid", "!H"), ("vlan_tci", "!H")) | |
| class sample(nla): | |
| nla_flags = NLA_F_NESTED | |
| nla_map = ( | |
| ("OVS_SAMPLE_ATTR_UNSPEC", "none"), | |
| ("OVS_SAMPLE_ATTR_PROBABILITY", "uint32"), | |
| ("OVS_SAMPLE_ATTR_ACTIONS", "ovsactions"), | |
| ) | |
| def dpstr(self, more=False): | |
| args = [] | |
| args.append("sample={:.2f}%".format( | |
| 100 * self.get_attr("OVS_SAMPLE_ATTR_PROBABILITY") / | |
| UINT32_MAX)) | |
| actions = self.get_attr("OVS_SAMPLE_ATTR_ACTIONS") | |
| if actions: | |
| args.append("actions(%s)" % actions.dpstr(more)) | |
| return "sample(%s)" % ",".join(args) | |
| def parse(self, actstr): | |
| def parse_nested_actions(actstr): | |
| subacts = ovsactions() | |
| parsed_len = subacts.parse(actstr) | |
| return subacts, actstr[parsed_len :] | |
| def percent_to_rate(percent): | |
| percent = float(percent.strip('%')) | |
| return int(math.floor(UINT32_MAX * (percent / 100.0) + .5)) | |
| desc = ( | |
| ("sample", "OVS_SAMPLE_ATTR_PROBABILITY", percent_to_rate), | |
| ("actions", "OVS_SAMPLE_ATTR_ACTIONS", parse_nested_actions), | |
| ) | |
| attrs, actstr = parse_attrs(actstr, desc) | |
| for attr in attrs: | |
| self["attrs"].append(attr) | |
| return actstr | |
| 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_EGRESS_TUN_PORT") is not None: | |
| print_str += "egress_tun_port=%d" % self.get_attr( | |
| "OVS_USERSPACE_ATTR_EGRESS_TUN_PORT" | |
| ) | |
| print_str += ")" | |
| return print_str | |
| def parse(self, actstr): | |
| attrs_desc = ( | |
| ("pid", "OVS_USERSPACE_ATTR_PID", int), | |
| ("userdata", "OVS_USERSPACE_ATTR_USERDATA", | |
| lambda x: list(bytearray.fromhex(x))), | |
| ("egress_tun_port", "OVS_USERSPACE_ATTR_EGRESS_TUN_PORT", int) | |
| ) | |
| attrs, actstr = parse_attrs(actstr, attrs_desc) | |
| for attr in attrs: | |
| self["attrs"].append(attr) | |
| return actstr | |
| def dpstr(self, more=False): | |
| print_str = "" | |
| for field in self["attrs"]: | |
| if field[1] == "none" or self.get_attr(field[0]) is None: | |
| continue | |
| if print_str != "": | |
| print_str += "," | |
| 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[0] == "OVS_ACTION_ATTR_DROP": | |
| print_str += "drop(%d)" % int(self.get_attr(field[0])) | |
| elif 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_DEC_TTL": | |
| datum = self.get_attr(field[0]) | |
| print_str += "dec_ttl(le_1(" | |
| subacts = datum.get_attr("OVS_DEC_TTL_ATTR_ACTION") | |
| if subacts and subacts.get("attrs"): | |
| print_str += subacts.dpstr(more) | |
| print_str += "))" | |
| elif field[0] == "OVS_ACTION_ATTR_PUSH_VLAN": | |
| datum = self.get_attr(field[0]) | |
| tpid = datum["vlan_tpid"] | |
| tci = datum["vlan_tci"] | |
| vid = tci & 0x0FFF | |
| pcp = (tci >> 13) & 0x7 | |
| print_str += "push_vlan(vid=%d,pcp=%d" \ | |
| ",tpid=0x%04x)" % (vid, pcp, tpid) | |
| 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]) | |
| if field[0] == "OVS_ACTION_ATTR_CLONE": | |
| print_str += "clone(" | |
| print_str += datum.dpstr(more) | |
| print_str += ")" | |
| elif field[0] == "OVS_ACTION_ATTR_SET" or \ | |
| field[0] == "OVS_ACTION_ATTR_SET_MASKED": | |
| print_str += "set" | |
| field = datum | |
| mask = None | |
| if field[0] == "OVS_ACTION_ATTR_SET_MASKED": | |
| print_str += "_masked" | |
| field = datum[0] | |
| mask = datum[1] | |
| print_str += "(" | |
| print_str += field.dpstr(mask, more) | |
| print_str += ")" | |
| else: | |
| try: | |
| print_str += datum.dpstr(more) | |
| except: | |
| print_str += "{ATTR: %s not decoded}" % field[0] | |
| return print_str | |
| def parse(self, actstr): | |
| totallen = len(actstr) | |
| while len(actstr) != 0: | |
| parsed = False | |
| parencount = 0 | |
| if actstr.startswith("drop"): | |
| # If no reason is provided, the implicit drop is used (i.e no | |
| # action). If some reason is given, an explicit action is used. | |
| reason = None | |
| if actstr.startswith("drop("): | |
| parencount += 1 | |
| actstr, reason = parse_extract_field( | |
| actstr, | |
| "drop(", | |
| r"([0-9]+)", | |
| lambda x: int(x, 0), | |
| False, | |
| None, | |
| ) | |
| if reason is not None: | |
| self["attrs"].append(["OVS_ACTION_ATTR_DROP", reason]) | |
| parsed = True | |
| else: | |
| actstr = actstr[len("drop"): ] | |
| return (totallen - len(actstr)) | |
| elif parse_starts_block(actstr, r"^(\d+)", False, True): | |
| actstr, output = parse_extract_field( | |
| actstr, None, r"(\d+)", lambda x: int(x), False, "0" | |
| ) | |
| self["attrs"].append(["OVS_ACTION_ATTR_OUTPUT", output]) | |
| parsed = True | |
| elif parse_starts_block(actstr, "recirc(", False): | |
| actstr, recircid = parse_extract_field( | |
| actstr, | |
| "recirc(", | |
| r"([0-9a-fA-Fx]+)", | |
| lambda x: int(x, 0), | |
| False, | |
| 0, | |
| ) | |
| parencount += 1 | |
| self["attrs"].append(["OVS_ACTION_ATTR_RECIRC", recircid]) | |
| parsed = True | |
| parse_flat_map = ( | |
| ("ct_clear", "OVS_ACTION_ATTR_CT_CLEAR"), | |
| ("pop_vlan", "OVS_ACTION_ATTR_POP_VLAN"), | |
| ("pop_eth", "OVS_ACTION_ATTR_POP_ETH"), | |
| ("pop_nsh", "OVS_ACTION_ATTR_POP_NSH"), | |
| ) | |
| for flat_act in parse_flat_map: | |
| if parse_starts_block(actstr, flat_act[0], False): | |
| actstr = actstr[len(flat_act[0]):] | |
| self["attrs"].append([flat_act[1], True]) | |
| actstr = actstr[strspn(actstr, ", ") :] | |
| parsed = True | |
| if parse_starts_block(actstr, "push_vlan(", False): | |
| actstr = actstr[len("push_vlan("):] | |
| vid = 0 | |
| pcp = 0 | |
| tpid = 0x8100 | |
| if ")" not in actstr: | |
| raise ValueError( | |
| "push_vlan(): missing ')'") | |
| paren = actstr.index(")") | |
| if not actstr[:paren].strip(): | |
| raise ValueError("push_vlan(): no fields") | |
| for kv in actstr[:paren].split(","): | |
| if "=" not in kv: | |
| raise ValueError( | |
| "push_vlan(): bad field '%s'" | |
| % kv.strip()) | |
| k = kv[:kv.index("=")].strip() | |
| v = kv[kv.index("=") + 1:].strip() | |
| if k == "vid": | |
| vid = int(v, 0) | |
| if vid < 0 or vid > 0xFFF: | |
| raise ValueError( | |
| "push_vlan(): vid=%d out of " | |
| "range (0-4095)" % vid) | |
| elif k == "pcp": | |
| pcp = int(v, 0) | |
| if pcp < 0 or pcp > 7: | |
| raise ValueError( | |
| "push_vlan(): pcp=%d out of " | |
| "range (0-7)" % pcp) | |
| elif k == "tpid": | |
| tpid = int(v, 0) | |
| if tpid < 0 or tpid > 0xFFFF: | |
| raise ValueError( | |
| "push_vlan(): tpid=0x%x out " | |
| "of range (0-0xffff)" % tpid) | |
| else: | |
| raise ValueError( | |
| "push_vlan(): unknown key '%s'" | |
| % k) | |
| tci = (vid & 0x0FFF) | ((pcp & 0x7) << 13) \ | |
| | 0x1000 | |
| pvact = self.push_vlan() | |
| pvact["vlan_tpid"] = tpid | |
| pvact["vlan_tci"] = tci | |
| self["attrs"].append( | |
| ["OVS_ACTION_ATTR_PUSH_VLAN", pvact]) | |
| actstr = actstr[paren + 1:] | |
| parsed = True | |
| elif parse_starts_block(actstr, "dec_ttl(le_1(", False): | |
| parencount += 2 | |
| subacts = ovsactions() | |
| actstr = actstr[len("dec_ttl(le_1("):] | |
| parsed_len = subacts.parse(actstr) | |
| decttl = ovsactions.dec_ttl() | |
| decttl["attrs"].append( | |
| ("OVS_DEC_TTL_ATTR_ACTION", subacts) | |
| ) | |
| self["attrs"].append( | |
| ("OVS_ACTION_ATTR_DEC_TTL", decttl) | |
| ) | |
| actstr = actstr[parsed_len:] | |
| parsed = True | |
| elif parse_starts_block(actstr, "clone(", False): | |
| parencount += 1 | |
| subacts = ovsactions() | |
| actstr = actstr[len("clone("):] | |
| parsedLen = subacts.parse(actstr) | |
| lst = [] | |
| self["attrs"].append(("OVS_ACTION_ATTR_CLONE", subacts)) | |
| actstr = actstr[parsedLen:] | |
| parsed = True | |
| elif parse_starts_block(actstr, "set(", False): | |
| parencount += 1 | |
| k = ovskey() | |
| actstr = actstr[len("set("):] | |
| actstr = k.parse(actstr, None) | |
| self["attrs"].append(("OVS_ACTION_ATTR_SET", k)) | |
| if not actstr.startswith(")"): | |
| actstr = ")" + actstr | |
| parsed = True | |
| elif parse_starts_block(actstr, "set_masked(", False): | |
| parencount += 1 | |
| k = ovskey() | |
| m = ovskey() | |
| actstr = actstr[len("set_masked("):] | |
| actstr = k.parse(actstr, m) | |
| self["attrs"].append(("OVS_ACTION_ATTR_SET_MASKED", [k, m])) | |
| if not actstr.startswith(")"): | |
| actstr = ")" + actstr | |
| parsed = True | |
| elif parse_starts_block(actstr, "ct(", False): | |
| parencount += 1 | |
| actstr = actstr[len("ct(") :] | |
| ctact = ovsactions.ctact() | |
| for scan in ( | |
| ("commit", "OVS_CT_ATTR_COMMIT", None), | |
| ("force_commit", "OVS_CT_ATTR_FORCE_COMMIT", None), | |
| ("zone", "OVS_CT_ATTR_ZONE", int), | |
| ("mark", "OVS_CT_ATTR_MARK", int), | |
| ("helper", "OVS_CT_ATTR_HELPER", lambda x, y: str(x)), | |
| ("timeout", "OVS_CT_ATTR_TIMEOUT", lambda x, y: str(x)), | |
| ): | |
| if actstr.startswith(scan[0]): | |
| actstr = actstr[len(scan[0]) :] | |
| if scan[2] is not None: | |
| if actstr[0] != "=": | |
| raise ValueError("Invalid ct attr") | |
| actstr = actstr[1:] | |
| pos = strcspn(actstr, ",)") | |
| datum = scan[2](actstr[:pos], 0) | |
| ctact["attrs"].append([scan[1], datum]) | |
| actstr = actstr[pos:] | |
| else: | |
| ctact["attrs"].append([scan[1], None]) | |
| actstr = actstr[strspn(actstr, ", ") :] | |
| # it seems strange to put this here, but nat() is a complex | |
| # sub-action and this lets it sit anywhere in the ct() action | |
| if actstr.startswith("nat"): | |
| actstr = actstr[3:] | |
| natact = ovsactions.ctact.natattr() | |
| if actstr.startswith("("): | |
| parencount += 1 | |
| t = None | |
| actstr = actstr[1:] | |
| if actstr.startswith("src"): | |
| t = "OVS_NAT_ATTR_SRC" | |
| actstr = actstr[3:] | |
| elif actstr.startswith("dst"): | |
| t = "OVS_NAT_ATTR_DST" | |
| actstr = actstr[3:] | |
| actstr, ip_block_min = parse_extract_field( | |
| actstr, "=", r"([0-9a-fA-F\.]+)", str, False | |
| ) | |
| actstr, ip_block_max = parse_extract_field( | |
| actstr, "-", r"([0-9a-fA-F\.]+)", str, False | |
| ) | |
| actstr, proto_min = parse_extract_field( | |
| actstr, ":", r"(\d+)", int, False | |
| ) | |
| actstr, proto_max = parse_extract_field( | |
| actstr, "-", r"(\d+)", int, False | |
| ) | |
| if t is not None: | |
| natact["attrs"].append([t, None]) | |
| if ip_block_min is not None: | |
| natact["attrs"].append( | |
| ["OVS_NAT_ATTR_IP_MIN", ip_block_min] | |
| ) | |
| if ip_block_max is not None: | |
| natact["attrs"].append( | |
| ["OVS_NAT_ATTR_IP_MAX", ip_block_max] | |
| ) | |
| if proto_min is not None: | |
| natact["attrs"].append( | |
| ["OVS_NAT_ATTR_PROTO_MIN", proto_min] | |
| ) | |
| if proto_max is not None: | |
| natact["attrs"].append( | |
| ["OVS_NAT_ATTR_PROTO_MAX", proto_max] | |
| ) | |
| for natscan in ( | |
| ("persistent", "OVS_NAT_ATTR_PERSISTENT"), | |
| ("hash", "OVS_NAT_ATTR_PROTO_HASH"), | |
| ("random", "OVS_NAT_ATTR_PROTO_RANDOM"), | |
| ): | |
| if actstr.startswith(natscan[0]): | |
| actstr = actstr[len(natscan[0]) :] | |
| natact["attrs"].append([natscan[1], None]) | |
| actstr = actstr[strspn(actstr, ", ") :] | |
| ctact["attrs"].append(["OVS_CT_ATTR_NAT", natact]) | |
| actstr = actstr[strspn(actstr, ", ") :] | |
| self["attrs"].append(["OVS_ACTION_ATTR_CT", ctact]) | |
| parsed = True | |
| elif parse_starts_block(actstr, "sample(", False): | |
| sampleact = self.sample() | |
| actstr = sampleact.parse(actstr[len("sample(") : ]) | |
| self["attrs"].append(["OVS_ACTION_ATTR_SAMPLE", sampleact]) | |
| parsed = True | |
| elif parse_starts_block(actstr, "psample(", False): | |
| psampleact = self.psample() | |
| actstr = psampleact.parse(actstr[len("psample(") : ]) | |
| self["attrs"].append(["OVS_ACTION_ATTR_PSAMPLE", psampleact]) | |
| parsed = True | |
| elif parse_starts_block(actstr, "userspace(", False): | |
| uact = self.userspace() | |
| actstr = uact.parse(actstr[len("userspace(") : ]) | |
| self["attrs"].append(["OVS_ACTION_ATTR_USERSPACE", uact]) | |
| parsed = True | |
| elif parse_starts_block(actstr, "trunc(", False): | |
| parencount += 1 | |
| actstr, val = parse_extract_field( | |
| actstr, | |
| "trunc(", | |
| r"([0-9]+)", | |
| int, | |
| False, | |
| None, | |
| ) | |
| self["attrs"].append(["OVS_ACTION_ATTR_TRUNC", val]) | |
| parsed = True | |
| actstr = actstr[strspn(actstr, ", ") :] | |
| while parencount > 0: | |
| parencount -= 1 | |
| actstr = actstr[strspn(actstr, " "):] | |
| if len(actstr) and actstr[0] != ")": | |
| raise ValueError("Action str: '%s' unbalanced" % actstr) | |
| actstr = actstr[1:] | |
| if len(actstr) and actstr[0] == ")": | |
| return (totallen - len(actstr)) | |
| actstr = actstr[strspn(actstr, ", ") :] | |
| if not parsed: | |
| raise ValueError("Action str: '%s' not supported" % actstr) | |
| return (totallen - len(actstr)) | |
| # pyroute2 resolves nla_map types via getattr(self, name). | |
| # dec_ttl needs "actions" to resolve to ovsactions, but | |
| # ovsactions is not defined when dec_ttl class body runs. | |
| ovsactions.dec_ttl.actions = ovsactions | |
| class ovskey(nla): | |
| nla_flags = NLA_F_NESTED | |
| nla_map = ( | |
| ("OVS_KEY_ATTR_UNSPEC", "none"), | |
| ("OVS_KEY_ATTR_ENCAP", "encap_ovskey"), | |
| ("OVS_KEY_ATTR_PRIORITY", "uint32"), | |
| ("OVS_KEY_ATTR_IN_PORT", "uint32"), | |
| ("OVS_KEY_ATTR_ETHERNET", "ethaddr"), | |
| ("OVS_KEY_ATTR_VLAN", "be16"), | |
| ("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", "ovs_key_tunnel"), | |
| ("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 else 0, | |
| convert_int(16)), | |
| ("dst", "dst", "%d", lambda x: int(x) if x else 0, | |
| convert_int(16)), | |
| ) | |
| 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 parse(self, flowstr, typeInst): | |
| if not flowstr.startswith(self.proto_str): | |
| return None, None | |
| k = typeInst() | |
| m = typeInst() | |
| flowstr = flowstr[len(self.proto_str) :] | |
| if flowstr.startswith("("): | |
| flowstr = flowstr[1:] | |
| keybits = b"" | |
| maskbits = b"" | |
| for f in self.fields_map: | |
| if flowstr.startswith(f[1]): | |
| # the following assumes that the field looks | |
| # something like 'field.' where '.' is a | |
| # character that we don't exactly care about. | |
| flowstr = flowstr[len(f[1]) + 1 :] | |
| splitchar = 0 | |
| for c in flowstr: | |
| if c == "," or c == ")": | |
| break | |
| splitchar += 1 | |
| data = flowstr[:splitchar] | |
| flowstr = flowstr[splitchar:] | |
| else: | |
| data = "" | |
| if len(f) > 4: | |
| k[f[0]], m[f[0]] = f[4](data) | |
| else: | |
| k[f[0]] = f[3](data) | |
| m[f[0]] = f[3](data) | |
| flowstr = flowstr[strspn(flowstr, ", ") :] | |
| if len(flowstr) == 0: | |
| return flowstr, k, m | |
| flowstr = flowstr[strspn(flowstr, "), ") :] | |
| return flowstr, k, m | |
| 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 else 0, | |
| convert_int(8)), | |
| ("tos", "tos", "%d", lambda x: int(x) if x else 0, | |
| convert_int(8)), | |
| ("ttl", "ttl", "%d", lambda x: int(x) if x else 0, | |
| convert_int(8)), | |
| ("frag", "frag", "%d", lambda x: int(x) if x else 0, | |
| convert_int(8)), | |
| ) | |
| 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: ipaddress.IPv6Address(x).packed if x else 0, | |
| convert_ipv6, | |
| ), | |
| ( | |
| "dst", | |
| "dst", | |
| lambda x: str(ipaddress.IPv6Address(x)), | |
| lambda x: ipaddress.IPv6Address(x).packed if x else 0, | |
| convert_ipv6, | |
| ), | |
| ("label", "label", "%d", lambda x: int(x) if x else 0, | |
| convert_int(20)), | |
| ("proto", "proto", "%d", lambda x: int(x) if x else 0, | |
| convert_int(8)), | |
| ("tclass", "tclass", "%d", lambda x: int(x) if x else 0, | |
| convert_int(8)), | |
| ("hlimit", "hlimit", "%d", lambda x: int(x) if x else 0, | |
| convert_int(8)), | |
| ("frag", "frag", "%d", lambda x: int(x) if x else 0, | |
| convert_int(8)), | |
| ) | |
| 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", lambda x: int(x) if x else 0, | |
| convert_int(8)), | |
| ("code", "code", "%d", lambda x: int(x) if x else 0, | |
| convert_int(8)), | |
| ) | |
| 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 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)), | |
| convert_ipv6, | |
| ), | |
| ("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, | |
| convert_ipv4, | |
| ), | |
| ( | |
| "dst", | |
| "dst", | |
| lambda x: str(ipaddress.IPv4Address(x)), | |
| int, | |
| convert_ipv4, | |
| ), | |
| ("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)), | |
| convert_ipv6, | |
| ), | |
| ( | |
| "dst", | |
| "dst", | |
| lambda x: str(ipaddress.IPv6Address(x)), | |
| convert_ipv6, | |
| ), | |
| ("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_tunnel(nla): | |
| nla_flags = NLA_F_NESTED | |
| nla_map = ( | |
| ("OVS_TUNNEL_KEY_ATTR_ID", "be64"), | |
| ("OVS_TUNNEL_KEY_ATTR_IPV4_SRC", "ipaddr"), | |
| ("OVS_TUNNEL_KEY_ATTR_IPV4_DST", "ipaddr"), | |
| ("OVS_TUNNEL_KEY_ATTR_TOS", "uint8"), | |
| ("OVS_TUNNEL_KEY_ATTR_TTL", "uint8"), | |
| ("OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT", "flag"), | |
| ("OVS_TUNNEL_KEY_ATTR_CSUM", "flag"), | |
| ("OVS_TUNNEL_KEY_ATTR_OAM", "flag"), | |
| ("OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS", "array(uint32)"), | |
| ("OVS_TUNNEL_KEY_ATTR_TP_SRC", "be16"), | |
| ("OVS_TUNNEL_KEY_ATTR_TP_DST", "be16"), | |
| ("OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS", "none"), | |
| ("OVS_TUNNEL_KEY_ATTR_IPV6_SRC", "ipaddr"), | |
| ("OVS_TUNNEL_KEY_ATTR_IPV6_DST", "ipaddr"), | |
| ("OVS_TUNNEL_KEY_ATTR_PAD", "none"), | |
| ("OVS_TUNNEL_KEY_ATTR_ERSPAN_OPTS", "none"), | |
| ("OVS_TUNNEL_KEY_ATTR_IPV4_INFO_BRIDGE", "flag"), | |
| ) | |
| def parse(self, flowstr, mask=None): | |
| if not flowstr.startswith("tunnel("): | |
| return None, None | |
| k = ovskey.ovs_key_tunnel() | |
| if mask is not None: | |
| mask = ovskey.ovs_key_tunnel() | |
| flowstr = flowstr[len("tunnel("):] | |
| v6_address = None | |
| fields = [ | |
| ("tun_id=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_ID", | |
| 0xffffffffffffffff, None, None), | |
| ("src=", r"([0-9a-fA-F\.]+)", str, | |
| "OVS_TUNNEL_KEY_ATTR_IPV4_SRC", "255.255.255.255", "0.0.0.0", | |
| False), | |
| ("dst=", r"([0-9a-fA-F\.]+)", str, | |
| "OVS_TUNNEL_KEY_ATTR_IPV4_DST", "255.255.255.255", "0.0.0.0", | |
| False), | |
| ("ipv6_src=", r"([0-9a-fA-F:]+)", str, | |
| "OVS_TUNNEL_KEY_ATTR_IPV6_SRC", | |
| "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::", True), | |
| ("ipv6_dst=", r"([0-9a-fA-F:]+)", str, | |
| "OVS_TUNNEL_KEY_ATTR_IPV6_DST", | |
| "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::", True), | |
| ("tos=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TOS", 255, 0, | |
| None), | |
| ("ttl=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TTL", 255, 0, | |
| None), | |
| ("tp_src=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TP_SRC", | |
| 65535, 0, None), | |
| ("tp_dst=", r"(\d+)", int, "OVS_TUNNEL_KEY_ATTR_TP_DST", | |
| 65535, 0, None), | |
| ] | |
| forced_include = ["OVS_TUNNEL_KEY_ATTR_TTL"] | |
| for prefix, regex, typ, attr_name, mask_val, default_val, v46_flag in fields: | |
| flowstr, value = parse_extract_field(flowstr, prefix, regex, typ, False) | |
| if not attr_name: | |
| raise Exception("Bad list value in tunnel fields") | |
| if value is None and attr_name in forced_include: | |
| value = default_val | |
| mask_val = default_val | |
| if value is not None: | |
| if v46_flag is not None: | |
| if v6_address is None: | |
| v6_address = v46_flag | |
| if v46_flag != v6_address: | |
| raise ValueError("Cannot mix v6 and v4 addresses") | |
| k["attrs"].append([attr_name, value]) | |
| if mask is not None: | |
| mask["attrs"].append([attr_name, mask_val]) | |
| else: | |
| if v46_flag is not None: | |
| if v6_address is None or v46_flag != v6_address: | |
| continue | |
| if mask is not None: | |
| mask["attrs"].append([attr_name, default_val]) | |
| if k["attrs"][0][0] != "OVS_TUNNEL_KEY_ATTR_ID": | |
| raise ValueError("Needs a tunid set") | |
| if flowstr.startswith("flags("): | |
| flowstr = flowstr[len("flags("):] | |
| flagspos = flowstr.find(")") | |
| flags = flowstr[:flagspos] | |
| flowstr = flowstr[flagspos + 1:] | |
| flag_attrs = { | |
| "df": "OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT", | |
| "csum": "OVS_TUNNEL_KEY_ATTR_CSUM", | |
| "oam": "OVS_TUNNEL_KEY_ATTR_OAM" | |
| } | |
| for flag in flags.split("|"): | |
| if flag in flag_attrs: | |
| k["attrs"].append([flag_attrs[flag], True]) | |
| if mask is not None: | |
| mask["attrs"].append([flag_attrs[flag], True]) | |
| flowstr = flowstr[strspn(flowstr, ", ") :] | |
| return flowstr, k, mask | |
| def dpstr(self, mask=None, more=False): | |
| print_str = "tunnel(" | |
| flagsattrs = [] | |
| for k in self["attrs"]: | |
| noprint = False | |
| if k[0] == "OVS_TUNNEL_KEY_ATTR_ID": | |
| print_str += "tun_id=%d" % k[1] | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV4_SRC": | |
| print_str += "src=%s" % k[1] | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV4_DST": | |
| print_str += "dst=%s" % k[1] | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV6_SRC": | |
| print_str += "ipv6_src=%s" % k[1] | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_IPV6_DST": | |
| print_str += "ipv6_dst=%s" % k[1] | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_TOS": | |
| print_str += "tos=%d" % k[1] | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_TTL": | |
| print_str += "ttl=%d" % k[1] | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_TP_SRC": | |
| print_str += "tp_src=%d" % k[1] | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_TP_DST": | |
| print_str += "tp_dst=%d" % k[1] | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT": | |
| noprint = True | |
| flagsattrs.append("df") | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_CSUM": | |
| noprint = True | |
| flagsattrs.append("csum") | |
| elif k[0] == "OVS_TUNNEL_KEY_ATTR_OAM": | |
| noprint = True | |
| flagsattrs.append("oam") | |
| if not noprint: | |
| print_str += "," | |
| if len(flagsattrs): | |
| print_str += "flags(" + "|".join(flagsattrs) + ")" | |
| print_str += ")" | |
| return print_str | |
| class ovs_key_mpls(nla): | |
| fields = (("lse", ">I"),) | |
| # 802.1Q CFI (Canonical Format Indicator) bit, always set for Ethernet | |
| _VLAN_CFI_MASK = 0x1000 | |
| @staticmethod | |
| def _vlan_dpstr(tci): | |
| """Format VLAN TCI as vid=X,pcp=Y,cfi=Z or tci=0xNNNN. | |
| When cfi=1 (standard Ethernet VLAN), outputs decomposed | |
| vid/pcp/cfi fields. When cfi=0 (truncated VLAN header), | |
| falls back to raw tci=0x%04x to ensure round-trip | |
| correctness: the parser auto-adds cfi=1 for vid/pcp | |
| format, so cfi=0 would be lost on re-parse.""" | |
| vid = tci & 0x0FFF | |
| pcp = (tci >> 13) & 0x7 | |
| cfi = (tci >> 12) & 0x1 | |
| if cfi: | |
| return "vid=%d,pcp=%d,cfi=%d" % (vid, pcp, cfi) | |
| return "tci=0x%04x" % tci | |
| @staticmethod | |
| def _parse_vlan_from_flowstr(flowstr): | |
| """Parse vlan(tci=X) or vlan(vid=X[,pcp=Y,cfi=Z]) from flowstr. | |
| Returns (remaining_flowstr, key_tci, mask_tci). | |
| TCI values use standard bit layout (VID bits 0-11, | |
| CFI bit 12, PCP bits 13-15); byte order conversion to | |
| big-endian happens in pyroute2 be16 NLA serialization. | |
| The mask covers only the fields the caller specified: | |
| vid -> 0x0FFF, pcp -> 0xE000, cfi -> 0x1000, tci -> 0xFFFF. | |
| The tci= key sets the raw TCI bitfield (no CFI validation) to allow | |
| non-Ethernet use cases. Use cfi=1 for standard Ethernet VLAN matching. | |
| """ | |
| tci = 0 | |
| mask = 0 | |
| has_tci = False | |
| has_vid = has_pcp = has_cfi = False | |
| _tci_mix_err = "vlan(): 'tci' cannot be mixed " \ | |
| "with 'vid'/'pcp'/'cfi'" | |
| first = True | |
| while True: | |
| flowstr = flowstr.lstrip() | |
| if not flowstr: | |
| raise ValueError("vlan(): missing ')'") | |
| if flowstr[0] == ')': | |
| break | |
| if not first: | |
| flowstr = flowstr[1:] # skip ',' | |
| if not flowstr: | |
| raise ValueError("vlan(): missing ')' after trailing comma") | |
| flowstr = flowstr.lstrip() | |
| if flowstr and flowstr[0] == ')': | |
| break | |
| if flowstr and flowstr[0] == ',': | |
| raise ValueError( | |
| "vlan(): empty or extra comma in field list") | |
| first = False | |
| eq = flowstr.find('=') | |
| if eq == -1: | |
| raise ValueError( | |
| "vlan(): expected key=value, got '%s'" % flowstr) | |
| key = flowstr[:eq].strip() | |
| flowstr = flowstr[eq + 1:] | |
| end = flowstr.find(',') | |
| end2 = flowstr.find(')') | |
| if end == -1 and end2 == -1: | |
| raise ValueError("vlan(): missing ')'") | |
| if end == -1 or (end2 != -1 and end2 < end): | |
| end = end2 | |
| val = flowstr[:end].strip() | |
| flowstr = flowstr[end:] | |
| if not val: | |
| raise ValueError("vlan(): empty value for key '%s'" % key) | |
| try: | |
| v = int(val, 0) | |
| except ValueError as exc: | |
| raise ValueError( | |
| "vlan(): invalid value '%s' for key '%s'" | |
| % (val, key)) from exc | |
| if key == 'tci': | |
| if has_tci: | |
| raise ValueError("vlan(): duplicate 'tci'") | |
| if has_vid or has_pcp or has_cfi: | |
| raise ValueError(_tci_mix_err) | |
| if v > 0xFFFF or v < 0: | |
| raise ValueError("vlan(): tci=0x%x out of range" % v) | |
| tci = v | |
| mask = 0xFFFF | |
| has_tci = True | |
| elif key == 'vid': | |
| if has_tci: | |
| raise ValueError(_tci_mix_err) | |
| if has_vid: | |
| raise ValueError("vlan(): duplicate 'vid'") | |
| if v < 0 or v > 0xFFF: | |
| raise ValueError("vlan(): vid=%d out of range (0-4095)" % v) | |
| tci |= v | |
| mask |= 0x0FFF | |
| has_vid = True | |
| elif key == 'pcp': | |
| if has_tci: | |
| raise ValueError(_tci_mix_err) | |
| if has_pcp: | |
| raise ValueError("vlan(): duplicate 'pcp'") | |
| if v < 0 or v > 7: | |
| raise ValueError("vlan(): pcp=%d out of range (0-7)" % v) | |
| tci |= (v & 0x7) << 13 | |
| mask |= 0xE000 | |
| has_pcp = True | |
| elif key == 'cfi': | |
| if has_tci: | |
| raise ValueError(_tci_mix_err) | |
| if has_cfi: | |
| raise ValueError("vlan(): duplicate 'cfi'") | |
| if v != 1: | |
| raise ValueError("vlan(): cfi must be 1 for Ethernet") | |
| tci |= ovskey._VLAN_CFI_MASK | |
| mask |= ovskey._VLAN_CFI_MASK | |
| has_cfi = True | |
| else: | |
| raise ValueError("vlan(): unknown key '%s'" % key) | |
| flowstr = flowstr[1:] # skip ')' | |
| # Catch immediate '))' (user error). A ')' after ',' is consumed | |
| # by parse()'s strspn(flowstr, "), ") inter-field separator stripping. | |
| if flowstr.lstrip().startswith(')'): | |
| raise ValueError("vlan(): unmatched ')'") | |
| # parse() strips trailing ',', ')', ' ' as inter-field separators, | |
| # so we do not need to call strspn here. | |
| if mask == 0: | |
| raise ValueError("vlan(): no fields specified, " | |
| "use vlan(vid=X[,pcp=Y,cfi=Z]) or vlan(tci=X)") | |
| if not has_tci: | |
| tci |= ovskey._VLAN_CFI_MASK | |
| mask |= ovskey._VLAN_CFI_MASK | |
| return flowstr, tci, mask | |
| @staticmethod | |
| def _parse_encap_from_flowstr(flowstr): | |
| """Parse encap(inner_flow) from flowstr. | |
| Returns (remaining_flowstr, inner_key_dict, inner_mask_dict) | |
| where each dict has an 'attrs' key for recursive NLA encoding. | |
| Parenthesis-depth tracking handles nested encap() calls but not | |
| quoted strings containing literal parentheses. | |
| """ | |
| depth = 1 | |
| end = -1 | |
| for i, c in enumerate(flowstr): | |
| if c == '(': | |
| depth += 1 | |
| elif c == ')': | |
| depth -= 1 | |
| if depth < 0: | |
| raise ValueError( | |
| "encap(): unmatched ')' at position %d" % i) | |
| if depth == 0: | |
| end = i | |
| break | |
| if end == -1: | |
| if depth > 1: | |
| raise ValueError("encap(): missing ')' in nested encap") | |
| raise ValueError("encap(): missing ')'") | |
| inner_str = flowstr[:end].strip() | |
| if not inner_str: | |
| raise ValueError("encap(): empty inner flow") | |
| flowstr = flowstr[end + 1:] | |
| if flowstr.lstrip().startswith(')'): | |
| raise ValueError("encap(): unmatched ')' after encap()") | |
| inner_key = encap_ovskey() | |
| inner_mask = encap_ovskey() | |
| remaining = inner_key.parse(inner_str, inner_mask) | |
| if remaining and re.search(r'[^\s,)]', remaining): | |
| raise ValueError( | |
| "encap(): unrecognized trailing " | |
| "content '%s'" % remaining.strip()) | |
| return flowstr, inner_key, inner_mask | |
| def parse(self, flowstr, mask=None): | |
| for field in ( | |
| ("OVS_KEY_ATTR_PRIORITY", "skb_priority", intparse), | |
| ("OVS_KEY_ATTR_SKB_MARK", "skb_mark", intparse), | |
| ("OVS_KEY_ATTR_RECIRC_ID", "recirc_id", intparse), | |
| ("OVS_KEY_ATTR_TUNNEL", "tunnel", ovskey.ovs_key_tunnel), | |
| ("OVS_KEY_ATTR_DP_HASH", "dp_hash", intparse), | |
| ("OVS_KEY_ATTR_CT_STATE", "ct_state", parse_ct_state), | |
| ("OVS_KEY_ATTR_CT_ZONE", "ct_zone", intparse), | |
| ("OVS_KEY_ATTR_CT_MARK", "ct_mark", intparse), | |
| ("OVS_KEY_ATTR_IN_PORT", "in_port", intparse), | |
| ( | |
| "OVS_KEY_ATTR_ETHERNET", | |
| "eth", | |
| ovskey.ethaddr, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_ETHERTYPE", | |
| "eth_type", | |
| lambda x: intparse(x, "0xffff"), | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_VLAN", | |
| "vlan", | |
| ovskey._parse_vlan_from_flowstr, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_ENCAP", | |
| "encap", | |
| ovskey._parse_encap_from_flowstr, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_IPV4", | |
| "ipv4", | |
| ovskey.ovs_key_ipv4, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_IPV6", | |
| "ipv6", | |
| ovskey.ovs_key_ipv6, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_ARP", | |
| "arp", | |
| ovskey.ovs_key_arp, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_TCP", | |
| "tcp", | |
| ovskey.ovs_key_tcp, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_UDP", | |
| "udp", | |
| ovskey.ovs_key_udp, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_SCTP", | |
| "sctp", | |
| ovskey.ovs_key_sctp, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_ICMP", | |
| "icmp", | |
| ovskey.ovs_key_icmp, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_ICMPV6", | |
| "icmpv6", | |
| ovskey.ovs_key_icmpv6, | |
| ), | |
| ( | |
| "OVS_KEY_ATTR_TCP_FLAGS", | |
| "tcp_flags", | |
| lambda x: parse_flags(x, None), | |
| ), | |
| ): | |
| fld = field[1] + "(" | |
| if not flowstr.startswith(fld): | |
| continue | |
| if not isinstance(field[2], types.FunctionType): | |
| nk = field[2]() | |
| flowstr, k, m = nk.parse(flowstr, field[2]) | |
| else: | |
| flowstr = flowstr[len(fld) :] | |
| flowstr, k, m = field[2](flowstr) | |
| if m and mask is not None: | |
| mask["attrs"].append([field[0], m]) | |
| self["attrs"].append([field[0], k]) | |
| flowstr = flowstr[strspn(flowstr, "), ") :] | |
| return flowstr | |
| 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_TUNNEL", | |
| "tunnel", | |
| None, | |
| False, | |
| False, | |
| ), | |
| ( | |
| "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_VLAN", "vlan", ovskey._vlan_dpstr, | |
| lambda x: False, True), | |
| ("OVS_KEY_ATTR_ENCAP", 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]) | |
| fmt = field[2] # str format or callable | |
| if field[4] is False: | |
| print_str += v.dpstr(m, more) | |
| print_str += "," | |
| else: | |
| if m is None or field[3](m): | |
| val = fmt(v) if callable(fmt) else fmt % v | |
| print_str += field[1] + "(" + val + ")," | |
| elif more or m != 0: | |
| if field[0] == "OVS_KEY_ATTR_VLAN": | |
| val = "tci=0x%04x/0x%04x" % (v, m) | |
| elif callable(fmt): | |
| val = fmt(v) + "/" + fmt(m) | |
| else: | |
| val = (fmt % v) + "/" + (fmt % m) | |
| print_str += field[1] + "(" + val + ")," | |
| return print_str | |
| class encap_ovskey(ovskey): | |
| """Inner flow key attributes valid inside 802.1Q ENCAP. | |
| Only L2-L4 key attributes (slots 0-21) appear inside ENCAP. | |
| Metadata-only attributes (SKB_MARK, DP_HASH, RECIRC_ID, etc.) | |
| are set to "none" -- they never appear inside ENCAP per | |
| ovs_nla_put_vlan() in net/openvswitch/flow_netlink.c. | |
| nla_map indexes must match OVS_KEY_ATTR_* enum values in | |
| include/uapi/linux/openvswitch.h. | |
| """ | |
| nla_map = ( | |
| ("OVS_KEY_ATTR_UNSPEC", "none"), | |
| ("OVS_KEY_ATTR_ENCAP", "none"), # placeholder, parsed by ovskey | |
| ("OVS_KEY_ATTR_PRIORITY", "none"), # skb metadata, not in ENCAP | |
| ("OVS_KEY_ATTR_IN_PORT", "none"), # skb metadata, not in ENCAP | |
| ("OVS_KEY_ATTR_ETHERNET", "ethaddr"), | |
| ("OVS_KEY_ATTR_VLAN", "be16"), | |
| ("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", "none"), # metadata, not in ENCAP | |
| ("OVS_KEY_ATTR_TUNNEL", "none"), # tunnel metadata, not in ENCAP | |
| ("OVS_KEY_ATTR_SCTP", "ovs_key_sctp"), | |
| ("OVS_KEY_ATTR_TCP_FLAGS", "be16"), | |
| ("OVS_KEY_ATTR_DP_HASH", "none"), # metadata, not in ENCAP | |
| ("OVS_KEY_ATTR_RECIRC_ID", "none"), # metadata, not in ENCAP | |
| ("OVS_KEY_ATTR_MPLS", "array(ovs_key_mpls)"), | |
| ) | |
| 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("Unknown 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 | |
| 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" | |
| raise ValueError("Unknown vport type:%d" % vport_type) | |
| def str_to_type(vport_type): | |
| if vport_type in ["netdev", "gre", "vxlan", "geneve"]: | |
| return OvsVport.OVS_VPORT_TYPE_NETDEV | |
| elif vport_type == "internal": | |
| return OvsVport.OVS_VPORT_TYPE_INTERNAL | |
| 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, dport): | |
| msg = OvsVport.ovs_vport_msg() | |
| msg["cmd"] = OVS_VPORT_CMD_NEW | |
| msg["version"] = OVS_DATAPATH_VERSION | |
| msg["reserved"] = 0 | |
| msg["dpifindex"] = dpindex | |
| msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) | |
| msg["attrs"].append( | |
| ["OVS_VPORT_ATTR_TYPE", OvsVport.str_to_type(ptype)] | |
| ) | |
| msg["attrs"].append( | |
| ["OVS_VPORT_ATTR_UPCALL_PID", [self.upcall_packet.epid]] | |
| ) | |
| TUNNEL_DEFAULTS = [("geneve", 6081), | |
| ("gre", 0), | |
| ("vxlan", 4789)] | |
| for tnl in TUNNEL_DEFAULTS: | |
| if ptype == tnl[0]: | |
| if not dport: | |
| dport = tnl[1] | |
| ipr = pyroute2.iproute.IPRoute() | |
| if tnl[0] == "geneve": | |
| ipr.link("add", ifname=vport_ifname, kind=tnl[0], | |
| geneve_port=dport, | |
| geneve_collect_metadata=True, | |
| geneve_udp_zero_csum6_rx=1) | |
| elif tnl[0] == "gre": | |
| ipr.link("add", ifname=vport_ifname, kind="gretap", | |
| gre_collect_metadata=True) | |
| elif tnl[0] == "vxlan": | |
| ipr.link("add", ifname=vport_ifname, kind=tnl[0], | |
| vxlan_learning=0, vxlan_collect_metadata=1, | |
| vxlan_udp_zero_csum6_rx=1, vxlan_port=dport) | |
| break | |
| 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 parse(self, flowstr, actstr, dpidx=0): | |
| OVS_UFID_F_OMIT_KEY = 1 << 0 | |
| OVS_UFID_F_OMIT_MASK = 1 << 1 | |
| OVS_UFID_F_OMIT_ACTIONS = 1 << 2 | |
| self["cmd"] = 0 | |
| self["version"] = 0 | |
| self["reserved"] = 0 | |
| self["dpifindex"] = 0 | |
| if flowstr.startswith("ufid:"): | |
| count = 5 | |
| while flowstr[count] != ",": | |
| count += 1 | |
| ufidstr = flowstr[5:count] | |
| flowstr = flowstr[count + 1 :] | |
| else: | |
| ufidstr = str(uuid.uuid4()) | |
| uuidRawObj = uuid.UUID(ufidstr).fields | |
| self["attrs"].append( | |
| [ | |
| "OVS_FLOW_ATTR_UFID", | |
| [ | |
| uuidRawObj[0], | |
| uuidRawObj[1] << 16 | uuidRawObj[2], | |
| uuidRawObj[3] << 24 | |
| | uuidRawObj[4] << 16 | |
| | uuidRawObj[5] & (0xFF << 32) >> 32, | |
| uuidRawObj[5] & (0xFFFFFFFF), | |
| ], | |
| ] | |
| ) | |
| self["attrs"].append( | |
| [ | |
| "OVS_FLOW_ATTR_UFID_FLAGS", | |
| int( | |
| OVS_UFID_F_OMIT_KEY | |
| | OVS_UFID_F_OMIT_MASK | |
| | OVS_UFID_F_OMIT_ACTIONS | |
| ), | |
| ] | |
| ) | |
| k = ovskey() | |
| m = ovskey() | |
| k.parse(flowstr, m) | |
| self["attrs"].append(["OVS_FLOW_ATTR_KEY", k]) | |
| self["attrs"].append(["OVS_FLOW_ATTR_MASK", m]) | |
| if actstr is not None: | |
| a = ovsactions() | |
| a.parse(actstr) | |
| self["attrs"].append(["OVS_FLOW_ATTR_ACTIONS", a]) | |
| def __init__(self): | |
| GenericNetlinkSocket.__init__(self) | |
| self.bind(OVS_FLOW_FAMILY, OvsFlow.ovs_flow_msg) | |
| def add_flow(self, dpifindex, flowmsg): | |
| """ | |
| Send a new flow message to the kernel. | |
| dpifindex should be a valid datapath obtained by calling | |
| into the OvsDatapath lookup | |
| flowmsg is a flow object obtained by calling a dpparse | |
| """ | |
| flowmsg["cmd"] = OVS_FLOW_CMD_NEW | |
| flowmsg["version"] = OVS_DATAPATH_VERSION | |
| flowmsg["reserved"] = 0 | |
| flowmsg["dpifindex"] = dpifindex | |
| try: | |
| reply = self.nlm_request( | |
| flowmsg, | |
| msg_type=self.prid, | |
| msg_flags=NLM_F_REQUEST | NLM_F_ACK, | |
| ) | |
| reply = reply[0] | |
| except NetlinkError as ne: | |
| print(flowmsg) | |
| raise ne | |
| return reply | |
| def mod_flow(self, dpifindex, flowmsg): | |
| """Modify an existing flow in the kernel.""" | |
| flowmsg["cmd"] = OVS_FLOW_CMD_SET | |
| flowmsg["version"] = OVS_DATAPATH_VERSION | |
| flowmsg["reserved"] = 0 | |
| flowmsg["dpifindex"] = dpifindex | |
| try: | |
| reply = self.nlm_request( | |
| flowmsg, | |
| msg_type=self.prid, | |
| msg_flags=NLM_F_REQUEST | NLM_F_ACK, | |
| ) | |
| reply = reply[0] | |
| except NetlinkError as ne: | |
| print(flowmsg) | |
| raise ne | |
| return reply | |
| def del_flows(self, dpifindex): | |
| """ | |
| Send a del message to the kernel that will drop all flows. | |
| dpifindex should be a valid datapath obtained by calling | |
| into the OvsDatapath lookup | |
| """ | |
| flowmsg = OvsFlow.ovs_flow_msg() | |
| flowmsg["cmd"] = OVS_FLOW_CMD_DEL | |
| flowmsg["version"] = OVS_DATAPATH_VERSION | |
| flowmsg["reserved"] = 0 | |
| flowmsg["dpifindex"] = dpifindex | |
| try: | |
| reply = self.nlm_request( | |
| flowmsg, | |
| msg_type=self.prid, | |
| msg_flags=NLM_F_REQUEST | NLM_F_ACK, | |
| ) | |
| reply = reply[0] | |
| except NetlinkError as ne: | |
| print(flowmsg) | |
| raise ne | |
| return reply | |
| 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", flush=True) | |
| def action(self, packetmsg): | |
| print("userspace action command", flush=True) | |
| class psample_sample(genlmsg): | |
| nla_map = ( | |
| ("PSAMPLE_ATTR_IIFINDEX", "none"), | |
| ("PSAMPLE_ATTR_OIFINDEX", "none"), | |
| ("PSAMPLE_ATTR_ORIGSIZE", "none"), | |
| ("PSAMPLE_ATTR_SAMPLE_GROUP", "uint32"), | |
| ("PSAMPLE_ATTR_GROUP_SEQ", "none"), | |
| ("PSAMPLE_ATTR_SAMPLE_RATE", "uint32"), | |
| ("PSAMPLE_ATTR_DATA", "array(uint8)"), | |
| ("PSAMPLE_ATTR_GROUP_REFCOUNT", "none"), | |
| ("PSAMPLE_ATTR_TUNNEL", "none"), | |
| ("PSAMPLE_ATTR_PAD", "none"), | |
| ("PSAMPLE_ATTR_OUT_TC", "none"), | |
| ("PSAMPLE_ATTR_OUT_TC_OCC", "none"), | |
| ("PSAMPLE_ATTR_LATENCY", "none"), | |
| ("PSAMPLE_ATTR_TIMESTAMP", "none"), | |
| ("PSAMPLE_ATTR_PROTO", "none"), | |
| ("PSAMPLE_ATTR_USER_COOKIE", "array(uint8)"), | |
| ) | |
| def dpstr(self): | |
| fields = [] | |
| data = "" | |
| for (attr, value) in self["attrs"]: | |
| if attr == "PSAMPLE_ATTR_SAMPLE_GROUP": | |
| fields.append("group:%d" % value) | |
| if attr == "PSAMPLE_ATTR_SAMPLE_RATE": | |
| fields.append("rate:%d" % value) | |
| if attr == "PSAMPLE_ATTR_USER_COOKIE": | |
| value = "".join(format(x, "02x") for x in value) | |
| fields.append("cookie:%s" % value) | |
| if attr == "PSAMPLE_ATTR_DATA" and len(value) > 0: | |
| data = "data:%s" % "".join(format(x, "02x") for x in value) | |
| return ("%s %s" % (",".join(fields), data)).strip() | |
| class psample_msg(Marshal): | |
| PSAMPLE_CMD_SAMPLE = 0 | |
| PSAMPLE_CMD_GET_GROUP = 1 | |
| PSAMPLE_CMD_NEW_GROUP = 2 | |
| PSAMPLE_CMD_DEL_GROUP = 3 | |
| PSAMPLE_CMD_SET_FILTER = 4 | |
| msg_map = {PSAMPLE_CMD_SAMPLE: psample_sample} | |
| class PsampleEvent(EventSocket): | |
| genl_family = "psample" | |
| mcast_groups = ["packets"] | |
| marshal_class = psample_msg | |
| def read_samples(self): | |
| print("listening for psample events", flush=True) | |
| while True: | |
| try: | |
| for msg in self.get(): | |
| print(msg.dpstr(), flush=True) | |
| except NetlinkError as ne: | |
| raise ne | |
| 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.encap_ovskey = encap_ovskey | |
| nlmsg_atoms.ovskey = ovskey | |
| nlmsg_atoms.ovsactions = ovsactions | |
| # version check for pyroute2 | |
| prverscheck = pyroute2.__version__.split(".") | |
| if int(prverscheck[0]) == 0 and int(prverscheck[1]) < 6: | |
| print("Need to upgrade the python pyroute2 package to >= 0.6.") | |
| sys.exit(1) | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| "-v", | |
| "--verbose", | |
| action="count", | |
| help="Increment 'verbose' output counter.", | |
| default=0, | |
| ) | |
| subparsers = parser.add_subparsers(dest="subcommand") | |
| 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", "gre", "geneve", "vxlan"], | |
| help="Interface type (default netdev)", | |
| ) | |
| addifcmd.add_argument( | |
| "-p", | |
| "--dport", | |
| type=int, | |
| default=0, | |
| help="Destination port (0 for default)" | |
| ) | |
| delifcmd = subparsers.add_parser("del-if") | |
| delifcmd.add_argument("dpname", help="Datapath Name") | |
| delifcmd.add_argument("delif", help="Interface name for adding") | |
| delifcmd.add_argument("-d", | |
| "--dellink", | |
| type=bool, default=False, | |
| help="Delete the link as well.") | |
| dumpflcmd = subparsers.add_parser("dump-flows") | |
| dumpflcmd.add_argument("dumpdp", help="Datapath Name") | |
| addflcmd = subparsers.add_parser("add-flow") | |
| addflcmd.add_argument("flbr", help="Datapath name") | |
| addflcmd.add_argument("flow", help="Flow specification") | |
| addflcmd.add_argument("acts", help="Flow actions") | |
| modflcmd = subparsers.add_parser("mod-flow") | |
| modflcmd.add_argument("modbr", help="Datapath name") | |
| modflcmd.add_argument("modflow", help="Flow specification") | |
| modflcmd.add_argument("modacts", help="Flow actions", | |
| nargs="?", default=None) | |
| delfscmd = subparsers.add_parser("del-flows") | |
| delfscmd.add_argument("flsbr", help="Datapath name") | |
| subparsers.add_parser("psample-events") | |
| 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() | |
| sys.setrecursionlimit(100000) | |
| if args.subcommand == "psample-events": | |
| PsampleEvent().read_samples() | |
| 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, | |
| args.dport) | |
| 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." | |
| if args.dellink: | |
| ipr = pyroute2.iproute.IPRoute() | |
| ipr.link("del", index=ipr.link_lookup(ifname=args.delif)[0]) | |
| 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)) | |
| elif hasattr(args, "flbr"): | |
| rep = ovsdp.info(args.flbr, 0) | |
| if rep is None: | |
| print("DP '%s' not found." % args.flbr) | |
| return 1 | |
| flow = OvsFlow.ovs_flow_msg() | |
| flow.parse(args.flow, args.acts, rep["dpifindex"]) | |
| ovsflow.add_flow(rep["dpifindex"], flow) | |
| elif hasattr(args, "modbr"): | |
| rep = ovsdp.info(args.modbr, 0) | |
| if rep is None: | |
| print(f"DP '{args.modbr}' not found.") | |
| return 1 | |
| flow = OvsFlow.ovs_flow_msg() | |
| flow.parse(args.modflow, args.modacts, rep["dpifindex"]) | |
| ovsflow.mod_flow(rep["dpifindex"], flow) | |
| elif hasattr(args, "flsbr"): | |
| rep = ovsdp.info(args.flsbr, 0) | |
| if rep is None: | |
| print("DP '%s' not found." % args.flsbr) | |
| ovsflow.del_flows(rep["dpifindex"]) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main(sys.argv)) |