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