Add support for connection tracking.
[cascardo/ovs.git] / build-aux / extract-ofp-fields
index 5543647..8d43e4b 100755 (executable)
@@ -14,55 +14,73 @@ VERSION = {"1.0": 0x01,
            "1.4": 0x05,
            "1.5": 0x06}
 
-TYPES = {"u8": 1,
-         "be16": 2,
-         "be32": 4,
-         "MAC": 6,
-         "be64": 8,
-         "IPv6": 16}
-
-FORMATTING = {"decimal":            ("MFS_DECIMAL",      1, 8),
-              "hexadecimal":        ("MFS_HEXADECIMAL",  1, 8),
-              "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)}
+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 to an oxm_class.
+           "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_": 0x0000,
-               "NXM_NX_": 0x0001,
-               "OXM_OF_": 0x8000,
-               "OXM_OF_PKT_REG": 0x8001}
+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.iteritems():
+    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])
@@ -72,6 +90,7 @@ def decode_version_range(range):
         a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
         return (VERSION[a], VERSION[b])
 
+
 def get_line():
     global line
     global line_number
@@ -80,27 +99,34 @@ def get_line():
     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 '''\
+    print('''\
 %(argv0)s, for extracting OpenFlow field properties from meta-flow.h
-usage: %(argv0)s INPUT
+usage: %(argv0)s INPUT [--meta-flow | --nx-match]
   where INPUT points to lib/meta-flow.h in the source directory.
-The output written to stdout is intended to be saved as lib/meta-flow.inc,
-which lib/meta-flow.c \"#include\"s.\
-''' % {"argv0": argv0}
+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:
@@ -109,20 +135,47 @@ def make_sizeof(s):
     else:
         return "sizeof(%s)" % s
 
-def parse_oxm(s, prefix, n_bytes):
+
+def parse_oxms(s, prefix, n_bytes):
     if s == 'none':
-        return 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, code, of_version, ovs_version = m.groups()
+
+    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)
-    header = ("NXM_HEADER(0x%04x, %s, %d)" % (class_, code, n_bytes))
+    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:
@@ -135,6 +188,7 @@ def parse_oxm(s, prefix, n_bytes):
 
     return (header, name, of_version_nr, ovs_version)
 
+
 def parse_field(mff, comment):
     f = {'mff': mff}
 
@@ -171,7 +225,7 @@ def parse_field(mff, comment):
         elif d[key] is not None:
             fatal("%s: duplicate key" % key)
         d[key] = value
-    for key, value in d.iteritems():
+    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))
@@ -182,7 +236,8 @@ def parse_field(mff, comment):
     type_ = m.group(1)
     if type_ not in TYPES:
         fatal("%s: unknown type %s" % (mff, d['Type']))
-    f['n_bytes'] = TYPES[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:
@@ -190,6 +245,7 @@ def parse_field(mff, comment):
                   % (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'
@@ -220,18 +276,19 @@ def parse_field(mff, comment):
     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_oxm(d['OXM'], 'OXM', f['n_bytes'])
-    f['NXM'] = parse_oxm(d['NXM'], 'NXM', f['n_bytes'])
+    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'
@@ -242,9 +299,98 @@ def protocols_to_c(protocols):
     elif protocols == set([]):
         return 'OFPUTIL_P_NONE'
     else:
-        assert False        
+        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)]
 
-def extract_ofp_fields():
+        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 = []
@@ -349,91 +495,16 @@ def extract_ofp_fields():
     if n_errors:
         sys.exit(1)
 
-    output = []
-    output += ["/* Generated automatically; do not modify!     "
-               "-*- buffer-read-only: t -*- */"]
-    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']]
-        output += ["    %d, %d," % (f['n_bytes'], f['n_bits'])]
-
-        if f['writable']:
-            rw = 'true'
-        else:
-            rw = 'false'
-        output += ["    %s, %s, %s, %s,"
-                   % (f['mask'], f['string'], f['prereqs'], rw)]
-
-        nxm = f['NXM']
-        oxm = f['OXM']
-        if not nxm:
-            nxm = oxm
-        elif not oxm:
-            oxm = nxm
-        if nxm:
-            output += ["    %s, \"%s\"," % (nxm[0], nxm[1])]
-            output += ["    %s, \"%s\", %s," % (oxm[0], oxm[1], oxm[2])]
-        else:
-            output += ["    0, NULL, 0, NULL, 0, /* no NXM or 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 nxm or 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 += ["},"]
+    print("""\
+/* Generated automatically; do not modify!    "-*- buffer-read-only: t -*- */
+""")
 
-    if n_errors:
-        sys.exit(1)
+    if mode == '--meta-flow':
+        output = make_meta_flow(fields)
+    elif mode == '--nx-match':
+        output = make_nx_match(fields)
+    else:
+        assert False
 
     return output
 
@@ -441,11 +512,11 @@ def extract_ofp_fields():
 if __name__ == '__main__':
     if '--help' in sys.argv:
         usage()
-    elif len(sys.argv) != 2:
-        sys.stderr.write("exactly one non-option argument required; "
+    elif len(sys.argv) != 3:
+        sys.stderr.write("exactly two arguments required; "
                          "use --help for help\n")
         sys.exit(1)
-    else:
+    elif sys.argv[2] in ('--meta-flow', '--nx-match'):
         global file_name
         global input_file
         global line_number
@@ -453,6 +524,8 @@ if __name__ == '__main__':
         input_file = open(file_name)
         line_number = 0
 
-        for oline in extract_ofp_fields():
-            print oline
-        
+        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)