532 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			532 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #! /usr/bin/python
 | |
| 
 | |
| import sys
 | |
| import os.path
 | |
| import re
 | |
| 
 | |
| line = ""
 | |
| 
 | |
| # Maps from user-friendly version number to its protocol encoding.
 | |
| VERSION = {"1.0": 0x01,
 | |
|            "1.1": 0x02,
 | |
|            "1.2": 0x03,
 | |
|            "1.3": 0x04,
 | |
|            "1.4": 0x05,
 | |
|            "1.5": 0x06}
 | |
| 
 | |
| TYPES = {"u8":       (1,   False),
 | |
|          "be16":     (2,   False),
 | |
|          "be32":     (4,   False),
 | |
|          "MAC":      (6,   False),
 | |
|          "be64":     (8,   False),
 | |
|          "be128":    (16,  False),
 | |
|          "tunnelMD": (124, True)}
 | |
| 
 | |
| FORMATTING = {"decimal":            ("MFS_DECIMAL",      1,   8),
 | |
|               "hexadecimal":        ("MFS_HEXADECIMAL",  1, 127),
 | |
|               "ct state":           ("MFS_CT_STATE",     4,   4),
 | |
|               "Ethernet":           ("MFS_ETHERNET",     6,   6),
 | |
|               "IPv4":               ("MFS_IPV4",         4,   4),
 | |
|               "IPv6":               ("MFS_IPV6",        16,  16),
 | |
|               "OpenFlow 1.0 port":  ("MFS_OFP_PORT",     2,   2),
 | |
|               "OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4,   4),
 | |
|               "frag":               ("MFS_FRAG",         1,   1),
 | |
|               "tunnel flags":       ("MFS_TNL_FLAGS",    2,   2),
 | |
|               "TCP flags":          ("MFS_TCP_FLAGS",    2,   2)}
 | |
| 
 | |
| PREREQS = {"none": "MFP_NONE",
 | |
|            "ARP": "MFP_ARP",
 | |
|            "VLAN VID": "MFP_VLAN_VID",
 | |
|            "IPv4": "MFP_IPV4",
 | |
|            "IPv6": "MFP_IPV6",
 | |
|            "IPv4/IPv6": "MFP_IP_ANY",
 | |
|            "MPLS": "MFP_MPLS",
 | |
|            "TCP": "MFP_TCP",
 | |
|            "UDP": "MFP_UDP",
 | |
|            "SCTP": "MFP_SCTP",
 | |
|            "ICMPv4": "MFP_ICMPV4",
 | |
|            "ICMPv6": "MFP_ICMPV6",
 | |
|            "ND": "MFP_ND",
 | |
|            "ND solicit": "MFP_ND_SOLICIT",
 | |
|            "ND advert": "MFP_ND_ADVERT"}
 | |
| 
 | |
| # Maps a name prefix into an (experimenter ID, class) pair, so:
 | |
| #
 | |
| #      - Standard OXM classes are written as (0, <oxm_class>)
 | |
| #
 | |
| #      - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
 | |
| #
 | |
| # If a name matches more than one prefix, the longest one is used.
 | |
| OXM_CLASSES = {"NXM_OF_":        (0,          0x0000),
 | |
|                "NXM_NX_":        (0,          0x0001),
 | |
|                "OXM_OF_":        (0,          0x8000),
 | |
|                "OXM_OF_PKT_REG": (0,          0x8001),
 | |
|                "ONFOXM_ET_":     (0x4f4e4600, 0xffff),
 | |
| 
 | |
|                # This is the experimenter OXM class for Nicira, which is the
 | |
|                # one that OVS would be using instead of NXM_OF_ and NXM_NX_
 | |
|                # if OVS didn't have those grandfathered in.  It is currently
 | |
|                # used only to test support for experimenter OXM, since there
 | |
|                # are barely any real uses of experimenter OXM in the wild.
 | |
|                "NXOXM_ET_":      (0x00002320, 0xffff)}
 | |
| 
 | |
| 
 | |
| def oxm_name_to_class(name):
 | |
|     prefix = ''
 | |
|     class_ = None
 | |
|     for p, c in OXM_CLASSES.items():
 | |
|         if name.startswith(p) and len(p) > len(prefix):
 | |
|             prefix = p
 | |
|             class_ = c
 | |
|     return class_
 | |
| 
 | |
| 
 | |
| def decode_version_range(range):
 | |
|     if range in VERSION:
 | |
