/* * Copyright (c) 2017 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 <plugins/abf/abf_policy.h> #include <vlib/vlib.h> #include <vnet/plugin/plugin.h> #include <vnet/fib/fib_path_list.h> #include <vnet/fib/fib_walk.h> /** * FIB node type the attachment is registered */ fib_node_type_t abf_policy_fib_node_type; /** * Pool of ABF objects */ static abf_policy_t *abf_policy_pool; /** * DB of ABF policy objects * - policy ID to index conversion. */ static uword *abf_policy_db; abf_policy_t * abf_policy_get (u32 index) { return (pool_elt_at_index (abf_policy_pool, index)); } static u32 abf_policy_get_index (const abf_policy_t * abf) { return (abf - abf_policy_pool); } static abf_policy_t * abf_policy_find_i (u32 policy_id) { u32 api; api = abf_policy_find (policy_id); if (INDEX_INVALID != api) return (abf_policy_get (api)); return (NULL); } u32 abf_policy_find (u32 policy_id) { uword *p; p = hash_get (abf_policy_db, policy_id); if (NULL != p) return (p[0]); return (INDEX_INVALID); } int abf_policy_update (u32 policy_id, u32 acl_index, const fib_route_path_t * rpaths) { abf_policy_t *ap; u32 api; api = abf_policy_find (policy_id); if (INDEX_INVALID == api) { /* * create a new policy */ pool_get (abf_policy_pool, ap); api = ap - abf_policy_pool; fib_node_init (&ap->ap_node, abf_policy_fib_node_type); ap->ap_acl = acl_index; ap->ap_id = policy_id; ap->ap_pl = fib_path_list_create ((FIB_PATH_LIST_FLAG_SHARED | FIB_PATH_LIST_FLAG_NO_URPF), rpaths); /* * become a child of the path list so we get poked when * the forwarding changes. */ ap->ap_sibling = fib_path_list_child_add (ap->ap_pl, abf_policy_fib_node_type, api); /* * add this new policy to the DB */ hash_set (abf_policy_db, policy_id, api); /* * take a lock on behalf of the CLI/API creation */ fib_node_lock (&ap->ap_node); } else { /* * update an existing policy. * - add the path to the path-list and swap our ancestry * - backwalk to poke all attachments to update */ fib_node_index_t old_pl; ap = abf_policy_get (api); old_pl = ap->ap_pl; if (ap->ap_acl != acl_index) { /* Should change this error code to something more descriptive */ return (VNET_API_ERROR_INVALID_VALUE); } if (FIB_NODE_INDEX_INVALID != old_pl) { ap->ap_pl = fib_path_list_copy_and_path_add (old_pl, (FIB_PATH_LIST_FLAG_SHARED | FIB_PATH_LIST_FLAG_NO_URPF), rpaths); fib_path_list_child_remove (old_pl, ap->ap_sibling); } else { ap->ap_pl = fib_path_list_create ((FIB_PATH_LIST_FLAG_SHARED | FIB_PATH_LIST_FLAG_NO_URPF), rpaths); } ap->ap_sibling = fib_path_list_child_add (ap->ap_pl, abf_policy_fib_node_type, api); fib_node_back_walk_ctx_t ctx = { .fnbw_reason = FIB_NODE_BW_REASON_FLAG_EVALUATE, }; fib_walk_sync (abf_policy_fib_node_type, api, &ctx); } return (0); } static void abf_policy_destroy (abf_policy_t * ap) { /* * this ABF should not be a sibling on the path list, since * that was removed when the API config went */ ASSERT (ap->ap_sibling == ~0); ASSERT (ap->ap_pl == FIB_NODE_INDEX_INVALID); hash_unset (abf_policy_db, ap->ap_id); pool_put (abf_policy_pool, ap); } int abf_policy_delete (u32 policy_id, const fib_route_path_t * rpaths) { abf_policy_t *ap; u32 api; api = abf_policy_find (policy_id); if (INDEX_INVALID == api) { /* * no such policy */ return (VNET_API_ERROR_INVALID_VALUE); } else { /* * update an existing policy. * - add the path to the path-list and swap our ancestry * - backwalk to poke all attachments to update */ fib_node_index_t old_pl; ap = abf_policy_get (api); old_pl = ap->ap_pl; fib_path_list_lock (old_pl); ap->ap_pl = fib_path_list_copy_and_path_remove (ap->ap_pl, (FIB_PATH_LIST_FLAG_SHARED | FIB_PATH_LIST_FLAG_NO_URPF), rpaths); fib_path_list_child_remove (old_pl, ap->ap_sibling); ap->ap_sibling = ~0; if (FIB_NODE_INDEX_INVALID == ap->ap_pl) { /* * no more paths on this policy. It's toast * remove the CLI/API's lock */ fib_node_unlock (&ap->ap_node); } else { ap->ap_sibling = fib_path_list_child_add (ap->ap_pl, abf_policy_fib_node_type, api); fib_node_back_walk_ctx_t ctx = { .fnbw_reason = FIB_NODE_BW_REASON_FLAG_EVALUATE, }; fib_walk_sync (abf_policy_fib_node_type, api, &ctx); } fib_path_list_unlock (old_pl); } return (0); } static clib_error_t * abf_policy_cmd (vlib_main_t * vm, unformat_input_t * main_input, vlib_cli_command_t * cmd) { unformat_input_t _line_input, *line_input = &_line_input; fib_route_path_t *rpaths = NULL, rpath; u32 acl_index, policy_id, is_del; dpo_proto_t payload_proto; int rv = 0; is_del = 0; acl_index = INDEX_INVALID; policy_id = INDEX_INVALID; /* Get a line of input. */ if (!unformat_user (main_input, unformat_line_input, line_input)) return 0; while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "acl %d", &acl_index)) ; else if (unformat (line_input, "id %d", &policy_id)) ; else if (unformat (line_input, "del")) is_del = 1; else if (unformat (line_input, "add")) is_del = 0; else if (unformat (line_input, "via %U", unformat_fib_route_path, &rpath, &payload_proto)) vec_add1 (rpaths, rpath); else return (clib_error_return (0, "unknown input '%U'", format_unformat_error, line_input)); } if (INDEX_INVALID == policy_id) { vlib_cli_output (vm, "Specify a Policy ID"); return 0; } if (!is_del) { if (INDEX_INVALID == acl_index) { vlib_cli_output (vm, "ACL index must be set"); return 0; } rv = abf_policy_update (policy_id, acl_index, rpaths); /* Should change this error code to something more descriptive */ if (rv == VNET_API_ERROR_INVALID_VALUE) { vlib_cli_output (vm, "ACL index must match existing ACL index in policy"); return 0; } } else { abf_policy_delete (policy_id, rpaths); } unformat_free (line_input); return (NULL); } /* *INDENT-OFF* */ /** * Create an ABF policy. */ VLIB_CLI_COMMAND (abf_policy_cmd_node, static) = { .path = "abf policy", .function = abf_policy_cmd, .short_help = "abf policy [add|del] id <index> acl <index> via ...", .is_mp_safe = 1, }; /* *INDENT-ON* */ static u8 * format_abf (u8 * s, va_list * args) { abf_policy_t *ap = va_arg (*args, abf_policy_t *); s = format (s, "abf:[%d]: policy:%d acl:%d", ap - abf_policy_pool, ap->ap_id, ap->ap_acl); s = format (s, "\n "); if (FIB_NODE_INDEX_INVALID == ap->ap_pl) { s = format (s, "no forwarding"); } else { s = fib_path_list_format (ap->ap_pl, s); } return (s); } void abf_policy_walk (abf_policy_walk_cb_t cb, void *ctx) { u32 api; /* *INDENT-OFF* */ pool_foreach_index(api, abf_policy_pool, ({ if (!cb(api, ctx)) break; })); /* *INDENT-ON* */ } static clib_error_t * abf_show_policy_cmd (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { u32 policy_id; abf_policy_t *ap; policy_id = INDEX_INVALID; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "%d", &policy_id)) ; else return (clib_error_return (0, "unknown input '%U'", format_unformat_error, input)); } if (INDEX_INVALID == policy_id) { /* *INDENT-OFF* */ pool_foreach(ap, abf_policy_pool, ({ vlib_cli_output(vm, "%U", format_abf, ap); })); /* *INDENT-ON* */ } else { ap = abf_policy_find_i (policy_id); if (NULL != ap) vlib_cli_output (vm, "%U", format_abf, ap); else vlib_cli_output (vm, "Invalid policy ID:%d", policy_id); } return (NULL); } /* *INDENT-OFF* */ VLIB_CLI_COMMAND (abf_policy_show_policy_cmd_node, static) = { .path = "show abf policy", .function = abf_show_policy_cmd, .short_help = "show abf policy <value>", .is_mp_safe = 1, }; /* *INDENT-ON* */ static fib_node_t * abf_policy_get_node (fib_node_index_t index) { abf_policy_t *ap = abf_policy_get (index); return (&(ap->ap_node)); } static abf_policy_t * abf_policy_get_from_node (fib_node_t * node) { return ((abf_policy_t *) (((char *) node) - STRUCT_OFFSET_OF (abf_policy_t, ap_node))); } static void abf_policy_last_lock_gone (fib_node_t * node) { abf_policy_destroy (abf_policy_get_from_node (node)); } /* * A back walk has reached this ABF policy */ static fib_node_back_walk_rc_t abf_policy_back_walk_notify (fib_node_t * node, fib_node_back_walk_ctx_t * ctx) { /* * re-stack the fmask on the n-eos of the via */ abf_policy_t *abf = abf_policy_get_from_node (node); /* * propagate further up the graph. * we can do this synchronously since the fan out is small. */ fib_walk_sync (abf_policy_fib_node_type, abf_policy_get_index (abf), ctx); return (FIB_NODE_BACK_WALK_CONTINUE); } /* * The BIER fmask's graph node virtual function table */ static const fib_node_vft_t abf_policy_vft = { .fnv_get = abf_policy_get_node, .fnv_last_lock = abf_policy_last_lock_gone, .fnv_back_walk = abf_policy_back_walk_notify, }; static clib_error_t * abf_policy_init (vlib_main_t * vm) { abf_policy_fib_node_type = fib_node_register_new_type (&abf_policy_vft); return (NULL); } VLIB_INIT_FUNCTION (abf_policy_init); /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */