summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorIWAMOTO Toshihiro <iwamoto@valinux.co.jp>2017-05-26 17:30:21 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2017-05-30 16:13:18 +0900
commita52c62a1207e9987e50d299742a15d097605ac79 (patch)
treea0e408b68f1ea21cd7838baa5710be0278eec8b4
parenta5dca1a1e35b445c5746b0ac66fb9d151f62f771 (diff)
ofctl: Add ovs-ofctl style action string parser
This commit adds a new method called ofp_instruction_from_str, which takes an ovs-ofctl style action string and returns a list of JSON dictionaries. Currently only a few action strings are understood. Signed-off-by: IWAMOTO Toshihiro <iwamoto@valinux.co.jp> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r--ryu/exception.py4
-rw-r--r--ryu/lib/ofctl_string.py324
-rw-r--r--ryu/ofproto/ofproto_parser.py52
3 files changed, 380 insertions, 0 deletions
diff --git a/ryu/exception.py b/ryu/exception.py
index 1be4ba12..93fba6c7 100644
--- a/ryu/exception.py
+++ b/ryu/exception.py
@@ -52,6 +52,10 @@ class OFPTruncatedMessage(RyuException):
super(OFPTruncatedMessage, self).__init__(msg, **kwargs)
+class OFPInvalidActionString(RyuException):
+ message = 'unable to parse: %(action_str)s'
+
+
class NetworkNotFound(RyuException):
message = 'no such network id %(network_id)s'
diff --git a/ryu/lib/ofctl_string.py b/ryu/lib/ofctl_string.py
new file mode 100644
index 00000000..06a91af2
--- /dev/null
+++ b/ryu/lib/ofctl_string.py
@@ -0,0 +1,324 @@
+# Copyright (C) 2017 Nippon Telegraph and Telephone Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import re
+
+import ryu.exception
+from ryu.lib.ofctl_utils import str_to_int
+from ryu.ofproto import nicira_ext
+
+
+def ofp_instruction_from_str(ofproto, action_str):
+ """
+ Parse an ovs-ofctl style action string and return a list of
+ jsondict representations of OFPInstructionActions, which
+ can then be passed to ofproto_parser.ofp_instruction_from_jsondict.
+
+ Please note that this is for making transition from ovs-ofctl
+ easier. Please consider using OFPAction constructors when writing
+ new codes.
+
+ This function takes the following arguments.
+
+ =========== =================================================
+ Argument Description
+ =========== =================================================
+ ofproto An ofproto module.
+ action_str An action string.
+ =========== =================================================
+ """
+ action_re = re.compile("([a-z_]+)(\([^)]*\)|[^a-z_,()][^,()]*)*")
+ result = []
+ while len(action_str):
+ m = action_re.match(action_str)
+ if not m:
+ raise ryu.exception.OFPInvalidActionString(action_str=action_str)
+ action_name = m.group(1)
+ this_action = m.group(0)
+ paren_level = this_action.count('(') - this_action.count(')')
+ assert paren_level >= 0
+ try:
+ # Parens can be nested. Look for as many ')'s as '('s.
+ if paren_level > 0:
+ this_action, rest = _tokenize_paren_block(action_str, m.end(0))
+ else:
+ rest = action_str[m.end(0):]
+ if len(rest):
+ assert rest[0] == ','
+ rest = rest[1:]
+ except Exception:
+ raise ryu.exception.OFPInvalidActionString(action_str=action_str)
+ if action_name == 'drop':
+ assert this_action == 'drop'
+ assert len(result) == 0 and rest == ''
+ return []
+ converter = getattr(OfctlActionConverter, action_name, None)
+ if converter is None or not callable(converter):
+ raise ryu.exception.OFPInvalidActionString(action_str=action_name)
+ result.append(converter(ofproto, this_action))
+ action_str = rest
+
+ return result
+
+
+def _tokenize_paren_block(string, pos):
+ paren_re = re.compile("[()]")
+ paren_level = string[:pos].count('(') - string[:pos].count(')')
+ while paren_level > 0:
+ m = paren_re.search(string[pos:])
+ if m.group(0) == '(':
+ paren_level += 1
+ else:
+ paren_level -= 1
+ pos += m.end(0)
+ return string[:pos], string[pos:]
+
+
+def tokenize_ofp_instruction_arg(arg):
+ """
+ Tokenize an argument portion of ovs-ofctl style action string.
+ """
+ arg_re = re.compile("[^,()]*")
+ try:
+ rest = arg
+ result = []
+ while len(rest):
+ m = arg_re.match(rest)
+ if m.end(0) == len(rest):
+ result.append(rest)
+ return result
+ if rest[m.end(0)] == '(':
+ this_block, rest = _tokenize_paren_block(
+ rest, m.end(0) + 1)
+ result.append(this_block)
+ elif rest[m.end(0)] == ',':
+ result.append(m.group(0))
+ rest = rest[m.end(0):]
+ else: # is ')'
+ raise Exception
+ if len(rest):
+ assert rest[0] == ','
+ rest = rest[1:]
+ return result
+ except Exception:
+ raise ryu.exception.OFPInvalidActionString(action_str=arg)
+
+
+_OXM_FIELD_OFCTL_ALIASES = {
+ 'tun_id': 'tunnel_id',
+ 'in_port': 'in_port_nxm',
+ 'in_port_oxm': 'in_port',
+ 'dl_src': 'eth_src',
+ 'dl_type': 'eth_type',
+ 'nw_src': 'ipv4_src',
+ 'ip_src': 'ipv4_src',
+ 'nw_proto': 'ip_proto',
+ 'nw_ecn': 'ip_ecn',
+ 'tp_src': 'tcp_src',
+ 'icmp_type': 'icmpv4_type',
+ 'icmp_code': 'icmpv4_code',
+ 'nd_target': 'ipv6_nd_target',
+ 'nd_sll': 'ipv6_nd_sll',
+ 'nd_tll': 'ipv6_nd_tll',
+ # Nicira extension
+ 'tun_src': 'tun_ipv4_src'
+}
+
+
+def ofp_ofctl_field_name_to_ryu(field):
+ """Convert an ovs-ofctl field name to ryu equivalent."""
+ mapped = _OXM_FIELD_OFCTL_ALIASES.get(field)
+ if mapped:
+ return mapped
+ if field.endswith("_dst"):
+ mapped = _OXM_FIELD_OFCTL_ALIASES.get(field[:-3] + "src")
+ if mapped:
+ return mapped[:-3] + "dst"
+ return field
+
+
+_NXM_FIELD_MAP = dict([(key, key + '_nxm') for key in [
+ 'arp_sha', 'arp_tha', 'ipv6_src', 'ipv6_dst',
+ 'icmpv6_type', 'icmpv6_code', 'ip_ecn', 'tcp_flags']])
+_NXM_FIELD_MAP.update({
+ 'tun_id': 'tunnel_id_nxm', 'ip_ttl': 'nw_ttl'})
+
+_NXM_OF_FIELD_MAP = dict([(key, key + '_nxm') for key in [
+ 'in_port', 'eth_dst', 'eth_src', 'eth_type', 'ip_proto',
+ 'tcp_src', 'tcp_dst', 'udp_src', 'udp_dst',
+ 'arp_op', 'arp_spa', 'arp_tpa']])
+_NXM_OF_FIELD_MAP.update({
+ 'ip_src': 'ipv4_src_nxm', 'ip_dst': 'ipv4_dst_nxm',
+ 'icmp_type': 'icmpv4_type_nxm', 'icmp_code': 'icmpv4_code_nxm'})
+
+
+def nxm_field_name_to_ryu(field):
+ """
+ Convert an ovs-ofctl style NXM_/OXM_ field name to
+ a ryu match field name.
+ """
+ if field.endswith("_W"):
+ field = field[:-2]
+ prefix = field[:7]
+ field = field[7:].lower()
+ mapped_result = None
+
+ if prefix == 'NXM_NX_':
+ mapped_result = _NXM_FIELD_MAP.get(field)
+ elif prefix == "NXM_OF_":
+ mapped_result = _NXM_OF_FIELD_MAP.get(field)
+ elif prefix == "OXM_OF_":
+ # no mapping needed
+ pass
+ else:
+ raise ValueError
+ if mapped_result is not None:
+ return mapped_result
+ return field
+
+
+class OfctlActionConverter(object):
+
+ @classmethod
+ def goto_table(cls, ofproto, action_str):
+ assert action_str.startswith('goto_table:')
+ table_id = str_to_int(action_str[len('goto_table:'):])
+ return dict(OFPInstructionGotoTable={'table_id': table_id})
+
+ @classmethod
+ def normal(cls, ofproto, action_str):
+ return cls.output(ofproto, action_str)
+
+ @classmethod
+ def output(cls, ofproto, action_str):
+ if action_str == 'normal':
+ port = ofproto.OFPP_NORMAL
+ else:
+ assert action_str.startswith('output:')
+ port = str_to_int(action_str[len('output:'):])
+ return dict(OFPActionOutput={'port': port})
+
+ @classmethod
+ def pop_vlan(cls, ofproto, action_str):
+ return dict(OFPActionPopVlan={})
+
+ @classmethod
+ def set_field(cls, ofproto, action_str):
+ try:
+ assert action_str.startswith("set_field:")
+ value, key = action_str[len("set_field:"):].split("->", 1)
+ fieldarg = dict(field=ofp_ofctl_field_name_to_ryu(key))
+ m = value.find('/')
+ if m >= 0:
+ fieldarg['value'] = str_to_int(value[:m])
+ fieldarg['mask'] = str_to_int(value[m + 1:])
+ else:
+ fieldarg['value'] = str_to_int(value)
+ except Exception:
+ raise ryu.exception.OFPInvalidActionString(action_str=action_str)
+ return dict(OFPActionSetField={
+ 'field': {'OXMTlv': fieldarg}})
+
+ # NX actions
+ @classmethod
+ def resubmit(cls, ofproto, action_str):
+ arg = action_str[len("resubmit"):]
+ kwargs = {}
+ try:
+ if arg[0] == ':':
+ kwargs['in_port'] = str_to_int(arg[1:])
+ elif arg[0] == '(' and arg[-1] == ')':
+ in_port, table_id = arg[1:-1].split(',')
+ if in_port:
+ kwargs['in_port'] = str_to_int(in_port)
+ if table_id:
+ kwargs['table_id'] = str_to_int(table_id)
+ else:
+ raise Exception
+ return dict(NXActionResubmitTable=kwargs)
+ except Exception:
+ raise ryu.exception.OFPInvalidActionString(
+ action_str=action_str)
+
+ @classmethod
+ def conjunction(cls, ofproto, action_str):
+ try:
+ assert action_str.startswith('conjunction(')
+ assert action_str[-1] == ')'
+ args = action_str[len('conjunction('):-1].split(',')
+ assert len(args) == 2
+ id_ = str_to_int(args[0])
+ clauses = list(map(str_to_int, args[1].split('/')))
+ assert len(clauses) == 2
+ return dict(NXActionConjunction={
+ 'clause': clauses[0] - 1,
+ 'n_clauses': clauses[1],
+ 'id': id_})
+ except Exception:
+ raise ryu.exception.OFPInvalidActionString(
+ action_str=action_str)
+
+ @classmethod
+ def ct(cls, ofproto, action_str):
+ str_to_port = {'ftp': 21, 'tftp': 69}
+ flags = 0
+ zone_src = ""
+ zone_ofs_nbits = 0
+ recirc_table = nicira_ext.NX_CT_RECIRC_NONE
+ alg = 0
+ ct_actions = []
+
+ if len(action_str) > 2:
+ if (not action_str.startswith('ct(') or
+ action_str[-1] != ')'):
+ raise ryu.exception.OFPInvalidActionString(
+ action_str=action_str)
+ rest = tokenize_ofp_instruction_arg(action_str[len('ct('):-1])
+ else:
+ rest = []
+ for arg in rest:
+ if arg == 'commit':
+ flags |= nicira_ext.NX_CT_F_COMMIT
+ rest = rest[len('commit'):]
+ elif arg == 'force':
+ flags |= nicira_ext.NX_CT_F_FORCE
+ elif arg.startswith('exec('):
+ ct_actions = ofp_instruction_from_str(
+ ofproto, arg[len('exec('):-1])
+ else:
+ try:
+ k, v = arg.split('=', 1)
+ if k == 'table':
+ recirc_table = str_to_int(v)
+ elif k == 'zone':
+ m = re.search('\[(\d*)\.\.(\d*)\]', v)
+ if m:
+ zone_ofs_nbits = nicira_ext.ofs_nbits(
+ int(m.group(1)), int(m.group(2)))
+ zone_src = nxm_field_name_to_ryu(
+ v[:m.start(0)])
+ else:
+ zone_ofs_nbits = str_to_int(v)
+ elif k == 'alg':
+ alg = str_to_port[arg[len('alg='):]]
+ except Exception:
+ raise ryu.exception.OFPInvalidActionString(
+ action_str=action_str)
+ return dict(NXActionCT={'flags': flags,
+ 'zone_src': zone_src,
+ 'zone_ofs_nbits': zone_ofs_nbits,
+ 'recirc_table': recirc_table,
+ 'alg': alg,
+ 'actions': ct_actions})
diff --git a/ryu/ofproto/ofproto_parser.py b/ryu/ofproto/ofproto_parser.py
index b68c5382..7b56968d 100644
--- a/ryu/ofproto/ofproto_parser.py
+++ b/ryu/ofproto/ofproto_parser.py
@@ -125,6 +125,58 @@ def ofp_msg_from_jsondict(dp, jsondict):
return cls.from_jsondict(v, datapath=dp)
+def ofp_instruction_from_jsondict(dp, jsonlist, encap=True):
+ """
+ This function is intended to be used with
+ ryu.lib.ofctl_string.ofp_instruction_from_str.
+ It is very similar to ofp_msg_from_jsondict, but works on
+ a list of OFPInstructions/OFPActions. It also encapsulates
+ OFPAction into OFPInstructionActions, as >OF1.0 OFPFlowMod
+ requires that.
+
+ This function takes the following arguments.
+
+ ======== ==================================================
+ Argument Description
+ ======== ==================================================
+ dp An instance of ryu.controller.Datapath.
+ jsonlist A list of JSON style dictionaries.
+ encap Encapsulate OFPAction into OFPInstructionActions.
+ Must be false for OF10.
+ ======== ==================================================
+ """
+ proto = dp.ofproto
+ parser = dp.ofproto_parser
+ result = []
+ for jsondict in jsonlist:
+ assert len(jsondict) == 1
+ k, v = list(jsondict.items())[0]
+ cls = getattr(parser, k)
+ if not issubclass(cls, parser.OFPAction):
+ ofpinst = getattr(parser, 'OFPInstruction', None)
+ if not ofpinst or not issubclass(cls, ofpinst):
+ raise ValueError("Supplied jsondict is of wrong type: %s",
+ jsondict)
+ result.append(cls.from_jsondict(v))
+
+ if not encap:
+ return result
+ insts = []
+ actions = []
+ result.append(None) # sentinel
+ for act_or_inst in result:
+ if isinstance(act_or_inst, parser.OFPAction):
+ actions.append(act_or_inst)
+ else:
+ if actions:
+ insts.append(parser.OFPInstructionActions(
+ proto.OFPIT_APPLY_ACTIONS, actions))
+ actions = []
+ if act_or_inst is not None:
+ insts.append(act_or_inst)
+ return insts
+
+
class StringifyMixin(stringify.StringifyMixin):
_class_prefixes = ["OFP", "ONF", "MT", "NX"]