Skip to content

Commit

Permalink
ofp-actions: Centralize all OpenFlow action code for maintainability.
Browse files Browse the repository at this point in the history
Until now, knowledge about OpenFlow has been somewhat scattered around the
tree.  Some of it is in ofp-actions, some of it is in ofp-util, some in
separate files for individual actions, and most of the wire format
declarations are in include/openflow.  This commit centralizes all of that
in ofp-actions.

Encoding and decoding OpenFlow actions was previously broken up by OpenFlow
version.  This was OK with only OpenFlow 1.0 and 1.1, but each additional
version added a new wrapper around the existing ones, which started to
become hard to understand.  This commit merges all of the processing for
the different versions, to the extent that they are similar, making the
version differences clearer.

Previously, ofp-actions contained OpenFlow encoding and decoding, plus
ofpact formatting, but OpenFlow parsing was separated into ofp-parse, which
seems an odd division.  This commit moves the parsing code into ofp-actions
with the rest of the code.

Before this commit, the four main bits of code associated with a particular
ofpact--OpenFlow encoding and decoding, ofpact formatting and parsing--were
all found far away from each other.  This often made it hard to see what
was going on for a particular ofpact, since you had to search around to
many different pieces of code.  This commit reorganizes so that all of the
code for a given ofpact is in a single place.

As a code refactoring, this commit has little visible behavioral change.
The update to ofproto-dpif.at illustrates one minor bug fix as a side
effect: a flow that was added with the action "dec_ttl" (a standard
OpenFlow action) was previously formatted as "dec_ttl(0)" (using a Nicira
extension to specifically direct packets bounced to the controller because
of too-low TTL), but after this commit it is correctly formatted as
"dec_ttl".

The other visible effect is to drop support for the Nicira extension
dec_ttl action in OpenFlow 1.1 and later in favor of the equivalent
standard action.  It seems unlikely that anyone was really using the
Nicira extension in OF1.1 or later.

Signed-off-by: Ben Pfaff <[email protected]>
Acked-by: Jarno Rajahalme <[email protected]>
  • Loading branch information
blp committed Aug 11, 2014
1 parent 8f2cded commit c2d936a
Show file tree
Hide file tree
Showing 28 changed files with 5,739 additions and 6,133 deletions.
376 changes: 376 additions & 0 deletions build-aux/extract-ofp-actions
Original file line number Diff line number Diff line change
@@ -0,0 +1,376 @@
#! /usr/bin/python

import sys
import os.path
import re

OFP_ACTION_ALIGN = 8

# Map from OpenFlow version number to version ID used in ofp_header.
version_map = {"1.0": 0x01,
"1.1": 0x02,
"1.2": 0x03,
"1.3": 0x04,
"1.4": 0x05,
"1.5": 0x06}
version_reverse_map = dict((v, k) for (k, v) in version_map.iteritems())

# Map from vendor name to the length of the action header.
vendor_map = {"OF": (0x00000000, 4),
"NX": (0x00002320, 10)}

# Basic types used in action arguments.
types = {}
types['uint8_t'] = {"size": 1, "align": 1, "ntoh": None, "hton": None}
types['ovs_be16'] = {"size": 2, "align": 2, "ntoh": "ntohs", "hton": "htons"}
types['ovs_be32'] = {"size": 4, "align": 4, "ntoh": "ntohl", "hton": "htonl"}
types['ovs_be64'] = {"size": 8, "align": 8, "ntoh": "ntohll", "hton": "htonll"}
types['uint16_t'] = {"size": 2, "align": 2, "ntoh": None, "hton": None}
types['uint32_t'] = {"size": 4, "align": 4, "ntoh": None, "hton": None}
types['uint64_t'] = {"size": 8, "align": 8, "ntoh": None, "hton": None}

line = ""

arg_structs = set()

def round_up(x, y):
return (x + (y - 1)) / y * y

def open_file(fn):
global file_name
global input_file
global line_number
file_name = fn
input_file = open(file_name)
line_number = 0

def get_line():
global input_file
global line
global line_number
line = input_file.readline()
line_number += 1
if line == "":
fatal("unexpected end of input")
return line

n_errors = 0
def error(msg):
global n_errors
sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
n_errors += 1

def fatal(msg):
error(msg)
sys.exit(1)

def usage():
argv0 = os.path.basename(sys.argv[0])
print ('''\
%(argv0)s, for extracting OpenFlow action data
usage: %(argv0)s OFP_ACTIONS.C [--prototypes | --definitions]
This program reads ofp-actions.c to obtain information about OpenFlow
actions. With --prototypes, it outputs on stdout a set of prototypes to
#include early in ofp-actions.c. With --definitions, it outputs on stdout
a set of definitions to #include late in ofp-actions.c
OFP_ACTIONS.C should point to lib/ofp-actions.c.\
''' % {"argv0": argv0})
sys.exit(0)

