Files
linux-stable-mirror/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
T
Minxi HouandJakub Kicinski 3e9201e4fe selftests/net/openvswitch: add flow modify test
Add mod_flow() and the mod-flow CLI command to ovs-dpctl.py, exercising
OVS_FLOW_CMD_SET. Add test_flow_set which first modifies an existing
flow with new actions and verifies the change via traffic, then modifies
the same flow without actions and verifies the kernel handles the
no-actions case gracefully.

The no-actions path is unreachable from userspace OVS tools (dpctl
mod-flow requires actions) but reachable via raw netlink. This is the
code path where Adrian Moreno found a possible kfree_skb of ERR_PTR
when reply allocation fails after locking.

Make parse() skip OVS_FLOW_ATTR_ACTIONS when actstr is None so the
kernel enters the post-lock allocation branch in ovs_flow_cmd_set().
After the no-actions set, verify via dump-flows that the flow retained
its drop action.

Suggested-by: Aaron Conole <aconole@redhat.com>
Signed-off-by: Minxi Hou <houminxi@gmail.com>
Reviewed-by: Aaron Conole <aconole@redhat.com>
Link: https://patch.msgid.link/20260609165725.107484-1-houminxi@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2026-06-11 15:45:30 -07:00

3158 lines
104 KiB
Python

#!/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),
("proto", "proto", "%d", lambda x: int(x) if x else 0),
("tclass", "tclass", "%d", lambda x: int(x) if x else 0),
("hlimit", "hlimit", "%d", lambda x: int(x) if x else 0),
("frag", "frag", "%d", lambda x: int(x) if x else 0),
)
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),
("code", "code", "%d", lambda x: int(x) if x else 0),
)
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_ICMP",
"icmp",
ovskey.ovs_key_icmp,
),
(
"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
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", "vportopts"),
("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 vportopts(nla):
nla_map = (
("OVS_TUNNEL_ATTR_UNSPEC", "none"),
("OVS_TUNNEL_ATTR_DST_PORT", "uint16"),
("OVS_TUNNEL_ATTR_EXTENSION", "none"),
)
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_GRE
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, dport, lwt):
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_NAME", vport_ifname])
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]
if not lwt:
if tnl[0] == "gre":
# GRE tunnels have no options.
break
vportopt = OvsVport.ovs_vport_msg.vportopts()
vportopt["attrs"].append(
["OVS_TUNNEL_ATTR_DST_PORT", dport]
)
msg["attrs"].append(
["OVS_VPORT_ATTR_OPTIONS", vportopt]
)
else:
port_type = OvsVport.OVS_VPORT_TYPE_NETDEV
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
msg["attrs"].append(["OVS_VPORT_ATTR_TYPE", port_type])
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:
opts = ""
vpo = rep.get_attr("OVS_VPORT_ATTR_OPTIONS")
if vpo:
dpo = vpo.get_attr("OVS_TUNNEL_ATTR_DST_PORT")
if dpo:
opts += " tnl-dport:%s" % dpo
print(
" port %d: %s (%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")),
opts,
)
)
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)"
)
addifcmd.add_argument(
"-l",
"--lwt",
action=argparse.BooleanOptionalAction,
default=True,
help="Use LWT infrastructure instead of vport (default true)."
)
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, args.lwt)
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))