/src/openvswitch/lib/ofp-errors.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2012, 2013, 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 | | #include <errno.h> |
19 | | #include "byte-order.h" |
20 | | #include "openflow/openflow.h" |
21 | | #include "openflow/nicira-ext.h" |
22 | | #include "openvswitch/dynamic-string.h" |
23 | | #include "openvswitch/ofp-actions.h" |
24 | | #include "openvswitch/ofp-errors.h" |
25 | | #include "openvswitch/ofp-msgs.h" |
26 | | #include "openvswitch/ofp-print.h" |
27 | | #include "openvswitch/ofpbuf.h" |
28 | | #include "openvswitch/vlog.h" |
29 | | #include "util.h" |
30 | | |
31 | | VLOG_DEFINE_THIS_MODULE(ofp_errors); |
32 | | |
33 | | struct triplet { |
34 | | uint32_t vendor; |
35 | | int type, code; |
36 | | }; |
37 | | |
38 | | #include "ofp-errors.inc" |
39 | | |
40 | | /* Returns an ofperr_domain that corresponds to the OpenFlow version number |
41 | | * 'version' (one of the possible values of struct ofp_header's 'version' |
42 | | * member). Returns NULL if the version isn't defined or isn't understood by |
43 | | * OVS. */ |
44 | | static const struct ofperr_domain * |
45 | | ofperr_domain_from_version(enum ofp_version version) |
46 | 1.84M | { |
47 | 1.84M | switch (version) { |
48 | 225k | case OFP10_VERSION: |
49 | 225k | return &ofperr_of10; |
50 | 221k | case OFP11_VERSION: |
51 | 221k | return &ofperr_of11; |
52 | 393k | case OFP12_VERSION: |
53 | 393k | return &ofperr_of12; |
54 | 236k | case OFP13_VERSION: |
55 | 236k | return &ofperr_of13; |
56 | 380k | case OFP14_VERSION: |
57 | 380k | return &ofperr_of14; |
58 | 273k | case OFP15_VERSION: |
59 | 273k | return &ofperr_of15; |
60 | 115k | default: |
61 | 115k | return NULL; |
62 | 1.84M | } |
63 | 1.84M | } |
64 | | |
65 | | /* Returns the name (e.g. "OpenFlow 1.0") of OpenFlow version 'version'. */ |
66 | | const char * |
67 | | ofperr_domain_get_name(enum ofp_version version) |
68 | 0 | { |
69 | 0 | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
70 | 0 | return domain ? domain->name : NULL; |
71 | 0 | } |
72 | | |
73 | | /* Returns true if 'error' is a valid OFPERR_* value, false otherwise. */ |
74 | | bool |
75 | | ofperr_is_valid(enum ofperr error) |
76 | 4.42M | { |
77 | 4.42M | return error >= OFPERR_OFS && error < OFPERR_OFS + OFPERR_N_ERRORS; |
78 | 4.42M | } |
79 | | |
80 | | /* Returns the OFPERR_* value that corresponds to 'type' and 'code' within |
81 | | * 'version', or 0 if either no such OFPERR_* value exists or 'version' is |
82 | | * unknown. */ |
83 | | static enum ofperr |
84 | | ofperr_decode(enum ofp_version version, |
85 | | uint32_t vendor, uint16_t type, uint16_t code) |
86 | 1.84M | { |
87 | 1.84M | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
88 | 1.84M | return domain ? domain->decode(vendor, type, code) : 0; |
89 | 1.84M | } |
90 | | |
91 | | /* Returns the name of 'error', e.g. "OFPBRC_BAD_TYPE" if 'error' is |
92 | | * OFPBRC_BAD_TYPE, or "<invalid>" if 'error' is not a valid OFPERR_* value. |
93 | | * |
94 | | * Consider ofperr_to_string() instead, if the error code might be an errno |
95 | | * value. */ |
96 | | const char * |
97 | | ofperr_get_name(enum ofperr error) |
98 | 4.42M | { |
99 | 4.42M | return (ofperr_is_valid(error) |
100 | 4.42M | ? error_names[error - OFPERR_OFS] |
101 | 4.42M | : "<invalid>"); |
102 | 4.42M | } |
103 | | |
104 | | /* Returns the OFPERR_* value that corresponds for 'name', 0 if none exists. |
105 | | * For example, returns OFPERR_OFPHFC_INCOMPATIBLE if 'name' is |
106 | | * "OFPHFC_INCOMPATIBLE". |
107 | | * |
108 | | * This is probably useful only for debugging and testing. */ |
109 | | enum ofperr |
110 | | ofperr_from_name(const char *name) |
111 | 0 | { |
112 | 0 | int i; |
113 | |
|
114 | 0 | for (i = 0; i < OFPERR_N_ERRORS; i++) { |
115 | 0 | if (!strcmp(name, error_names[i])) { |
116 | 0 | return i + OFPERR_OFS; |
117 | 0 | } |
118 | 0 | } |
119 | 0 | return 0; |
120 | 0 | } |
121 | | |
122 | | /* Returns an extended description name of 'error', e.g. "ofp_header.type not |
123 | | * supported." if 'error' is OFPBRC_BAD_TYPE, or "<invalid>" if 'error' is not |
124 | | * a valid OFPERR_* value. */ |
125 | | const char * |
126 | | ofperr_get_description(enum ofperr error) |
127 | 0 | { |
128 | 0 | return (ofperr_is_valid(error) |
129 | 0 | ? error_comments[error - OFPERR_OFS] |
130 | 0 | : "<invalid>"); |
131 | 0 | } |
132 | | |
133 | | static const struct triplet * |
134 | | ofperr_get_triplet__(enum ofperr error, const struct ofperr_domain *domain) |
135 | 0 | { |
136 | 0 | size_t ofs = error - OFPERR_OFS; |
137 | |
|
138 | 0 | ovs_assert(ofperr_is_valid(error)); |
139 | 0 | return &domain->errors[ofs]; |
140 | 0 | } |
141 | | |
142 | | static struct ofpbuf * |
143 | | ofperr_encode_msg__(enum ofperr error, enum ofp_version ofp_version, |
144 | | ovs_be32 xid, const void *data, size_t data_len) |
145 | 0 | { |
146 | 0 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); |
147 | 0 | const struct ofperr_domain *domain; |
148 | 0 | const struct triplet *triplet; |
149 | 0 | struct ofp_error_msg *oem; |
150 | 0 | struct ofpbuf *buf; |
151 | | |
152 | | /* Get the error domain for 'ofp_version', or fall back to OF1.0. */ |
153 | 0 | domain = ofperr_domain_from_version(ofp_version); |
154 | 0 | if (!domain) { |
155 | 0 | VLOG_ERR_RL(&rl, "cannot encode error for unknown OpenFlow " |
156 | 0 | "version 0x%02x", ofp_version); |
157 | 0 | domain = &ofperr_of10; |
158 | 0 | } |
159 | | |
160 | | /* Make sure 'error' is valid in 'domain', or use a fallback error. */ |
161 | 0 | if (!ofperr_is_valid(error)) { |
162 | | /* 'error' seems likely to be a system errno value. */ |
163 | 0 | VLOG_ERR_RL(&rl, "invalid OpenFlow error code %d (%s)", |
164 | 0 | error, ovs_strerror(error)); |
165 | 0 | error = OFPERR_NXBRC_UNENCODABLE_ERROR; |
166 | 0 | } else if (domain->errors[error - OFPERR_OFS].code < 0) { |
167 | 0 | VLOG_ERR_RL(&rl, "cannot encode %s for %s", |
168 | 0 | ofperr_get_name(error), domain->name); |
169 | 0 | error = OFPERR_NXBRC_UNENCODABLE_ERROR; |
170 | 0 | } |
171 | |
|
172 | 0 | triplet = ofperr_get_triplet__(error, domain); |
173 | 0 | if (!triplet->vendor) { |
174 | 0 | buf = ofpraw_alloc_xid(OFPRAW_OFPT_ERROR, domain->version, xid, |
175 | 0 | sizeof *oem + data_len); |
176 | |
|
177 | 0 | oem = ofpbuf_put_uninit(buf, sizeof *oem); |
178 | 0 | oem->type = htons(triplet->type); |
179 | 0 | oem->code = htons(triplet->code); |
180 | 0 | } else if (ofp_version <= OFP11_VERSION) { |
181 | 0 | struct nx_vendor_error *nve; |
182 | |
|
183 | 0 | buf = ofpraw_alloc_xid(OFPRAW_OFPT_ERROR, domain->version, xid, |
184 | 0 | sizeof *oem + sizeof *nve + data_len); |
185 | |
|
186 | 0 | oem = ofpbuf_put_uninit(buf, sizeof *oem); |
187 | 0 | oem->type = htons(NXET_VENDOR); |
188 | 0 | oem->code = htons(NXVC_VENDOR_ERROR); |
189 | |
|
190 | 0 | nve = ofpbuf_put_uninit(buf, sizeof *nve); |
191 | 0 | nve->vendor = htonl(triplet->vendor); |
192 | 0 | nve->type = htons(triplet->type); |
193 | 0 | nve->code = htons(triplet->code); |
194 | 0 | } else { |
195 | 0 | ovs_be32 vendor = htonl(triplet->vendor); |
196 | |
|
197 | 0 | buf = ofpraw_alloc_xid(OFPRAW_OFPT_ERROR, domain->version, xid, |
198 | 0 | sizeof *oem + sizeof(uint32_t) + data_len); |
199 | |
|
200 | 0 | oem = ofpbuf_put_uninit(buf, sizeof *oem); |
201 | 0 | oem->type = htons(OFPET12_EXPERIMENTER); |
202 | 0 | oem->code = htons(triplet->type); |
203 | 0 | ofpbuf_put(buf, &vendor, sizeof vendor); |
204 | 0 | } |
205 | |
|
206 | 0 | ofpbuf_put(buf, data, MIN(data_len, UINT16_MAX - buf->size)); |
207 | 0 | ofpmsg_update_length(buf); |
208 | |
|
209 | 0 | return buf; |
210 | 0 | } |
211 | | |
212 | | /* Creates and returns an OpenFlow message of type OFPT_ERROR that conveys the |
213 | | * given 'error'. |
214 | | * |
215 | | * 'oh->version' determines the OpenFlow version of the error reply. |
216 | | * 'oh->xid' determines the xid of the error reply. |
217 | | * The error reply will contain an initial subsequence of 'oh', up to |
218 | | * 'oh->length' (or however much fits). |
219 | | * |
220 | | * This function isn't appropriate for encoding OFPET_HELLO_FAILED error |
221 | | * messages. Use ofperr_encode_hello() instead. */ |
222 | | struct ofpbuf * |
223 | | ofperr_encode_reply(enum ofperr error, const struct ofp_header *oh) |
224 | 0 | { |
225 | 0 | return ofperr_encode_msg__(error, oh->version, oh->xid, |
226 | 0 | oh, ntohs(oh->length)); |
227 | 0 | } |
228 | | |
229 | | /* Creates and returns an OpenFlow message of type OFPT_ERROR that conveys the |
230 | | * given 'error', in the error domain 'domain'. The error message will include |
231 | | * the additional null-terminated text string 's'. |
232 | | * |
233 | | * If 'version' is an unknown version then OFP10_VERSION is used. |
234 | | * OFPET_HELLO_FAILED error messages are supposed to be backward-compatible, |
235 | | * so in theory this should work. */ |
236 | | struct ofpbuf * |
237 | | ofperr_encode_hello(enum ofperr error, enum ofp_version ofp_version, |
238 | | const char *s) |
239 | 0 | { |
240 | 0 | return ofperr_encode_msg__(error, ofp_version, htonl(0), s, strlen(s)); |
241 | 0 | } |
242 | | |
243 | | void |
244 | | ofperr_msg_format(struct ds *string, enum ofperr error, |
245 | | const struct ofpbuf *payload, |
246 | | const struct ofputil_port_map *port_map, |
247 | | const struct ofputil_table_map *table_map) |
248 | 1.13M | { |
249 | 1.13M | ds_put_format(string, " %s\n", ofperr_get_name(error)); |
250 | | |
251 | 1.13M | if (error == OFPERR_OFPHFC_INCOMPATIBLE || error == OFPERR_OFPHFC_EPERM) { |
252 | 34.5k | ds_put_printable(string, payload->data, payload->size); |
253 | 1.09M | } else { |
254 | 1.09M | char *s = ofp_to_string(payload->data, payload->size, |
255 | 1.09M | port_map, table_map, 1); |
256 | 1.09M | ds_put_cstr(string, s); |
257 | 1.09M | free(s); |
258 | 1.09M | } |
259 | 1.13M | } |
260 | | |
261 | | int |
262 | | ofperr_get_vendor(enum ofperr error, enum ofp_version version) |
263 | 0 | { |
264 | 0 | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
265 | 0 | return domain ? ofperr_get_triplet__(error, domain)->vendor : -1; |
266 | 0 | } |
267 | | |
268 | | /* Returns the value that would go into an OFPT_ERROR message's 'type' for |
269 | | * encoding 'error' in 'domain'. Returns -1 if 'error' is not encodable in |
270 | | * 'version' or 'version' is unknown. |
271 | | * |
272 | | * 'error' must be a valid OFPERR_* code, as checked by ofperr_is_valid(). */ |
273 | | int |
274 | | ofperr_get_type(enum ofperr error, enum ofp_version version) |
275 | 0 | { |
276 | 0 | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
277 | 0 | return domain ? ofperr_get_triplet__(error, domain)->type : -1; |
278 | 0 | } |
279 | | |
280 | | /* Returns the value that would go into an OFPT_ERROR message's 'code' for |
281 | | * encoding 'error' in 'domain'. Returns -1 if 'error' is not encodable in |
282 | | * 'version', 'version' is unknown or if 'error' represents a category |
283 | | * rather than a specific error. |
284 | | * |
285 | | * |
286 | | * 'error' must be a valid OFPERR_* code, as checked by ofperr_is_valid(). */ |
287 | | int |
288 | | ofperr_get_code(enum ofperr error, enum ofp_version version) |
289 | 0 | { |
290 | 0 | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
291 | 0 | return domain ? ofperr_get_triplet__(error, domain)->code : -1; |
292 | 0 | } |
293 | | |
294 | | /* Tries to decode 'oh', which should be an OpenFlow OFPT_ERROR message. |
295 | | * Returns an OFPERR_* constant on success, 0 on failure. |
296 | | * |
297 | | * If 'payload' is nonnull, on success '*payload' is initialized with a copy of |
298 | | * the error's payload (copying is required because the payload is not properly |
299 | | * aligned). The caller must free the payload (with ofpbuf_uninit()) when it |
300 | | * is no longer needed. On failure, '*payload' is cleared. */ |
301 | | enum ofperr |
302 | | ofperr_decode_msg(const struct ofp_header *oh, struct ofpbuf *payload) |
303 | 1.88M | { |
304 | 1.88M | const struct ofp_error_msg *oem; |
305 | 1.88M | enum ofpraw raw; |
306 | 1.88M | uint16_t type, code; |
307 | 1.88M | uint32_t vendor; |
308 | | |
309 | 1.88M | if (payload) { |
310 | 1.88M | memset(payload, 0, sizeof *payload); |
311 | 1.88M | } |
312 | | |
313 | | /* Pull off the error message. */ |
314 | 1.88M | struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length)); |
315 | 1.88M | enum ofperr error = ofpraw_pull(&raw, &b); |
316 | 1.88M | if (error) { |
317 | 0 | return 0; |
318 | 0 | } |
319 | 1.88M | oem = ofpbuf_pull(&b, sizeof *oem); |
320 | | |
321 | | /* Get the error type and code. */ |
322 | 1.88M | vendor = 0; |
323 | 1.88M | type = ntohs(oem->type); |
324 | 1.88M | code = ntohs(oem->code); |
325 | 1.88M | if (type == NXET_VENDOR && code == NXVC_VENDOR_ERROR) { |
326 | 178k | const struct nx_vendor_error *nve = ofpbuf_try_pull(&b, sizeof *nve); |
327 | 178k | if (!nve) { |
328 | 37 | return 0; |
329 | 37 | } |
330 | | |
331 | 178k | vendor = ntohl(nve->vendor); |
332 | 178k | type = ntohs(nve->type); |
333 | 178k | code = ntohs(nve->code); |
334 | 1.70M | } else if (type == OFPET12_EXPERIMENTER) { |
335 | 200k | const ovs_be32 *vendorp = ofpbuf_try_pull(&b, sizeof *vendorp); |
336 | 200k | if (!vendorp) { |
337 | 35.3k | return 0; |
338 | 35.3k | } |
339 | | |
340 | 165k | vendor = ntohl(*vendorp); |
341 | 165k | type = code; |
342 | 165k | code = 0; |
343 | 165k | } |
344 | | |
345 | | /* Translate the error type and code into an ofperr. */ |
346 | 1.84M | error = ofperr_decode(oh->version, vendor, type, code); |
347 | 1.84M | if (error && payload) { |
348 | 1.13M | ofpbuf_init(payload, b.size); |
349 | 1.13M | ofpbuf_push(payload, b.data, b.size); |
350 | 1.13M | ofpbuf_trim(payload); |
351 | 1.13M | } |
352 | 1.84M | return error; |
353 | 1.88M | } |
354 | | |
355 | | /* If 'error' is a valid OFPERR_* value, returns its name |
356 | | * (e.g. "OFPBRC_BAD_TYPE" for OFPBRC_BAD_TYPE). Otherwise, assumes that |
357 | | * 'error' is a positive errno value and returns what ovs_strerror() produces |
358 | | * for 'error'. */ |
359 | | const char * |
360 | | ofperr_to_string(enum ofperr error) |
361 | 942 | { |
362 | 942 | return (ofperr_is_valid(error) |
363 | 942 | ? ofperr_get_name(error) |
364 | 942 | : ovs_strerror(error)); |
365 | 942 | } |