frr/bgpd/bgp_filter.c
Francois Dumontet 094dcc3cda bgpd: fix "bgp as-pah access-list" with "set aspath exclude" set/unset issues
whith the following config

router bgp 65001
 no bgp ebgp-requires-policy
 neighbor 192.168.1.2 remote-as external
 neighbor 192.168.1.2 timers 3 10
 !
 address-family ipv4 unicast
  neighbor 192.168.1.2 route-map r2 in
 exit-address-family
exit
!
bgp as-path access-list FIRST seq 5 permit ^65
bgp as-path access-list SECOND seq 5 permit 2$
!
route-map r2 permit 6
 match ip address prefix-list p2
 set as-path exclude as-path-access-list SECOND
exit
!
route-map r2 permit 10
 match ip address prefix-list p1
 set as-path exclude 65003
exit
!
route-map r2 permit 20
 match ip address prefix-list p3
 set as-path exclude all
exit

making some
no bgp as-path access-list SECOND permit 2$
bgp as-path access-list SECOND permit 3$

clear bgp *

no bgp as-path access-list SECOND permit 3$
bgp as-path access-list SECOND permit 2$

clear bgp *

will induce some crashes

thus  we rework the links between aslists and aspath_exclude

Signed-off-by: Francois Dumontet <francois.dumontet@6wind.com>
2024-05-16 17:49:42 +02:00

