diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
commit | 2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch) | |
tree | c05dc0f8e6aa3accc84e3e5cffc933ed94941383 /lib/filter.c | |
parent | Initial commit. (diff) | |
download | frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.tar.xz frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.zip |
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | lib/filter.c | 919 |
1 files changed, 919 insertions, 0 deletions
diff --git a/lib/filter.c b/lib/filter.c new file mode 100644 index 0000000..fc4b578 --- /dev/null +++ b/lib/filter.c @@ -0,0 +1,919 @@ +/* Route filtering function. + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <zebra.h> + +#include "prefix.h" +#include "filter.h" +#include "memory.h" +#include "command.h" +#include "sockunion.h" +#include "buffer.h" +#include "log.h" +#include "routemap.h" +#include "libfrr.h" +#include "northbound_cli.h" +#include "json.h" + +DEFINE_MTYPE_STATIC(LIB, ACCESS_LIST, "Access List"); +DEFINE_MTYPE_STATIC(LIB, ACCESS_LIST_STR, "Access List Str"); +DEFINE_MTYPE_STATIC(LIB, ACCESS_FILTER, "Access Filter"); + +/* Static structure for mac access_list's master. */ +static struct access_master access_master_mac = { + {NULL, NULL}, + NULL, + NULL, +}; + +/* Static structure for IPv4 access_list's master. */ +static struct access_master access_master_ipv4 = { + {NULL, NULL}, + NULL, + NULL, +}; + +/* Static structure for IPv6 access_list's master. */ +static struct access_master access_master_ipv6 = { + {NULL, NULL}, + NULL, + NULL, +}; + +static struct access_master *access_master_get(afi_t afi) +{ + if (afi == AFI_IP) + return &access_master_ipv4; + else if (afi == AFI_IP6) + return &access_master_ipv6; + else if (afi == AFI_L2VPN) + return &access_master_mac; + return NULL; +} + +/* Allocate new filter structure. */ +struct filter *filter_new(void) +{ + return XCALLOC(MTYPE_ACCESS_FILTER, sizeof(struct filter)); +} + +static void filter_free(struct filter *filter) +{ + XFREE(MTYPE_ACCESS_FILTER, filter); +} + +/* Return string of filter_type. */ +static const char *filter_type_str(struct filter *filter) +{ + switch (filter->type) { + case FILTER_PERMIT: + return "permit"; + case FILTER_DENY: + return "deny"; + case FILTER_DYNAMIC: + return "dynamic"; + default: + return ""; + } +} + +/* If filter match to the prefix then return 1. */ +static int filter_match_cisco(struct filter *mfilter, const struct prefix *p) +{ + struct filter_cisco *filter; + struct in_addr mask; + uint32_t check_addr; + uint32_t check_mask; + + filter = &mfilter->u.cfilter; + check_addr = p->u.prefix4.s_addr & ~filter->addr_mask.s_addr; + + if (filter->extended) { + masklen2ip(p->prefixlen, &mask); + check_mask = mask.s_addr & ~filter->mask_mask.s_addr; + + if (memcmp(&check_addr, &filter->addr.s_addr, IPV4_MAX_BYTELEN) + == 0 + && memcmp(&check_mask, &filter->mask.s_addr, + IPV4_MAX_BYTELEN) + == 0) + return 1; + } else if (memcmp(&check_addr, &filter->addr.s_addr, IPV4_MAX_BYTELEN) + == 0) + return 1; + + return 0; +} + +/* If filter match to the prefix then return 1. */ +static int filter_match_zebra(struct filter *mfilter, const struct prefix *p) +{ + struct filter_zebra *filter = NULL; + + filter = &mfilter->u.zfilter; + + if (filter->prefix.family == p->family) { + if (filter->exact) { + if (filter->prefix.prefixlen == p->prefixlen) + return prefix_match(&filter->prefix, p); + else + return 0; + } else + return prefix_match(&filter->prefix, p); + } else + return 0; +} + +/* Allocate new access list structure. */ +static struct access_list *access_list_new(void) +{ + return XCALLOC(MTYPE_ACCESS_LIST, sizeof(struct access_list)); +} + +/* Free allocated access_list. */ +static void access_list_free(struct access_list *access) +{ + XFREE(MTYPE_ACCESS_LIST, access); +} + +/* Delete access_list from access_master and free it. */ +void access_list_delete(struct access_list *access) +{ + struct filter *filter; + struct filter *next; + struct access_list_list *list; + struct access_master *master; + + for (filter = access->head; filter; filter = next) { + next = filter->next; + filter_free(filter); + } + + master = access->master; + + list = &master->str; + + if (access->next) + access->next->prev = access->prev; + else + list->tail = access->prev; + + if (access->prev) + access->prev->next = access->next; + else + list->head = access->next; + + route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_DELETED); + + if (master->delete_hook) + master->delete_hook(access); + + XFREE(MTYPE_ACCESS_LIST_STR, access->name); + + XFREE(MTYPE_TMP, access->remark); + + access_list_free(access); +} + +/* Insert new access list to list of access_list. Each access_list + is sorted by the name. */ +static struct access_list *access_list_insert(afi_t afi, const char *name) +{ + struct access_list *access; + struct access_list *point; + struct access_list_list *alist; + struct access_master *master; + + master = access_master_get(afi); + if (master == NULL) + return NULL; + + /* Allocate new access_list and copy given name. */ + access = access_list_new(); + access->name = XSTRDUP(MTYPE_ACCESS_LIST_STR, name); + access->master = master; + + /* Set access_list to string list. */ + alist = &master->str; + + /* Set point to insertion point. */ + for (point = alist->head; point; point = point->next) + if (strcmp(point->name, name) >= 0) + break; + + /* In case of this is the first element of master. */ + if (alist->head == NULL) { + alist->head = alist->tail = access; + return access; + } + + /* In case of insertion is made at the tail of access_list. */ + if (point == NULL) { + access->prev = alist->tail; + alist->tail->next = access; + alist->tail = access; + return access; + } + + /* In case of insertion is made at the head of access_list. */ + if (point == alist->head) { + access->next = alist->head; + alist->head->prev = access; + alist->head = access; + return access; + } + + /* Insertion is made at middle of the access_list. */ + access->next = point; + access->prev = point->prev; + + if (point->prev) + point->prev->next = access; + point->prev = access; + + return access; +} + +/* Lookup access_list from list of access_list by name. */ +struct access_list *access_list_lookup(afi_t afi, const char *name) +{ + struct access_list *access; + struct access_master *master; + + if (name == NULL) + return NULL; + + master = access_master_get(afi); + if (master == NULL) + return NULL; + + for (access = master->str.head; access; access = access->next) + if (strcmp(access->name, name) == 0) + return access; + + return NULL; +} + +/* Get access list from list of access_list. If there isn't matched + access_list create new one and return it. */ +struct access_list *access_list_get(afi_t afi, const char *name) +{ + struct access_list *access; + + access = access_list_lookup(afi, name); + if (access == NULL) + access = access_list_insert(afi, name); + return access; +} + +/* Apply access list to object (which should be struct prefix *). */ +enum filter_type access_list_apply(struct access_list *access, + const void *object) +{ + struct filter *filter; + const struct prefix *p = (const struct prefix *)object; + + if (access == NULL) + return FILTER_DENY; + + for (filter = access->head; filter; filter = filter->next) { + if (filter->cisco) { + if (filter_match_cisco(filter, p)) + return filter->type; + } else { + if (filter_match_zebra(filter, p)) + return filter->type; + } + } + + return FILTER_DENY; +} + +/* Add hook function. */ +void access_list_add_hook(void (*func)(struct access_list *access)) +{ + access_master_ipv4.add_hook = func; + access_master_ipv6.add_hook = func; + access_master_mac.add_hook = func; +} + +/* Delete hook function. */ +void access_list_delete_hook(void (*func)(struct access_list *access)) +{ + access_master_ipv4.delete_hook = func; + access_master_ipv6.delete_hook = func; + access_master_mac.delete_hook = func; +} + +/* Calculate new sequential number. */ +int64_t filter_new_seq_get(struct access_list *access) +{ + int64_t maxseq; + int64_t newseq; + struct filter *filter; + + maxseq = 0; + + for (filter = access->head; filter; filter = filter->next) { + if (maxseq < filter->seq) + maxseq = filter->seq; + } + + newseq = ((maxseq / 5) * 5) + 5; + + return (newseq > UINT_MAX) ? UINT_MAX : newseq; +} + +/* Return access list entry which has same seq number. */ +static struct filter *filter_seq_check(struct access_list *access, + int64_t seq) +{ + struct filter *filter; + + for (filter = access->head; filter; filter = filter->next) + if (filter->seq == seq) + return filter; + return NULL; +} + +/* Delete filter from specified access_list. If there is hook + function execute it. */ +void access_list_filter_delete(struct access_list *access, + struct filter *filter) +{ + struct access_master *master; + + master = access->master; + + if (filter->next) + filter->next->prev = filter->prev; + else + access->tail = filter->prev; + + if (filter->prev) + filter->prev->next = filter->next; + else + access->head = filter->next; + + filter_free(filter); + + route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_DELETED); + /* Run hook function. */ + if (master->delete_hook) + (*master->delete_hook)(access); +} + +/* Add new filter to the end of specified access_list. */ +void access_list_filter_add(struct access_list *access, + struct filter *filter) +{ + struct filter *replace; + struct filter *point; + + /* Automatic assignment of seq no. */ + if (filter->seq == -1) + filter->seq = filter_new_seq_get(access); + + if (access->tail && filter->seq > access->tail->seq) + point = NULL; + else { + /* Is there any same seq access list filter? */ + replace = filter_seq_check(access, filter->seq); + if (replace) + access_list_filter_delete(access, replace); + + /* Check insert point. */ + for (point = access->head; point; point = point->next) + if (point->seq >= filter->seq) + break; + } + + /* In case of this is the first element of the list. */ + filter->next = point; + + if (point) { + if (point->prev) + point->prev->next = filter; + else + access->head = filter; + + filter->prev = point->prev; + point->prev = filter; + } else { + if (access->tail) + access->tail->next = filter; + else + access->head = filter; + + filter->prev = access->tail; + access->tail = filter; + } + + /* Run hook function. */ + if (access->master->add_hook) + (*access->master->add_hook)(access); + route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_ADDED); +} + +/* + deny Specify packets to reject + permit Specify packets to forward + dynamic ? +*/ + +/* + Hostname or A.B.C.D Address to match + any Any source host + host A single host address +*/ + +static void config_write_access_zebra(struct vty *, struct filter *, + json_object *); +static void config_write_access_cisco(struct vty *, struct filter *, + json_object *); + +static const char *filter_type2str(struct filter *filter) +{ + if (filter->cisco) { + if (filter->u.cfilter.extended) + return "Extended"; + else + return "Standard"; + } else + return "Zebra"; +} + +/* show access-list command. */ +static int filter_show(struct vty *vty, const char *name, afi_t afi, + bool use_json) +{ + struct access_list *access; + struct access_master *master; + struct filter *mfilter; + struct filter_cisco *filter; + bool first; + json_object *json = NULL; + json_object *json_proto = NULL; + + master = access_master_get(afi); + if (master == NULL) { + if (use_json) + vty_out(vty, "{}\n"); + return 0; + } + + if (use_json) + json = json_object_new_object(); + + /* Print the name of the protocol */ + if (json) { + json_proto = json_object_new_object(); + json_object_object_add(json, frr_protoname, json_proto); + } else + vty_out(vty, "%s:\n", frr_protoname); + + for (access = master->str.head; access; access = access->next) { + json_object *json_acl = NULL; + json_object *json_rules = NULL; + + if (name && strcmp(access->name, name) != 0) + continue; + + first = true; + + for (mfilter = access->head; mfilter; mfilter = mfilter->next) { + json_object *json_rule = NULL; + + filter = &mfilter->u.cfilter; + + if (first) { + const char *type = filter_type2str(mfilter); + + if (json) { + json_acl = json_object_new_object(); + json_object_object_add(json_proto, + access->name, + json_acl); + + json_object_string_add(json_acl, "type", + type); + json_object_string_add(json_acl, + "addressFamily", + afi2str(afi)); + json_rules = json_object_new_array(); + json_object_object_add( + json_acl, "rules", json_rules); + } else { + vty_out(vty, "%s %s access list %s\n", + type, + (afi == AFI_IP) + ? ("IP") + : ((afi == AFI_IP6) + ? ("IPv6 ") + : ("MAC ")), + access->name); + } + + first = false; + } + + if (json) { + json_rule = json_object_new_object(); + json_object_array_add(json_rules, json_rule); + + json_object_int_add(json_rule, "sequenceNumber", + mfilter->seq); + json_object_string_add( + json_rule, "filterType", + filter_type_str(mfilter)); + } else { + vty_out(vty, " seq %" PRId64, mfilter->seq); + vty_out(vty, " %s%s", filter_type_str(mfilter), + mfilter->type == FILTER_DENY ? " " + : ""); + } + + if (!mfilter->cisco) + config_write_access_zebra(vty, mfilter, + json_rule); + else if (filter->extended) + config_write_access_cisco(vty, mfilter, + json_rule); + else { + if (json) { + json_object_string_addf( + json_rule, "address", "%pI4", + &filter->addr); + json_object_string_addf( + json_rule, "mask", "%pI4", + &filter->addr_mask); + } else { + if (filter->addr_mask.s_addr + == 0xffffffff) + vty_out(vty, " any\n"); + else { + vty_out(vty, " %pI4", + &filter->addr); + if (filter->addr_mask.s_addr + != INADDR_ANY) + vty_out(vty, + ", wildcard bits %pI4", + &filter->addr_mask); + vty_out(vty, "\n"); + } + } + } + } + } + + return vty_json(vty, json); +} + +/* show MAC access list - this only has MAC filters for now*/ +DEFUN (show_mac_access_list, + show_mac_access_list_cmd, + "show mac access-list", + SHOW_STR + "mac access lists\n" + "List mac access lists\n") +{ + return filter_show(vty, NULL, AFI_L2VPN, false); +} + +DEFUN (show_mac_access_list_name, + show_mac_access_list_name_cmd, + "show mac access-list ACCESSLIST_MAC_NAME", + SHOW_STR + "mac access lists\n" + "List mac access lists\n" + "mac address\n") +{ + return filter_show(vty, argv[3]->arg, AFI_L2VPN, false); +} + +DEFUN (show_ip_access_list, + show_ip_access_list_cmd, + "show ip access-list [json]", + SHOW_STR + IP_STR + "List IP access lists\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + return filter_show(vty, NULL, AFI_IP, uj); +} + +DEFUN (show_ip_access_list_name, + show_ip_access_list_name_cmd, + "show ip access-list ACCESSLIST4_NAME [json]", + SHOW_STR + IP_STR + "List IP access lists\n" + "IP access-list name\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + int idx_acl = 3; + return filter_show(vty, argv[idx_acl]->arg, AFI_IP, uj); +} + +DEFUN (show_ipv6_access_list, + show_ipv6_access_list_cmd, + "show ipv6 access-list [json]", + SHOW_STR + IPV6_STR + "List IPv6 access lists\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + return filter_show(vty, NULL, AFI_IP6, uj); +} + +DEFUN (show_ipv6_access_list_name, + show_ipv6_access_list_name_cmd, + "show ipv6 access-list ACCESSLIST6_NAME [json]", + SHOW_STR + IPV6_STR + "List IPv6 access lists\n" + "IPv6 access-list name\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + int idx_word = 3; + return filter_show(vty, argv[idx_word]->arg, AFI_IP6, uj); +} + +static void config_write_access_cisco(struct vty *vty, struct filter *mfilter, + json_object *json) +{ + struct filter_cisco *filter; + + filter = &mfilter->u.cfilter; + + if (json) { + json_object_boolean_add(json, "extended", !!filter->extended); + json_object_string_addf(json, "sourceAddress", "%pI4", + &filter->addr); + json_object_string_addf(json, "sourceMask", "%pI4", + &filter->addr_mask); + json_object_string_addf(json, "destinationAddress", "%pI4", + &filter->mask); + json_object_string_addf(json, "destinationMask", "%pI4", + &filter->mask_mask); + } else { + vty_out(vty, " ip"); + if (filter->addr_mask.s_addr == 0xffffffff) + vty_out(vty, " any"); + else if (filter->addr_mask.s_addr == INADDR_ANY) + vty_out(vty, " host %pI4", &filter->addr); + else { + vty_out(vty, " %pI4", &filter->addr); + vty_out(vty, " %pI4", &filter->addr_mask); + } + + if (filter->mask_mask.s_addr == 0xffffffff) + vty_out(vty, " any"); + else if (filter->mask_mask.s_addr == INADDR_ANY) + vty_out(vty, " host %pI4", &filter->mask); + else { + vty_out(vty, " %pI4", &filter->mask); + vty_out(vty, " %pI4", &filter->mask_mask); + } + vty_out(vty, "\n"); + } +} + +static void config_write_access_zebra(struct vty *vty, struct filter *mfilter, + json_object *json) +{ + struct filter_zebra *filter; + struct prefix *p; + char buf[BUFSIZ]; + + filter = &mfilter->u.zfilter; + p = &filter->prefix; + + if (json) { + json_object_string_addf(json, "prefix", "%pFX", p); + json_object_boolean_add(json, "exact-match", !!filter->exact); + } else { + if (p->prefixlen == 0 && !filter->exact) + vty_out(vty, " any"); + else if (p->family == AF_INET6 || p->family == AF_INET) + vty_out(vty, " %pFX%s", p, + filter->exact ? " exact-match" : ""); + else if (p->family == AF_ETHERNET) { + if (p->prefixlen == 0) + vty_out(vty, " any"); + else + vty_out(vty, " %s", + prefix_mac2str(&(p->u.prefix_eth), buf, + sizeof(buf))); + } + + vty_out(vty, "\n"); + } +} + +static struct cmd_node access_mac_node = { + .name = "MAC access list", + .node = ACCESS_MAC_NODE, + .prompt = "", +}; + +static void access_list_reset_mac(void) +{ + struct access_list *access; + struct access_list *next; + struct access_master *master; + + master = access_master_get(AFI_L2VPN); + if (master == NULL) + return; + + for (access = master->str.head; access; access = next) { + next = access->next; + access_list_delete(access); + } + + assert(master->str.head == NULL); + assert(master->str.tail == NULL); +} + +/* Install vty related command. */ +static void access_list_init_mac(void) +{ + install_node(&access_mac_node); + + install_element(ENABLE_NODE, &show_mac_access_list_cmd); + install_element(ENABLE_NODE, &show_mac_access_list_name_cmd); +} + +/* Access-list node. */ +static int config_write_access(struct vty *vty); +static struct cmd_node access_node = { + .name = "ipv4 access list", + .node = ACCESS_NODE, + .prompt = "", + .config_write = config_write_access, +}; + +static int config_write_access(struct vty *vty) +{ + struct lyd_node *dnode; + int written = 0; + + dnode = yang_dnode_get(running_config->dnode, "/frr-filter:lib"); + if (dnode) { + nb_cli_show_dnode_cmds(vty, dnode, false); + written = 1; + } + + return written; +} + +static void access_list_reset_ipv4(void) +{ + struct access_list *access; + struct access_list *next; + struct access_master *master; + + master = access_master_get(AFI_IP); + if (master == NULL) + return; + + for (access = master->str.head; access; access = next) { + next = access->next; + access_list_delete(access); + } + + assert(master->str.head == NULL); + assert(master->str.tail == NULL); +} + +/* Install vty related command. */ +static void access_list_init_ipv4(void) +{ + install_node(&access_node); + + install_element(ENABLE_NODE, &show_ip_access_list_cmd); + install_element(ENABLE_NODE, &show_ip_access_list_name_cmd); +} + +static void access_list_autocomplete_afi(afi_t afi, vector comps, + struct cmd_token *token) +{ + struct access_list *access; + struct access_list *next; + struct access_master *master; + + master = access_master_get(afi); + if (master == NULL) + return; + + for (access = master->str.head; access; access = next) { + next = access->next; + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, access->name)); + } +} + +static struct cmd_node access_ipv6_node = { + .name = "ipv6 access list", + .node = ACCESS_IPV6_NODE, + .prompt = "", +}; + +static void access_list_autocomplete(vector comps, struct cmd_token *token) +{ + access_list_autocomplete_afi(AFI_IP, comps, token); + access_list_autocomplete_afi(AFI_IP6, comps, token); + access_list_autocomplete_afi(AFI_L2VPN, comps, token); +} + +static void access_list4_autocomplete(vector comps, struct cmd_token *token) +{ + access_list_autocomplete_afi(AFI_IP, comps, token); +} + +static void access_list6_autocomplete(vector comps, struct cmd_token *token) +{ + access_list_autocomplete_afi(AFI_IP6, comps, token); +} + +static void access_list_mac_autocomplete(vector comps, struct cmd_token *token) +{ + access_list_autocomplete_afi(AFI_L2VPN, comps, token); +} + +static const struct cmd_variable_handler access_list_handlers[] = { + {.tokenname = "ACCESSLIST_NAME", + .completions = access_list_autocomplete}, + {.tokenname = "ACCESSLIST4_NAME", + .completions = access_list4_autocomplete}, + {.tokenname = "ACCESSLIST6_NAME", + .completions = access_list6_autocomplete}, + {.tokenname = "ACCESSLIST_MAC_NAME", + .completions = access_list_mac_autocomplete}, + {.completions = NULL}}; + +static void access_list_reset_ipv6(void) +{ + struct access_list *access; + struct access_list *next; + struct access_master *master; + + master = access_master_get(AFI_IP6); + if (master == NULL) + return; + + for (access = master->str.head; access; access = next) { + next = access->next; + access_list_delete(access); + } + + assert(master->str.head == NULL); + assert(master->str.tail == NULL); +} + +static void access_list_init_ipv6(void) +{ + install_node(&access_ipv6_node); + + install_element(ENABLE_NODE, &show_ipv6_access_list_cmd); + install_element(ENABLE_NODE, &show_ipv6_access_list_name_cmd); +} + +void access_list_init(void) +{ + cmd_variable_handler_register(access_list_handlers); + + access_list_init_ipv4(); + access_list_init_ipv6(); + access_list_init_mac(); + + filter_cli_init(); +} + +void access_list_reset(void) +{ + access_list_reset_ipv4(); + access_list_reset_ipv6(); + access_list_reset_mac(); +} |