mirror of
https://github.com/openvswitch/ovs
synced 2025-08-22 01:51:26 +00:00
The table has only 6 columns, not 7. This doesn't really affect rendering. Only slightly affects calculations around how much space the table needs. Fixes: 96fee5e0a2a0 ("ovs-fields: New manpage to document Open vSwitch and OpenFlow fields.") Acked-by: Simon Horman <horms@ovn.org> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
515 lines
14 KiB
Python
Executable File
515 lines
14 KiB
Python
Executable File
#! /usr/bin/python3
|
|
|
|
import getopt
|
|
import sys
|
|
import os.path
|
|
import xml.dom.minidom
|
|
|
|
from ovs_build_helpers import nroff
|
|
from ovs_build_helpers.extract_ofp_fields import (
|
|
extract_ofp_fields,
|
|
PREREQS,
|
|
OXM_CLASSES,
|
|
VERSION,
|
|
fatal,
|
|
n_errors,
|
|
)
|
|
|
|
VERSION_REVERSE = dict((v, k) for k, v in VERSION.items())
|
|
|
|
|
|
def oxm_name_to_class(name):
|
|
prefix = ""
|
|
class_ = None
|
|
for p, c in OXM_CLASSES.items():
|
|
if name.startswith(p) and len(p) > len(prefix):
|
|
prefix = p
|
|
class_ = c
|
|
return class_
|
|
|
|
|
|
def is_standard_oxm(name):
|
|
oxm_vendor, oxm_class, oxm_class_type = oxm_name_to_class(name)
|
|
return oxm_class_type == "standard"
|
|
|
|
|
|
def usage():
|
|
argv0 = os.path.basename(sys.argv[0])
|
|
print(
|
|
"""\
|
|
%(argv0)s, for extracting OpenFlow field properties from meta-flow.h
|
|
usage: %(argv0)s INPUT [--meta-flow | --nx-match]
|
|
where INPUT points to lib/meta-flow.h in the source directory.
|
|
Depending on the option given, the output written to stdout is intended to be
|
|
saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C
|
|
file to #include.\
|
|
"""
|
|
% {"argv0": argv0}
|
|
)
|
|
sys.exit(0)
|
|
|
|
|
|
def protocols_to_c(protocols):
|
|
if protocols == set(["of10", "of11", "oxm"]):
|
|
return "OFPUTIL_P_ANY"
|
|
elif protocols == set(["of11", "oxm"]):
|
|
return "OFPUTIL_P_NXM_OF11_UP"
|
|
elif protocols == set(["oxm"]):
|
|
return "OFPUTIL_P_NXM_OXM_ANY"
|
|
elif protocols == set([]):
|
|
return "OFPUTIL_P_NONE"
|
|
else:
|
|
assert False
|
|
|
|
|
|
def autogen_c_comment():
|
|
return [
|
|
"/* Generated automatically; do not modify! "
|
|
"-*- buffer-read-only: t -*- */",
|
|
"",
|
|
]
|
|
|
|
|
|
def make_meta_flow(meta_flow_h):
|
|
fields = extract_ofp_fields(meta_flow_h)
|
|
output = autogen_c_comment()
|
|
for f in fields:
|
|
output += ["{"]
|
|
output += [" %s," % f["mff"]]
|
|
if f["extra_name"]:
|
|
output += [' "%s", "%s",' % (f["name"], f["extra_name"])]
|
|
else:
|
|
output += [' "%s", NULL,' % f["name"]]
|
|
|
|
if f["variable"]:
|
|
variable = "true"
|
|
else:
|
|
variable = "false"
|
|
output += [" %d, %d, %s," % (f["n_bytes"], f["n_bits"], variable)]
|
|
|
|
if f["writable"]:
|
|
rw = "true"
|
|
else:
|
|
rw = "false"
|
|
output += [
|
|
" %s, %s, %s, %s, false,"
|
|
% (f["mask"], f["string"], PREREQS[f["prereqs"]], rw)
|
|
]
|
|
|
|
oxm = f["OXM"]
|
|
of10 = f["OF1.0"]
|
|
of11 = f["OF1.1"]
|
|
if f["mff"] in ("MFF_DL_VLAN", "MFF_DL_VLAN_PCP"):
|
|
# MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to
|
|
# OF1.1, nor do they have NXM or OXM assignments, but their
|
|
# meanings can be expressed in every protocol, which is the goal of
|
|
# this member.
|
|
protocols = set(["of10", "of11", "oxm"])
|
|
else:
|
|
protocols = set([])
|
|
if of10:
|
|
protocols |= set(["of10"])
|
|
if of11:
|
|
protocols |= set(["of11"])
|
|
if oxm:
|
|
protocols |= set(["oxm"])
|
|
|
|
if f["mask"] == "MFM_FULLY":
|
|
cidr_protocols = protocols.copy()
|
|
bitwise_protocols = protocols.copy()
|
|
|
|
if of10 == "exact match":
|
|
bitwise_protocols -= set(["of10"])
|
|
cidr_protocols -= set(["of10"])
|
|
elif of10 == "CIDR mask":
|
|
bitwise_protocols -= set(["of10"])
|
|
else:
|
|
assert of10 is None
|
|
|
|
if of11 == "exact match":
|
|
bitwise_protocols -= set(["of11"])
|
|
cidr_protocols -= set(["of11"])
|
|
else:
|
|
assert of11 in (None, "bitwise mask")
|
|
else:
|
|
assert f["mask"] == "MFM_NONE"
|
|
cidr_protocols = set([])
|
|
bitwise_protocols = set([])
|
|
|
|
output += [" %s," % protocols_to_c(protocols)]
|
|
output += [" %s," % protocols_to_c(cidr_protocols)]
|
|
output += [" %s," % protocols_to_c(bitwise_protocols)]
|
|
|
|
if f["prefix"]:
|
|
output += [" FLOW_U32OFS(%s)," % f["prefix"]]
|
|
else:
|
|
output += [" -1, /* not usable for prefix lookup */"]
|
|
|
|
output += ["},"]
|
|
for oline in output:
|
|
print(oline)
|
|
|
|
|
|
def make_nx_match(meta_flow_h):
|
|
fields = extract_ofp_fields(meta_flow_h)
|
|
output = autogen_c_comment()
|
|
print("static struct nxm_field_index all_nxm_fields[] = {")
|
|
for f in fields:
|
|
# Sort by OpenFlow version number (nx-match.c depends on this).
|
|
for oxm in sorted(f["OXM"], key=lambda x: x[2]):
|
|
header = "NXM_HEADER(0x%x,0x%x,%s,0,%d)" % oxm[0]
|
|
print(
|
|
"""{ .nf = { %s, %d, "%s", %s } },"""
|
|
% (header, oxm[2], oxm[1], f["mff"])
|
|
)
|
|
print("};")
|
|
for oline in output:
|
|
print(oline)
|
|
|
|
|
|
# ------------------------ #
|
|
# Documentation Generation #
|
|
# ------------------------ #
|
|
|
|
|
|
def field_to_xml(field_node, f, body, summary):
|
|
f["used"] = True
|
|
|
|
# Summary.
|
|
if field_node.hasAttribute("internal"):
|
|
return
|
|
|
|
min_of_version = None
|
|
min_ovs_version = None
|
|
for header, name, of_version_nr, ovs_version_s in f["OXM"]:
|
|
if is_standard_oxm(name) and (
|
|
min_ovs_version is None or of_version_nr < min_of_version
|
|
):
|
|
min_of_version = of_version_nr
|
|
ovs_version = [int(x) for x in ovs_version_s.split(".")]
|
|
if min_ovs_version is None or ovs_version < min_ovs_version:
|
|
min_ovs_version = ovs_version
|
|
summary += [r"\fB%s\fR" % f["name"]]
|
|
if f["extra_name"]:
|
|
summary += [r" aka \fB%s\fR" % f["extra_name"]]
|
|
summary += [";%d" % f["n_bytes"]]
|
|
if f["n_bits"] != 8 * f["n_bytes"]:
|
|
summary += [" (low %d bits)" % f["n_bits"]]
|
|
summary += [";%s;" % {"MFM_NONE": "no", "MFM_FULLY": "yes"}[f["mask"]]]
|
|
summary += ["%s;" % {True: "yes", False: "no"}[f["writable"]]]
|
|
summary += ["%s;" % f["prereqs"]]
|
|
support = []
|
|
if min_of_version is not None:
|
|
support += ["OF %s+" % VERSION_REVERSE[min_of_version]]
|
|
if min_ovs_version is not None:
|
|
support += ["OVS %s+" % ".".join([str(x) for x in min_ovs_version])]
|
|
summary += " and ".join(support)
|
|
summary += ["\n"]
|
|
|
|
# Full description.
|
|
if field_node.hasAttribute("hidden"):
|
|
return
|
|
|
|
title = field_node.attributes["title"].nodeValue
|
|
|
|
body += [
|
|
r""".PP
|
|
\fB%s Field\fR
|
|
.TS
|
|
tab(;),nowarn;
|
|
l lx.
|
|
"""
|
|
% title
|
|
]
|
|
|
|
body += [r"Name:;\fB%s\fR" % f["name"]]
|
|
if f["extra_name"]:
|
|
body += [r" (aka \fB%s\fR)" % f["extra_name"]]
|
|
body += ["\n"]
|
|
|
|
body += ["Width:;"]
|
|
if f["n_bits"] != 8 * f["n_bytes"]:
|
|
body += [
|
|
"%d bits (only the least-significant %d bits "
|
|
"may be nonzero)" % (f["n_bytes"] * 8, f["n_bits"])
|
|
]
|
|
elif f["n_bits"] <= 128:
|
|
body += ["%d bits" % f["n_bits"]]
|
|
else:
|
|
body += ["%d bits (%d bytes)" % (f["n_bits"], f["n_bits"] / 8)]
|
|
body += ["\n"]
|
|
|
|
body += ["Format:;%s\n" % f["formatting"]]
|
|
|
|
masks = {
|
|
"MFM_NONE": "not maskable",
|
|
"MFM_FULLY": "arbitrary bitwise masks",
|
|
}
|
|
body += ["Masking:;%s\n" % masks[f["mask"]]]
|
|
body += ["Prerequisites:;%s\n" % f["prereqs"]]
|
|
|
|
access = {True: "read/write", False: "read-only"}[f["writable"]]
|
|
body += ["Access:;%s\n" % access]
|
|
|
|
of10 = {
|
|
None: "not supported",
|
|
"exact match": "yes (exact match only)",
|
|
"CIDR mask": "yes (CIDR match only)",
|
|
}
|
|
body += ["OpenFlow 1.0:;%s\n" % of10[f["OF1.0"]]]
|
|
|
|
of11 = {
|
|
None: "not supported",
|
|
"exact match": "yes (exact match only)",
|
|
"bitwise mask": "yes",
|
|
}
|
|
body += ["OpenFlow 1.1:;%s\n" % of11[f["OF1.1"]]]
|
|
|
|
oxms = []
|
|
for header, name, of_version_nr, ovs_version in [
|
|
x
|
|
for x in sorted(f["OXM"], key=lambda x: x[2])
|
|
if is_standard_oxm(x[1])
|
|
]:
|
|
of_version = VERSION_REVERSE[of_version_nr]
|
|
oxms += [
|
|
r"\fB%s\fR (%d) since OpenFlow %s and Open vSwitch %s"
|
|
% (name, header[2], of_version, ovs_version)
|
|
]
|
|
if not oxms:
|
|
oxms = ["none"]
|
|
body += ["OXM:;T{\n%s\nT}\n" % r"\[char59] ".join(oxms)]
|
|
|
|
nxms = []
|
|
for header, name, of_version_nr, ovs_version in [
|
|
x
|
|
for x in sorted(f["OXM"], key=lambda x: x[2])
|
|
if not is_standard_oxm(x[1])
|
|
]:
|
|
nxms += [
|
|
r"\fB%s\fR (%d) since Open vSwitch %s"
|
|
% (name, header[2], ovs_version)
|
|
]
|
|
if not nxms:
|
|
nxms = ["none"]
|
|
body += ["NXM:;T{\n%s\nT}\n" % r"\[char59] ".join(nxms)]
|
|
|
|
body += [".TE\n"]
|
|
|
|
body += [".PP\n"]
|
|
body += [nroff.block_xml_to_nroff(field_node.childNodes)]
|
|
|
|
|
|
def group_xml_to_nroff(group_node, fields):
|
|
title = group_node.attributes["title"].nodeValue
|
|
|
|
summary = []
|
|
body = []
|
|
for node in group_node.childNodes:
|
|
if node.nodeType == node.ELEMENT_NODE and node.tagName == "field":
|
|
id_ = node.attributes["id"].nodeValue
|
|
field_to_xml(node, fields[id_], body, summary)
|
|
else:
|
|
body += [nroff.block_xml_to_nroff([node])]
|
|
|
|
content = [
|
|
".bp\n",
|
|
'.SH "%s"\n' % nroff.text_to_nroff(title.upper() + " FIELDS"),
|
|
'.SS "Summary:"\n',
|
|
".TS\n",
|
|
"tab(;),nowarn;\n",
|
|
"l l l l l l.\n",
|
|
"Name;Bytes;Mask;RW?;Prereqs;NXM/OXM Support\n",
|
|
r"\_;\_;\_;\_;\_;\_",
|
|
"\n",
|
|
]
|
|
content += summary
|
|
content += [".TE\n"]
|
|
content += body
|
|
return "".join(content)
|
|
|
|
|
|
def make_oxm_classes_xml(document):
|
|
s = r"""tab(;),nowarn;
|
|
l l l.
|
|
Prefix;Vendor;Class
|
|
\_;\_;\_
|
|
"""
|
|
for key in sorted(OXM_CLASSES, key=OXM_CLASSES.get):
|
|
vendor, class_, class_type = OXM_CLASSES.get(key)
|
|
s += r"\fB%s\fR;" % key.rstrip("_")
|
|
if vendor:
|
|
s += r"\fL0x%08x\fR;" % vendor
|
|
else:
|
|
s += "(none);"
|
|
s += r"\fL0x%04x\fR;" % class_
|
|
s += "\n"
|
|
e = document.createElement("tbl")
|
|
e.appendChild(document.createTextNode(s))
|
|
return e
|
|
|
|
|
|
def recursively_replace(node, name, replacement):
|
|
for child in node.childNodes:
|
|
if child.nodeType == node.ELEMENT_NODE:
|
|
if child.tagName == name:
|
|
node.replaceChild(replacement, child)
|
|
else:
|
|
recursively_replace(child, name, replacement)
|
|
|
|
|
|
def make_ovs_fields(meta_flow_h, meta_flow_xml):
|
|
fields = extract_ofp_fields(meta_flow_h)
|
|
fields_map = {}
|
|
for f in fields:
|
|
fields_map[f["mff"]] = f
|
|
|
|
document = xml.dom.minidom.parse(meta_flow_xml)
|
|
doc = document.documentElement
|
|
|
|
global version
|
|
if version is None:
|
|
version = "UNKNOWN"
|
|
|
|
print(
|
|
r"""'\" tp
|
|
.\" -*- mode: troff; coding: utf-8 -*-
|
|
.TH "ovs\-fields" 7 "%s" "Open vSwitch" "Open vSwitch Manual"
|
|
.fp 5 L CR \" Make fixed-width font available as \fL.
|
|
.de ST
|
|
. PP
|
|
. RS -0.15in
|
|
. I "\\$1"
|
|
. RE
|
|
..
|
|
|
|
.de SU
|
|
. PP
|
|
. I "\\$1"
|
|
..
|
|
|
|
.de IQ
|
|
. br
|
|
. ns
|
|
. IP "\\$1"
|
|
..
|
|
|
|
.de TQ
|
|
. br
|
|
. ns
|
|
. TP "\\$1"
|
|
..
|
|
.de URL
|
|
\\$2 \(laURL: \\$1 \(ra\\$3
|
|
..
|
|
.if \n[.g] .mso www.tmac
|
|
.SH NAME
|
|
ovs\-fields \- protocol header fields in OpenFlow and Open vSwitch
|
|
.
|
|
.PP
|
|
"""
|
|
% version
|
|
)
|
|
|
|
recursively_replace(doc, "oxm_classes", make_oxm_classes_xml(document))
|
|
|
|
s = ""
|
|
for node in doc.childNodes:
|
|
if node.nodeType == node.ELEMENT_NODE and node.tagName == "group":
|
|
s += group_xml_to_nroff(node, fields_map)
|
|
elif node.nodeType == node.TEXT_NODE:
|
|
assert node.data.isspace()
|
|
elif node.nodeType == node.COMMENT_NODE:
|
|
pass
|
|
else:
|
|
s += nroff.block_xml_to_nroff([node])
|
|
|
|
for f in fields:
|
|
if "used" not in f:
|
|
fatal(
|
|
"%s: field not documented "
|
|
"(please add documentation in lib/meta-flow.xml)" % f["mff"]
|
|
)
|
|
if n_errors:
|
|
sys.exit(1)
|
|
|
|
output = []
|
|
for oline in s.split("\n"):
|
|
oline = oline.strip()
|
|
|
|
# Life is easier with nroff if we don't try to feed it Unicode.
|
|
# Fortunately, we only use a few characters outside the ASCII range.
|
|
oline = oline.replace(u"\u2208", r"\[mo]")
|
|
oline = oline.replace(u"\u2260", r"\[!=]")
|
|
oline = oline.replace(u"\u2264", r"\[<=]")
|
|
oline = oline.replace(u"\u2265", r"\[>=]")
|
|
oline = oline.replace(u"\u00d7", r"\[mu]")
|
|
if len(oline):
|
|
output += [oline]
|
|
|
|
# nroff tends to ignore .bp requests if they come after .PP requests,
|
|
# so remove .PPs that precede .bp.
|
|
for i in range(len(output)):
|
|
if output[i] == ".bp":
|
|
j = i - 1
|
|
while j >= 0 and output[j] == ".PP":
|
|
output[j] = None
|
|
j -= 1
|
|
for i in range(len(output)):
|
|
if output[i] is not None:
|
|
print(output[i])
|
|
|
|
|
|
# ------------ #
|
|
# Main Program #
|
|
# ------------ #
|
|
|
|
if __name__ == "__main__":
|
|
argv0 = sys.argv[0]
|
|
try:
|
|
options, args = getopt.gnu_getopt(
|
|
sys.argv[1:], "h", ["help", "ovs-version="]
|
|
)
|
|
except getopt.GetoptError as geo:
|
|
sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
|
|
sys.exit(1)
|
|
|
|
global version
|
|
version = None
|
|
for key, value in options:
|
|
if key in ["-h", "--help"]:
|
|
usage()
|
|
elif key == "--ovs-version":
|
|
version = value
|
|
else:
|
|
sys.exit(0)
|
|
|
|
if not args:
|
|
sys.stderr.write(
|
|
"%s: missing command argument " "(use --help for help)\n" % argv0
|
|
)
|
|
sys.exit(1)
|
|
|
|
commands = {
|
|
"meta-flow": (make_meta_flow, 1),
|
|
"nx-match": (make_nx_match, 1),
|
|
"ovs-fields": (make_ovs_fields, 2),
|
|
}
|
|
|
|
if not args[0] in commands:
|
|
sys.stderr.write(
|
|
'%s: unknown command "%s" '
|
|
"(use --help for help)\n" % (argv0, args[0])
|
|
)
|
|
sys.exit(1)
|
|
|
|
func, n_args = commands[args[0]]
|
|
if len(args) - 1 != n_args:
|
|
sys.stderr.write(
|
|
'%s: "%s" requires %d arguments but %d '
|
|
"provided\n" % (argv0, args[0], n_args, len(args) - 1)
|
|
)
|
|
sys.exit(1)
|
|
|
|
func(*args[1:])
|