9 # Maps from user-friendly version number to its protocol encoding.
10 VERSION = {"1.0": 0x01,
24 FORMATTING = {"decimal": ("MFS_DECIMAL", 1, 8),
25 "hexadecimal": ("MFS_HEXADECIMAL", 1, 8),
26 "Ethernet": ("MFS_ETHERNET", 6, 6),
27 "IPv4": ("MFS_IPV4", 4, 4),
28 "IPv6": ("MFS_IPV6", 16,16),
29 "OpenFlow 1.0 port": ("MFS_OFP_PORT", 2, 2),
30 "OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4, 4),
31 "frag": ("MFS_FRAG", 1, 1),
32 "tunnel flags": ("MFS_TNL_FLAGS", 2, 2),
33 "TCP flags": ("MFS_TCP_FLAGS", 2, 2)}
35 PREREQS = {"none": "MFP_NONE",
37 "VLAN VID": "MFP_VLAN_VID",
40 "IPv4/IPv6": "MFP_IP_ANY",
45 "ICMPv4": "MFP_ICMPV4",
46 "ICMPv6": "MFP_ICMPV6",
48 "ND solicit": "MFP_ND_SOLICIT",
49 "ND advert": "MFP_ND_ADVERT"}
51 # Maps a name prefix to an oxm_class.
52 # If a name matches more than one prefix, the longest one is used.
53 OXM_CLASSES = {"NXM_OF_": 0x0000,
56 "OXM_OF_PKT_REG": 0x8001}
57 def oxm_name_to_class(name):
60 for p, c in OXM_CLASSES.iteritems():
61 if name.startswith(p) and len(p) > len(prefix):
66 def decode_version_range(range):
68 return (VERSION[range], VERSION[range])
69 elif range.endswith('+'):
70 return (VERSION[range[:-1]], max(VERSION.values()))
72 a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
73 return (VERSION[a], VERSION[b])
78 line = input_file.readline()
81 fatal("unexpected end of input")
86 sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
94 argv0 = os.path.basename(sys.argv[0])
96 %(argv0)s, for extracting OpenFlow field properties from meta-flow.h
97 usage: %(argv0)s INPUT
98 where INPUT points to lib/meta-flow.h in the source directory.
99 The output written to stdout is intended to be saved as lib/meta-flow.inc,
100 which lib/meta-flow.c \"#include\"s.\
101 ''' % {"argv0": argv0}
105 m = re.match(r'(.*) up to (.*)', s)
107 struct, member = m.groups()
108 return "offsetof(%s, %s)" % (struct, member)
110 return "sizeof(%s)" % s
112 def parse_oxm(s, prefix, n_bytes):
116 m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s)
118 fatal("%s: syntax error parsing %s" % (s, prefix))
120 name, code, of_version, ovs_version = m.groups()
122 class_ = oxm_name_to_class(name)
124 fatal("unknown OXM class for %s" % name)
125 header = ("NXM_HEADER(0x%04x, %s, %d)" % (class_, code, n_bytes))
128 if of_version not in VERSION:
129 fatal("%s: unknown OpenFlow version %s" % (name, of_version))
130 of_version_nr = VERSION[of_version]
131 if of_version_nr < VERSION['1.2']:
132 fatal("%s: claimed version %s predates OXM" % (name, of_version))
136 return (header, name, of_version_nr, ovs_version)
138 def parse_field(mff, comment):
141 # First line of comment is the field name.
142 m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0])
144 fatal("%s lacks field name" % mff)
145 f['name'], f['extra_name'] = m.groups()
147 # Find the last blank line the comment. The field definitions
150 for i in range(len(comment)):
154 fatal("%s: missing blank line in comment" % mff)
157 for key in ("Type", "Maskable", "Formatting", "Prerequisites",
158 "Access", "Prefix lookup member",
159 "OXM", "NXM", "OF1.0", "OF1.1"):
161 for fline in comment[blank + 1:]:
162 m = re.match(r'([^:]+):\s+(.*)\.$', fline)
164 fatal("%s: syntax error parsing key-value pair as part of %s"
166 key, value = m.groups()
168 fatal("%s: unknown key" % key)
169 elif key == 'Code point':
171 elif d[key] is not None:
172 fatal("%s: duplicate key" % key)
174 for key, value in d.iteritems():
175 if not value and key not in ("OF1.0", "OF1.1",
176 "Prefix lookup member", "Notes"):
177 fatal("%s: missing %s" % (mff, key))
179 m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
181 fatal("%s: syntax error in type" % mff)
183 if type_ not in TYPES:
184 fatal("%s: unknown type %s" % (mff, d['Type']))
185 f['n_bytes'] = TYPES[type_]
187 f['n_bits'] = int(m.group(2))
188 if f['n_bits'] > f['n_bytes'] * 8:
189 fatal("%s: more bits (%d) than field size (%d)"
190 % (mff, f['n_bits'], 8 * f['n_bytes']))
192 f['n_bits'] = 8 * f['n_bytes']
194 if d['Maskable'] == 'no':
195 f['mask'] = 'MFM_NONE'
196 elif d['Maskable'] == 'bitwise':
197 f['mask'] = 'MFM_FULLY'
199 fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
201 fmt = FORMATTING.get(d['Formatting'])
203 fatal("%s: unknown format %s" % (mff, d['Formatting']))
204 if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
205 fatal("%s: %d-byte field can't be formatted as %s"
206 % (mff, f['n_bytes'], d['Formatting']))
209 f['prereqs'] = PREREQS.get(d['Prerequisites'])
211 fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
213 if d['Access'] == 'read-only':
214 f['writable'] = False
215 elif d['Access'] == 'read/write':
218 fatal("%s: unknown access %s" % (mff, d['Access']))
220 f['OF1.0'] = d['OF1.0']
221 if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
222 fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
224 f['OF1.1'] = d['OF1.1']
225 if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
226 fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
228 f['OXM'] = parse_oxm(d['OXM'], 'OXM', f['n_bytes'])
229 f['NXM'] = parse_oxm(d['NXM'], 'NXM', f['n_bytes'])
231 f['prefix'] = d["Prefix lookup member"]
235 def protocols_to_c(protocols):
236 if protocols == set(['of10', 'of11', 'oxm']):
237 return 'OFPUTIL_P_ANY'
238 elif protocols == set(['of11', 'oxm']):
239 return 'OFPUTIL_P_NXM_OF11_UP'
240 elif protocols == set(['oxm']):
241 return 'OFPUTIL_P_NXM_OXM_ANY'
242 elif protocols == set([]):
243 return 'OFPUTIL_P_NONE'
247 def extract_ofp_fields():
254 if re.match('enum.*mf_field_id', line):
259 first_line_number = line_number
260 here = '%s:%d' % (file_name, line_number)
261 if (line.startswith('/*')
262 or line.startswith(' *')
263 or line.startswith('#')
267 elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
270 # Parse the comment preceding an MFF_ constant into 'comment',
271 # one line to an array element.
273 if not line.startswith('/*'):
274 fatal("unexpected syntax between fields")
280 if line.startswith('*/'):
283 if not line.startswith('*'):
284 fatal("unexpected syntax within field")
287 if line.startswith(' '):
289 if line.startswith(' ') and comment:
295 if line.endswith('*/'):
296 line = line[:-2].rstrip()
302 comment[-1] += " " + line
307 # Drop blank lines at each end of comment.
308 while comment and not comment[0]:
309 comment = comment[1:]
310 while comment and not comment[-1]:
311 comment = comment[:-1]
313 # Parse the MFF_ constant(s).
316 m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
322 fatal("unexpected syntax looking for MFF_ constants")
324 if len(mffs) > 1 or '<N>' in comment[0]:
326 # Extract trailing integer.
327 m = re.match('.*[^0-9]([0-9]+)$', mff)
329 fatal("%s lacks numeric suffix in register group" % mff)
332 # Search-and-replace <N> within the comment,
333 # and drop lines that have <x> for x != n.
336 y = x.replace('<N>', n)
337 if re.search('<[0-9]+>', y):
338 if ('<%s>' % n) not in y:
340 y = re.sub('<[0-9]+>', '', y)
341 instance += [y.strip()]
342 fields += [parse_field(mff, instance)]
344 fields += [parse_field(mffs[0], comment)]
353 output += ["/* Generated automatically; do not modify! "
354 "-*- buffer-read-only: t -*- */"]
359 output += [" %s," % f['mff']]
361 output += [" \"%s\", \"%s\"," % (f['name'], f['extra_name'])]
363 output += [" \"%s\", NULL," % f['name']]
364 output += [" %d, %d," % (f['n_bytes'], f['n_bits'])]
370 output += [" %s, %s, %s, %s,"
371 % (f['mask'], f['string'], f['prereqs'], rw)]
380 output += [" %s, \"%s\"," % (nxm[0], nxm[1])]
381 output += [" %s, \"%s\", %s," % (oxm[0], oxm[1], oxm[2])]
383 output += [" 0, NULL, 0, NULL, 0, /* no NXM or OXM */"]
387 if f['mff'] in ('MFF_DL_VLAN', 'MFF_DL_VLAN_PCP'):
388 # MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to
389 # OF1.1, nor do they have NXM or OXM assignments, but their
390 # meanings can be expressed in every protocol, which is the goal of
392 protocols = set(["of10", "of11", "oxm"])
396 protocols |= set(["of10"])
398 protocols |= set(["of11"])
400 protocols |= set(["oxm"])
402 if f['mask'] == 'MFM_FULLY':
403 cidr_protocols = protocols.copy()
404 bitwise_protocols = protocols.copy()
406 if of10 == 'exact match':
407 bitwise_protocols -= set(['of10'])
408 cidr_protocols -= set(['of10'])
409 elif of10 == 'CIDR mask':
410 bitwise_protocols -= set(['of10'])
414 if of11 == 'exact match':
415 bitwise_protocols -= set(['of11'])
416 cidr_protocols -= set(['of11'])
418 assert of11 in (None, 'bitwise mask')
420 assert f['mask'] == 'MFM_NONE'
421 cidr_protocols = set([])
422 bitwise_protocols = set([])
424 output += [" %s," % protocols_to_c(protocols)]
425 output += [" %s," % protocols_to_c(cidr_protocols)]
426 output += [" %s," % protocols_to_c(bitwise_protocols)]
429 output += [" FLOW_U32OFS(%s)," % f['prefix']]
431 output += [" -1, /* not usable for prefix lookup */"]
441 if __name__ == '__main__':
442 if '--help' in sys.argv:
444 elif len(sys.argv) != 2:
445 sys.stderr.write("exactly one non-option argument required; "
446 "use --help for help\n")
452 file_name = sys.argv[1]
453 input_file = open(file_name)
456 for oline in extract_ofp_fields():