|         return (VERSION[range], VERSION[range])
 | |
|     elif range.endswith('+'):
 | |
|         return (VERSION[range[:-1]], max(VERSION.values()))
 | |
|     else:
 | |
|         a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
 | |
|         return (VERSION[a], VERSION[b])
 | |
| 
 | |
| 
 | |
| def get_line():
 | |
|     global line
 | |
|     global line_number
 | |
|     line = input_file.readline()
 | |
|     line_number += 1
 | |
|     if line == "":
 | |
|         fatal("unexpected end of input")
 | |
| 
 | |
| 
 | |
| 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 field properties from meta-flow.h
 | |
| usage: %(argv0)s INPUT [--meta-flow | --nx-match]
 | |
|   where INPUT points to lib/meta-flow.h in the source directory.
 | |
| Depending on the option given, the output written to stdout is intended to be
 | |
| saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C
 | |
| file to #include.\
 | |
| ''' % {"argv0": argv0})
 | |
|     sys.exit(0)
 | |
| 
 | |
| 
 | |
| def make_sizeof(s):
 | |
|     m = re.match(r'(.*) up to (.*)', s)
 | |
|     if m:
 | |
|         struct, member = m.groups()
 | |
|         return "offsetof(%s, %s)" % (struct, member)
 | |
|     else:
 | |
|         return "sizeof(%s)" % s
 | |
| 
 | |
| 
 | |
| def parse_oxms(s, prefix, n_bytes):
 | |
|     if s == 'none':
 | |
|         return ()
 | |
| 
 | |
|     return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in s.split(','))
 | |
| 
 | |
| 
 | |
| match_types = dict()
 | |
| 
 | |
| 
 | |
| def parse_oxm(s, prefix, n_bytes):
 | |
|     global match_types
 | |
| 
 | |
|     m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s)
 | |
|     if not m:
 | |
|         fatal("%s: syntax error parsing %s" % (s, prefix))
 | |
| 
 | |
|     name, oxm_type, of_version, ovs_version = m.groups()
 | |
| 
 | |
|     class_ = oxm_name_to_class(name)
 | |
|     if class_ is None:
 | |
|         fatal("unknown OXM class for %s" % name)
 | |
|     oxm_vendor, oxm_class = class_
 | |
| 
 | |
|     if class_ in match_types:
 | |
|         if oxm_type in match_types[class_]:
 | |
|             fatal("duplicate match type for %s (conflicts with %s)" %
 | |
|                   (name, match_types[class_][oxm_type]))
 | |
|     else:
 | |
|         match_types[class_] = dict()
 | |
|     match_types[class_][oxm_type] = name
 | |
| 
 | |
|     # Normally the oxm_length is the size of the field, but for experimenter
 | |
|     # OXMs oxm_length also includes the 4-byte experimenter ID.
 | |
|     oxm_length = n_bytes
 | |
|     if oxm_class == 0xffff:
 | |
|         oxm_length += 4
 | |
| 
 | |
|     header = ("NXM_HEADER(0x%x,0x%x,%s,0,%d)"
 | |
|               % (oxm_vendor, oxm_class, oxm_type, oxm_length))
 | |
| 
 | |
|     if of_version:
 | |
|         if of_version not in VERSION:
 | |
|             fatal("%s: unknown OpenFlow version %s" % (name, of_version))
 | |
|         of_version_nr = VERSION[of_version]
 | |
|         if of_version_nr < VERSION['1.2']:
 | |
|             fatal("%s: claimed version %s predates OXM" % (name, of_version))
 | |
|     else:
 | |
|         of_version_nr = 0
 | |
| 
 | |
|     return (header, name, of_version_nr, ovs_version)
 | |
| 
 | |
| 
 | |
| def parse_field(mff, comment):
 | |
|     f = {'mff': mff}
 | |
| 
 | |
|     # First line of comment is the field name.
 | |
|     m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0])
 | |
|     if not m:
 | |
|         fatal("%s lacks field name" % mff)
 | |
|     f['name'], f['extra_name'] = m.groups()
 | |
| 
 | |
|     # Find the last blank line the comment.  The field definitions
 | |
|     # start after that.
 | |
|     blank = None
 | |
|     for i in range(len(comment)):
 | |
|         if not comment[i]:
 | |
|             blank = i
 | |
|     if not blank:
 | |
|         fatal("%s: missing blank line in comment" % mff)
 | |
| 
 | |
|     d = {}
 | |
|     for key in ("Type", "Maskable", "Formatting", "Prerequisites",
 | |
|                 "Access", "Prefix lookup member",
 | |
|                 "OXM", "NXM", "OF1.0", "OF1.1"):
 | |
|         d[key] = None
 | |
|     for fline in comment[blank + 1:]:
 | |
|         m = re.match(r'([^:]+):\s+(.*)\.$', fline)
 | |
|         if not m:
 | |
|             fatal("%s: syntax error parsing key-value pair as part of %s"
 | |
|                   % (fline, mff))
 | |
|         key, value = m.groups()
 | |
|         if key not in d:
 | |
|             fatal("%s: unknown key" % key)
 | |
|         elif key == 'Code point':
 | |
|             d[key] += [value]
 | |
|         elif d[key] is not None:
 | |
|             fatal("%s: duplicate key" % key)
 | |
|         d[key] = value
 | |
|     for key, value in d.items():
 | |
|         if not value and key not in ("OF1.0", "OF1.1",
 | |
|                                      "Prefix lookup member", "Notes"):
 | |
|             fatal("%s: missing %s" % (mff, key))
 | |
| 
 | |
|     m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
 | |
|     if not m:
 | |
|         fatal("%s: syntax error in type" % mff)
 | |
|     type_ = m.group(1)
 | |
|     if type_ not in TYPES:
 | |
|         fatal("%s: unknown type %s" % (mff, d['Type']))
 | |
| 
 | |
|     f['n_bytes'] = TYPES[type_][0]
 | |
|     if m.group(2):
 | |
|         f['n_bits'] = int(m.group(2))
 | |
|         if f['n_bits'] > f['n_bytes'] * 8:
 | |
|             fatal("%s: more bits (%d) than field size (%d)"
 | |
|                   % (mff, f['n_bits'], 8 * f['n_bytes']))
 | |
|     else:
 | |
|         f['n_bits'] = 8 * f['n_bytes']
 | |
|     f['variable'] = TYPES[type_][1]
 | |
| 
 | |
|     if d['Maskable'] == 'no':
 | |
|         f['mask'] = 'MFM_NONE'
 | |
|     elif d['Maskable'] == 'bitwise':
 | |
|         f['mask'] = 'MFM_FULLY'
 | |
|     else:
 | |
|         fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
 | |
| 
 | |
|     fmt = FORMATTING.get(d['Formatting'])
 | |
|     if not fmt:
 | |
|         fatal("%s: unknown format %s" % (mff, d['Formatting']))
 | |
|     if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
 | |
|         fatal("%s: %d-byte field can't be formatted as %s"
 | |
|               % (mff, f['n_bytes'], d['Formatting']))
 | |
|     f['string'] = fmt[0]
 | |
| 
 | |
|     f['prereqs'] = PREREQS.get(d['Prerequisites'])
 | |
|     if not f['prereqs']:
 | |
|         fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
 | |
| 
 | |
|     if d['Access'] == 'read-only':
 | |
|         f['writable'] = False
 | |
|     elif d['Access'] == 'read/write':
 | |
|         f['writable'] = True
 | |
|     else:
 | |
|         fatal("%s: unknown access %s" % (mff, d['Access']))
 | |
| 
 | |
|     f['OF1.0'] = d['OF1.0']
 | |
|     if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
 | |
|         fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
 | |
| 
 | |
|     f['OF1.1'] = d['OF1.1']
 | |
|     if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
 | |
|         fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
 | |
| 
 | |
|     f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) +
 | |
|                 parse_oxms(d['NXM'], 'NXM', f['n_bytes']))
 | |
| 
 | |
|     f['prefix'] = d["Prefix lookup member"]
 | |
| 
 | |
|     return f
 | |
| 
 | |
| 
 | |
| def protocols_to_c(protocols):
 | |
|     if protocols == set(['of10', 'of11', 'oxm']):
 | |
|         return 'OFPUTIL_P_ANY'
 | |
|     elif protocols == set(['of11', 'oxm']):
 | |
|         return 'OFPUTIL_P_NXM_OF11_UP'
 | |
|     elif protocols == set(['oxm']):
 | |
|         return 'OFPUTIL_P_NXM_OXM_ANY'
 | |
|     elif protocols == set([]):
 | |
|         return 'OFPUTIL_P_NONE'
 | |
|     else:
 | |
|         assert False
 | |
| 
 | |
| 
 | |
| def make_meta_flow(fields):
 | |
|     output = []
 | |
|     for f in fields:
 | |
|         output += ["{"]
 | |
|         output += ["    %s," % f['mff']]
 | |
|         if f['extra_name']:
 | |
|             output += ["    \"%s\", \"%s\"," % (f['name'], f['extra_name'])]
 | |
|         else:
 | |
|             output += ["    \"%s\", NULL," % f['name']]
 | |
| 
 | |
|         if f['variable']:
 | |
|             variable = 'true'
 | |
|         else:
 | |
|             variable = 'false'
 | |
|         output += ["    %d, %d, %s," % (f['n_bytes'], f['n_bits'], variable)]
 | |
| 
 | |
|         if f['writable']:
 | |
|             rw = 'true'
 | |
|         else:
 | |
|             rw = 'false'
 | |
|         output += ["    %s, %s, %s, %s,"
 | |
|                    % (f['mask'], f['string'], f['prereqs'], rw)]
 | |
| 
 | |
|         oxm = f['OXM']
 | |
|         of10 = f['OF1.0']
 | |
|         of11 = f['OF1.1']
 | |
|         if f['mff'] in ('MFF_DL_VLAN', 'MFF_DL_VLAN_PCP'):
 | |
|             # MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to
 | |
|             # OF1.1, nor do they have NXM or OXM assignments, but their
 | |
|             # meanings can be expressed in every protocol, which is the goal of
 | |
|             # this member.
 | |
|             protocols = set(["of10", "of11", "oxm"])
 | |
|         else:
 | |
|             protocols = set([])
 | |
|             if of10:
 | |
|                 protocols |= set(["of10"])
 | |
|             if of11:
 | |
|                 protocols |= set(["of11"])
 | |
|             if oxm:
 | |
|                 protocols |= set(["oxm"])
 | |
| 
 | |
|         if f['mask'] == 'MFM_FULLY':
 | |
|             cidr_protocols = protocols.copy()
 | |
|             bitwise_protocols = protocols.copy()
 | |
| 
 | |
|             if of10 == 'exact match':
 | |
|                 bitwise_protocols -= set(['of10'])
 | |
|                 cidr_protocols -= set(['of10'])
 | |
|             elif of10 == 'CIDR mask':
 | |
|                 bitwise_protocols -= set(['of10'])
 | |
|             else:
 | |
|                 assert of10 is None
 | |
| 
 | |
|             if of11 == 'exact match':
 | |
|                 bitwise_protocols -= set(['of11'])
 | |
|                 cidr_protocols -= set(['of11'])
 | |
|             else:
 | |
|                 assert of11 in (None, 'bitwise mask')
 | |
|         else:
 | |
|             assert f['mask'] == 'MFM_NONE'
 | |
|             cidr_protocols = set([])
 | |
|             bitwise_protocols = set([])
 | |
| 
 | |
|         output += ["    %s," % protocols_to_c(protocols)]
 | |
|         output += ["    %s," % protocols_to_c(cidr_protocols)]
 | |
|         output += ["    %s," % protocols_to_c(bitwise_protocols)]
 | |
| 
 | |
|         if f['prefix']:
 | |
|             output += ["    FLOW_U32OFS(%s)," % f['prefix']]
 | |
|         else:
 | |
|             output += ["    -1, /* not usable for prefix lookup */"]
 | |
| 
 | |
|         output += ["},"]
 | |
|     return output
 | |
| 
 | |
| 
 | |
| def make_nx_match(fields):
 | |
|     output = []
 | |
|     print("static struct nxm_field_index all_nxm_fields[] = {")
 | |
|     for f in fields:
 | |
|         # Sort by OpenFlow version number (nx-match.c depends on this).
 | |
|         for oxm in sorted(f['OXM'], key=lambda x: x[2]):
 | |
|             print("""{ .nf = { %s, %d, "%s", %s } },""" % (
 | |
|                 oxm[0], oxm[2], oxm[1], f['mff']))
 | |
|     print("};")
 | |
|     return output
 | |
| 
 | |
| 
 | |
| def extract_ofp_fields(mode):
 | |
|     global line
 | |
| 
 | |
|     fields = []
 | |
| 
 | |
|     while True:
 | |
|         get_line()
 | |
|         if re.match('enum.*mf_field_id', line):
 | |
|             break
 | |
| 
 | |
|     while True:
 | |
|         get_line()
 | |
|         first_line_number = line_number
 | |
|         here = '%s:%d' % (file_name, line_number)
 | |
|         if (line.startswith('/*')
 | |
|             or line.startswith(' *')
 | |
|             or line.startswith('#')
 | |
|             or not line
 | |
|             or line.isspace()):
 | |
|             continue
 | |
|         elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
 | |
|             break
 | |
| 
 | |
|         # Parse the comment preceding an MFF_ constant into 'comment',
 | |
|         # one line to an array element.
 | |
|         line = line.strip()
 | |
|         if not line.startswith('/*'):
 | |
|             fatal("unexpected syntax between fields")
 | |
|         line = line[1:]
 | |
|         comment = []
 | |
|         end = False
 | |
|         while not end:
 | |
|             line = line.strip()
 | |
|             if line.startswith('*/'):
 | |
|                 get_line()
 | |
|                 break
 | |
|             if not line.startswith('*'):
 | |
|                 fatal("unexpected syntax within field")
 | |
| 
 | |
|             line = line[1:]
 | |
|             if line.startswith(' '):
 | |
|                 line = line[1:]
 | |
|             if line.startswith(' ') and comment:
 | |
|                 continuation = True
 | |
|                 line = line.lstrip()
 | |
|             else:
 | |
|                 continuation = False
 | |
| 
 | |
|             if line.endswith('*/'):
 | |
|                 line = line[:-2].rstrip()
 | |
|                 end = True
 | |
|             else:
 | |
|                 end = False
 | |
| 
 | |
|             if continuation:
 | |
|                 comment[-1] += " " + line
 | |
|             else:
 | |
|                 comment += [line]
 | |
|             get_line()
 | |
| 
 | |
|         # Drop blank lines at each end of comment.
 | |
|         while comment and not comment[0]:
 | |
|             comment = comment[1:]
 | |
|         while comment and not comment[-1]:
 | |
|             comment = comment[:-1]
 | |
| 
 | |
|         # Parse the MFF_ constant(s).
 | |
|         mffs = []
 | |
|         while True:
 | |
|             m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
 | |
|             if not m:
 | |
|                 break
 | |
|             mffs += [m.group(1)]
 | |
|             get_line()
 | |
|         if not mffs:
 | |
|             fatal("unexpected syntax looking for MFF_ constants")
 | |
| 
 | |
|         if len(mffs) > 1 or '<N>' in comment[0]:
 | |
|             for mff in mffs:
 | |
|                 # Extract trailing integer.
 | |
|                 m = re.match('.*[^0-9]([0-9]+)$', mff)
 | |
|                 if not m:
 | |
|                     fatal("%s lacks numeric suffix in register group" % mff)
 | |
|                 n = m.group(1)
 | |
| 
 | |
|                 # Search-and-replace <N> within the comment,
 | |
|                 # and drop lines that have <x> for x != n.
 | |
|                 instance = []
 | |
|                 for x in comment:
 | |
|                     y = x.replace('<N>', n)
 | |
|                     if re.search('<[0-9]+>', y):
 | |
|                         if ('<%s>' % n) not in y:
 | |
|                             continue
 | |
|                         y = re.sub('<[0-9]+>', '', y)
 | |
|                     instance += [y.strip()]
 | |
|                 fields += [parse_field(mff, instance)]
 | |
|         else:
 | |
|             fields += [parse_field(mffs[0], comment)]
 | |
|         continue
 | |
| 
 | |
|     input_file.close()
 | |
| 
 | |
|     if n_errors:
 | |
|         sys.exit(1)
 | |
| 
 | |
|     print("""\
 | |
| /* Generated automatically; do not modify!    "-*- buffer-read-only: t -*- */
 | |
| """)
 | |
| 
 | |
|     if mode == '--meta-flow':
 | |
|         output = make_meta_flow(fields)
 | |
|     elif mode == '--nx-match':
 | |
|         output = make_nx_match(fields)
 | |
|     else:
 | |
|         assert False
 | |
| 
 | |
|     return output
 | |
| 
 | |
| 
 | |
| 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] in ('--meta-flow', '--nx-match'):
 | |
|         global file_name
 | |
|         global input_file
 | |
|         global line_number
 | |
|         file_name = sys.argv[1]
 | |
|         input_file = open(file_name)
 | |
|         line_number = 0
 | |
| 
 | |
|         for oline in extract_ofp_fields(sys.argv[2]):
 | |
|             print(oline)
 | |
|     else:
 | |
|         sys.stderr.write("invalid arguments; use --help for help\n")
 | |
|         sys.exit(1)
 |