]> git.proxmox.com Git - mirror_ovs.git/blame - build-aux/extract-ofp-fields
flow: Support OF1.5+ (draft) actset_output field.
[mirror_ovs.git] / build-aux / extract-ofp-fields
CommitLineData
a4ce8b25
BP
1#! /usr/bin/python
2
3import sys
4import os.path
5import re
6
7line = ""
8
9# Maps from user-friendly version number to its protocol encoding.
10VERSION = {"1.0": 0x01,
11 "1.1": 0x02,
12 "1.2": 0x03,
13 "1.3": 0x04,
14 "1.4": 0x05,
15 "1.5": 0x06}
16
17TYPES = {"u8": 1,
18 "be16": 2,
19 "be32": 4,
20 "MAC": 6,
21 "be64": 8,
22 "IPv6": 16}
23
24FORMATTING = {"decimal": ("MFS_DECIMAL", 1, 8),
25 "hexadecimal": ("MFS_HEXADECIMAL", 1, 8),
26 "Ethernet": ("MFS_ETHERNET", 6, 6),
27 "IPv4": ("MFS_IPV4", 4, 4),
28 "IPv6": ("MFS_IPV6", 16,16),
29 "OpenFlow 1.0 port": ("MFS_OFP_PORT", 2, 2),
30 "OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4, 4),
31 "frag": ("MFS_FRAG", 1, 1),
32 "tunnel flags": ("MFS_TNL_FLAGS", 2, 2),
33 "TCP flags": ("MFS_TCP_FLAGS", 2, 2)}
34
35PREREQS = {"none": "MFP_NONE",
36 "ARP": "MFP_ARP",
37 "VLAN VID": "MFP_VLAN_VID",
38 "IPv4": "MFP_IPV4",
39 "IPv6": "MFP_IPV6",
40 "IPv4/IPv6": "MFP_IP_ANY",
41 "MPLS": "MFP_MPLS",
42 "TCP": "MFP_TCP",
43 "UDP": "MFP_UDP",
44 "SCTP": "MFP_SCTP",
45 "ICMPv4": "MFP_ICMPV4",
46 "ICMPv6": "MFP_ICMPV6",
47 "ND": "MFP_ND",
48 "ND solicit": "MFP_ND_SOLICIT",
49 "ND advert": "MFP_ND_ADVERT"}
50
508a9338
BP
51# Maps a name prefix into an (experimenter ID, class) pair, so:
52#
53# - Standard OXM classes are written as (0, <oxm_class>)
54#
55# - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
56#
a4ce8b25 57# If a name matches more than one prefix, the longest one is used.
508a9338
BP
58OXM_CLASSES = {"NXM_OF_": (0, 0x0000),
59 "NXM_NX_": (0, 0x0001),
60 "OXM_OF_": (0, 0x8000),
61 "OXM_OF_PKT_REG": (0, 0x8001),
c61f3870 62 "ONFOXM_ET_": (0x4f4e4600, 0xffff),
508a9338
BP
63
64 # This is the experimenter OXM class for Nicira, which is the
65 # one that OVS would be using instead of NXM_OF_ and NXM_NX_
66 # if OVS didn't have those grandfathered in. It is currently
67 # used only to test support for experimenter OXM, since there
68 # are barely any real uses of experimenter OXM in the wild.
69 "NXOXM_ET_": (0x00002320, 0xffff)}
a4ce8b25
BP
70def oxm_name_to_class(name):
71 prefix = ''
72 class_ = None
73 for p, c in OXM_CLASSES.iteritems():
74 if name.startswith(p) and len(p) > len(prefix):
75 prefix = p
76 class_ = c
77 return class_
78
79def decode_version_range(range):
80 if range in VERSION:
81 return (VERSION[range], VERSION[range])
82 elif range.endswith('+'):
83 return (VERSION[range[:-1]], max(VERSION.values()))
84 else:
85 a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
86 return (VERSION[a], VERSION[b])
87
88def get_line():
89 global line
90 global line_number
91 line = input_file.readline()
92 line_number += 1
93 if line == "":
94 fatal("unexpected end of input")
95
96n_errors = 0
97def error(msg):
98 global n_errors
99 sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
100 n_errors += 1
101
102def fatal(msg):
103 error(msg)
104 sys.exit(1)
105
106def usage():
107 argv0 = os.path.basename(sys.argv[0])
108 print '''\
109%(argv0)s, for extracting OpenFlow field properties from meta-flow.h
178742f9 110usage: %(argv0)s INPUT [--meta-flow | --nx-match]
a4ce8b25 111 where INPUT points to lib/meta-flow.h in the source directory.
178742f9
BP
112Depending on the option given, the output written to stdout is intended to be
113saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C
114file to #include.\
a4ce8b25
BP
115''' % {"argv0": argv0}
116 sys.exit(0)
117
118def make_sizeof(s):
119 m = re.match(r'(.*) up to (.*)', s)
120 if m:
121 struct, member = m.groups()
122 return "offsetof(%s, %s)" % (struct, member)
123 else:
124 return "sizeof(%s)" % s
125
e6556fe3 126def parse_oxms(s, prefix, n_bytes):
a4ce8b25 127 if s == 'none':
e6556fe3
BP
128 return ()
129
130 return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in s.split(','))
a4ce8b25 131
e6556fe3 132def parse_oxm(s, prefix, n_bytes):
a4ce8b25
BP
133 m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s)
134 if not m:
135 fatal("%s: syntax error parsing %s" % (s, prefix))
136
508a9338 137 name, oxm_type, of_version, ovs_version = m.groups()
a4ce8b25
BP
138
139 class_ = oxm_name_to_class(name)
140 if class_ is None:
141 fatal("unknown OXM class for %s" % name)
508a9338
BP
142 oxm_vendor, oxm_class = class_
143
144 # Normally the oxm_length is the size of the field, but for experimenter
145 # OXMs oxm_length also includes the 4-byte experimenter ID.
146 oxm_length = n_bytes
147 if oxm_class == 0xffff:
148 oxm_length += 4
149
150 header = ("NXM_HEADER(0x%x,0x%x,%s,0,%d)"
151 % (oxm_vendor, oxm_class, oxm_type, oxm_length))
a4ce8b25
BP
152
153 if of_version:
154 if of_version not in VERSION:
155 fatal("%s: unknown OpenFlow version %s" % (name, of_version))
156 of_version_nr = VERSION[of_version]
157 if of_version_nr < VERSION['1.2']:
158 fatal("%s: claimed version %s predates OXM" % (name, of_version))
159 else:
160 of_version_nr = 0
161
162 return (header, name, of_version_nr, ovs_version)
163
164def parse_field(mff, comment):
165 f = {'mff': mff}
166
167 # First line of comment is the field name.
168 m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0])
169 if not m:
170 fatal("%s lacks field name" % mff)
171 f['name'], f['extra_name'] = m.groups()
172
173 # Find the last blank line the comment. The field definitions
174 # start after that.
175 blank = None
176 for i in range(len(comment)):
177 if not comment[i]:
178 blank = i
179 if not blank:
180 fatal("%s: missing blank line in comment" % mff)
181
182 d = {}
183 for key in ("Type", "Maskable", "Formatting", "Prerequisites",
184 "Access", "Prefix lookup member",
185 "OXM", "NXM", "OF1.0", "OF1.1"):
186 d[key] = None
187 for fline in comment[blank + 1:]:
188 m = re.match(r'([^:]+):\s+(.*)\.$', fline)
189 if not m:
190 fatal("%s: syntax error parsing key-value pair as part of %s"
191 % (fline, mff))
192 key, value = m.groups()
193 if key not in d:
194 fatal("%s: unknown key" % key)
195 elif key == 'Code point':
196 d[key] += [value]
197 elif d[key] is not None:
198 fatal("%s: duplicate key" % key)
199 d[key] = value
200 for key, value in d.iteritems():
201 if not value and key not in ("OF1.0", "OF1.1",
202 "Prefix lookup member", "Notes"):
203 fatal("%s: missing %s" % (mff, key))
204
205 m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
206 if not m:
207 fatal("%s: syntax error in type" % mff)
208 type_ = m.group(1)
209 if type_ not in TYPES:
210 fatal("%s: unknown type %s" % (mff, d['Type']))
211 f['n_bytes'] = TYPES[type_]
212 if m.group(2):
213 f['n_bits'] = int(m.group(2))
214 if f['n_bits'] > f['n_bytes'] * 8:
215 fatal("%s: more bits (%d) than field size (%d)"
216 % (mff, f['n_bits'], 8 * f['n_bytes']))
217 else:
218 f['n_bits'] = 8 * f['n_bytes']
219
220 if d['Maskable'] == 'no':
221 f['mask'] = 'MFM_NONE'
222 elif d['Maskable'] == 'bitwise':
223 f['mask'] = 'MFM_FULLY'
224 else:
225 fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
226
227 fmt = FORMATTING.get(d['Formatting'])
228 if not fmt:
229 fatal("%s: unknown format %s" % (mff, d['Formatting']))
230 if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
231 fatal("%s: %d-byte field can't be formatted as %s"
232 % (mff, f['n_bytes'], d['Formatting']))
233 f['string'] = fmt[0]
234
235 f['prereqs'] = PREREQS.get(d['Prerequisites'])
236 if not f['prereqs']:
237 fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
238
239 if d['Access'] == 'read-only':
240 f['writable'] = False
241 elif d['Access'] == 'read/write':
242 f['writable'] = True
243 else:
244 fatal("%s: unknown access %s" % (mff, d['Access']))
245
246 f['OF1.0'] = d['OF1.0']
247 if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
248 fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
249
250 f['OF1.1'] = d['OF1.1']
251 if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
252 fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
253
e6556fe3
BP
254 f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) +
255 parse_oxms(d['NXM'], 'NXM', f['n_bytes']))
a4ce8b25
BP
256
257 f['prefix'] = d["Prefix lookup member"]
258
259 return f
260
261def protocols_to_c(protocols):
262 if protocols == set(['of10', 'of11', 'oxm']):
263 return 'OFPUTIL_P_ANY'
264 elif protocols == set(['of11', 'oxm']):
265 return 'OFPUTIL_P_NXM_OF11_UP'
266 elif protocols == set(['oxm']):
267 return 'OFPUTIL_P_NXM_OXM_ANY'
268 elif protocols == set([]):
269 return 'OFPUTIL_P_NONE'
270 else:
271 assert False
272
178742f9
BP
273def make_meta_flow(fields):
274 output = []
275 for f in fields:
276 output += ["{"]
277 output += [" %s," % f['mff']]
278 if f['extra_name']:
279 output += [" \"%s\", \"%s\"," % (f['name'], f['extra_name'])]
280 else:
281 output += [" \"%s\", NULL," % f['name']]
282 output += [" %d, %d," % (f['n_bytes'], f['n_bits'])]
283
284 if f['writable']:
285 rw = 'true'
286 else:
287 rw = 'false'
288 output += [" %s, %s, %s, %s,"
289 % (f['mask'], f['string'], f['prereqs'], rw)]
290
178742f9 291 oxm = f['OXM']
178742f9
BP
292 of10 = f['OF1.0']
293 of11 = f['OF1.1']
294 if f['mff'] in ('MFF_DL_VLAN', 'MFF_DL_VLAN_PCP'):
295 # MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to
296 # OF1.1, nor do they have NXM or OXM assignments, but their
297 # meanings can be expressed in every protocol, which is the goal of
298 # this member.
299 protocols = set(["of10", "of11", "oxm"])
300 else:
301 protocols = set([])
302 if of10:
303 protocols |= set(["of10"])
304 if of11:
305 protocols |= set(["of11"])
e6556fe3 306 if oxm:
178742f9
BP
307 protocols |= set(["oxm"])
308
309 if f['mask'] == 'MFM_FULLY':
310 cidr_protocols = protocols.copy()
311 bitwise_protocols = protocols.copy()
312
313 if of10 == 'exact match':
314 bitwise_protocols -= set(['of10'])
315 cidr_protocols -= set(['of10'])
316 elif of10 == 'CIDR mask':
317 bitwise_protocols -= set(['of10'])
318 else:
319 assert of10 is None
320
321 if of11 == 'exact match':
322 bitwise_protocols -= set(['of11'])
323 cidr_protocols -= set(['of11'])
324 else:
325 assert of11 in (None, 'bitwise mask')
326 else:
327 assert f['mask'] == 'MFM_NONE'
328 cidr_protocols = set([])
329 bitwise_protocols = set([])
330
331 output += [" %s," % protocols_to_c(protocols)]
332 output += [" %s," % protocols_to_c(cidr_protocols)]
333 output += [" %s," % protocols_to_c(bitwise_protocols)]
334
335 if f['prefix']:
336 output += [" FLOW_U32OFS(%s)," % f['prefix']]
337 else:
338 output += [" -1, /* not usable for prefix lookup */"]
339
340 output += ["},"]
341 return output
342
178742f9
BP
343def make_nx_match(fields):
344 output = []
345 print "static struct nxm_field_index all_nxm_fields[] = {";
346 for f in fields:
e6556fe3
BP
347 # Sort by OpenFlow version number (nx-match.c depends on this).
348 for oxm in sorted(f['OXM'], key=lambda x: x[2]):
349 print """{ .nf = { %s, %d, "%s", %s } },""" % (
350 oxm[0], oxm[2], oxm[1], f['mff'])
178742f9
BP
351 print "};"
352 return output
353
354def extract_ofp_fields(mode):
a4ce8b25
BP
355 global line
356
357 fields = []
358
359 while True:
360 get_line()
361 if re.match('enum.*mf_field_id', line):
362 break
363
364 while True:
365 get_line()
366 first_line_number = line_number
367 here = '%s:%d' % (file_name, line_number)
368 if (line.startswith('/*')
369 or line.startswith(' *')
370 or line.startswith('#')
371 or not line
372 or line.isspace()):
373 continue
374 elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
375 break
376
377 # Parse the comment preceding an MFF_ constant into 'comment',
378 # one line to an array element.
379 line = line.strip()
380 if not line.startswith('/*'):
381 fatal("unexpected syntax between fields")
382 line = line[1:]
383 comment = []
384 end = False
385 while not end:
386 line = line.strip()
387 if line.startswith('*/'):
388 get_line()
389 break
390 if not line.startswith('*'):
391 fatal("unexpected syntax within field")
392
393 line = line[1:]
394 if line.startswith(' '):
395 line = line[1:]
396 if line.startswith(' ') and comment:
397 continuation = True
398 line = line.lstrip()
399 else:
400 continuation = False
401
402 if line.endswith('*/'):
403 line = line[:-2].rstrip()
404 end = True
405 else:
406 end = False
407
408 if continuation:
409 comment[-1] += " " + line
410 else:
411 comment += [line]
412 get_line()
413
414 # Drop blank lines at each end of comment.
415 while comment and not comment[0]:
416 comment = comment[1:]
417 while comment and not comment[-1]:
418 comment = comment[:-1]
419
420 # Parse the MFF_ constant(s).
421 mffs = []
422 while True:
423 m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
424 if not m:
425 break
426 mffs += [m.group(1)]
427 get_line()
428 if not mffs:
429 fatal("unexpected syntax looking for MFF_ constants")
430
431 if len(mffs) > 1 or '<N>' in comment[0]:
432 for mff in mffs:
433 # Extract trailing integer.
434 m = re.match('.*[^0-9]([0-9]+)$', mff)
435 if not m:
436 fatal("%s lacks numeric suffix in register group" % mff)
437 n = m.group(1)
438
439 # Search-and-replace <N> within the comment,
440 # and drop lines that have <x> for x != n.
441 instance = []
442 for x in comment:
443 y = x.replace('<N>', n)
444 if re.search('<[0-9]+>', y):
445 if ('<%s>' % n) not in y:
446 continue
447 y = re.sub('<[0-9]+>', '', y)
448 instance += [y.strip()]
449 fields += [parse_field(mff, instance)]
450 else:
451 fields += [parse_field(mffs[0], comment)]
452 continue
453
454 input_file.close()
455
456 if n_errors:
457 sys.exit(1)
458
178742f9
BP
459 print """\
460/* Generated automatically; do not modify! "-*- buffer-read-only: t -*- */
461"""
a4ce8b25 462
178742f9
BP
463 if mode == '--meta-flow':
464 output = make_meta_flow(fields)
465 elif mode == '--nx-match':
466 output = make_nx_match(fields)
467 else:
468 assert False
a4ce8b25
BP
469
470 return output
471
472
473if __name__ == '__main__':
474 if '--help' in sys.argv:
475 usage()
178742f9
BP
476 elif len(sys.argv) != 3:
477 sys.stderr.write("exactly two arguments required; "
a4ce8b25
BP
478 "use --help for help\n")
479 sys.exit(1)
178742f9 480 elif sys.argv[2] in ('--meta-flow', '--nx-match'):
a4ce8b25
BP
481 global file_name
482 global input_file
483 global line_number
484 file_name = sys.argv[1]
485 input_file = open(file_name)
486 line_number = 0
487
178742f9 488 for oline in extract_ofp_fields(sys.argv[2]):
a4ce8b25 489 print oline
178742f9
BP
490 else:
491 sys.stderr.write("invalid arguments; use --help for help\n")
492 sys.exit(1)
493
a4ce8b25 494