]>
Commit | Line | Data |
---|---|---|
90bf1e07 BP |
1 | #include <config.h> |
2 | #include "ofp-errors.h" | |
3 | #include <errno.h> | |
4 | #include "byte-order.h" | |
5 | #include "dynamic-string.h" | |
982697a4 | 6 | #include "ofp-msgs.h" |
90bf1e07 BP |
7 | #include "ofp-util.h" |
8 | #include "ofpbuf.h" | |
9 | #include "openflow/openflow.h" | |
10 | #include "vlog.h" | |
11 | ||
12 | VLOG_DEFINE_THIS_MODULE(ofp_errors); | |
13 | ||
14 | struct pair { | |
15 | int type, code; | |
16 | }; | |
17 | ||
18 | #include "ofp-errors.inc" | |
19 | ||
20 | /* Returns an ofperr_domain that corresponds to the OpenFlow version number | |
21 | * 'version' (one of the possible values of struct ofp_header's 'version' | |
22 | * member). Returns NULL if the version isn't defined or isn't understood by | |
23 | * OVS. */ | |
688e86e1 SH |
24 | static const struct ofperr_domain * |
25 | ofperr_domain_from_version(enum ofp_version version) | |
90bf1e07 | 26 | { |
688e86e1 SH |
27 | switch (version) { |
28 | case OFP10_VERSION: | |
29 | return &ofperr_of10; | |
30 | case OFP11_VERSION: | |
31 | return &ofperr_of11; | |
32 | case OFP12_VERSION: | |
33 | return &ofperr_of12; | |
2e1ae200 JR |
34 | case OFP13_VERSION: |
35 | return &ofperr_of13; | |
688e86e1 SH |
36 | default: |
37 | return NULL; | |
38 | } | |
90bf1e07 BP |
39 | } |
40 | ||
688e86e1 | 41 | /* Returns the name (e.g. "OpenFlow 1.0") of OpenFlow version 'version'. */ |
2e0525bc | 42 | const char * |
688e86e1 | 43 | ofperr_domain_get_name(enum ofp_version version) |
2e0525bc | 44 | { |
688e86e1 SH |
45 | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
46 | return domain ? domain->name : NULL; | |
2e0525bc SH |
47 | } |
48 | ||
90bf1e07 BP |
49 | /* Returns true if 'error' is a valid OFPERR_* value, false otherwise. */ |
50 | bool | |
51 | ofperr_is_valid(enum ofperr error) | |
52 | { | |
53 | return error >= OFPERR_OFS && error < OFPERR_OFS + OFPERR_N_ERRORS; | |
54 | } | |
55 | ||
56 | /* Returns true if 'error' is a valid OFPERR_* value that designates a whole | |
57 | * category of errors instead of a particular error, e.g. if it is an | |
58 | * OFPERR_OFPET_* value, and false otherwise. */ | |
59 | bool | |
60 | ofperr_is_category(enum ofperr error) | |
61 | { | |
62 | return (ofperr_is_valid(error) | |
63 | && ofperr_of10.errors[error - OFPERR_OFS].code == -1 | |
64 | && ofperr_of11.errors[error - OFPERR_OFS].code == -1); | |
65 | } | |
66 | ||
67 | /* Returns true if 'error' is a valid OFPERR_* value that is a Nicira | |
68 | * extension, e.g. if it is an OFPERR_NX* value, and false otherwise. */ | |
69 | bool | |
70 | ofperr_is_nx_extension(enum ofperr error) | |
71 | { | |
72 | return (ofperr_is_valid(error) | |
73 | && (ofperr_of10.errors[error - OFPERR_OFS].code >= 0x100 || | |
74 | ofperr_of11.errors[error - OFPERR_OFS].code >= 0x100)); | |
75 | } | |
76 | ||
77 | /* Returns true if 'error' can be encoded as an OpenFlow error message in | |
78 | * 'domain', false otherwise. | |
79 | * | |
80 | * A given error may not be encodable in some domains because each OpenFlow | |
81 | * version tends to introduce new errors and retire some old ones. */ | |
82 | bool | |
688e86e1 | 83 | ofperr_is_encodable(enum ofperr error, enum ofp_version version) |
90bf1e07 | 84 | { |
688e86e1 | 85 | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
90bf1e07 | 86 | return (ofperr_is_valid(error) |
688e86e1 | 87 | && domain && domain->errors[error - OFPERR_OFS].code >= 0); |
90bf1e07 BP |
88 | } |
89 | ||
90 | /* Returns the OFPERR_* value that corresponds to 'type' and 'code' within | |
688e86e1 SH |
91 | * 'version', or 0 if either no such OFPERR_* value exists or 'version' is |
92 | * unknown. */ | |
90bf1e07 | 93 | enum ofperr |
688e86e1 | 94 | ofperr_decode(enum ofp_version version, uint16_t type, uint16_t code) |
90bf1e07 | 95 | { |
688e86e1 SH |
96 | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
97 | return domain ? domain->decode(type, code) : 0; | |
90bf1e07 BP |
98 | } |
99 | ||
100 | /* Returns the OFPERR_* value that corresponds to the category 'type' within | |
688e86e1 SH |
101 | * 'version', or 0 if either no such OFPERR_* value exists or 'version' is |
102 | * unknown. */ | |
90bf1e07 | 103 | enum ofperr |
688e86e1 | 104 | ofperr_decode_type(enum ofp_version version, uint16_t type) |
90bf1e07 | 105 | { |
688e86e1 SH |
106 | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
107 | return domain ? domain->decode_type(type) : 0; | |
90bf1e07 BP |
108 | } |
109 | ||
110 | /* Returns the name of 'error', e.g. "OFPBRC_BAD_TYPE" if 'error' is | |
111 | * OFPBRC_BAD_TYPE, or "<invalid>" if 'error' is not a valid OFPERR_* value. | |
112 | * | |
113 | * Consider ofperr_to_string() instead, if the error code might be an errno | |
114 | * value. */ | |
115 | const char * | |
116 | ofperr_get_name(enum ofperr error) | |
117 | { | |
118 | return (ofperr_is_valid(error) | |
119 | ? error_names[error - OFPERR_OFS] | |
120 | : "<invalid>"); | |
121 | } | |
122 | ||
2e0525bc SH |
123 | /* Returns the OFPERR_* value that corresponds for 'name', 0 if none exists. |
124 | * For example, returns OFPERR_OFPHFC_INCOMPATIBLE if 'name' is | |
125 | * "OFPHFC_INCOMPATIBLE". | |
126 | * | |
127 | * This is probably useful only for debugging and testing. */ | |
128 | enum ofperr | |
129 | ofperr_from_name(const char *name) | |
130 | { | |
131 | int i; | |
132 | ||
133 | for (i = 0; i < OFPERR_N_ERRORS; i++) { | |
134 | if (!strcmp(name, error_names[i])) { | |
135 | return i + OFPERR_OFS; | |
136 | } | |
137 | } | |
138 | return 0; | |
139 | } | |
140 | ||
90bf1e07 BP |
141 | /* Returns an extended description name of 'error', e.g. "ofp_header.type not |
142 | * supported." if 'error' is OFPBRC_BAD_TYPE, or "<invalid>" if 'error' is not | |
143 | * a valid OFPERR_* value. */ | |
144 | const char * | |
145 | ofperr_get_description(enum ofperr error) | |
146 | { | |
147 | return (ofperr_is_valid(error) | |
148 | ? error_comments[error - OFPERR_OFS] | |
149 | : "<invalid>"); | |
150 | } | |
151 | ||
2e0525bc SH |
152 | static const struct pair * |
153 | ofperr_get_pair__(enum ofperr error, const struct ofperr_domain *domain) | |
154 | { | |
155 | size_t ofs = error - OFPERR_OFS; | |
156 | ||
157 | assert(ofperr_is_valid(error)); | |
158 | return &domain->errors[ofs]; | |
159 | } | |
160 | ||
90bf1e07 | 161 | static struct ofpbuf * |
9b7e2112 | 162 | ofperr_encode_msg__(enum ofperr error, enum ofp_version ofp_version, |
90bf1e07 BP |
163 | ovs_be32 xid, const void *data, size_t data_len) |
164 | { | |
165 | struct ofp_error_msg *oem; | |
166 | const struct pair *pair; | |
167 | struct ofpbuf *buf; | |
9b7e2112 | 168 | const struct ofperr_domain *domain; |
90bf1e07 | 169 | |
9b7e2112 | 170 | domain = ofperr_domain_from_version(ofp_version); |
90bf1e07 BP |
171 | if (!domain) { |
172 | return NULL; | |
173 | } | |
174 | ||
688e86e1 | 175 | if (!ofperr_is_encodable(error, ofp_version)) { |
90bf1e07 BP |
176 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); |
177 | ||
178 | if (!ofperr_is_valid(error)) { | |
179 | /* 'error' seems likely to be a system errno value. */ | |
180 | VLOG_WARN_RL(&rl, "invalid OpenFlow error code %d (%s)", | |
181 | error, strerror(error)); | |
182 | } else { | |
183 | const char *s = ofperr_get_name(error); | |
184 | if (ofperr_is_category(error)) { | |
185 | VLOG_WARN_RL(&rl, "cannot encode error category (%s)", s); | |
186 | } else { | |
187 | VLOG_WARN_RL(&rl, "cannot encode %s for %s", s, domain->name); | |
188 | } | |
189 | } | |
190 | ||
191 | return NULL; | |
192 | } | |
193 | ||
2e0525bc | 194 | pair = ofperr_get_pair__(error, domain); |
90bf1e07 | 195 | if (!ofperr_is_nx_extension(error)) { |
982697a4 BP |
196 | buf = ofpraw_alloc_xid(OFPRAW_OFPT_ERROR, domain->version, xid, |
197 | sizeof *oem + data_len); | |
198 | ||
199 | oem = ofpbuf_put_uninit(buf, sizeof *oem); | |
90bf1e07 BP |
200 | oem->type = htons(pair->type); |
201 | oem->code = htons(pair->code); | |
202 | } else { | |
203 | struct nx_vendor_error *nve; | |
204 | ||
982697a4 BP |
205 | buf = ofpraw_alloc_xid(OFPRAW_OFPT_ERROR, domain->version, xid, |
206 | sizeof *oem + sizeof *nve + data_len); | |
207 | ||
208 | oem = ofpbuf_put_uninit(buf, sizeof *oem); | |
90bf1e07 BP |
209 | oem->type = htons(NXET_VENDOR); |
210 | oem->code = htons(NXVC_VENDOR_ERROR); | |
211 | ||
982697a4 | 212 | nve = ofpbuf_put_uninit(buf, sizeof *nve); |
90bf1e07 BP |
213 | nve->vendor = htonl(NX_VENDOR_ID); |
214 | nve->type = htons(pair->type); | |
215 | nve->code = htons(pair->code); | |
216 | } | |
90bf1e07 | 217 | |
90bf1e07 BP |
218 | ofpbuf_put(buf, data, data_len); |
219 | ||
220 | return buf; | |
221 | } | |
222 | ||
223 | /* Creates and returns an OpenFlow message of type OFPT_ERROR that conveys the | |
224 | * given 'error'. | |
225 | * | |
226 | * 'oh->version' determines the OpenFlow version of the error reply. | |
227 | * 'oh->xid' determines the xid of the error reply. | |
228 | * The error reply will contain an initial subsequence of 'oh', up to | |
229 | * 'oh->length' or 64 bytes, whichever is shorter. | |
230 | * | |
231 | * Returns NULL if 'error' is not an OpenFlow error code or if 'error' cannot | |
232 | * be encoded as OpenFlow version 'oh->version'. | |
233 | * | |
234 | * This function isn't appropriate for encoding OFPET_HELLO_FAILED error | |
235 | * messages. Use ofperr_encode_hello() instead. */ | |
236 | struct ofpbuf * | |
237 | ofperr_encode_reply(enum ofperr error, const struct ofp_header *oh) | |
238 | { | |
90bf1e07 BP |
239 | uint16_t len = ntohs(oh->length); |
240 | ||
9b7e2112 | 241 | return ofperr_encode_msg__(error, oh->version, oh->xid, oh, MIN(len, 64)); |
90bf1e07 BP |
242 | } |
243 | ||
244 | /* Creates and returns an OpenFlow message of type OFPT_ERROR that conveys the | |
245 | * given 'error', in the error domain 'domain'. The error message will include | |
246 | * the additional null-terminated text string 's'. | |
247 | * | |
9b7e2112 SH |
248 | * If 'version' is an unknown version then OFP10_VERSION is used. |
249 | * OFPET_HELLO_FAILED error messages are supposed to be backward-compatible, | |
250 | * so in theory this should work. | |
90bf1e07 BP |
251 | * |
252 | * Returns NULL if 'error' is not an OpenFlow error code or if 'error' cannot | |
253 | * be encoded in 'domain'. */ | |
254 | struct ofpbuf * | |
9b7e2112 | 255 | ofperr_encode_hello(enum ofperr error, enum ofp_version ofp_version, |
90bf1e07 BP |
256 | const char *s) |
257 | { | |
9b7e2112 SH |
258 | switch (ofp_version) { |
259 | case OFP10_VERSION: | |
260 | case OFP11_VERSION: | |
261 | case OFP12_VERSION: | |
2e1ae200 | 262 | case OFP13_VERSION: |
9b7e2112 SH |
263 | break; |
264 | ||
265 | default: | |
266 | ofp_version = OFP10_VERSION; | |
90bf1e07 | 267 | } |
9b7e2112 SH |
268 | |
269 | return ofperr_encode_msg__(error, ofp_version, htonl(0), s, strlen(s)); | |
90bf1e07 BP |
270 | } |
271 | ||
2e0525bc SH |
272 | /* Returns the value that would go into an OFPT_ERROR message's 'type' for |
273 | * encoding 'error' in 'domain'. Returns -1 if 'error' is not encodable in | |
688e86e1 | 274 | * 'version' or 'version' is unknown. |
2e0525bc SH |
275 | * |
276 | * 'error' must be a valid OFPERR_* code, as checked by ofperr_is_valid(). */ | |
277 | int | |
688e86e1 | 278 | ofperr_get_type(enum ofperr error, enum ofp_version version) |
2e0525bc | 279 | { |
688e86e1 SH |
280 | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
281 | return domain ? ofperr_get_pair__(error, domain)->type : -1; | |
2e0525bc SH |
282 | } |
283 | ||
284 | /* Returns the value that would go into an OFPT_ERROR message's 'code' for | |
285 | * encoding 'error' in 'domain'. Returns -1 if 'error' is not encodable in | |
688e86e1 SH |
286 | * 'version', 'version' is unknown or if 'error' represents a category |
287 | * rather than a specific error. | |
288 | * | |
2e0525bc SH |
289 | * |
290 | * 'error' must be a valid OFPERR_* code, as checked by ofperr_is_valid(). */ | |
291 | int | |
688e86e1 | 292 | ofperr_get_code(enum ofperr error, enum ofp_version version) |
2e0525bc | 293 | { |
688e86e1 SH |
294 | const struct ofperr_domain *domain = ofperr_domain_from_version(version); |
295 | return domain ? ofperr_get_pair__(error, domain)->code : -1; | |
2e0525bc SH |
296 | } |
297 | ||
90bf1e07 BP |
298 | /* Tries to decodes 'oh', which should be an OpenFlow OFPT_ERROR message. |
299 | * Returns an OFPERR_* constant on success, 0 on failure. | |
300 | * | |
982697a4 BP |
301 | * If 'payload' is nonnull, on success '*payload' is initialized to the |
302 | * error's payload, and on failure it is cleared. */ | |
90bf1e07 | 303 | enum ofperr |
982697a4 | 304 | ofperr_decode_msg(const struct ofp_header *oh, struct ofpbuf *payload) |
90bf1e07 BP |
305 | { |
306 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); | |
307 | ||
90bf1e07 | 308 | const struct ofp_error_msg *oem; |
982697a4 | 309 | enum ofpraw raw; |
90bf1e07 BP |
310 | uint16_t type, code; |
311 | enum ofperr error; | |
312 | struct ofpbuf b; | |
313 | ||
982697a4 BP |
314 | if (payload) { |
315 | memset(payload, 0, sizeof *payload); | |
90bf1e07 BP |
316 | } |
317 | ||
318 | /* Pull off the error message. */ | |
319 | ofpbuf_use_const(&b, oh, ntohs(oh->length)); | |
982697a4 BP |
320 | error = ofpraw_pull(&raw, &b); |
321 | if (error) { | |
90bf1e07 BP |
322 | return 0; |
323 | } | |
982697a4 | 324 | oem = ofpbuf_pull(&b, sizeof *oem); |
90bf1e07 | 325 | |
90bf1e07 BP |
326 | /* Get the error type and code. */ |
327 | type = ntohs(oem->type); | |
328 | code = ntohs(oem->code); | |
329 | if (type == NXET_VENDOR && code == NXVC_VENDOR_ERROR) { | |
330 | const struct nx_vendor_error *nve = ofpbuf_try_pull(&b, sizeof *nve); | |
331 | if (!nve) { | |
332 | return 0; | |
333 | } | |
334 | ||
335 | if (nve->vendor != htonl(NX_VENDOR_ID)) { | |
336 | VLOG_WARN_RL(&rl, "error contains unknown vendor ID %#"PRIx32, | |
337 | ntohl(nve->vendor)); | |
338 | return 0; | |
339 | } | |
340 | type = ntohs(nve->type); | |
341 | code = ntohs(nve->code); | |
342 | } | |
343 | ||
344 | /* Translate the error type and code into an ofperr. | |
345 | * If we don't know the error type and code, at least try for the type. */ | |
688e86e1 | 346 | error = ofperr_decode(oh->version, type, code); |
90bf1e07 | 347 | if (!error) { |
688e86e1 | 348 | error = ofperr_decode_type(oh->version, type); |
90bf1e07 | 349 | } |
982697a4 BP |
350 | if (error && payload) { |
351 | ofpbuf_use_const(payload, b.data, b.size); | |
90bf1e07 BP |
352 | } |
353 | return error; | |
354 | } | |
355 | ||
356 | /* If 'error' is a valid OFPERR_* value, returns its name | |
357 | * (e.g. "OFPBRC_BAD_TYPE" for OFPBRC_BAD_TYPE). Otherwise, assumes that | |
358 | * 'error' is a positive errno value and returns what strerror() produces for | |
359 | * 'error'. */ | |
360 | const char * | |
361 | ofperr_to_string(enum ofperr error) | |
362 | { | |
363 | return ofperr_is_valid(error) ? ofperr_get_name(error) : strerror(error); | |
364 | } |