aboutsummaryrefslogtreecommitdiffstats
path: root/test/vpp_pppoe_interface.py
blob: 9a8b8699328ef92a7dd17d55b114aecb1cb32d31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
from vpp_interface import VppInterface
import socket
from util import ppp, ppc, mactobinary


class VppPppoeInterface(VppInterface):
    """
    VPP Pppoe interface
    """

    def __init__(self, test, client_ip, client_mac,
                 session_id, decap_vrf_id=0):
        """ Create VPP PPPoE4 interface """
        self._sw_if_index = 0
        super(VppPppoeInterface, self).__init__(test)
        self._test = test
        self.client_ip = client_ip
        self.client_mac = client_mac
        self.session_id = session_id
        self.decap_vrf_id = decap_vrf_id

    def add_vpp_config(self):
        cip = socket.inet_pton(socket.AF_INET, self.client_ip)
        cmac = mactobinary(self.client_mac)
        r = self.test.vapi.pppoe_add_del_session(
                cip, cmac,
                session_id=self.session_id,
                decap_vrf_id=self.decap_vrf_id)
        self._sw_if_index = r.sw_if_index
        self.generate_remote_hosts()

    def remove_vpp_config(self):
        cip = socket.inet_pton(socket.AF_INET, self.client_ip)
        cmac = mactobinary(self.client_mac)
        self.unconfig()
        r = self.test.vapi.pppoe_add_del_session(
                cip, cmac,
                session_id=self.session_id,
                decap_vrf_id=self.decap_vrf_id,
                is_add=0)


class VppPppoe6Interface(VppInterface):
    """
    VPP Pppoe IPv6 interface
    """

    def __init__(self, test, src_ip, dst_ip, outer_fib_id=0, is_teb=0):
        """ Create VPP PPPoE6 interface """
        self._sw_if_index = 0
        super(VppPppoe6Interface, self).__init__(test)
        self._test = test
        self.client_ip = client_ip
        self.client_mac = client_mac
        self.session_id = session_id
        self.decap_vrf_id = decap_vrf_id

    def add_vpp_config(self):
        cip = socket.inet_pton(socket.AF_INET6, self.client_ip)
        cmac = mactobinary(self.client_mac)
        r = self.test.vapi.pppoe_add_del_session(
                cip, cmac,
                session_id=self.session_id,
                decap_vrf_id=self.decap_vrf_id,
                is_ip6=1)
        self._sw_if_index = r.sw_if_index
        self.generate_remote_hosts()

    def remove_vpp_config(self):
        cip = socket.inet_pton(socket.AF_INET6, self.client_ip)
        cmac = mactobinary(self.client_mac)
        self.unconfig()
        r = self.test.vapi.pppoe_add_del_session(
                cip, cmac,
                session_id=self.session_id,
                decap_vrf_id=self.decap_vrf_id,
                is_add=0,
                is_ip6=1)
hlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ } @media (prefers-color-scheme: light) { .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
/*
 * Copyright (c) 2016 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.
 */

#include <vnet/adj/adj.h>
#include <vnet/adj/adj_internal.h>
#include <vnet/adj/adj_glean.h>
#include <vnet/adj/adj_midchain.h>
#include <vnet/adj/adj_mcast.h>
#include <vnet/adj/adj_delegate.h>
#include <vnet/fib/fib_node_list.h>

/* Adjacency packet/byte counters indexed by adjacency index. */
vlib_combined_counter_main_t adjacency_counters;

/*
 * the single adj pool
 */
ip_adjacency_t *adj_pool;

/**
 * @brief Global Config for enabling per-adjacency counters.
 * By default these are disabled.
 */
int adj_per_adj_counters;

always_inline void
adj_poison (ip_adjacency_t * adj)
{
    if (CLIB_DEBUG > 0)
    {
	memset (adj, 0xfe, sizeof (adj[0]));
    }
}

ip_adjacency_t *
adj_alloc (fib_protocol_t proto)
{
    ip_adjacency_t *adj;

    pool_get_aligned(adj_pool, adj, CLIB_CACHE_LINE_BYTES);

    adj_poison(adj);

    /* Make sure certain fields are always initialized. */
    /* Validate adjacency counters. */
    vlib_validate_combined_counter(&adjacency_counters,
                                   adj_get_index(adj));

    fib_node_init(&adj->ia_node,
                  FIB_NODE_TYPE_ADJ);

    adj->ia_nh_proto = proto;
    adj->ia_flags = 0;
    adj->rewrite_header.sw_if_index = ~0;
    adj->lookup_next_index = 0;
    adj->ia_delegates = NULL;

    /* lest it become a midchain in the future */
    memset(&adj->sub_type.midchain.next_dpo, 0,
           sizeof(adj->sub_type.midchain.next_dpo));

    ip4_main.lookup_main.adjacency_heap = adj_pool;
    ip6_main.lookup_main.adjacency_heap = adj_pool;

    return (adj);
}

static int
adj_index_is_special (adj_index_t adj_index)
{
    if (ADJ_INDEX_INVALID == adj_index)
	return (!0);

    return (0);
}

/**
 * @brief Pretty print helper function for formatting specific adjacencies.
 * @param s - input string to format
 * @param args - other args passed to format function such as:
 *                 - vnet_main_t
 *                 - ip_lookup_main_t
 *                 - adj_index
 */
u8 *
format_ip_adjacency (u8 * s, va_list * args)
{
    format_ip_adjacency_flags_t fiaf;
    ip_adjacency_t * adj;
    u32 adj_index;

    adj_index = va_arg (*args, u32);
    fiaf = va_arg (*args, format_ip_adjacency_flags_t);
    adj = adj_get(adj_index);
  
    switch (adj->lookup_next_index)
    {
    case IP_LOOKUP_NEXT_REWRITE:
	s = format (s, "%U", format_adj_nbr, adj_index, 0);
	break;
    case IP_LOOKUP_NEXT_ARP:
	s = format (s, "%U", format_adj_nbr_incomplete, adj_index, 0);
	break;
    case IP_LOOKUP_NEXT_GLEAN:
	s = format (s, "%U", format_adj_glean, adj_index, 0);
	break;
    case IP_LOOKUP_NEXT_MIDCHAIN:
	s = format (s, "%U", format_adj_midchain, adj_index, 2);
	break;
    case IP_LOOKUP_NEXT_MCAST:
	s = format (s, "%U", format_adj_mcast, adj_index, 0);
	break;
    case IP_LOOKUP_NEXT_MCAST_MIDCHAIN:
	s = format (s, "%U", format_adj_mcast_midchain, adj_index, 0);
	break;
    default:
	break;
    }

    if (fiaf & FORMAT_IP_ADJACENCY_DETAIL)
    {
        adj_delegate_type_t adt;
        adj_delegate_t *aed;
        vlib_counter_t counts;

        vlib_get_combined_counter(&adjacency_counters, adj_index, &counts);
        s = format (s, "\n counts:[%Ld:%Ld]", counts.packets, counts.bytes);
	s = format (s, "\n locks:%d", adj->ia_node.fn_locks);
	s = format(s, "\n delegates:\n  ");
        FOR_EACH_ADJ_DELEGATE(adj, adt, aed,
        {
            s = format(s, "  %U\n", format_adj_deletegate, aed);
        });

	s = format(s, "\n children:\n  ");
	s = fib_node_children_format(adj->ia_node.fn_children, s);
    }

    return s;
}

/*
 * adj_last_lock_gone
 *
 * last lock/reference to the adj has gone, we no longer need it.
 */
static void
adj_last_lock_gone (ip_adjacency_t *adj)
{
    vlib_main_t * vm = vlib_get_main();

    ASSERT(0 == fib_node_list_get_size(adj->ia_node.fn_children));
    ADJ_DBG(adj, "last-lock-gone");

    vlib_worker_thread_barrier_sync (vm);

    switch (adj->lookup_next_index)
    {
    case IP_LOOKUP_NEXT_MIDCHAIN:
        dpo_reset(&adj->sub_type.midchain.next_dpo);
        /* FALL THROUGH */
    case IP_LOOKUP_NEXT_ARP:
    case IP_LOOKUP_NEXT_REWRITE:
	/*
	 * complete and incomplete nbr adjs
	 */
	adj_nbr_remove(adj_get_index(adj),
                       adj->ia_nh_proto,
		       adj->ia_link,
		       &adj->sub_type.nbr.next_hop,
		       adj->rewrite_header.sw_if_index);
	break;
    case IP_LOOKUP_NEXT_GLEAN:
	adj_glean_remove(adj->ia_nh_proto,
			 adj->rewrite_header.sw_if_index);
	break;
    case IP_LOOKUP_NEXT_MCAST:
    case IP_LOOKUP_NEXT_MCAST_MIDCHAIN:
	adj_mcast_remove(adj->ia_nh_proto,
			 adj->rewrite_header.sw_if_index);
	break;
    case IP_LOOKUP_NEXT_DROP:
    case IP_LOOKUP_NEXT_PUNT:
    case IP_LOOKUP_NEXT_LOCAL:
    case IP_LOOKUP_NEXT_ICMP_ERROR:
    case IP_LOOKUP_N_NEXT:
	/*
	 * type not stored in any DB from which we need to remove it
	 */
	break;
    }

    vlib_worker_thread_barrier_release(vm);

    fib_node_deinit(&adj->ia_node);
    ASSERT(0 == vec_len(adj->ia_delegates));
    vec_free(adj->ia_delegates);
    pool_put(adj_pool, adj);
}

void
adj_lock (adj_index_t adj_index)
{
    ip_adjacency_t *adj;

    if (adj_index_is_special(adj_index))
    {
	return;
    }

    adj = adj_get(adj_index);
    ASSERT(adj);

    ADJ_DBG(adj, "lock");
    fib_node_lock(&adj->ia_node);
}

void
adj_unlock (adj_index_t adj_index)
{
    ip_adjacency_t *adj;

    if (adj_index_is_special(adj_index))
    {
	return;
    }

    adj = adj_get(adj_index);
    ASSERT(adj);

    ADJ_DBG(adj, "unlock");
    ASSERT(adj);

    fib_node_unlock(&adj->ia_node);
}

u32
adj_child_add (adj_index_t adj_index,
	       fib_node_type_t child_type,
	       fib_node_index_t child_index)
{
    ASSERT(ADJ_INDEX_INVALID != adj_index);
    if (adj_index_is_special(adj_index))
    {
	return (~0);
    }

    return (fib_node_child_add(FIB_NODE_TYPE_ADJ,
                               adj_index,
                               child_type,
                               child_index));
}

void
adj_child_remove (adj_index_t adj_index,
		  u32 sibling_index)
{
    if (adj_index_is_special(adj_index))
    {
	return;
    }

    fib_node_child_remove(FIB_NODE_TYPE_ADJ,
                          adj_index,
                          sibling_index);
}

/*
 * Context for the walk to update the cached feture flags.
 */
typedef struct adj_feature_update_t_
{
    u8 arc;
    u8 enable;
} adj_feature_update_ctx_t;

static adj_walk_rc_t
adj_feature_update_walk_cb (adj_index_t ai,
                            void *arg)
{
    adj_feature_update_ctx_t *ctx = arg;
    ip_adjacency_t *adj;

    adj = adj_get(ai);

    /*
     * this ugly mess matches the feature arc that is changing with affected
     * adjacencies
     */
    if (((ctx->arc == ip6_main.lookup_main.output_feature_arc_index) &&
         (VNET_LINK_IP6 == adj->ia_link)) ||
        ((ctx->arc == ip4_main.lookup_main.output_feature_arc_index) &&
         (VNET_LINK_IP4 == adj->ia_link)) ||
        ((ctx->arc == mpls_main.output_feature_arc_index) &&
         (VNET_LINK_MPLS == adj->ia_link)))
    {
        if (ctx->enable)
            adj->rewrite_header.flags |= VNET_REWRITE_HAS_FEATURES;
        else
            adj->rewrite_header.flags &= ~VNET_REWRITE_HAS_FEATURES;
    }
    return (ADJ_WALK_RC_CONTINUE);
}

void
adj_feature_update (u32 sw_if_index,
                    u8 arc_index,
                    u8 is_enable)
{
    /*
     * Walk all the adjacencies on the interface to update the cached
     * 'has-features' flag
     */
    adj_feature_update_ctx_t ctx = {
        .arc = arc_index,
        .enable = is_enable,
    };
    adj_walk (sw_if_index, adj_feature_update_walk_cb, &ctx);
}

/**
 * @brief Walk the Adjacencies on a given interface
 */
void
adj_walk (u32 sw_if_index,
          adj_walk_cb_t cb,
          void *ctx)
{
    /*
     * walk all the neighbor adjacencies
     */
    fib_protocol_t proto;

    FOR_EACH_FIB_IP_PROTOCOL(proto)
    {
        adj_nbr_walk(sw_if_index, proto, cb, ctx);
        adj_mcast_walk(sw_if_index, proto, cb, ctx);
    }
}

/**
 * @brief Return the link type of the adjacency
 */
vnet_link_t
adj_get_link_type (adj_index_t ai)
{
    const ip_adjacency_t *adj;

    adj = adj_get(ai);

    return (adj->ia_link); 
}

/**
 * @brief Return the sw interface index of the adjacency.
 */
u32
adj_get_sw_if_index (adj_index_t ai)
{
    const ip_adjacency_t *adj;

    adj = adj_get(ai);

    return (adj->rewrite_header.sw_if_index);
}

/**
 * @brief Return true if the adjacency is 'UP', i.e. can be used for forwarding
 * 0 is down, !0 is up.
 */
int
adj_is_up (adj_index_t ai)
{
    const adj_delegate_t *aed;

    aed = adj_delegate_get(adj_get(ai), ADJ_DELEGATE_BFD);

    if (NULL == aed)
    {
        /*
         * no BFD tracking - resolved
         */
        return (!0);
    }
    else
    {
        /*
         * defer to the state of the BFD tracking
         */
        return (ADJ_BFD_STATE_UP == aed->ad_bfd_state);
    }
}

/**
 * @brief Return the rewrite string of the adjacency
 */
const u8*
adj_get_rewrite (adj_index_t ai)
{
    vnet_rewrite_header_t *rw;
    ip_adjacency_t *adj;

    adj = adj_get(ai);
    rw = &adj->rewrite_header;

    ASSERT (rw->data_bytes != 0xfefe);

    return (rw->data - rw->data_bytes);
}

static fib_node_t *
adj_get_node (fib_node_index_t index)
{
    ip_adjacency_t *adj;

    adj = adj_get(index);

    return (&adj->ia_node);
}

#define ADJ_FROM_NODE(_node)						\
    ((ip_adjacency_t*)((char*)_node - STRUCT_OFFSET_OF(ip_adjacency_t, ia_node)))

static void
adj_node_last_lock_gone (fib_node_t *node)
{
    adj_last_lock_gone(ADJ_FROM_NODE(node));
}

static fib_node_back_walk_rc_t
adj_back_walk_notify (fib_node_t *node,
		      fib_node_back_walk_ctx_t *ctx)
{
    /*
     * Que pasa. yo soj en el final!
     */
    ASSERT(0);

    return (FIB_NODE_BACK_WALK_CONTINUE);
}

/*
 * Adjacency's graph node virtual function table
 */
static const fib_node_vft_t adj_vft = {
    .fnv_get = adj_get_node,
    .fnv_last_lock = adj_node_last_lock_gone,
    .fnv_back_walk = adj_back_walk_notify,
};

static clib_error_t *
adj_module_init (vlib_main_t * vm)
{
    fib_node_register_type(FIB_NODE_TYPE_ADJ, &adj_vft);

    adj_nbr_module_init();
    adj_glean_module_init();
    adj_midchain_module_init();
    adj_mcast_module_init();

    return (NULL);
}

VLIB_INIT_FUNCTION (adj_module_init);

static clib_error_t *
adj_show (vlib_main_t * vm,
	  unformat_input_t * input,
	  vlib_cli_command_t * cmd)
{
    adj_index_t ai = ADJ_INDEX_INVALID;
    u32 sw_if_index = ~0;
    int summary = 0;

    while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
	if (unformat (input, "%d", &ai))
	    ;
	else if (unformat (input, "sum"))
	    summary = 1;
	else if (unformat (input, "summary"))
	    summary = 1;
	else if (unformat (input, "%U",
			   unformat_vnet_sw_interface, vnet_get_main(),
			   &sw_if_index))
	    ;
	else
	    break;
    }

    if (summary)
    {
        vlib_cli_output (vm, "Number of adjacenies: %d", pool_elts(adj_pool));
        vlib_cli_output (vm, "Per-adjacency counters: %s",
                         (adj_are_counters_enabled() ?
                          "enabled":
                          "disabled"));
    }
    else
    {
        if (ADJ_INDEX_INVALID != ai)
        {
            if (pool_is_free_index(adj_pool, ai))
            {
                vlib_cli_output (vm, "adjacency %d invalid", ai);
                return 0;
            }

            vlib_cli_output (vm, "[@%d] %U",
                             ai,
                             format_ip_adjacency,  ai,
                             FORMAT_IP_ADJACENCY_DETAIL);
        }
        else
        {
            /* *INDENT-OFF* */
            pool_foreach_index(ai, adj_pool,
            ({
                if (~0 != sw_if_index &&
                    sw_if_index != adj_get_sw_if_index(ai))
                {
                }
                else
                {
                    vlib_cli_output (vm, "[@%d] %U",
                                     ai,
                                     format_ip_adjacency, ai,
                                     FORMAT_IP_ADJACENCY_NONE);
                }
            }));
            /* *INDENT-ON* */
        }
    }
    return 0;
}

