2 * Copyright (c) 2018 Cisco and/or its affiliates.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at:
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 #include <vnet/vnet.h>
18 #include <vnet/devices/devices.h>
19 #include <vnet/ip/ip.h>
20 #include <vnet/ethernet/ethernet.h>
21 #include <vnet/ethernet/packet.h>
22 #include <vnet/flow/flow.h>
24 static format_function_t format_flow;
27 unformat_ip_port_and_mask (unformat_input_t * input, va_list * args)
29 ip_port_and_mask_t *pm = va_arg (*args, ip_port_and_mask_t *);
30 u32 port = 0, mask = 0;
32 if (unformat (input, "any"))
34 else if (unformat (input, "%u/%u", &port, &mask))
36 else if (unformat (input, "%u/0x%x", &port, &mask))
38 else if (unformat (input, "%u", &port))
43 if (port > 0xffff || mask > 0xffff)
52 format_ip_port_and_mask (u8 * s, va_list * args)
54 ip_port_and_mask_t *pm = va_arg (*args, ip_port_and_mask_t *);
56 if (pm->port == 0 && pm->mask == 0)
57 return format (s, "any");
59 if (pm->mask == 0xffff)
60 return format (s, "%u", pm->port);
62 return format (s, "%u/0x%x", pm->port, pm->mask);
66 unformat_ip_protocol_and_mask (unformat_input_t * input, va_list * args)
68 ip_prot_and_mask_t *pm = va_arg (*args, ip_prot_and_mask_t *);
69 u32 prot = 0, mask = 0;
71 if (unformat (input, "any"))
73 else if (unformat (input, "%U", unformat_ip_protocol, &prot))
75 else if (unformat (input, "%u", &prot))
80 if (prot > 0XFF || mask > 0xFF)
89 format_ip_protocol_and_mask (u8 * s, va_list * args)
91 ip_prot_and_mask_t *pm = va_arg (*args, ip_prot_and_mask_t *);
93 if (pm->prot == 0 && pm->mask == 0)
94 return format (s, "any");
96 return format (s, "%U", format_ip_protocol, pm->prot);
100 format_flow_error (u8 * s, va_list * args)
102 int error = va_arg (*args, int);
105 return format (s, "no error");
107 #define _(v,n,str) if (error == v) return format (s, #str);
111 return format (s, "unknown error (%d)", error);
115 format_flow_actions (u8 * s, va_list * args)
117 u32 actions = va_arg (*args, u32);
120 #define _(a, b, c) if (actions & (1 << a)) \
121 t = format (t, "%s%s", t ? " ":"", c);
124 s = format (s, "%v", t);
130 format_flow_enabled_hw (u8 * s, va_list * args)
132 u32 flow_index = va_arg (*args, u32);
133 vnet_flow_t *f = vnet_get_flow (flow_index);
135 return format (s, "not found");
140 vnet_main_t *vnm = vnet_get_main ();
142 hash_foreach (hw_if_index, private_data, f->private_data,
144 t = format (t, "%s%U", t ? ", " : "",
145 format_vnet_hw_if_index_name, vnm, hw_if_index);
148 s = format (s, "%v", t);
154 format_rss_function (u8 * s, va_list * args)
156 vnet_rss_function_t func = va_arg (*args, vnet_rss_function_t);
162 else if (func == VNET_RSS_FUNC_##f) \
163 return format (s, n);
167 return format (s, "unknown");
171 format_rss_types (u8 * s, va_list * args)
173 u64 type = va_arg (*args, u64);
177 if (type & (1UL<<a)) \
178 s = format (s, "%s ", c);
180 foreach_flow_rss_types
185 static const char *flow_type_strings[] = { 0,
191 static clib_error_t *
192 show_flow_entry (vlib_main_t * vm, unformat_input_t * input,
193 vlib_cli_command_t * cmd_arg)
195 vnet_main_t *vnm = vnet_get_main ();
196 vnet_flow_main_t *fm = &flow_main;
197 unformat_input_t _line_input, *line_input = &_line_input;
198 vnet_hw_interface_t *hi;
199 vnet_device_class_t *dev_class;
202 u32 index = ~0, hw_if_index;
204 if (!unformat_user (input, unformat_line_input, line_input))
207 while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
209 if (unformat (line_input, "index %u", &index))
212 return clib_error_return (0, "parse error: '%U'",
213 format_unformat_error, line_input);
216 unformat_free (line_input);
220 if ((f = vnet_get_flow (index)) == 0)
221 return clib_error_return (0, "no such flow");
223 vlib_cli_output (vm, "%-10s: %u", "index", f->index);
224 vlib_cli_output (vm, "%-10s: %s", "type", flow_type_strings[f->type]);
225 vlib_cli_output (vm, "%-10s: %U", "match", format_flow, f);
226 if (f->type == VNET_FLOW_TYPE_GENERIC)
228 vlib_cli_output (vm, "%s: %s", "spec", f->generic.pattern.spec);
229 vlib_cli_output (vm, "%s: %s", "mask", f->generic.pattern.mask);
232 hash_foreach (hw_if_index, private_data, f->private_data,
234 hi = vnet_get_hw_interface (vnm, hw_if_index);
235 dev_class = vnet_get_device_class (vnm, hi->dev_class_index);
236 vlib_cli_output (vm, "interface %U\n",
237 format_vnet_hw_if_index_name, vnm, hw_if_index);
238 if (dev_class->format_flow)
239 vlib_cli_output (vm, " %U\n", dev_class->format_flow,
240 hi->dev_instance, f->index, private_data);
248 pool_foreach (f, fm->global_flow_pool)
250 vlib_cli_output (vm, "%U\n", format_flow, f);
251 if (f->type == VNET_FLOW_TYPE_GENERIC)
253 vlib_cli_output (vm, "%s: %s", "spec", f->generic.pattern.spec);
254 vlib_cli_output (vm, "%s: %s", "mask", f->generic.pattern.mask);
263 VLIB_CLI_COMMAND (show_flow_entry_command, static) = {
264 .path = "show flow entry",
265 .short_help = "show flow entry [index <index>]",
266 .function = show_flow_entry,
270 static clib_error_t *
271 show_flow_ranges (vlib_main_t * vm, unformat_input_t * input,
272 vlib_cli_command_t * cmd_arg)
274 vnet_flow_main_t *fm = &flow_main;
275 vnet_flow_range_t *r = 0;
277 vlib_cli_output (vm, "%8s %8s %s", "Start", "Count", "Owner");
280 vec_foreach (r, fm->ranges)
282 vlib_cli_output (vm, "%8u %8u %s", r->start, r->count, r->owner);
289 VLIB_CLI_COMMAND (show_flow_ranges_command, static) = {
290 .path = "show flow ranges",
291 .short_help = "show flow ranges",
292 .function = show_flow_ranges,
296 static clib_error_t *
297 show_flow_interface (vlib_main_t * vm, unformat_input_t * input,
298 vlib_cli_command_t * cmd_arg)
300 vnet_main_t *vnm = vnet_get_main ();
301 vnet_hw_interface_t *hi;
302 vnet_device_class_t *dev_class;
303 unformat_input_t _line_input, *line_input = &_line_input;
304 u32 hw_if_index = ~0;
306 if (unformat_user (input, unformat_line_input, line_input))
308 while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
310 if (unformat (line_input, "%U",
311 unformat_vnet_hw_interface, vnm, &hw_if_index))
314 return clib_error_return (0, "parse error: '%U'",
315 format_unformat_error, line_input);
317 unformat_free (line_input);
320 if (hw_if_index == ~0)
321 return clib_error_return (0, "please specify interface");
323 hi = vnet_get_hw_interface (vnm, hw_if_index);
324 dev_class = vnet_get_device_class (vnm, hi->dev_class_index);
325 if (dev_class->format_flow == 0)
326 return clib_error_return (0, "not supported");
328 vlib_cli_output (vm, "%U", dev_class->format_flow, hi->dev_instance, ~0, 0);
333 VLIB_CLI_COMMAND (show_flow_interface_command, static) = {
334 .path = "show flow interface",
335 .short_help = "show flow interface <interface name>",
336 .function = show_flow_interface,
340 static clib_error_t *
341 test_flow (vlib_main_t * vm, unformat_input_t * input,
342 vlib_cli_command_t * cmd_arg)
345 vnet_main_t *vnm = vnet_get_main ();
346 unformat_input_t _line_input, *line_input = &_line_input;
354 } action = FLOW_UNKNOWN_ACTION;
361 } flow_class = FLOW_UNKNOWN_CLASS;
363 u32 hw_if_index = ~0, flow_index = ~0;
365 u32 teid = 0, session_id = 0, spi = 0;
367 u32 queue_start = 0, queue_end = 0;
368 vnet_flow_type_t type = VNET_FLOW_TYPE_UNKNOWN;
369 ip4_address_and_mask_t ip4s = { };
370 ip4_address_and_mask_t ip4d = { };
371 ip6_address_and_mask_t ip6s = { };
372 ip6_address_and_mask_t ip6d = { };
373 ip_port_and_mask_t sport = { };
374 ip_port_and_mask_t dport = { };
375 ip_prot_and_mask_t protocol = { };
377 bool tcp_udp_port_set = false;
378 bool gtpc_set = false;
379 bool gtpu_set = false;
380 bool vni_set = false;
381 bool l2tpv3oip_set = false;
382 bool ipsec_esp_set = false, ipsec_ah_set = false;
383 u8 *rss_type[3] = { };
388 clib_memset (&flow, 0, sizeof (vnet_flow_t));
392 if (!unformat_user (input, unformat_line_input, line_input))
395 while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
397 if (unformat (line_input, "add"))
399 else if (unformat (line_input, "del"))
401 else if (unformat (line_input, "enable"))
402 action = FLOW_ENABLE;
403 else if (unformat (line_input, "disable"))
404 action = FLOW_DISABLE;
405 else if (unformat (line_input, "spec %s", &spec))
407 else if (unformat (line_input, "mask %s", &mask))
409 else if (unformat (line_input, "eth-type %U",
410 unformat_ethernet_type_host_byte_order, ð_type))
411 flow_class = FLOW_ETHERNET_CLASS;
412 else if (unformat (line_input, "src-ip %U",
413 unformat_ip4_address_and_mask, &ip4s))
414 flow_class = FLOW_IPV4_CLASS;
415 else if (unformat (line_input, "dst-ip %U",
416 unformat_ip4_address_and_mask, &ip4d))
417 flow_class = FLOW_IPV4_CLASS;
418 else if (unformat (line_input, "ip6-src-ip %U",
419 unformat_ip6_address_and_mask, &ip6s))
420 flow_class = FLOW_IPV6_CLASS;
421 else if (unformat (line_input, "ip6-dst-ip %U",
422 unformat_ip6_address_and_mask, &ip6d))
423 flow_class = FLOW_IPV6_CLASS;
424 else if (unformat (line_input, "src-port %U", unformat_ip_port_and_mask,
426 tcp_udp_port_set = true;
427 else if (unformat (line_input, "dst-port %U", unformat_ip_port_and_mask,
429 tcp_udp_port_set = true;
432 (line_input, "proto %U", unformat_ip_protocol_and_mask,
435 else if (unformat (line_input, "gtpc teid %u", &teid))
437 else if (unformat (line_input, "gtpu teid %u", &teid))
439 else if (unformat (line_input, "vxlan vni %u", &vni))
441 else if (unformat (line_input, "session id %u", &session_id))
443 if (protocol.prot == IP_PROTOCOL_L2TP)
444 l2tpv3oip_set = true;
446 else if (unformat (line_input, "spi %u", &spi))
448 if (protocol.prot == IP_PROTOCOL_IPSEC_ESP)
449 ipsec_esp_set = true;
450 else if (protocol.prot == IP_PROTOCOL_IPSEC_AH)
453 else if (unformat (line_input, "index %u", &flow_index))
455 else if (unformat (line_input, "next-node %U", unformat_vlib_node, vm,
456 &flow.redirect_node_index))
457 flow.actions |= VNET_FLOW_ACTION_REDIRECT_TO_NODE;
458 else if (unformat (line_input, "mark %d", &flow.mark_flow_id))
459 flow.actions |= VNET_FLOW_ACTION_MARK;
460 else if (unformat (line_input, "buffer-advance %d",
461 &flow.buffer_advance))
462 flow.actions |= VNET_FLOW_ACTION_BUFFER_ADVANCE;
463 else if (unformat (line_input, "redirect-to-queue %d",
464 &flow.redirect_queue))
465 flow.actions |= VNET_FLOW_ACTION_REDIRECT_TO_QUEUE;
466 else if (unformat (line_input, "drop"))
467 flow.actions |= VNET_FLOW_ACTION_DROP;
468 else if (unformat (line_input, "rss function"))
474 else if (unformat (line_input, s)) \
475 flow.rss_fun = VNET_RSS_FUNC_##f;
481 return clib_error_return (0, "unknown input `%U'",
482 format_unformat_error, line_input);
485 flow.actions |= VNET_FLOW_ACTION_RSS;
487 else if (unformat (line_input, "rss types"))
494 if (unformat (line_input, "%s use %s and %s",
495 &rss_type[0], &rss_type[1], &rss_type[2]))
498 (line_input, "%s use %s", &rss_type[0], &rss_type[1]))
500 else if (unformat (line_input, "%s", &rss_type[0]))
505 else if (!clib_strcmp(c, (const char *)type_str)) \
506 flow.rss_types |= (1ULL<<a);
508 #define check_rss_types(_str) \
513 foreach_flow_rss_types \
516 return clib_error_return (0, "parse error: '%U'", \
517 format_unformat_error, line_input); \
521 check_rss_types (rss_type[0])
522 check_rss_types (rss_type[1]) check_rss_types (rss_type[2])
524 flow.actions |= VNET_FLOW_ACTION_RSS;
526 else if (unformat (line_input, "rss queues"))
528 if (unformat (line_input, "%d to %d", &queue_start, &queue_end))
532 return clib_error_return (0, "unknown input `%U'",
533 format_unformat_error, line_input);
536 flow.queue_index = queue_start;
537 flow.queue_num = queue_end - queue_start + 1;
539 flow.actions |= VNET_FLOW_ACTION_RSS;
541 else if (unformat (line_input, "%U", unformat_vnet_hw_interface, vnm,
545 return clib_error_return (0, "parse error: '%U'",
546 format_unformat_error, line_input);
549 unformat_free (line_input);
551 if (hw_if_index == ~0 && (action == FLOW_ENABLE || action == FLOW_DISABLE))
552 return clib_error_return (0, "Please specify interface name");
554 if (flow_index == ~0 && (action == FLOW_ENABLE || action == FLOW_DISABLE ||
556 return clib_error_return (0, "Please specify flow index");
561 if (flow.actions == 0)
562 return clib_error_return (0, "Please specify at least one action");
564 /* Adjust the flow type */
567 case FLOW_ETHERNET_CLASS:
568 type = VNET_FLOW_TYPE_ETHERNET;
571 case FLOW_IPV4_CLASS:
574 type = VNET_FLOW_TYPE_IP4_GTPC;
575 protocol.prot = IP_PROTOCOL_UDP;
579 type = VNET_FLOW_TYPE_IP4_GTPU;
580 protocol.prot = IP_PROTOCOL_UDP;
584 type = VNET_FLOW_TYPE_IP4_VXLAN;
585 protocol.prot = IP_PROTOCOL_UDP;
587 else if (l2tpv3oip_set)
588 type = VNET_FLOW_TYPE_IP4_L2TPV3OIP;
589 else if (ipsec_esp_set)
590 type = VNET_FLOW_TYPE_IP4_IPSEC_ESP;
591 else if (ipsec_ah_set)
592 type = VNET_FLOW_TYPE_IP4_IPSEC_AH;
593 else if (tcp_udp_port_set)
594 type = VNET_FLOW_TYPE_IP4_N_TUPLE;
596 type = VNET_FLOW_TYPE_IP4;
598 case FLOW_IPV6_CLASS:
599 if (tcp_udp_port_set)
600 type = VNET_FLOW_TYPE_IP6_N_TUPLE;
602 type = VNET_FLOW_TYPE_IP6_VXLAN;
604 type = VNET_FLOW_TYPE_IP6;
610 type = VNET_FLOW_TYPE_GENERIC;
613 return clib_error_return (0,
614 "Please specify a supported flow type");
617 /* Assign specific field values per flow type */
618 if (flow_class == FLOW_ETHERNET_CLASS)
620 flow.ethernet.eth_hdr.type = eth_type;
622 else if (flow_class == FLOW_IPV4_CLASS)
624 vnet_flow_ip4_t *ip4_ptr = &flow.ip4;
626 clib_memcpy (&ip4_ptr->src_addr, &ip4s,
627 sizeof (ip4_address_and_mask_t));
628 clib_memcpy (&ip4_ptr->dst_addr, &ip4d,
629 sizeof (ip4_address_and_mask_t));
630 ip4_ptr->protocol.prot = protocol.prot;
632 /* In this cli, we use the protocol.mask only when the flow type is
633 * VNET_FLOW_TYPE_IP4/IP6. For other cases, the IP protocol is just
634 * used to identify the next layer type: e.g. UDP/TCP or IPSEC_ESP
636 if (type == VNET_FLOW_TYPE_IP4)
637 ip4_ptr->protocol.mask = protocol.mask;
639 switch (protocol.prot)
642 case IP_PROTOCOL_TCP:
643 case IP_PROTOCOL_UDP:
644 flow.ip4_n_tuple.src_port = sport;
645 flow.ip4_n_tuple.dst_port = dport;
647 if (type == VNET_FLOW_TYPE_IP4_GTPC)
648 flow.ip4_gtpc.teid = teid;
649 else if (type == VNET_FLOW_TYPE_IP4_GTPU)
650 flow.ip4_gtpu.teid = teid;
651 else if (type == VNET_FLOW_TYPE_IP4_VXLAN)
652 flow.ip4_vxlan.vni = vni;
654 case IP_PROTOCOL_L2TP:
655 flow.ip4_l2tpv3oip.session_id = session_id;
657 case IP_PROTOCOL_IPSEC_ESP:
658 flow.ip4_ipsec_esp.spi = spi;
660 case IP_PROTOCOL_IPSEC_AH:
661 flow.ip4_ipsec_esp.spi = spi;
667 else if (flow_class == FLOW_IPV6_CLASS)
669 vnet_flow_ip6_t *ip6_ptr = &flow.ip6;
671 clib_memcpy (&flow.ip6_n_tuple.src_addr, &ip6s,
672 sizeof (ip6_address_and_mask_t));
673 clib_memcpy (&flow.ip6_n_tuple.dst_addr, &ip6d,
674 sizeof (ip6_address_and_mask_t));
676 ip6_ptr->protocol.prot = protocol.prot;
678 /* In this cli, we use the protocol.mask only when the flow type is
679 * VNET_FLOW_TYPE_IP4/IP6. For other cases, the IP protocol is just
680 * used to identify the next layer type: e.g. UDP/TCP or IPSEC_ESP
682 if (type == VNET_FLOW_TYPE_IP6)
683 ip6_ptr->protocol.mask = protocol.mask;
685 switch (protocol.prot)
688 case IP_PROTOCOL_TCP:
689 case IP_PROTOCOL_UDP:
690 flow.ip6_n_tuple.src_port = sport;
691 flow.ip6_n_tuple.dst_port = dport;
693 if (type == VNET_FLOW_TYPE_IP6_VXLAN)
694 flow.ip6_vxlan.vni = vni;
700 if (type == VNET_FLOW_TYPE_GENERIC)
702 clib_memcpy (flow.generic.pattern.spec, spec,
703 sizeof (flow.generic.pattern.spec));
704 clib_memcpy (flow.generic.pattern.mask, mask,
705 sizeof (flow.generic.pattern.mask));
709 rv = vnet_flow_add (vnm, &flow, &flow_index);
711 vlib_cli_output (vm, "flow %u added", flow_index);
715 rv = vnet_flow_del (vnm, flow_index);
718 rv = vnet_flow_enable (vnm, flow_index, hw_if_index);
721 rv = vnet_flow_disable (vnm, flow_index, hw_if_index);
724 return clib_error_return (0, "please specify action (add, del, enable,"
729 return clib_error_return (0, "flow error: %U", format_flow_error, rv);
735 VLIB_CLI_COMMAND (test_flow_command, static) = {
737 .short_help = "test flow [add|del|enable|disable] [index <id>] "
738 "[src-ip <ip-addr/mask>] [dst-ip <ip-addr/mask>] "
739 "[ip6-src-ip <ip-addr/mask>] [ip6-dst-ip <ip-addr/mask>] "
740 "[src-port <port/mask>] [dst-port <port/mask>] "
741 "[proto <ip-proto>] "
742 "[gtpc teid <teid>] [gtpu teid <teid>] [vxlan <vni>] "
743 "[session id <session>] [spi <spi>]"
744 "[spec <spec string>] [mask <mask string>]"
745 "[next-node <node>] [mark <id>] [buffer-advance <len>] "
746 "[redirect-to-queue <queue>] [drop] "
747 "[rss function <name>] [rss types <flow type>]"
748 "[rss queues <queue_start> to <queue_end>]",
749 .function = test_flow,
754 format_flow_match_element (u8 * s, va_list * args)
756 char *type = va_arg (*args, char *);
757 void *ptr = va_arg (*args, void *);
759 if (strncmp (type, "u8", 2) == 0)
760 return format (s, "%d", *(u8 *) ptr);
762 if (strncmp (type, "u16", 3) == 0)
763 return format (s, "%d", *(u16 *) ptr);
765 if (strncmp (type, "u32", 3) == 0)
766 return format (s, "%d", *(u32 *) ptr);
768 if (strncmp (type, "ethernet_header_t", 13) == 0)
770 ethernet_max_header_t m;
771 memset (&m, 0, sizeof (m));
772 m.ethernet = *(ethernet_header_t *) ptr;
773 /* convert the ethernet type to net order */
774 m.ethernet.type = clib_host_to_net_u16 (m.ethernet.type);
775 return format (s, "%U", format_ethernet_header, &m);
778 if (strncmp (type, "ip4_address_t", 13) == 0)
779 return format (s, "%U", format_ip4_address, ptr);
781 if (strncmp (type, "ip4_address_and_mask_t", 13) == 0)
782 return format (s, "%U", format_ip4_address_and_mask, ptr);
784 if (strncmp (type, "ip6_address_t", 13) == 0)
785 return format (s, "%U", format_ip6_address, ptr);
787 if (strncmp (type, "ip6_address_and_mask_t", 13) == 0)
788 return format (s, "%U", format_ip6_address_and_mask, ptr);
790 if (strncmp (type, "ip_prot_and_mask_t", 13) == 0)
791 return format (s, "%U", format_ip_protocol_and_mask, ptr);
793 if (strncmp (type, "ip_port_and_mask_t", 18) == 0)
794 return format (s, "%U", format_ip_port_and_mask, ptr);
796 s = format (s, "unknown type '%s'", type);
800 #define _fe(a,b) s2 = format (s2, "%s%s %U", s2 ? ", ":"", #b, \
801 format_flow_match_element, #a, &f->b);
803 u8 * format_flow_match_##b (u8 * s, va_list * args) \
805 vnet_flow_##b##_t *f = __builtin_va_arg (*args, vnet_flow_##b##_t *); \
807 foreach_flow_entry_##b \
808 s = format (s, "%v", s2);; \
816 format_flow_match (u8 * s, va_list * args)
818 vnet_flow_t *f = va_arg (*args, vnet_flow_t *);
821 if (f->type == VNET_FLOW_TYPE_##a) \
822 return format (s, "%U", format_flow_match_##b, &f->b);
830 format_flow (u8 * s, va_list * args)
832 vlib_main_t *vm = vlib_get_main ();
833 vnet_flow_t *f = va_arg (*args, vnet_flow_t *);
834 u32 indent = format_get_indent (s);
837 s = format (s, "flow-index %u type %s active %u",
838 f->index, flow_type_strings[f->type],
839 hash_elts (f->private_data)),
840 s = format (s, "\n%Umatch: %U", format_white_space, indent + 2,
841 format_flow_match, f);
842 s = format (s, "\n%Uaction: %U", format_white_space, indent + 2,
843 format_flow_actions, f->actions);
845 if (f->actions & VNET_FLOW_ACTION_DROP)
846 t = format (t, "%sdrop", t ? ", " : "");
848 if (f->actions & VNET_FLOW_ACTION_MARK)
849 t = format (t, "%smark %u", t ? ", " : "", f->mark_flow_id);
851 if (f->actions & VNET_FLOW_ACTION_REDIRECT_TO_QUEUE)
853 format (t, "%sredirect-to-queue %u", t ? ", " : "", f->redirect_queue);
855 if (f->actions & VNET_FLOW_ACTION_REDIRECT_TO_NODE)
856 t = format (t, "%snext-node %U", t ? ", " : "",
857 format_vlib_node_name, vm, f->redirect_node_index);
859 if (f->actions & VNET_FLOW_ACTION_BUFFER_ADVANCE)
860 t = format (t, "%sbuffer-advance %d", t ? ", " : "", f->buffer_advance);
862 if (f->actions & VNET_FLOW_ACTION_RSS)
864 t = format (t, "%srss function %U", t ? ", " : "",
865 format_rss_function, f->rss_fun);
866 t = format (t, "%srss types %U", t ? ", " : "",
867 format_rss_types, f->rss_types);
872 s = format (s, "\n%U%v", format_white_space, indent + 4, t);
880 * fd.io coding-style-patch-verification: ON
883 * eval: (c-set-style "gnu")