]>
Commit | Line | Data |
---|---|---|
a4ce8b25 BP |
1 | #! /usr/bin/python |
2 | ||
3 | import sys | |
4 | import os.path | |
5 | import re | |
6 | ||
7 | line = "" | |
8 | ||
9 | # Maps from user-friendly version number to its protocol encoding. | |
10 | VERSION = {"1.0": 0x01, | |
11 | "1.1": 0x02, | |
12 | "1.2": 0x03, | |
13 | "1.3": 0x04, | |
14 | "1.4": 0x05, | |
15 | "1.5": 0x06} | |
16 | ||
17 | TYPES = {"u8": 1, | |
18 | "be16": 2, | |
19 | "be32": 4, | |
20 | "MAC": 6, | |
21 | "be64": 8, | |
22 | "IPv6": 16} | |
23 | ||
24 | FORMATTING = {"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 | ||
35 | PREREQS = {"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 |
58 | OXM_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 |
70 | def 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 | ||
79 | def 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 | ||
88 | def 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 | ||
96 | n_errors = 0 | |
97 | def error(msg): | |
98 | global n_errors | |
99 | sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg)) | |
100 | n_errors += 1 | |
101 | ||
102 | def fatal(msg): | |
103 | error(msg) | |
104 | sys.exit(1) | |
105 | ||
106 | def usage(): | |
107 | argv0 = os.path.basename(sys.argv[0]) | |
108 | print '''\ | |
109 | %(argv0)s, for extracting OpenFlow field properties from meta-flow.h | |
178742f9 | 110 | usage: %(argv0)s INPUT [--meta-flow | --nx-match] |
a4ce8b25 | 111 | where INPUT points to lib/meta-flow.h in the source directory. |
178742f9 BP |
112 | Depending on the option given, the output written to stdout is intended to be |
113 | saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C | |
114 | file to #include.\ | |
a4ce8b25 BP |
115 | ''' % {"argv0": argv0} |
116 | sys.exit(0) | |
117 | ||
118 | def 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 | 126 | def 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 | 132 | def 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 | ||
164 | def 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 | ||
261 | def 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 |
273 | def 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 |
343 | def 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 | ||
354 | def 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 | ||
473 | if __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 |