#! /usr/bin/python3 import getopt import sys import os.path import xml.dom.minidom import build.nroff from build.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 += ["\\fB%s\\fR" % f["name"]] if f["extra_name"]: summary += [" 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 += [ """.PP \\fB%s Field\\fR .TS tab(;); l lx. """ % title ] body += ["Name:;\\fB%s\\fR" % f["name"]] if f["extra_name"]: body += [" (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 += [build.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 += [build.nroff.block_xml_to_nroff([node])] content = [ ".bp\n", '.SH "%s"\n' % build.nroff.text_to_nroff(title.upper() + " FIELDS"), '.SS "Summary:"\n', ".TS\n", "tab(;);\n", "l l l l l l l.\n", "Name;Bytes;Mask;RW?;Prereqs;NXM/OXM Support\n", "\_;\_;\_;\_;\_;\_\n", ] content += summary content += [".TE\n"] content += body return "".join(content) def make_oxm_classes_xml(document): s = """tab(;); 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 == None: version = "UNKNOWN" print( """\ '\\" 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 += build.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:])