/* * Copyright (c) 2019 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 /** * The last allocated punt reason */ static vlib_punt_reason_t punt_reason_last; /** * Counters per punt-reason */ vlib_combined_counter_main_t punt_counters = { .name = "punt", .stat_segment_name = "/net/punt", }; /** * A punt reason */ typedef struct punt_reason_data_t_ { /** * The reason name */ u8 *pd_name; /** * The allocated reason value */ vlib_punt_reason_t pd_reason; /** * Clients/owners that have registered this reason */ u32 *pd_owners; } punt_reason_data_t; /** * data for each punt reason */ static punt_reason_data_t *punt_reason_data; typedef enum punt_format_flags_t_ { PUNT_FORMAT_FLAG_NONE = 0, PUNT_FORMAT_FLAG_DETAIL = (1 << 0), } punt_format_flags_t; /** * A registration, by a client, to direct punted traffic to a given node */ typedef struct punt_reg_t_ { /** * Reason the packets were punted */ vlib_punt_reason_t pr_reason; /** * number of clients that have made this registration */ u16 pr_locks; /** * The edge from the punt dispatch node to the requested node */ u16 pr_edge; /** * node-index to send punted packets to */ u32 pr_node_index; } punt_reg_t; /** * Pool of registrations */ static punt_reg_t *punt_reg_pool; /** * A DB of all the register nodes against punt reason and node index */ static uword *punt_reg_db; /** * A DB used in the DP per-reason to dispatch packets to the requested nodes. * this is a vector of edges per-reason */ u16 **punt_dp_db; /** * A client using the punt serivce and its registrations */ typedef struct punt_client_t_ { /** * The name of the client */ u8 *pc_name; /** * The registrations is has made */ u32 *pc_regs; } punt_client_t; /** * Pool of clients */ static punt_client_t *punt_client_pool; /** * DB of clients key'd by their name */ static uword *punt_client_db; u8 * format_vlib_punt_reason (u8 * s, va_list * args) { vlib_punt_reason_t pr = va_arg (*args, int); return (format (s, "[%d] %v", pr, punt_reason_data[pr].pd_name)); } vlib_punt_hdl_t vlib_punt_client_register (const char *who) { u8 *pc_name; uword *p; u32 pci; pc_name = format (NULL, "%s", who); p = hash_get_mem (punt_client_db, pc_name); if (NULL == p) { punt_client_t *pc; pool_get (punt_client_pool, pc); pci = pc - punt_client_pool; pc->pc_name = pc_name; hash_set_mem (punt_client_db, pc->pc_name, pci); } else { pci = p[0]; vec_free (pc_name); } return (pci); } static int punt_validate_client (vlib_punt_hdl_t client) { return (!pool_is_free_index (punt_client_pool, client)); } static u64 punt_reg_mk_key (vlib_punt_reason_t reason, u32 node_index) { return (((u64) node_index) << 32 | reason); } static u32 punt_reg_find (vlib_punt_reason_t reason, u32 node_index) { uword *p; p = hash_get (punt_reg_db, punt_reg_mk_key (reason, node_index)); if (p) return p[0]; return ~0; } static void punt_reg_add (const punt_reg_t * pr) { hash_set (punt_reg_db, punt_reg_mk_key (pr->pr_reason, pr->pr_node_index), pr - punt_reg_pool); } static void punt_reg_remove (const punt_reg_t * pr) { hash_unset (punt_reg_db, punt_reg_mk_key (pr->pr_reason, pr->pr_node_index)); } /** * reconstruct the DP per-reason DB */ static void punt_reg_mk_dp (vlib_punt_reason_t reason) { u32 pri, *prip, *pris; const punt_reg_t *pr; u16 *edges, *old; u64 key; pris = NULL; edges = NULL; vec_validate (punt_dp_db, reason); old = punt_dp_db[reason]; /* *INDENT-OFF* */ hash_foreach (key, pri, punt_reg_db, ({ vec_add1(pris, pri); })); /* *INDENT-ON* */ /* * A check for an empty vector is done in the DP, so the a zero * length vector here is ok */ vec_foreach (prip, pris) { pr = pool_elt_at_index (punt_reg_pool, *prip); if (pr->pr_reason == reason) vec_add1 (edges, pr->pr_edge); } /* atomic update of the DP */ punt_dp_db[reason] = edges; vec_free (old); } int vlib_punt_register (vlib_punt_hdl_t client, vlib_punt_reason_t reason, const char *node_name) { vlib_node_t *punt_to, *punt_from; punt_client_t *pc; vlib_main_t *vm; punt_reg_t *pr; u32 pri; if (reason >= punt_reason_last) return -1; if (!punt_validate_client (client)) return -2; vm = vlib_get_main (); pc = pool_elt_at_index (punt_client_pool, client); punt_to = vlib_get_node_by_name (vm, (u8 *) node_name); punt_from = vlib_get_node_by_name (vm, (u8 *) "punt-dispatch"); /* * find a global matching registration */ pri = punt_reg_find (reason, punt_to->index); if (~0 != pri) { u32 pos; pos = vec_search (pc->pc_regs, pri); if (~0 != pos) { /* duplicate registration for this client */ return -1; } pr = pool_elt_at_index (punt_reg_pool, pri); } else { pool_get (punt_reg_pool, pr); pr->pr_reason = reason; pr->pr_node_index = punt_to->index; pr->pr_edge = vlib_node_add_next (vm, punt_from->index, pr->pr_node_index); pri = pr - punt_reg_pool; punt_reg_add (pr); } /* * add this reg to the list the client has made */ pr->pr_locks++; vec_add1 (pc->pc_regs, pri); punt_reg_mk_dp (reason); return 0; } int vlib_punt_unregister (vlib_punt_hdl_t client, vlib_punt_reason_t reason, const char *node_name) { vlib_node_t *punt_to; punt_client_t *pc; vlib_main_t *vm; punt_reg_t *pr; u32 pri; if (reason >= punt_reason_last) return -1; vm = vlib_get_main (); pc = pool_elt_at_index (punt_client_pool, client); punt_to = vlib_get_node_by_name (vm, (u8 *) node_name); /* * construct a registration and check if it's one this client already has */ pri = punt_reg_find (reason, punt_to->index); if (~0 != pri) { u32 pos; pos = vec_search (pc->pc_regs, pri); if (~0 == pos) { /* not a registration for this client */ return -1; } vec_del1 (pc->pc_regs, pos); pr = pool_elt_at_index (punt_reg_pool, pri); pr->pr_locks--; if (0 == pr->pr_locks) { punt_reg_remove (pr); pool_put (punt_reg_pool, pr); } } /* * rebuild the DP data-base */ punt_reg_mk_dp (reason); return (0); } int vlib_punt_reason_alloc (vlib_punt_hdl_t client, const char *reason_name, vlib_punt_reason_t * reason) { vlib_punt_reason_t new; if (!punt_validate_client (client)) return -2; new = punt_reason_last++; vec_validate (punt_reason_data, new); punt_reason_data[new].pd_name = format (NULL, "%s", reason_name); punt_reason_data[new].pd_reason = new; vec_add1 (punt_reason_data[new].pd_owners, client); vlib_validate_combined_counter (&punt_counters, new); vlib_zero_combined_counter (&punt_counters, new); *reason = new; /* build the DP data-base */ punt_reg_mk_dp (*reason); return (0); } /* Parse node name -> node index. */ uword unformat_punt_client (unformat_input_t * input, va_list * args) { u32 *result = va_arg (*args, u32 *); return unformat_user (input, unformat_hash_vec_string, punt_client_db, result); } u8 * format_punt_reg (u8 * s, va_list * args) { u32 pri = va_arg (*args, u32);