/*?
 * Show all adjacencies.
 * @cliexpar
 * @cliexstart{sh adj}
 * [@0]
 * [@1]  glean: loop0
 * [@2] ipv4 via 1.0.0.2 loop0: IP4: 00:00:22:aa:bb:cc -> 00:00:11:aa:bb:cc
 * [@3] mpls via 1.0.0.2 loop0: MPLS: 00:00:22:aa:bb:cc -> 00:00:11:aa:bb:cc
 * [@4] ipv4 via 1.0.0.3 loop0: IP4: 00:00:22:aa:bb:cc -> 00:00:11:aa:bb:cc
 * [@5] mpls via 1.0.0.3 loop0: MPLS: 00:00:22:aa:bb:cc -> 00:00:11:aa:bb:cc
 * @cliexend
 ?*/
VLIB_CLI_COMMAND (adj_show_command, static) = {
    .path = "show adj",
    .short_help = "show adj [<adj_index>] [interface] [summary]",
    .function = adj_show,
};

/**
 * @brief CLI invoked function to enable/disable per-adj counters
 */
static clib_error_t *
adj_cli_counters_set (vlib_main_t * vm,
                      unformat_input_t * input,
                      vlib_cli_command_t * cmd)
{
    clib_error_t *error = NULL;
    int enable = ~0;

    while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
    {
	if (unformat (input, "enable"))
	    enable = 1;
	else if (unformat (input, "disable"))
	    enable = 0;
	else
	    break;
    }

    if (enable != ~0)
    {
        /* user requested something sensible */
        adj_per_adj_counters = enable;
    }
    else
    {
        error = clib_error_return (0, "specify 'enable' or 'disable'");
    }

    return (error);
}

/*?
 * Enabe/disble per-adjacency counters. This is optional because it comes with
 * a non-negligible performance cost.
 ?*/
VLIB_CLI_COMMAND (adj_cli_counters_set_command, static) = {
    .path = "adjacency counters",
    .short_help = "adjacency counters [enable|disable]",
    .function = adj_cli_counters_set,
};