2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 18:07:40 +00:00
ovs/build-aux/extract-ofp-fields

818 lines
25 KiB
Plaintext
Raw Normal View History

#! /usr/bin/python
import getopt
import sys
import os.path
import re
import xml.dom.minidom
import build.nroff
line = ""
# Maps from user-friendly version number to its protocol encoding.
VERSION = {"1.0": 0x01,
"1.1": 0x02,
"1.2": 0x03,
"1.3": 0x04,
"1.4": 0x05,
"1.5": 0x06}
VERSION_REVERSE = dict((v,k) for k, v in VERSION.iteritems())
tunnel: Geneve TLV handling support for OpenFlow. The current support for Geneve in OVS is exactly equivalent to VXLAN: it is possible to set and match on the VNI but not on any options contained in the header. This patch enables the use of options. The goal for Geneve support is not to add support for any particular option but to allow end users or controllers to specify what they would like to match. That is, the full range of Geneve's capabilities should be exposed without modifying the code (the one exception being options that require per-packet computation in the fast path). The main issue with supporting Geneve options is how to integrate the fields into the existing OpenFlow pipeline. All existing operations are referred to by their NXM/OXM field name - matches, action generation, arithmetic operations (i.e. tranfer to a register). However, the Geneve option space is exactly the same as the OXM space, so a direct mapping is not feasible. Instead, we create a pool of 64 NXMs that are then dynamically mapped on Geneve option TLVs using OpenFlow. Once mapped, these fields become first-class citizens in the OpenFlow pipeline. An example of how to use Geneve options: ovs-ofctl add-geneve-map br0 {class=0xffff,type=0,len=4}->tun_metadata0 ovs-ofctl add-flow br0 in_port=LOCAL,actions=set_field:0xffffffff->tun_metadata0,1 This will add a 4 bytes option (filled will all 1's) to all packets coming from the LOCAL port and then send then out to port 1. A limitation of this patch is that although the option table is specified for a particular switch over OpenFlow, it is currently global to all switches. This will be addressed in a future patch. Based on work originally done by Madhu Challa. Ben Pfaff also significantly improved the comments. Signed-off-by: Madhu Challa <challa@noironetworks.com> Signed-off-by: Jesse Gross <jesse@nicira.com> Acked-by: Ben Pfaff <blp@nicira.com>
2015-04-30 18:09:57 -07:00
TYPES = {"u8": (1, False),
"be16": (2, False),
"be32": (4, False),
"MAC": (6, False),
"be64": (8, False),
"be128": (16, False),
tunnel: Geneve TLV handling support for OpenFlow. The current support for Geneve in OVS is exactly equivalent to VXLAN: it is possible to set and match on the VNI but not on any options contained in the header. This patch enables the use of options. The goal for Geneve support is not to add support for any particular option but to allow end users or controllers to specify what they would like to match. That is, the full range of Geneve's capabilities should be exposed without modifying the code (the one exception being options that require per-packet computation in the fast path). The main issue with supporting Geneve options is how to integrate the fields into the existing OpenFlow pipeline. All existing operations are referred to by their NXM/OXM field name - matches, action generation, arithmetic operations (i.e. tranfer to a register). However, the Geneve option space is exactly the same as the OXM space, so a direct mapping is not feasible. Instead, we create a pool of 64 NXMs that are then dynamically mapped on Geneve option TLVs using OpenFlow. Once mapped, these fields become first-class citizens in the OpenFlow pipeline. An example of how to use Geneve options: ovs-ofctl add-geneve-map br0 {class=0xffff,type=0,len=4}->tun_metadata0 ovs-ofctl add-flow br0 in_port=LOCAL,actions=set_field:0xffffffff->tun_metadata0,1 This will add a 4 bytes option (filled will all 1's) to all packets coming from the LOCAL port and then send then out to port 1. A limitation of this patch is that although the option table is specified for a particular switch over OpenFlow, it is currently global to all switches. This will be addressed in a future patch. Based on work originally done by Madhu Challa. Ben Pfaff also significantly improved the comments. Signed-off-by: Madhu Challa <challa@noironetworks.com> Signed-off-by: Jesse Gross <jesse@nicira.com> Acked-by: Ben Pfaff <blp@nicira.com>
2015-04-30 18:09:57 -07:00
"tunnelMD": (124, True)}
FORMATTING = {"decimal": ("MFS_DECIMAL", 1, 8),
"hexadecimal": ("MFS_HEXADECIMAL", 1, 127),
Add support for connection tracking. This patch adds a new action and fields to OVS that allow connection tracking to be performed. This support works in conjunction with the Linux kernel support merged into the Linux-4.3 development cycle. Packets have two possible states with respect to connection tracking: Untracked packets have not previously passed through the connection tracker, while tracked packets have previously been through the connection tracker. For OpenFlow pipeline processing, untracked packets can become tracked, and they will remain tracked until the end of the pipeline. Tracked packets cannot become untracked. Connections can be unknown, uncommitted, or committed. Packets which are untracked have unknown connection state. To know the connection state, the packet must become tracked. Uncommitted connections have no connection state stored about them, so it is only possible for the connection tracker to identify whether they are a new connection or whether they are invalid. Committed connections have connection state stored beyond the lifetime of the packet, which allows later packets in the same connection to be identified as part of the same established connection, or related to an existing connection - for instance ICMP error responses. The new 'ct' action transitions the packet from "untracked" to "tracked" by sending this flow through the connection tracker. The following parameters are supported initally: - "commit": When commit is executed, the connection moves from uncommitted state to committed state. This signals that information about the connection should be stored beyond the lifetime of the packet within the pipeline. This allows future packets in the same connection to be recognized as part of the same "established" (est) connection, as well as identifying packets in the reply (rpl) direction, or packets related to an existing connection (rel). - "zone=[u16|NXM]": Perform connection tracking in the zone specified. Each zone is an independent connection tracking context. When the "commit" parameter is used, the connection will only be committed in the specified zone, and not in other zones. This is 0 by default. - "table=NUMBER": Fork pipeline processing in two. The original instance of the packet will continue processing the current actions list as an untracked packet. An additional instance of the packet will be sent to the connection tracker, which will be re-injected into the OpenFlow pipeline to resume processing in the specified table, with the ct_state and other ct match fields set. If the table is not specified, then the packet is submitted to the connection tracker, but the pipeline does not fork and the ct match fields are not populated. It is strongly recommended to specify a table later than the current table to prevent loops. When the "table" option is used, the packet that continues processing in the specified table will have the ct_state populated. The ct_state may have any of the following flags set: - Tracked (trk): Connection tracking has occurred. - Reply (rpl): The flow is in the reply direction. - Invalid (inv): The connection tracker couldn't identify the connection. - New (new): This is the beginning of a new connection. - Established (est): This is part of an already existing connection. - Related (rel): This connection is related to an existing connection. For more information, consult the ovs-ofctl(8) man pages. Below is a simple example flow table to allow outbound TCP traffic from port 1 and drop traffic from port 2 that was not initiated by port 1: table=0,priority=1,action=drop table=0,arp,action=normal table=0,in_port=1,tcp,ct_state=-trk,action=ct(commit,zone=9),2 table=0,in_port=2,tcp,ct_state=-trk,action=ct(zone=9,table=1) table=1,in_port=2,ct_state=+trk+est,tcp,action=1 table=1,in_port=2,ct_state=+trk+new,tcp,action=drop Based on original design by Justin Pettit, contributions from Thomas Graf and Daniele Di Proietto. Signed-off-by: Joe Stringer <joestringer@nicira.com> Acked-by: Jarno Rajahalme <jrajahalme@nicira.com> Acked-by: Ben Pfaff <blp@nicira.com>
2015-08-11 10:56:09 -07:00
"ct state": ("MFS_CT_STATE", 4, 4),
"Ethernet": ("MFS_ETHERNET", 6, 6),
"IPv4": ("MFS_IPV4", 4, 4),
"IPv6": ("MFS_IPV6", 16, 16),
"OpenFlow 1.0 port": ("MFS_OFP_PORT", 2, 2),
"OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4, 4),
"frag": ("MFS_FRAG", 1, 1),
"tunnel flags": ("MFS_TNL_FLAGS", 2, 2),
"TCP flags": ("MFS_TCP_FLAGS", 2, 2)}
PREREQS = {"none": "MFP_NONE",
"ARP": "MFP_ARP",
"VLAN VID": "MFP_VLAN_VID",
"IPv4": "MFP_IPV4",
"IPv6": "MFP_IPV6",
"IPv4/IPv6": "MFP_IP_ANY",
"CT": "MFP_CT_VALID",
"CTv4": "MFP_CTV4_VALID",
"CTv6": "MFP_CTV6_VALID",
"MPLS": "MFP_MPLS",
"TCP": "MFP_TCP",
"UDP": "MFP_UDP",
"SCTP": "MFP_SCTP",
"ICMPv4": "MFP_ICMPV4",
"ICMPv6": "MFP_ICMPV6",
"ND": "MFP_ND",
"ND solicit": "MFP_ND_SOLICIT",
"ND advert": "MFP_ND_ADVERT"}
nx-match: Add support for experimenter OXM. OpenFlow 1.2+ defines a means for vendors to define vendor-specific OXM fields, called "experimenter OXM". These OXM fields are expressed with a 64-bit OXM header instead of the 32-bit header used for standard OXM (and NXM). Until now, OVS has not implemented experimenter OXM, and indeed we have had little need to do so because of a pair of special 32-bit OXM classes grandfathered to OVS as part of the OpenFlow 1.2 standardization process. However, I want to prototype a feature for OpenFlow 1.5 that uses an experimenter OXM as part of the prototype, so to do this OVS needs to support experimenter OXM. This commit adds that support. Most of this commit is a fairly straightforward change: it extends the type used for OXM/NXM from 32 to 64 bits and adds code to encode and decode the longer headers when necessary. Some other changes are necessary because experimenter OXMs have a funny idea of the division between "header" and "body": the extra 32 bits for experimenter OXMs are counted as part of the body rather than the header according to the OpenFlow standard (even though this does not entirely make sense), so arithmetic in various places has to be adjusted, which is the reason for the new functions nxm_experimenter_len(), nxm_payload_len(), and nxm_header_len(). Another change that calls for explanation is the new function mf_nxm_header() that has been split from mf_oxm_header(). This function is used in actions where the space for an NXM or OXM header is fixed so that there is no room for a 64-bit experimenter type. An upcoming commit will add new variations of these actions that can support experimenter OXM. Testing experimenter OXM is tricky because I do not know of any in widespread use. Two ONF proposals use experimenter OXMs: EXT-256 and EXT-233. EXT-256 is not suitable to implement for testing because its use of experimenter OXM is wrong and will be changed. EXT-233 is not suitable to implement for testing because it requires adding a new field to struct flow and I am not yet convinced that that field and the feature that it supports is worth having in Open vSwitch. Thus, this commit assigns an experimenter OXM code point to an existing OVS field that is currently restricted from use by controllers, "dp_hash", and uses that for testing. Because controllers cannot use it, this leaves future versions of OVS free to drop the support for the experimenter OXM for this field without causing backward compatibility problems. Signed-off-by: Ben Pfaff <blp@nicira.com> Acked-by: Jarno Rajahalme <jrajahalme@nicira.com>
2014-10-08 15:41:00 -07:00
# Maps a name prefix into an (experimenter ID, class) pair, so:
#
# - Standard OXM classes are written as (0, <oxm_class>)
#
# - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
#
# If a name matches more than one prefix, the longest one is used.
nx-match: Add support for experimenter OXM. OpenFlow 1.2+ defines a means for vendors to define vendor-specific OXM fields, called "experimenter OXM". These OXM fields are expressed with a 64-bit OXM header instead of the 32-bit header used for standard OXM (and NXM). Until now, OVS has not implemented experimenter OXM, and indeed we have had little need to do so because of a pair of special 32-bit OXM classes grandfathered to OVS as part of the OpenFlow 1.2 standardization process. However, I want to prototype a feature for OpenFlow 1.5 that uses an experimenter OXM as part of the prototype, so to do this OVS needs to support experimenter OXM. This commit adds that support. Most of this commit is a fairly straightforward change: it extends the type used for OXM/NXM from 32 to 64 bits and adds code to encode and decode the longer headers when necessary. Some other changes are necessary because experimenter OXMs have a funny idea of the division between "header" and "body": the extra 32 bits for experimenter OXMs are counted as part of the body rather than the header according to the OpenFlow standard (even though this does not entirely make sense), so arithmetic in various places has to be adjusted, which is the reason for the new functions nxm_experimenter_len(), nxm_payload_len(), and nxm_header_len(). Another change that calls for explanation is the new function mf_nxm_header() that has been split from mf_oxm_header(). This function is used in actions where the space for an NXM or OXM header is fixed so that there is no room for a 64-bit experimenter type. An upcoming commit will add new variations of these actions that can support experimenter OXM. Testing experimenter OXM is tricky because I do not know of any in widespread use. Two ONF proposals use experimenter OXMs: EXT-256 and EXT-233. EXT-256 is not suitable to implement for testing because its use of experimenter OXM is wrong and will be changed. EXT-233 is not suitable to implement for testing because it requires adding a new field to struct flow and I am not yet convinced that that field and the feature that it supports is worth having in Open vSwitch. Thus, this commit assigns an experimenter OXM code point to an existing OVS field that is currently restricted from use by controllers, "dp_hash", and uses that for testing. Because controllers cannot use it, this leaves future versions of OVS free to drop the support for the experimenter OXM for this field without causing backward compatibility problems. Signed-off-by: Ben Pfaff <blp@nicira.com> Acked-by: Jarno Rajahalme <jrajahalme@nicira.com>
2014-10-08 15:41:00 -07:00
OXM_CLASSES = {"NXM_OF_": (0, 0x0000),
"NXM_NX_": (0, 0x0001),
"OXM_OF_": (0, 0x8000),
"OXM_OF_PKT_REG": (0, 0x8001),
"ONFOXM_ET_": (0x4f4e4600, 0xffff),
nx-match: Add support for experimenter OXM. OpenFlow 1.2+ defines a means for vendors to define vendor-specific OXM fields, called "experimenter OXM". These OXM fields are expressed with a 64-bit OXM header instead of the 32-bit header used for standard OXM (and NXM). Until now, OVS has not implemented experimenter OXM, and indeed we have had little need to do so because of a pair of special 32-bit OXM classes grandfathered to OVS as part of the OpenFlow 1.2 standardization process. However, I want to prototype a feature for OpenFlow 1.5 that uses an experimenter OXM as part of the prototype, so to do this OVS needs to support experimenter OXM. This commit adds that support. Most of this commit is a fairly straightforward change: it extends the type used for OXM/NXM from 32 to 64 bits and adds code to encode and decode the longer headers when necessary. Some other changes are necessary because experimenter OXMs have a funny idea of the division between "header" and "body": the extra 32 bits for experimenter OXMs are counted as part of the body rather than the header according to the OpenFlow standard (even though this does not entirely make sense), so arithmetic in various places has to be adjusted, which is the reason for the new functions nxm_experimenter_len(), nxm_payload_len(), and nxm_header_len(). Another change that calls for explanation is the new function mf_nxm_header() that has been split from mf_oxm_header(). This function is used in actions where the space for an NXM or OXM header is fixed so that there is no room for a 64-bit experimenter type. An upcoming commit will add new variations of these actions that can support experimenter OXM. Testing experimenter OXM is tricky because I do not know of any in widespread use. Two ONF proposals use experimenter OXMs: EXT-256 and EXT-233. EXT-256 is not suitable to implement for testing because its use of experimenter OXM is wrong and will be changed. EXT-233 is not suitable to implement for testing because it requires adding a new field to struct flow and I am not yet convinced that that field and the feature that it supports is worth having in Open vSwitch. Thus, this commit assigns an experimenter OXM code point to an existing OVS field that is currently restricted from use by controllers, "dp_hash", and uses that for testing. Because controllers cannot use it, this leaves future versions of OVS free to drop the support for the experimenter OXM for this field without causing backward compatibility problems. Signed-off-by: Ben Pfaff <blp@nicira.com> Acked-by: Jarno Rajahalme <jrajahalme@nicira.com>
2014-10-08 15:41:00 -07:00
# This is the experimenter OXM class for Nicira, which is the
# one that OVS would be using instead of NXM_OF_ and NXM_NX_
# if OVS didn't have those grandfathered in. It is currently
# used only to test support for experimenter OXM, since there
# are barely any real uses of experimenter OXM in the wild.
"NXOXM_ET_": (0x00002320, 0xffff)}
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 decode_version_range(range):
if range in VERSION:
return (VERSION[range], VERSION[range])
elif range.endswith('+'):
return (VERSION[range[:-1]], max(VERSION.values()))
else:
a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
return (VERSION[a], VERSION[b])
def get_line():
global line
global line_number
line = input_file.readline()
line_number += 1
if line == "":
fatal("unexpected end of input")
n_errors = 0
def error(msg):
global n_errors
sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
n_errors += 1
def fatal(msg):
error(msg)
sys.exit(1)
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 make_sizeof(s):
m = re.match(r'(.*) up to (.*)', s)
if m:
struct, member = m.groups()
return "offsetof(%s, %s)" % (struct, member)
else:
return "sizeof(%s)" % s
def parse_oxms(s, prefix, n_bytes):
if s == 'none':
return ()
return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in s.split(','))
match_types = dict()
def parse_oxm(s, prefix, n_bytes):
global match_types
m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s)
if not m:
fatal("%s: syntax error parsing %s" % (s, prefix))
nx-match: Add support for experimenter OXM. OpenFlow 1.2+ defines a means for vendors to define vendor-specific OXM fields, called "experimenter OXM". These OXM fields are expressed with a 64-bit OXM header instead of the 32-bit header used for standard OXM (and NXM). Until now, OVS has not implemented experimenter OXM, and indeed we have had little need to do so because of a pair of special 32-bit OXM classes grandfathered to OVS as part of the OpenFlow 1.2 standardization process. However, I want to prototype a feature for OpenFlow 1.5 that uses an experimenter OXM as part of the prototype, so to do this OVS needs to support experimenter OXM. This commit adds that support. Most of this commit is a fairly straightforward change: it extends the type used for OXM/NXM from 32 to 64 bits and adds code to encode and decode the longer headers when necessary. Some other changes are necessary because experimenter OXMs have a funny idea of the division between "header" and "body": the extra 32 bits for experimenter OXMs are counted as part of the body rather than the header according to the OpenFlow standard (even though this does not entirely make sense), so arithmetic in various places has to be adjusted, which is the reason for the new functions nxm_experimenter_len(), nxm_payload_len(), and nxm_header_len(). Another change that calls for explanation is the new function mf_nxm_header() that has been split from mf_oxm_header(). This function is used in actions where the space for an NXM or OXM header is fixed so that there is no room for a 64-bit experimenter type. An upcoming commit will add new variations of these actions that can support experimenter OXM. Testing experimenter OXM is tricky because I do not know of any in widespread use. Two ONF proposals use experimenter OXMs: EXT-256 and EXT-233. EXT-256 is not suitable to implement for testing because its use of experimenter OXM is wrong and will be changed. EXT-233 is not suitable to implement for testing because it requires adding a new field to struct flow and I am not yet convinced that that field and the feature that it supports is worth having in Open vSwitch. Thus, this commit assigns an experimenter OXM code point to an existing OVS field that is currently restricted from use by controllers, "dp_hash", and uses that for testing. Because controllers cannot use it, this leaves future versions of OVS free to drop the support for the experimenter OXM for this field without causing backward compatibility problems. Signed-off-by: Ben Pfaff <blp@nicira.com> Acked-by: Jarno Rajahalme <jrajahalme@nicira.com>
2014-10-08 15:41:00 -07:00
name, oxm_type, of_version, ovs_version = m.groups()
class_ = oxm_name_to_class(name)
if class_ is None:
fatal("unknown OXM class for %s" % name)
nx-match: Add support for experimenter OXM. OpenFlow 1.2+ defines a means for vendors to define vendor-specific OXM fields, called "experimenter OXM". These OXM fields are expressed with a 64-bit OXM header instead of the 32-bit header used for standard OXM (and NXM). Until now, OVS has not implemented experimenter OXM, and indeed we have had little need to do so because of a pair of special 32-bit OXM classes grandfathered to OVS as part of the OpenFlow 1.2 standardization process. However, I want to prototype a feature for OpenFlow 1.5 that uses an experimenter OXM as part of the prototype, so to do this OVS needs to support experimenter OXM. This commit adds that support. Most of this commit is a fairly straightforward change: it extends the type used for OXM/NXM from 32 to 64 bits and adds code to encode and decode the longer headers when necessary. Some other changes are necessary because experimenter OXMs have a funny idea of the division between "header" and "body": the extra 32 bits for experimenter OXMs are counted as part of the body rather than the header according to the OpenFlow standard (even though this does not entirely make sense), so arithmetic in various places has to be adjusted, which is the reason for the new functions nxm_experimenter_len(), nxm_payload_len(), and nxm_header_len(). Another change that calls for explanation is the new function mf_nxm_header() that has been split from mf_oxm_header(). This function is used in actions where the space for an NXM or OXM header is fixed so that there is no room for a 64-bit experimenter type. An upcoming commit will add new variations of these actions that can support experimenter OXM. Testing experimenter OXM is tricky because I do not know of any in widespread use. Two ONF proposals use experimenter OXMs: EXT-256 and EXT-233. EXT-256 is not suitable to implement for testing because its use of experimenter OXM is wrong and will be changed. EXT-233 is not suitable to implement for testing because it requires adding a new field to struct flow and I am not yet convinced that that field and the feature that it supports is worth having in Open vSwitch. Thus, this commit assigns an experimenter OXM code point to an existing OVS field that is currently restricted from use by controllers, "dp_hash", and uses that for testing. Because controllers cannot use it, this leaves future versions of OVS free to drop the support for the experimenter OXM for this field without causing backward compatibility problems. Signed-off-by: Ben Pfaff <blp@nicira.com> Acked-by: Jarno Rajahalme <jrajahalme@nicira.com>
2014-10-08 15:41:00 -07:00
oxm_vendor, oxm_class = class_
if class_ in match_types:
if oxm_type in match_types[class_]:
fatal("duplicate match type for %s (conflicts with %s)" %
(name, match_types[class_][oxm_type]))
else:
match_types[class_] = dict()
match_types[class_][oxm_type] = name
nx-match: Add support for experimenter OXM. OpenFlow 1.2+ defines a means for vendors to define vendor-specific OXM fields, called "experimenter OXM". These OXM fields are expressed with a 64-bit OXM header instead of the 32-bit header used for standard OXM (and NXM). Until now, OVS has not implemented experimenter OXM, and indeed we have had little need to do so because of a pair of special 32-bit OXM classes grandfathered to OVS as part of the OpenFlow 1.2 standardization process. However, I want to prototype a feature for OpenFlow 1.5 that uses an experimenter OXM as part of the prototype, so to do this OVS needs to support experimenter OXM. This commit adds that support. Most of this commit is a fairly straightforward change: it extends the type used for OXM/NXM from 32 to 64 bits and adds code to encode and decode the longer headers when necessary. Some other changes are necessary because experimenter OXMs have a funny idea of the division between "header" and "body": the extra 32 bits for experimenter OXMs are counted as part of the body rather than the header according to the OpenFlow standard (even though this does not entirely make sense), so arithmetic in various places has to be adjusted, which is the reason for the new functions nxm_experimenter_len(), nxm_payload_len(), and nxm_header_len(). Another change that calls for explanation is the new function mf_nxm_header() that has been split from mf_oxm_header(). This function is used in actions where the space for an NXM or OXM header is fixed so that there is no room for a 64-bit experimenter type. An upcoming commit will add new variations of these actions that can support experimenter OXM. Testing experimenter OXM is tricky because I do not know of any in widespread use. Two ONF proposals use experimenter OXMs: EXT-256 and EXT-233. EXT-256 is not suitable to implement for testing because its use of experimenter OXM is wrong and will be changed. EXT-233 is not suitable to implement for testing because it requires adding a new field to struct flow and I am not yet convinced that that field and the feature that it supports is worth having in Open vSwitch. Thus, this commit assigns an experimenter OXM code point to an existing OVS field that is currently restricted from use by controllers, "dp_hash", and uses that for testing. Because controllers cannot use it, this leaves future versions of OVS free to drop the support for the experimenter OXM for this field without causing backward compatibility problems. Signed-off-by: Ben Pfaff <blp@nicira.com> Acked-by: Jarno Rajahalme <jrajahalme@nicira.com>
2014-10-08 15:41:00 -07:00
# Normally the oxm_length is the size of the field, but for experimenter
# OXMs oxm_length also includes the 4-byte experimenter ID.
oxm_length = n_bytes
if oxm_class == 0xffff:
oxm_length += 4
header = (oxm_vendor, oxm_class, int(oxm_type), oxm_length)
if of_version:
if of_version not in VERSION:
fatal("%s: unknown OpenFlow version %s" % (name, of_version))
of_version_nr = VERSION[of_version]
if of_version_nr < VERSION['1.2']:
fatal("%s: claimed version %s predates OXM" % (name, of_version))
elif prefix == 'OXM':
fatal("%s: missing OpenFlow version number" % name)
else:
of_version_nr = 0
return (header, name, of_version_nr, ovs_version)
def parse_field(mff, comment):
f = {'mff': mff}
# First line of comment is the field name.
m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0])
if not m:
fatal("%s lacks field name" % mff)
f['name'], f['extra_name'] = m.groups()
# Find the last blank line the comment. The field definitions
# start after that.
blank = None
for i in range(len(comment)):
if not comment[i]:
blank = i
if not blank:
fatal("%s: missing blank line in comment" % mff)
d = {}
for key in ("Type", "Maskable", "Formatting", "Prerequisites",
"Access", "Prefix lookup member",
"OXM", "NXM", "OF1.0", "OF1.1"):
d[key] = None
for fline in comment[blank + 1:]:
m = re.match(r'([^:]+):\s+(.*)\.$', fline)
if not m:
fatal("%s: syntax error parsing key-value pair as part of %s"
% (fline, mff))
key, value = m.groups()
if key not in d:
fatal("%s: unknown key" % key)
elif key == 'Code point':
d[key] += [value]
elif d[key] is not None:
fatal("%s: duplicate key" % key)
d[key] = value
for key, value in d.items():
if not value and key not in ("OF1.0", "OF1.1",
"Prefix lookup member", "Notes"):
fatal("%s: missing %s" % (mff, key))
m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
if not m:
fatal("%s: syntax error in type" % mff)
type_ = m.group(1)
if type_ not in TYPES:
fatal("%s: unknown type %s" % (mff, d['Type']))
f['n_bytes'] = TYPES[type_][0]
if m.group(2):
f['n_bits'] = int(m.group(2))
if f['n_bits'] > f['n_bytes'] * 8:
fatal("%s: more bits (%d) than field size (%d)"
% (mff, f['n_bits'], 8 * f['n_bytes']))
else:
f['n_bits'] = 8 * f['n_bytes']
f['variable'] = TYPES[type_][1]
if d['Maskable'] == 'no':
f['mask'] = 'MFM_NONE'
elif d['Maskable'] == 'bitwise':
f['mask'] = 'MFM_FULLY'
else:
fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
fmt = FORMATTING.get(d['Formatting'])
if not fmt:
fatal("%s: unknown format %s" % (mff, d['Formatting']))
f['formatting'] = d['Formatting']
if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
fatal("%s: %d-byte field can't be formatted as %s"
% (mff, f['n_bytes'], d['Formatting']))
f['string'] = fmt[0]
f['prereqs'] = d['Prerequisites']
if f['prereqs'] not in PREREQS:
fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
if d['Access'] == 'read-only':
f['writable'] = False
elif d['Access'] == 'read/write':
f['writable'] = True
else:
fatal("%s: unknown access %s" % (mff, d['Access']))
f['OF1.0'] = d['OF1.0']
if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
f['OF1.1'] = d['OF1.1']
if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) +
parse_oxms(d['NXM'], 'NXM', f['n_bytes']))
f['prefix'] = d["Prefix lookup member"]
return f
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'
ofp-actions: Fix variable length meta-flow OXMs. Previously, if a flow action that involves a tunnel metadata meta-flow field is dumped from vswitchd, the replied field length in the OXM header is filled with the maximum possible field length, instead of the length configured in the tunnel TLV mapping table. To solve this issue, this patch introduces the following changes. In order to maintain the correct length of variable length mf_fields (i.e. tun_metadata), this patch creates a per-switch based map (struct vl_mff_map) that hosts the variable length mf_fields. This map is updated when a controller adds/deletes tlv-mapping entries to/from a switch. Although the per-swtch based vl_mff_map only hosts tun_metadata for now, it is able to support new variable length mf_fields in the future. With this commit, when a switch decodes a flow action with mf_field, the switch firstly looks up the global mf_fields map to identify the mf_field type. For the variable length mf_fields, the switch uses the vl_mff_map to get the configured mf_field entries. By lookig up vl_mff_map, the switch can check if the added flow action access beyond the configured size of a variable length mf_field, and the switch reports an ofperr if the controller adds a flow with unmapped variable length mf_field. Later on, when a controller request flows from the switch, with the per-switch based mf_fields, the switch will encode the OXM header with correct length for variable length mf_fields. To use the vl_mff_map for decoding flow actions, extract-ofp-actions is updated to pass the vl_mff_map to the required action decoding functions. Also, a new error code is introduced to identify a flow with an invalid variable length mf_field. Moreover, a testcase is added to prevent future regressions. Committer notes: - Factor out common code - Style fixups - Rename OFPERR_NXFMFC_INVALID_VL_MFF -> OFPERR_NXFMFC_INVALID_TLV_FIELD VMWare-BZ: #1768370 Reported-by: Harold Lim <haroldl@vmware.com> Suggested-by: Joe Stringer <joe@ovn.org> Suggested-by: Jarno Rajahalme <jarno@ovn.org> Signed-off-by: Yi-Hung Wei <yihung.wei@gmail.com> Signed-off-by: Joe Stringer <joe@ovn.org>
2017-01-20 15:12:21 -08:00
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)
def extract_ofp_fields(fn):
global file_name
global input_file
global line_number
global line
file_name = fn
input_file = open(file_name)
line_number = 0
fields = []
while True:
get_line()
if re.match('enum.*mf_field_id', line):
break
while True:
get_line()
first_line_number = line_number
here = '%s:%d' % (file_name, line_number)
if (line.startswith('/*')
or line.startswith(' *')
or line.startswith('#')
or not line
or line.isspace()):
continue
elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
break
# Parse the comment preceding an MFF_ constant into 'comment',
# one line to an array element.
line = line.strip()
if not line.startswith('/*'):
fatal("unexpected syntax between fields")
line = line[1:]
comment = []
end = False
while not end:
line = line.strip()
if line.startswith('*/'):
get_line()
break
if not line.startswith('*'):
fatal("unexpected syntax within field")
line = line[1:]
if line.startswith(' '):
line = line[1:]
if line.startswith(' ') and comment:
continuation = True
line = line.lstrip()
else:
continuation = False
if line.endswith('*/'):
line = line[:-2].rstrip()
end = True
else:
end = False
if continuation:
comment[-1] += " " + line
else:
comment += [line]
get_line()
# Drop blank lines at each end of comment.
while comment and not comment[0]:
comment = comment[1:]
while comment and not comment[-1]:
comment = comment[:-1]
# Parse the MFF_ constant(s).
mffs = []
while True:
m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
if not m:
break
mffs += [m.group(1)]
get_line()
if not mffs:
fatal("unexpected syntax looking for MFF_ constants")
if len(mffs) > 1 or '<N>' in comment[0]:
for mff in mffs:
# Extract trailing integer.
m = re.match('.*[^0-9]([0-9]+)$', mff)
if not m:
fatal("%s lacks numeric suffix in register group" % mff)
n = m.group(1)
# Search-and-replace <N> within the comment,
# and drop lines that have <x> for x != n.
instance = []
for x in comment:
y = x.replace('<N>', n)
if re.search('<[0-9]+>', y):
if ('<%s>' % n) not in y:
continue
y = re.sub('<[0-9]+>', '', y)
instance += [y.strip()]
fields += [parse_field(mff, instance)]
else:
fields += [parse_field(mffs[0], comment)]
continue
input_file.close()
if n_errors:
sys.exit(1)
return fields
## ------------------------ ##
## 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 (not name.startswith('NXM')
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 not x[1].startswith('NXM')]:
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 x[1].startswith('NXM')]:
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_ = 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" % f["mff"])
if n_errors:
sys.exit(1)
output = []
for oline in s.split("\n"):
oline = oline.strip()
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, 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:])