def extract_ofp_actions(fn, definitions):
error_types = {}

comments = []
names = []
domain = {}
for code, size in vendor_map.values():
domain[code] = {}
enums = {}

n_errors = 0

open_file(fn)

while True:
get_line()
if re.match('enum ofp_raw_action_type {', line):
break

while True:
get_line()
if line.startswith('/*') or not line or line.isspace():
continue
elif re.match('}', line):
break

if not line.lstrip().startswith('/*'):
fatal("unexpected syntax between actions")

comment = line.lstrip()[2:].strip()
while not comment.endswith('*/'):
get_line()
if line.startswith('/*') or not line or line.isspace():
fatal("unexpected syntax within action")
comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
comment = re.sub('\[[^]]*\]', '', comment)
comment = comment[:-2].rstrip()

m = re.match('([^:]+):\s+(.*)$', comment)
if not m:
fatal("unexpected syntax between actions")

dsts = m.group(1)
argtype = m.group(2).strip().replace('.', '', 1)

get_line()
m = re.match(r'\s+(([A-Z]+)_RAW([0-9]*)_([A-Z0-9_]+)),?', line)
if not m:
fatal("syntax error expecting enum value")

enum = m.group(1)
if enum in names:
fatal("%s specified twice" % enum)

names.append(enum)

for dst in dsts.split(', '):
m = re.match(r'([A-Z]+)([0-9.]+)(\+|-[0-9.]+)?(?:\((\d+)\))(?: is deprecated \(([^)]+)\))?$', dst)
if not m:
fatal("%r: syntax error in destination" % dst)
vendor_name = m.group(1)
version1_name = m.group(2)
version2_name = m.group(3)
type_ = int(m.group(4))
deprecation = m.group(5)

if vendor_name not in vendor_map:
fatal("%s: unknown vendor" % vendor_name)
vendor = vendor_map[vendor_name][0]

if version1_name not in version_map:
fatal("%s: unknown OpenFlow version" % version1_name)
v1 = version_map[version1_name]

if version2_name is None:
v2 = v1
elif version2_name == "+":
v2 = max(version_map.values())
elif version2_name[1:] not in version_map:
fatal("%s: unknown OpenFlow version" % version2_name[1:])
else:
v2 = version_map[version2_name[1:]]

if v2 < v1:
fatal("%s%s: %s precedes %s"
% (version1_name, version2_name,
version2_name, version1_name))

for version in range(v1, v2 + 1):
domain[vendor].setdefault(type_, {})
if version in domain[vendor][type_]:
v = domain[vendor][type_][version]
msg = "%#x,%d in OF%s means both %s and %s" % (
vendor, type_, version_reverse_map[version],
v["enum"], enum)
error("%s: %s." % (dst, msg))
sys.stderr.write("%s:%d: %s: Here is the location "
"of the previous definition.\n"
% (v["file_name"], v["line_number"],
dst))
n_errors += 1
else:
header_len = vendor_map[vendor_name][1]

base_argtype = argtype.replace(', ..', '', 1)
if base_argtype in types:
arg_align = types[base_argtype]['align']
arg_len = types[base_argtype]['size']
arg_ofs = round_up(header_len, arg_align)
min_length = round_up(arg_ofs + arg_len,
OFP_ACTION_ALIGN)
elif base_argtype == 'void':
min_length = round_up(header_len, OFP_ACTION_ALIGN)
arg_len = 0
arg_ofs = 0
elif re.match(r'struct [a-zA-Z0-9_]+$', base_argtype):
min_length = 'sizeof(%s)' % base_argtype
arg_structs.add(base_argtype)
arg_len = 0
arg_ofs = 0
# should also emit OFP_ACTION_ALIGN assertion
else:
fatal("bad argument type %s" % argtype)

ellipsis = argtype != base_argtype
if ellipsis:
max_length = '65536 - OFP_ACTION_ALIGN'
else:
max_length = min_length

info = {"enum": enum, # 0
"deprecation": deprecation, # 1
"file_name": file_name, # 2
"line_number": line_number, # 3
"min_length": min_length, # 4
"max_length": max_length, # 5
"arg_ofs": arg_ofs, # 6
"arg_len": arg_len, # 7
"base_argtype": base_argtype, # 8
"version": version, # 9
"type": type_} # 10
domain[vendor][type_][version] = info

enums.setdefault(enum, [])
enums[enum].append(info)

input_file.close()

if n_errors:
sys.exit(1)

