2 * Copyright (c) 2011, 2012 Nicira, Inc.
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:
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 #include "byte-order.h"
22 #include "dynamic-string.h"
23 #include "meta-flow.h"
25 #include "ofp-actions.h"
26 #include "ofp-errors.h"
29 #include "openflow/openflow.h"
30 #include "unaligned.h"
33 get_be16(const void **pp
)
35 const ovs_be16
*p
= *pp
;
42 get_be32(const void **pp
)
44 const ovs_be32
*p
= *pp
;
45 ovs_be32 value
= get_unaligned_be32(p
);
51 get_subfield(int n_bits
, const void **p
, struct mf_subfield
*sf
)
53 sf
->field
= mf_from_nxm_header(ntohl(get_be32(p
)));
54 sf
->ofs
= ntohs(get_be16(p
));
59 learn_min_len(uint16_t header
)
61 int n_bits
= header
& NX_LEARN_N_BITS_MASK
;
62 int src_type
= header
& NX_LEARN_SRC_MASK
;
63 int dst_type
= header
& NX_LEARN_DST_MASK
;
67 if (src_type
== NX_LEARN_SRC_FIELD
) {
68 min_len
+= sizeof(ovs_be32
); /* src_field */
69 min_len
+= sizeof(ovs_be16
); /* src_ofs */
71 min_len
+= DIV_ROUND_UP(n_bits
, 16);
73 if (dst_type
== NX_LEARN_DST_MATCH
||
74 dst_type
== NX_LEARN_DST_LOAD
) {
75 min_len
+= sizeof(ovs_be32
); /* dst_field */
76 min_len
+= sizeof(ovs_be16
); /* dst_ofs */
81 /* Converts 'nal' into a "struct ofpact_learn" and appends that struct to
82 * 'ofpacts'. Returns 0 if successful, otherwise an OFPERR_*. */
84 learn_from_openflow(const struct nx_action_learn
*nal
, struct ofpbuf
*ofpacts
)
86 struct ofpact_learn
*learn
;
90 return OFPERR_OFPBAC_BAD_ARGUMENT
;
93 learn
= ofpact_put_LEARN(ofpacts
);
95 learn
->idle_timeout
= ntohs(nal
->idle_timeout
);
96 learn
->hard_timeout
= ntohs(nal
->hard_timeout
);
97 learn
->priority
= ntohs(nal
->priority
);
98 learn
->cookie
= ntohll(nal
->cookie
);
99 learn
->flags
= ntohs(nal
->flags
);
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
);
104 if (learn
->flags
& ~OFPFF_SEND_FLOW_REM
|| learn
->table_id
== 0xff) {
105 return OFPERR_OFPBAC_BAD_ARGUMENT
;
108 end
= (char *) nal
+ ntohs(nal
->len
);
109 for (p
= nal
+ 1; p
!= end
; ) {
110 struct ofpact_learn_spec
*spec
;
111 uint16_t header
= ntohs(get_be16(&p
));
117 spec
= ofpbuf_put_zeros(ofpacts
, sizeof *spec
);
121 spec
->src_type
= header
& NX_LEARN_SRC_MASK
;
122 spec
->dst_type
= header
& NX_LEARN_DST_MASK
;
123 spec
->n_bits
= header
& NX_LEARN_N_BITS_MASK
;
125 /* Check for valid src and dst type combination. */
126 if (spec
->dst_type
== NX_LEARN_DST_MATCH
||
127 spec
->dst_type
== NX_LEARN_DST_LOAD
||
128 (spec
->dst_type
== NX_LEARN_DST_OUTPUT
&&
129 spec
->src_type
== NX_LEARN_SRC_FIELD
)) {
132 return OFPERR_OFPBAC_BAD_ARGUMENT
;
135 /* Check that the arguments don't overrun the end of the action. */
136 if ((char *) end
- (char *) p
< learn_min_len(header
)) {
137 return OFPERR_OFPBAC_BAD_LEN
;
140 /* Get the source. */
141 if (spec
->src_type
== NX_LEARN_SRC_FIELD
) {
142 get_subfield(spec
->n_bits
, &p
, &spec
->src
);
144 int p_bytes
= 2 * DIV_ROUND_UP(spec
->n_bits
, 16);
146 bitwise_copy(p
, p_bytes
, 0,
147 &spec
->src_imm
, sizeof spec
->src_imm
, 0,
149 p
= (const uint8_t *) p
+ p_bytes
;
152 /* Get the destination. */
153 if (spec
->dst_type
== NX_LEARN_DST_MATCH
||
154 spec
->dst_type
== NX_LEARN_DST_LOAD
) {
155 get_subfield(spec
->n_bits
, &p
, &spec
->dst
);
158 ofpact_update_len(ofpacts
, &learn
->ofpact
);
160 if (!is_all_zeros(p
, (char *) end
- (char *) p
)) {
161 return OFPERR_OFPBAC_BAD_ARGUMENT
;
167 /* Checks that 'learn' is a valid action on 'flow'. Returns 0 if it is valid,
168 * otherwise an OFPERR_*. */
170 learn_check(const struct ofpact_learn
*learn
, const struct flow
*flow
)
172 const struct ofpact_learn_spec
*spec
;
173 struct cls_rule rule
;
175 cls_rule_init_catchall(&rule
, 0);
176 for (spec
= learn
->specs
; spec
< &learn
->specs
[learn
->n_specs
]; spec
++) {
179 /* Check the source. */
180 if (spec
->src_type
== NX_LEARN_SRC_FIELD
) {
181 error
= mf_check_src(&spec
->src
, flow
);
187 /* Check the destination. */
188 switch (spec
->dst_type
) {
189 case NX_LEARN_DST_MATCH
:
190 error
= mf_check_src(&spec
->dst
, &rule
.flow
);
195 mf_write_subfield(&spec
->dst
, &spec
->src_imm
, &rule
);
198 case NX_LEARN_DST_LOAD
:
199 error
= mf_check_dst(&spec
->dst
, &rule
.flow
);
205 case NX_LEARN_DST_OUTPUT
:
214 put_be16(struct ofpbuf
*b
, ovs_be16 x
)
216 ofpbuf_put(b
, &x
, sizeof x
);
220 put_be32(struct ofpbuf
*b
, ovs_be32 x
)
222 ofpbuf_put(b
, &x
, sizeof x
);
226 put_u16(struct ofpbuf
*b
, uint16_t x
)
228 put_be16(b
, htons(x
));
232 put_u32(struct ofpbuf
*b
, uint32_t x
)
234 put_be32(b
, htonl(x
));
237 /* Converts 'learn' into a "struct nx_action_learn" and appends that action to
240 learn_to_nxast(const struct ofpact_learn
*learn
, struct ofpbuf
*openflow
)
242 const struct ofpact_learn_spec
*spec
;
243 struct nx_action_learn
*nal
;
246 start_ofs
= openflow
->size
;
247 nal
= ofputil_put_NXAST_LEARN(openflow
);
248 nal
->idle_timeout
= htons(learn
->idle_timeout
);
249 nal
->hard_timeout
= htons(learn
->hard_timeout
);
250 nal
->fin_idle_timeout
= htons(learn
->fin_idle_timeout
);
251 nal
->fin_hard_timeout
= htons(learn
->fin_hard_timeout
);
252 nal
->priority
= htons(learn
->priority
);
253 nal
->cookie
= htonll(learn
->cookie
);
254 nal
->flags
= htons(learn
->flags
);
255 nal
->table_id
= learn
->table_id
;
257 for (spec
= learn
->specs
; spec
< &learn
->specs
[learn
->n_specs
]; spec
++) {
258 put_u16(openflow
, spec
->n_bits
| spec
->dst_type
| spec
->src_type
);
260 if (spec
->src_type
== NX_LEARN_SRC_FIELD
) {
261 put_u32(openflow
, spec
->src
.field
->nxm_header
);
262 put_u16(openflow
, spec
->src
.ofs
);
264 size_t n_dst_bytes
= 2 * DIV_ROUND_UP(spec
->n_bits
, 16);
265 uint8_t *bits
= ofpbuf_put_zeros(openflow
, n_dst_bytes
);
266 bitwise_copy(&spec
->src_imm
, sizeof spec
->src_imm
, 0,
267 bits
, n_dst_bytes
, 0,
271 if (spec
->dst_type
== NX_LEARN_DST_MATCH
||
272 spec
->dst_type
== NX_LEARN_DST_LOAD
) {
273 put_u32(openflow
, spec
->dst
.field
->nxm_header
);
274 put_u16(openflow
, spec
->dst
.ofs
);
278 if ((openflow
->size
- start_ofs
) % 8) {
279 ofpbuf_put_zeros(openflow
, 8 - (openflow
->size
- start_ofs
) % 8);
282 nal
= ofpbuf_at_assert(openflow
, start_ofs
, sizeof *nal
);
283 nal
->len
= htons(openflow
->size
- start_ofs
);
286 /* Composes 'fm' so that executing it will implement 'learn' given that the
287 * packet being processed has 'flow' as its flow.
289 * Uses 'ofpacts' to store the flow mod's actions. The caller must initialize
290 * 'ofpacts' and retains ownership of it. 'fm->ofpacts' will point into the
293 * The caller has to actually execute 'fm'. */
295 learn_execute(const struct ofpact_learn
*learn
, const struct flow
*flow
,
296 struct ofputil_flow_mod
*fm
, struct ofpbuf
*ofpacts
)
298 const struct ofpact_learn_spec
*spec
;
300 cls_rule_init_catchall(&fm
->cr
, learn
->priority
);
301 fm
->cookie
= htonll(0);
302 fm
->cookie_mask
= htonll(0);
303 fm
->new_cookie
= htonll(learn
->cookie
);
304 fm
->table_id
= learn
->table_id
;
305 fm
->command
= OFPFC_MODIFY_STRICT
;
306 fm
->idle_timeout
= learn
->idle_timeout
;
307 fm
->hard_timeout
= learn
->hard_timeout
;
308 fm
->buffer_id
= UINT32_MAX
;
309 fm
->out_port
= OFPP_NONE
;
310 fm
->flags
= learn
->flags
;
314 if (learn
->fin_idle_timeout
|| learn
->fin_hard_timeout
) {
315 struct ofpact_fin_timeout
*oft
;
317 oft
= ofpact_put_FIN_TIMEOUT(ofpacts
);
318 oft
->fin_idle_timeout
= learn
->fin_idle_timeout
;
319 oft
->fin_hard_timeout
= learn
->fin_hard_timeout
;
322 for (spec
= learn
->specs
; spec
< &learn
->specs
[learn
->n_specs
]; spec
++) {
323 union mf_subvalue value
;
326 if (spec
->src_type
== NX_LEARN_SRC_FIELD
) {
327 mf_read_subfield(&spec
->src
, flow
, &value
);
329 value
= spec
->src_imm
;
332 switch (spec
->dst_type
) {
333 case NX_LEARN_DST_MATCH
:
334 mf_write_subfield(&spec
->dst
, &value
, &fm
->cr
);
337 case NX_LEARN_DST_LOAD
:
338 for (ofs
= 0; ofs
< spec
->n_bits
; ofs
+= chunk
) {
339 struct ofpact_reg_load
*load
;
342 chunk
= MIN(spec
->n_bits
- ofs
, 64);
344 load
= ofpact_put_REG_LOAD(ofpacts
);
345 load
->dst
.field
= spec
->dst
.field
;
346 load
->dst
.ofs
= spec
->dst
.ofs
+ ofs
;
347 load
->dst
.n_bits
= chunk
;
349 memset(&value_be
, 0, sizeof value_be
);
350 bitwise_copy(&value
, sizeof value
, ofs
,
351 &value_be
, sizeof value_be
, 0,
353 load
->value
= ntohll(value_be
);
357 case NX_LEARN_DST_OUTPUT
:
358 if (spec
->n_bits
<= 16
359 || is_all_zeros(value
.u8
, sizeof value
- 2)) {
360 uint16_t port
= ntohs(value
.be16
[7]);
363 || port
== OFPP_IN_PORT
364 || port
== OFPP_FLOOD
365 || port
== OFPP_LOCAL
366 || port
== OFPP_ALL
) {
367 ofpact_put_OUTPUT(ofpacts
)->port
= port
;
375 fm
->ofpacts
= ofpacts
->data
;
376 fm
->ofpacts_len
= ofpacts
->size
;
380 learn_parse_load_immediate(const char *s
, struct ofpact_learn_spec
*spec
)
382 const char *full_s
= s
;
383 const char *arrow
= strstr(s
, "->");
384 struct mf_subfield dst
;
385 union mf_subvalue imm
;
387 memset(&imm
, 0, sizeof imm
);
388 if (s
[0] == '0' && (s
[1] == 'x' || s
[1] == 'X') && arrow
) {
389 const char *in
= arrow
- 1;
390 uint8_t *out
= imm
.u8
+ sizeof imm
.u8
- 1;
391 int n
= arrow
- (s
+ 2);
394 for (i
= 0; i
< n
; i
++) {
395 int hexit
= hexit_value(in
[-i
]);
397 ovs_fatal(0, "%s: bad hex digit in value", full_s
);
399 out
[-(i
/ 2)] |= i
% 2 ? hexit
<< 4 : hexit
;
403 imm
.be64
[1] = htonll(strtoull(s
, (char **) &s
, 0));
406 if (strncmp(s
, "->", 2)) {
407 ovs_fatal(0, "%s: missing `->' following value", full_s
);
411 s
= mf_parse_subfield(&dst
, s
);
413 ovs_fatal(0, "%s: trailing garbage following destination", full_s
);
416 if (!bitwise_is_all_zeros(&imm
, sizeof imm
, dst
.n_bits
,
417 (8 * sizeof imm
) - dst
.n_bits
)) {
418 ovs_fatal(0, "%s: value does not fit into %u bits",
422 spec
->n_bits
= dst
.n_bits
;
423 spec
->src_type
= NX_LEARN_SRC_IMMEDIATE
;
425 spec
->dst_type
= NX_LEARN_DST_LOAD
;
430 learn_parse_spec(const char *orig
, char *name
, char *value
,
431 struct ofpact_learn_spec
*spec
)
433 if (mf_from_name(name
)) {
434 const struct mf_field
*dst
= mf_from_name(name
);
438 error
= mf_parse_value(dst
, value
, &imm
);
440 ovs_fatal(0, "%s", error
);
443 spec
->n_bits
= dst
->n_bits
;
444 spec
->src_type
= NX_LEARN_SRC_IMMEDIATE
;
445 memset(&spec
->src_imm
, 0, sizeof spec
->src_imm
);
446 memcpy(&spec
->src_imm
.u8
[sizeof spec
->src_imm
- dst
->n_bytes
],
448 spec
->dst_type
= NX_LEARN_DST_MATCH
;
449 spec
->dst
.field
= dst
;
451 spec
->dst
.n_bits
= dst
->n_bits
;
452 } else if (strchr(name
, '[')) {
453 /* Parse destination and check prerequisites. */
454 if (mf_parse_subfield(&spec
->dst
, name
)[0] != '\0') {
455 ovs_fatal(0, "%s: syntax error after NXM field name `%s'",
459 /* Parse source and check prerequisites. */
460 if (value
[0] != '\0') {
461 if (mf_parse_subfield(&spec
->src
, value
)[0] != '\0') {
462 ovs_fatal(0, "%s: syntax error after NXM field name `%s'",
465 if (spec
->src
.n_bits
!= spec
->dst
.n_bits
) {
466 ovs_fatal(0, "%s: bit widths of %s (%u) and %s (%u) differ",
467 orig
, name
, spec
->src
.n_bits
, value
,
471 spec
->src
= spec
->dst
;
474 spec
->n_bits
= spec
->src
.n_bits
;
475 spec
->src_type
= NX_LEARN_SRC_FIELD
;
476 spec
->dst_type
= NX_LEARN_DST_MATCH
;
477 } else if (!strcmp(name
, "load")) {
478 if (value
[strcspn(value
, "[-")] == '-') {
479 learn_parse_load_immediate(value
, spec
);
481 struct ofpact_reg_move move
;
483 nxm_parse_reg_move(&move
, value
);
485 spec
->n_bits
= move
.src
.n_bits
;
486 spec
->src_type
= NX_LEARN_SRC_FIELD
;
487 spec
->src
= move
.src
;
488 spec
->dst_type
= NX_LEARN_DST_LOAD
;
489 spec
->dst
= move
.dst
;
491 } else if (!strcmp(name
, "output")) {
492 if (mf_parse_subfield(&spec
->src
, value
)[0] != '\0') {
493 ovs_fatal(0, "%s: syntax error after NXM field name `%s'",
497 spec
->n_bits
= spec
->src
.n_bits
;
498 spec
->src_type
= NX_LEARN_SRC_FIELD
;
499 spec
->dst_type
= NX_LEARN_DST_OUTPUT
;
501 ovs_fatal(0, "%s: unknown keyword %s", orig
, name
);
505 /* Parses 'arg' as a set of arguments to the "learn" action and appends a
506 * matching OFPACT_LEARN action to 'ofpacts'. ovs-ofctl(8) describes the
509 * Prints an error on stderr and aborts the program if 'arg' syntax is invalid.
511 * If 'flow' is nonnull, then it should be the flow from a cls_rule that is
512 * the matching rule for the learning action. This helps to better validate
513 * the action's arguments.
517 learn_parse(char *arg
, const struct flow
*flow
, struct ofpbuf
*ofpacts
)
519 char *orig
= xstrdup(arg
);
522 struct ofpact_learn
*learn
;
523 struct cls_rule rule
;
526 learn
= ofpact_put_LEARN(ofpacts
);
527 learn
->idle_timeout
= OFP_FLOW_PERMANENT
;
528 learn
->hard_timeout
= OFP_FLOW_PERMANENT
;
529 learn
->priority
= OFP_DEFAULT_PRIORITY
;
532 cls_rule_init_catchall(&rule
, 0);
533 while (ofputil_parse_key_value(&arg
, &name
, &value
)) {
534 if (!strcmp(name
, "table")) {
535 learn
->table_id
= atoi(value
);
536 if (learn
->table_id
== 255) {
537 ovs_fatal(0, "%s: table id 255 not valid for `learn' action",
540 } else if (!strcmp(name
, "priority")) {
541 learn
->priority
= atoi(value
);
542 } else if (!strcmp(name
, "idle_timeout")) {
543 learn
->idle_timeout
= atoi(value
);
544 } else if (!strcmp(name
, "hard_timeout")) {
545 learn
->hard_timeout
= atoi(value
);
546 } else if (!strcmp(name
, "fin_idle_timeout")) {
547 learn
->fin_idle_timeout
= atoi(value
);
548 } else if (!strcmp(name
, "fin_hard_timeout")) {
549 learn
->fin_hard_timeout
= atoi(value
);
550 } else if (!strcmp(name
, "cookie")) {
551 learn
->cookie
= strtoull(value
, NULL
, 0);
553 struct ofpact_learn_spec
*spec
;
555 spec
= ofpbuf_put_zeros(ofpacts
, sizeof *spec
);
559 learn_parse_spec(orig
, name
, value
, spec
);
561 /* Check prerequisites. */
562 if (spec
->src_type
== NX_LEARN_SRC_FIELD
563 && flow
&& !mf_are_prereqs_ok(spec
->src
.field
, flow
)) {
564 ovs_fatal(0, "%s: cannot specify source field %s because "
565 "prerequisites are not satisfied",
566 orig
, spec
->src
.field
->name
);
568 if ((spec
->dst_type
== NX_LEARN_DST_MATCH
569 || spec
->dst_type
== NX_LEARN_DST_LOAD
)
570 && !mf_are_prereqs_ok(spec
->dst
.field
, &rule
.flow
)) {
571 ovs_fatal(0, "%s: cannot specify destination field %s because "
572 "prerequisites are not satisfied",
573 orig
, spec
->dst
.field
->name
);
576 /* Update 'rule' to allow for satisfying destination
578 if (spec
->src_type
== NX_LEARN_SRC_IMMEDIATE
579 && spec
->dst_type
== NX_LEARN_DST_MATCH
) {
580 mf_write_subfield(&spec
->dst
, &spec
->src_imm
, &rule
);
584 ofpact_update_len(ofpacts
, &learn
->ofpact
);
586 /* In theory the above should have caught any errors, but... */
588 error
= learn_check(learn
, flow
);
590 ovs_fatal(0, "%s: %s", orig
, ofperr_to_string(error
));
597 format_subvalue(const union mf_subvalue
*subvalue
, struct ds
*s
)
601 for (i
= 0; i
< ARRAY_SIZE(subvalue
->u8
); i
++) {
602 if (subvalue
->u8
[i
]) {
603 ds_put_format(s
, "0x%"PRIx8
, subvalue
->u8
[i
]);
604 for (i
++; i
< ARRAY_SIZE(subvalue
->u8
); i
++) {
605 ds_put_format(s
, "%02"PRIx8
, subvalue
->u8
[i
]);
613 /* Appends a description of 'learn' to 's', in the format that ovs-ofctl(8)
616 learn_format(const struct ofpact_learn
*learn
, struct ds
*s
)
618 const struct ofpact_learn_spec
*spec
;
619 struct cls_rule rule
;
621 cls_rule_init_catchall(&rule
, 0);
623 ds_put_format(s
, "learn(table=%"PRIu8
, learn
->table_id
);
624 if (learn
->idle_timeout
!= OFP_FLOW_PERMANENT
) {
625 ds_put_format(s
, ",idle_timeout=%"PRIu16
, learn
->idle_timeout
);
627 if (learn
->hard_timeout
!= OFP_FLOW_PERMANENT
) {
628 ds_put_format(s
, ",hard_timeout=%"PRIu16
, learn
->hard_timeout
);
630 if (learn
->fin_idle_timeout
) {
631 ds_put_format(s
, ",fin_idle_timeout=%"PRIu16
, learn
->fin_idle_timeout
);
633 if (learn
->fin_hard_timeout
) {
634 ds_put_format(s
, ",fin_hard_timeout=%"PRIu16
, learn
->fin_hard_timeout
);
636 if (learn
->priority
!= OFP_DEFAULT_PRIORITY
) {
637 ds_put_format(s
, ",priority=%"PRIu16
, learn
->priority
);
639 if (learn
->flags
& OFPFF_SEND_FLOW_REM
) {
640 ds_put_cstr(s
, ",OFPFF_SEND_FLOW_REM");
642 if (learn
->cookie
!= 0) {
643 ds_put_format(s
, ",cookie=%#"PRIx64
, learn
->cookie
);
646 for (spec
= learn
->specs
; spec
< &learn
->specs
[learn
->n_specs
]; spec
++) {
649 switch (spec
->src_type
| spec
->dst_type
) {
650 case NX_LEARN_SRC_IMMEDIATE
| NX_LEARN_DST_MATCH
:
651 if (spec
->dst
.ofs
== 0
652 && spec
->dst
.n_bits
== spec
->dst
.field
->n_bits
) {
653 union mf_value value
;
655 memset(&value
, 0, sizeof value
);
656 bitwise_copy(&spec
->src_imm
, sizeof spec
->src_imm
, 0,
657 &value
, spec
->dst
.field
->n_bytes
, 0,
658 spec
->dst
.field
->n_bits
);
659 ds_put_format(s
, "%s=", spec
->dst
.field
->name
);
660 mf_format(spec
->dst
.field
, &value
, NULL
, s
);
662 mf_format_subfield(&spec
->dst
, s
);
664 format_subvalue(&spec
->src_imm
, s
);
668 case NX_LEARN_SRC_FIELD
| NX_LEARN_DST_MATCH
:
669 mf_format_subfield(&spec
->dst
, s
);
670 if (spec
->src
.field
!= spec
->dst
.field
||
671 spec
->src
.ofs
!= spec
->dst
.ofs
) {
673 mf_format_subfield(&spec
->src
, s
);
677 case NX_LEARN_SRC_IMMEDIATE
| NX_LEARN_DST_LOAD
:
678 ds_put_format(s
, "load:");
679 format_subvalue(&spec
->src_imm
, s
);
680 ds_put_cstr(s
, "->");
681 mf_format_subfield(&spec
->dst
, s
);
684 case NX_LEARN_SRC_FIELD
| NX_LEARN_DST_LOAD
:
685 ds_put_cstr(s
, "load:");
686 mf_format_subfield(&spec
->src
, s
);
687 ds_put_cstr(s
, "->");
688 mf_format_subfield(&spec
->dst
, s
);
691 case NX_LEARN_SRC_FIELD
| NX_LEARN_DST_OUTPUT
:
692 ds_put_cstr(s
, "output:");
693 mf_format_subfield(&spec
->src
, s
);