]> git.proxmox.com Git - mirror_ovs.git/blob - build-aux/extract-ofp-fields
ovs-fields: New manpage to document Open vSwitch and OpenFlow fields.
[mirror_ovs.git] / build-aux / extract-ofp-fields
1 #! /usr/bin/python
2
3 import getopt
4 import sys
5 import os.path
6 import re
7 import xml.dom.minidom
8 import build.nroff
9
10 line = ""
11
12 # Maps from user-friendly version number to its protocol encoding.
13 VERSION = {"1.0": 0x01,
14 "1.1": 0x02,
15 "1.2": 0x03,
16 "1.3": 0x04,
17 "1.4": 0x05,
18 "1.5": 0x06}
19 VERSION_REVERSE = dict((v,k) for k, v in VERSION.iteritems())
20
21 TYPES = {"u8": (1, False),
22 "be16": (2, False),
23 "be32": (4, False),
24 "MAC": (6, False),
25 "be64": (8, False),
26 "be128": (16, False),
27 "tunnelMD": (124, True)}
28
29 FORMATTING = {"decimal": ("MFS_DECIMAL", 1, 8),
30 "hexadecimal": ("MFS_HEXADECIMAL", 1, 127),
31 "ct state": ("MFS_CT_STATE", 4, 4),
32 "Ethernet": ("MFS_ETHERNET", 6, 6),
33 "IPv4": ("MFS_IPV4", 4, 4),
34 "IPv6": ("MFS_IPV6", 16, 16),
35 "OpenFlow 1.0 port": ("MFS_OFP_PORT", 2, 2),
36 "OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4, 4),
37 "frag": ("MFS_FRAG", 1, 1),
38 "tunnel flags": ("MFS_TNL_FLAGS", 2, 2),
39 "TCP flags": ("MFS_TCP_FLAGS", 2, 2)}
40
41 PREREQS = {"none": "MFP_NONE",
42 "ARP": "MFP_ARP",
43 "VLAN VID": "MFP_VLAN_VID",
44 "IPv4": "MFP_IPV4",
45 "IPv6": "MFP_IPV6",
46 "IPv4/IPv6": "MFP_IP_ANY",
47 "MPLS": "MFP_MPLS",
48 "TCP": "MFP_TCP",
49 "UDP": "MFP_UDP",
50 "SCTP": "MFP_SCTP",
51 "ICMPv4": "MFP_ICMPV4",
52 "ICMPv6": "MFP_ICMPV6",
53 "ND": "MFP_ND",
54 "ND solicit": "MFP_ND_SOLICIT",
55 "ND advert": "MFP_ND_ADVERT"}
56
57 # Maps a name prefix into an (experimenter ID, class) pair, so:
58 #
59 # - Standard OXM classes are written as (0, <oxm_class>)
60 #
61 # - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
62 #
63 # If a name matches more than one prefix, the longest one is used.
64 OXM_CLASSES = {"NXM_OF_": (0, 0x0000),
65 "NXM_NX_": (0, 0x0001),
66 "OXM_OF_": (0, 0x8000),
67 "OXM_OF_PKT_REG": (0, 0x8001),
68 "ONFOXM_ET_": (0x4f4e4600, 0xffff),
69
70 # This is the experimenter OXM class for Nicira, which is the
71 # one that OVS would be using instead of NXM_OF_ and NXM_NX_
72 # if OVS didn't have those grandfathered in. It is currently
73 # used only to test support for experimenter OXM, since there
74 # are barely any real uses of experimenter OXM in the wild.
75 "NXOXM_ET_": (0x00002320, 0xffff)}
76
77
78 def oxm_name_to_class(name):
79 prefix = ''
80 class_ = None
81 for p, c in OXM_CLASSES.items():
82 if name.startswith(p) and len(p) > len(prefix):
83 prefix = p
84 class_ = c
85 return class_
86
87
88 def decode_version_range(range):
89 if range in VERSION:
90 return (VERSION[range], VERSION[range])
91 elif range.endswith('+'):
92 return (VERSION[range[:-1]], max(VERSION.values()))
93 else:
94 a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
95 return (VERSION[a], VERSION[b])
96
97
98 def get_line():
99 global line
100 global line_number
101 line = input_file.readline()
102 line_number += 1
103 if line == "":
104 fatal("unexpected end of input")
105
106
107 n_errors = 0
108
109
110 def error(msg):
111 global n_errors
112 sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
113 n_errors += 1
114
115
116 def fatal(msg):
117 error(msg)
118 sys.exit(1)
119
120
121 def usage():
122 argv0 = os.path.basename(sys.argv[0])
123 print('''\
124 %(argv0)s, for extracting OpenFlow field properties from meta-flow.h
125 usage: %(argv0)s INPUT [--meta-flow | --nx-match]
126 where INPUT points to lib/meta-flow.h in the source directory.
127 Depending on the option given, the output written to stdout is intended to be
128 saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C
129 file to #include.\
130 ''' % {"argv0": argv0})
131 sys.exit(0)
132
133
134 def make_sizeof(s):
135 m = re.match(r'(.*) up to (.*)', s)
136 if m:
137 struct, member = m.groups()
138 return "offsetof(%s, %s)" % (struct, member)
139 else:
140 return "sizeof(%s)" % s
141
142
143 def parse_oxms(s, prefix, n_bytes):
144 if s == 'none':
145 return ()
146
147 return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in s.split(','))
148
149
150 match_types = dict()
151
152
153 def parse_oxm(s, prefix, n_bytes):
154 global match_types
155
156 m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s)
157 if not m:
158 fatal("%s: syntax error parsing %s" % (s, prefix))
159
160 name, oxm_type, of_version, ovs_version = m.groups()
161
162 class_ = oxm_name_to_class(name)
163 if class_ is None:
164 fatal("unknown OXM class for %s" % name)
165 oxm_vendor, oxm_class = class_
166
167 if class_ in match_types:
168 if oxm_type in match_types[class_]:
169 fatal("duplicate match type for %s (conflicts with %s)" %
170 (name, match_types[class_][oxm_type]))
171 else:
172 match_types[class_] = dict()
173 match_types[class_][oxm_type] = name
174
175 # Normally the oxm_length is the size of the field, but for experimenter
176 # OXMs oxm_length also includes the 4-byte experimenter ID.
177 oxm_length = n_bytes
178 if oxm_class == 0xffff:
179 oxm_length += 4
180
181 header = (oxm_vendor, oxm_class, int(oxm_type), oxm_length)
182
183 if of_version:
184 if of_version not in VERSION:
185 fatal("%s: unknown OpenFlow version %s" % (name, of_version))
186 of_version_nr = VERSION[of_version]
187 if of_version_nr < VERSION['1.2']:
188 fatal("%s: claimed version %s predates OXM" % (name, of_version))
189 elif prefix == 'OXM':
190 fatal("%s: missing OpenFlow version number" % name)
191 else:
192 of_version_nr = 0
193
194 return (header, name, of_version_nr, ovs_version)
195
196
197 def parse_field(mff, comment):
198 f = {'mff': mff}
199
200 # First line of comment is the field name.
201 m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0])
202 if not m:
203 fatal("%s lacks field name" % mff)
204 f['name'], f['extra_name'] = m.groups()
205
206 # Find the last blank line the comment. The field definitions
207 # start after that.
208 blank = None
209 for i in range(len(comment)):
210 if not comment[i]:
211 blank = i
212 if not blank:
213 fatal("%s: missing blank line in comment" % mff)
214
215 d = {}
216 for key in ("Type", "Maskable", "Formatting", "Prerequisites",
217 "Access", "Prefix lookup member",
218 "OXM", "NXM", "OF1.0", "OF1.1"):
219 d[key] = None
220 for fline in comment[blank + 1:]:
221 m = re.match(r'([^:]+):\s+(.*)\.$', fline)
222 if not m:
223 fatal("%s: syntax error parsing key-value pair as part of %s"
224 % (fline, mff))
225 key, value = m.groups()
226 if key not in d:
227 fatal("%s: unknown key" % key)
228 elif key == 'Code point':
229 d[key] += [value]
230 elif d[key] is not None:
231 fatal("%s: duplicate key" % key)
232 d[key] = value
233 for key, value in d.items():
234 if not value and key not in ("OF1.0", "OF1.1",
235 "Prefix lookup member", "Notes"):
236 fatal("%s: missing %s" % (mff, key))
237
238 m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
239 if not m:
240 fatal("%s: syntax error in type" % mff)
241 type_ = m.group(1)
242 if type_ not in TYPES:
243 fatal("%s: unknown type %s" % (mff, d['Type']))
244
245 f['n_bytes'] = TYPES[type_][0]
246 if m.group(2):
247 f['n_bits'] = int(m.group(2))
248 if f['n_bits'] > f['n_bytes'] * 8:
249 fatal("%s: more bits (%d) than field size (%d)"
250 % (mff, f['n_bits'], 8 * f['n_bytes']))
251 else:
252 f['n_bits'] = 8 * f['n_bytes']
253 f['variable'] = TYPES[type_][1]
254
255 if d['Maskable'] == 'no':
256 f['mask'] = 'MFM_NONE'
257 elif d['Maskable'] == 'bitwise':
258 f['mask'] = 'MFM_FULLY'
259 else:
260 fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
261
262 fmt = FORMATTING.get(d['Formatting'])
263 if not fmt:
264 fatal("%s: unknown format %s" % (mff, d['Formatting']))
265 f['formatting'] = d['Formatting']
266 if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
267 fatal("%s: %d-byte field can't be formatted as %s"
268 % (mff, f['n_bytes'], d['Formatting']))
269 f['string'] = fmt[0]
270
271 f['prereqs'] = d['Prerequisites']
272 if f['prereqs'] not in PREREQS:
273 fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
274
275 if d['Access'] == 'read-only':
276 f['writable'] = False
277 elif d['Access'] == 'read/write':
278 f['writable'] = True
279 else:
280 fatal("%s: unknown access %s" % (mff, d['Access']))
281
282 f['OF1.0'] = d['OF1.0']
283 if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
284 fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
285
286 f['OF1.1'] = d['OF1.1']
287 if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
288 fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
289
290 f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) +
291 parse_oxms(d['NXM'], 'NXM', f['n_bytes']))
292
293 f['prefix'] = d["Prefix lookup member"]
294
295 return f
296
297
298 def protocols_to_c(protocols):
299 if protocols == set(['of10', 'of11', 'oxm']):
300 return 'OFPUTIL_P_ANY'
301 elif protocols == set(['of11', 'oxm']):
302 return 'OFPUTIL_P_NXM_OF11_UP'
303 elif protocols == set(['oxm']):
304 return 'OFPUTIL_P_NXM_OXM_ANY'
305 elif protocols == set([]):
306 return 'OFPUTIL_P_NONE'
307 else:
308 assert False
309
310
311 def autogen_c_comment():
312 return [
313 "/* Generated automatically; do not modify! -*- buffer-read-only: t -*- */",
314 ""]
315
316 def make_meta_flow(meta_flow_h):
317 fields = extract_ofp_fields(meta_flow_h)
318 output = autogen_c_comment()
319 for f in fields:
320 output += ["{"]
321 output += [" %s," % f['mff']]
322 if f['extra_name']:
323 output += [" \"%s\", \"%s\"," % (f['name'], f['extra_name'])]
324 else:
325 output += [" \"%s\", NULL," % f['name']]
326
327 if f['variable']:
328 variable = 'true'
329 else:
330 variable = 'false'
331 output += [" %d, %d, %s," % (f['n_bytes'], f['n_bits'], variable)]
332
333 if f['writable']:
334 rw = 'true'
335 else:
336 rw = 'false'
337 output += [" %s, %s, %s, %s,"
338 % (f['mask'], f['string'], PREREQS[f['prereqs']], rw)]
339
340 oxm = f['OXM']
341 of10 = f['OF1.0']
342 of11 = f['OF1.1']
343 if f['mff'] in ('MFF_DL_VLAN', 'MFF_DL_VLAN_PCP'):
344 # MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to
345 # OF1.1, nor do they have NXM or OXM assignments, but their
346 # meanings can be expressed in every protocol, which is the goal of
347 # this member.
348 protocols = set(["of10", "of11", "oxm"])
349 else:
350 protocols = set([])
351 if of10:
352 protocols |= set(["of10"])
353 if of11:
354 protocols |= set(["of11"])
355 if oxm:
356 protocols |= set(["oxm"])
357
358 if f['mask'] == 'MFM_FULLY':
359 cidr_protocols = protocols.copy()
360 bitwise_protocols = protocols.copy()
361
362 if of10 == 'exact match':
363 bitwise_protocols -= set(['of10'])
364 cidr_protocols -= set(['of10'])
365 elif of10 == 'CIDR mask':
366 bitwise_protocols -= set(['of10'])
367 else:
368 assert of10 is None
369
370 if of11 == 'exact match':
371 bitwise_protocols -= set(['of11'])
372 cidr_protocols -= set(['of11'])
373 else:
374 assert of11 in (None, 'bitwise mask')
375 else:
376 assert f['mask'] == 'MFM_NONE'
377 cidr_protocols = set([])
378 bitwise_protocols = set([])
379
380 output += [" %s," % protocols_to_c(protocols)]
381 output += [" %s," % protocols_to_c(cidr_protocols)]
382 output += [" %s," % protocols_to_c(bitwise_protocols)]
383
384 if f['prefix']:
385 output += [" FLOW_U32OFS(%s)," % f['prefix']]
386 else:
387 output += [" -1, /* not usable for prefix lookup */"]
388
389 output += ["},"]
390 for oline in output:
391 print(oline)
392
393
394 def make_nx_match(meta_flow_h):
395 fields = extract_ofp_fields(meta_flow_h)
396 output = autogen_c_comment()
397 print("static struct nxm_field_index all_nxm_fields[] = {")
398 for f in fields:
399 # Sort by OpenFlow version number (nx-match.c depends on this).
400 for oxm in sorted(f['OXM'], key=lambda x: x[2]):
401 header = ("NXM_HEADER(0x%x,0x%x,%s,0,%d)" % oxm[0])
402 print("""{ .nf = { %s, %d, "%s", %s } },""" % (
403 header, oxm[2], oxm[1], f['mff']))
404 print("};")
405 for oline in output:
406 print(oline)
407
408
409 def extract_ofp_fields(fn):
410 global file_name
411 global input_file
412 global line_number
413 global line
414
415 file_name = fn
416 input_file = open(file_name)
417 line_number = 0
418
419 fields = []
420
421 while True:
422 get_line()
423 if re.match('enum.*mf_field_id', line):
424 break
425
426 while True:
427 get_line()
428 first_line_number = line_number
429 here = '%s:%d' % (file_name, line_number)
430 if (line.startswith('/*')
431 or line.startswith(' *')
432 or line.startswith('#')
433 or not line
434 or line.isspace()):
435 continue
436 elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
437 break
438
439 # Parse the comment preceding an MFF_ constant into 'comment',
440 # one line to an array element.
441 line = line.strip()
442 if not line.startswith('/*'):
443 fatal("unexpected syntax between fields")
444 line = line[1:]
445 comment = []
446 end = False
447 while not end:
448 line = line.strip()
449 if line.startswith('*/'):
450 get_line()
451 break
452 if not line.startswith('*'):
453 fatal("unexpected syntax within field")
454
455 line = line[1:]
456 if line.startswith(' '):
457 line = line[1:]
458 if line.startswith(' ') and comment:
459 continuation = True
460 line = line.lstrip()
461 else:
462 continuation = False
463
464 if line.endswith('*/'):
465 line = line[:-2].rstrip()
466 end = True
467 else:
468 end = False
469
470 if continuation:
471 comment[-1] += " " + line
472 else:
473 comment += [line]
474 get_line()
475
476 # Drop blank lines at each end of comment.
477 while comment and not comment[0]:
478 comment = comment[1:]
479 while comment and not comment[-1]:
480 comment = comment[:-1]
481
482 # Parse the MFF_ constant(s).
483 mffs = []
484 while True:
485 m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
486 if not m:
487 break
488 mffs += [m.group(1)]
489 get_line()
490 if not mffs:
491 fatal("unexpected syntax looking for MFF_ constants")
492
493 if len(mffs) > 1 or '<N>' in comment[0]:
494 for mff in mffs:
495 # Extract trailing integer.
496 m = re.match('.*[^0-9]([0-9]+)$', mff)
497 if not m:
498 fatal("%s lacks numeric suffix in register group" % mff)
499 n = m.group(1)
500
501 # Search-and-replace <N> within the comment,
502 # and drop lines that have <x> for x != n.
503 instance = []
504 for x in comment:
505 y = x.replace('<N>', n)
506 if re.search('<[0-9]+>', y):
507 if ('<%s>' % n) not in y:
508 continue
509 y = re.sub('<[0-9]+>', '', y)
510 instance += [y.strip()]
511 fields += [parse_field(mff, instance)]
512 else:
513 fields += [parse_field(mffs[0], comment)]
514 continue
515
516 input_file.close()
517
518 if n_errors:
519 sys.exit(1)
520
521 return fields
522 \f
523 ## ------------------------ ##
524 ## Documentation Generation ##
525 ## ------------------------ ##
526
527 def field_to_xml(field_node, f, body, summary):
528 f["used"] = True
529
530 # Summary.
531 if field_node.hasAttribute('internal'):
532 return
533
534 min_of_version = None
535 min_ovs_version = None
536 for header, name, of_version_nr, ovs_version_s in f['OXM']:
537 if (not name.startswith('NXM')
538 and (min_ovs_version is None or of_version_nr < min_of_version)):
539 min_of_version = of_version_nr
540 ovs_version = [int(x) for x in ovs_version_s.split('.')]
541 if min_ovs_version is None or ovs_version < min_ovs_version:
542 min_ovs_version = ovs_version
543 summary += ["\\fB%s\\fR" % f["name"]]
544 if f["extra_name"]:
545 summary += [" aka \\fB%s\\fR" % f["extra_name"]]
546 summary += [";%d" % f["n_bytes"]]
547 if f["n_bits"] != 8 * f["n_bytes"]:
548 summary += [" (low %d bits)" % f["n_bits"]]
549 summary += [";%s;" % {"MFM_NONE": "no", "MFM_FULLY": "yes"}[f["mask"]]]
550 summary += ["%s;" % {True: "yes", False: "no"}[f["writable"]]]
551 summary += ["%s;" % f["prereqs"]]
552 support = []
553 if min_of_version is not None:
554 support += ["OF %s+" % VERSION_REVERSE[min_of_version]]
555 if min_ovs_version is not None:
556 support += ["OVS %s+" % '.'.join([str(x) for x in min_ovs_version])]
557 summary += ' and '.join(support)
558 summary += ["\n"]
559
560 # Full description.
561 if field_node.hasAttribute('hidden'):
562 return
563
564 title = field_node.attributes['title'].nodeValue
565
566 body += [""".PP
567 \\fB%s Field\\fR
568 .TS
569 tab(;);
570 l lx.
571 """ % title]
572
573 body += ["Name:;\\fB%s\\fR" % f["name"]]
574 if f["extra_name"]:
575 body += [" (aka \\fB%s\\fR)" % f["extra_name"]]
576 body += ['\n']
577
578 body += ["Width:;"]
579 if f["n_bits"] != 8 * f["n_bytes"]:
580 body += ["%d bits (only the least-significant %d bits "
581 "may be nonzero)" % (f["n_bytes"] * 8, f["n_bits"])]
582 elif f["n_bits"] <= 128:
583 body += ["%d bits" % f["n_bits"]]
584 else:
585 body += ["%d bits (%d bytes)" % (f["n_bits"], f["n_bits"] / 8)]
586 body += ['\n']
587
588 body += ["Format:;%s\n" % f["formatting"]]
589
590 masks = {"MFM_NONE": "not maskable",
591 "MFM_FULLY": "arbitrary bitwise masks"}
592 body += ["Masking:;%s\n" % masks[f["mask"]]]
593 body += ["Prerequisites:;%s\n" % f["prereqs"]]
594
595 access = {True: "read/write",
596 False: "read-only"}[f["writable"]]
597 body += ["Access:;%s\n" % access]
598
599 of10 = {None: "not supported",
600 "exact match": "yes (exact match only)",
601 "CIDR mask": "yes (CIDR match only)"}
602 body += ["OpenFlow 1.0:;%s\n" % of10[f["OF1.0"]]]
603
604 of11 = {None: "not supported",
605 "exact match": "yes (exact match only)",
606 "bitwise mask": "yes"}
607 body += ["OpenFlow 1.1:;%s\n" % of11[f["OF1.1"]]]
608
609 oxms = []
610 for header, name, of_version_nr, ovs_version in [x for x in sorted(f['OXM'], key=lambda x: x[2]) if not x[1].startswith('NXM')]:
611 of_version = VERSION_REVERSE[of_version_nr]
612 oxms += [r"\fB%s\fR (%d) since OpenFlow %s and Open vSwitch %s" % (name, header[2], of_version, ovs_version)]
613 if not oxms:
614 oxms = ['none']
615 body += ['OXM:;T{\n%s\nT}\n' % r'\[char59] '.join(oxms)]
616
617 nxms = []
618 for header, name, of_version_nr, ovs_version in [x for x in sorted(f['OXM'], key=lambda x: x[2]) if x[1].startswith('NXM')]:
619 nxms += [r"\fB%s\fR (%d) since Open vSwitch %s" % (name, header[2], ovs_version)]
620 if not nxms:
621 nxms = ['none']
622 body += ['NXM:;T{\n%s\nT}\n' % r'\[char59] '.join(nxms)]
623
624 body += [".TE\n"]
625
626 body += ['.PP\n']
627 body += [build.nroff.block_xml_to_nroff(field_node.childNodes)]
628
629 def group_xml_to_nroff(group_node, fields):
630 title = group_node.attributes['title'].nodeValue
631
632 summary = []
633 body = []
634 for node in group_node.childNodes:
635 if node.nodeType == node.ELEMENT_NODE and node.tagName == 'field':
636 id_ = node.attributes['id'].nodeValue
637 field_to_xml(node, fields[id_], body, summary)
638 else:
639 body += [build.nroff.block_xml_to_nroff([node])]
640
641 content = [
642 '.bp\n',
643 '.SH \"%s\"\n' % build.nroff.text_to_nroff(title.upper() + " FIELDS"),
644 '.SS "Summary:"\n',
645 '.TS\n',
646 'tab(;);\n',
647 'l l l l l l l.\n',
648 'Name;Bytes;Mask;RW?;Prereqs;NXM/OXM Support\n',
649 '\_;\_;\_;\_;\_;\_\n']
650 content += summary
651 content += ['.TE\n']
652 content += body
653 return ''.join(content)
654
655 def make_oxm_classes_xml(document):
656 s = '''tab(;);
657 l l l.
658 Prefix;Vendor;Class
659 \_;\_;\_
660 '''
661 for key in sorted(OXM_CLASSES, key=OXM_CLASSES.get):
662 vendor, class_ = OXM_CLASSES.get(key)
663 s += r"\fB%s\fR;" % key.rstrip('_')
664 if vendor:
665 s += r"\fL0x%08x\fR;" % vendor
666 else:
667 s += "(none);"
668 s += r"\fL0x%04x\fR;" % class_
669 s += "\n"
670 e = document.createElement('tbl')
671 e.appendChild(document.createTextNode(s))
672 return e
673
674 def recursively_replace(node, name, replacement):
675 for child in node.childNodes:
676 if child.nodeType == node.ELEMENT_NODE:
677 if child.tagName == name:
678 node.replaceChild(replacement, child)
679 else:
680 recursively_replace(child, name, replacement)
681
682 def make_ovs_fields(meta_flow_h, meta_flow_xml):
683 fields = extract_ofp_fields(meta_flow_h)
684 fields_map = {}
685 for f in fields:
686 fields_map[f['mff']] = f
687
688 document = xml.dom.minidom.parse(meta_flow_xml)
689 doc = document.documentElement
690
691 global version
692 if version == None:
693 version = "UNKNOWN"
694
695 print('''\
696 '\\" tp
697 .\\" -*- mode: troff; coding: utf-8 -*-
698 .TH "ovs\-fields" 7 "%s" "Open vSwitch" "Open vSwitch Manual"
699 .fp 5 L CR \\" Make fixed-width font available as \\fL.
700 .de ST
701 . PP
702 . RS -0.15in
703 . I "\\\\$1"
704 . RE
705 ..
706
707 .de SU
708 . PP
709 . I "\\\\$1"
710 ..
711
712 .de IQ
713 . br
714 . ns
715 . IP "\\\\$1"
716 ..
717 .de URL
718 \\\\$2 \\(laURL: \\\\$1 \\(ra\\\\$3
719 ..
720 .if \\n[.g] .mso www.tmac
721 .SH NAME
722 ovs\-fields \- protocol header fields in OpenFlow and Open vSwitch
723 .
724 .PP
725 ''') % version
726
727 recursively_replace(doc, 'oxm_classes', make_oxm_classes_xml(document))
728
729 s = ''
730 for node in doc.childNodes:
731 if node.nodeType == node.ELEMENT_NODE and node.tagName == "group":
732 s += group_xml_to_nroff(node, fields_map)
733 elif node.nodeType == node.TEXT_NODE:
734 assert node.data.isspace()
735 elif node.nodeType == node.COMMENT_NODE:
736 pass
737 else:
738 s += build.nroff.block_xml_to_nroff([node])
739
740 for f in fields:
741 if "used" not in f:
742 fatal("%s: field not documented" % f["mff"])
743 if n_errors:
744 sys.exit(1)
745
746 output = []
747 for oline in s.split("\n"):
748 oline = oline.strip()
749
750 # Life is easier with nroff if we don't try to feed it Unicode.
751 # Fortunately, we only use a few characters outside the ASCII range.
752 oline = oline.replace(u'\u2208', r'\[mo]')
753 oline = oline.replace(u'\u2260', r'\[!=]')
754 oline = oline.replace(u'\u2264', r'\[<=]')
755 if len(oline):
756 output += [oline]
757
758 # nroff tends to ignore .bp requests if they come after .PP requests,
759 # so remove .PPs that precede .bp.
760 for i in range(len(output)):
761 if output[i] == '.bp':
762 j = i - 1
763 while j >= 0 and output[j] == '.PP':
764 output[j] = None
765 j -= 1
766 for i in range(len(output)):
767 if output[i] is not None:
768 print(output[i])
769 \f
770 ## ------------ ##
771 ## Main Program ##
772 ## ------------ ##
773
774 if __name__ == "__main__":
775 argv0 = sys.argv[0]
776 try:
777 options, args = getopt.gnu_getopt(sys.argv[1:], 'h',
778 ['help', 'ovs-version='])
779 except getopt.GetoptError, geo:
780 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
781 sys.exit(1)
782
783 global version
784 version = None
785 for key, value in options:
786 if key in ['-h', '--help']:
787 usage()
788 elif key == '--ovs-version':
789 version = value
790 else:
791 sys.exit(0)
792
793 if not args:
794 sys.stderr.write("%s: missing command argument "
795 "(use --help for help)\n" % argv0)
796 sys.exit(1)
797
798 commands = {"meta-flow": (make_meta_flow, 1),
799 "nx-match": (make_nx_match, 1),
800 "ovs-fields": (make_ovs_fields, 2)}
801
802 if not args[0] in commands:
803 sys.stderr.write("%s: unknown command \"%s\" "
804 "(use --help for help)\n" % (argv0, args[0]))
805 sys.exit(1)
806
807 func, n_args = commands[args[0]]
808 if len(args) - 1 != n_args:
809 sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
810 "provided\n"
811 % (argv0, args[0], n_args, len(args) - 1))
812 sys.exit(1)
813
814 func(*args[1:])