]>
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 | ||
56134136 JS |
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)} | |
a4ce8b25 BP |
34 | |
35 | PREREQS = {"none": "MFP_NONE", | |
56134136 JS |
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"} | |
a4ce8b25 | 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)} | |
56134136 JS |
70 | |
71 | ||
a4ce8b25 BP |
72 | def oxm_name_to_class(name): |
73 | prefix = '' | |
74 | class_ = None | |
1421f682 | 75 | for p, c in OXM_CLASSES.items(): |
a4ce8b25 BP |
76 | if name.startswith(p) and len(p) > len(prefix): |
77 | prefix = p | |
78 | class_ = c | |
79 | return class_ | |
80 | ||
56134136 | 81 | |
a4ce8b25 BP |
82 | def decode_version_range(range): |
83 | if range in VERSION: | |
84 | return (VERSION[range], VERSION[range]) | |
85 | elif range.endswith('+'): | |
86 | return (VERSION[range[:-1]], max(VERSION.values())) | |
87 | else: | |
88 | a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups() | |
89 | return (VERSION[a], VERSION[b]) | |
90 | ||
56134136 | 91 | |
a4ce8b25 BP |
92 | def get_line(): |
93 | global line | |
94 | global line_number | |
95 | line = input_file.readline() | |
96 | line_number += 1 | |
97 | if line == "": | |
98 | fatal("unexpected end of input") | |
99 | ||
56134136 | 100 | |
a4ce8b25 | 101 | n_errors = 0 |
56134136 JS |
102 | |
103 | ||
a4ce8b25 BP |
104 | def error(msg): |
105 | global n_errors | |
106 | sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg)) | |
107 | n_errors += 1 | |
108 | ||
56134136 | 109 | |
a4ce8b25 BP |
110 | def fatal(msg): |
111 | error(msg) | |
112 | sys.exit(1) | |
113 | ||
56134136 | 114 | |
a4ce8b25 BP |
115 | def usage(): |
116 | argv0 = os.path.basename(sys.argv[0]) | |
1421f682 | 117 | print('''\ |
a4ce8b25 | 118 | %(argv0)s, for extracting OpenFlow field properties from meta-flow.h |
178742f9 | 119 | usage: %(argv0)s INPUT [--meta-flow | --nx-match] |
a4ce8b25 | 120 | where INPUT points to lib/meta-flow.h in the source directory. |
178742f9 BP |
121 | Depending on the option given, the output written to stdout is intended to be |
122 | saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C | |
123 | file to #include.\ | |
1421f682 | 124 | ''' % {"argv0": argv0}) |
a4ce8b25 BP |
125 | sys.exit(0) |
126 | ||
56134136 | 127 | |
a4ce8b25 BP |
128 | def make_sizeof(s): |
129 | m = re.match(r'(.*) up to (.*)', s) | |
130 | if m: | |
131 | struct, member = m.groups() | |
132 | return "offsetof(%s, %s)" % (struct, member) | |
133 | else: | |
134 | return "sizeof(%s)" % s | |
135 | ||
56134136 | 136 | |
e6556fe3 | 137 | def parse_oxms(s, prefix, n_bytes): |
a4ce8b25 | 138 | if s == 'none': |
e6556fe3 BP |
139 | return () |
140 | ||
141 | return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in s.split(',')) | |
a4ce8b25 | 142 | |
56134136 | 143 | |
8c101866 JS |
144 | match_types = dict() |
145 | ||
146 | ||
e6556fe3 | 147 | def parse_oxm(s, prefix, n_bytes): |
8c101866 JS |
148 | global match_types |
149 | ||
a4ce8b25 BP |
150 | m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s) |
151 | if not m: | |
152 | fatal("%s: syntax error parsing %s" % (s, prefix)) | |
56134136 | 153 | |
508a9338 | 154 | name, oxm_type, of_version, ovs_version = m.groups() |
a4ce8b25 BP |
155 | |
156 | class_ = oxm_name_to_class(name) | |
157 | if class_ is None: | |
158 | fatal("unknown OXM class for %s" % name) | |
508a9338 BP |
159 | oxm_vendor, oxm_class = class_ |
160 | ||
8c101866 JS |
161 | if class_ in match_types: |
162 | if oxm_type in match_types[class_]: | |
163 | fatal("duplicate match type for %s (conflicts with %s)" % | |
164 | (name, match_types[class_][oxm_type])) | |
165 | else: | |
166 | match_types[class_] = dict() | |
167 | match_types[class_][oxm_type] = name | |
168 | ||
508a9338 BP |
169 | # Normally the oxm_length is the size of the field, but for experimenter |
170 | # OXMs oxm_length also includes the 4-byte experimenter ID. | |
171 | oxm_length = n_bytes | |
172 | if oxm_class == 0xffff: | |
173 | oxm_length += 4 | |
174 | ||
175 | header = ("NXM_HEADER(0x%x,0x%x,%s,0,%d)" | |
176 | % (oxm_vendor, oxm_class, oxm_type, oxm_length)) | |
a4ce8b25 BP |
177 | |
178 | if of_version: | |
179 | if of_version not in VERSION: | |
180 | fatal("%s: unknown OpenFlow version %s" % (name, of_version)) | |
181 | of_version_nr = VERSION[of_version] | |
182 | if of_version_nr < VERSION['1.2']: | |
183 | fatal("%s: claimed version %s predates OXM" % (name, of_version)) | |
184 | else: | |
185 | of_version_nr = 0 | |
186 | ||
187 | return (header, name, of_version_nr, ovs_version) | |
188 | ||
56134136 | 189 | |
a4ce8b25 BP |
190 | def parse_field(mff, comment): |
191 | f = {'mff': mff} | |
192 | ||
193 | # First line of comment is the field name. | |
194 | m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0]) | |
195 | if not m: | |
196 | fatal("%s lacks field name" % mff) | |
197 | f['name'], f['extra_name'] = m.groups() | |
198 | ||
199 | # Find the last blank line the comment. The field definitions | |
200 | # start after that. | |
201 | blank = None | |
202 | for i in range(len(comment)): | |
203 | if not comment[i]: | |
204 | blank = i | |
205 | if not blank: | |
206 | fatal("%s: missing blank line in comment" % mff) | |
207 | ||
208 | d = {} | |
209 | for key in ("Type", "Maskable", "Formatting", "Prerequisites", | |
210 | "Access", "Prefix lookup member", | |
211 | "OXM", "NXM", "OF1.0", "OF1.1"): | |
212 | d[key] = None | |
213 | for fline in comment[blank + 1:]: | |
214 | m = re.match(r'([^:]+):\s+(.*)\.$', fline) | |
215 | if not m: | |
216 | fatal("%s: syntax error parsing key-value pair as part of %s" | |
217 | % (fline, mff)) | |
218 | key, value = m.groups() | |
219 | if key not in d: | |
220 | fatal("%s: unknown key" % key) | |
221 | elif key == 'Code point': | |
222 | d[key] += [value] | |
223 | elif d[key] is not None: | |
224 | fatal("%s: duplicate key" % key) | |
225 | d[key] = value | |
1421f682 | 226 | for key, value in d.items(): |
a4ce8b25 BP |
227 | if not value and key not in ("OF1.0", "OF1.1", |
228 | "Prefix lookup member", "Notes"): | |
229 | fatal("%s: missing %s" % (mff, key)) | |
230 | ||
231 | m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type']) | |
232 | if not m: | |
233 | fatal("%s: syntax error in type" % mff) | |
234 | type_ = m.group(1) | |
235 | if type_ not in TYPES: | |
236 | fatal("%s: unknown type %s" % (mff, d['Type'])) | |
237 | f['n_bytes'] = TYPES[type_] | |
238 | if m.group(2): | |
239 | f['n_bits'] = int(m.group(2)) | |
240 | if f['n_bits'] > f['n_bytes'] * 8: | |
241 | fatal("%s: more bits (%d) than field size (%d)" | |
242 | % (mff, f['n_bits'], 8 * f['n_bytes'])) | |
243 | else: | |
244 | f['n_bits'] = 8 * f['n_bytes'] | |
245 | ||
246 | if d['Maskable'] == 'no': | |
247 | f['mask'] = 'MFM_NONE' | |
248 | elif d['Maskable'] == 'bitwise': | |
249 | f['mask'] = 'MFM_FULLY' | |
250 | else: | |
251 | fatal("%s: unknown maskable %s" % (mff, d['Maskable'])) | |
252 | ||
253 | fmt = FORMATTING.get(d['Formatting']) | |
254 | if not fmt: | |
255 | fatal("%s: unknown format %s" % (mff, d['Formatting'])) | |
256 | if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]: | |
257 | fatal("%s: %d-byte field can't be formatted as %s" | |
258 | % (mff, f['n_bytes'], d['Formatting'])) | |
259 | f['string'] = fmt[0] | |
260 | ||
261 | f['prereqs'] = PREREQS.get(d['Prerequisites']) | |
262 | if not f['prereqs']: | |
263 | fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites'])) | |
264 | ||
265 | if d['Access'] == 'read-only': | |
266 | f['writable'] = False | |
267 | elif d['Access'] == 'read/write': | |
268 | f['writable'] = True | |
269 | else: | |
270 | fatal("%s: unknown access %s" % (mff, d['Access'])) | |
271 | ||
272 | f['OF1.0'] = d['OF1.0'] | |
273 | if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'): | |
274 | fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0'])) | |
56134136 | 275 | |
a4ce8b25 BP |
276 | f['OF1.1'] = d['OF1.1'] |
277 | if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'): | |
278 | fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1'])) | |
279 | ||
e6556fe3 BP |
280 | f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) + |
281 | parse_oxms(d['NXM'], 'NXM', f['n_bytes'])) | |
a4ce8b25 BP |
282 | |
283 | f['prefix'] = d["Prefix lookup member"] | |
284 | ||
285 | return f | |
286 | ||
56134136 | 287 | |
a4ce8b25 BP |
288 | def protocols_to_c(protocols): |
289 | if protocols == set(['of10', 'of11', 'oxm']): | |
290 | return 'OFPUTIL_P_ANY' | |
291 | elif protocols == set(['of11', 'oxm']): | |
292 | return 'OFPUTIL_P_NXM_OF11_UP' | |
293 | elif protocols == set(['oxm']): | |
294 | return 'OFPUTIL_P_NXM_OXM_ANY' | |
295 | elif protocols == set([]): | |
296 | return 'OFPUTIL_P_NONE' | |
297 | else: | |
56134136 JS |
298 | assert False |
299 | ||
a4ce8b25 | 300 | |
178742f9 BP |
301 | def make_meta_flow(fields): |
302 | output = [] | |
303 | for f in fields: | |
304 | output += ["{"] | |
305 | output += [" %s," % f['mff']] | |
306 | if f['extra_name']: | |
307 | output += [" \"%s\", \"%s\"," % (f['name'], f['extra_name'])] | |
308 | else: | |
309 | output += [" \"%s\", NULL," % f['name']] | |
310 | output += [" %d, %d," % (f['n_bytes'], f['n_bits'])] | |
311 | ||
312 | if f['writable']: | |
313 | rw = 'true' | |
314 | else: | |
315 | rw = 'false' | |
316 | output += [" %s, %s, %s, %s," | |
317 | % (f['mask'], f['string'], f['prereqs'], rw)] | |
318 | ||
178742f9 | 319 | oxm = f['OXM'] |
178742f9 BP |
320 | of10 = f['OF1.0'] |
321 | of11 = f['OF1.1'] | |
322 | if f['mff'] in ('MFF_DL_VLAN', 'MFF_DL_VLAN_PCP'): | |
323 | # MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to | |
324 | # OF1.1, nor do they have NXM or OXM assignments, but their | |
325 | # meanings can be expressed in every protocol, which is the goal of | |
326 | # this member. | |
327 | protocols = set(["of10", "of11", "oxm"]) | |
328 | else: | |
329 | protocols = set([]) | |
330 | if of10: | |
331 | protocols |= set(["of10"]) | |
332 | if of11: | |
333 | protocols |= set(["of11"]) | |
e6556fe3 | 334 | if oxm: |
178742f9 BP |
335 | protocols |= set(["oxm"]) |
336 | ||
337 | if f['mask'] == 'MFM_FULLY': | |
338 | cidr_protocols = protocols.copy() | |
339 | bitwise_protocols = protocols.copy() | |
340 | ||
341 | if of10 == 'exact match': | |
342 | bitwise_protocols -= set(['of10']) | |
343 | cidr_protocols -= set(['of10']) | |
344 | elif of10 == 'CIDR mask': | |
345 | bitwise_protocols -= set(['of10']) | |
346 | else: | |
347 | assert of10 is None | |
348 | ||
349 | if of11 == 'exact match': | |
350 | bitwise_protocols -= set(['of11']) | |
351 | cidr_protocols -= set(['of11']) | |
352 | else: | |
353 | assert of11 in (None, 'bitwise mask') | |
354 | else: | |
355 | assert f['mask'] == 'MFM_NONE' | |
356 | cidr_protocols = set([]) | |
357 | bitwise_protocols = set([]) | |
358 | ||
359 | output += [" %s," % protocols_to_c(protocols)] | |
360 | output += [" %s," % protocols_to_c(cidr_protocols)] | |
361 | output += [" %s," % protocols_to_c(bitwise_protocols)] | |
56134136 | 362 | |
178742f9 BP |
363 | if f['prefix']: |
364 | output += [" FLOW_U32OFS(%s)," % f['prefix']] | |
365 | else: | |
366 | output += [" -1, /* not usable for prefix lookup */"] | |
367 | ||
368 | output += ["},"] | |
369 | return output | |
370 | ||
56134136 | 371 | |
178742f9 BP |
372 | def make_nx_match(fields): |
373 | output = [] | |
1421f682 | 374 | print("static struct nxm_field_index all_nxm_fields[] = {") |
178742f9 | 375 | for f in fields: |
e6556fe3 BP |
376 | # Sort by OpenFlow version number (nx-match.c depends on this). |
377 | for oxm in sorted(f['OXM'], key=lambda x: x[2]): | |
1421f682 JS |
378 | print("""{ .nf = { %s, %d, "%s", %s } },""" % ( |
379 | oxm[0], oxm[2], oxm[1], f['mff'])) | |
380 | print("};") | |
178742f9 BP |
381 | return output |
382 | ||
56134136 | 383 | |
178742f9 | 384 | def extract_ofp_fields(mode): |
a4ce8b25 BP |
385 | global line |
386 | ||
387 | fields = [] | |
388 | ||
389 | while True: | |
390 | get_line() | |
391 | if re.match('enum.*mf_field_id', line): | |
392 | break | |
393 | ||
394 | while True: | |
395 | get_line() | |
396 | first_line_number = line_number | |
397 | here = '%s:%d' % (file_name, line_number) | |
398 | if (line.startswith('/*') | |
399 | or line.startswith(' *') | |
400 | or line.startswith('#') | |
401 | or not line | |
402 | or line.isspace()): | |
403 | continue | |
404 | elif re.match('}', line) or re.match('\s+MFF_N_IDS', line): | |
405 | break | |
406 | ||
407 | # Parse the comment preceding an MFF_ constant into 'comment', | |
408 | # one line to an array element. | |
409 | line = line.strip() | |
410 | if not line.startswith('/*'): | |
411 | fatal("unexpected syntax between fields") | |
412 | line = line[1:] | |
413 | comment = [] | |
414 | end = False | |
415 | while not end: | |
416 | line = line.strip() | |
417 | if line.startswith('*/'): | |
418 | get_line() | |
419 | break | |
420 | if not line.startswith('*'): | |
421 | fatal("unexpected syntax within field") | |
422 | ||
423 | line = line[1:] | |
424 | if line.startswith(' '): | |
425 | line = line[1:] | |
426 | if line.startswith(' ') and comment: | |
427 | continuation = True | |
428 | line = line.lstrip() | |
429 | else: | |
430 | continuation = False | |
431 | ||
432 | if line.endswith('*/'): | |
433 | line = line[:-2].rstrip() | |
434 | end = True | |
435 | else: | |
436 | end = False | |
437 | ||
438 | if continuation: | |
439 | comment[-1] += " " + line | |
440 | else: | |
441 | comment += [line] | |
442 | get_line() | |
443 | ||
444 | # Drop blank lines at each end of comment. | |
445 | while comment and not comment[0]: | |
446 | comment = comment[1:] | |
447 | while comment and not comment[-1]: | |
448 | comment = comment[:-1] | |
449 | ||
450 | # Parse the MFF_ constant(s). | |
451 | mffs = [] | |
452 | while True: | |
453 | m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line) | |
454 | if not m: | |
455 | break | |
456 | mffs += [m.group(1)] | |
457 | get_line() | |
458 | if not mffs: | |
459 | fatal("unexpected syntax looking for MFF_ constants") | |
460 | ||
461 | if len(mffs) > 1 or '<N>' in comment[0]: | |
462 | for mff in mffs: | |
463 | # Extract trailing integer. | |
464 | m = re.match('.*[^0-9]([0-9]+)$', mff) | |
465 | if not m: | |
466 | fatal("%s lacks numeric suffix in register group" % mff) | |
467 | n = m.group(1) | |
468 | ||
469 | # Search-and-replace <N> within the comment, | |
470 | # and drop lines that have <x> for x != n. | |
471 | instance = [] | |
472 | for x in comment: | |
473 | y = x.replace('<N>', n) | |
474 | if re.search('<[0-9]+>', y): | |
475 | if ('<%s>' % n) not in y: | |
476 | continue | |
477 | y = re.sub('<[0-9]+>', '', y) | |
478 | instance += [y.strip()] | |
479 | fields += [parse_field(mff, instance)] | |
480 | else: | |
481 | fields += [parse_field(mffs[0], comment)] | |
482 | continue | |
483 | ||
484 | input_file.close() | |
485 | ||
486 | if n_errors: | |
487 | sys.exit(1) | |
488 | ||
1421f682 | 489 | print("""\ |
178742f9 | 490 | /* Generated automatically; do not modify! "-*- buffer-read-only: t -*- */ |
1421f682 | 491 | """) |
a4ce8b25 | 492 | |
178742f9 BP |
493 | if mode == '--meta-flow': |
494 | output = make_meta_flow(fields) | |
495 | elif mode == '--nx-match': | |
496 | output = make_nx_match(fields) | |
497 | else: | |
498 | assert False | |
a4ce8b25 BP |
499 | |
500 | return output | |
501 | ||
502 | ||
503 | if __name__ == '__main__': | |
504 | if '--help' in sys.argv: | |
505 | usage() | |
178742f9 BP |
506 | elif len(sys.argv) != 3: |
507 | sys.stderr.write("exactly two arguments required; " | |
a4ce8b25 BP |
508 | "use --help for help\n") |
509 | sys.exit(1) | |
178742f9 | 510 | elif sys.argv[2] in ('--meta-flow', '--nx-match'): |
a4ce8b25 BP |
511 | global file_name |
512 | global input_file | |
513 | global line_number | |
514 | file_name = sys.argv[1] | |
515 | input_file = open(file_name) | |
516 | line_number = 0 | |
517 | ||
178742f9 | 518 | for oline in extract_ofp_fields(sys.argv[2]): |
1421f682 | 519 | print(oline) |
178742f9 BP |
520 | else: |
521 | sys.stderr.write("invalid arguments; use --help for help\n") | |
522 | sys.exit(1) |