]>
Commit | Line | Data |
---|---|---|
75a75043 | 1 | /* |
bdda5aca | 2 | * Copyright (c) 2011, 2012, 2013 Nicira, Inc. |
75a75043 BP |
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 | ||
19 | #include "learn.h" | |
20 | ||
21 | #include "byte-order.h" | |
22 | #include "dynamic-string.h" | |
81a76618 | 23 | #include "match.h" |
75a75043 BP |
24 | #include "meta-flow.h" |
25 | #include "nx-match.h" | |
f25d0cf3 | 26 | #include "ofp-actions.h" |
90bf1e07 | 27 | #include "ofp-errors.h" |
75a75043 BP |
28 | #include "ofp-util.h" |
29 | #include "ofpbuf.h" | |
30 | #include "openflow/openflow.h" | |
31 | #include "unaligned.h" | |
32 | ||
33 | static ovs_be16 | |
34 | get_be16(const void **pp) | |
35 | { | |
36 | const ovs_be16 *p = *pp; | |
37 | ovs_be16 value = *p; | |
38 | *pp = p + 1; | |
39 | return value; | |
40 | } | |
41 | ||
42 | static ovs_be32 | |
43 | get_be32(const void **pp) | |
44 | { | |
45 | const ovs_be32 *p = *pp; | |
46 | ovs_be32 value = get_unaligned_be32(p); | |
47 | *pp = p + 1; | |
48 | return value; | |
49 | } | |
50 | ||
816fd533 BP |
51 | static void |
52 | get_subfield(int n_bits, const void **p, struct mf_subfield *sf) | |
53 | { | |
54 | sf->field = mf_from_nxm_header(ntohl(get_be32(p))); | |
55 | sf->ofs = ntohs(get_be16(p)); | |
56 | sf->n_bits = n_bits; | |
57 | } | |
58 | ||
75a75043 BP |
59 | static unsigned int |
60 | learn_min_len(uint16_t header) | |
61 | { | |
62 | int n_bits = header & NX_LEARN_N_BITS_MASK; | |
63 | int src_type = header & NX_LEARN_SRC_MASK; | |
64 | int dst_type = header & NX_LEARN_DST_MASK; | |
65 | unsigned int min_len; | |
66 | ||
67 | min_len = 0; | |
68 | if (src_type == NX_LEARN_SRC_FIELD) { | |
69 | min_len += sizeof(ovs_be32); /* src_field */ | |
70 | min_len += sizeof(ovs_be16); /* src_ofs */ | |
71 | } else { | |
72 | min_len += DIV_ROUND_UP(n_bits, 16); | |
73 | } | |
74 | if (dst_type == NX_LEARN_DST_MATCH || | |
75 | dst_type == NX_LEARN_DST_LOAD) { | |
76 | min_len += sizeof(ovs_be32); /* dst_field */ | |
77 | min_len += sizeof(ovs_be16); /* dst_ofs */ | |
78 | } | |
79 | return min_len; | |
80 | } | |
81 | ||
f25d0cf3 BP |
82 | /* Converts 'nal' into a "struct ofpact_learn" and appends that struct to |
83 | * 'ofpacts'. Returns 0 if successful, otherwise an OFPERR_*. */ | |
84 | enum ofperr | |
85 | learn_from_openflow(const struct nx_action_learn *nal, struct ofpbuf *ofpacts) | |
75a75043 | 86 | { |
f25d0cf3 BP |
87 | struct ofpact_learn *learn; |
88 | const void *p, *end; | |
75a75043 | 89 | |
f25d0cf3 | 90 | if (nal->pad) { |
90bf1e07 | 91 | return OFPERR_OFPBAC_BAD_ARGUMENT; |
75a75043 BP |
92 | } |
93 | ||
f25d0cf3 | 94 | learn = ofpact_put_LEARN(ofpacts); |
75a75043 | 95 | |
f25d0cf3 BP |
96 | learn->idle_timeout = ntohs(nal->idle_timeout); |
97 | learn->hard_timeout = ntohs(nal->hard_timeout); | |
98 | learn->priority = ntohs(nal->priority); | |
99 | learn->cookie = ntohll(nal->cookie); | |
f25d0cf3 BP |
100 | learn->table_id = nal->table_id; |
101 | learn->fin_idle_timeout = ntohs(nal->fin_idle_timeout); | |
102 | learn->fin_hard_timeout = ntohs(nal->fin_hard_timeout); | |
75a75043 | 103 | |
0fb88c18 BP |
104 | /* We only support "send-flow-removed" for now. */ |
105 | switch (ntohs(nal->flags)) { | |
106 | case 0: | |
107 | learn->flags = 0; | |
108 | break; | |
109 | case OFPFF_SEND_FLOW_REM: | |
110 | learn->flags = OFPUTIL_FF_SEND_FLOW_REM; | |
111 | break; | |
112 | default: | |
113 | return OFPERR_OFPBAC_BAD_ARGUMENT; | |
114 | } | |
115 | ||
116 | if (learn->table_id == 0xff) { | |
90bf1e07 | 117 | return OFPERR_OFPBAC_BAD_ARGUMENT; |
75a75043 BP |
118 | } |
119 | ||
f25d0cf3 BP |
120 | end = (char *) nal + ntohs(nal->len); |
121 | for (p = nal + 1; p != end; ) { | |
122 | struct ofpact_learn_spec *spec; | |
75a75043 | 123 | uint16_t header = ntohs(get_be16(&p)); |
75a75043 BP |
124 | |
125 | if (!header) { | |
126 | break; | |
127 | } | |
128 | ||
f25d0cf3 BP |
129 | spec = ofpbuf_put_zeros(ofpacts, sizeof *spec); |
130 | learn = ofpacts->l2; | |
131 | learn->n_specs++; | |
75a75043 | 132 | |
f25d0cf3 BP |
133 | spec->src_type = header & NX_LEARN_SRC_MASK; |
134 | spec->dst_type = header & NX_LEARN_DST_MASK; | |
135 | spec->n_bits = header & NX_LEARN_N_BITS_MASK; | |
75a75043 | 136 | |
f25d0cf3 BP |
137 | /* Check for valid src and dst type combination. */ |
138 | if (spec->dst_type == NX_LEARN_DST_MATCH || | |
139 | spec->dst_type == NX_LEARN_DST_LOAD || | |
140 | (spec->dst_type == NX_LEARN_DST_OUTPUT && | |
141 | spec->src_type == NX_LEARN_SRC_FIELD)) { | |
142 | /* OK. */ | |
75a75043 | 143 | } else { |
f25d0cf3 | 144 | return OFPERR_OFPBAC_BAD_ARGUMENT; |
75a75043 BP |
145 | } |
146 | ||
f25d0cf3 BP |
147 | /* Check that the arguments don't overrun the end of the action. */ |
148 | if ((char *) end - (char *) p < learn_min_len(header)) { | |
149 | return OFPERR_OFPBAC_BAD_LEN; | |
150 | } | |
75a75043 | 151 | |
f25d0cf3 BP |
152 | /* Get the source. */ |
153 | if (spec->src_type == NX_LEARN_SRC_FIELD) { | |
154 | get_subfield(spec->n_bits, &p, &spec->src); | |
155 | } else { | |
156 | int p_bytes = 2 * DIV_ROUND_UP(spec->n_bits, 16); | |
75a75043 | 157 | |
f25d0cf3 BP |
158 | bitwise_copy(p, p_bytes, 0, |
159 | &spec->src_imm, sizeof spec->src_imm, 0, | |
160 | spec->n_bits); | |
161 | p = (const uint8_t *) p + p_bytes; | |
162 | } | |
163 | ||
164 | /* Get the destination. */ | |
165 | if (spec->dst_type == NX_LEARN_DST_MATCH || | |
166 | spec->dst_type == NX_LEARN_DST_LOAD) { | |
167 | get_subfield(spec->n_bits, &p, &spec->dst); | |
75a75043 BP |
168 | } |
169 | } | |
f25d0cf3 BP |
170 | ofpact_update_len(ofpacts, &learn->ofpact); |
171 | ||
75a75043 | 172 | if (!is_all_zeros(p, (char *) end - (char *) p)) { |
90bf1e07 | 173 | return OFPERR_OFPBAC_BAD_ARGUMENT; |
75a75043 BP |
174 | } |
175 | ||
176 | return 0; | |
177 | } | |
178 | ||
f25d0cf3 BP |
179 | /* Checks that 'learn' is a valid action on 'flow'. Returns 0 if it is valid, |
180 | * otherwise an OFPERR_*. */ | |
181 | enum ofperr | |
182 | learn_check(const struct ofpact_learn *learn, const struct flow *flow) | |
75a75043 | 183 | { |
f25d0cf3 | 184 | const struct ofpact_learn_spec *spec; |
81a76618 | 185 | struct match match; |
75a75043 | 186 | |
81a76618 | 187 | match_init_catchall(&match); |
f25d0cf3 BP |
188 | for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) { |
189 | enum ofperr error; | |
337b9cec | 190 | |
f25d0cf3 BP |
191 | /* Check the source. */ |
192 | if (spec->src_type == NX_LEARN_SRC_FIELD) { | |
193 | error = mf_check_src(&spec->src, flow); | |
194 | if (error) { | |
195 | return error; | |
196 | } | |
75a75043 BP |
197 | } |
198 | ||
f25d0cf3 BP |
199 | /* Check the destination. */ |
200 | switch (spec->dst_type) { | |
75a75043 | 201 | case NX_LEARN_DST_MATCH: |
81a76618 | 202 | error = mf_check_src(&spec->dst, &match.flow); |
f25d0cf3 BP |
203 | if (error) { |
204 | return error; | |
205 | } | |
206 | ||
81a76618 | 207 | mf_write_subfield(&spec->dst, &spec->src_imm, &match); |
75a75043 BP |
208 | break; |
209 | ||
210 | case NX_LEARN_DST_LOAD: | |
81a76618 | 211 | error = mf_check_dst(&spec->dst, &match.flow); |
f25d0cf3 BP |
212 | if (error) { |
213 | return error; | |
337b9cec | 214 | } |
75a75043 BP |
215 | break; |
216 | ||
217 | case NX_LEARN_DST_OUTPUT: | |
f25d0cf3 | 218 | /* Nothing to do. */ |
75a75043 BP |
219 | break; |
220 | } | |
221 | } | |
f25d0cf3 | 222 | return 0; |
75a75043 BP |
223 | } |
224 | ||
225 | static void | |
226 | put_be16(struct ofpbuf *b, ovs_be16 x) | |
227 | { | |
228 | ofpbuf_put(b, &x, sizeof x); | |
229 | } | |
230 | ||
231 | static void | |
232 | put_be32(struct ofpbuf *b, ovs_be32 x) | |
233 | { | |
234 | ofpbuf_put(b, &x, sizeof x); | |
235 | } | |
236 | ||
237 | static void | |
238 | put_u16(struct ofpbuf *b, uint16_t x) | |
239 | { | |
240 | put_be16(b, htons(x)); | |
241 | } | |
242 | ||
243 | static void | |
244 | put_u32(struct ofpbuf *b, uint32_t x) | |
245 | { | |
246 | put_be32(b, htonl(x)); | |
247 | } | |
248 | ||
f25d0cf3 BP |
249 | /* Converts 'learn' into a "struct nx_action_learn" and appends that action to |
250 | * 'ofpacts'. */ | |
251 | void | |
252 | learn_to_nxast(const struct ofpact_learn *learn, struct ofpbuf *openflow) | |
253 | { | |
254 | const struct ofpact_learn_spec *spec; | |
255 | struct nx_action_learn *nal; | |
256 | size_t start_ofs; | |
257 | ||
258 | start_ofs = openflow->size; | |
259 | nal = ofputil_put_NXAST_LEARN(openflow); | |
260 | nal->idle_timeout = htons(learn->idle_timeout); | |
261 | nal->hard_timeout = htons(learn->hard_timeout); | |
262 | nal->fin_idle_timeout = htons(learn->fin_idle_timeout); | |
263 | nal->fin_hard_timeout = htons(learn->fin_hard_timeout); | |
264 | nal->priority = htons(learn->priority); | |
265 | nal->cookie = htonll(learn->cookie); | |
266 | nal->flags = htons(learn->flags); | |
267 | nal->table_id = learn->table_id; | |
268 | ||
269 | for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) { | |
270 | put_u16(openflow, spec->n_bits | spec->dst_type | spec->src_type); | |
271 | ||
272 | if (spec->src_type == NX_LEARN_SRC_FIELD) { | |
273 | put_u32(openflow, spec->src.field->nxm_header); | |
274 | put_u16(openflow, spec->src.ofs); | |
275 | } else { | |
276 | size_t n_dst_bytes = 2 * DIV_ROUND_UP(spec->n_bits, 16); | |
277 | uint8_t *bits = ofpbuf_put_zeros(openflow, n_dst_bytes); | |
278 | bitwise_copy(&spec->src_imm, sizeof spec->src_imm, 0, | |
279 | bits, n_dst_bytes, 0, | |
280 | spec->n_bits); | |
281 | } | |
282 | ||
283 | if (spec->dst_type == NX_LEARN_DST_MATCH || | |
284 | spec->dst_type == NX_LEARN_DST_LOAD) { | |
285 | put_u32(openflow, spec->dst.field->nxm_header); | |
286 | put_u16(openflow, spec->dst.ofs); | |
287 | } | |
288 | } | |
289 | ||
290 | if ((openflow->size - start_ofs) % 8) { | |
291 | ofpbuf_put_zeros(openflow, 8 - (openflow->size - start_ofs) % 8); | |
292 | } | |
293 | ||
294 | nal = ofpbuf_at_assert(openflow, start_ofs, sizeof *nal); | |
295 | nal->len = htons(openflow->size - start_ofs); | |
296 | } | |
297 | ||
298 | /* Composes 'fm' so that executing it will implement 'learn' given that the | |
299 | * packet being processed has 'flow' as its flow. | |
300 | * | |
301 | * Uses 'ofpacts' to store the flow mod's actions. The caller must initialize | |
302 | * 'ofpacts' and retains ownership of it. 'fm->ofpacts' will point into the | |
303 | * 'ofpacts' buffer. | |
304 | * | |
305 | * The caller has to actually execute 'fm'. */ | |
306 | void | |
307 | learn_execute(const struct ofpact_learn *learn, const struct flow *flow, | |
308 | struct ofputil_flow_mod *fm, struct ofpbuf *ofpacts) | |
309 | { | |
310 | const struct ofpact_learn_spec *spec; | |
75a75043 | 311 | |
81a76618 BP |
312 | match_init_catchall(&fm->match); |
313 | fm->priority = learn->priority; | |
f25d0cf3 BP |
314 | fm->cookie = htonll(0); |
315 | fm->cookie_mask = htonll(0); | |
316 | fm->new_cookie = htonll(learn->cookie); | |
b8266395 | 317 | fm->modify_cookie = fm->new_cookie != OVS_BE64_MAX; |
f25d0cf3 BP |
318 | fm->table_id = learn->table_id; |
319 | fm->command = OFPFC_MODIFY_STRICT; | |
320 | fm->idle_timeout = learn->idle_timeout; | |
321 | fm->hard_timeout = learn->hard_timeout; | |
322 | fm->buffer_id = UINT32_MAX; | |
323 | fm->out_port = OFPP_NONE; | |
324 | fm->flags = learn->flags; | |
325 | fm->ofpacts = NULL; | |
326 | fm->ofpacts_len = 0; | |
75a75043 | 327 | |
f25d0cf3 BP |
328 | if (learn->fin_idle_timeout || learn->fin_hard_timeout) { |
329 | struct ofpact_fin_timeout *oft; | |
330 | ||
331 | oft = ofpact_put_FIN_TIMEOUT(ofpacts); | |
332 | oft->fin_idle_timeout = learn->fin_idle_timeout; | |
333 | oft->fin_hard_timeout = learn->fin_hard_timeout; | |
334 | } | |
335 | ||
336 | for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) { | |
337 | union mf_subvalue value; | |
338 | int chunk, ofs; | |
339 | ||
340 | if (spec->src_type == NX_LEARN_SRC_FIELD) { | |
341 | mf_read_subfield(&spec->src, flow, &value); | |
342 | } else { | |
343 | value = spec->src_imm; | |
344 | } | |
345 | ||
346 | switch (spec->dst_type) { | |
347 | case NX_LEARN_DST_MATCH: | |
81a76618 | 348 | mf_write_subfield(&spec->dst, &value, &fm->match); |
f25d0cf3 BP |
349 | break; |
350 | ||
351 | case NX_LEARN_DST_LOAD: | |
352 | for (ofs = 0; ofs < spec->n_bits; ofs += chunk) { | |
353 | struct ofpact_reg_load *load; | |
f25d0cf3 BP |
354 | |
355 | chunk = MIN(spec->n_bits - ofs, 64); | |
356 | ||
357 | load = ofpact_put_REG_LOAD(ofpacts); | |
358 | load->dst.field = spec->dst.field; | |
359 | load->dst.ofs = spec->dst.ofs + ofs; | |
360 | load->dst.n_bits = chunk; | |
158edc8d BP |
361 | bitwise_copy(&value, sizeof value, ofs, |
362 | &load->subvalue, sizeof load->subvalue, 0, | |
363 | chunk); | |
f25d0cf3 BP |
364 | } |
365 | break; | |
366 | ||
367 | case NX_LEARN_DST_OUTPUT: | |
368 | if (spec->n_bits <= 16 | |
369 | || is_all_zeros(value.u8, sizeof value - 2)) { | |
4e022ec0 | 370 | ofp_port_t port = u16_to_ofp(ntohs(value.be16[7])); |
f25d0cf3 | 371 | |
4e022ec0 | 372 | if (ofp_to_u16(port) < ofp_to_u16(OFPP_MAX) |
f25d0cf3 BP |
373 | || port == OFPP_IN_PORT |
374 | || port == OFPP_FLOOD | |
375 | || port == OFPP_LOCAL | |
376 | || port == OFPP_ALL) { | |
377 | ofpact_put_OUTPUT(ofpacts)->port = port; | |
378 | } | |
379 | } | |
380 | break; | |
381 | } | |
382 | } | |
383 | ofpact_pad(ofpacts); | |
384 | ||
385 | fm->ofpacts = ofpacts->data; | |
386 | fm->ofpacts_len = ofpacts->size; | |
387 | } | |
75a75043 | 388 | |
bcd2633a JP |
389 | /* Perform a bitwise-OR on 'wc''s fields that are relevant as sources in |
390 | * the learn action 'learn'. */ | |
391 | void | |
392 | learn_mask(const struct ofpact_learn *learn, struct flow_wildcards *wc) | |
393 | { | |
394 | const struct ofpact_learn_spec *spec; | |
395 | union mf_subvalue value; | |
396 | ||
397 | memset(&value, 0xff, sizeof value); | |
398 | for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) { | |
399 | if (spec->src_type == NX_LEARN_SRC_FIELD) { | |
400 | mf_write_subfield_flow(&spec->src, &value, &wc->masks); | |
401 | } | |
402 | } | |
403 | } | |
404 | ||
bdda5aca BP |
405 | /* Returns NULL if successful, otherwise a malloc()'d string describing the |
406 | * error. The caller is responsible for freeing the returned string. */ | |
407 | static char * WARN_UNUSED_RESULT | |
f25d0cf3 | 408 | learn_parse_load_immediate(const char *s, struct ofpact_learn_spec *spec) |
3d792b70 BP |
409 | { |
410 | const char *full_s = s; | |
411 | const char *arrow = strstr(s, "->"); | |
412 | struct mf_subfield dst; | |
413 | union mf_subvalue imm; | |
bdda5aca | 414 | char *error; |
3d792b70 BP |
415 | |
416 | memset(&imm, 0, sizeof imm); | |
417 | if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X') && arrow) { | |
418 | const char *in = arrow - 1; | |
419 | uint8_t *out = imm.u8 + sizeof imm.u8 - 1; | |
420 | int n = arrow - (s + 2); | |
421 | int i; | |
422 | ||
423 | for (i = 0; i < n; i++) { | |
424 | int hexit = hexit_value(in[-i]); | |
425 | if (hexit < 0) { | |
bdda5aca | 426 | return xasprintf("%s: bad hex digit in value", full_s); |
3d792b70 BP |
427 | } |
428 | out[-(i / 2)] |= i % 2 ? hexit << 4 : hexit; | |
429 | } | |
430 | s = arrow; | |
431 | } else { | |
432 | imm.be64[1] = htonll(strtoull(s, (char **) &s, 0)); | |
433 | } | |
434 | ||
435 | if (strncmp(s, "->", 2)) { | |
bdda5aca | 436 | return xasprintf("%s: missing `->' following value", full_s); |
3d792b70 BP |
437 | } |
438 | s += 2; | |
439 | ||
bdda5aca BP |
440 | error = mf_parse_subfield(&dst, s); |
441 | if (error) { | |
442 | return error; | |
3d792b70 BP |
443 | } |
444 | ||
445 | if (!bitwise_is_all_zeros(&imm, sizeof imm, dst.n_bits, | |
446 | (8 * sizeof imm) - dst.n_bits)) { | |
bdda5aca BP |
447 | return xasprintf("%s: value does not fit into %u bits", |
448 | full_s, dst.n_bits); | |
3d792b70 BP |
449 | } |
450 | ||
451 | spec->n_bits = dst.n_bits; | |
452 | spec->src_type = NX_LEARN_SRC_IMMEDIATE; | |
453 | spec->src_imm = imm; | |
454 | spec->dst_type = NX_LEARN_DST_LOAD; | |
455 | spec->dst = dst; | |
bdda5aca | 456 | return NULL; |
3d792b70 BP |
457 | } |
458 | ||
bdda5aca BP |
459 | /* Returns NULL if successful, otherwise a malloc()'d string describing the |
460 | * error. The caller is responsible for freeing the returned string. */ | |
461 | static char * WARN_UNUSED_RESULT | |
75a75043 | 462 | learn_parse_spec(const char *orig, char *name, char *value, |
f25d0cf3 | 463 | struct ofpact_learn_spec *spec) |
75a75043 BP |
464 | { |
465 | if (mf_from_name(name)) { | |
466 | const struct mf_field *dst = mf_from_name(name); | |
467 | union mf_value imm; | |
468 | char *error; | |
469 | ||
470 | error = mf_parse_value(dst, value, &imm); | |
471 | if (error) { | |
bdda5aca | 472 | return error; |
75a75043 BP |
473 | } |
474 | ||
475 | spec->n_bits = dst->n_bits; | |
476 | spec->src_type = NX_LEARN_SRC_IMMEDIATE; | |
3d792b70 BP |
477 | memset(&spec->src_imm, 0, sizeof spec->src_imm); |
478 | memcpy(&spec->src_imm.u8[sizeof spec->src_imm - dst->n_bytes], | |
479 | &imm, dst->n_bytes); | |
75a75043 | 480 | spec->dst_type = NX_LEARN_DST_MATCH; |
816fd533 BP |
481 | spec->dst.field = dst; |
482 | spec->dst.ofs = 0; | |
483 | spec->dst.n_bits = dst->n_bits; | |
75a75043 | 484 | } else if (strchr(name, '[')) { |
75a75043 | 485 | /* Parse destination and check prerequisites. */ |
bdda5aca BP |
486 | char *error; |
487 | ||
488 | error = mf_parse_subfield(&spec->dst, name); | |
489 | if (error) { | |
490 | return error; | |
75a75043 BP |
491 | } |
492 | ||
493 | /* Parse source and check prerequisites. */ | |
494 | if (value[0] != '\0') { | |
bdda5aca BP |
495 | error = mf_parse_subfield(&spec->src, value); |
496 | if (error) { | |
497 | return error; | |
75a75043 | 498 | } |
816fd533 | 499 | if (spec->src.n_bits != spec->dst.n_bits) { |
bdda5aca BP |
500 | return xasprintf("%s: bit widths of %s (%u) and %s (%u) " |
501 | "differ", orig, name, spec->src.n_bits, value, | |
502 | spec->dst.n_bits); | |
75a75043 BP |
503 | } |
504 | } else { | |
816fd533 | 505 | spec->src = spec->dst; |
75a75043 BP |
506 | } |
507 | ||
816fd533 | 508 | spec->n_bits = spec->src.n_bits; |
75a75043 | 509 | spec->src_type = NX_LEARN_SRC_FIELD; |
75a75043 | 510 | spec->dst_type = NX_LEARN_DST_MATCH; |
75a75043 BP |
511 | } else if (!strcmp(name, "load")) { |
512 | if (value[strcspn(value, "[-")] == '-') { | |
bdda5aca BP |
513 | char *error = learn_parse_load_immediate(value, spec); |
514 | if (error) { | |
515 | return error; | |
516 | } | |
75a75043 | 517 | } else { |
f25d0cf3 | 518 | struct ofpact_reg_move move; |
bdda5aca | 519 | char *error; |
75a75043 | 520 | |
bdda5aca BP |
521 | error = nxm_parse_reg_move(&move, value); |
522 | if (error) { | |
523 | return error; | |
524 | } | |
75a75043 | 525 | |
f25d0cf3 | 526 | spec->n_bits = move.src.n_bits; |
75a75043 | 527 | spec->src_type = NX_LEARN_SRC_FIELD; |
f25d0cf3 | 528 | spec->src = move.src; |
75a75043 | 529 | spec->dst_type = NX_LEARN_DST_LOAD; |
f25d0cf3 | 530 | spec->dst = move.dst; |
75a75043 BP |
531 | } |
532 | } else if (!strcmp(name, "output")) { | |
bdda5aca BP |
533 | char *error = mf_parse_subfield(&spec->src, value); |
534 | if (error) { | |
535 | return error; | |
75a75043 BP |
536 | } |
537 | ||
816fd533 | 538 | spec->n_bits = spec->src.n_bits; |
75a75043 | 539 | spec->src_type = NX_LEARN_SRC_FIELD; |
75a75043 | 540 | spec->dst_type = NX_LEARN_DST_OUTPUT; |
75a75043 | 541 | } else { |
bdda5aca | 542 | return xasprintf("%s: unknown keyword %s", orig, name); |
75a75043 | 543 | } |
bdda5aca BP |
544 | |
545 | return NULL; | |
75a75043 BP |
546 | } |
547 | ||
bdda5aca BP |
548 | /* Returns NULL if successful, otherwise a malloc()'d string describing the |
549 | * error. The caller is responsible for freeing the returned string. */ | |
550 | static char * WARN_UNUSED_RESULT | |
551 | learn_parse__(char *orig, char *arg, struct ofpbuf *ofpacts) | |
75a75043 | 552 | { |
f25d0cf3 | 553 | struct ofpact_learn *learn; |
81a76618 | 554 | struct match match; |
bdda5aca | 555 | char *name, *value; |
75a75043 | 556 | |
f25d0cf3 BP |
557 | learn = ofpact_put_LEARN(ofpacts); |
558 | learn->idle_timeout = OFP_FLOW_PERMANENT; | |
559 | learn->hard_timeout = OFP_FLOW_PERMANENT; | |
560 | learn->priority = OFP_DEFAULT_PRIORITY; | |
75a75043 BP |
561 | learn->table_id = 1; |
562 | ||
81a76618 | 563 | match_init_catchall(&match); |
75a75043 | 564 | while (ofputil_parse_key_value(&arg, &name, &value)) { |
75a75043 BP |
565 | if (!strcmp(name, "table")) { |
566 | learn->table_id = atoi(value); | |
567 | if (learn->table_id == 255) { | |
bdda5aca BP |
568 | return xasprintf("%s: table id 255 not valid for `learn' " |
569 | "action", orig); | |
75a75043 BP |
570 | } |
571 | } else if (!strcmp(name, "priority")) { | |
f25d0cf3 | 572 | learn->priority = atoi(value); |
75a75043 | 573 | } else if (!strcmp(name, "idle_timeout")) { |
f25d0cf3 | 574 | learn->idle_timeout = atoi(value); |
75a75043 | 575 | } else if (!strcmp(name, "hard_timeout")) { |
f25d0cf3 | 576 | learn->hard_timeout = atoi(value); |
0e553d9c | 577 | } else if (!strcmp(name, "fin_idle_timeout")) { |
f25d0cf3 | 578 | learn->fin_idle_timeout = atoi(value); |
0e553d9c | 579 | } else if (!strcmp(name, "fin_hard_timeout")) { |
f25d0cf3 | 580 | learn->fin_hard_timeout = atoi(value); |
75a75043 | 581 | } else if (!strcmp(name, "cookie")) { |
f25d0cf3 | 582 | learn->cookie = strtoull(value, NULL, 0); |
75a75043 | 583 | } else { |
f25d0cf3 | 584 | struct ofpact_learn_spec *spec; |
bdda5aca | 585 | char *error; |
f25d0cf3 BP |
586 | |
587 | spec = ofpbuf_put_zeros(ofpacts, sizeof *spec); | |
588 | learn = ofpacts->l2; | |
589 | learn->n_specs++; | |
75a75043 | 590 | |
bdda5aca BP |
591 | error = learn_parse_spec(orig, name, value, spec); |
592 | if (error) { | |
593 | return error; | |
594 | } | |
75a75043 | 595 | |
81a76618 | 596 | /* Update 'match' to allow for satisfying destination |
75a75043 | 597 | * prerequisites. */ |
f25d0cf3 BP |
598 | if (spec->src_type == NX_LEARN_SRC_IMMEDIATE |
599 | && spec->dst_type == NX_LEARN_DST_MATCH) { | |
81a76618 | 600 | mf_write_subfield(&spec->dst, &spec->src_imm, &match); |
75a75043 BP |
601 | } |
602 | } | |
603 | } | |
f25d0cf3 | 604 | ofpact_update_len(ofpacts, &learn->ofpact); |
4cb3fde7 | 605 | |
bdda5aca BP |
606 | return NULL; |
607 | } | |
608 | ||
609 | /* Parses 'arg' as a set of arguments to the "learn" action and appends a | |
610 | * matching OFPACT_LEARN action to 'ofpacts'. ovs-ofctl(8) describes the | |
611 | * format parsed. | |
612 | * | |
613 | * Returns NULL if successful, otherwise a malloc()'d string describing the | |
614 | * error. The caller is responsible for freeing the returned string. | |
615 | * | |
616 | * If 'flow' is nonnull, then it should be the flow from a struct match that is | |
617 | * the matching rule for the learning action. This helps to better validate | |
618 | * the action's arguments. | |
619 | * | |
620 | * Modifies 'arg'. */ | |
621 | char * WARN_UNUSED_RESULT | |
622 | learn_parse(char *arg, struct ofpbuf *ofpacts) | |
623 | { | |
624 | char *orig = xstrdup(arg); | |
625 | char *error = learn_parse__(orig, arg, ofpacts); | |
4cb3fde7 | 626 | free(orig); |
bdda5aca | 627 | return error; |
75a75043 BP |
628 | } |
629 | ||
f25d0cf3 BP |
630 | /* Appends a description of 'learn' to 's', in the format that ovs-ofctl(8) |
631 | * describes. */ | |
75a75043 | 632 | void |
f25d0cf3 | 633 | learn_format(const struct ofpact_learn *learn, struct ds *s) |
75a75043 | 634 | { |
f25d0cf3 | 635 | const struct ofpact_learn_spec *spec; |
81a76618 | 636 | struct match match; |
75a75043 | 637 | |
81a76618 | 638 | match_init_catchall(&match); |
75a75043 BP |
639 | |
640 | ds_put_format(s, "learn(table=%"PRIu8, learn->table_id); | |
f25d0cf3 BP |
641 | if (learn->idle_timeout != OFP_FLOW_PERMANENT) { |
642 | ds_put_format(s, ",idle_timeout=%"PRIu16, learn->idle_timeout); | |
75a75043 | 643 | } |
f25d0cf3 BP |
644 | if (learn->hard_timeout != OFP_FLOW_PERMANENT) { |
645 | ds_put_format(s, ",hard_timeout=%"PRIu16, learn->hard_timeout); | |
75a75043 | 646 | } |
0e553d9c | 647 | if (learn->fin_idle_timeout) { |
f25d0cf3 | 648 | ds_put_format(s, ",fin_idle_timeout=%"PRIu16, learn->fin_idle_timeout); |
0e553d9c BP |
649 | } |
650 | if (learn->fin_hard_timeout) { | |
f25d0cf3 | 651 | ds_put_format(s, ",fin_hard_timeout=%"PRIu16, learn->fin_hard_timeout); |
0e553d9c | 652 | } |
f25d0cf3 BP |
653 | if (learn->priority != OFP_DEFAULT_PRIORITY) { |
654 | ds_put_format(s, ",priority=%"PRIu16, learn->priority); | |
75a75043 | 655 | } |
f25d0cf3 | 656 | if (learn->flags & OFPFF_SEND_FLOW_REM) { |
75a75043 BP |
657 | ds_put_cstr(s, ",OFPFF_SEND_FLOW_REM"); |
658 | } | |
f25d0cf3 BP |
659 | if (learn->cookie != 0) { |
660 | ds_put_format(s, ",cookie=%#"PRIx64, learn->cookie); | |
75a75043 | 661 | } |
75a75043 | 662 | |
f25d0cf3 | 663 | for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) { |
75a75043 BP |
664 | ds_put_char(s, ','); |
665 | ||
f25d0cf3 | 666 | switch (spec->src_type | spec->dst_type) { |
75a75043 | 667 | case NX_LEARN_SRC_IMMEDIATE | NX_LEARN_DST_MATCH: |
f25d0cf3 BP |
668 | if (spec->dst.ofs == 0 |
669 | && spec->dst.n_bits == spec->dst.field->n_bits) { | |
75a75043 | 670 | union mf_value value; |
f299fbeb | 671 | |
75a75043 | 672 | memset(&value, 0, sizeof value); |
f25d0cf3 BP |
673 | bitwise_copy(&spec->src_imm, sizeof spec->src_imm, 0, |
674 | &value, spec->dst.field->n_bytes, 0, | |
675 | spec->dst.field->n_bits); | |
676 | ds_put_format(s, "%s=", spec->dst.field->name); | |
677 | mf_format(spec->dst.field, &value, NULL, s); | |
75a75043 | 678 | } else { |
f25d0cf3 BP |
679 | mf_format_subfield(&spec->dst, s); |
680 | ds_put_char(s, '='); | |
9bab681f | 681 | mf_format_subvalue(&spec->src_imm, s); |
75a75043 BP |
682 | } |
683 | break; | |
684 | ||
685 | case NX_LEARN_SRC_FIELD | NX_LEARN_DST_MATCH: | |
f25d0cf3 BP |
686 | mf_format_subfield(&spec->dst, s); |
687 | if (spec->src.field != spec->dst.field || | |
688 | spec->src.ofs != spec->dst.ofs) { | |
75a75043 | 689 | ds_put_char(s, '='); |
f25d0cf3 | 690 | mf_format_subfield(&spec->src, s); |
75a75043 BP |
691 | } |
692 | break; | |
693 | ||
694 | case NX_LEARN_SRC_IMMEDIATE | NX_LEARN_DST_LOAD: | |
f25d0cf3 | 695 | ds_put_format(s, "load:"); |
9bab681f | 696 | mf_format_subvalue(&spec->src_imm, s); |
75a75043 | 697 | ds_put_cstr(s, "->"); |
f25d0cf3 | 698 | mf_format_subfield(&spec->dst, s); |
75a75043 BP |
699 | break; |
700 | ||
701 | case NX_LEARN_SRC_FIELD | NX_LEARN_DST_LOAD: | |
702 | ds_put_cstr(s, "load:"); | |
f25d0cf3 | 703 | mf_format_subfield(&spec->src, s); |
75a75043 | 704 | ds_put_cstr(s, "->"); |
f25d0cf3 | 705 | mf_format_subfield(&spec->dst, s); |
75a75043 BP |
706 | break; |
707 | ||
708 | case NX_LEARN_SRC_FIELD | NX_LEARN_DST_OUTPUT: | |
709 | ds_put_cstr(s, "output:"); | |
f25d0cf3 | 710 | mf_format_subfield(&spec->src, s); |
75a75043 BP |
711 | break; |
712 | } | |
713 | } | |
75a75043 BP |
714 | ds_put_char(s, ')'); |
715 | } |