print """\
/* Generated automatically; do not modify! -*- buffer-read-only: t -*- */
"""

if definitions:
print "/* Verify that structs used as actions are reasonable sizes. */"
for s in sorted(arg_structs):
print "BUILD_ASSERT_DECL(sizeof(%s) %% OFP_ACTION_ALIGN == 0);" % s

print "\nstatic struct ofpact_raw_instance all_raw_instances[] = {"
for vendor in domain:
for type_ in domain[vendor]:
for version in domain[vendor][type_]:
d = domain[vendor][type_][version]
print " { { 0x%08x, %2d, 0x%02x }, " % (
vendor, type_, version)
print " %s," % d["enum"]
print " HMAP_NODE_NULL_INITIALIZER,"
print " HMAP_NODE_NULL_INITIALIZER,"
print " %s," % d["min_length"]
print " %s," % d["max_length"]
print " %s," % d["arg_ofs"]
print " %s," % d["arg_len"]
print " \"%s\"," % re.sub('_RAW[0-9]*', '', d["enum"], 1)
if d["deprecation"]:
print " \"%s\"," % re.sub(r'(["\\])', r'\\\1', d["deprecation"])
else:
print " NULL,"
print " },"
print "};";

for versions in enums.values():
need_ofp_version = False
for v in versions:
assert v["arg_len"] == versions[0]["arg_len"]
assert v["base_argtype"] == versions[0]["base_argtype"]
if (v["min_length"] != versions[0]["min_length"] or
v["arg_ofs"] != versions[0]["arg_ofs"] or
v["type"] != versions[0]["type"]):
need_ofp_version = True
base_argtype = versions[0]["base_argtype"]

decl = "static inline "
if base_argtype.startswith('struct'):
decl += "%s *" %base_argtype
else:
decl += "void"
decl += "\nput_%s(struct ofpbuf *openflow" % versions[0]["enum"].replace('_RAW', '', 1)
if need_ofp_version:
decl += ", enum ofp_version version"
if base_argtype != 'void' and not base_argtype.startswith('struct'):
decl += ", %s arg" % base_argtype
decl += ")"
if definitions:
decl += "{\n"
decl += " "
if base_argtype.startswith('struct'):
decl += "return "
decl += "ofpact_put_raw(openflow, "
if need_ofp_version:
decl += "version"
else:
decl += "%s" % versions[0]["version"]
decl += ", %s, " % versions[0]["enum"]
if base_argtype.startswith('struct') or base_argtype == 'void':
decl += "0"
else:
ntoh = types[base_argtype]['ntoh']
if ntoh:
decl += "%s(arg)" % ntoh
else:
decl += "arg"
decl += ");\n"
decl += "}"
else:
decl += ";"
print decl
print

if definitions:
print """\
static enum ofperr
ofpact_decode(const struct ofp_action_header *a, enum ofp_raw_action_type raw,
uint64_t arg, struct ofpbuf *out)
{
switch (raw) {\
"""
for versions in enums.values():
enum = versions[0]["enum"]
print " case %s:" % enum
base_argtype = versions[0]["base_argtype"]
if base_argtype == 'void':
print " return decode_%s(out);" % enum
else:
if base_argtype.startswith('struct'):
arg = "ALIGNED_CAST(const %s *, a)" % base_argtype
else:
hton = types[base_argtype]['hton']
if hton:
arg = "%s(arg)" % hton
else:
arg = "arg"
print " return decode_%s(%s, out);" % (enum, arg)
print
print """\
default:
OVS_NOT_REACHED();
}
}\
"""
else:
for versions in enums.values():
enum = versions[0]["enum"]
prototype = "static enum ofperr decode_%s(" % enum
base_argtype = versions[0]["base_argtype"]
if base_argtype != 'void':
if base_argtype.startswith('struct'):
prototype += "const %s *, " % base_argtype
else:
prototype += "%s, " % base_argtype
prototype += "struct ofpbuf *);"
print prototype

print """
static enum ofperr ofpact_decode(const struct ofp_action_header *,
enum ofp_raw_action_type raw,
uint64_t arg, struct ofpbuf *out);
"""

if __name__ == '__main__':
if '--help' in sys.argv:
usage()
elif len(sys.argv) != 3:
sys.stderr.write("exactly two arguments required; "
"use --help for help\n")
sys.exit(1)
elif sys.argv[2] == '--prototypes':
extract_ofp_actions(sys.argv[1], False)
elif sys.argv[2] == '--definitions':
extract_ofp_actions(sys.argv[1], True)
else:
sys.stderr.write("invalid arguments; use --help for help\n")
sys.exit(1)

Loading

0 comments on commit c2d936a

Please sign in to comment.