/src/openvswitch/lib/learn.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 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 | | |
19 | | #include "learn.h" |
20 | | |
21 | | #include "byte-order.h" |
22 | | #include "colors.h" |
23 | | #include "nx-match.h" |
24 | | #include "openflow/openflow.h" |
25 | | #include "openvswitch/dynamic-string.h" |
26 | | #include "openvswitch/match.h" |
27 | | #include "openvswitch/meta-flow.h" |
28 | | #include "openvswitch/ofp-actions.h" |
29 | | #include "openvswitch/ofp-errors.h" |
30 | | #include "openvswitch/ofp-flow.h" |
31 | | #include "openvswitch/ofp-parse.h" |
32 | | #include "openvswitch/ofp-table.h" |
33 | | #include "openvswitch/ofpbuf.h" |
34 | | #include "vl-mff-map.h" |
35 | | #include "unaligned.h" |
36 | | |
37 | | |
38 | | /* Checks that 'learn' is a valid action on 'flow'. Returns 0 if it is valid, |
39 | | * otherwise an OFPERR_*. */ |
40 | | enum ofperr |
41 | | learn_check(const struct ofpact_learn *learn, const struct match *src_match) |
42 | 0 | { |
43 | 0 | const struct ofpact_learn_spec *spec; |
44 | 0 | struct match dst_match; |
45 | |
|
46 | 0 | match_init_catchall(&dst_match); |
47 | 0 | OFPACT_LEARN_SPEC_FOR_EACH (spec, learn) { |
48 | 0 | enum ofperr error; |
49 | | |
50 | | /* Check the source. */ |
51 | 0 | if (spec->src_type == NX_LEARN_SRC_FIELD) { |
52 | 0 | error = mf_check_src(&spec->src, src_match); |
53 | 0 | if (error) { |
54 | 0 | return error; |
55 | 0 | } |
56 | 0 | } |
57 | | |
58 | | /* Check the destination. */ |
59 | 0 | switch (spec->dst_type) { |
60 | 0 | case NX_LEARN_DST_MATCH: |
61 | 0 | error = mf_check_src(&spec->dst, &dst_match); |
62 | 0 | if (error) { |
63 | 0 | return error; |
64 | 0 | } |
65 | 0 | if (spec->src_type & NX_LEARN_SRC_IMMEDIATE) { |
66 | 0 | mf_write_subfield_value(&spec->dst, |
67 | 0 | ofpact_learn_spec_imm(spec), |
68 | 0 | &dst_match); |
69 | 0 | } |
70 | 0 | break; |
71 | | |
72 | 0 | case NX_LEARN_DST_LOAD: |
73 | 0 | error = mf_check_dst(&spec->dst, &dst_match); |
74 | 0 | if (error) { |
75 | 0 | return error; |
76 | 0 | } |
77 | 0 | break; |
78 | | |
79 | 0 | case NX_LEARN_DST_OUTPUT: |
80 | | /* Nothing to do. */ |
81 | 0 | break; |
82 | 0 | } |
83 | 0 | } |
84 | 0 | return 0; |
85 | 0 | } |
86 | | |
87 | | /* Composes 'fm' so that executing it will implement 'learn' given that the |
88 | | * packet being processed has 'flow' as its flow. |
89 | | * |
90 | | * Uses 'ofpacts' to store the flow mod's actions. The caller must initialize |
91 | | * 'ofpacts' and retains ownership of it. 'fm->ofpacts' will point into the |
92 | | * 'ofpacts' buffer. |
93 | | * |
94 | | * The caller must eventually destroy fm->match. |
95 | | * |
96 | | * The caller has to actually execute 'fm'. */ |
97 | | void |
98 | | learn_execute(const struct ofpact_learn *learn, const struct flow *flow, |
99 | | struct ofputil_flow_mod *fm, struct ofpbuf *ofpacts) |
100 | 0 | { |
101 | 0 | const struct ofpact_learn_spec *spec; |
102 | 0 | struct match match; |
103 | |
|
104 | 0 | match_init_catchall(&match); |
105 | 0 | fm->priority = learn->priority; |
106 | 0 | fm->cookie = htonll(0); |
107 | 0 | fm->cookie_mask = htonll(0); |
108 | 0 | fm->new_cookie = learn->cookie; |
109 | 0 | fm->modify_cookie = fm->new_cookie != OVS_BE64_MAX; |
110 | 0 | fm->table_id = learn->table_id; |
111 | 0 | fm->command = OFPFC_MODIFY_STRICT; |
112 | 0 | fm->idle_timeout = learn->idle_timeout; |
113 | 0 | fm->hard_timeout = learn->hard_timeout; |
114 | 0 | fm->importance = 0; |
115 | 0 | fm->buffer_id = UINT32_MAX; |
116 | 0 | fm->out_port = OFPP_NONE; |
117 | 0 | fm->ofpacts_tlv_bitmap = 0; |
118 | 0 | fm->flags = 0; |
119 | 0 | if (learn->flags & NX_LEARN_F_SEND_FLOW_REM) { |
120 | 0 | fm->flags |= OFPUTIL_FF_SEND_FLOW_REM; |
121 | 0 | } |
122 | 0 | fm->ofpacts = NULL; |
123 | 0 | fm->ofpacts_len = 0; |
124 | |
|
125 | 0 | if (learn->fin_idle_timeout || learn->fin_hard_timeout) { |
126 | 0 | struct ofpact_fin_timeout *oft; |
127 | |
|
128 | 0 | oft = ofpact_put_FIN_TIMEOUT(ofpacts); |
129 | 0 | oft->fin_idle_timeout = learn->fin_idle_timeout; |
130 | 0 | oft->fin_hard_timeout = learn->fin_hard_timeout; |
131 | 0 | } |
132 | |
|
133 | 0 | OFPACT_LEARN_SPEC_FOR_EACH (spec, learn) { |
134 | 0 | struct ofpact_set_field *sf; |
135 | 0 | union mf_subvalue value; |
136 | |
|
137 | 0 | if (spec->src_type == NX_LEARN_SRC_FIELD) { |
138 | 0 | mf_read_subfield(&spec->src, flow, &value); |
139 | 0 | } else { |
140 | 0 | mf_subvalue_from_value(&spec->dst, &value, |
141 | 0 | ofpact_learn_spec_imm(spec)); |
142 | 0 | } |
143 | |
|
144 | 0 | switch (spec->dst_type) { |
145 | 0 | case NX_LEARN_DST_MATCH: |
146 | 0 | mf_write_subfield(&spec->dst, &value, &match); |
147 | 0 | match_add_ethernet_prereq(&match, spec->dst.field); |
148 | 0 | mf_vl_mff_set_tlv_bitmap( |
149 | 0 | spec->dst.field, &match.flow.tunnel.metadata.present.map); |
150 | 0 | break; |
151 | | |
152 | 0 | case NX_LEARN_DST_LOAD: |
153 | 0 | sf = ofpact_put_reg_load(ofpacts, spec->dst.field, NULL, NULL); |
154 | 0 | bitwise_copy(&value, sizeof value, 0, |
155 | 0 | sf->value, spec->dst.field->n_bytes, spec->dst.ofs, |
156 | 0 | spec->n_bits); |
157 | 0 | bitwise_one(ofpact_set_field_mask(sf), spec->dst.field->n_bytes, |
158 | 0 | spec->dst.ofs, spec->n_bits); |
159 | 0 | mf_vl_mff_set_tlv_bitmap(spec->dst.field, &fm->ofpacts_tlv_bitmap); |
160 | 0 | break; |
161 | | |
162 | 0 | case NX_LEARN_DST_OUTPUT: |
163 | 0 | if (spec->n_bits <= 16 |
164 | 0 | || is_all_zeros(value.u8, sizeof value - 2)) { |
165 | 0 | ofp_port_t port = u16_to_ofp(ntohll(value.integer)); |
166 | |
|
167 | 0 | if (ofp_to_u16(port) < ofp_to_u16(OFPP_MAX) |
168 | 0 | || port == OFPP_IN_PORT |
169 | 0 | || port == OFPP_FLOOD |
170 | 0 | || port == OFPP_LOCAL |
171 | 0 | || port == OFPP_ALL) { |
172 | 0 | ofpact_put_OUTPUT(ofpacts)->port = port; |
173 | 0 | } |
174 | 0 | } |
175 | 0 | break; |
176 | 0 | } |
177 | 0 | } |
178 | | |
179 | 0 | minimatch_init(&fm->match, &match); |
180 | 0 | fm->ofpacts = ofpacts->data; |
181 | 0 | fm->ofpacts_len = ofpacts->size; |
182 | 0 | } |
183 | | |
184 | | /* Perform a bitwise-OR on 'wc''s fields that are relevant as sources in |
185 | | * the learn action 'learn'. */ |
186 | | void |
187 | | learn_mask(const struct ofpact_learn *learn, struct flow_wildcards *wc) |
188 | 0 | { |
189 | 0 | const struct ofpact_learn_spec *spec; |
190 | 0 | union mf_subvalue value; |
191 | |
|
192 | 0 | memset(&value, 0xff, sizeof value); |
193 | 0 | OFPACT_LEARN_SPEC_FOR_EACH (spec, learn) { |
194 | 0 | if (spec->src_type == NX_LEARN_SRC_FIELD) { |
195 | 0 | mf_write_subfield_flow(&spec->src, &value, &wc->masks); |
196 | 0 | } |
197 | 0 | } |
198 | 0 | } |
199 | | |
200 | | /* Returns NULL if successful, otherwise a malloc()'d string describing the |
201 | | * error. The caller is responsible for freeing the returned string. */ |
202 | | static char * OVS_WARN_UNUSED_RESULT |
203 | | learn_parse_load_immediate(union mf_subvalue *imm, const char *s, |
204 | | const char *full_s, struct ofpact_learn_spec *spec, |
205 | | struct ofpbuf *ofpacts) |
206 | 0 | { |
207 | 0 | struct mf_subfield dst; |
208 | 0 | char *error; |
209 | |
|
210 | 0 | error = mf_parse_subfield(&dst, s); |
211 | 0 | if (error) { |
212 | 0 | return error; |
213 | 0 | } |
214 | 0 | if (!mf_nxm_header(dst.field->id)) { |
215 | 0 | return xasprintf("%s: experimenter OXM field '%s' not supported", |
216 | 0 | full_s, s); |
217 | 0 | } |
218 | | |
219 | 0 | if (!bitwise_is_all_zeros(imm, sizeof *imm, dst.n_bits, |
220 | 0 | (8 * sizeof *imm) - dst.n_bits)) { |
221 | 0 | return xasprintf("%s: value does not fit into %u bits", |
222 | 0 | full_s, dst.n_bits); |
223 | 0 | } |
224 | | |
225 | 0 | spec->n_bits = dst.n_bits; |
226 | 0 | spec->src_type = NX_LEARN_SRC_IMMEDIATE; |
227 | 0 | spec->dst_type = NX_LEARN_DST_LOAD; |
228 | 0 | spec->dst = dst; |
229 | | |
230 | | /* Push value last, as this may reallocate 'spec'! */ |
231 | 0 | unsigned int n_bytes = DIV_ROUND_UP(dst.n_bits, 8); |
232 | 0 | uint8_t *src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(n_bytes)); |
233 | 0 | memcpy(src_imm, &imm->u8[sizeof imm->u8 - n_bytes], n_bytes); |
234 | |
|
235 | 0 | return NULL; |
236 | 0 | } |
237 | | |
238 | | /* Returns NULL if successful, otherwise a malloc()'d string describing the |
239 | | * error. The caller is responsible for freeing the returned string. */ |
240 | | static char * OVS_WARN_UNUSED_RESULT |
241 | | learn_parse_spec(const char *orig, char *name, char *value, |
242 | | const struct ofputil_port_map *port_map, |
243 | | struct ofpact_learn_spec *spec, |
244 | | struct ofpbuf *ofpacts) |
245 | 0 | { |
246 | | /* Parse destination and check prerequisites. */ |
247 | 0 | struct mf_subfield dst; |
248 | |
|
249 | 0 | char *error = mf_parse_subfield(&dst, name); |
250 | 0 | bool parse_error = error != NULL; |
251 | 0 | free(error); |
252 | |
|
253 | 0 | if (!parse_error) { |
254 | 0 | if (!mf_nxm_header(dst.field->id)) { |
255 | 0 | return xasprintf("%s: experimenter OXM field '%s' not supported", |
256 | 0 | orig, name); |
257 | 0 | } |
258 | 0 | spec->dst = dst; |
259 | 0 | spec->n_bits = dst.n_bits; |
260 | 0 | spec->dst_type = NX_LEARN_DST_MATCH; |
261 | | |
262 | | /* Parse source and check prerequisites. */ |
263 | 0 | if (value[0] != '\0') { |
264 | 0 | struct mf_subfield src; |
265 | 0 | error = mf_parse_subfield(&src, value); |
266 | 0 | if (error) { |
267 | 0 | union mf_value imm; |
268 | 0 | char *imm_error = NULL; |
269 | | |
270 | | /* Try an immediate value. */ |
271 | 0 | if (dst.ofs == 0 && dst.n_bits == dst.field->n_bits) { |
272 | | /* Full field value. */ |
273 | 0 | imm_error = mf_parse_value(dst.field, value, port_map, |
274 | 0 | &imm); |
275 | 0 | } else { |
276 | 0 | char *tail; |
277 | | /* Partial field value. */ |
278 | 0 | if (parse_int_string(value, imm.b, |
279 | 0 | dst.field->n_bytes, &tail) |
280 | 0 | || *tail != 0) { |
281 | 0 | imm_error = xasprintf("%s: cannot parse integer value", orig); |
282 | 0 | } |
283 | |
|
284 | 0 | if (!imm_error && |
285 | 0 | !bitwise_is_all_zeros(imm.b, dst.field->n_bytes, |
286 | 0 | dst.n_bits, |
287 | 0 | dst.field->n_bytes * 8 - dst.n_bits)) { |
288 | 0 | struct ds ds; |
289 | |
|
290 | 0 | ds_init(&ds); |
291 | 0 | mf_format(dst.field, &imm, NULL, NULL, &ds); |
292 | 0 | imm_error = xasprintf("%s: value %s does not fit into %d bits", |
293 | 0 | orig, ds_cstr(&ds), dst.n_bits); |
294 | 0 | ds_destroy(&ds); |
295 | 0 | } |
296 | 0 | } |
297 | 0 | if (imm_error) { |
298 | 0 | char *err = xasprintf("%s: %s value %s cannot be parsed as a subfield (%s) or an immediate value (%s)", |
299 | 0 | orig, name, value, error, imm_error); |
300 | 0 | free(error); |
301 | 0 | free(imm_error); |
302 | 0 | return err; |
303 | 0 | } |
304 | | |
305 | 0 | spec->src_type = NX_LEARN_SRC_IMMEDIATE; |
306 | | |
307 | | /* Push value last, as this may reallocate 'spec'! */ |
308 | 0 | unsigned int imm_bytes = DIV_ROUND_UP(dst.n_bits, 8); |
309 | 0 | uint8_t *src_imm = ofpbuf_put_zeros(ofpacts, |
310 | 0 | OFPACT_ALIGN(imm_bytes)); |
311 | |
|
312 | 0 | memcpy(src_imm, &imm.b[dst.field->n_bytes - imm_bytes], |
313 | 0 | imm_bytes); |
314 | |
|
315 | 0 | free(error); |
316 | 0 | return NULL; |
317 | 0 | } |
318 | 0 | spec->src = src; |
319 | 0 | if (spec->src.n_bits != spec->dst.n_bits) { |
320 | 0 | return xasprintf("%s: bit widths of %s (%u) and %s (%u) " |
321 | 0 | "differ", orig, name, spec->src.n_bits, value, |
322 | 0 | spec->dst.n_bits); |
323 | 0 | } |
324 | 0 | } else { |
325 | 0 | spec->src = spec->dst; |
326 | 0 | } |
327 | | |
328 | 0 | spec->src_type = NX_LEARN_SRC_FIELD; |
329 | 0 | } else if (!strcmp(name, "load")) { |
330 | 0 | union mf_subvalue imm; |
331 | 0 | char *tail; |
332 | 0 | char *dst_value = strstr(value, "->"); |
333 | |
|
334 | 0 | if (dst_value == value) { |
335 | 0 | return xasprintf("%s: missing source before `->' in `%s'", name, |
336 | 0 | value); |
337 | 0 | } |
338 | 0 | if (!dst_value) { |
339 | 0 | return xasprintf("%s: missing `->' in `%s'", name, value); |
340 | 0 | } |
341 | | |
342 | 0 | if (!parse_int_string(value, imm.u8, sizeof imm.u8, (char **) &tail) |
343 | 0 | && tail != value) { |
344 | 0 | if (tail != dst_value) { |
345 | 0 | return xasprintf("%s: garbage before `->' in `%s'", |
346 | 0 | name, value); |
347 | 0 | } |
348 | | |
349 | 0 | error = learn_parse_load_immediate(&imm, dst_value + 2, value, spec, |
350 | 0 | ofpacts); |
351 | 0 | if (error) { |
352 | 0 | return error; |
353 | 0 | } |
354 | 0 | } else { |
355 | 0 | struct ofpact_reg_move move; |
356 | |
|
357 | 0 | error = nxm_parse_reg_move(&move, value); |
358 | 0 | if (error) { |
359 | 0 | return error; |
360 | 0 | } |
361 | | |
362 | 0 | spec->n_bits = move.src.n_bits; |
363 | 0 | spec->src_type = NX_LEARN_SRC_FIELD; |
364 | 0 | spec->src = move.src; |
365 | 0 | spec->dst_type = NX_LEARN_DST_LOAD; |
366 | 0 | spec->dst = move.dst; |
367 | 0 | } |
368 | 0 | } else if (!strcmp(name, "output")) { |
369 | 0 | error = mf_parse_subfield(&spec->src, value); |
370 | 0 | if (error) { |
371 | 0 | return error; |
372 | 0 | } |
373 | | |
374 | 0 | spec->n_bits = spec->src.n_bits; |
375 | 0 | spec->src_type = NX_LEARN_SRC_FIELD; |
376 | 0 | spec->dst_type = NX_LEARN_DST_OUTPUT; |
377 | 0 | } else { |
378 | 0 | return xasprintf("%s: unknown keyword %s", orig, name); |
379 | 0 | } |
380 | | |
381 | 0 | return NULL; |
382 | 0 | } |
383 | | |
384 | | /* Returns NULL if successful, otherwise a malloc()'d string describing the |
385 | | * error. The caller is responsible for freeing the returned string. */ |
386 | | static char * OVS_WARN_UNUSED_RESULT |
387 | | learn_parse__(char *orig, char *arg, const struct ofputil_port_map *port_map, |
388 | | const struct ofputil_table_map *table_map, |
389 | | struct ofpbuf *ofpacts) |
390 | 0 | { |
391 | 0 | struct ofpact_learn *learn; |
392 | 0 | char *name, *value; |
393 | |
|
394 | 0 | learn = ofpact_put_LEARN(ofpacts); |
395 | 0 | learn->idle_timeout = OFP_FLOW_PERMANENT; |
396 | 0 | learn->hard_timeout = OFP_FLOW_PERMANENT; |
397 | 0 | learn->priority = OFP_DEFAULT_PRIORITY; |
398 | 0 | learn->table_id = 1; |
399 | |
|
400 | 0 | while (ofputil_parse_key_value(&arg, &name, &value)) { |
401 | 0 | if (!strcmp(name, "table")) { |
402 | 0 | if (!ofputil_table_from_string(value, table_map, |
403 | 0 | &learn->table_id)) { |
404 | 0 | return xasprintf("unknown table \"%s\"", value); |
405 | 0 | } else if (learn->table_id == 255) { |
406 | 0 | return xasprintf("%s: table id 255 not valid for `learn' " |
407 | 0 | "action", orig); |
408 | 0 | } |
409 | 0 | } else if (!strcmp(name, "priority")) { |
410 | 0 | learn->priority = atoi(value); |
411 | 0 | } else if (!strcmp(name, "idle_timeout")) { |
412 | 0 | learn->idle_timeout = atoi(value); |
413 | 0 | } else if (!strcmp(name, "hard_timeout")) { |
414 | 0 | learn->hard_timeout = atoi(value); |
415 | 0 | } else if (!strcmp(name, "fin_idle_timeout")) { |
416 | 0 | learn->fin_idle_timeout = atoi(value); |
417 | 0 | } else if (!strcmp(name, "fin_hard_timeout")) { |
418 | 0 | learn->fin_hard_timeout = atoi(value); |
419 | 0 | } else if (!strcmp(name, "cookie")) { |
420 | 0 | learn->cookie = htonll(strtoull(value, NULL, 0)); |
421 | 0 | } else if (!strcmp(name, "send_flow_rem")) { |
422 | 0 | learn->flags |= NX_LEARN_F_SEND_FLOW_REM; |
423 | 0 | } else if (!strcmp(name, "delete_learned")) { |
424 | 0 | learn->flags |= NX_LEARN_F_DELETE_LEARNED; |
425 | 0 | } else if (!strcmp(name, "limit")) { |
426 | 0 | learn->limit = atoi(value); |
427 | 0 | } else if (!strcmp(name, "result_dst")) { |
428 | 0 | char *error; |
429 | 0 | learn->flags |= NX_LEARN_F_WRITE_RESULT; |
430 | 0 | error = mf_parse_subfield(&learn->result_dst, value); |
431 | 0 | if (error) { |
432 | 0 | return error; |
433 | 0 | } |
434 | 0 | if (!learn->result_dst.field->writable) { |
435 | 0 | return xasprintf("%s is read-only", value); |
436 | 0 | } |
437 | 0 | if (learn->result_dst.n_bits != 1) { |
438 | 0 | return xasprintf("result_dst in 'learn' action must be a " |
439 | 0 | "single bit"); |
440 | 0 | } |
441 | 0 | } else { |
442 | 0 | struct ofpact_learn_spec *spec; |
443 | 0 | char *error; |
444 | |
|
445 | 0 | spec = ofpbuf_put_zeros(ofpacts, sizeof *spec); |
446 | 0 | error = learn_parse_spec(orig, name, value, port_map, |
447 | 0 | spec, ofpacts); |
448 | 0 | if (error) { |
449 | 0 | return error; |
450 | 0 | } |
451 | 0 | learn = ofpacts->header; |
452 | 0 | } |
453 | 0 | } |
454 | | |
455 | 0 | if (ofpbuf_oversized(ofpacts)) { |
456 | 0 | return xasprintf("input too big"); |
457 | 0 | } |
458 | | |
459 | 0 | ofpact_finish_LEARN(ofpacts, &learn); |
460 | |
|
461 | 0 | return NULL; |
462 | 0 | } |
463 | | |
464 | | /* Parses 'arg' as a set of arguments to the "learn" action and appends a |
465 | | * matching OFPACT_LEARN action to 'ofpacts'. ovs-actions(7) describes the |
466 | | * format parsed. |
467 | | * |
468 | | * Returns NULL if successful, otherwise a malloc()'d string describing the |
469 | | * error. The caller is responsible for freeing the returned string. |
470 | | * |
471 | | * If 'flow' is nonnull, then it should be the flow from a struct match that is |
472 | | * the matching rule for the learning action. This helps to better validate |
473 | | * the action's arguments. |
474 | | * |
475 | | * Modifies 'arg'. */ |
476 | | char * OVS_WARN_UNUSED_RESULT |
477 | | learn_parse(char *arg, const struct ofputil_port_map *port_map, |
478 | | const struct ofputil_table_map *table_map, |
479 | | struct ofpbuf *ofpacts) |
480 | 0 | { |
481 | 0 | char *orig = xstrdup(arg); |
482 | 0 | char *error = learn_parse__(orig, arg, port_map, table_map, ofpacts); |
483 | 0 | free(orig); |
484 | 0 | return error; |
485 | 0 | } |
486 | | |
487 | | /* Appends a description of 'learn' to 's', in the format that ovs-actions(7) |
488 | | * describes. */ |
489 | | void |
490 | | learn_format(const struct ofpact_learn *learn, |
491 | | const struct ofputil_port_map *port_map, |
492 | | const struct ofputil_table_map *table_map, |
493 | | struct ds *s) |
494 | 0 | { |
495 | 0 | const struct ofpact_learn_spec *spec; |
496 | 0 | struct match match; |
497 | |
|
498 | 0 | match_init_catchall(&match); |
499 | |
|
500 | 0 | ds_put_format(s, "%slearn(%s%stable=%s", |
501 | 0 | colors.learn, colors.end, colors.special, colors.end); |
502 | 0 | ofputil_format_table(learn->table_id, table_map, s); |
503 | 0 | if (learn->idle_timeout != OFP_FLOW_PERMANENT) { |
504 | 0 | ds_put_format(s, ",%sidle_timeout=%s%"PRIu16, |
505 | 0 | colors.param, colors.end, learn->idle_timeout); |
506 | 0 | } |
507 | 0 | if (learn->hard_timeout != OFP_FLOW_PERMANENT) { |
508 | 0 | ds_put_format(s, ",%shard_timeout=%s%"PRIu16, |
509 | 0 | colors.param, colors.end, learn->hard_timeout); |
510 | 0 | } |
511 | 0 | if (learn->fin_idle_timeout) { |
512 | 0 | ds_put_format(s, ",%sfin_idle_timeout=%s%"PRIu16, |
513 | 0 | colors.param, colors.end, learn->fin_idle_timeout); |
514 | 0 | } |
515 | 0 | if (learn->fin_hard_timeout) { |
516 | 0 | ds_put_format(s, "%s,fin_hard_timeout=%s%"PRIu16, |
517 | 0 | colors.param, colors.end, learn->fin_hard_timeout); |
518 | 0 | } |
519 | 0 | if (learn->priority != OFP_DEFAULT_PRIORITY) { |
520 | 0 | ds_put_format(s, "%s,priority=%s%"PRIu16, |
521 | 0 | colors.special, colors.end, learn->priority); |
522 | 0 | } |
523 | 0 | if (learn->flags & NX_LEARN_F_SEND_FLOW_REM) { |
524 | 0 | ds_put_format(s, ",%ssend_flow_rem%s", colors.value, colors.end); |
525 | 0 | } |
526 | 0 | if (learn->flags & NX_LEARN_F_DELETE_LEARNED) { |
527 | 0 | ds_put_format(s, ",%sdelete_learned%s", colors.value, colors.end); |
528 | 0 | } |
529 | 0 | if (learn->cookie != 0) { |
530 | 0 | ds_put_format(s, ",%scookie=%s%#"PRIx64, |
531 | 0 | colors.param, colors.end, ntohll(learn->cookie)); |
532 | 0 | } |
533 | 0 | if (learn->limit != 0) { |
534 | 0 | ds_put_format(s, ",%slimit=%s%"PRIu32, |
535 | 0 | colors.param, colors.end, learn->limit); |
536 | 0 | } |
537 | 0 | if (learn->flags & NX_LEARN_F_WRITE_RESULT) { |
538 | 0 | ds_put_format(s, ",%sresult_dst=%s", colors.param, colors.end); |
539 | 0 | mf_format_subfield(&learn->result_dst, s); |
540 | 0 | } |
541 | |
|
542 | 0 | OFPACT_LEARN_SPEC_FOR_EACH (spec, learn) { |
543 | 0 | unsigned int n_bytes = DIV_ROUND_UP(spec->n_bits, 8); |
544 | 0 | ds_put_char(s, ','); |
545 | |
|
546 | 0 | switch (spec->src_type | spec->dst_type) { |
547 | 0 | case NX_LEARN_SRC_IMMEDIATE | NX_LEARN_DST_MATCH: { |
548 | 0 | if (spec->dst.ofs == 0 |
549 | 0 | && spec->dst.n_bits == spec->dst.field->n_bits) { |
550 | 0 | union mf_value value; |
551 | |
|
552 | 0 | memset(&value, 0, sizeof value); |
553 | 0 | memcpy(&value.b[spec->dst.field->n_bytes - n_bytes], |
554 | 0 | ofpact_learn_spec_imm(spec), n_bytes); |
555 | 0 | ds_put_format(s, "%s%s=%s", colors.param, |
556 | 0 | spec->dst.field->name, colors.end); |
557 | 0 | mf_format(spec->dst.field, &value, NULL, port_map, s); |
558 | 0 | } else { |
559 | 0 | ds_put_format(s, "%s", colors.param); |
560 | 0 | mf_format_subfield(&spec->dst, s); |
561 | 0 | ds_put_format(s, "=%s", colors.end); |
562 | 0 | ds_put_hex(s, ofpact_learn_spec_imm(spec), n_bytes); |
563 | 0 | } |
564 | 0 | break; |
565 | 0 | } |
566 | 0 | case NX_LEARN_SRC_FIELD | NX_LEARN_DST_MATCH: |
567 | 0 | ds_put_format(s, "%s", colors.param); |
568 | 0 | mf_format_subfield(&spec->dst, s); |
569 | 0 | ds_put_format(s, "%s", colors.end); |
570 | 0 | if (spec->src.field != spec->dst.field || |
571 | 0 | spec->src.ofs != spec->dst.ofs) { |
572 | 0 | ds_put_format(s, "%s=%s", colors.param, colors.end); |
573 | 0 | mf_format_subfield(&spec->src, s); |
574 | 0 | } |
575 | 0 | break; |
576 | | |
577 | 0 | case NX_LEARN_SRC_IMMEDIATE | NX_LEARN_DST_LOAD: |
578 | 0 | ds_put_format(s, "%sload:%s", colors.special, colors.end); |
579 | 0 | ds_put_hex(s, ofpact_learn_spec_imm(spec), n_bytes); |
580 | 0 | ds_put_format(s, "%s->%s", colors.special, colors.end); |
581 | 0 | mf_format_subfield(&spec->dst, s); |
582 | 0 | break; |
583 | | |
584 | 0 | case NX_LEARN_SRC_FIELD | NX_LEARN_DST_LOAD: |
585 | 0 | ds_put_format(s, "%sload:%s", colors.special, colors.end); |
586 | 0 | mf_format_subfield(&spec->src, s); |
587 | 0 | ds_put_format(s, "%s->%s", colors.special, colors.end); |
588 | 0 | mf_format_subfield(&spec->dst, s); |
589 | 0 | break; |
590 | | |
591 | 0 | case NX_LEARN_SRC_FIELD | NX_LEARN_DST_OUTPUT: |
592 | 0 | ds_put_format(s, "%soutput:%s", colors.special, colors.end); |
593 | 0 | mf_format_subfield(&spec->src, s); |
594 | 0 | break; |
595 | 0 | } |
596 | 0 | } |
597 | 0 | ds_put_format(s, "%s)%s", colors.learn, colors.end); |
598 | 0 | } |