2 * Copyright (c) 2011, 2012 Nicira Networks.
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-errors.h"
28 #include "openflow/openflow.h"
29 #include "unaligned.h"
32 get_be16(const void **pp
)
34 const ovs_be16
*p
= *pp
;
41 get_be32(const void **pp
)
43 const ovs_be32
*p
= *pp
;
44 ovs_be32 value
= get_unaligned_be32(p
);
50 get_bits(int n_bits
, const void **p
)
52 int n_segs
= DIV_ROUND_UP(n_bits
, 16);
56 while (n_segs
-- > 0) {
57 value
= (value
<< 16) | ntohs(get_be16(p
));
63 get_subfield(int n_bits
, const void **p
, struct mf_subfield
*sf
)
65 sf
->field
= mf_from_nxm_header(ntohl(get_be32(p
)));
66 sf
->ofs
= ntohs(get_be16(p
));
71 learn_min_len(uint16_t header
)
73 int n_bits
= header
& NX_LEARN_N_BITS_MASK
;
74 int src_type
= header
& NX_LEARN_SRC_MASK
;
75 int dst_type
= header
& NX_LEARN_DST_MASK
;
79 if (src_type
== NX_LEARN_SRC_FIELD
) {
80 min_len
+= sizeof(ovs_be32
); /* src_field */
81 min_len
+= sizeof(ovs_be16
); /* src_ofs */
83 min_len
+= DIV_ROUND_UP(n_bits
, 16);
85 if (dst_type
== NX_LEARN_DST_MATCH
||
86 dst_type
== NX_LEARN_DST_LOAD
) {
87 min_len
+= sizeof(ovs_be32
); /* dst_field */
88 min_len
+= sizeof(ovs_be16
); /* dst_ofs */
94 learn_check_header(uint16_t header
, size_t len
)
96 int src_type
= header
& NX_LEARN_SRC_MASK
;
97 int dst_type
= header
& NX_LEARN_DST_MASK
;
99 /* Check for valid src and dst type combination. */
100 if (dst_type
== NX_LEARN_DST_MATCH
||
101 dst_type
== NX_LEARN_DST_LOAD
||
102 (dst_type
== NX_LEARN_DST_OUTPUT
&&
103 src_type
== NX_LEARN_SRC_FIELD
)) {
106 return OFPERR_OFPBAC_BAD_ARGUMENT
;
109 /* Check that the arguments don't overrun the end of the action. */
110 if (len
< learn_min_len(header
)) {
111 return OFPERR_OFPBAC_BAD_LEN
;
117 /* Checks that 'learn' (which must be at least 'sizeof *learn' bytes long) is a
118 * valid action on 'flow'. */
120 learn_check(const struct nx_action_learn
*learn
, const struct flow
*flow
)
122 struct cls_rule rule
;
125 cls_rule_init_catchall(&rule
, 0);
127 if (learn
->flags
& ~htons(OFPFF_SEND_FLOW_REM
)
129 || learn
->table_id
== 0xff) {
130 return OFPERR_OFPBAC_BAD_ARGUMENT
;
133 end
= (char *) learn
+ ntohs(learn
->len
);
134 for (p
= learn
+ 1; p
!= end
; ) {
135 uint16_t header
= ntohs(get_be16(&p
));
136 int n_bits
= header
& NX_LEARN_N_BITS_MASK
;
137 int src_type
= header
& NX_LEARN_SRC_MASK
;
138 int dst_type
= header
& NX_LEARN_DST_MASK
;
147 error
= learn_check_header(header
, (char *) end
- (char *) p
);
152 /* Check the source. */
153 if (src_type
== NX_LEARN_SRC_FIELD
) {
154 struct mf_subfield src
;
156 get_subfield(n_bits
, &p
, &src
);
157 error
= mf_check_src(&src
, flow
);
163 value
= get_bits(n_bits
, &p
);
166 /* Check the destination. */
167 if (dst_type
== NX_LEARN_DST_MATCH
|| dst_type
== NX_LEARN_DST_LOAD
) {
168 struct mf_subfield dst
;
170 get_subfield(n_bits
, &p
, &dst
);
171 error
= (dst_type
== NX_LEARN_DST_LOAD
172 ? mf_check_dst(&dst
, &rule
.flow
)
173 : mf_check_src(&dst
, &rule
.flow
));
178 if (dst_type
== NX_LEARN_DST_MATCH
179 && src_type
== NX_LEARN_SRC_IMMEDIATE
) {
181 mf_set_subfield(&dst
, value
, &rule
);
183 /* We're only setting subfields to allow us to check
184 * prerequisites. No prerequisite depends on the value of
185 * a field that is wider than 64 bits. So just skip
186 * setting it entirely. */
187 BUILD_ASSERT_DECL(FLOW_WC_SEQ
== 10);
192 if (!is_all_zeros(p
, (char *) end
- (char *) p
)) {
193 return OFPERR_OFPBAC_BAD_ARGUMENT
;
200 learn_execute(const struct nx_action_learn
*learn
, const struct flow
*flow
,
201 struct ofputil_flow_mod
*fm
)
204 struct ofpbuf actions
;
206 cls_rule_init_catchall(&fm
->cr
, ntohs(learn
->priority
));
207 fm
->cookie
= learn
->cookie
;
208 fm
->cookie_mask
= htonll(UINT64_MAX
);
209 fm
->table_id
= learn
->table_id
;
210 fm
->command
= OFPFC_MODIFY_STRICT
;
211 fm
->idle_timeout
= ntohs(learn
->idle_timeout
);
212 fm
->hard_timeout
= ntohs(learn
->hard_timeout
);
213 fm
->buffer_id
= UINT32_MAX
;
214 fm
->out_port
= OFPP_NONE
;
215 fm
->flags
= ntohs(learn
->flags
) & OFPFF_SEND_FLOW_REM
;
219 ofpbuf_init(&actions
, 64);
221 if (learn
->fin_idle_timeout
|| learn
->fin_hard_timeout
) {
222 struct nx_action_fin_timeout
*naft
;
224 naft
= ofputil_put_NXAST_FIN_TIMEOUT(&actions
);
225 naft
->fin_idle_timeout
= learn
->fin_idle_timeout
;
226 naft
->fin_hard_timeout
= learn
->fin_hard_timeout
;
229 for (p
= learn
+ 1, end
= (char *) learn
+ ntohs(learn
->len
); p
!= end
; ) {
230 uint16_t header
= ntohs(get_be16(&p
));
231 int n_bits
= header
& NX_LEARN_N_BITS_MASK
;
232 int src_type
= header
& NX_LEARN_SRC_MASK
;
233 int dst_type
= header
& NX_LEARN_DST_MASK
;
234 union mf_subvalue value
;
236 struct mf_subfield dst
;
243 if (src_type
== NX_LEARN_SRC_FIELD
) {
244 struct mf_subfield src
;
246 get_subfield(n_bits
, &p
, &src
);
247 mf_read_subfield(&src
, flow
, &value
);
249 int p_bytes
= 2 * DIV_ROUND_UP(n_bits
, 16);
251 memset(&value
, 0, sizeof value
);
252 bitwise_copy(p
, p_bytes
, 0,
253 &value
, sizeof value
, 0,
255 p
= (const uint8_t *) p
+ p_bytes
;
259 case NX_LEARN_DST_MATCH
:
260 get_subfield(n_bits
, &p
, &dst
);
261 mf_write_subfield(&dst
, &value
, &fm
->cr
);
264 case NX_LEARN_DST_LOAD
:
265 get_subfield(n_bits
, &p
, &dst
);
266 for (ofs
= 0; ofs
< n_bits
; ofs
+= chunk
) {
267 struct nx_action_reg_load
*load
;
269 chunk
= MIN(n_bits
- ofs
, 64);
271 load
= ofputil_put_NXAST_REG_LOAD(&actions
);
272 load
->ofs_nbits
= nxm_encode_ofs_nbits(dst
.ofs
+ ofs
, chunk
);
273 load
->dst
= htonl(dst
.field
->nxm_header
);
274 bitwise_copy(&value
, sizeof value
, ofs
,
275 &load
->value
, sizeof load
->value
, 0,
280 case NX_LEARN_DST_OUTPUT
:
281 if (n_bits
<= 16 || is_all_zeros(value
.u8
, sizeof value
- 2)) {
282 ofputil_put_OFPAT10_OUTPUT(&actions
)->port
= value
.be16
[7];
288 fm
->actions
= ofpbuf_steal_data(&actions
);
289 fm
->n_actions
= actions
.size
/ sizeof(struct ofp_action_header
);
293 put_be16(struct ofpbuf
*b
, ovs_be16 x
)
295 ofpbuf_put(b
, &x
, sizeof x
);
299 put_be32(struct ofpbuf
*b
, ovs_be32 x
)
301 ofpbuf_put(b
, &x
, sizeof x
);
305 put_u16(struct ofpbuf
*b
, uint16_t x
)
307 put_be16(b
, htons(x
));
311 put_u32(struct ofpbuf
*b
, uint32_t x
)
313 put_be32(b
, htonl(x
));
320 struct mf_subfield src
;
321 union mf_subvalue src_imm
;
324 struct mf_subfield dst
;
328 learn_parse_load_immediate(const char *s
, struct learn_spec
*spec
)
330 const char *full_s
= s
;
331 const char *arrow
= strstr(s
, "->");
332 struct mf_subfield dst
;
333 union mf_subvalue imm
;
335 memset(&imm
, 0, sizeof imm
);
336 if (s
[0] == '0' && (s
[1] == 'x' || s
[1] == 'X') && arrow
) {
337 const char *in
= arrow
- 1;
338 uint8_t *out
= imm
.u8
+ sizeof imm
.u8
- 1;
339 int n
= arrow
- (s
+ 2);
342 for (i
= 0; i
< n
; i
++) {
343 int hexit
= hexit_value(in
[-i
]);
345 ovs_fatal(0, "%s: bad hex digit in value", full_s
);
347 out
[-(i
/ 2)] |= i
% 2 ? hexit
<< 4 : hexit
;
351 imm
.be64
[1] = htonll(strtoull(s
, (char **) &s
, 0));
354 if (strncmp(s
, "->", 2)) {
355 ovs_fatal(0, "%s: missing `->' following value", full_s
);
359 s
= mf_parse_subfield(&dst
, s
);
361 ovs_fatal(0, "%s: trailing garbage following destination", full_s
);
364 if (!bitwise_is_all_zeros(&imm
, sizeof imm
, dst
.n_bits
,
365 (8 * sizeof imm
) - dst
.n_bits
)) {
366 ovs_fatal(0, "%s: value does not fit into %u bits",
370 spec
->n_bits
= dst
.n_bits
;
371 spec
->src_type
= NX_LEARN_SRC_IMMEDIATE
;
373 spec
->dst_type
= NX_LEARN_DST_LOAD
;
378 learn_parse_spec(const char *orig
, char *name
, char *value
,
379 struct learn_spec
*spec
)
381 memset(spec
, 0, sizeof *spec
);
382 if (mf_from_name(name
)) {
383 const struct mf_field
*dst
= mf_from_name(name
);
387 error
= mf_parse_value(dst
, value
, &imm
);
389 ovs_fatal(0, "%s", error
);
392 spec
->n_bits
= dst
->n_bits
;
393 spec
->src_type
= NX_LEARN_SRC_IMMEDIATE
;
394 memset(&spec
->src_imm
, 0, sizeof spec
->src_imm
);
395 memcpy(&spec
->src_imm
.u8
[sizeof spec
->src_imm
- dst
->n_bytes
],
397 spec
->dst_type
= NX_LEARN_DST_MATCH
;
398 spec
->dst
.field
= dst
;
400 spec
->dst
.n_bits
= dst
->n_bits
;
401 } else if (strchr(name
, '[')) {
402 /* Parse destination and check prerequisites. */
403 if (mf_parse_subfield(&spec
->dst
, name
)[0] != '\0') {
404 ovs_fatal(0, "%s: syntax error after NXM field name `%s'",
408 /* Parse source and check prerequisites. */
409 if (value
[0] != '\0') {
410 if (mf_parse_subfield(&spec
->src
, value
)[0] != '\0') {
411 ovs_fatal(0, "%s: syntax error after NXM field name `%s'",
414 if (spec
->src
.n_bits
!= spec
->dst
.n_bits
) {
415 ovs_fatal(0, "%s: bit widths of %s (%u) and %s (%u) differ",
416 orig
, name
, spec
->src
.n_bits
, value
,
420 spec
->src
= spec
->dst
;
423 spec
->n_bits
= spec
->src
.n_bits
;
424 spec
->src_type
= NX_LEARN_SRC_FIELD
;
425 spec
->dst_type
= NX_LEARN_DST_MATCH
;
426 } else if (!strcmp(name
, "load")) {
427 if (value
[strcspn(value
, "[-")] == '-') {
428 learn_parse_load_immediate(value
, spec
);
430 struct nx_action_reg_move move
;
432 nxm_parse_reg_move(&move
, value
);
434 spec
->n_bits
= ntohs(move
.n_bits
);
435 spec
->src_type
= NX_LEARN_SRC_FIELD
;
436 nxm_decode_discrete(&spec
->src
,
437 move
.src
, move
.src_ofs
, move
.n_bits
);
438 spec
->dst_type
= NX_LEARN_DST_LOAD
;
439 nxm_decode_discrete(&spec
->dst
,
440 move
.dst
, move
.dst_ofs
, move
.n_bits
);
442 } else if (!strcmp(name
, "output")) {
443 if (mf_parse_subfield(&spec
->src
, value
)[0] != '\0') {
444 ovs_fatal(0, "%s: syntax error after NXM field name `%s'",
448 spec
->n_bits
= spec
->src
.n_bits
;
449 spec
->src_type
= NX_LEARN_SRC_FIELD
;
450 spec
->dst_type
= NX_LEARN_DST_OUTPUT
;
452 ovs_fatal(0, "%s: unknown keyword %s", orig
, name
);
456 /* Parses 'arg' as a set of arguments to the "learn" action and appends a
457 * matching NXAST_LEARN action to 'b'. The format parsed is described in
460 * Prints an error on stderr and aborts the program if 'arg' syntax is invalid.
462 * If 'flow' is nonnull, then it should be the flow from a cls_rule that is
463 * the matching rule for the learning action. This helps to better validate
464 * the action's arguments.
468 learn_parse(struct ofpbuf
*b
, char *arg
, const struct flow
*flow
)
470 char *orig
= xstrdup(arg
);
476 struct nx_action_learn
*learn
;
477 struct cls_rule rule
;
480 learn
= ofputil_put_NXAST_LEARN(b
);
481 learn
->idle_timeout
= htons(OFP_FLOW_PERMANENT
);
482 learn
->hard_timeout
= htons(OFP_FLOW_PERMANENT
);
483 learn
->priority
= htons(OFP_DEFAULT_PRIORITY
);
484 learn
->cookie
= htonll(0);
485 learn
->flags
= htons(0);
488 cls_rule_init_catchall(&rule
, 0);
489 while (ofputil_parse_key_value(&arg
, &name
, &value
)) {
490 learn
= ofpbuf_at_assert(b
, learn_ofs
, sizeof *learn
);
491 if (!strcmp(name
, "table")) {
492 learn
->table_id
= atoi(value
);
493 if (learn
->table_id
== 255) {
494 ovs_fatal(0, "%s: table id 255 not valid for `learn' action",
497 } else if (!strcmp(name
, "priority")) {
498 learn
->priority
= htons(atoi(value
));
499 } else if (!strcmp(name
, "idle_timeout")) {
500 learn
->idle_timeout
= htons(atoi(value
));
501 } else if (!strcmp(name
, "hard_timeout")) {
502 learn
->hard_timeout
= htons(atoi(value
));
503 } else if (!strcmp(name
, "fin_idle_timeout")) {
504 learn
->fin_idle_timeout
= htons(atoi(value
));
505 } else if (!strcmp(name
, "fin_hard_timeout")) {
506 learn
->fin_hard_timeout
= htons(atoi(value
));
507 } else if (!strcmp(name
, "cookie")) {
508 learn
->cookie
= htonll(strtoull(value
, NULL
, 0));
510 struct learn_spec spec
;
512 learn_parse_spec(orig
, name
, value
, &spec
);
514 /* Check prerequisites. */
515 if (spec
.src_type
== NX_LEARN_SRC_FIELD
516 && flow
&& !mf_are_prereqs_ok(spec
.src
.field
, flow
)) {
517 ovs_fatal(0, "%s: cannot specify source field %s because "
518 "prerequisites are not satisfied",
519 orig
, spec
.src
.field
->name
);
521 if ((spec
.dst_type
== NX_LEARN_DST_MATCH
522 || spec
.dst_type
== NX_LEARN_DST_LOAD
)
523 && !mf_are_prereqs_ok(spec
.dst
.field
, &rule
.flow
)) {
524 ovs_fatal(0, "%s: cannot specify destination field %s because "
525 "prerequisites are not satisfied",
526 orig
, spec
.dst
.field
->name
);
529 /* Update 'rule' to allow for satisfying destination
531 if (spec
.src_type
== NX_LEARN_SRC_IMMEDIATE
532 && spec
.dst_type
== NX_LEARN_DST_MATCH
) {
533 mf_write_subfield(&spec
.dst
, &spec
.src_imm
, &rule
);
536 /* Output the flow_mod_spec. */
537 put_u16(b
, spec
.n_bits
| spec
.src_type
| spec
.dst_type
);
538 if (spec
.src_type
== NX_LEARN_SRC_IMMEDIATE
) {
539 int n_bytes
= DIV_ROUND_UP(spec
.n_bits
, 16) * 2;
540 int ofs
= sizeof spec
.src_imm
- n_bytes
;
541 ofpbuf_put(b
, &spec
.src_imm
.u8
[ofs
], n_bytes
);
543 put_u32(b
, spec
.src
.field
->nxm_header
);
544 put_u16(b
, spec
.src
.ofs
);
546 if (spec
.dst_type
== NX_LEARN_DST_MATCH
||
547 spec
.dst_type
== NX_LEARN_DST_LOAD
) {
548 put_u32(b
, spec
.dst
.field
->nxm_header
);
549 put_u16(b
, spec
.dst
.ofs
);
551 assert(spec
.dst_type
== NX_LEARN_DST_OUTPUT
);
558 len
= b
->size
- learn_ofs
;
560 ofpbuf_put_zeros(b
, 8 - len
% 8);
563 learn
= ofpbuf_at_assert(b
, learn_ofs
, sizeof *learn
);
564 learn
->len
= htons(b
->size
- learn_ofs
);
566 /* In theory the above should have caught any errors, but... */
568 error
= learn_check(learn
, flow
);
570 ovs_fatal(0, "%s: %s", orig
, ofperr_to_string(error
));
577 learn_format(const struct nx_action_learn
*learn
, struct ds
*s
)
579 struct cls_rule rule
;
582 cls_rule_init_catchall(&rule
, 0);
584 ds_put_format(s
, "learn(table=%"PRIu8
, learn
->table_id
);
585 if (learn
->idle_timeout
!= htons(OFP_FLOW_PERMANENT
)) {
586 ds_put_format(s
, ",idle_timeout=%"PRIu16
, ntohs(learn
->idle_timeout
));
588 if (learn
->hard_timeout
!= htons(OFP_FLOW_PERMANENT
)) {
589 ds_put_format(s
, ",hard_timeout=%"PRIu16
, ntohs(learn
->hard_timeout
));
591 if (learn
->fin_idle_timeout
) {
592 ds_put_format(s
, ",fin_idle_timeout=%"PRIu16
,
593 ntohs(learn
->fin_idle_timeout
));
595 if (learn
->fin_hard_timeout
) {
596 ds_put_format(s
, ",fin_hard_timeout=%"PRIu16
,
597 ntohs(learn
->fin_hard_timeout
));
599 if (learn
->priority
!= htons(OFP_DEFAULT_PRIORITY
)) {
600 ds_put_format(s
, ",priority=%"PRIu16
, ntohs(learn
->priority
));
602 if (learn
->flags
& htons(OFPFF_SEND_FLOW_REM
)) {
603 ds_put_cstr(s
, ",OFPFF_SEND_FLOW_REM");
605 if (learn
->flags
& htons(~OFPFF_SEND_FLOW_REM
)) {
606 ds_put_format(s
, ",***flags=%"PRIu16
"***",
607 ntohs(learn
->flags
) & ~OFPFF_SEND_FLOW_REM
);
609 if (learn
->cookie
!= htonll(0)) {
610 ds_put_format(s
, ",cookie=0x%"PRIx64
, ntohll(learn
->cookie
));
612 if (learn
->pad
!= 0) {
613 ds_put_cstr(s
, ",***nonzero pad***");
616 end
= (char *) learn
+ ntohs(learn
->len
);
617 for (p
= learn
+ 1; p
!= end
; ) {
618 uint16_t header
= ntohs(get_be16(&p
));
619 int n_bits
= header
& NX_LEARN_N_BITS_MASK
;
621 int src_type
= header
& NX_LEARN_SRC_MASK
;
622 struct mf_subfield src
;
623 const uint8_t *src_value
;
626 int dst_type
= header
& NX_LEARN_DST_MASK
;
627 struct mf_subfield dst
;
636 error
= learn_check_header(header
, (char *) end
- (char *) p
);
637 if (error
== OFPERR_OFPBAC_BAD_ARGUMENT
) {
638 ds_put_format(s
, ",***bad flow_mod_spec header %"PRIx16
"***)",
641 } else if (error
== OFPERR_OFPBAC_BAD_LEN
) {
642 ds_put_format(s
, ",***flow_mod_spec at offset %td is %u bytes "
643 "long but only %td bytes are left***)",
644 (char *) p
- (char *) (learn
+ 1) - 2,
645 learn_min_len(header
) + 2,
646 (char *) end
- (char *) p
+ 2);
651 /* Get the source. */
652 if (src_type
== NX_LEARN_SRC_FIELD
) {
653 get_subfield(n_bits
, &p
, &src
);
660 src_value_bytes
= 2 * DIV_ROUND_UP(n_bits
, 16);
662 p
= (const void *) ((const uint8_t *) p
+ src_value_bytes
);
665 /* Get the destination. */
666 if (dst_type
== NX_LEARN_DST_MATCH
|| dst_type
== NX_LEARN_DST_LOAD
) {
667 get_subfield(n_bits
, &p
, &dst
);
676 switch (src_type
| dst_type
) {
677 case NX_LEARN_SRC_IMMEDIATE
| NX_LEARN_DST_MATCH
:
678 if (dst
.field
&& dst
.ofs
== 0 && n_bits
== dst
.field
->n_bits
) {
679 union mf_value value
;
680 uint8_t *bytes
= (uint8_t *) &value
;
682 if (src_value_bytes
> dst
.field
->n_bytes
) {
683 /* The destination field is an odd number of bytes, which
684 * got rounded up to a multiple of 2 to be put into the
685 * learning action. Skip over the leading byte, which
686 * should be zero anyway. Otherwise the memcpy() below
687 * will overrun the start of 'value'. */
688 int diff
= src_value_bytes
- dst
.field
->n_bytes
;
690 src_value_bytes
-= diff
;
693 memset(&value
, 0, sizeof value
);
694 memcpy(&bytes
[dst
.field
->n_bytes
- src_value_bytes
],
695 src_value
, src_value_bytes
);
696 ds_put_format(s
, "%s=", dst
.field
->name
);
697 mf_format(dst
.field
, &value
, NULL
, s
);
699 mf_format_subfield(&dst
, s
);
700 ds_put_cstr(s
, "=0x");
701 for (i
= 0; i
< src_value_bytes
; i
++) {
702 ds_put_format(s
, "%02"PRIx8
, src_value
[i
]);
707 case NX_LEARN_SRC_FIELD
| NX_LEARN_DST_MATCH
:
708 mf_format_subfield(&dst
, s
);
709 if (src
.field
!= dst
.field
|| src
.ofs
!= dst
.ofs
) {
711 mf_format_subfield(&src
, s
);
715 case NX_LEARN_SRC_IMMEDIATE
| NX_LEARN_DST_LOAD
:
716 ds_put_cstr(s
, "load:0x");
717 for (i
= 0; i
< src_value_bytes
; i
++) {
718 ds_put_format(s
, "%02"PRIx8
, src_value
[i
]);
720 ds_put_cstr(s
, "->");
721 mf_format_subfield(&dst
, s
);
724 case NX_LEARN_SRC_FIELD
| NX_LEARN_DST_LOAD
:
725 ds_put_cstr(s
, "load:");
726 mf_format_subfield(&src
, s
);
727 ds_put_cstr(s
, "->");
728 mf_format_subfield(&dst
, s
);
731 case NX_LEARN_SRC_FIELD
| NX_LEARN_DST_OUTPUT
:
732 ds_put_cstr(s
, "output:");
733 mf_format_subfield(&src
, s
);
737 if (!is_all_zeros(p
, (char *) end
- (char *) p
)) {
738 ds_put_cstr(s
, ",***nonzero trailer***");