9 # Maps from user-friendly version number to its protocol encoding.
10 VERSION = {"1.0": 0x01,
17 TYPES = {"u8": (1, False),
23 "tunnelMD": (124, True)}
25 FORMATTING = {"decimal": ("MFS_DECIMAL", 1, 8),
26 "hexadecimal": ("MFS_HEXADECIMAL", 1, 127),
27 "ct state": ("MFS_CT_STATE", 4, 4),
28 "Ethernet": ("MFS_ETHERNET", 6, 6),
29 "IPv4": ("MFS_IPV4", 4, 4),
30 "IPv6": ("MFS_IPV6", 16, 16),
31 "OpenFlow 1.0 port": ("MFS_OFP_PORT", 2, 2),
32 "OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4, 4),
33 "frag": ("MFS_FRAG", 1, 1),
34 "tunnel flags": ("MFS_TNL_FLAGS", 2, 2),
35 "TCP flags": ("MFS_TCP_FLAGS", 2, 2)}
37 PREREQS = {"none": "MFP_NONE",
39 "VLAN VID": "MFP_VLAN_VID",
42 "IPv4/IPv6": "MFP_IP_ANY",
47 "ICMPv4": "MFP_ICMPV4",
48 "ICMPv6": "MFP_ICMPV6",
50 "ND solicit": "MFP_ND_SOLICIT",
51 "ND advert": "MFP_ND_ADVERT"}
53 # Maps a name prefix into an (experimenter ID, class) pair, so:
55 # - Standard OXM classes are written as (0, <oxm_class>)
57 # - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
59 # If a name matches more than one prefix, the longest one is used.
60 OXM_CLASSES = {"NXM_OF_": (0, 0x0000),
61 "NXM_NX_": (0, 0x0001),
62 "OXM_OF_": (0, 0x8000),
63 "OXM_OF_PKT_REG": (0, 0x8001),
64 "ONFOXM_ET_": (0x4f4e4600, 0xffff),
66 # This is the experimenter OXM class for Nicira, which is the
67 # one that OVS would be using instead of NXM_OF_ and NXM_NX_
68 # if OVS didn't have those grandfathered in. It is currently
69 # used only to test support for experimenter OXM, since there
70 # are barely any real uses of experimenter OXM in the wild.
71 "NXOXM_ET_": (0x00002320, 0xffff)}
74 def oxm_name_to_class(name):
77 for p, c in OXM_CLASSES.items():
78 if name.startswith(p) and len(p) > len(prefix):
84 def decode_version_range(range):
86 return (VERSION[range], VERSION[range])
87 elif range.endswith('+'):
88 return (VERSION[range[:-1]], max(VERSION.values()))
90 a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
91 return (VERSION[a], VERSION[b])
97 line = input_file.readline()
100 fatal("unexpected end of input")
108 sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
118 argv0 = os.path.basename(sys.argv[0])
120 %(argv0)s, for extracting OpenFlow field properties from meta-flow.h
121 usage: %(argv0)s INPUT [--meta-flow | --nx-match]
122 where INPUT points to lib/meta-flow.h in the source directory.
123 Depending on the option given, the output written to stdout is intended to be
124 saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C
126 ''' % {"argv0": argv0})
131 m = re.match(r'(.*) up to (.*)', s)
133 struct, member = m.groups()
134 return "offsetof(%s, %s)" % (struct, member)
136 return "sizeof(%s)" % s
139 def parse_oxms(s, prefix, n_bytes):
143 return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in s.split(','))
149 def parse_oxm(s, prefix, n_bytes):
152 m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s)
154 fatal("%s: syntax error parsing %s" % (s, prefix))
156 name, oxm_type, of_version, ovs_version = m.groups()
158 class_ = oxm_name_to_class(name)
160 fatal("unknown OXM class for %s" % name)
161 oxm_vendor, oxm_class = class_
163 if class_ in match_types:
164 if oxm_type in match_types[class_]:
165 fatal("duplicate match type for %s (conflicts with %s)" %
166 (name, match_types[class_][oxm_type]))
168 match_types[class_] = dict()
169 match_types[class_][oxm_type] = name
171 # Normally the oxm_length is the size of the field, but for experimenter
172 # OXMs oxm_length also includes the 4-byte experimenter ID.
174 if oxm_class == 0xffff:
177 header = ("NXM_HEADER(0x%x,0x%x,%s,0,%d)"
178 % (oxm_vendor, oxm_class, oxm_type, oxm_length))
181 if of_version not in VERSION:
182 fatal("%s: unknown OpenFlow version %s" % (name, of_version))
183 of_version_nr = VERSION[of_version]
184 if of_version_nr < VERSION['1.2']:
185 fatal("%s: claimed version %s predates OXM" % (name, of_version))
189 return (header, name, of_version_nr, ovs_version)
192 def parse_field(mff, comment):
195 # First line of comment is the field name.
196 m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0])
198 fatal("%s lacks field name" % mff)
199 f['name'], f['extra_name'] = m.groups()
201 # Find the last blank line the comment. The field definitions
204 for i in range(len(comment)):
208 fatal("%s: missing blank line in comment" % mff)
211 for key in ("Type", "Maskable", "Formatting", "Prerequisites",
212 "Access", "Prefix lookup member",
213 "OXM", "NXM", "OF1.0", "OF1.1"):
215 for fline in comment[blank + 1:]:
216 m = re.match(r'([^:]+):\s+(.*)\.$', fline)
218 fatal("%s: syntax error parsing key-value pair as part of %s"
220 key, value = m.groups()
222 fatal("%s: unknown key" % key)
223 elif key == 'Code point':
225 elif d[key] is not None:
226 fatal("%s: duplicate key" % key)
228 for key, value in d.items():
229 if not value and key not in ("OF1.0", "OF1.1",
230 "Prefix lookup member", "Notes"):
231 fatal("%s: missing %s" % (mff, key))
233 m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
235 fatal("%s: syntax error in type" % mff)
237 if type_ not in TYPES:
238 fatal("%s: unknown type %s" % (mff, d['Type']))
240 f['n_bytes'] = TYPES[type_][0]
242 f['n_bits'] = int(m.group(2))
243 if f['n_bits'] > f['n_bytes'] * 8:
244 fatal("%s: more bits (%d) than field size (%d)"
245 % (mff, f['n_bits'], 8 * f['n_bytes']))
247 f['n_bits'] = 8 * f['n_bytes']
248 f['variable'] = TYPES[type_][1]
250 if d['Maskable'] == 'no':
251 f['mask'] = 'MFM_NONE'
252 elif d['Maskable'] == 'bitwise':
253 f['mask'] = 'MFM_FULLY'
255 fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
257 fmt = FORMATTING.get(d['Formatting'])
259 fatal("%s: unknown format %s" % (mff, d['Formatting']))
260 if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
261 fatal("%s: %d-byte field can't be formatted as %s"
262 % (mff, f['n_bytes'], d['Formatting']))
265 f['prereqs'] = PREREQS.get(d['Prerequisites'])
267 fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
269 if d['Access'] == 'read-only':
270 f['writable'] = False
271 elif d['Access'] == 'read/write':
274 fatal("%s: unknown access %s" % (mff, d['Access']))
276 f['OF1.0'] = d['OF1.0']
277 if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
278 fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
280 f['OF1.1'] = d['OF1.1']
281 if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
282 fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
284 f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) +
285 parse_oxms(d['NXM'], 'NXM', f['n_bytes']))
287 f['prefix'] = d["Prefix lookup member"]
292 def protocols_to_c(protocols):
293 if protocols == set(['of10', 'of11', 'oxm']):
294 return 'OFPUTIL_P_ANY'
295 elif protocols == set(['of11', 'oxm']):
296 return 'OFPUTIL_P_NXM_OF11_UP'
297 elif protocols == set(['oxm']):
298 return 'OFPUTIL_P_NXM_OXM_ANY'
299 elif protocols == set([]):
300 return 'OFPUTIL_P_NONE'
305 def make_meta_flow(fields):
309 output += [" %s," % f['mff']]
311 output += [" \"%s\", \"%s\"," % (f['name'], f['extra_name'])]
313 output += [" \"%s\", NULL," % f['name']]
319 output += [" %d, %d, %s," % (f['n_bytes'], f['n_bits'], variable)]
325 output += [" %s, %s, %s, %s,"
326 % (f['mask'], f['string'], f['prereqs'], rw)]
331 if f['mff'] in ('MFF_DL_VLAN', 'MFF_DL_VLAN_PCP'):
332 # MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to
333 # OF1.1, nor do they have NXM or OXM assignments, but their
334 # meanings can be expressed in every protocol, which is the goal of
336 protocols = set(["of10", "of11", "oxm"])
340 protocols |= set(["of10"])
342 protocols |= set(["of11"])
344 protocols |= set(["oxm"])
346 if f['mask'] == 'MFM_FULLY':
347 cidr_protocols = protocols.copy()
348 bitwise_protocols = protocols.copy()
350 if of10 == 'exact match':
351 bitwise_protocols -= set(['of10'])
352 cidr_protocols -= set(['of10'])
353 elif of10 == 'CIDR mask':
354 bitwise_protocols -= set(['of10'])
358 if of11 == 'exact match':
359 bitwise_protocols -= set(['of11'])
360 cidr_protocols -= set(['of11'])
362 assert of11 in (None, 'bitwise mask')
364 assert f['mask'] == 'MFM_NONE'
365 cidr_protocols = set([])
366 bitwise_protocols = set([])
368 output += [" %s," % protocols_to_c(protocols)]
369 output += [" %s," % protocols_to_c(cidr_protocols)]
370 output += [" %s," % protocols_to_c(bitwise_protocols)]
373 output += [" FLOW_U32OFS(%s)," % f['prefix']]
375 output += [" -1, /* not usable for prefix lookup */"]
381 def make_nx_match(fields):
383 print("static struct nxm_field_index all_nxm_fields[] = {")
385 # Sort by OpenFlow version number (nx-match.c depends on this).
386 for oxm in sorted(f['OXM'], key=lambda x: x[2]):
387 print("""{ .nf = { %s, %d, "%s", %s } },""" % (
388 oxm[0], oxm[2], oxm[1], f['mff']))
393 def extract_ofp_fields(mode):
400 if re.match('enum.*mf_field_id', line):
405 first_line_number = line_number
406 here = '%s:%d' % (file_name, line_number)
407 if (line.startswith('/*')
408 or line.startswith(' *')
409 or line.startswith('#')
413 elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
416 # Parse the comment preceding an MFF_ constant into 'comment',
417 # one line to an array element.
419 if not line.startswith('/*'):
420 fatal("unexpected syntax between fields")
426 if line.startswith('*/'):
429 if not line.startswith('*'):
430 fatal("unexpected syntax within field")
433 if line.startswith(' '):
435 if line.startswith(' ') and comment:
441 if line.endswith('*/'):
442 line = line[:-2].rstrip()
448 comment[-1] += " " + line
453 # Drop blank lines at each end of comment.
454 while comment and not comment[0]:
455 comment = comment[1:]
456 while comment and not comment[-1]:
457 comment = comment[:-1]
459 # Parse the MFF_ constant(s).
462 m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
468 fatal("unexpected syntax looking for MFF_ constants")
470 if len(mffs) > 1 or '<N>' in comment[0]:
472 # Extract trailing integer.
473 m = re.match('.*[^0-9]([0-9]+)$', mff)
475 fatal("%s lacks numeric suffix in register group" % mff)
478 # Search-and-replace <N> within the comment,
479 # and drop lines that have <x> for x != n.
482 y = x.replace('<N>', n)
483 if re.search('<[0-9]+>', y):
484 if ('<%s>' % n) not in y:
486 y = re.sub('<[0-9]+>', '', y)
487 instance += [y.strip()]
488 fields += [parse_field(mff, instance)]
490 fields += [parse_field(mffs[0], comment)]
499 /* Generated automatically; do not modify! "-*- buffer-read-only: t -*- */
502 if mode == '--meta-flow':
503 output = make_meta_flow(fields)
504 elif mode == '--nx-match':
505 output = make_nx_match(fields)
512 if __name__ == '__main__':
513 if '--help' in sys.argv:
515 elif len(sys.argv) != 3:
516 sys.stderr.write("exactly two arguments required; "
517 "use --help for help\n")
519 elif sys.argv[2] in ('--meta-flow', '--nx-match'):
523 file_name = sys.argv[1]
524 input_file = open(file_name)
527 for oline in extract_ofp_fields(sys.argv[2]):
530 sys.stderr.write("invalid arguments; use --help for help\n")