mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-08-07 22:29:23 +00:00
bgpd: separate lcommunity validation from tokenizer
`lcommunity_gettoken` expects a space-delimeted list of 0 or more large communities. `lcommunity_list_valid` can perform this check. `lcommunity_list_valid` now validates large community lists more accurately based on the following condition: Each quantity in a standard bgp large community must: 1. Contain at least one digit 2. Fit within 4 octets 3. Contain only digits unless the lcommunity is "expanded" 4. Contain a valid regex if the lcommunity is "expanded" Moreover we validate that each large community list contains exactly 3 such values separated by a single colon each. One quirk of our validation which is worth documenting is: ``` bgp large-community-list standard test2 permit 1:c:3 bgp large-community-list expanded test1 permit 1:c:3 ``` The first line will throw an error complaining about a "malformed community-list value". The second line will be accepted because the each value is each treated as a regex when matching large communities, it simply will never match anything so it's rather useless. Signed-off-by: Wesley Coakley <wcoakley@nvidia.com>
This commit is contained in:
parent
a7f52fb0f7
commit
c850908b9d
@ -1110,11 +1110,14 @@ struct lcommunity *lcommunity_list_match_delete(struct lcommunity *lcom,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Helper to check if every octet do not exceed UINT_MAX */
|
/* Helper to check if every octet do not exceed UINT_MAX */
|
||||||
static bool lcommunity_list_valid(const char *community)
|
bool lcommunity_list_valid(const char *community, int style)
|
||||||
{
|
{
|
||||||
int octets;
|
int octets;
|
||||||
char **splits, **communities;
|
char **splits, **communities;
|
||||||
|
char *endptr;
|
||||||
int num, num_communities;
|
int num, num_communities;
|
||||||
|
regex_t *regres;
|
||||||
|
int invalid = 0;
|
||||||
|
|
||||||
frrstr_split(community, " ", &communities, &num_communities);
|
frrstr_split(community, " ", &communities, &num_communities);
|
||||||
|
|
||||||
@ -1123,25 +1126,43 @@ static bool lcommunity_list_valid(const char *community)
|
|||||||
frrstr_split(communities[j], ":", &splits, &num);
|
frrstr_split(communities[j], ":", &splits, &num);
|
||||||
|
|
||||||
for (int i = 0; i < num; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
if (strtoul(splits[i], NULL, 10) > UINT_MAX)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (strlen(splits[i]) == 0)
|
if (strlen(splits[i]) == 0)
|
||||||
return false;
|
/* There is no digit to check */
|
||||||
|
invalid++;
|
||||||
|
|
||||||
|
if (style == LARGE_COMMUNITY_LIST_STANDARD) {
|
||||||
|
if (*splits[i] == '-')
|
||||||
|
/* Must not be negative */
|
||||||
|
invalid++;
|
||||||
|
else if (strtoul(splits[i], &endptr, 10)
|
||||||
|
> UINT_MAX)
|
||||||
|
/* Larger than 4 octets */
|
||||||
|
invalid++;
|
||||||
|
else if (*endptr)
|
||||||
|
/* Not all characters were digits */
|
||||||
|
invalid++;
|
||||||
|
} else {
|
||||||
|
regres = bgp_regcomp(communities[j]);
|
||||||
|
if (!regres)
|
||||||
|
/* malformed regex */
|
||||||
|
invalid++;
|
||||||
|
else
|
||||||
|
bgp_regex_free(regres);
|
||||||
|
}
|
||||||
|
|
||||||
octets++;
|
octets++;
|
||||||
XFREE(MTYPE_TMP, splits[i]);
|
XFREE(MTYPE_TMP, splits[i]);
|
||||||
}
|
}
|
||||||
XFREE(MTYPE_TMP, splits);
|
XFREE(MTYPE_TMP, splits);
|
||||||
|
|
||||||
if (octets < 3)
|
if (octets != 3)
|
||||||
return false;
|
invalid++;
|
||||||
|
|
||||||
XFREE(MTYPE_TMP, communities[j]);
|
XFREE(MTYPE_TMP, communities[j]);
|
||||||
}
|
}
|
||||||
XFREE(MTYPE_TMP, communities);
|
XFREE(MTYPE_TMP, communities);
|
||||||
|
|
||||||
return true;
|
return (invalid > 0) ? false : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set lcommunity-list. */
|
/* Set lcommunity-list. */
|
||||||
@ -1176,7 +1197,7 @@ int lcommunity_list_set(struct community_list_handler *ch, const char *name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (str) {
|
if (str) {
|
||||||
if (!lcommunity_list_valid(str))
|
if (!lcommunity_list_valid(str, style))
|
||||||
return COMMUNITY_LIST_ERR_MALFORMED_VAL;
|
return COMMUNITY_LIST_ERR_MALFORMED_VAL;
|
||||||
|
|
||||||
if (style == LARGE_COMMUNITY_LIST_STANDARD)
|
if (style == LARGE_COMMUNITY_LIST_STANDARD)
|
||||||
|
@ -154,6 +154,7 @@ extern int extcommunity_list_unset(struct community_list_handler *ch,
|
|||||||
extern int lcommunity_list_set(struct community_list_handler *ch,
|
extern int lcommunity_list_set(struct community_list_handler *ch,
|
||||||
const char *name, const char *str,
|
const char *name, const char *str,
|
||||||
const char *seq, int direct, int style);
|
const char *seq, int direct, int style);
|
||||||
|
extern bool lcommunity_list_valid(const char *community, int style);
|
||||||
extern int lcommunity_list_unset(struct community_list_handler *ch,
|
extern int lcommunity_list_unset(struct community_list_handler *ch,
|
||||||
const char *name, const char *str,
|
const char *name, const char *str,
|
||||||
const char *seq, int direct, int style);
|
const char *seq, int direct, int style);
|
||||||
|
@ -348,16 +348,12 @@ void lcommunity_finish(void)
|
|||||||
lcomhash = NULL;
|
lcomhash = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Large Communities token enum. */
|
/* Get next Large Communities token from the string.
|
||||||
enum lcommunity_token {
|
* Assumes str is space-delimeted and describes 0 or more
|
||||||
lcommunity_token_unknown = 0,
|
* valid large communities
|
||||||
lcommunity_token_val,
|
*/
|
||||||
};
|
|
||||||
|
|
||||||
/* Get next Large Communities token from the string. */
|
|
||||||
static const char *lcommunity_gettoken(const char *str,
|
static const char *lcommunity_gettoken(const char *str,
|
||||||
struct lcommunity_val *lval,
|
struct lcommunity_val *lval)
|
||||||
enum lcommunity_token *token)
|
|
||||||
{
|
{
|
||||||
const char *p = str;
|
const char *p = str;
|
||||||
|
|
||||||
@ -372,19 +368,17 @@ static const char *lcommunity_gettoken(const char *str,
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Community value. */
|
/* Community value. */
|
||||||
if (isdigit((unsigned char)*p)) {
|
|
||||||
int separator = 0;
|
int separator = 0;
|
||||||
int digit = 0;
|
int digit = 0;
|
||||||
uint32_t globaladmin = 0;
|
uint32_t globaladmin = 0;
|
||||||
uint32_t localdata1 = 0;
|
uint32_t localdata1 = 0;
|
||||||
uint32_t localdata2 = 0;
|
uint32_t localdata2 = 0;
|
||||||
|
|
||||||
while (isdigit((unsigned char)*p) || *p == ':') {
|
while (*p && *p != ' ') {
|
||||||
|
/* large community valid chars */
|
||||||
|
assert(isdigit((unsigned char)*p) || *p == ':');
|
||||||
|
|
||||||
if (*p == ':') {
|
if (*p == ':') {
|
||||||
if (separator == 2) {
|
|
||||||
*token = lcommunity_token_unknown;
|
|
||||||
return NULL;
|
|
||||||
} else {
|
|
||||||
separator++;
|
separator++;
|
||||||
digit = 0;
|
digit = 0;
|
||||||
if (separator == 1) {
|
if (separator == 1) {
|
||||||
@ -393,18 +387,19 @@ static const char *lcommunity_gettoken(const char *str,
|
|||||||
localdata1 = localdata2;
|
localdata1 = localdata2;
|
||||||
}
|
}
|
||||||
localdata2 = 0;
|
localdata2 = 0;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
digit = 1;
|
digit = 1;
|
||||||
|
/* left shift the accumulated value and add current
|
||||||
|
* digit
|
||||||
|
*/
|
||||||
localdata2 *= 10;
|
localdata2 *= 10;
|
||||||
localdata2 += (*p - '0');
|
localdata2 += (*p - '0');
|
||||||
}
|
}
|
||||||
p++;
|
p++;
|
||||||
}
|
}
|
||||||
if (!digit) {
|
|
||||||
*token = lcommunity_token_unknown;
|
/* Assert str was a valid large community */
|
||||||
return NULL;
|
assert(separator == 2 && digit == 1);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copy the large comm.
|
* Copy the large comm.
|
||||||
@ -422,10 +417,6 @@ static const char *lcommunity_gettoken(const char *str,
|
|||||||
lval->val[10] = (localdata2 >> 8) & 0xff;
|
lval->val[10] = (localdata2 >> 8) & 0xff;
|
||||||
lval->val[11] = localdata2 & 0xff;
|
lval->val[11] = localdata2 & 0xff;
|
||||||
|
|
||||||
*token = lcommunity_token_val;
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
*token = lcommunity_token_unknown;
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,23 +430,16 @@ static const char *lcommunity_gettoken(const char *str,
|
|||||||
struct lcommunity *lcommunity_str2com(const char *str)
|
struct lcommunity *lcommunity_str2com(const char *str)
|
||||||
{
|
{
|
||||||
struct lcommunity *lcom = NULL;
|
struct lcommunity *lcom = NULL;
|
||||||
enum lcommunity_token token = lcommunity_token_unknown;
|
|
||||||
struct lcommunity_val lval;
|
struct lcommunity_val lval;
|
||||||
|
|
||||||
|
if (!lcommunity_list_valid(str, LARGE_COMMUNITY_LIST_STANDARD))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
str = lcommunity_gettoken(str, &lval, &token);
|
str = lcommunity_gettoken(str, &lval);
|
||||||
switch (token) {
|
|
||||||
case lcommunity_token_val:
|
|
||||||
if (lcom == NULL)
|
if (lcom == NULL)
|
||||||
lcom = lcommunity_new();
|
lcom = lcommunity_new();
|
||||||
lcommunity_add_val(lcom, &lval);
|
lcommunity_add_val(lcom, &lval);
|
||||||
break;
|
|
||||||
case lcommunity_token_unknown:
|
|
||||||
default:
|
|
||||||
if (lcom)
|
|
||||||
lcommunity_free(&lcom);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
} while (str);
|
} while (str);
|
||||||
|
|
||||||
return lcom;
|
return lcom;
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include "lib/json.h"
|
#include "lib/json.h"
|
||||||
#include "bgpd/bgp_route.h"
|
#include "bgpd/bgp_route.h"
|
||||||
|
#include "bgpd/bgp_clist.h"
|
||||||
|
|
||||||
/* Large Communities value is twelve octets long. */
|
/* Large Communities value is twelve octets long. */
|
||||||
#define LCOMMUNITY_SIZE 12
|
#define LCOMMUNITY_SIZE 12
|
||||||
|
@ -1174,6 +1174,39 @@ def test_large_community_boundary_values(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_large_community_invalid_chars(request):
|
||||||
|
"""
|
||||||
|
BGP canonical lcommunities must only be digits
|
||||||
|
"""
|
||||||
|
tc_name = request.node.name
|
||||||
|
write_test_header(tc_name)
|
||||||
|
tgen = get_topogen()
|
||||||
|
|
||||||
|
# Don"t run this test if we have any failure.
|
||||||
|
if tgen.routers_have_failure():
|
||||||
|
pytest.skip(tgen.errors)
|
||||||
|
|
||||||
|
input_dict = {
|
||||||
|
"r4": {
|
||||||
|
"bgp_community_lists": [
|
||||||
|
{
|
||||||
|
"community_type": "standard",
|
||||||
|
"action": "permit",
|
||||||
|
"name": "ANY",
|
||||||
|
"value": "1:a:2",
|
||||||
|
"large": True,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step("Checking boundary value for community 1:a:2")
|
||||||
|
result = create_bgp_community_lists(tgen, input_dict)
|
||||||
|
assert result is not True, "Test case {} : Failed \n Error: {}".format(
|
||||||
|
tc_name, result
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_large_community_after_clear_bgp(request):
|
def test_large_community_after_clear_bgp(request):
|
||||||
"""
|
"""
|
||||||
Clear BGP neighbor-ship and check if large community and community
|
Clear BGP neighbor-ship and check if large community and community
|
||||||
|
Loading…
Reference in New Issue
Block a user