]>
git.proxmox.com Git - 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
.items())
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),
40 "packet type": ("MFS_PACKET_TYPE", 4, 4)}
42 PREREQS
= {"none": "MFP_NONE",
43 "Ethernet": "MFP_ETHERNET",
45 "VLAN VID": "MFP_VLAN_VID",
48 "IPv4/IPv6": "MFP_IP_ANY",
54 "ICMPv4": "MFP_ICMPV4",
55 "ICMPv6": "MFP_ICMPV6",
57 "ND solicit": "MFP_ND_SOLICIT",
58 "ND advert": "MFP_ND_ADVERT"}
60 # Maps a name prefix into an (experimenter ID, class) pair, so:
62 # - Standard OXM classes are written as (0, <oxm_class>)
64 # - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
66 # If a name matches more than one prefix, the longest one is used.
67 OXM_CLASSES
= {"NXM_OF_": (0, 0x0000),
68 "NXM_NX_": (0, 0x0001),
69 "OXM_OF_": (0, 0x8000),
70 "OXM_OF_PKT_REG": (0, 0x8001),
71 "ONFOXM_ET_": (0x4f4e4600, 0xffff),
73 # This is the experimenter OXM class for Nicira, which is the
74 # one that OVS would be using instead of NXM_OF_ and NXM_NX_
75 # if OVS didn't have those grandfathered in. It is currently
76 # used only to test support for experimenter OXM, since there
77 # are barely any real uses of experimenter OXM in the wild.
78 "NXOXM_ET_": (0x00002320, 0xffff)}
81 def oxm_name_to_class(name
):
84 for p
, c
in OXM_CLASSES
.items():
85 if name
.startswith(p
) and len(p
) > len(prefix
):
91 def decode_version_range(range):
93 return (VERSION
[range], VERSION
[range])
94 elif range.endswith('+'):
95 return (VERSION
[range[:-1]], max(VERSION
.values()))
97 a
, b
= re
.match(r
'^([^-]+)-([^-]+)$', range).groups()
98 return (VERSION
[a
], VERSION
[b
])
104 line
= input_file
.readline()
107 fatal("unexpected end of input")
115 sys
.stderr
.write("%s:%d: %s\n" % (file_name
, line_number
, msg
))
125 argv0
= os
.path
.basename(sys
.argv
[0])
127 %(argv0)s, for extracting OpenFlow field properties from meta-flow.h
128 usage: %(argv0)s INPUT [--meta-flow | --nx-match]
129 where INPUT points to lib/meta-flow.h in the source directory.
130 Depending on the option given, the output written to stdout is intended to be
131 saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C
133 ''' % {"argv0": argv0
})
138 m
= re
.match(r
'(.*) up to (.*)', s
)
140 struct
, member
= m
.groups()
141 return "offsetof(%s, %s)" % (struct
, member
)
143 return "sizeof(%s)" % s
146 def parse_oxms(s
, prefix
, n_bytes
):
150 return tuple(parse_oxm(s2
.strip(), prefix
, n_bytes
) for s2
in s
.split(','))
156 def parse_oxm(s
, prefix
, n_bytes
):
159 m
= re
.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s
)
161 fatal("%s: syntax error parsing %s" % (s
, prefix
))
163 name
, oxm_type
, of_version
, ovs_version
= m
.groups()
165 class_
= oxm_name_to_class(name
)
167 fatal("unknown OXM class for %s" % name
)
168 oxm_vendor
, oxm_class
= class_
170 if class_
in match_types
:
171 if oxm_type
in match_types
[class_
]:
172 fatal("duplicate match type for %s (conflicts with %s)" %
173 (name
, match_types
[class_
][oxm_type
]))
175 match_types
[class_
] = dict()
176 match_types
[class_
][oxm_type
] = name
178 # Normally the oxm_length is the size of the field, but for experimenter
179 # OXMs oxm_length also includes the 4-byte experimenter ID.
181 if oxm_class
== 0xffff:
184 header
= (oxm_vendor
, oxm_class
, int(oxm_type
), oxm_length
)
187 if of_version
not in VERSION
:
188 fatal("%s: unknown OpenFlow version %s" % (name
, of_version
))
189 of_version_nr
= VERSION
[of_version
]
190 if of_version_nr
< VERSION
['1.2']:
191 fatal("%s: claimed version %s predates OXM" % (name
, of_version
))
192 elif prefix
== 'OXM':
193 fatal("%s: missing OpenFlow version number" % name
)
197 return (header
, name
, of_version_nr
, ovs_version
)
200 def parse_field(mff
, comment
):
203 # First line of comment is the field name.
204 m
= re
.match(r
'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment
[0])
206 fatal("%s lacks field name" % mff
)
207 f
['name'], f
['extra_name'] = m
.groups()
209 # Find the last blank line the comment. The field definitions
212 for i
in range(len(comment
)):
216 fatal("%s: missing blank line in comment" % mff
)
219 for key
in ("Type", "Maskable", "Formatting", "Prerequisites",
220 "Access", "Prefix lookup member",
221 "OXM", "NXM", "OF1.0", "OF1.1"):
223 for fline
in comment
[blank
+ 1:]:
224 m
= re
.match(r
'([^:]+):\s+(.*)\.$', fline
)
226 fatal("%s: syntax error parsing key-value pair as part of %s"
228 key
, value
= m
.groups()
230 fatal("%s: unknown key" % key
)
231 elif key
== 'Code point':
233 elif d
[key
] is not None:
234 fatal("%s: duplicate key" % key
)
236 for key
, value
in d
.items():
237 if not value
and key
not in ("OF1.0", "OF1.1",
238 "Prefix lookup member", "Notes"):
239 fatal("%s: missing %s" % (mff
, key
))
241 m
= re
.match(r
'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d
['Type'])
243 fatal("%s: syntax error in type" % mff
)
245 if type_
not in TYPES
:
246 fatal("%s: unknown type %s" % (mff
, d
['Type']))
248 f
['n_bytes'] = TYPES
[type_
][0]
250 f
['n_bits'] = int(m
.group(2))
251 if f
['n_bits'] > f
['n_bytes'] * 8:
252 fatal("%s: more bits (%d) than field size (%d)"
253 % (mff
, f
['n_bits'], 8 * f
['n_bytes']))
255 f
['n_bits'] = 8 * f
['n_bytes']
256 f
['variable'] = TYPES
[type_
][1]
258 if d
['Maskable'] == 'no':
259 f
['mask'] = 'MFM_NONE'
260 elif d
['Maskable'] == 'bitwise':
261 f
['mask'] = 'MFM_FULLY'
263 fatal("%s: unknown maskable %s" % (mff
, d
['Maskable']))
265 fmt
= FORMATTING
.get(d
['Formatting'])
267 fatal("%s: unknown format %s" % (mff
, d
['Formatting']))
268 f
['formatting'] = d
['Formatting']
269 if f
['n_bytes'] < fmt
[1] or f
['n_bytes'] > fmt
[2]:
270 fatal("%s: %d-byte field can't be formatted as %s"
271 % (mff
, f
['n_bytes'], d
['Formatting']))
274 f
['prereqs'] = d
['Prerequisites']
275 if f
['prereqs'] not in PREREQS
:
276 fatal("%s: unknown prerequisites %s" % (mff
, d
['Prerequisites']))
278 if d
['Access'] == 'read-only':
279 f
['writable'] = False
280 elif d
['Access'] == 'read/write':
283 fatal("%s: unknown access %s" % (mff
, d
['Access']))
285 f
['OF1.0'] = d
['OF1.0']
286 if not d
['OF1.0'] in (None, 'exact match', 'CIDR mask'):
287 fatal("%s: unknown OF1.0 match type %s" % (mff
, d
['OF1.0']))
289 f
['OF1.1'] = d
['OF1.1']
290 if not d
['OF1.1'] in (None, 'exact match', 'bitwise mask'):
291 fatal("%s: unknown OF1.1 match type %s" % (mff
, d
['OF1.1']))
293 f
['OXM'] = (parse_oxms(d
['OXM'], 'OXM', f
['n_bytes']) +
294 parse_oxms(d
['NXM'], 'NXM', f
['n_bytes']))
296 f
['prefix'] = d
["Prefix lookup member"]
301 def protocols_to_c(protocols
):
302 if protocols
== set(['of10', 'of11', 'oxm']):
303 return 'OFPUTIL_P_ANY'
304 elif protocols
== set(['of11', 'oxm']):
305 return 'OFPUTIL_P_NXM_OF11_UP'
306 elif protocols
== set(['oxm']):
307 return 'OFPUTIL_P_NXM_OXM_ANY'
308 elif protocols
== set([]):
309 return 'OFPUTIL_P_NONE'
314 def autogen_c_comment():
316 "/* Generated automatically; do not modify! -*- buffer-read-only: t -*- */",
319 def make_meta_flow(meta_flow_h
):
320 fields
= extract_ofp_fields(meta_flow_h
)
321 output
= autogen_c_comment()
324 output
+= [" %s," % f
['mff']]
326 output
+= [" \"%s\", \"%s\"," % (f
['name'], f
['extra_name'])]
328 output
+= [" \"%s\", NULL," % f
['name']]
334 output
+= [" %d, %d, %s," % (f
['n_bytes'], f
['n_bits'], variable
)]
340 output
+= [" %s, %s, %s, %s, false,"
341 % (f
['mask'], f
['string'], PREREQS
[f
['prereqs']], rw
)]
346 if f
['mff'] in ('MFF_DL_VLAN', 'MFF_DL_VLAN_PCP'):
347 # MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to
348 # OF1.1, nor do they have NXM or OXM assignments, but their
349 # meanings can be expressed in every protocol, which is the goal of
351 protocols
= set(["of10", "of11", "oxm"])
355 protocols |
= set(["of10"])
357 protocols |
= set(["of11"])
359 protocols |
= set(["oxm"])
361 if f
['mask'] == 'MFM_FULLY':
362 cidr_protocols
= protocols
.copy()
363 bitwise_protocols
= protocols
.copy()
365 if of10
== 'exact match':
366 bitwise_protocols
-= set(['of10'])
367 cidr_protocols
-= set(['of10'])
368 elif of10
== 'CIDR mask':
369 bitwise_protocols
-= set(['of10'])
373 if of11
== 'exact match':
374 bitwise_protocols
-= set(['of11'])
375 cidr_protocols
-= set(['of11'])
377 assert of11
in (None, 'bitwise mask')
379 assert f
['mask'] == 'MFM_NONE'
380 cidr_protocols
= set([])
381 bitwise_protocols
= set([])
383 output
+= [" %s," % protocols_to_c(protocols
)]
384 output
+= [" %s," % protocols_to_c(cidr_protocols
)]
385 output
+= [" %s," % protocols_to_c(bitwise_protocols
)]
388 output
+= [" FLOW_U32OFS(%s)," % f
['prefix']]
390 output
+= [" -1, /* not usable for prefix lookup */"]
397 def make_nx_match(meta_flow_h
):
398 fields
= extract_ofp_fields(meta_flow_h
)
399 output
= autogen_c_comment()
400 print("static struct nxm_field_index all_nxm_fields[] = {")
402 # Sort by OpenFlow version number (nx-match.c depends on this).
403 for oxm
in sorted(f
['OXM'], key
=lambda x
: x
[2]):
404 header
= ("NXM_HEADER(0x%x,0x%x,%s,0,%d)" % oxm
[0])
405 print("""{ .nf = { %s, %d, "%s", %s } },""" % (
406 header
, oxm
[2], oxm
[1], f
['mff']))
412 def extract_ofp_fields(fn
):
419 input_file
= open(file_name
)
426 if re
.match('enum.*mf_field_id', line
):
431 first_line_number
= line_number
432 here
= '%s:%d' % (file_name
, line_number
)
433 if (line
.startswith('/*')
434 or line
.startswith(' *')
435 or line
.startswith('#')
439 elif re
.match('}', line
) or re
.match('\s+MFF_N_IDS', line
):
442 # Parse the comment preceding an MFF_ constant into 'comment',
443 # one line to an array element.
445 if not line
.startswith('/*'):
446 fatal("unexpected syntax between fields")
452 if line
.startswith('*/'):
455 if not line
.startswith('*'):
456 fatal("unexpected syntax within field")
459 if line
.startswith(' '):
461 if line
.startswith(' ') and comment
:
467 if line
.endswith('*/'):
468 line
= line
[:-2].rstrip()
474 comment
[-1] += " " + line
479 # Drop blank lines at each end of comment.
480 while comment
and not comment
[0]:
481 comment
= comment
[1:]
482 while comment
and not comment
[-1]:
483 comment
= comment
[:-1]
485 # Parse the MFF_ constant(s).
488 m
= re
.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line
)
494 fatal("unexpected syntax looking for MFF_ constants")
496 if len(mffs
) > 1 or '<N>' in comment
[0]:
498 # Extract trailing integer.
499 m
= re
.match('.*[^0-9]([0-9]+)$', mff
)
501 fatal("%s lacks numeric suffix in register group" % mff
)
504 # Search-and-replace <N> within the comment,
505 # and drop lines that have <x> for x != n.
508 y
= x
.replace('<N>', n
)
509 if re
.search('<[0-9]+>', y
):
510 if ('<%s>' % n
) not in y
:
512 y
= re
.sub('<[0-9]+>', '', y
)
513 instance
+= [y
.strip()]
514 fields
+= [parse_field(mff
, instance
)]
516 fields
+= [parse_field(mffs
[0], comment
)]
526 ## ------------------------ ##
527 ## Documentation Generation ##
528 ## ------------------------ ##
530 def field_to_xml(field_node
, f
, body
, summary
):
534 if field_node
.hasAttribute('internal'):
537 min_of_version
= None
538 min_ovs_version
= None
539 for header
, name
, of_version_nr
, ovs_version_s
in f
['OXM']:
540 if (not name
.startswith('NXM')
541 and (min_ovs_version
is None or of_version_nr
< min_of_version
)):
542 min_of_version
= of_version_nr
543 ovs_version
= [int(x
) for x
in ovs_version_s
.split('.')]
544 if min_ovs_version
is None or ovs_version
< min_ovs_version
:
545 min_ovs_version
= ovs_version
546 summary
+= ["\\fB%s\\fR" % f
["name"]]
548 summary
+= [" aka \\fB%s\\fR" % f
["extra_name"]]
549 summary
+= [";%d" % f
["n_bytes"]]
550 if f
["n_bits"] != 8 * f
["n_bytes"]:
551 summary
+= [" (low %d bits)" % f
["n_bits"]]
552 summary
+= [";%s;" % {"MFM_NONE": "no", "MFM_FULLY": "yes"}[f
["mask"]]]
553 summary
+= ["%s;" % {True: "yes", False: "no"}[f
["writable"]]]
554 summary
+= ["%s;" % f
["prereqs"]]
556 if min_of_version
is not None:
557 support
+= ["OF %s+" % VERSION_REVERSE
[min_of_version
]]
558 if min_ovs_version
is not None:
559 support
+= ["OVS %s+" % '.'.join([str(x
) for x
in min_ovs_version
])]
560 summary
+= ' and '.join(support
)
564 if field_node
.hasAttribute('hidden'):
567 title
= field_node
.attributes
['title'].nodeValue
576 body
+= ["Name:;\\fB%s\\fR" % f
["name"]]
578 body
+= [" (aka \\fB%s\\fR)" % f
["extra_name"]]
582 if f
["n_bits"] != 8 * f
["n_bytes"]:
583 body
+= ["%d bits (only the least-significant %d bits "
584 "may be nonzero)" % (f
["n_bytes"] * 8, f
["n_bits"])]
585 elif f
["n_bits"] <= 128:
586 body
+= ["%d bits" % f
["n_bits"]]
588 body
+= ["%d bits (%d bytes)" % (f
["n_bits"], f
["n_bits"] / 8)]
591 body
+= ["Format:;%s\n" % f
["formatting"]]
593 masks
= {"MFM_NONE": "not maskable",
594 "MFM_FULLY": "arbitrary bitwise masks"}
595 body
+= ["Masking:;%s\n" % masks
[f
["mask"]]]
596 body
+= ["Prerequisites:;%s\n" % f
["prereqs"]]
598 access
= {True: "read/write",
599 False: "read-only"}[f
["writable"]]
600 body
+= ["Access:;%s\n" % access
]
602 of10
= {None: "not supported",
603 "exact match": "yes (exact match only)",
604 "CIDR mask": "yes (CIDR match only)"}
605 body
+= ["OpenFlow 1.0:;%s\n" % of10
[f
["OF1.0"]]]
607 of11
= {None: "not supported",
608 "exact match": "yes (exact match only)",
609 "bitwise mask": "yes"}
610 body
+= ["OpenFlow 1.1:;%s\n" % of11
[f
["OF1.1"]]]
613 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')]:
614 of_version
= VERSION_REVERSE
[of_version_nr
]
615 oxms
+= [r
"\fB%s\fR (%d) since OpenFlow %s and Open vSwitch %s" % (name
, header
[2], of_version
, ovs_version
)]
618 body
+= ['OXM:;T{\n%s\nT}\n' % r
'\[char59] '.join(oxms
)]
621 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')]:
622 nxms
+= [r
"\fB%s\fR (%d) since Open vSwitch %s" % (name
, header
[2], ovs_version
)]
625 body
+= ['NXM:;T{\n%s\nT}\n' % r
'\[char59] '.join(nxms
)]
630 body
+= [build
.nroff
.block_xml_to_nroff(field_node
.childNodes
)]
632 def group_xml_to_nroff(group_node
, fields
):
633 title
= group_node
.attributes
['title'].nodeValue
637 for node
in group_node
.childNodes
:
638 if node
.nodeType
== node
.ELEMENT_NODE
and node
.tagName
== 'field':
639 id_
= node
.attributes
['id'].nodeValue
640 field_to_xml(node
, fields
[id_
], body
, summary
)
642 body
+= [build
.nroff
.block_xml_to_nroff([node
])]
646 '.SH \"%s\"\n' % build
.nroff
.text_to_nroff(title
.upper() + " FIELDS"),
651 'Name;Bytes;Mask;RW?;Prereqs;NXM/OXM Support\n',
652 '\_;\_;\_;\_;\_;\_\n']
656 return ''.join(content
)
658 def make_oxm_classes_xml(document
):
664 for key
in sorted(OXM_CLASSES
, key
=OXM_CLASSES
.get
):
665 vendor
, class_
= OXM_CLASSES
.get(key
)
666 s
+= r
"\fB%s\fR;" % key
.rstrip('_')
668 s
+= r
"\fL0x%08x\fR;" % vendor
671 s
+= r
"\fL0x%04x\fR;" % class_
673 e
= document
.createElement('tbl')
674 e
.appendChild(document
.createTextNode(s
))
677 def recursively_replace(node
, name
, replacement
):
678 for child
in node
.childNodes
:
679 if child
.nodeType
== node
.ELEMENT_NODE
:
680 if child
.tagName
== name
:
681 node
.replaceChild(replacement
, child
)
683 recursively_replace(child
, name
, replacement
)
685 def make_ovs_fields(meta_flow_h
, meta_flow_xml
):
686 fields
= extract_ofp_fields(meta_flow_h
)
689 fields_map
[f
['mff']] = f
691 document
= xml
.dom
.minidom
.parse(meta_flow_xml
)
692 doc
= document
.documentElement
700 .\\" -*- mode: troff; coding: utf-8 -*-
701 .TH "ovs\-fields" 7 "%s" "Open vSwitch" "Open vSwitch Manual"
702 .fp 5 L CR \\" Make fixed-width font available as \\fL.
727 \\\\$2 \\(laURL: \\\\$1 \\(ra\\\\$3
729 .if \\n[.g] .mso www.tmac
731 ovs\-fields \- protocol header fields in OpenFlow and Open vSwitch
736 recursively_replace(doc
, 'oxm_classes', make_oxm_classes_xml(document
))
739 for node
in doc
.childNodes
:
740 if node
.nodeType
== node
.ELEMENT_NODE
and node
.tagName
== "group":
741 s
+= group_xml_to_nroff(node
, fields_map
)
742 elif node
.nodeType
== node
.TEXT_NODE
:
743 assert node
.data
.isspace()
744 elif node
.nodeType
== node
.COMMENT_NODE
:
747 s
+= build
.nroff
.block_xml_to_nroff([node
])
751 fatal("%s: field not documented" % f
["mff"])
756 for oline
in s
.split("\n"):
757 oline
= oline
.strip()
759 # Life is easier with nroff if we don't try to feed it Unicode.
760 # Fortunately, we only use a few characters outside the ASCII range.
761 oline
= oline
.replace(u
'\u2208', r
'\[mo]')
762 oline
= oline
.replace(u
'\u2260', r
'\[!=]')
763 oline
= oline
.replace(u
'\u2264', r
'\[<=]')
764 oline
= oline
.replace(u
'\u2265', r
'\[>=]')
765 oline
= oline
.replace(u
'\u00d7', r
'\[mu]')
769 # nroff tends to ignore .bp requests if they come after .PP requests,
770 # so remove .PPs that precede .bp.
771 for i
in range(len(output
)):
772 if output
[i
] == '.bp':
774 while j
>= 0 and output
[j
] == '.PP':
777 for i
in range(len(output
)):
778 if output
[i
] is not None:
785 if __name__
== "__main__":
788 options
, args
= getopt
.gnu_getopt(sys
.argv
[1:], 'h',
789 ['help', 'ovs-version='])
790 except getopt
.GetoptError
as geo
:
791 sys
.stderr
.write("%s: %s\n" % (argv0
, geo
.msg
))
796 for key
, value
in options
:
797 if key
in ['-h', '--help']:
799 elif key
== '--ovs-version':
805 sys
.stderr
.write("%s: missing command argument "
806 "(use --help for help)\n" % argv0
)
809 commands
= {"meta-flow": (make_meta_flow
, 1),
810 "nx-match": (make_nx_match
, 1),
811 "ovs-fields": (make_ovs_fields
, 2)}
813 if not args
[0] in commands
:
814 sys
.stderr
.write("%s: unknown command \"%s\" "
815 "(use --help for help)\n" % (argv0
, args
[0]))
818 func
, n_args
= commands
[args
[0]]
819 if len(args
) - 1 != n_args
:
820 sys
.stderr
.write("%s: \"%s\" requires %d arguments but %d "
822 % (argv0
, args
[0], n_args
, len(args
) - 1))