778 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/* AS path filter list.
* Copyright (C) 1999 Kunihiro Ishiguro
*/
#include <zebra.h>
#include "command.h"
#include "log.h"
#include "memory.h"
#include "buffer.h"
#include "queue.h"
#include "filter.h"
#include "bgpd/bgpd.h"
#include "bgpd/bgp_aspath.h"
#include "bgpd/bgp_regex.h"
/* List of AS list. */
struct as_list_list {
struct as_list *head;
struct as_list *tail;
};
/* AS path filter master. */
struct as_list_master {
/* List of access_list which name is string. */
struct as_list_list str;
/* Hook function which is executed when new access_list is added. */
void (*add_hook)(char *);
/* Hook function which is executed when access_list is deleted. */
void (*delete_hook)(const char *);
};
/* Calculate new sequential number. */
static int64_t bgp_alist_new_seq_get(struct as_list *list)
{
int64_t maxseq;
int64_t newseq;
struct as_filter *entry;
maxseq = 0;
for (entry = list->head; entry; entry = entry->next) {
if (maxseq < entry->seq)
maxseq = entry->seq;
}
newseq = ((maxseq / 5) * 5) + 5;
return (newseq > UINT_MAX) ? UINT_MAX : newseq;
}
/* Return as-list entry which has same seq number. */
static struct as_filter *bgp_aslist_seq_check(struct as_list *list, int64_t seq)
{
struct as_filter *entry;
for (entry = list->head; entry; entry = entry->next)
if (entry->seq == seq)
return entry;
return NULL;
}
/* as-path access-list 10 permit AS1. */
static struct as_list_master as_list_master = {{NULL, NULL},
NULL,
NULL};
/* Allocate new AS filter. */
static struct as_filter *as_filter_new(void)
{
return XCALLOC(MTYPE_AS_FILTER, sizeof(struct as_filter));
}
/* Free allocated AS filter. */
static void as_filter_free(struct as_filter *asfilter)
{
if (asfilter->reg)
bgp_regex_free(asfilter->reg);
XFREE(MTYPE_AS_FILTER_STR, asfilter->reg_str);
XFREE(MTYPE_AS_FILTER, asfilter);
}
/* Make new AS filter. */
static struct as_filter *as_filter_make(regex_t *reg, const char *reg_str,
enum as_filter_type type)
{
struct as_filter *asfilter;
asfilter = as_filter_new();
asfilter->reg = reg;
asfilter->type = type;
asfilter->reg_str = XSTRDUP(MTYPE_AS_FILTER_STR, reg_str);
return asfilter;
}
static struct as_filter *as_filter_lookup(struct as_list *aslist,
const char *reg_str,
enum as_filter_type type)
{
struct as_filter *asfilter;
for (asfilter = aslist->head; asfilter; asfilter = asfilter->next)
if (strcmp(reg_str, asfilter->reg_str) == 0)
return asfilter;
return NULL;
}
static void as_filter_entry_replace(struct as_list *list,
struct as_filter *replace,
struct as_filter *entry)
{
if (replace->next) {
entry->next = replace->next;
replace->next->prev = entry;
} else {
entry->next = NULL;
list->tail = entry;
}
if (replace->prev) {
entry->prev = replace->prev;
replace->prev->next = entry;
} else {
entry->prev = NULL;
list->head = entry;
}
as_filter_free(replace);
}
static void as_list_filter_add(struct as_list *aslist,
struct as_filter *asfilter)
{
struct as_filter *point;
struct as_filter *replace;
if (aslist->tail && asfilter->seq > aslist->tail->seq)
point = NULL;
else {
replace = bgp_aslist_seq_check(aslist, asfilter->seq);
if (replace) {
as_filter_entry_replace(aslist, replace, asfilter);
goto hook;
}
/* Check insert point. */
for (point = aslist->head; point; point = point->next)
if (point->seq >= asfilter->seq)
break;
}
asfilter->next = point;
if (point) {
if (point->prev)
point->prev->next = asfilter;
else
aslist->head = asfilter;
asfilter->prev = point->prev;
point->prev = asfilter;
} else {
if (aslist->tail)
aslist->tail->next = asfilter;
else
aslist->head = asfilter;
asfilter->prev = aslist->tail;
aslist->tail = asfilter;
}
hook:
/* Run hook function. */
if (as_list_master.add_hook)
(*as_list_master.add_hook)(aslist->name);
}
/* Lookup as_list from list of as_list by name. */
struct as_list *as_list_lookup(const char *name)
{
struct as_list *aslist;
if (name == NULL)
return NULL;
for (aslist = as_list_master.str.head; aslist; aslist = aslist->next)
if (strcmp(aslist->name, name) == 0)
return aslist;
return NULL;
}
static struct as_list *as_list_new(void)
{
return XCALLOC(MTYPE_AS_LIST, sizeof(struct as_list));
}
static void as_list_free(struct as_list *aslist)
{
XFREE (MTYPE_AS_STR, aslist->name);
XFREE (MTYPE_AS_LIST, aslist);
}
/* Insert new AS list to list of as_list. Each as_list is sorted by
the name. */
static struct as_list *as_list_insert(const char *name)
{
struct as_list *aslist;
struct as_list *point;
struct as_list_list *list;
/* Allocate new access_list and copy given name. */
aslist = as_list_new();
aslist->name = XSTRDUP(MTYPE_AS_STR, name);
assert(aslist->name);
/* Set access_list to string list. */
list = &as_list_master.str;
/* Set point to insertion point. */
for (point = list->head; point; point = point->next)
if (strcmp(point->name, name) >= 0)
break;
/* In case of this is the first element of master. */
if (list->head == NULL) {
list->head = list->tail = aslist;
return aslist;
}
/* In case of insertion is made at the tail of access_list. */
if (point == NULL) {
aslist->prev = list->tail;
list->tail->next = aslist;
list->tail = aslist;
return aslist;
}
/* In case of insertion is made at the head of access_list. */
if (point == list->head) {
aslist->next = list->head;
list->head->prev = aslist;
list->head = aslist;
return aslist;
}
/* Insertion is made at middle of the access_list. */
aslist->next = point;
aslist->prev = point->prev;
if (point->prev)
point->prev->next = aslist;
point->prev = aslist;
return aslist;
}
static struct as_list *as_list_get(const char *name)
{
struct as_list *aslist;
aslist = as_list_lookup(name);
if (aslist == NULL)
aslist = as_list_insert(name);
return aslist;
}
static const char *filter_type_str(enum as_filter_type type)
{
switch (type) {
case AS_FILTER_PERMIT:
return "permit";
case AS_FILTER_DENY:
return "deny";
default:
return "";
}
}
static void as_list_delete(struct as_list *aslist)
{
struct as_list_list *list;
struct as_filter *filter, *next;
for (filter = aslist->head; filter; filter = next) {
next = filter->next;
as_filter_free(filter);
}
list = &as_list_master.str;
if (aslist->next)
aslist->next->prev = aslist->prev;
else
list->tail = aslist->prev;
if (aslist->prev)
aslist->prev->next = aslist->next;
else
list->head = aslist->next;
as_list_free(aslist);
}
static bool as_list_empty(struct as_list *aslist)
{
return aslist->head == NULL && aslist->tail == NULL;
}
static void as_list_filter_delete(struct as_list *aslist,
struct as_filter *asfilter)
{
char *name = XSTRDUP(MTYPE_AS_STR, aslist->name);
if (asfilter->next)
asfilter->next->prev = asfilter->prev;
else
aslist->tail = asfilter->prev;
if (asfilter->prev)
asfilter->prev->next = asfilter->next;
else
aslist->head = asfilter->next;
as_filter_free(asfilter);
/* If access_list becomes empty delete it from access_master. */
if (as_list_empty(aslist))
as_list_delete(aslist);
/* Run hook function. */
if (as_list_master.delete_hook)
(*as_list_master.delete_hook)(name);
XFREE(MTYPE_AS_STR, name);
}
static bool as_filter_match(struct as_filter *asfilter, struct aspath *aspath)
{
return bgp_regexec(asfilter->reg, aspath) != REG_NOMATCH;
}
/* Apply AS path filter to AS. */
enum as_filter_type as_list_apply(struct as_list *aslist, void *object)
{
struct as_filter *asfilter;
struct aspath *aspath;
aspath = (struct aspath *)object;
if (aslist == NULL)
return AS_FILTER_DENY;
for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) {
if (as_filter_match(asfilter, aspath))
return asfilter->type;
}
return AS_FILTER_DENY;
}
/* Add hook function. */
void as_list_add_hook(void (*func)(char *))
{
as_list_master.add_hook = func;
}
/* Delete hook function. */
void as_list_delete_hook(void (*func)(const char *))
{
as_list_master.delete_hook = func;
}
static bool as_list_dup_check(struct as_list *aslist, struct as_filter *new)
{
struct as_filter *asfilter;
for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) {
if (asfilter->type == new->type
&& strcmp(asfilter->reg_str, new->reg_str) == 0)
return true;
}
return false;
}
bool config_bgp_aspath_validate(const char *regstr)
{
char valid_chars[] = "1234567890_^|[,{}() ]$*+.?-\\";
if (strspn(regstr, valid_chars) == strlen(regstr))
return true;
return false;
}
DEFUN(as_path, bgp_as_path_cmd,
"bgp as-path access-list AS_PATH_FILTER_NAME [seq (0-4294967295)] <deny|permit> LINE...",
BGP_STR
"BGP autonomous system path filter\n"
"Specify an access list name\n"
"Regular expression access list name\n"
"Sequence number of an entry\n"
"Sequence number\n"
"Specify packets to reject\n"
"Specify packets to forward\n"
"A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n")
{
int idx = 0;
enum as_filter_type type;
struct as_filter *asfilter;
struct as_list *aslist;
struct aspath_exclude *ase;
regex_t *regex;
char *regstr;
int64_t seqnum = ASPATH_SEQ_NUMBER_AUTO;
/* Retrieve access list name */
argv_find(argv, argc, "AS_PATH_FILTER_NAME", &idx);
char *alname = argv[idx]->arg;
if (argv_find(argv, argc, "(0-4294967295)", &idx))
seqnum = (int64_t)atol(argv[idx]->arg);
/* Check the filter type. */
type = argv_find(argv, argc, "deny", &idx) ? AS_FILTER_DENY
: AS_FILTER_PERMIT;
/* Check AS path regex. */
argv_find(argv, argc, "LINE", &idx);
regstr = argv_concat(argv, argc, idx);
regex = bgp_regcomp(regstr);
if (!regex) {
vty_out(vty, "can't compile regexp %s\n", regstr);
XFREE(MTYPE_TMP, regstr);
return CMD_WARNING_CONFIG_FAILED;
}
if (!config_bgp_aspath_validate(regstr)) {
vty_out(vty, "Invalid character in as-path access-list %s\n",
regstr);
XFREE(MTYPE_TMP, regstr);
return CMD_WARNING_CONFIG_FAILED;
}
asfilter = as_filter_make(regex, regstr, type);
XFREE(MTYPE_TMP, regstr);
/* Install new filter to the access_list. */
aslist = as_list_get(alname);
if (seqnum == ASPATH_SEQ_NUMBER_AUTO)
seqnum = bgp_alist_new_seq_get(aslist);
asfilter->seq = seqnum;
/* Duplicate insertion check. */;
if (as_list_dup_check(aslist, asfilter))
as_filter_free(asfilter);
else
as_list_filter_add(aslist, asfilter);
/* init the exclude rule list*/
as_list_list_init(&aslist->exclude_rule);
/* get aspath orphan exclude that are using this acl */
ase = as_exclude_lookup_orphan(alname);
if (ase) {
as_list_list_add_head(&aslist->exclude_rule, ase);
/* set reverse pointer */
ase->exclude_aspath_acl = aslist;
/* set list of aspath excludes using that acl */
while ((ase = as_exclude_lookup_orphan(alname))) {
as_list_list_add_head(&aslist->exclude_rule, ase);
ase->exclude_aspath_acl = aslist;
}
}
return CMD_SUCCESS;
}
DEFUN(no_as_path, no_bgp_as_path_cmd,
"no bgp as-path access-list AS_PATH_FILTER_NAME [seq (0-4294967295)] <deny|permit> LINE...",
NO_STR
BGP_STR
"BGP autonomous system path filter\n"
"Specify an access list name\n"
"Regular expression access list name\n"
"Sequence number of an entry\n"
"Sequence number\n"
"Specify packets to reject\n"
"Specify packets to forward\n"
"A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n")
{
int idx = 0;
enum as_filter_type type;
struct as_filter *asfilter;
struct as_list *aslist;
struct aspath_exclude *ase;
char *regstr;
regex_t *regex;
char *aslistname =
argv_find(argv, argc, "AS_PATH_FILTER_NAME", &idx) ? argv[idx]->arg : NULL;
/* Lookup AS list from AS path list. */
aslist = as_list_lookup(aslistname);
if (aslist == NULL) {
vty_out(vty, "bgp as-path access-list %s doesn't exist\n",
aslistname);
return CMD_WARNING_CONFIG_FAILED;
}
/* Check the filter type. */
if (argv_find(argv, argc, "permit", &idx))
type = AS_FILTER_PERMIT;
else if (argv_find(argv, argc, "deny", &idx))
type = AS_FILTER_DENY;
else {
vty_out(vty, "filter type must be [permit|deny]\n");
return CMD_WARNING_CONFIG_FAILED;
}
/* Compile AS path. */
argv_find(argv, argc, "LINE", &idx);
regstr = argv_concat(argv, argc, idx);
if (!config_bgp_aspath_validate(regstr)) {
vty_out(vty, "Invalid character in as-path access-list %s\n",
regstr);
return CMD_WARNING_CONFIG_FAILED;
}
regex = bgp_regcomp(regstr);
if (!regex) {
vty_out(vty, "can't compile regexp %s\n", regstr);
XFREE(MTYPE_TMP, regstr);
return CMD_WARNING_CONFIG_FAILED;
}
/* Lookup asfilter. */
asfilter = as_filter_lookup(aslist, regstr, type);
bgp_regex_free(regex);
if (asfilter == NULL) {
vty_out(vty, "Regex entered %s does not exist\n", regstr);
XFREE(MTYPE_TMP, regstr);
return CMD_WARNING_CONFIG_FAILED;
}
XFREE(MTYPE_TMP, regstr);
/* put aspath exclude list into orphan */
if (as_list_list_count(&aslist->exclude_rule))
while ((ase = as_list_list_pop(&aslist->exclude_rule)))
as_exclude_set_orphan(ase);
as_list_list_fini(&aslist->exclude_rule);
as_list_filter_delete(aslist, asfilter);
return CMD_SUCCESS;
}
DEFUN (no_as_path_all,
no_bgp_as_path_all_cmd,
"no bgp as-path access-list AS_PATH_FILTER_NAME",
NO_STR
BGP_STR
"BGP autonomous system path filter\n"
"Specify an access list name\n"
"Regular expression access list name\n")
{
int idx_word = 4;
struct as_list *aslist;
aslist = as_list_lookup(argv[idx_word]->arg);
if (aslist == NULL) {
vty_out(vty, "bgp as-path access-list %s doesn't exist\n",
argv[idx_word]->arg);
return CMD_WARNING_CONFIG_FAILED;
}
as_list_delete(aslist);
/* Run hook function. */
if (as_list_master.delete_hook)
(*as_list_master.delete_hook)(argv[idx_word]->arg);
return CMD_SUCCESS;
}
static void as_list_show(struct vty *vty, struct as_list *aslist,
json_object *json)
{
struct as_filter *asfilter;
json_object *json_aslist = NULL;
if (json) {
json_aslist = json_object_new_array();
json_object_object_add(json, aslist->name, json_aslist);
} else
vty_out(vty, "AS path access list %s\n", aslist->name);
for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) {
if (json) {
json_object *json_asfilter = json_object_new_object();
json_object_int_add(json_asfilter, "sequenceNumber",
asfilter->seq);
json_object_string_add(json_asfilter, "type",
filter_type_str(asfilter->type));
json_object_string_add(json_asfilter, "regExp",
asfilter->reg_str);
json_object_array_add(json_aslist, json_asfilter);
} else
vty_out(vty, " %s %s\n",
filter_type_str(asfilter->type),
asfilter->reg_str);
}
}
static void as_list_show_all(struct vty *vty, json_object *json)
{
struct as_list *aslist;
for (aslist = as_list_master.str.head; aslist; aslist = aslist->next)
as_list_show(vty, aslist, json);
}
DEFUN (show_as_path_access_list,
show_bgp_as_path_access_list_cmd,
"show bgp as-path-access-list AS_PATH_FILTER_NAME [json]",
SHOW_STR
BGP_STR
"List AS path access lists\n"
"AS path access list name\n"
JSON_STR)
{
int idx_word = 3;
struct as_list *aslist;
bool uj = use_json(argc, argv);
json_object *json = NULL;
if (uj)
json = json_object_new_object();
aslist = as_list_lookup(argv[idx_word]->arg);
if (aslist)
as_list_show(vty, aslist, json);
if (uj)
vty_json(vty, json);
return CMD_SUCCESS;
}
ALIAS (show_as_path_access_list,
show_ip_as_path_access_list_cmd,
"show ip as-path-access-list AS_PATH_FILTER_NAME [json]",
SHOW_STR
IP_STR
"List AS path access lists\n"
"AS path access list name\n"
JSON_STR)
DEFUN (show_as_path_access_list_all,
show_bgp_as_path_access_list_all_cmd,
"show bgp as-path-access-list [json]",
SHOW_STR
BGP_STR
"List AS path access lists\n"
JSON_STR)
{
bool uj = use_json(argc, argv);
json_object *json = NULL;
if (uj)
json = json_object_new_object();
as_list_show_all(vty, json);
if (uj)
vty_json(vty, json);
return CMD_SUCCESS;
}
ALIAS (show_as_path_access_list_all,
show_ip_as_path_access_list_all_cmd,
"show ip as-path-access-list [json]",
SHOW_STR
IP_STR
"List AS path access lists\n"
JSON_STR)
static int config_write_as_list(struct vty *vty)
{
struct as_list *aslist;
struct as_filter *asfilter;
int write = 0;
for (aslist = as_list_master.str.head; aslist; aslist = aslist->next)
for (asfilter = aslist->head; asfilter;
asfilter = asfilter->next) {
vty_out(vty,
"bgp as-path access-list %s seq %" PRId64
" %s %s\n",
aslist->name, asfilter->seq,
filter_type_str(asfilter->type),
asfilter->reg_str);
write++;
}
return write;
}
static int config_write_as_list(struct vty *vty);
static struct cmd_node as_list_node = {
.name = "as list",
.node = AS_LIST_NODE,
.prompt = "",
.config_write = config_write_as_list,
};
static void bgp_aspath_filter_cmd_completion(vector comps,
struct cmd_token *token)
{
struct as_list *aslist;
for (aslist = as_list_master.str.head; aslist; aslist = aslist->next)
vector_set(comps, XSTRDUP(MTYPE_COMPLETION, aslist->name));
}
static const struct cmd_variable_handler aspath_filter_handlers[] = {
{.tokenname = "AS_PATH_FILTER_NAME",
.completions = bgp_aspath_filter_cmd_completion},
{.completions = NULL}};
/* Register functions. */
void bgp_filter_init(void)
{
install_node(&as_list_node);
install_element(CONFIG_NODE, &bgp_as_path_cmd);
install_element(CONFIG_NODE, &no_bgp_as_path_cmd);
install_element(CONFIG_NODE, &no_bgp_as_path_all_cmd);
install_element(VIEW_NODE, &show_bgp_as_path_access_list_cmd);
install_element(VIEW_NODE, &show_ip_as_path_access_list_cmd);
install_element(VIEW_NODE, &show_bgp_as_path_access_list_all_cmd);
install_element(VIEW_NODE, &show_ip_as_path_access_list_all_cmd);
cmd_variable_handler_register(aspath_filter_handlers);
}
void bgp_filter_reset(void)
{
struct as_list *aslist;
struct as_list *next;
for (aslist = as_list_master.str.head; aslist; aslist = next) {
next = aslist->next;
as_list_delete(aslist);
}
assert(as_list_master.str.head == NULL);
assert(as_list_master.str.tail == NULL);
}