]>
git.proxmox.com Git - mirror_ovs.git/blob - build-aux/extract-ofp-fields
12 # Maps from user-friendly version number to its protocol encoding.
13 VERSION
= {"1.0": 0x01,
19 VERSION_REVERSE
= dict((v
,k
) for k
, v
in VERSION
.iteritems())
21 TYPES
= {"u8": (1, False),
27 "tunnelMD": (124, True)}
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)}
41 PREREQS
= {"none": "MFP_NONE",
43 "VLAN VID": "MFP_VLAN_VID",
46 "IPv4/IPv6": "MFP_IP_ANY",
51 "ICMPv4": "MFP_ICMPV4",
52 "ICMPv6": "MFP_ICMPV6",
54 "ND solicit": "MFP_ND_SOLICIT",
55 "ND advert": "MFP_ND_ADVERT"}
57 # Maps a name prefix into an (experimenter ID, class) pair, so:
59 # - Standard OXM classes are written as (0, <oxm_class>)
61 # - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
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),
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)}
78 def oxm_name_to_class(name
):
81 for p
, c
in OXM_CLASSES
.items():
82 if name
.startswith(p
) and len(p
) > len(prefix
):
88 def decode_version_range(range):
90 return (VERSION
[range], VERSION
[range])
91 elif range.endswith('+'):
92 return (VERSION
[range[:-1]], max(VERSION
.values()))
94 a
, b
= re
.match(r
'^([^-]+)-([^-]+)$', range).groups()
95 return (VERSION
[a
], VERSION
[b
])
101 line
= input_file
.readline()
104 fatal("unexpected end of input")
112 sys
.stderr
.write("%s:%d: %s\n" % (file_name
, line_number
, msg
))
122 argv0
= os
.path
.basename(sys
.argv
[0])
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
130 ''' % {"argv0": argv0
})
135 m
= re
.match(r
'(.*) up to (.*)', s
)
137 struct
, member
= m
.groups()
138 return "offsetof(%s, %s)" % (struct
, member
)
140 return "sizeof(%s)" % s
143 def parse_oxms(s
, prefix
, n_bytes
):
147 return tuple(parse_oxm(s2
.strip(), prefix
, n_bytes
) for s2
in s
.split(','))
153 def parse_oxm(s
, prefix
, n_bytes
):
156 m
= re
.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s
)
158 fatal("%s: syntax error parsing %s" % (s
, prefix
))
160 name
, oxm_type
, of_version
, ovs_version
= m
.groups()
162 class_
= oxm_name_to_class(name
)
164 fatal("unknown OXM class for %s" % name
)
165 oxm_vendor
, oxm_class
= class_
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
]))
172 match_types
[class_
] = dict()
173 match_types
[class_
][oxm_type
] = name
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.
178 if oxm_class
== 0xffff:
181 header
= (oxm_vendor
, oxm_class
, int(oxm_type
), oxm_length
)
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
)
194 return (header
, name
, of_version_nr
, ovs_version
)
197 def parse_field(mff
, comment
):
200 # First line of comment is the field name.
201 m
= re
.match(r
'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment
[0])
203 fatal("%s lacks field name" % mff
)
204 f
['name'], f
['extra_name'] = m
.groups()
206 # Find the last blank line the comment. The field definitions
209 for i
in range(len(comment
)):
213 fatal("%s: missing blank line in comment" % mff
)
216 for key
in ("Type", "Maskable", "Formatting", "Prerequisites",
217 "Access", "Prefix lookup member",
218 "OXM", "NXM", "OF1.0", "OF1.1"):
220 for fline
in comment
[blank
+ 1:]:
221 m
= re
.match(r
'([^:]+):\s+(.*)\.$', fline
)
223 fatal("%s: syntax error parsing key-value pair as part of %s"
225 key
, value
= m
.groups()
227 fatal("%s: unknown key" % key
)
228 elif key
== 'Code point':
230 elif d
[key
] is not None:
231 fatal("%s: duplicate key" % key
)
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
))
238 m
= re
.match(r
'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d
['Type'])
240 fatal("%s: syntax error in type" % mff
)
242 if type_
not in TYPES
:
243 fatal("%s: unknown type %s" % (mff
, d
['Type']))
245 f
['n_bytes'] = TYPES
[type_
][0]
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']))
252 f
['n_bits'] = 8 * f
['n_bytes']
253 f
['variable'] = TYPES
[type_
][1]
255 if d
['Maskable'] == 'no':
256 f
['mask'] = 'MFM_NONE'
257 elif d
['Maskable'] == 'bitwise':
258 f
['mask'] = 'MFM_FULLY'
260 fatal("%s: unknown maskable %s" % (mff
, d
['Maskable']))
262 fmt
= FORMATTING
.get(d
['Formatting'])
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']))
271 f
['prereqs'] = d
['Prerequisites']
272 if f
['prereqs'] not in PREREQS
:
273 fatal("%s: unknown prerequisites %s" % (mff
, d
['Prerequisites']))
275 if d
['Access'] == 'read-only':
276 f
['writable'] = False
277 elif d
['Access'] == 'read/write':
280 fatal("%s: unknown access %s" % (mff
, d
['Access']))
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']))
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']))
290 f
['OXM'] = (parse_oxms(d
['OXM'], 'OXM', f
['n_bytes']) +
291 parse_oxms(d
['NXM'], 'NXM', f
['n_bytes']))
293 f
['prefix'] = d
["Prefix lookup member"]
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'
311 def autogen_c_comment():
313 "/* Generated automatically; do not modify! -*- buffer-read-only: t -*- */",
316 def make_meta_flow(meta_flow_h
):
317 fields
= extract_ofp_fields(meta_flow_h
)
318 output
= autogen_c_comment()
321 output
+= [" %s," % f
['mff']]
323 output
+= [" \"%s\", \"%s\"," % (f
['name'], f
['extra_name'])]
325 output
+= [" \"%s\", NULL," % f
['name']]
331 output
+= [" %d, %d, %s," % (f
['n_bytes'], f
['n_bits'], variable
)]
337 output
+= [" %s, %s, %s, %s,"
338 % (f
['mask'], f
['string'], PREREQS
[f
['prereqs']], rw
)]
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
348 protocols
= set(["of10", "of11", "oxm"])
352 protocols |
= set(["of10"])
354 protocols |
= set(["of11"])
356 protocols |
= set(["oxm"])
358 if f
['mask'] == 'MFM_FULLY':
359 cidr_protocols
= protocols
.copy()
360 bitwise_protocols
= protocols
.copy()
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'])
370 if of11
== 'exact match':
371 bitwise_protocols
-= set(['of11'])
372 cidr_protocols
-= set(['of11'])
374 assert of11
in (None, 'bitwise mask')
376 assert f
['mask'] == 'MFM_NONE'
377 cidr_protocols
= set([])
378 bitwise_protocols
= set([])
380 output
+= [" %s," % protocols_to_c(protocols
)]
381 output
+= [" %s," % protocols_to_c(cidr_protocols
)]
382 output
+= [" %s," % protocols_to_c(bitwise_protocols
)]
385 output
+= [" FLOW_U32OFS(%s)," % f
['prefix']]
387 output
+= [" -1, /* not usable for prefix lookup */"]
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[] = {")
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']))
409 def extract_ofp_fields(fn
):
416 input_file
= open(file_name
)
423 if re
.match('enum.*mf_field_id', 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('#')
436 elif re
.match('}', line
) or re
.match('\s+MFF_N_IDS', line
):
439 # Parse the comment preceding an MFF_ constant into 'comment',
440 # one line to an array element.
442 if not line
.startswith('/*'):
443 fatal("unexpected syntax between fields")
449 if line
.startswith('*/'):
452 if not line
.startswith('*'):
453 fatal("unexpected syntax within field")
456 if line
.startswith(' '):
458 if line
.startswith(' ') and comment
:
464 if line
.endswith('*/'):
465 line
= line
[:-2].rstrip()
471 comment
[-1] += " " + line
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]
482 # Parse the MFF_ constant(s).
485 m
= re
.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line
)
491 fatal("unexpected syntax looking for MFF_ constants")
493 if len(mffs
) > 1 or '<N>' in comment
[0]:
495 # Extract trailing integer.
496 m
= re
.match('.*[^0-9]([0-9]+)$', mff
)
498 fatal("%s lacks numeric suffix in register group" % mff
)
501 # Search-and-replace <N> within the comment,
502 # and drop lines that have <x> for x != n.
505 y
= x
.replace('<N>', n
)
506 if re
.search('<[0-9]+>', y
):
507 if ('<%s>' % n
) not in y
:
509 y
= re
.sub('<[0-9]+>', '', y
)
510 instance
+= [y
.strip()]
511 fields
+= [parse_field(mff
, instance
)]
513 fields
+= [parse_field(mffs
[0], comment
)]
523 ## ------------------------ ##
524 ## Documentation Generation ##
525 ## ------------------------ ##
527 def field_to_xml(field_node
, f
, body
, summary
):
531 if field_node
.hasAttribute('internal'):
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"]]
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"]]
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
)
561 if field_node
.hasAttribute('hidden'):
564 title
= field_node
.attributes
['title'].nodeValue
573 body
+= ["Name:;\\fB%s\\fR" % f
["name"]]
575 body
+= [" (aka \\fB%s\\fR)" % f
["extra_name"]]
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"]]
585 body
+= ["%d bits (%d bytes)" % (f
["n_bits"], f
["n_bits"] / 8)]
588 body
+= ["Format:;%s\n" % f
["formatting"]]
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"]]
595 access
= {True: "read/write",
596 False: "read-only"}[f
["writable"]]
597 body
+= ["Access:;%s\n" % access
]
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"]]]
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"]]]
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
)]
615 body
+= ['OXM:;T{\n%s\nT}\n' % r
'\[char59] '.join(oxms
)]
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
)]
622 body
+= ['NXM:;T{\n%s\nT}\n' % r
'\[char59] '.join(nxms
)]
627 body
+= [build
.nroff
.block_xml_to_nroff(field_node
.childNodes
)]
629 def group_xml_to_nroff(group_node
, fields
):
630 title
= group_node
.attributes
['title'].nodeValue
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
)
639 body
+= [build
.nroff
.block_xml_to_nroff([node
])]
643 '.SH \"%s\"\n' % build
.nroff
.text_to_nroff(title
.upper() + " FIELDS"),
648 'Name;Bytes;Mask;RW?;Prereqs;NXM/OXM Support\n',
649 '\_;\_;\_;\_;\_;\_\n']
653 return ''.join(content
)
655 def make_oxm_classes_xml(document
):
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('_')
665 s
+= r
"\fL0x%08x\fR;" % vendor
668 s
+= r
"\fL0x%04x\fR;" % class_
670 e
= document
.createElement('tbl')
671 e
.appendChild(document
.createTextNode(s
))
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
)
680 recursively_replace(child
, name
, replacement
)
682 def make_ovs_fields(meta_flow_h
, meta_flow_xml
):
683 fields
= extract_ofp_fields(meta_flow_h
)
686 fields_map
[f
['mff']] = f
688 document
= xml
.dom
.minidom
.parse(meta_flow_xml
)
689 doc
= document
.documentElement
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.
718 \\\\$2 \\(laURL: \\\\$1 \\(ra\\\\$3
720 .if \\n[.g] .mso www.tmac
722 ovs\-fields \- protocol header fields in OpenFlow and Open vSwitch
727 recursively_replace(doc
, 'oxm_classes', make_oxm_classes_xml(document
))
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
:
738 s
+= build
.nroff
.block_xml_to_nroff([node
])
742 fatal("%s: field not documented" % f
["mff"])
747 for oline
in s
.split("\n"):
748 oline
= oline
.strip()
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
'\[<=]')
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':
763 while j
>= 0 and output
[j
] == '.PP':
766 for i
in range(len(output
)):
767 if output
[i
] is not None:
774 if __name__
== "__main__":
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
))
785 for key
, value
in options
:
786 if key
in ['-h', '--help']:
788 elif key
== '--ovs-version':
794 sys
.stderr
.write("%s: missing command argument "
795 "(use --help for help)\n" % argv0
)
798 commands
= {"meta-flow": (make_meta_flow
, 1),
799 "nx-match": (make_nx_match
, 1),
800 "ovs-fields": (make_ovs_fields
, 2)}
802 if not args
[0] in commands
:
803 sys
.stderr
.write("%s: unknown command \"%s\" "
804 "(use --help for help)\n" % (argv0
, args
[0]))
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 "
811 % (argv0
, args
[0], n_args
, len(args
) - 1))