]>
Commit | Line | Data |
---|---|---|
c5562271 BP |
1 | /* |
2 | * Copyright (c) 2014, 2015, 2016 Nicira, Inc. | |
3 | * | |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | ||
c5562271 | 19 | #include "byte-order.h" |
64c96779 | 20 | #include "openvswitch/ofpbuf.h" |
e03c096d | 21 | #include "openvswitch/ofp-errors.h" |
66bd43fa | 22 | #include "openvswitch/ofp-prop.h" |
34a543e3 | 23 | #include "openvswitch/vlog.h" |
c5562271 | 24 | #include "util.h" |
f01c94c5 | 25 | #include "uuid.h" |
c5562271 | 26 | |
b611d3ac BP |
27 | struct ofp_prop_be16 { |
28 | ovs_be16 type; | |
29 | ovs_be16 len; | |
30 | ovs_be16 value; | |
31 | uint8_t pad[2]; | |
32 | }; | |
33 | BUILD_ASSERT_DECL(sizeof(struct ofp_prop_be16) == 8); | |
34 | ||
35 | struct ofp_prop_be32 { | |
36 | ovs_be16 type; | |
37 | ovs_be16 len; | |
38 | ovs_be32 value; | |
39 | }; | |
40 | BUILD_ASSERT_DECL(sizeof(struct ofp_prop_be32) == 8); | |
41 | ||
34a543e3 BP |
42 | static uint32_t |
43 | ofpprop_type_to_exp_id(uint64_t type) | |
44 | { | |
45 | return type >> 32; | |
46 | } | |
47 | ||
48 | static uint32_t | |
49 | ofpprop_type_to_exp_type(uint64_t type) | |
50 | { | |
51 | return type & UINT32_MAX; | |
52 | } | |
53 | ||
c5562271 BP |
54 | /* Pulls a property, beginning with struct ofp_prop_header, from the beginning |
55 | * of 'msg'. Stores the type of the property in '*typep' and, if 'property' is | |
f01c94c5 BP |
56 | * nonnull, the entire property, including the header, in '*property'. Points |
57 | * 'property->header' to the property header (which could be ofp_prop_header or | |
58 | * ofp_prop_experimenter) and 'property->msg' to just past it. Returns 0 if | |
59 | * successful, otherwise an OpenFlow error code. | |
c5562271 | 60 | * |
34a543e3 BP |
61 | * This function treats property types 'min_exp' and larger as introducing |
62 | * experimenter properties. For most kinds of properties, 0xffff is the | |
63 | * appropriate value for 'min_exp', because 0xffff is the only property type | |
64 | * used for experimenters, but async config properties also use 0xfffe. Use | |
65 | * 0x10000 (or higher) if experimenter properties are not supported. | |
66 | * | |
c5562271 BP |
67 | * This function pulls the property's stated size padded out to a multiple of |
68 | * 'alignment' bytes. The common case in OpenFlow is an 'alignment' of 8, so | |
69 | * you can use ofpprop_pull() for that case. */ | |
70 | enum ofperr | |
71 | ofpprop_pull__(struct ofpbuf *msg, struct ofpbuf *property, | |
34a543e3 BP |
72 | unsigned int alignment, unsigned int min_exp, |
73 | uint64_t *typep) | |
c5562271 BP |
74 | { |
75 | struct ofp_prop_header *oph; | |
76 | unsigned int padded_len; | |
77 | unsigned int len; | |
78 | ||
79 | if (msg->size < sizeof *oph) { | |
80 | return OFPERR_OFPBPC_BAD_LEN; | |
81 | } | |
82 | ||
83 | oph = msg->data; | |
84 | len = ntohs(oph->len); | |
85 | padded_len = ROUND_UP(len, alignment); | |
86 | if (len < sizeof *oph || padded_len > msg->size) { | |
87 | return OFPERR_OFPBPC_BAD_LEN; | |
88 | } | |
89 | ||
34a543e3 BP |
90 | uint16_t type = ntohs(oph->type); |
91 | if (type < min_exp) { | |
92 | *typep = type; | |
93 | } else { | |
94 | struct ofp_prop_experimenter *ope = msg->data; | |
95 | if (len < sizeof *ope) { | |
96 | return OFPERR_OFPBPC_BAD_LEN; | |
97 | } | |
98 | ||
99 | if (!ope->experimenter) { | |
100 | /* Reject experimenter 0 because it yields ambiguity with standard | |
101 | * property types. */ | |
102 | return OFPERR_OFPBPC_BAD_EXPERIMENTER; | |
103 | } | |
104 | ||
105 | *typep = OFPPROP_EXP(ntohl(ope->experimenter), ntohl(ope->exp_type)); | |
106 | } | |
107 | ||
c5562271 BP |
108 | if (property) { |
109 | ofpbuf_use_const(property, msg->data, len); | |
34a543e3 BP |
110 | property->header = property->data; |
111 | property->msg = ((uint8_t *) property->data | |
112 | + (type < min_exp | |
113 | ? sizeof(struct ofp_prop_header) | |
114 | : sizeof(struct ofp_prop_experimenter))); | |
c5562271 BP |
115 | } |
116 | ofpbuf_pull(msg, padded_len); | |
117 | return 0; | |
118 | } | |
119 | ||
120 | /* Pulls a property, beginning with struct ofp_prop_header, from the beginning | |
121 | * of 'msg'. Stores the type of the property in '*typep' and, if 'property' is | |
f01c94c5 BP |
122 | * nonnull, the entire property, including the header, in '*property'. Points |
123 | * 'property->header' to the property header (which could be ofp_prop_header or | |
124 | * ofp_prop_experimenter) and 'property->msg' to just past it. Returns 0 if | |
125 | * successful, otherwise an error code. | |
c5562271 | 126 | * |
f01c94c5 BP |
127 | * This function treats property type 0xffff as introducing an experimenter |
128 | * property. Use ofpprop_pull__() instead if some other behavior is needed. | |
129 | * | |
130 | * This function pulls the property's stated size padded out to a multiple of 8 | |
131 | * bytes, which is the common case for OpenFlow properties. Use | |
132 | * ofpprop_pull__() instead if some other behavior is needed.*/ | |
c5562271 | 133 | enum ofperr |
34a543e3 | 134 | ofpprop_pull(struct ofpbuf *msg, struct ofpbuf *property, uint64_t *typep) |
c5562271 | 135 | { |
34a543e3 | 136 | return ofpprop_pull__(msg, property, 8, 0xffff, typep); |
c5562271 BP |
137 | } |
138 | ||
b611d3ac BP |
139 | /* Attempts to parse 'property' as a property containing a 16-bit value. If |
140 | * successful, stores the value into '*value' and returns 0; otherwise returns | |
141 | * an OpenFlow error. */ | |
142 | enum ofperr | |
143 | ofpprop_parse_be16(const struct ofpbuf *property, ovs_be16 *value) | |
144 | { | |
145 | /* OpenFlow uses 8-byte properties for 16-bit values, which doesn't really | |
146 | * make sense. Be forgiving by allowing any size payload as long as it's | |
147 | * at least big enough. */ | |
148 | ovs_be16 *p = property->msg; | |
149 | if (ofpbuf_msgsize(property) < sizeof *p) { | |
150 | return OFPERR_OFPBPC_BAD_LEN; | |
151 | } | |
152 | *value = *p; | |
153 | return 0; | |
154 | } | |
155 | ||
156 | /* Attempts to parse 'property' as a property containing a 32-bit value. If | |
157 | * successful, stores the value into '*value' and returns 0; otherwise returns | |
158 | * an OpenFlow error. */ | |
159 | enum ofperr | |
160 | ofpprop_parse_be32(const struct ofpbuf *property, ovs_be32 *value) | |
161 | { | |
162 | ovs_be32 *p = property->msg; | |
163 | if (ofpbuf_msgsize(property) != sizeof *p) { | |
164 | return OFPERR_OFPBPC_BAD_LEN; | |
165 | } | |
166 | *value = *p; | |
167 | return 0; | |
168 | } | |
169 | ||
f01c94c5 BP |
170 | /* Attempts to parse 'property' as a property containing a 64-bit value. If |
171 | * successful, stores the value into '*value' and returns 0; otherwise returns | |
172 | * an OpenFlow error. */ | |
173 | enum ofperr | |
174 | ofpprop_parse_be64(const struct ofpbuf *property, ovs_be64 *value) | |
175 | { | |
176 | ovs_be64 *p; | |
177 | size_t be64_offset = ROUND_UP(ofpbuf_headersize(property), 8); | |
178 | if (property->size != be64_offset + sizeof *p) { | |
179 | return OFPERR_OFPBPC_BAD_LEN; | |
180 | } | |
181 | ||
182 | p = ALIGNED_CAST(ovs_be64 *, (char *) property->data + be64_offset); | |
183 | *value = *p; | |
184 | return 0; | |
185 | } | |
186 | ||
187 | /* Attempts to parse 'property' as a property containing a 8-bit value. If | |
188 | * successful, stores the value into '*value' and returns 0; otherwise returns | |
189 | * an OpenFlow error. */ | |
190 | enum ofperr | |
191 | ofpprop_parse_u8(const struct ofpbuf *property, uint8_t *value) | |
192 | { | |
193 | /* OpenFlow 1.5 and earlier don't have any 8-bit properties, but it uses | |
194 | * 8-byte properties for 16-bit values, which doesn't really make sense. | |
195 | * Be forgiving by allowing any size payload as long as it's at least big | |
196 | * enough. */ | |
197 | uint8_t *p = property->msg; | |
198 | if (ofpbuf_msgsize(property) < sizeof *p) { | |
199 | return OFPERR_OFPBPC_BAD_LEN; | |
200 | } | |
201 | *value = *p; | |
202 | return 0; | |
203 | } | |
204 | ||
b611d3ac BP |
205 | /* Attempts to parse 'property' as a property containing a 16-bit value. If |
206 | * successful, stores the value into '*value' and returns 0; otherwise returns | |
207 | * an OpenFlow error. */ | |
208 | enum ofperr | |
209 | ofpprop_parse_u16(const struct ofpbuf *property, uint16_t *value) | |
210 | { | |
211 | /* OpenFlow uses 8-byte properties for 16-bit values, which doesn't really | |
212 | * make sense. Be forgiving by allowing any size payload as long as it's | |
213 | * at least big enough. */ | |
214 | ovs_be16 *p = property->msg; | |
215 | if (ofpbuf_msgsize(property) < sizeof *p) { | |
216 | return OFPERR_OFPBPC_BAD_LEN; | |
217 | } | |
218 | *value = ntohs(*p); | |
219 | return 0; | |
220 | } | |
221 | ||
222 | /* Attempts to parse 'property' as a property containing a 32-bit value. If | |
223 | * successful, stores the value into '*value' and returns 0; otherwise returns | |
224 | * an OpenFlow error. */ | |
225 | enum ofperr | |
226 | ofpprop_parse_u32(const struct ofpbuf *property, uint32_t *value) | |
227 | { | |
228 | ovs_be32 *p = property->msg; | |
229 | if (ofpbuf_msgsize(property) != sizeof *p) { | |
230 | return OFPERR_OFPBPC_BAD_LEN; | |
231 | } | |
232 | *value = ntohl(*p); | |
233 | return 0; | |
234 | } | |
235 | ||
f01c94c5 BP |
236 | /* Attempts to parse 'property' as a property containing a 64-bit value. If |
237 | * successful, stores the value into '*value' and returns 0; otherwise returns | |
238 | * an OpenFlow error. */ | |
239 | enum ofperr | |
240 | ofpprop_parse_u64(const struct ofpbuf *property, uint64_t *value) | |
241 | { | |
242 | ovs_be64 *p; | |
243 | size_t be64_offset = ROUND_UP(ofpbuf_headersize(property), 8); | |
244 | if (property->size != be64_offset + sizeof *p) { | |
245 | return OFPERR_OFPBPC_BAD_LEN; | |
246 | } | |
247 | ||
248 | p = ALIGNED_CAST(ovs_be64 *, (char *) property->data + be64_offset); | |
249 | *value = ntohll(*p); | |
250 | return 0; | |
251 | } | |
252 | ||
253 | /* Attempts to parse 'property' as a property containing a UUID. If | |
254 | * successful, stores the value into '*uuid' and returns 0; otherwise returns | |
255 | * an OpenFlow error. */ | |
256 | enum ofperr | |
257 | ofpprop_parse_uuid(const struct ofpbuf *property, struct uuid *uuid) | |
258 | { | |
259 | struct uuid *p = property->msg; | |
260 | if (ofpbuf_msgsize(property) != sizeof *p) { | |
261 | return OFPERR_OFPBPC_BAD_LEN; | |
262 | } | |
263 | *uuid = *p; | |
264 | return 0; | |
265 | } | |
266 | ||
5d10476a BP |
267 | /* Attempts to parse 'property' as a property that contains nested properties. |
268 | * If successful, stores the nested data into '*nested' and returns 0; | |
269 | * otherwise returns an OpenFlow error. | |
270 | * | |
271 | * The only thing special about nested properties is that the property header | |
272 | * is followed by 4 bytes of padding, so that the nested properties begin at an | |
273 | * 8-byte aligned offset. This function can be used in other situations where | |
274 | * this is the case. */ | |
275 | enum ofperr | |
276 | ofpprop_parse_nested(const struct ofpbuf *property, struct ofpbuf *nested) | |
277 | { | |
278 | size_t nested_offset = ROUND_UP(ofpbuf_headersize(property), 8); | |
279 | if (property->size < nested_offset) { | |
280 | return OFPERR_OFPBPC_BAD_LEN; | |
281 | } | |
282 | ||
283 | ofpbuf_use_const(nested, property->data, property->size); | |
284 | ofpbuf_pull(nested, nested_offset); | |
285 | return 0; | |
286 | } | |
287 | ||
c5562271 BP |
288 | /* Adds a property with the given 'type' and 'len'-byte contents 'value' to |
289 | * 'msg', padding the property out to a multiple of 8 bytes. */ | |
290 | void | |
34a543e3 | 291 | ofpprop_put(struct ofpbuf *msg, uint64_t type, const void *value, size_t len) |
c5562271 BP |
292 | { |
293 | size_t start_ofs = ofpprop_start(msg, type); | |
294 | ofpbuf_put(msg, value, len); | |
295 | ofpprop_end(msg, start_ofs); | |
296 | } | |
297 | ||
303721ee BP |
298 | /* Adds a property with the given 'type' to 'msg', consisting of a struct |
299 | * ofp_prop_header or ofp_prop_experimenter followed by enough zero bytes to | |
300 | * total 'len' bytes, followed by padding to bring the property up to a | |
301 | * multiple of 8 bytes. Returns the property header. */ | |
302 | void * | |
303 | ofpprop_put_zeros(struct ofpbuf *msg, uint64_t type, size_t len) | |
304 | { | |
305 | void *header = ofpbuf_put_zeros(msg, ROUND_UP(len, 8)); | |
306 | if (!ofpprop_is_experimenter(type)) { | |
307 | struct ofp_prop_header *oph = header; | |
308 | oph->type = htons(type); | |
309 | oph->len = htons(len); | |
310 | } else { | |
311 | struct ofp_prop_experimenter *ope = header; | |
312 | ope->type = htons(0xffff); | |
313 | ope->len = htons(len); | |
314 | ope->experimenter = htonl(ofpprop_type_to_exp_id(type)); | |
315 | ope->exp_type = htonl(ofpprop_type_to_exp_type(type)); | |
316 | } | |
317 | return header; | |
318 | } | |
319 | ||
b611d3ac BP |
320 | /* Adds a property with the given 'type' and 16-bit 'value' to 'msg'. */ |
321 | void | |
322 | ofpprop_put_be16(struct ofpbuf *msg, uint64_t type, ovs_be16 value) | |
323 | { | |
324 | if (!ofpprop_is_experimenter(type)) { | |
325 | /* The OpenFlow specs consistently (at least they're consistent!) give | |
326 | * properties with a 16-bit integer value a length of 8, not 6, so add | |
327 | * two bytes of padding. */ | |
328 | ovs_be16 padded_value[2] = { value, 0 }; | |
329 | ofpprop_put(msg, type, padded_value, sizeof padded_value); | |
330 | } else { | |
331 | /* There's no precedent but let's assume that this is generally done | |
332 | * sanely. */ | |
333 | ofpprop_put(msg, type, &value, sizeof value); | |
334 | } | |
335 | } | |
336 | ||
337 | /* Adds a property with the given 'type' and 32-bit 'value' to 'msg'. */ | |
338 | void | |
339 | ofpprop_put_be32(struct ofpbuf *msg, uint64_t type, ovs_be32 value) | |
340 | { | |
341 | ofpprop_put(msg, type, &value, sizeof value); | |
342 | } | |
343 | ||
f01c94c5 BP |
344 | /* Adds a property with the given 'type' and 64-bit 'value' to 'msg'. */ |
345 | void | |
346 | ofpprop_put_be64(struct ofpbuf *msg, uint64_t type, ovs_be64 value) | |
347 | { | |
348 | size_t start = ofpprop_start(msg, type); | |
349 | ofpbuf_put_zeros(msg, 4); | |
350 | ofpbuf_put(msg, &value, sizeof value); | |
351 | ofpprop_end(msg, start); | |
352 | } | |
353 | ||
354 | /* Adds a property with the given 'type' and 8-bit 'value' to 'msg'. */ | |
355 | void | |
356 | ofpprop_put_u8(struct ofpbuf *msg, uint64_t type, uint8_t value) | |
357 | { | |
358 | /* There's no precedent for 8-bit properties in OpenFlow 1.5 and earlier | |
359 | * but let's assume they're done sanely. */ | |
360 | ofpprop_put(msg, type, &value, 1); | |
361 | } | |
362 | ||
b611d3ac BP |
363 | /* Adds a property with the given 'type' and 16-bit 'value' to 'msg'. */ |
364 | void | |
365 | ofpprop_put_u16(struct ofpbuf *msg, uint64_t type, uint16_t value) | |
366 | { | |
367 | ofpprop_put_be16(msg, type, htons(value)); | |
368 | } | |
369 | ||
370 | /* Adds a property with the given 'type' and 32-bit 'value' to 'msg'. */ | |
371 | void | |
372 | ofpprop_put_u32(struct ofpbuf *msg, uint64_t type, uint32_t value) | |
373 | { | |
374 | ofpprop_put_be32(msg, type, htonl(value)); | |
375 | } | |
376 | ||
f01c94c5 BP |
377 | /* Adds a property with the given 'type' and 64-bit 'value' to 'msg'. */ |
378 | void | |
379 | ofpprop_put_u64(struct ofpbuf *msg, uint64_t type, uint64_t value) | |
380 | { | |
381 | ofpprop_put_be64(msg, type, htonll(value)); | |
382 | } | |
383 | ||
c5562271 BP |
384 | /* Appends a property to 'msg' whose type is 'type' and whose contents is a |
385 | * series of property headers, one for each 1-bit in 'bitmap'. */ | |
386 | void | |
34a543e3 | 387 | ofpprop_put_bitmap(struct ofpbuf *msg, uint64_t type, uint64_t bitmap) |
c5562271 BP |
388 | { |
389 | size_t start_ofs = ofpprop_start(msg, type); | |
390 | ||
391 | for (; bitmap; bitmap = zero_rightmost_1bit(bitmap)) { | |
392 | ofpprop_start(msg, rightmost_1bit_idx(bitmap)); | |
393 | } | |
394 | ofpprop_end(msg, start_ofs); | |
395 | } | |
396 | ||
f01c94c5 BP |
397 | /* Appends a content-free property with the given 'type' to 'msg'. |
398 | * | |
399 | * (The idea is that the presence of the property acts as a flag.) */ | |
400 | void | |
401 | ofpprop_put_flag(struct ofpbuf *msg, uint64_t type) | |
402 | { | |
403 | size_t start = ofpprop_start(msg, type); | |
404 | ofpprop_end(msg, start); | |
405 | } | |
406 | ||
407 | /* Appends a property to 'msg' with the given 'type' and 'uuid' as its | |
408 | * value. */ | |
409 | void | |
410 | ofpprop_put_uuid(struct ofpbuf *msg, uint64_t type, const struct uuid *uuid) | |
411 | { | |
412 | ofpprop_put(msg, type, uuid, sizeof *uuid); | |
413 | } | |
414 | ||
5d10476a BP |
415 | /* Appends a property of type 'type' to 'msg' whose contents are padding to |
416 | * 8-byte alignment followed by 'nested'. This is a suitable way to add nested | |
417 | * properties to 'msg'. */ | |
418 | void | |
419 | ofpprop_put_nested(struct ofpbuf *msg, uint64_t type, | |
420 | const struct ofpbuf *nested) | |
421 | { | |
422 | size_t start = ofpprop_start_nested(msg, type); | |
423 | ofpbuf_put(msg, nested->data, nested->size); | |
424 | ofpprop_end(msg, start); | |
425 | } | |
426 | ||
c5562271 BP |
427 | /* Appends a header for a property of type 'type' to 'msg'. The caller should |
428 | * add the contents of the property to 'msg', then finish it by calling | |
429 | * ofpprop_end(). Returns the offset of the beginning of the property (to pass | |
430 | * to ofpprop_end() later). */ | |
431 | size_t | |
34a543e3 | 432 | ofpprop_start(struct ofpbuf *msg, uint64_t type) |
c5562271 BP |
433 | { |
434 | size_t start_ofs = msg->size; | |
34a543e3 BP |
435 | if (!ofpprop_is_experimenter(type)) { |
436 | struct ofp_prop_header *oph = ofpbuf_put_uninit(msg, sizeof *oph); | |
437 | oph->type = htons(type); | |
438 | oph->len = htons(4); | |
439 | } else { | |
440 | struct ofp_prop_experimenter *ope | |
441 | = ofpbuf_put_uninit(msg, sizeof *ope); | |
442 | ope->type = htons(0xffff); | |
443 | ope->len = htons(12); | |
444 | ope->experimenter = htonl(ofpprop_type_to_exp_id(type)); | |
445 | ope->exp_type = htonl(ofpprop_type_to_exp_type(type)); | |
446 | } | |
c5562271 BP |
447 | return start_ofs; |
448 | } | |
449 | ||
450 | /* Finishes serializing a property that was begun with ofpprop_start(), by | |
451 | * padding 'msg' to a multiple of 8 bytes and updating the property's length. | |
452 | * 'start_ofs' should be the offset of the beginning of the property, as | |
453 | * returned by ofpprop_start(). */ | |
454 | void | |
455 | ofpprop_end(struct ofpbuf *msg, size_t start_ofs) | |
456 | { | |
457 | struct ofp_prop_header *oph; | |
458 | ||
459 | oph = ofpbuf_at_assert(msg, start_ofs, sizeof *oph); | |
460 | oph->len = htons(msg->size - start_ofs); | |
461 | ofpbuf_padto(msg, ROUND_UP(msg->size, 8)); | |
462 | } | |
463 | ||
5d10476a BP |
464 | /* Appends a header for a property of type 'type' to 'msg', followed by padding |
465 | * suitable for putting nested properties into the property; that is, padding | |
466 | * to an 8-byte alignment. | |
467 | * | |
468 | * This otherwise works like ofpprop_start(). | |
469 | * | |
470 | * There's no need for ofpprop_end_nested(), because ofpprop_end() works fine | |
471 | * for this case. */ | |
472 | size_t | |
473 | ofpprop_start_nested(struct ofpbuf *msg, uint64_t type) | |
474 | { | |
475 | size_t start_ofs = ofpprop_start(msg, type); | |
476 | ofpbuf_padto(msg, ROUND_UP(msg->size, 8)); | |
477 | return start_ofs; | |
478 | } | |
479 | ||
34a543e3 BP |
480 | enum ofperr |
481 | ofpprop_unknown(struct vlog_module *module, bool loose, const char *msg, | |
482 | uint64_t type) | |
483 | { | |
484 | bool is_experimenter = ofpprop_is_experimenter(type); | |
485 | ||
486 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); | |
487 | enum vlog_level level = loose ? VLL_DBG : VLL_WARN; | |
488 | if (!is_experimenter) { | |
489 | vlog_rate_limit(module, level, &rl, "unknown %s property type %"PRId64, | |
490 | msg, type); | |
491 | } else { | |
492 | vlog_rate_limit(module, level, &rl, | |
493 | "unknown %s property type for exp_id 0x%"PRIx32", " | |
494 | "exp_type %"PRId32, msg, | |
495 | ofpprop_type_to_exp_id(type), | |
496 | ofpprop_type_to_exp_type(type)); | |
497 | } | |
498 | ||
499 | /* There's an error OFPBPC_BAD_EXPERIMENTER that we could use for | |
500 | * experimenter IDs that we don't know at all, but that seems like a | |
501 | * difficult distinction and OFPERR_OFPBPC_BAD_EXP_TYPE communicates the | |
502 | * problem quite well. */ | |
503 | return (loose ? 0 | |
504 | : is_experimenter ? OFPERR_OFPBPC_BAD_EXP_TYPE | |
505 | : OFPERR_OFPBPC_BAD_TYPE); | |
506 | } | |
507 |