aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorBenoît Ganne <bganne@cisco.com>2019-06-26 13:36:51 +0200
committerNeale Ranns <nranns@cisco.com>2019-07-02 14:19:07 +0000
commitcfc7a107e6cb8be6e7c53a08e23a146c431c8e90 (patch)
tree2179e92193ce7453b6eb67ba94a3c881f998f4fc /src
parent2ec825937b7ac856f67d086ce6814dd21c5e9bd7 (diff)
gbp: add anonymous l3-out external interfaces
So far, GBP l3-out packets classification & policy relied on programmed EP. All traffic to/from l3-out must go through a known EP. This patch introduces a new feature where l3-out next-hops are only known by their subnets (l3-out prefixes). As there are no longer known EPs to program, an interface must be configured as external anonymous l3-out. Packets classification & policy on this interface will rely on the external subnets programmed in the BD VRF. Note that contrary to all other interfaces in a GBP BD, external anonymous l3-out interfaces have BD L2 learning turned on and rely on ARP/ND. Type: feature Change-Id: Ieedb29dff4e967d08c4301e82d06bff450a63e5f Signed-off-by: Benoît Ganne <bganne@cisco.com>
Diffstat (limited to 'src')
-rw-r--r--src/plugins/gbp/gbp.api8
-rw-r--r--src/plugins/gbp/gbp.h11
-rw-r--r--src/plugins/gbp/gbp_api.c31
-rw-r--r--src/plugins/gbp/gbp_api_print.h23
-rw-r--r--src/plugins/gbp/gbp_classify.c8
-rw-r--r--src/plugins/gbp/gbp_classify.h45
-rw-r--r--src/plugins/gbp/gbp_classify_node.c216
-rw-r--r--src/plugins/gbp/gbp_ext_itf.c114
-rw-r--r--src/plugins/gbp/gbp_ext_itf.h3
-rw-r--r--src/plugins/gbp/gbp_policy.c14
-rw-r--r--src/plugins/gbp/gbp_policy_dpo.h29
-rw-r--r--src/plugins/gbp/gbp_policy_node.c135
-rw-r--r--src/vnet/l2/l2_input.h1
-rw-r--r--src/vnet/l2/l2_output.h1
14 files changed, 483 insertions, 156 deletions
diff --git a/src/plugins/gbp/gbp.api b/src/plugins/gbp/gbp.api
index f6775e7dcd7..d1e483d5e5d 100644
--- a/src/plugins/gbp/gbp.api
+++ b/src/plugins/gbp/gbp.api
@@ -419,6 +419,14 @@ define gbp_ext_itf_details
vl_api_gbp_ext_itf_t ext_itf;
};
+manual_print autoreply define gbp_ext_itf_anon_add_del
+{
+ u32 client_index;
+ u32 context;
+ u8 is_add;
+ vl_api_gbp_ext_itf_t ext_itf;
+};
+
/*
* Local Variables:
* eval: (c-set-style "gnu")
diff --git a/src/plugins/gbp/gbp.h b/src/plugins/gbp/gbp.h
index 35e02d26ca7..e194a9df82c 100644
--- a/src/plugins/gbp/gbp.h
+++ b/src/plugins/gbp/gbp.h
@@ -48,6 +48,15 @@ typedef struct
extern gbp_main_t gbp_main;
+typedef enum gbp_policy_type_t_
+{
+ GBP_POLICY_PORT,
+ GBP_POLICY_MAC,
+ GBP_POLICY_LPM,
+ GBP_N_POLICY
+#define GBP_N_POLICY GBP_N_POLICY
+} gbp_policy_type_t;
+
/**
* Grouping of global data for the GBP source EPG classification feature
*/
@@ -56,7 +65,7 @@ typedef struct gbp_policy_main_t_
/**
* Next nodes for L2 output features
*/
- u32 l2_output_feat_next[2][32];
+ u32 l2_output_feat_next[GBP_N_POLICY][32];
} gbp_policy_main_t;
extern gbp_policy_main_t gbp_policy_main;
diff --git a/src/plugins/gbp/gbp_api.c b/src/plugins/gbp/gbp_api.c
index 8155a8ff0f1..010e3087eb3 100644
--- a/src/plugins/gbp/gbp_api.c
+++ b/src/plugins/gbp/gbp_api.c
@@ -82,7 +82,8 @@
_(GBP_CONTRACT_DUMP, gbp_contract_dump) \
_(GBP_VXLAN_TUNNEL_ADD, gbp_vxlan_tunnel_add) \
_(GBP_VXLAN_TUNNEL_DEL, gbp_vxlan_tunnel_del) \
- _(GBP_VXLAN_TUNNEL_DUMP, gbp_vxlan_tunnel_dump)
+ _(GBP_VXLAN_TUNNEL_DUMP, gbp_vxlan_tunnel_dump) \
+ _(GBP_EXT_ITF_ANON_ADD_DEL, gbp_ext_itf_anon_add_del)
gbp_main_t gbp_main;
@@ -1146,6 +1147,34 @@ vl_api_gbp_vxlan_tunnel_dump_t_handler (vl_api_gbp_vxlan_tunnel_dump_t * mp)
gbp_vxlan_walk (gbp_vxlan_tunnel_send_details, &ctx);
}
+static void
+vl_api_gbp_ext_itf_anon_add_del_t_handler (vl_api_gbp_ext_itf_anon_add_del_t *
+ mp)
+{
+ vl_api_gbp_ext_itf_anon_add_del_reply_t *rmp;
+ u32 sw_if_index = ~0;
+ vl_api_gbp_ext_itf_t *ext_itf;
+ int rv = 0;
+
+ ext_itf = &mp->ext_itf;
+ if (ext_itf)
+ sw_if_index = ntohl (ext_itf->sw_if_index);
+
+ if (!vnet_sw_if_index_is_api_valid (sw_if_index))
+ goto bad_sw_if_index;
+
+ if (mp->is_add)
+ rv = gbp_ext_itf_anon_add (sw_if_index,
+ ntohl (ext_itf->bd_id),
+ ntohl (ext_itf->rd_id));
+ else
+ rv = gbp_ext_itf_anon_delete (sw_if_index);
+
+ BAD_SW_IF_INDEX_LABEL;
+
+ REPLY_MACRO (VL_API_GBP_EXT_ITF_ANON_ADD_DEL_REPLY + GBP_MSG_BASE);
+}
+
/*
* gbp_api_hookup
* Add vpe's API message handlers to the table.
diff --git a/src/plugins/gbp/gbp_api_print.h b/src/plugins/gbp/gbp_api_print.h
index 67cd30c7da7..b3afc94dc4b 100644
--- a/src/plugins/gbp/gbp_api_print.h
+++ b/src/plugins/gbp/gbp_api_print.h
@@ -340,6 +340,29 @@ vl_api_gbp_ext_itf_add_del_t_print (vl_api_gbp_ext_itf_add_del_t * a,
return handle;
}
+static inline void *
+vl_api_gbp_ext_itf_anon_add_del_t_print (vl_api_gbp_ext_itf_anon_add_del_t *
+ a, void *handle)
+{
+ u8 *s = 0;
+
+ s = format (s, "SCRIPT: gbp_ext_itf_anon_add_del ");
+ if (a->is_add)
+ s = format (s, "add ");
+ else
+ s = format (s, "del ");
+
+ s = format (s, "sw_if_index %d ", ntohl (a->ext_itf.sw_if_index));
+ s = format (s, "bd_id %d ", ntohl (a->ext_itf.bd_id));
+ s = format (s, "rd_id %d ", ntohl (a->ext_itf.rd_id));
+
+ s = format (s, "\n");
+
+ PRINT_S;
+
+ return handle;
+}
+
/*
* fd.io coding-style-patch-verification: ON
*
diff --git a/src/plugins/gbp/gbp_classify.c b/src/plugins/gbp/gbp_classify.c
index 5735f359af0..255db252871 100644
--- a/src/plugins/gbp/gbp_classify.c
+++ b/src/plugins/gbp/gbp_classify.c
@@ -49,6 +49,14 @@ gbp_src_classify_init (vlib_main_t * vm)
l2input_get_feat_names (),
em->l2_input_feat_next[GBP_SRC_CLASSIFY_LPM]);
+ node = vlib_get_node_by_name (vm, (u8 *) "l2-gbp-lpm-anon-classify");
+ feat_bitmap_init_next_nodes (vm,
+ node->index,
+ L2INPUT_N_FEAT,
+ l2input_get_feat_names (),
+ em->l2_input_feat_next
+ [GBP_SRC_CLASSIFY_LPM_ANON]);
+
return 0;
}
diff --git a/src/plugins/gbp/gbp_classify.h b/src/plugins/gbp/gbp_classify.h
index c0c1fd53dc5..ca7db94a2c0 100644
--- a/src/plugins/gbp/gbp_classify.h
+++ b/src/plugins/gbp/gbp_classify.h
@@ -19,16 +19,18 @@
#define __GBP_CLASSIFY_H__
#include <plugins/gbp/gbp.h>
+#include <vnet/ethernet/arp_packet.h>
typedef enum gbp_src_classify_type_t_
{
GBP_SRC_CLASSIFY_NULL,
GBP_SRC_CLASSIFY_PORT,
GBP_SRC_CLASSIFY_LPM,
+ GBP_SRC_CLASSIFY_LPM_ANON,
+ GBP_SRC_N_CLASSIFY
+#define GBP_SRC_N_CLASSIFY GBP_SRC_N_CLASSIFY
} gbp_src_classify_type_t;
-#define GBP_SRC_N_CLASSIFY (GBP_SRC_CLASSIFY_LPM + 1)
-
/**
* Grouping of global data for the GBP source EPG classification feature
*/
@@ -42,6 +44,45 @@ typedef struct gbp_src_classify_main_t_
extern gbp_src_classify_main_t gbp_src_classify_main;
+enum gbp_classify_get_ip_way
+{
+ GBP_CLASSIFY_GET_IP_SRC = 0,
+ GBP_CLASSIFY_GET_IP_DST = 1
+};
+
+static_always_inline dpo_proto_t
+gbp_classify_get_ip_address (const ethernet_header_t * eh0,
+ const ip4_address_t ** ip4,
+ const ip6_address_t ** ip6,
+ const enum gbp_classify_get_ip_way way)
+{
+ u16 etype = clib_net_to_host_u16 (eh0->type);
+ const void *l3h0 = eh0 + 1;
+
+ if (ETHERNET_TYPE_VLAN == etype)
+ {
+ const ethernet_vlan_header_t *vh0 =
+ (ethernet_vlan_header_t *) (eh0 + 1);
+ etype = clib_net_to_host_u16 (vh0->type);
+ l3h0 = vh0 + 1;
+ }
+
+ switch (etype)
+ {
+ case ETHERNET_TYPE_IP4:
+ *ip4 = &(&((const ip4_header_t *) l3h0)->src_address)[way];
+ return DPO_PROTO_IP4;
+ case ETHERNET_TYPE_IP6:
+ *ip6 = &(&((const ip6_header_t *) l3h0)->src_address)[way];
+ return DPO_PROTO_IP6;
+ case ETHERNET_TYPE_ARP:
+ *ip4 = &((ethernet_arp_header_t *) l3h0)->ip4_over_ethernet[way].ip4;
+ return DPO_PROTO_IP4;
+ }
+
+ return DPO_PROTO_NONE;
+}
+
#endif
/*
diff --git a/src/plugins/gbp/gbp_classify_node.c b/src/plugins/gbp/gbp_classify_node.c
index 9ad2b06148d..a2058a21284 100644
--- a/src/plugins/gbp/gbp_classify_node.c
+++ b/src/plugins/gbp/gbp_classify_node.c
@@ -279,36 +279,6 @@ typedef enum gbp_lpm_classify_next_t_
GPB_LPM_CLASSIFY_DROP,
} gbp_lpm_classify_next_t;
-always_inline void
-gbp_classify_get_src_ip_address (const ethernet_header_t * eh0,
- const ip4_address_t ** ip4,
- const ip6_address_t ** ip6)
-{
- u16 etype = clib_net_to_host_u16 (eh0->type);
- const void *l3h0 = eh0 + 1;
-
- if (ETHERNET_TYPE_VLAN == etype)
- {
- const ethernet_vlan_header_t *vh0 =
- (ethernet_vlan_header_t *) (eh0 + 1);
- etype = clib_net_to_host_u16 (vh0->type);
- l3h0 = vh0 + 1;
- }
-
- switch (etype)
- {
- case ETHERNET_TYPE_IP4:
- *ip4 = &((const ip4_header_t *) l3h0)->src_address;
- break;
- case ETHERNET_TYPE_IP6:
- *ip6 = &((const ip6_header_t *) l3h0)->src_address;
- break;
- case ETHERNET_TYPE_ARP:
- *ip4 = &((ethernet_arp_header_t *) l3h0)->ip4_over_ethernet[0].ip4;
- break;
- }
-}
-
/**
* per-packet trace data
*/
@@ -333,6 +303,13 @@ format_gbp_lpm_classify_trace (u8 * s, va_list * args)
return s;
}
+enum gbp_lpm_type
+{
+ GBP_LPM_RECIRC,
+ GBP_LPM_EPG,
+ GBP_LPM_ANON
+};
+
/*
* Determine the SRC EPG from a LPM
*/
@@ -340,7 +317,8 @@ always_inline uword
gbp_lpm_classify_inline (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame,
- dpo_proto_t dproto, u8 is_recirc)
+ const dpo_proto_t dproto,
+ const enum gbp_lpm_type type)
{
gbp_src_classify_main_t *gscm = &gbp_src_classify_main;
u32 n_left_from, *from, *to_next;
@@ -366,8 +344,6 @@ gbp_lpm_classify_inline (vlib_main_t * vm,
const ip4_address_t *ip4_0;
const ip6_address_t *ip6_0;
const gbp_recirc_t *gr0;
- const dpo_id_t *dpo0;
- load_balance_t *lb0;
vlib_buffer_t *b0;
sclass_t sclass0;
@@ -397,10 +373,11 @@ gbp_lpm_classify_inline (vlib_main_t * vm,
else if (DPO_PROTO_ETHERNET == dproto)
{
eh0 = vlib_buffer_get_current (b0);
- gbp_classify_get_src_ip_address (eh0, &ip4_0, &ip6_0);
+ gbp_classify_get_ip_address (eh0, &ip4_0, &ip6_0,
+ GBP_CLASSIFY_GET_IP_SRC);
}
- if (is_recirc)
+ if (GBP_LPM_RECIRC == type)
{
gr0 = gbp_recirc_get (sw_if_index0);
fib_index0 = gr0->gr_fib_index[dproto];
@@ -417,96 +394,105 @@ gbp_lpm_classify_inline (vlib_main_t * vm,
goto trace;
}
- ge0 = gbp_endpoint_find_mac (eh0->src_address,
- vnet_buffer (b0)->l2.bd_index);
-
- if (NULL == ge0)
+ if (GBP_LPM_ANON == type)
{
- /* packet must have come from an EP's mac */
- sclass0 = SCLASS_INVALID;
- goto trace;
- }
-
- fib_index0 = ge0->ge_fwd.gef_fib_index;
-
- if (~0 == fib_index0)
- {
- sclass0 = SCLASS_INVALID;
- goto trace;
- }
-
- if (ip4_0)
- {
- ge_lpm0 = gbp_endpoint_find_ip4 (ip4_0, fib_index0);
- }
- else if (ip6_0)
- {
- ge_lpm0 = gbp_endpoint_find_ip6 (ip6_0, fib_index0);
+ /*
+ * anonymous LPM classification: only honour LPM as no EP
+ * were programmed
+ */
+ gbp_ext_itf_t *gei = gbp_ext_itf_get (sw_if_index0);
+ if (ip4_0)
+ fib_index0 = gei->gx_fib_index[DPO_PROTO_IP4];
+ else if (ip6_0)
+ fib_index0 = gei->gx_fib_index[DPO_PROTO_IP6];
+ else
+ {
+ /* not IP so no LPM classify possible */
+ sclass0 = SCLASS_INVALID;
+ next0 = GPB_LPM_CLASSIFY_DROP;
+ goto trace;
+ }
+ next0 = vnet_l2_feature_next
+ (b0, gscm->l2_input_feat_next[GBP_SRC_CLASSIFY_LPM_ANON],
+ L2INPUT_FEAT_GBP_LPM_ANON_CLASSIFY);
}
else
{
- ge_lpm0 = NULL;
- }
+ /*
+ * not an anonymous LPM classification: check it comes from
+ * an EP, and use EP RD info
+ */
+ ge0 = gbp_endpoint_find_mac (eh0->src_address,
+ vnet_buffer (b0)->l2.bd_index);
- next0 = vnet_l2_feature_next
- (b0, gscm->l2_input_feat_next[GBP_SRC_CLASSIFY_LPM],
- L2INPUT_FEAT_GBP_LPM_CLASSIFY);
+ if (NULL == ge0)
+ {
+ /* packet must have come from an EP's mac */
+ sclass0 = SCLASS_INVALID;
+ goto trace;
+ }
- /*
- * if we found the EP by IP lookup, it must be from the EP
- * not a network behind it
- */
- if (NULL != ge_lpm0)
- {
- if (PREDICT_FALSE (ge0 != ge_lpm0))
+ fib_index0 = ge0->ge_fwd.gef_fib_index;
+
+ if (~0 == fib_index0)
{
- /* an EP spoofing another EP */
sclass0 = SCLASS_INVALID;
- next0 = GPB_LPM_CLASSIFY_DROP;
+ goto trace;
+ }
+
+ if (ip4_0)
+ {
+ ge_lpm0 = gbp_endpoint_find_ip4 (ip4_0, fib_index0);
+ }
+ else if (ip6_0)
+ {
+ ge_lpm0 = gbp_endpoint_find_ip6 (ip6_0, fib_index0);
}
else
{
- sclass0 = ge0->ge_fwd.gef_sclass;
+ ge_lpm0 = NULL;
+ }
+
+ next0 = vnet_l2_feature_next
+ (b0, gscm->l2_input_feat_next[GBP_SRC_CLASSIFY_LPM],
+ L2INPUT_FEAT_GBP_LPM_CLASSIFY);
+
+ /*
+ * if we found the EP by IP lookup, it must be from the EP
+ * not a network behind it
+ */
+ if (NULL != ge_lpm0)
+ {
+ if (PREDICT_FALSE (ge0 != ge_lpm0))
+ {
+ /* an EP spoofing another EP */
+ sclass0 = SCLASS_INVALID;
+ next0 = GPB_LPM_CLASSIFY_DROP;
+ }
+ else
+ {
+ sclass0 = ge0->ge_fwd.gef_sclass;
+ }
+ goto trace;
}
- goto trace;
}
}
- if (ip4_0)
- {
- lbi0 = ip4_fib_forwarding_lookup (fib_index0, ip4_0);
- }
- else if (ip6_0)
+ gpd0 = gbp_classify_get_gpd (ip4_0, ip6_0, fib_index0);
+ if (0 == gpd0)
{
- lbi0 =
- ip6_fib_table_fwding_lookup (&ip6_main, fib_index0, ip6_0);
- }
- else
- {
- /* not IP so no LPM classify possible */
+ /* could not classify => drop */
sclass0 = SCLASS_INVALID;
next0 = GPB_LPM_CLASSIFY_DROP;
goto trace;
}
- lb0 = load_balance_get (lbi0);
- dpo0 = load_balance_get_bucket_i (lb0, 0);
+
+ sclass0 = gpd0->gpd_sclass;
/* all packets from an external network should not be learned by the
* reciever. so set the Do-not-learn bit here */
vnet_buffer2 (b0)->gbp.flags = VXLAN_GBP_GPFLAGS_D;
- if (gbp_policy_dpo_type == dpo0->dpoi_type)
- {
- gpd0 = gbp_policy_dpo_get (dpo0->dpoi_index);
- sclass0 = gpd0->gpd_sclass;
- }
- else
- {
- /* could not classify => drop */
- sclass0 = SCLASS_INVALID;
- goto trace;
- }
-
trace:
vnet_buffer2 (b0)->gbp.sclass = sclass0;
@@ -537,21 +523,32 @@ VLIB_NODE_FN (gbp_ip4_lpm_classify_node) (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
- return (gbp_lpm_classify_inline (vm, node, frame, DPO_PROTO_IP4, 1));
+ return (gbp_lpm_classify_inline
+ (vm, node, frame, DPO_PROTO_IP4, GBP_LPM_RECIRC));
}
VLIB_NODE_FN (gbp_ip6_lpm_classify_node) (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
- return (gbp_lpm_classify_inline (vm, node, frame, DPO_PROTO_IP6, 1));
+ return (gbp_lpm_classify_inline
+ (vm, node, frame, DPO_PROTO_IP6, GBP_LPM_RECIRC));
}
VLIB_NODE_FN (gbp_l2_lpm_classify_node) (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
- return (gbp_lpm_classify_inline (vm, node, frame, DPO_PROTO_ETHERNET, 0));
+ return (gbp_lpm_classify_inline
+ (vm, node, frame, DPO_PROTO_ETHERNET, GBP_LPM_EPG));
+}
+
+VLIB_NODE_FN (gbp_l2_lpm_anon_classify_node) (vlib_main_t * vm,
+ vlib_node_runtime_t * node,
+ vlib_frame_t * frame)
+{
+ return (gbp_lpm_classify_inline
+ (vm, node, frame, DPO_PROTO_ETHERNET, GBP_LPM_ANON));
}
/* *INDENT-OFF* */
@@ -594,6 +591,19 @@ VLIB_REGISTER_NODE (gbp_l2_lpm_classify_node) = {
},
};
+VLIB_REGISTER_NODE (gbp_l2_lpm_anon_classify_node) = {
+ .name = "l2-gbp-lpm-anon-classify",
+ .vector_size = sizeof (u32),
+ .format_trace = format_gbp_lpm_classify_trace,
+ .type = VLIB_NODE_TYPE_INTERNAL,
+
+ .n_errors = 0,
+ .n_next_nodes = 1,
+ .next_nodes = {
+ [GPB_LPM_CLASSIFY_DROP] = "error-drop"
+ },
+};
+
VNET_FEATURE_INIT (gbp_ip4_lpm_classify_feat_node, static) =
{
.arc_name = "ip4-unicast",
diff --git a/src/plugins/gbp/gbp_ext_itf.c b/src/plugins/gbp/gbp_ext_itf.c
index be2d614e1fa..89bcb3da49e 100644
--- a/src/plugins/gbp/gbp_ext_itf.c
+++ b/src/plugins/gbp/gbp_ext_itf.c
@@ -130,6 +130,120 @@ gbp_ext_itf_delete (u32 sw_if_index)
return (VNET_API_ERROR_NO_SUCH_ENTRY);
}
+int
+gbp_ext_itf_anon_add (u32 sw_if_index, u32 bd_id, u32 rd_id)
+{
+ int rv = gbp_ext_itf_add (sw_if_index, bd_id, rd_id);
+ if (rv)
+ return rv;
+ /* add interface to the BD */
+ index_t itf = gbp_itf_add_and_lock (sw_if_index, bd_id);
+ /* setup GBP L2 features on this interface */
+ gbp_itf_set_l2_input_feature (itf, 0,
+ L2INPUT_FEAT_GBP_LPM_ANON_CLASSIFY |
+ L2INPUT_FEAT_LEARN);
+ gbp_itf_set_l2_output_feature (itf, 0, L2OUTPUT_FEAT_GBP_POLICY_LPM);
+ return 0;
+}
+
+int
+gbp_ext_itf_anon_delete (u32 sw_if_index)
+{
+ int rv = gbp_ext_itf_delete (sw_if_index);
+ if (rv)
+ return rv;
+ gbp_itf_unlock (sw_if_index);
+ return 0;
+}
+
+static clib_error_t *
+gbp_ext_itf_add_del_cli (vlib_main_t * vm,
+ unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+ unformat_input_t _line_input, *line_input = &_line_input;
+ u32 sw_if_index = ~0, bd_id = ~0, rd_id = ~0;
+ int add = 1, anon = 0;
+ int rv;
+
+ /* Get a line of input. */
+ if (!unformat_user (input, unformat_line_input, line_input))
+ return 0;
+
+ while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (line_input, "del"))
+ add = 0;
+ else
+ if (unformat
+ (line_input, "%U", unformat_vnet_sw_interface, vnet_get_main (),
+ &sw_if_index))
+ ;
+ else if (unformat (line_input, "bd %d", &bd_id))
+ ;
+ else if (unformat (line_input, "rd %d", &rd_id))
+ ;
+ else if (unformat (line_input, "anon-l3-out"))
+ anon = 1;
+ else
+ return clib_error_return (0, "unknown input `%U'",
+ format_unformat_error, line_input);
+ }
+ unformat_free (line_input);
+
+ if (~0 == sw_if_index)
+ return clib_error_return (0, "interface must be specified");
+
+ if (add)
+ {
+ if (~0 == bd_id)
+ return clib_error_return (0, "BD-ID must be specified");
+ if (~0 == rd_id)
+ return clib_error_return (0, "RD-ID must be specified");
+ if (anon)
+ rv = gbp_ext_itf_anon_add (sw_if_index, bd_id, rd_id);
+ else
+ rv = gbp_ext_itf_add (sw_if_index, bd_id, rd_id);
+ }
+ else
+ {
+ if (anon)
+ rv = gbp_ext_itf_anon_delete (sw_if_index);
+ else
+ rv = gbp_ext_itf_delete (sw_if_index);
+ }
+
+ switch (rv)
+ {
+ case 0:
+ return 0;
+ case VNET_API_ERROR_ENTRY_ALREADY_EXISTS:
+ return clib_error_return (0, "interface already exists");
+ case VNET_API_ERROR_NO_SUCH_ENTRY: /* fallthrough */
+ case VNET_API_ERROR_INVALID_SW_IF_INDEX:
+ return clib_error_return (0, "unknown interface");
+ default:
+ return clib_error_return (0, "error %d", rv);
+ }
+
+ /* never reached */
+ return 0;
+}
+
+/*?
+ * Add Group Based Interface as anonymous L3out interface
+ *
+ * @cliexpar
+ * @cliexstart{gbp interface [del] anon-l3out <interface> bd <ID>}
+ * @cliexend
+ ?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (gbp_itf_anon_l3out_add_del_node, static) = {
+ .path = "gbp ext-itf",
+ .short_help = "gbp ext-itf [del] <interface> bd <ID> rd <ID> [anon-l3-out]\n",
+ .function = gbp_ext_itf_add_del_cli,
+};
+/* *INDENT-ON* */
+
void
gbp_ext_itf_walk (gbp_ext_itf_cb_t cb, void *ctx)
{
diff --git a/src/plugins/gbp/gbp_ext_itf.h b/src/plugins/gbp/gbp_ext_itf.h
index dafcd08a2e4..d1b0f983447 100644
--- a/src/plugins/gbp/gbp_ext_itf.h
+++ b/src/plugins/gbp/gbp_ext_itf.h
@@ -52,6 +52,9 @@ typedef struct gpb_ext_itf_t_
extern int gbp_ext_itf_add (u32 sw_if_index, u32 bd_id, u32 rd_id);
extern int gbp_ext_itf_delete (u32 sw_if_index);
+extern int gbp_ext_itf_anon_add (u32 sw_if_index, u32 bd_id, u32 rd_id);
+extern int gbp_ext_itf_anon_delete (u32 sw_if_index);
+
extern u8 *format_gbp_ext_itf (u8 * s, va_list * args);
typedef walk_rc_t (*gbp_ext_itf_cb_t) (gbp_ext_itf_t * gbpe, void *ctx);
diff --git a/src/plugins/gbp/gbp_policy.c b/src/plugins/gbp/gbp_policy.c
index fbdf3946d1d..0f26701bd19 100644
--- a/src/plugins/gbp/gbp_policy.c
+++ b/src/plugins/gbp/gbp_policy.c
@@ -24,21 +24,27 @@ gbp_policy_init (vlib_main_t * vm)
gbp_policy_main_t *gpm = &gbp_policy_main;
clib_error_t *error = 0;
- vlib_node_t *node = vlib_get_node_by_name (vm, (u8 *) "gbp-policy-port");
-
/* Initialize the feature next-node indexes */
+ vlib_node_t *node = vlib_get_node_by_name (vm, (u8 *) "gbp-policy-port");
feat_bitmap_init_next_nodes (vm,
node->index,
L2OUTPUT_N_FEAT,
l2output_get_feat_names (),
- gpm->l2_output_feat_next[1]);
+ gpm->l2_output_feat_next[GBP_POLICY_PORT]);
node = vlib_get_node_by_name (vm, (u8 *) "gbp-policy-mac");
feat_bitmap_init_next_nodes (vm,
node->index,
L2OUTPUT_N_FEAT,
l2output_get_feat_names (),
- gpm->l2_output_feat_next[0]);
+ gpm->l2_output_feat_next[GBP_POLICY_MAC]);
+
+ node = vlib_get_node_by_name (vm, (u8 *) "gbp-policy-lpm");
+ feat_bitmap_init_next_nodes (vm,
+ node->index,
+ L2OUTPUT_N_FEAT,
+ l2output_get_feat_names (),
+ gpm->l2_output_feat_next[GBP_POLICY_LPM]);
return error;
}
diff --git a/src/plugins/gbp/gbp_policy_dpo.h b/src/plugins/gbp/gbp_policy_dpo.h
index 6b4f8c57fd0..1dc11ab2e32 100644
--- a/src/plugins/gbp/gbp_policy_dpo.h
+++ b/src/plugins/gbp/gbp_policy_dpo.h
@@ -17,6 +17,9 @@
#define __GBP_POLICY_DPO_H__
#include <vnet/dpo/dpo.h>
+#include <vnet/dpo/load_balance.h>
+#include <vnet/fib/ip4_fib.h>
+#include <vnet/fib/ip6_fib.h>
/**
* @brief
@@ -81,6 +84,32 @@ gbp_policy_dpo_get (index_t index)
return (pool_elt_at_index (gbp_policy_dpo_pool, index));
}
+static_always_inline const gbp_policy_dpo_t *
+gbp_classify_get_gpd (const ip4_address_t * ip4, const ip6_address_t * ip6,
+ const u32 fib_index)
+{
+ const gbp_policy_dpo_t *gpd;
+ const dpo_id_t *dpo;
+ const load_balance_t *lb;
+ u32 lbi;
+
+ if (ip4)
+ lbi = ip4_fib_forwarding_lookup (fib_index, ip4);
+ else if (ip6)
+ lbi = ip6_fib_table_fwding_lookup (&ip6_main, fib_index, ip6);
+ else
+ return 0;
+
+ lb = load_balance_get (lbi);
+ dpo = load_balance_get_bucket_i (lb, 0);
+
+ if (dpo->dpoi_type != gbp_policy_dpo_type)
+ return 0;
+
+ gpd = gbp_policy_dpo_get (dpo->dpoi_index);
+ return gpd;
+}
+
/*
* fd.io coding-style-patch-verification: ON
*
diff --git a/src/plugins/gbp/gbp_policy_node.c b/src/plugins/gbp/gbp_policy_node.c
index 26f7e9b8c59..10f5956033e 100644
--- a/src/plugins/gbp/gbp_policy_node.c
+++ b/src/plugins/gbp/gbp_policy_node.c
@@ -14,8 +14,10 @@
*/
#include <plugins/gbp/gbp.h>
+#include <plugins/gbp/gbp_classify.h>
#include <plugins/gbp/gbp_policy_dpo.h>
#include <plugins/gbp/gbp_bridge_domain.h>
+#include <plugins/gbp/gbp_ext_itf.h>
#include <vnet/vxlan-gbp/vxlan_gbp_packet.h>
#include <vnet/vxlan-gbp/vxlan_gbp.h>
@@ -109,10 +111,34 @@ gbp_policy_is_ethertype_allowed (const gbp_contract_t * gc0, u16 ethertype)
return (0);
}
+static_always_inline gbp_policy_next_t
+gbp_policy_l2_feature_next (gbp_policy_main_t * gpm, vlib_buffer_t * b,
+ const gbp_policy_type_t type)
+{
+ u32 feat_bit;
+
+ switch (type)
+ {
+ case GBP_POLICY_PORT:
+ feat_bit = L2OUTPUT_FEAT_GBP_POLICY_PORT;
+ break;
+ case GBP_POLICY_MAC:
+ feat_bit = L2OUTPUT_FEAT_GBP_POLICY_MAC;
+ break;
+ case GBP_POLICY_LPM:
+ feat_bit = L2OUTPUT_FEAT_GBP_POLICY_LPM;
+ break;
+ default:
+ return GBP_POLICY_NEXT_DROP;
+ }
+
+ return vnet_l2_feature_next (b, gpm->l2_output_feat_next[type], feat_bit);
+}
+
static uword
gbp_policy_inline (vlib_main_t * vm,
vlib_node_runtime_t * node,
- vlib_frame_t * frame, u8 is_port_based)
+ vlib_frame_t * frame, const gbp_policy_type_t type)
{
gbp_main_t *gm = &gbp_main;
gbp_policy_main_t *gpm = &gbp_policy_main;
@@ -174,12 +200,7 @@ gbp_policy_inline (vlib_main_t * vm,
*/
if (vnet_buffer2 (b0)->gbp.flags & VXLAN_GBP_GPFLAGS_A)
{
- next0 = vnet_l2_feature_next (b0,
- gpm->l2_output_feat_next
- [is_port_based],
- (is_port_based ?
- L2OUTPUT_FEAT_GBP_POLICY_PORT :
- L2OUTPUT_FEAT_GBP_POLICY_MAC));
+ next0 = gbp_policy_l2_feature_next (gpm, b0, type);
n_allow_a_bit++;
key0.as_u64 = ~0;
goto trace;
@@ -188,20 +209,40 @@ gbp_policy_inline (vlib_main_t * vm,
/*
* determine the src and dst EPG
*/
- if (is_port_based)
- ge0 = gbp_endpoint_find_itf (sw_if_index0);
- else
- ge0 = gbp_endpoint_find_mac (h0->dst_address,
- vnet_buffer (b0)->l2.bd_index);
- if (NULL != ge0)
+ key0.gck_dst = SCLASS_INVALID;
+
+ if (GBP_POLICY_LPM == type)
{
- key0.gck_dst = ge0->ge_fwd.gef_sclass;
- key0.gck_scope =
- gbp_bridge_domain_get_scope (vnet_buffer (b0)->l2.bd_index);
+ const ip4_address_t *ip4 = 0;
+ const ip6_address_t *ip6 = 0;
+ const dpo_proto_t proto =
+ gbp_classify_get_ip_address (h0, &ip4, &ip6,
+ GBP_CLASSIFY_GET_IP_DST);
+ if (PREDICT_TRUE (DPO_PROTO_NONE != proto))
+ {
+ const gbp_ext_itf_t *ext_itf =
+ gbp_ext_itf_get (sw_if_index0);
+ const gbp_policy_dpo_t *gpd =
+ gbp_classify_get_gpd (ip4, ip6,
+ ext_itf->gx_fib_index[proto]);
+ if (gpd)
+ key0.gck_dst = gpd->gpd_sclass;
+ }
}
else
{
+ if (GBP_POLICY_PORT == type)
+ ge0 = gbp_endpoint_find_itf (sw_if_index0);
+ else
+ ge0 = gbp_endpoint_find_mac (h0->dst_address,
+ vnet_buffer (b0)->l2.bd_index);
+ if (NULL != ge0)
+ key0.gck_dst = ge0->ge_fwd.gef_sclass;
+ }
+
+ if (SCLASS_INVALID == key0.gck_dst)
+ {
/* If you cannot determine the destination EP then drop */
b0->error = node->errors[GBP_POLICY_ERROR_DROP_NO_DCLASS];
goto trace;
@@ -215,13 +256,7 @@ gbp_policy_inline (vlib_main_t * vm,
/*
* intra-epg allowed
*/
- next0 =
- vnet_l2_feature_next (b0,
- gpm->l2_output_feat_next
- [is_port_based],
- (is_port_based ?
- L2OUTPUT_FEAT_GBP_POLICY_PORT :
- L2OUTPUT_FEAT_GBP_POLICY_MAC));
+ next0 = gbp_policy_l2_feature_next (gpm, b0, type);
vnet_buffer2 (b0)->gbp.flags |= VXLAN_GBP_GPFLAGS_A;
n_allow_intra++;
}
@@ -230,18 +265,15 @@ gbp_policy_inline (vlib_main_t * vm,
/*
* sclass or dclass 1 allowed
*/
- next0 =
- vnet_l2_feature_next (b0,
- gpm->l2_output_feat_next
- [is_port_based],
- (is_port_based ?
- L2OUTPUT_FEAT_GBP_POLICY_PORT :
- L2OUTPUT_FEAT_GBP_POLICY_MAC));
+ next0 = gbp_policy_l2_feature_next (gpm, b0, type);
vnet_buffer2 (b0)->gbp.flags |= VXLAN_GBP_GPFLAGS_A;
n_allow_sclass_1++;
}
else
{
+ key0.gck_scope =
+ gbp_bridge_domain_get_scope (vnet_buffer (b0)->
+ l2.bd_index);
gci0 = gbp_contract_find (&key0);
if (INDEX_INVALID != gci0)
@@ -321,13 +353,9 @@ gbp_policy_inline (vlib_main_t * vm,
switch (gu->gu_action)
{
case GBP_RULE_PERMIT:
- next0 = vnet_l2_feature_next
- (b0,
- gpm->l2_output_feat_next
- [is_port_based],
- (is_port_based ?
- L2OUTPUT_FEAT_GBP_POLICY_PORT :
- L2OUTPUT_FEAT_GBP_POLICY_MAC));
+ next0 =
+ gbp_policy_l2_feature_next (gpm, b0,
+ type);
break;
case GBP_RULE_DENY:
next0 = GBP_POLICY_NEXT_DROP;
@@ -368,12 +396,7 @@ gbp_policy_inline (vlib_main_t * vm,
* the src EPG is not set when the packet arrives on an EPG
* uplink interface and we do not need to apply policy
*/
- next0 =
- vnet_l2_feature_next (b0,
- gpm->l2_output_feat_next[is_port_based],
- (is_port_based ?
- L2OUTPUT_FEAT_GBP_POLICY_PORT :
- L2OUTPUT_FEAT_GBP_POLICY_MAC));
+ next0 = gbp_policy_l2_feature_next (gpm, b0, type);
}
trace:
@@ -413,14 +436,21 @@ VLIB_NODE_FN (gbp_policy_port_node) (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
- return (gbp_policy_inline (vm, node, frame, 1));
+ return (gbp_policy_inline (vm, node, frame, GBP_POLICY_PORT));
}
VLIB_NODE_FN (gbp_policy_mac_node) (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
- return (gbp_policy_inline (vm, node, frame, 0));
+ return (gbp_policy_inline (vm, node, frame, GBP_POLICY_MAC));
+}
+
+VLIB_NODE_FN (gbp_policy_lpm_node) (vlib_main_t * vm,
+ vlib_node_runtime_t * node,
+ vlib_frame_t * frame)
+{
+ return (gbp_policy_inline (vm, node, frame, GBP_POLICY_LPM));
}
/* packet trace format function */
@@ -470,6 +500,21 @@ VLIB_REGISTER_NODE (gbp_policy_mac_node) = {
},
};
+VLIB_REGISTER_NODE (gbp_policy_lpm_node) = {
+ .name = "gbp-policy-lpm",
+ .vector_size = sizeof (u32),
+ .format_trace = format_gbp_policy_trace,
+ .type = VLIB_NODE_TYPE_INTERNAL,
+
+ .n_errors = ARRAY_LEN(gbp_policy_error_strings),
+ .error_strings = gbp_policy_error_strings,
+
+ .n_next_nodes = GBP_POLICY_N_NEXT,
+ .next_nodes = {
+ [GBP_POLICY_NEXT_DROP] = "error-drop",
+ },
+};
+
/* *INDENT-ON* */
/*
diff --git a/src/vnet/l2/l2_input.h b/src/vnet/l2/l2_input.h
index ce9a7d5f0cd..677186bbdf3 100644
--- a/src/vnet/l2/l2_input.h
+++ b/src/vnet/l2/l2_input.h
@@ -112,6 +112,7 @@ l2input_bd_config (u32 bd_index)
_(LEARN, "l2-learn") \
_(L2_EMULATION, "l2-emulation") \
_(GBP_LEARN, "gbp-learn-l2") \
+ _(GBP_LPM_ANON_CLASSIFY, "l2-gbp-lpm-anon-classify") \
_(GBP_NULL_CLASSIFY, "gbp-null-classify") \
_(GBP_SRC_CLASSIFY, "gbp-src-classify") \
_(GBP_LPM_CLASSIFY, "l2-gbp-lpm-classify") \
diff --git a/src/vnet/l2/l2_output.h b/src/vnet/l2/l2_output.h
index 74d2829839f..1cc1e738841 100644
--- a/src/vnet/l2/l2_output.h
+++ b/src/vnet/l2/l2_output.h
@@ -81,6 +81,7 @@ extern vlib_node_registration_t l2output_node;
#define foreach_l2output_feat \
_(OUTPUT, "interface-output") \
_(SPAN, "span-l2-output") \
+ _(GBP_POLICY_LPM, "gbp-policy-lpm") \
_(GBP_POLICY_PORT, "gbp-policy-port") \
_(GBP_POLICY_MAC, "gbp-policy-mac") \
_(CFM, "feature-bitmap-drop") \
n1850'>1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910
/*
 * Copyright (c) 2015 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * cli.c: command line interface
 *
 * Copyright (c) 2008 Eliot Dresselhaus
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <vlib/vlib.h>
#include <vlib/stats/stats.h>
#include <vlib/unix/unix.h>
#include <vppinfra/callback.h>
#include <vppinfra/cpu.h>
#include <vppinfra/elog.h>
#include <unistd.h>
#include <ctype.h>

/** \file src/vlib/cli.c Debug CLI Implementation
 */

int vl_api_set_elog_trace_api_messages (int enable);
int vl_api_get_elog_trace_api_messages (void);

static void *current_traced_heap;

/* Root of all show commands. */
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (vlib_cli_show_command, static) = {
  .path = "show",
  .short_help = "Show commands",
};
/* *INDENT-ON* */

/* Root of all clear commands. */
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (vlib_cli_clear_command, static) = {
  .path = "clear",
  .short_help = "Clear commands",
};
/* *INDENT-ON* */

/* Root of all set commands. */
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (vlib_cli_set_command, static) = {
  .path = "set",
  .short_help = "Set commands",
};
/* *INDENT-ON* */

/* Root of all test commands. */
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (vlib_cli_test_command, static) = {
  .path = "test",
  .short_help = "Test commands",
};
/* *INDENT-ON* */

/* Returns bitmap of commands which match key. */
static uword *
vlib_cli_sub_command_match (vlib_cli_command_t * c, unformat_input_t * input)
{
  int i, n;
  uword *match = 0;
  vlib_cli_parse_position_t *p;

  unformat_skip_white_space (input);

  for (i = 0;; i++)
    {
      uword k;

      k = unformat_get_input (input);
      switch (k)
	{
	case 'a' ... 'z':
	case 'A' ... 'Z':
	case '0' ... '9':
	case '-':
	case '_':
	  break;

	case ' ':
	case '\t':
	case '\r':
	case '\n':
	case UNFORMAT_END_OF_INPUT:
	  /* White space or end of input removes any non-white
	     matches that were before possible. */
	  if (i < vec_len (c->sub_command_positions)
	      && clib_bitmap_count_set_bits (match) > 1)
	    {
	      p = vec_elt_at_index (c->sub_command_positions, i);
	      for (n = 0; n < vec_len (p->bitmaps); n++)
		match = clib_bitmap_andnot (match, p->bitmaps[n]);
	    }
	  goto done;

	default:
	  unformat_put_input (input);
	  goto done;
	}

      if (i >= vec_len (c->sub_command_positions))
	{
	no_match:
	  clib_bitmap_free (match);
	  return 0;
	}

      p = vec_elt_at_index (c->sub_command_positions, i);
      if (vec_len (p->bitmaps) == 0)
	goto no_match;

      n = k - p->min_char;
      if (n < 0 || n >= vec_len (p->bitmaps))
	goto no_match;

      if (i == 0)
	match = clib_bitmap_dup (p->bitmaps[n]);
      else
	match = clib_bitmap_and (match, p->bitmaps[n]);

      if (clib_bitmap_is_zero (match))
	goto no_match;
    }

done:
  return match;
}

uword
unformat_vlib_cli_line (unformat_input_t *i, va_list *va)
{
  unformat_input_t *result = va_arg (*va, unformat_input_t *);
  u8 *line = 0;
  uword c;
  int skip;

next_line:
  skip = 0;

  /* skip leading whitespace if any */
  unformat_skip_white_space (i);

  if (unformat_is_eof (i))
    return 0;

  while ((c = unformat_get_input (i)) != UNFORMAT_END_OF_INPUT)
    {
      if (c == '\\')
	{
	  c = unformat_get_input (i);

	  if (c == '\n')
	    {
	      if (!skip)
		vec_add1 (line, '\n');
	      skip = 0;
	      continue;
	    }

	  if (!skip)
	    vec_add1 (line, '\\');

	  if (c == UNFORMAT_END_OF_INPUT)
	    break;

	  if (!skip)
	    vec_add1 (line, c);
	  continue;
	}

      if (c == '#')
	skip = 1;
      else if (c == '\n')
	break;

      if (!skip)
	vec_add1 (line, c);
    }

  if (line == 0)
    goto next_line;

  unformat_init_vector (result, line);
  return 1;
}

/* Looks for string based sub-input formatted { SUB-INPUT }. */
uword
unformat_vlib_cli_sub_input (unformat_input_t * i, va_list * args)
{
  unformat_input_t *sub_input = va_arg (*args, unformat_input_t *);
  u8 *s;
  uword c;

  while (1)
    {
      c = unformat_get_input (i);
      switch (c)
	{
	case ' ':
	case '\t':
	case '\n':
	case '\r':
	case '\f':
	  break;

	case '{':
	default:
	  /* Put back paren. */
	  if (c != UNFORMAT_END_OF_INPUT)
	    unformat_put_input (i);

	  if (c == '{' && unformat (i, "%v", &s))
	    {
	      unformat_init_vector (sub_input, s);
	      return 1;
	    }
	  return 0;
	}
    }
  return 0;
}

static vlib_cli_command_t *
get_sub_command (vlib_cli_main_t * cm, vlib_cli_command_t * parent, u32 si)
{
  vlib_cli_sub_command_t *s = vec_elt_at_index (parent->sub_commands, si);
  return vec_elt_at_index (cm->commands, s->index);
}

static uword
unformat_vlib_cli_sub_command (unformat_input_t * i, va_list * args)
{
  vlib_main_t __clib_unused *vm = va_arg (*args, vlib_main_t *);
  vlib_global_main_t *vgm = vlib_get_global_main ();
  vlib_cli_command_t *c = va_arg (*args, vlib_cli_command_t *);
  vlib_cli_command_t **result = va_arg (*args, vlib_cli_command_t **);
  vlib_cli_main_t *cm = &vgm->cli_main;
  uword *match_bitmap, is_unique, index;

  match_bitmap = vlib_cli_sub_command_match (c, i);
  is_unique = clib_bitmap_count_set_bits (match_bitmap) == 1;
  index = ~0;
  if (is_unique)
    {
      index = clib_bitmap_first_set (match_bitmap);
      *result = get_sub_command (cm, c, index);
    }
  clib_bitmap_free (match_bitmap);

  return is_unique;
}

static int
vlib_cli_cmp_strings (void *a1, void *a2)
{
  u8 *c1 = *(u8 **) a1;
  u8 *c2 = *(u8 **) a2;

  return vec_cmp (c1, c2);
}

u8 **
vlib_cli_get_possible_completions (u8 * str)
{
  vlib_cli_command_t *c;
  vlib_cli_sub_command_t *sc;
  vlib_global_main_t *vgm = vlib_get_global_main ();
  vlib_cli_main_t *vcm = &vgm->cli_main;
  uword *match_bitmap = 0;
  uword index, is_unique, help_next_level;
  u8 **result = 0;
  unformat_input_t input;
  unformat_init_vector (&input, vec_dup (str));
  c = vec_elt_at_index (vcm->commands, 0);

  /* remove trailing whitespace, except for one of them */
  while (vec_len (input.buffer) >= 2 &&
	 isspace (input.buffer[vec_len (input.buffer) - 1]) &&
	 isspace (input.buffer[vec_len (input.buffer) - 2]))
    {
      vec_del1 (input.buffer, vec_len (input.buffer) - 1);
    }

  /* if input is empty, directly return list of root commands */
  if (vec_len (input.buffer) == 0 ||
      (vec_len (input.buffer) == 1 && isspace (input.buffer[0])))
    {
      vec_foreach (sc, c->sub_commands)
      {
	vec_add1 (result, (u8 *) sc->name);
      }
      goto done;
    }

  /* add a trailing '?' so that vlib_cli_sub_command_match can find
   * all commands starting with the input string */
  vec_add1 (input.buffer, '?');

  while (1)
    {
      match_bitmap = vlib_cli_sub_command_match (c, &input);
      /* no match: return no result */
      if (match_bitmap == 0)
	{
	  goto done;
	}
      is_unique = clib_bitmap_count_set_bits (match_bitmap) == 1;
      /* unique match: try to step one subcommand level further */
      if (is_unique)
	{
	  /* stop if no more input */
	  if (input.index >= vec_len (input.buffer) - 1)
	    {
	      break;
	    }

	  index = clib_bitmap_first_set (match_bitmap);
	  c = get_sub_command (vcm, c, index);
	  clib_bitmap_free (match_bitmap);
	  continue;
	}
      /* multiple matches: stop here, return all matches */
      break;
    }

  /* remove trailing '?' */
  vec_del1 (input.buffer, vec_len (input.buffer) - 1);

  /* if we have a space at the end of input, and a unique match,
   * autocomplete the next level of subcommands */
  help_next_level = (vec_len (str) == 0) || isspace (str[vec_len (str) - 1]);
  /* *INDENT-OFF* */
  clib_bitmap_foreach (index, match_bitmap) {
    if (help_next_level && is_unique) {
	c = get_sub_command (vcm, c, index);
	vec_foreach (sc, c->sub_commands) {
	  vec_add1 (result, (u8*) sc->name);
	}
	goto done; /* break doesn't work in this macro-loop */
    }
    sc = &c->sub_commands[index];
    vec_add1(result, (u8*) sc->name);
  }
  /* *INDENT-ON* */

done:
  clib_bitmap_free (match_bitmap);
  unformat_free (&input);

  if (result)
    vec_sort_with_function (result, vlib_cli_cmp_strings);
  return result;
}

static u8 *
format_vlib_cli_command_help (u8 * s, va_list * args)
{
  vlib_cli_command_t *c = va_arg (*args, vlib_cli_command_t *);
  int is_long = va_arg (*args, int);
  if (is_long && c->long_help)
    s = format (s, "%s", c->long_help);
  else if (c->short_help)
    s = format (s, "%s", c->short_help);
  else
    s = format (s, "%v commands", c->path);
  return s;
}

static u8 *
format_vlib_cli_path (u8 * s, va_list * args)
{
  u8 *path = va_arg (*args, u8 *);

  s = format (s, "%v", path);

  return s;
}

static vlib_cli_command_t *
all_subs (vlib_cli_main_t * cm, vlib_cli_command_t * subs, u32 command_index)
{
  vlib_cli_command_t *c = vec_elt_at_index (cm->commands, command_index);
  vlib_cli_sub_command_t *sc;

  if (c->function)
    vec_add1 (subs, c[0]);

  vec_foreach (sc, c->sub_commands) subs = all_subs (cm, subs, sc->index);

  return subs;
}

static int
vlib_cli_cmp_rule (void *a1, void *a2)
{
  vlib_cli_sub_rule_t *r1 = a1;
  vlib_cli_sub_rule_t *r2 = a2;

  return vec_cmp (r1->name, r2->name);
}

static int
vlib_cli_cmp_command (void *a1, void *a2)
{
  vlib_cli_command_t *c1 = a1;
  vlib_cli_command_t *c2 = a2;

  return vec_cmp (c1->path, c2->path);
}

static clib_error_t *
vlib_cli_dispatch_sub_commands (vlib_main_t * vm,
				vlib_cli_main_t * cm,
				unformat_input_t * input,
				uword parent_command_index)
{
  vlib_global_main_t *vgm = vlib_get_global_main ();
  vlib_cli_command_t *parent, *c;
  clib_error_t *error = 0;
  unformat_input_t sub_input;
  u8 *string;
  uword is_main_dispatch = cm == &vgm->cli_main;

  parent = vec_elt_at_index (cm->commands, parent_command_index);
  if (is_main_dispatch && unformat (input, "help"))
    {
      uword help_at_end_of_line, i;

      help_at_end_of_line =
	unformat_check_input (input) == UNFORMAT_END_OF_INPUT;
      while (1)
	{
	  c = parent;
	  if (unformat_user
	      (input, unformat_vlib_cli_sub_command, vm, c, &parent))
	    ;

	  else if (!(unformat_check_input (input) == UNFORMAT_END_OF_INPUT))
	    goto unknown;

	  else
	    break;
	}

      /* help SUB-COMMAND => long format help.
         "help" at end of line: show all commands. */
      if (!help_at_end_of_line)
	vlib_cli_output (vm, "%U", format_vlib_cli_command_help, c,
			 /* is_long */ 1);

      else if (vec_len (c->sub_commands) == 0)
	vlib_cli_output (vm, "%v: no sub-commands", c->path);

      else
	{
	  vlib_cli_sub_rule_t *sr, *subs = 0;
	  vlib_cli_sub_command_t *sc;

	  vec_foreach (sc, c->sub_commands)
	  {
	    vec_add2 (subs, sr, 1);
	    sr->name = sc->name;
	    sr->command_index = sc->index;
	    sr->rule_index = ~0;
	  }

	  vec_sort_with_function (subs, vlib_cli_cmp_rule);

	  for (i = 0; i < vec_len (subs); i++)
	    {
	      vlib_cli_command_t *d;

	      d = vec_elt_at_index (cm->commands, subs[i].command_index);
	      vlib_cli_output
		(vm, "  %-30v %U", subs[i].name,
		 format_vlib_cli_command_help, d, /* is_long */ 0);
	    }

	  vec_free (subs);
	}
    }

  else if (is_main_dispatch
	   && (unformat (input, "choices") || unformat (input, "?")))
    {
      vlib_cli_command_t *sub, *subs;

      subs = all_subs (cm, 0, parent_command_index);
      vec_sort_with_function (subs, vlib_cli_cmp_command);
      vec_foreach (sub, subs)
	vlib_cli_output (vm, "  %-40U %U",
			 format_vlib_cli_path, sub->path,
			 format_vlib_cli_command_help, sub, /* is_long */ 0);
      vec_free (subs);
    }

  else if (unformat (input, "comment %v", &string))
    {
      vec_free (string);
    }

  else if (unformat (input, "vpplog %v", &string))
    {
      int i;
      /*
       * Delete leading whitespace, so "vpplog { this and that }"
       * and "vpplog this" line up nicely.
       */
      for (i = 0; i < vec_len (string); i++)
	if (string[i] != ' ')
	  break;
      if (i > 0)
	vec_delete (string, i, 0);

      vlib_log_notice (cm->log, "CLI: %v", string);
      vec_free (string);
    }

  else if (unformat (input, "uncomment %U",
		     unformat_vlib_cli_sub_input, &sub_input))
    {
      error =
	vlib_cli_dispatch_sub_commands (vm, cm, &sub_input,
					parent_command_index);
      unformat_free (&sub_input);
    }
  else if (unformat (input, "leak-check %U",
		     unformat_vlib_cli_sub_input, &sub_input))
    {
      u8 *leak_report;
      if (current_traced_heap)
	{
	  void *oldheap;
	  oldheap = clib_mem_set_heap (current_traced_heap);
	  clib_mem_trace (0);
	  clib_mem_set_heap (oldheap);
	  current_traced_heap = 0;
	}
      clib_mem_trace (1);
      error =
	vlib_cli_dispatch_sub_commands (vm, cm, &sub_input,
					parent_command_index);
      unformat_free (&sub_input);

      /* Otherwise, the clib_error_t shows up as a leak... */
      if (error)
	{
	  vlib_cli_output (vm, "%v", error->what);
	  clib_error_free (error);
	  error = 0;
	}

      (void) clib_mem_trace_enable_disable (0);
      leak_report = format (0, "%U", format_clib_mem_heap, 0,
			    1 /* verbose, i.e. print leaks */ );
      clib_mem_trace (0);
      vlib_cli_output (vm, "%v", leak_report);
      vec_free (leak_report);
    }

  else
    if (unformat_user (input, unformat_vlib_cli_sub_command, vm, parent, &c))
    {
      unformat_input_t *si;
      uword has_sub_commands =
	vec_len (c->sub_commands) + vec_len (c->sub_rules) > 0;

      si = input;
      if (unformat_user (input, unformat_vlib_cli_sub_input, &sub_input))
	si = &sub_input;

      if (has_sub_commands)
	error = vlib_cli_dispatch_sub_commands (vm, cm, si, c - cm->commands);

      if (has_sub_commands && !error)
	/* Found valid sub-command. */ ;

      else if (c->function)
	{
	  clib_error_t *c_error;

	  /* Skip white space for benefit of called function. */
	  unformat_skip_white_space (si);

	  if (unformat (si, "?"))
	    {
	      vlib_cli_output (vm, "  %-40U %U", format_vlib_cli_path, c->path, format_vlib_cli_command_help, c,	/* is_long */
			       0);
	    }
	  else
	    {
	      if (PREDICT_FALSE (vm->elog_trace_cli_commands))
		{
                  /* *INDENT-OFF* */
                  ELOG_TYPE_DECLARE (e) =
                    {
                      .format = "cli-cmd: %s",
                      .format_args = "T4",
                    };
                  /* *INDENT-ON* */
		  struct
		  {
		    u32 c;
		  } *ed;
		  ed = ELOG_DATA (vlib_get_elog_main (), e);
		  ed->c = elog_string (vlib_get_elog_main (), "%v", c->path);
		}

	      if (!c->is_mp_safe)
		vlib_worker_thread_barrier_sync (vm);
	      if (PREDICT_FALSE (vec_len (cm->perf_counter_cbs) != 0))
		clib_call_callbacks (cm->perf_counter_cbs, cm,
				     c - cm->commands, 0 /* before */ );

	      c->hit_counter++;
	      c_error = c->function (vm, si, c);

	      if (PREDICT_FALSE (vec_len (cm->perf_counter_cbs) != 0))
		clib_call_callbacks (cm->perf_counter_cbs, cm,
				     c - cm->commands, 1 /* after */ );
	      if (!c->is_mp_safe)
		vlib_worker_thread_barrier_release (vm);

	      if (PREDICT_FALSE (vm->elog_trace_cli_commands))
		{
                  /* *INDENT-OFF* */
                  ELOG_TYPE_DECLARE (e) =
                    {
                      .format = "cli-cmd: %s %s",
                      .format_args = "T4T4",
                    };
                  /* *INDENT-ON* */
		  struct
		  {
		    u32 c, err;
		  } *ed;
		  ed = ELOG_DATA (vlib_get_elog_main (), e);
		  ed->c = elog_string (vlib_get_elog_main (), "%v", c->path);
		  if (c_error)
		    {
		      vec_add1 (c_error->what, 0);
		      ed->err = elog_string (vlib_get_elog_main (),
					     (char *) c_error->what);
		      vec_dec_len (c_error->what, 1);
		    }
		  else
		    ed->err = elog_string (vlib_get_elog_main (), "OK");
		}

	      if (c_error)
		{
		  error =
		    clib_error_return (0, "%v: %v", c->path, c_error->what);
		  clib_error_free (c_error);
		  /* Free sub input. */
		  if (si != input)
		    unformat_free (si);

		  return error;
		}
	    }

	  /* Free any previous error. */
	  clib_error_free (error);
	}

      else if (!error)
	error = clib_error_return (0, "%v: no sub-commands", c->path);

      /* Free sub input. */
      if (si != input)
	unformat_free (si);
    }

  else
    goto unknown;

  return error;

unknown:
  if (parent->path)
    return clib_error_return (0, "%v: unknown input `%U'", parent->path,
			      format_unformat_error, input);
  else
    return clib_error_return (0, "unknown input `%U'", format_unformat_error,
			      input);
}


void vlib_unix_error_report (vlib_main_t *, clib_error_t *)
  __attribute__ ((weak));

void
vlib_unix_error_report (vlib_main_t * vm, clib_error_t * error)
{
}

/* Process CLI input. */
int
vlib_cli_input (vlib_main_t * vm,
		unformat_input_t * input,
		vlib_cli_output_function_t * function, uword function_arg)
{
  vlib_global_main_t *vgm = vlib_get_global_main ();
  vlib_process_t *cp = vlib_get_current_process (vm);
  clib_error_t *error;
  vlib_cli_output_function_t *save_function;
  uword save_function_arg;
  int rv = 0;

  save_function = cp->output_function;
  save_function_arg = cp->output_function_arg;

  cp->output_function = function;
  cp->output_function_arg = function_arg;

  do
    {
      error = vlib_cli_dispatch_sub_commands (vm, &vgm->cli_main, input,
					      /* parent */ 0);
    }
  while (!error && !unformat (input, "%U", unformat_eof));

  if (error)
    {
      vlib_cli_output (vm, "%v", error->what);
      vlib_unix_error_report (vm, error);
      /* clib_error_return is unfortunately often called with a '0'
         return code */
      rv = error->code != 0 ? error->code : -1;
      clib_error_free (error);
    }

  cp->output_function = save_function;
  cp->output_function_arg = save_function_arg;
  return rv;
}

/* Output to current CLI connection. */
void
vlib_cli_output (vlib_main_t * vm, char *fmt, ...)
{
  vlib_process_t *cp = vlib_get_current_process (vm);
  va_list va;
  u8 *s;

  va_start (va, fmt);
  s = va_format (0, fmt, &va);
  va_end (va);

  /* some format functions might return 0
   * e.g. show int addr */
  if (NULL == s)
    return;

  /* Terminate with \n if not present. */
  if (vec_len (s) > 0 && s[vec_len (s) - 1] != '\n')
    vec_add1 (s, '\n');

  if ((!cp) || (!cp->output_function))
    fformat (stdout, "%v", s);
  else
    cp->output_function (cp->output_function_arg, s, vec_len (s));

  vec_free (s);
}

void *vl_msg_push_heap (void) __attribute__ ((weak));
void *
vl_msg_push_heap (void)
{
  return 0;
}

void vl_msg_pop_heap (void *oldheap) __attribute__ ((weak));
void
vl_msg_pop_heap (void *oldheap)
{
}

static clib_error_t *
show_memory_usage (vlib_main_t * vm,
		   unformat_input_t * input, vlib_cli_command_t * cmd)
{
  clib_mem_main_t *mm = &clib_mem_main;
  int verbose __attribute__ ((unused)) = 0;
  int api_segment = 0, stats_segment = 0, main_heap = 0, numa_heaps = 0;
  int map = 0;
  clib_error_t *error;
  u32 index = 0;
  int i;
  uword clib_mem_trace_enable_disable (uword enable);
  uword was_enabled;


  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "verbose"))
	verbose = 1;
      else if (unformat (input, "api-segment"))
	api_segment = 1;
      else if (unformat (input, "stats-segment"))
	stats_segment = 1;
      else if (unformat (input, "main-heap"))
	main_heap = 1;
      else if (unformat (input, "numa-heaps"))
	numa_heaps = 1;
      else if (unformat (input, "map"))
	map = 1;
      else
	{
	  error = clib_error_return (0, "unknown input `%U'",
				     format_unformat_error, input);
	  return error;
	}
    }

  if ((api_segment + stats_segment + main_heap + numa_heaps + map) == 0)
    return clib_error_return
      (0, "Need one of api-segment, stats-segment, main-heap, numa-heaps "
       "or map");

  if (api_segment)
    {
      void *oldheap = vl_msg_push_heap ();
      was_enabled = clib_mem_trace_enable_disable (0);
      u8 *s_in_svm = format (0, "%U\n", format_clib_mem_heap, 0, 1);
      vl_msg_pop_heap (oldheap);
      u8 *s = vec_dup (s_in_svm);

      oldheap = vl_msg_push_heap ();
      vec_free (s_in_svm);
      clib_mem_trace_enable_disable (was_enabled);
      vl_msg_pop_heap (oldheap);
      vlib_cli_output (vm, "API segment");
      vlib_cli_output (vm, "%v", s);
      vec_free (s);
    }
  if (stats_segment)
    {
      void *oldheap = vlib_stats_set_heap ();
      was_enabled = clib_mem_trace_enable_disable (0);
      u8 *s_in_svm = format (0, "%U\n", format_clib_mem_heap, 0, 1);
      if (oldheap)
	clib_mem_set_heap (oldheap);
      u8 *s = vec_dup (s_in_svm);

      oldheap = vlib_stats_set_heap ();
      vec_free (s_in_svm);
      if (oldheap)
	{
	  clib_mem_trace_enable_disable (was_enabled);
	  clib_mem_set_heap (oldheap);
	}
      vlib_cli_output (vm, "Stats segment");
      vlib_cli_output (vm, "%v", s);
      vec_free (s);
    }


  {
    if (main_heap)
      {
	/*
	 * Note: the foreach_vlib_main causes allocator traffic,
	 * so shut off tracing before we go there...
	 */
	was_enabled = clib_mem_trace_enable_disable (0);

	foreach_vlib_main ()
	  {
	    vlib_cli_output (vm, "%sThread %d %s\n", index ? "\n" : "", index,
			     vlib_worker_threads[index].name);
	    vlib_cli_output (vm, "  %U\n", format_clib_mem_heap,
			     mm->per_cpu_mheaps[index], verbose);
	    index++;
	  }

	/* Restore the trace flag */
	clib_mem_trace_enable_disable (was_enabled);
      }
    if (numa_heaps)
      {
	for (i = 0; i < ARRAY_LEN (mm->per_numa_mheaps); i++)
	  {
	    if (mm->per_numa_mheaps[i] == 0)
	      continue;
	    if (mm->per_numa_mheaps[i] == mm->per_cpu_mheaps[i])
	      {
		vlib_cli_output (vm, "Numa %d uses the main heap...", i);
		continue;
	      }
	    was_enabled = clib_mem_trace_enable_disable (0);

	    vlib_cli_output (vm, "Numa %d:", i);
	    vlib_cli_output (vm, "  %U\n", format_clib_mem_heap,
			     mm->per_numa_mheaps[index], verbose);
	  }
      }
    if (map)
      {
	clib_mem_page_stats_t stats = { };
	clib_mem_vm_map_hdr_t *hdr = 0;
	u8 *s = 0;
	int numa = -1;

	s = format (s, "\n%-16s%7s%5s%7s%7s",
		    "StartAddr", "size", "FD", "PageSz", "Pages");
	while ((numa = vlib_mem_get_next_numa_node (numa)) != -1)
	  s = format (s, " Numa%u", numa);
	s = format (s, " NotMap");
	s = format (s, " Name");
	vlib_cli_output (vm, "%v", s);
	vec_reset_length (s);

	while ((hdr = clib_mem_vm_get_next_map_hdr (hdr)))
	  {
	    clib_mem_get_page_stats ((void *) hdr->base_addr,
				     hdr->log2_page_sz, hdr->num_pages,
				     &stats);
	    s = format (s, "%016lx%7U",
			hdr->base_addr, format_memory_size,
			hdr->num_pages << hdr->log2_page_sz);

	    if (hdr->fd != -1)
	      s = format (s, "%5d", hdr->fd);
	    else
	      s = format (s, "%5s", " ");

	    s = format (s, "%7U%7lu",
			format_log2_page_size, hdr->log2_page_sz,
			hdr->num_pages);
	    while ((numa = vlib_mem_get_next_numa_node (numa)) != -1)
	      s = format (s, "%6lu", stats.per_numa[numa]);
	    s = format (s, "%7lu", stats.not_mapped);
	    s = format (s, " %s", hdr->name);
	    vlib_cli_output (vm, "%v", s);
	    vec_reset_length (s);
	  }
	vec_free (s);
      }
  }
  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (show_memory_usage_command, static) = {
  .path = "show memory",
  .short_help = "show memory [api-segment][stats-segment][verbose]\n"
  "            [numa-heaps][map]",
  .function = show_memory_usage,
};
/* *INDENT-ON* */

static clib_error_t *
show_cpu (vlib_main_t * vm, unformat_input_t * input,
	  vlib_cli_command_t * cmd)
{
#define _(a,b,c) vlib_cli_output (vm, "%-25s " b, a ":", c);
  _("Model name", "%U", format_cpu_model_name);
  _("Microarch model (family)", "%U", format_cpu_uarch);
  _("Flags", "%U", format_cpu_flags);
  _("Base frequency", "%.2f GHz",
    ((f64) vm->clib_time.clocks_per_second) * 1e-9);
#undef _
  return 0;
}

/*?
 * Displays various information about the CPU.
 *
 * @cliexpar
 * @cliexstart{show cpu}
 * Model name:               Intel(R) Xeon(R) CPU E5-2667 v4 @ 3.20GHz
 * Microarchitecture:        Broadwell (Broadwell-EP/EX)
 * Flags:                    sse3 ssse3 sse41 sse42 avx avx2 aes
 * Base Frequency:           3.20 GHz
 * @cliexend
?*/
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (show_cpu_command, static) = {
  .path = "show cpu",
  .short_help = "Show cpu information",
  .function = show_cpu,
};
/* *INDENT-ON* */

static clib_error_t *
enable_disable_memory_trace (vlib_main_t * vm,
			     unformat_input_t * input,
			     vlib_cli_command_t * cmd)
{
  clib_mem_main_t *mm = &clib_mem_main;
  unformat_input_t _line_input, *line_input = &_line_input;
  int enable = 1;
  int api_segment = 0;
  int stats_segment = 0;
  int main_heap = 0;
  u32 numa_id = ~0;
  void *oldheap;

  if (!unformat_user (input, unformat_line_input, line_input))
    return 0;

  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (line_input, "%U", unformat_vlib_enable_disable, &enable))
	;
      else if (unformat (line_input, "api-segment"))
	api_segment = 1;
      else if (unformat (line_input, "stats-segment"))
	stats_segment = 1;
      else if (unformat (line_input, "main-heap"))
	main_heap = 1;
      else if (unformat (line_input, "numa-heap %d", &numa_id))
	;
      else
	{
	  unformat_free (line_input);
	  return clib_error_return (0, "invalid input");
	}
    }
  unformat_free (line_input);

  if ((api_segment + stats_segment + main_heap + (enable == 0)
       + (numa_id != ~0)) == 0)
    {
      return clib_error_return
	(0, "Need one of main-heap, stats-segment, api-segment,\n"
	 "numa-heap <nn> or disable");
    }

  /* Turn off current trace, if any */
  if (current_traced_heap)
    {
      void *oldheap;
      oldheap = clib_mem_set_heap (current_traced_heap);
      clib_mem_trace (0);
      clib_mem_set_heap (oldheap);
      current_traced_heap = 0;
    }

  if (enable == 0)
    return 0;

  /* API segment */
  if (api_segment)
    {
      oldheap = vl_msg_push_heap ();
      current_traced_heap = clib_mem_get_heap ();
      clib_mem_trace (1);
      vl_msg_pop_heap (oldheap);

    }

  /* Stats segment */
  if (stats_segment)
    {
      oldheap = vlib_stats_set_heap ();
      current_traced_heap = clib_mem_get_heap ();
      clib_mem_trace (stats_segment);
      /* We don't want to call vlib_stats_pop_heap... */
      if (oldheap)
	clib_mem_set_heap (oldheap);
    }

  /* main_heap */
  if (main_heap)
    {
      current_traced_heap = clib_mem_get_heap ();
      clib_mem_trace (main_heap);
    }

  if (numa_id != ~0)
    {
      if (numa_id >= ARRAY_LEN (mm->per_numa_mheaps))
	return clib_error_return (0, "Numa %d out of range", numa_id);
      if (mm->per_numa_mheaps[numa_id] == 0)
	return clib_error_return (0, "Numa %d heap not active", numa_id);

      if (mm->per_numa_mheaps[numa_id] == clib_mem_get_heap ())
	return clib_error_return (0, "Numa %d uses the main heap...",
				  numa_id);
      current_traced_heap = mm->per_numa_mheaps[numa_id];
      oldheap = clib_mem_set_heap (current_traced_heap);
      clib_mem_trace (1);
      clib_mem_set_heap (oldheap);
    }


  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (enable_disable_memory_trace_command, static) = {
  .path = "memory-trace",
  .short_help = "memory-trace on|off [api-segment][stats-segment][main-heap]\n"
  "                   [numa-heap <numa-id>]\n",
  .function = enable_disable_memory_trace,
};
/* *INDENT-ON* */

static clib_error_t *
restart_cmd_fn (vlib_main_t * vm, unformat_input_t * input,
		vlib_cli_command_t * cmd)
{
  vlib_global_main_t *vgm = vlib_get_global_main ();
  clib_file_main_t *fm = &file_main;
  clib_file_t *f;

  /* environ(7) does not indicate a header for this */
  extern char **environ;

  /* Close all known open files */
  /* *INDENT-OFF* */
  pool_foreach (f, fm->file_pool)
     {
      if (f->file_descriptor > 2)
        close(f->file_descriptor);
    }
  /* *INDENT-ON* */

  /* Exec ourself */
  execve (vgm->name, (char **) vgm->argv, environ);

  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (restart_cmd,static) = {
    .path = "restart",
    .short_help = "restart process",
    .function = restart_cmd_fn,
};
/* *INDENT-ON* */

#ifdef TEST_CODE
/*
 * A trivial test harness to verify the per-process output_function
 * is working correcty.
 */

static clib_error_t *
sleep_ten_seconds (vlib_main_t * vm,
		   unformat_input_t * input, vlib_cli_command_t * cmd)
{
  u16 i;
  u16 my_id = rand ();

  vlib_cli_output (vm, "Starting 10 seconds sleep with id %u\n", my_id);

  for (i = 0; i < 10; i++)
    {
      vlib_process_wait_for_event_or_clock (vm, 1.0);
      vlib_cli_output (vm, "Iteration number %u, my id: %u\n", i, my_id);
    }
  vlib_cli_output (vm, "Done with sleep with id %u\n", my_id);
  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (ping_command, static) = {
  .path = "test sleep",
  .function = sleep_ten_seconds,
  .short_help = "Sleep for 10 seconds",
};
/* *INDENT-ON* */
#endif /* ifdef TEST_CODE */

static uword
vlib_cli_normalize_path (char *input, char **result)
{
  char *i = input;
  char *s = 0;
  uword l = 0;
  uword index_of_last_space = ~0;

  while (*i != 0)
    {
      u8 c = *i++;
      /* Multiple white space -> single space. */
      switch (c)
	{
	case ' ':
	case '\t':
	case '\n':
	case '\r':
	  if (l > 0 && s[l - 1] != ' ')
	    {
	      vec_add1 (s, ' ');
	      l++;
	    }
	  break;

	default:
	  if (l > 0 && s[l - 1] == ' ')
	    index_of_last_space = vec_len (s);
	  vec_add1 (s, c);
	  l++;
	  break;
	}
    }

  /* Remove any extra space at end. */
  if (l > 0 && s[l - 1] == ' ')
    vec_dec_len (s, 1);

  *result = s;
  return index_of_last_space;
}

always_inline uword
parent_path_len (char *path)
{
  word i;
  for (i = vec_len (path) - 1; i >= 0; i--)
    {
      if (path[i] == ' ')
	return i;
    }
  return ~0;
}

static void
add_sub_command (vlib_cli_main_t * cm, uword parent_index, uword child_index)
{
  vlib_cli_command_t *p, *c;
  vlib_cli_sub_command_t *sub_c;
  u8 *sub_name;
  word i, l;

  p = vec_elt_at_index (cm->commands, parent_index);
  c = vec_elt_at_index (cm->commands, child_index);

  l = parent_path_len (c->path);
  if (l == ~0)
    sub_name = vec_dup ((u8 *) c->path);
  else
    {
      ASSERT (l + 1 < vec_len (c->path));
      sub_name = 0;
      vec_add (sub_name, c->path + l + 1, vec_len (c->path) - (l + 1));
    }

  /* "Can't happen," check mainly to shut up coverity */
  ALWAYS_ASSERT (sub_name != 0);

  if (sub_name[0] == '%')
    {
      uword *q;
      vlib_cli_sub_rule_t *sr;

      /* Remove %. */
      vec_delete (sub_name, 1, 0);

      if (!p->sub_rule_index_by_name)
	p->sub_rule_index_by_name = hash_create_vec ( /* initial length */ 32,
						     sizeof (sub_name[0]),
						     sizeof (uword));
      q = hash_get_mem (p->sub_rule_index_by_name, sub_name);
      if (q)
	{
	  sr = vec_elt_at_index (p->sub_rules, q[0]);
	  ASSERT (sr->command_index == child_index);
	  return;
	}

      hash_set_mem (p->sub_rule_index_by_name, sub_name,
		    vec_len (p->sub_rules));
      vec_add2 (p->sub_rules, sr, 1);
      sr->name = sub_name;
      sr->rule_index = sr - p->sub_rules;
      sr->command_index = child_index;
      return;
    }

  if (!p->sub_command_index_by_name)
    p->sub_command_index_by_name = hash_create_vec ( /* initial length */ 32,
						    sizeof (c->path[0]),
						    sizeof (uword));

  /* Check if sub-command has already been created. */
  if (hash_get_mem (p->sub_command_index_by_name, sub_name))
    {
      vec_free (sub_name);
      return;
    }

  vec_add2 (p->sub_commands, sub_c, 1);
  sub_c->index = child_index;
  sub_c->name = sub_name;
  hash_set_mem (p->sub_command_index_by_name, sub_c->name,
		sub_c - p->sub_commands);

  vec_validate (p->sub_command_positions, vec_len (sub_c->name) - 1);
  for (i = 0; i < vec_len (sub_c->name); i++)
    {
      int n;
      vlib_cli_parse_position_t *pos;

      pos = vec_elt_at_index (p->sub_command_positions, i);

      if (!pos->bitmaps)
	pos->min_char = sub_c->name[i];

      n = sub_c->name[i] - pos->min_char;
      if (n < 0)
	{
	  pos->min_char = sub_c->name[i];
	  vec_insert (pos->bitmaps, -n, 0);
	  n = 0;
	}

      vec_validate (pos->bitmaps, n);
      pos->bitmaps[n] =
	clib_bitmap_ori (pos->bitmaps[n], sub_c - p->sub_commands);
    }
}

static void
vlib_cli_make_parent (vlib_cli_main_t * cm, uword ci)
{
  uword p_len, pi, *p;
  char *p_path;
  vlib_cli_command_t *c, *parent;

  /* Root command (index 0) should have already been added. */
  ASSERT (vec_len (cm->commands) > 0);

  c = vec_elt_at_index (cm->commands, ci);
  p_len = parent_path_len (c->path);

  /* No space?  Parent is root command. */
  if (p_len == ~0)
    {
      add_sub_command (cm, 0, ci);
      return;
    }

  p_path = 0;
  vec_add (p_path, c->path, p_len);

  p = hash_get_mem (cm->command_index_by_path, p_path);

  /* Parent exists? */
  if (!p)
    {
      /* Parent does not exist; create it. */
      vec_add2 (cm->commands, parent, 1);
      parent->path = p_path;
      hash_set_mem (cm->command_index_by_path, parent->path,
		    parent - cm->commands);
      pi = parent - cm->commands;
    }
  else
    {
      pi = p[0];
      vec_free (p_path);
    }

  add_sub_command (cm, pi, ci);

  /* Create parent's parent. */
  if (!p)
    vlib_cli_make_parent (cm, pi);
}

always_inline uword
vlib_cli_command_is_empty (vlib_cli_command_t * c)
{
  return (c->long_help == 0 && c->short_help == 0 && c->function == 0);
}

clib_error_t *
vlib_cli_register (vlib_main_t * vm, vlib_cli_command_t * c)
{
  vlib_global_main_t *vgm = vlib_get_global_main ();
  vlib_cli_main_t *cm = &vgm->cli_main;
  clib_error_t *error = 0;
  uword ci, *p;
  char *normalized_path;

  if ((error = vlib_call_init_function (vm, vlib_cli_init)))
    return error;

  (void) vlib_cli_normalize_path (c->path, &normalized_path);

  if (!cm->command_index_by_path)
    cm->command_index_by_path = hash_create_vec ( /* initial length */ 32,
						 sizeof (c->path[0]),
						 sizeof (uword));

  /* See if command already exists with given path. */
  p = hash_get_mem (cm->command_index_by_path, normalized_path);
  if (p)
    {
      vlib_cli_command_t *d;

      ci = p[0];
      d = vec_elt_at_index (cm->commands, ci);

      /* If existing command was created via vlib_cli_make_parent
         replaced it with callers data. */
      if (vlib_cli_command_is_empty (d))
	{
	  vlib_cli_command_t save = d[0];

	  ASSERT (!vlib_cli_command_is_empty (c));

	  /* Copy callers fields. */
	  d[0] = c[0];

	  /* Save internal fields. */
	  d->path = save.path;
	  d->sub_commands = save.sub_commands;
	  d->sub_command_index_by_name = save.sub_command_index_by_name;
	  d->sub_command_positions = save.sub_command_positions;
	  d->sub_rules = save.sub_rules;
	}
      else
	error =
	  clib_error_return (0, "duplicate command name with path %v",
			     normalized_path);

      vec_free (normalized_path);
      if (error)
	return error;
    }
  else
    {
      /* Command does not exist: create it. */

      /* Add root command (index 0). */
      if (vec_len (cm->commands) == 0)
	{
	  /* Create command with index 0; path is empty string. */
	  vec_resize (cm->commands, 1);
	}

      ci = vec_len (cm->commands);
      hash_set_mem (cm->command_index_by_path, normalized_path, ci);
      vec_add1 (cm->commands, c[0]);

      c = vec_elt_at_index (cm->commands, ci);
      c->path = normalized_path;

      /* Don't inherit from registration. */
      c->sub_commands = 0;
      c->sub_command_index_by_name = 0;
      c->sub_command_positions = 0;
    }

  vlib_cli_make_parent (cm, ci);
  return 0;
}

#if 0
/* $$$ turn back on again someday, maybe */
clib_error_t *
vlib_cli_register_parse_rule (vlib_main_t * vm, vlib_cli_parse_rule_t * r_reg)
{
  vlib_cli_main_t *cm = &vm->cli_main;
  vlib_cli_parse_rule_t *r;
  clib_error_t *error = 0;
  u8 *r_name;
  uword *p;

  if (!cm->parse_rule_index_by_name)
    cm->parse_rule_index_by_name = hash_create_vec ( /* initial length */ 32,
						    sizeof (r->name[0]),
						    sizeof (uword));

  /* Make vector copy of name. */
  r_name = format (0, "%s", r_reg->name);

  if ((p = hash_get_mem (cm->parse_rule_index_by_name, r_name)))
    {
      vec_free (r_name);
      return clib_error_return (0, "duplicate parse rule name `%s'",
				r_reg->name);
    }

  vec_add2 (cm->parse_rules, r, 1);
  r[0] = r_reg[0];
  r->name = (char *) r_name;
  hash_set_mem (cm->parse_rule_index_by_name, r->name, r - cm->parse_rules);

  return error;
}

static clib_error_t *vlib_cli_register_parse_rules (vlib_main_t * vm,
						    vlib_cli_parse_rule_t *
						    lo,
						    vlib_cli_parse_rule_t *
						    hi)
  __attribute__ ((unused))
{
  clib_error_t *error = 0;
  vlib_cli_parse_rule_t *r;

  for (r = lo; r < hi; r = clib_elf_section_data_next (r, 0))
    {
      if (!r->name || strlen (r->name) == 0)
	{
	  error = clib_error_return (0, "parse rule with no name");
	  goto done;
	}

      error = vlib_cli_register_parse_rule (vm, r);
      if (error)
	goto done;
    }

done:
  return error;
}
#endif

static clib_error_t *
event_logger_trace_command_fn (vlib_main_t * vm,
			       unformat_input_t * input,
			       vlib_cli_command_t * cmd)
{
  unformat_input_t _line_input, *line_input = &_line_input;
  int enable = 1;
  int api = 0, cli = 0, barrier = 0, dispatch = 0, circuit = 0;
  u32 circuit_node_index;

  if (!unformat_user (input, unformat_line_input, line_input))
    goto print_status;

  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (line_input, "api"))
	api = 1;
      else if (unformat (line_input, "dispatch"))
	dispatch = 1;
      else if (unformat (line_input, "circuit-node %U",
			 unformat_vlib_node, vm, &circuit_node_index))
	circuit = 1;
      else if (unformat (line_input, "cli"))
	cli = 1;
      else if (unformat (line_input, "barrier"))
	barrier = 1;
      else if (unformat (line_input, "disable"))
	enable = 0;
      else if (unformat (line_input, "enable"))
	enable = 1;
      else
	break;
    }
  unformat_free (line_input);

  vl_api_set_elog_trace_api_messages
    (api ? enable : vl_api_get_elog_trace_api_messages ());
  vm->elog_trace_cli_commands = cli ? enable : vm->elog_trace_cli_commands;
  vm->elog_trace_graph_dispatch = dispatch ?
    enable : vm->elog_trace_graph_dispatch;
  vm->elog_trace_graph_circuit = circuit ?
    enable : vm->elog_trace_graph_circuit;
  vlib_worker_threads->barrier_elog_enabled =
    barrier ? enable : vlib_worker_threads->barrier_elog_enabled;
  vm->elog_trace_graph_circuit_node_index = circuit_node_index;

  /*
   * Set up start-of-buffer logic-analyzer trigger
   * for main loop event logs, which are fairly heavyweight.
   * See src/vlib/main/vlib_elog_main_loop_event(...), which
   * will fully disable the scheme when the elog buffer fills.
   */
  if (dispatch || circuit)
    {
      elog_main_t *em = &vlib_global_main.elog_main;

      em->n_total_events_disable_limit =
	em->n_total_events + vec_len (em->event_ring);
    }


print_status:
  vlib_cli_output (vm, "Current status:");

  vlib_cli_output
    (vm, "    Event log API message trace: %s\n    CLI command trace: %s",
     vl_api_get_elog_trace_api_messages ()? "on" : "off",
     vm->elog_trace_cli_commands ? "on" : "off");
  vlib_cli_output
    (vm, "    Barrier sync trace: %s",
     vlib_worker_threads->barrier_elog_enabled ? "on" : "off");
  vlib_cli_output
    (vm, "    Graph Dispatch: %s",
     vm->elog_trace_graph_dispatch ? "on" : "off");
  vlib_cli_output
    (vm, "    Graph Circuit: %s",
     vm->elog_trace_graph_circuit ? "on" : "off");
  if (vm->elog_trace_graph_circuit)
    vlib_cli_output
      (vm, "                   node %U",
       format_vlib_node_name, vm, vm->elog_trace_graph_circuit_node_index);

  return 0;
}

/*?
 * Control event logging of api, cli, and thread barrier events
 * With no arguments, displays the current trace status.
 * Name the event groups you wish to trace or stop tracing.
 *
 * @cliexpar
 * @clistart
 * event-logger trace api cli barrier
 * event-logger trace api cli barrier disable
 * event-logger trace dispatch
 * event-logger trace circuit-node ethernet-input
 * @cliend
 * @cliexcmd{event-logger trace [api][cli][barrier][disable]}
?*/
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (event_logger_trace_command, static) =
{
  .path = "event-logger trace",
  .short_help = "event-logger trace [api][cli][barrier][dispatch]\n"
  "[circuit-node <name> e.g. ethernet-input][disable]",
  .function = event_logger_trace_command_fn,
};
/* *INDENT-ON* */

static clib_error_t *
suspend_command_fn (vlib_main_t * vm,
		    unformat_input_t * input, vlib_cli_command_t * cmd)
{
  vlib_process_suspend (vm, 30e-3);
  return 0;
}

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (suspend_command, static) =
{
  .path = "suspend",
  .short_help = "suspend debug CLI for 30ms",
  .function = suspend_command_fn,
  .is_mp_safe = 1,
};
/* *INDENT-ON* */


static int
sort_cmds_by_path (void *a1, void *a2)
{
  u32 *index1 = a1;
  u32 *index2 = a2;
  vlib_global_main_t *vgm = vlib_get_global_main ();
  vlib_cli_main_t *cm = &vgm->cli_main;
  vlib_cli_command_t *c1, *c2;
  int i, lmin;

  c1 = vec_elt_at_index (cm->commands, *index1);
  c2 = vec_elt_at_index (cm->commands, *index2);

  lmin = vec_len (c1->path);
  lmin = (vec_len (c2->path) >= lmin) ? lmin : vec_len (c2->path);

  for (i = 0; i < lmin; i++)
    {
      if (c1->path[i] < c2->path[i])
	return -1;
      else if (c1->path[i] > c2->path[i])
	return 1;
    }

  return 0;
}

typedef struct
{
  vlib_cli_main_t *cm;
  u32 parent_command_index;
  int show_mp_safe;
  int show_not_mp_safe;
  int show_hit;
  int clear_hit;
} vlib_cli_walk_args_t;

static void
cli_recursive_walk (vlib_cli_walk_args_t * aa)
{
  vlib_cli_command_t *parent;
  vlib_cli_sub_command_t *sub;
  vlib_cli_walk_args_t _a, *a = &_a;
  vlib_cli_main_t *cm;
  int i;

  /* Copy args into this stack frame */
  *a = *aa;
  cm = a->cm;

  parent = vec_elt_at_index (cm->commands, a->parent_command_index);

  if (parent->function)
    {
      if (((a->show_mp_safe && parent->is_mp_safe)
	   || (a->show_not_mp_safe && !parent->is_mp_safe))
	  && (a->show_hit == 0 || parent->hit_counter))
	{
	  vec_add1 (cm->sort_vector, a->parent_command_index);
	}

      if (a->clear_hit)
	parent->hit_counter = 0;
    }

  for (i = 0; i < vec_len (parent->sub_commands); i++)
    {
      sub = vec_elt_at_index (parent->sub_commands, i);
      a->parent_command_index = sub->index;
      cli_recursive_walk (a);
    }
}

static u8 *
format_mp_safe (u8 * s, va_list * args)
{
  vlib_cli_main_t *cm = va_arg (*args, vlib_cli_main_t *);
  int show_mp_safe = va_arg (*args, int);
  int show_not_mp_safe = va_arg (*args, int);
  int show_hit = va_arg (*args, int);
  int clear_hit = va_arg (*args, int);
  vlib_cli_command_t *c;
  vlib_cli_walk_args_t _a, *a = &_a;
  int i;
  char *format_string = "\n%v";

  if (show_hit)
    format_string = "\n%v: %u";

  vec_reset_length (cm->sort_vector);

  a->cm = cm;
  a->parent_command_index = 0;
  a->show_mp_safe = show_mp_safe;
  a->show_not_mp_safe = show_not_mp_safe;
  a->show_hit = show_hit;
  a->clear_hit = clear_hit;

  cli_recursive_walk (a);

  vec_sort_with_function (cm->sort_vector, sort_cmds_by_path);

  for (i = 0; i < vec_len (cm->sort_vector); i++)
    {
      c = vec_elt_at_index (cm->commands, cm->sort_vector[i]);
      s = format (s, format_string, c->path, c->hit_counter);
    }

  return s;
}


static clib_error_t *
show_cli_command_fn (vlib_main_t * vm,
		     unformat_input_t * input, vlib_cli_command_t * cmd)
{
  vlib_global_main_t *vgm = vlib_get_global_main ();
  int show_mp_safe = 0;
  int show_not_mp_safe = 0;
  int show_hit = 0;
  int clear_hit = 0;

  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
      if (unformat (input, "mp-safe"))
	show_mp_safe = 1;
      if (unformat (input, "not-mp-safe"))
	show_not_mp_safe = 1;
      else if (unformat (input, "hit"))
	show_hit = 1;
      else if (unformat (input, "clear-hit"))
	clear_hit = 1;
      else
	break;
    }

  /* default set: all cli commands */
  if (clear_hit == 0 && (show_mp_safe + show_not_mp_safe) == 0)
    show_mp_safe = show_not_mp_safe = 1;

  vlib_cli_output (vm, "%U", format_mp_safe, &vgm->cli_main, show_mp_safe,
		   show_not_mp_safe, show_hit, clear_hit);
  if (clear_hit)
    vlib_cli_output (vm, "hit counters cleared...");

  return 0;
}

/*?
 * Displays debug cli command information
 *
 * @cliexpar
 * @cliexstart{show cli [mp-safe][not-mp-safe][hit][clear-hit]}
 *
 * "show cli" displays the entire debug cli:
 *
 * abf attach
 * abf policy
 * adjacency counters
 * api trace
 * app ns
 * bfd key del
 * ... and so on ...
 *
 * "show cli mp-safe" displays mp-safe debug CLI commands:
 *
 * abf policy
 * binary-api
 * create vhost-user
 * exec
 * ip container
 * ip mroute
 * ip probe-neighbor
 * ip route
 * ip scan-neighbor
 * ip table
 * ip6 table
 *
 * "show cli not-mp-safe" displays debug CLI commands
 * which cause worker thread barrier synchronization
 *
 * "show cli hit" displays commands which have been executed. Qualify
 * as desired with "mp-safe" or "not-mp-safe".
 *
 * "show cli clear-hit" clears the per-command hit counters.
 * @cliexend
?*/

/* *INDENT-OFF* */
VLIB_CLI_COMMAND (show_cli_command, static) =
{
  .path = "show cli",
  .short_help = "show cli [mp-safe][not-mp-safe][hit][clear-hit]",
  .function = show_cli_command_fn,
  .is_mp_safe = 1,
};
/* *INDENT-ON* */

static clib_error_t *
vlib_cli_init (vlib_main_t * vm)
{
  vlib_global_main_t *vgm = vlib_get_global_main ();
  vlib_cli_main_t *cm = &vgm->cli_main;
  clib_error_t *error = 0;
  vlib_cli_command_t *cmd;

  cmd = cm->cli_command_registrations;

  while (cmd)
    {
      error = vlib_cli_register (vm, cmd);
      if (error)
	return error;
      cmd = cmd->next_cli_command;
    }

  cm->log = vlib_log_register_class_rate_limit (
    "cli", "log", 0x7FFFFFFF /* aka no rate limit */);
  return error;
}

VLIB_INIT_FUNCTION (vlib_cli_init);

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */