a52c62a120
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>
325 lines
11 KiB
Python
325 lines
11 KiB
Python
# 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})
|