You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
6.2 KiB
C

// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep_communities.c
*
* COMMUNITY matching expressions parsing.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgpgrep_local.h"
#include "sys/endian.h"
#include "sys/fs.h"
#include "sys/sys.h"
#include "lexer.h"
#include "numlib.h"
#include "strlib.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define FATALF(fmt, ...) ((void) ((lexp) ? \
LexerError(lexp, fmt, __VA_ARGS__) : \
Bgpgrep_Fatal("%s: " fmt, BgpgrepC_CurTerm(), __VA_ARGS__)))
typedef struct {
size_t cap, len;
Bgpmatchcomm c[FLEX_ARRAY];
} Matchcommlist;
#define MINLISTSIZ 256
#define LISTGROWSIZ 128
static Boolean ParseWellKnownCommunity(Bgpmatchcomm *dest, const char *s)
{
if (Df_stricmp(s, "PLANNED_SHUT") == 0)
dest->c.code = BGP_COMMUNITY_PLANNED_SHUT;
else if (Df_stricmp(s, "ACCEPT_OWN") == 0)
dest->c.code = BGP_COMMUNITY_ACCEPT_OWN;
else if (Df_stricmp(s, "ROUTE_FILTER_TRANSLATED_V4") == 0)
dest->c.code = BGP_COMMUNITY_ROUTE_FILTER_TRANSLATED_V4;
else if (Df_stricmp(s, "ROUTE_FILTER_V4") == 0)
dest->c.code = BGP_COMMUNITY_ROUTE_FILTER_V4;
else if (Df_stricmp(s, "ROUTE_FILTER_TRANSLATED_V6") == 0)
dest->c.code = BGP_COMMUNITY_ROUTE_FILTER_TRANSLATED_V6;
else if (Df_stricmp(s, "ROUTE_FILTER_V6") == 0)
dest->c.code = BGP_COMMUNITY_ROUTE_FILTER_V6;
else if (Df_stricmp(s, "LLGR_STALE") == 0)
dest->c.code = BGP_COMMUNITY_LLGR_STALE;
else if (Df_stricmp(s, "NO_LLGR") == 0)
dest->c.code = BGP_COMMUNITY_NO_LLGR;
else if (Df_stricmp(s, "ACCEPT_OWN_NEXTHOP") == 0)
dest->c.code = BGP_COMMUNITY_ACCEPT_OWN_NEXTHOP;
else if (Df_stricmp(s, "STANDBY_PE") == 0)
dest->c.code = BGP_COMMUNITY_STANDBY_PE;
else if (Df_stricmp(s, "BLACKHOLE") == 0)
dest->c.code = BGP_COMMUNITY_BLACKHOLE;
else if (Df_stricmp(s, "NO_EXPORT") == 0)
dest->c.code = BGP_COMMUNITY_NO_EXPORT;
else if (Df_stricmp(s, "NO_ADVERTISE") == 0)
dest->c.code = BGP_COMMUNITY_NO_ADVERTISE;
else if (Df_stricmp(s, "NO_EXPORT_SUBCONFED") == 0)
dest->c.code = BGP_COMMUNITY_NO_EXPORT_SUBCONFED;
else if (Df_stricmp(s, "NO_PEER") == 0)
dest->c.code = BGP_COMMUNITY_NO_PEER;
else return FALSE;
return TRUE;
}
static Bgpmatchcomm *AppendMatch(Matchcommlist **dest)
{
Matchcommlist *list = *dest;
if (list->len == list->cap) {
list->cap += LISTGROWSIZ;
list = (Matchcommlist *) realloc(list, offsetof(Matchcommlist, c[list->cap]));
if (!list)
Sys_OutOfMemory();
*dest = list;
}
return &list->c[list->len++];
}
static void ParseCommunity(Matchcommlist **dest, const char *s, Lex *lexp)
{
char *p = (char *) s;
unsigned long long n;
NumConvRet res;
Bgpmatchcomm *m = AppendMatch(dest);
memset(m, 0, sizeof(*m));
if (ParseWellKnownCommunity(m, s))
return; // got a well known community
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
// Hexadecimal representation
n = Atoull(p, &p, 16, &res);
if (res != NCVENOERR || n > 0xffffffffuLL || *p != '\0')
FATALF("%s: Hexadecimal COMMUNITY code is out of range", s);
m->c.code = beswap32(n);
return;
}
// HIVALUE:LOVALUE (either one may be *)
if (*p == '*') {
m->maskHi = TRUE;
p++;
} else {
n = Atoull(p, &p, 10, &res);
if (res != NCVENOERR || n > 0xffffuLL)
FATALF("%s: Expected numeric value or '*' for COMMUNITY high order bytes", s);
m->c.hi = beswap16(n);
}
if (*p != ':')
FATALF("%s: Expecting ':' after COMMUNITY high order bytes", s);
p++;
if (*p == '*') {
m->maskLo = TRUE;
p++;
} else {
n = Atoull(p, &p, 10, &res);
if (res != NCVENOERR || n > 0xffffuLL)
FATALF("%s: Expected numeric value or '*' for COMMUNITY low order bytes", s);
m->c.lo = beswap16(n);
}
if (*p != '\0')
FATALF("%s: Bad COMMUNITY, should be an hexadecimal code or 'hivalue:lowvalue'", s);
if (m->maskHi && m->maskLo)
FATALF("%s: At least one between high order or low order bytes must be defined", s);
}
static Judgement ParseCommunityFile(Matchcommlist **dest, const char *filename)
{
Lex lex;
Tok tok;
// Map file in memory
Fildes fd = Sys_Fopen(filename, FM_READ, FH_SEQ);
if (fd == FILDES_BAD)
return NG;
Sint64 fsiz = Sys_FileSize(fd); // NOTE: aborts on failure
assert(fsiz >= 0);
char *text = (char *) malloc(fsiz);
if (!text)
Sys_OutOfMemory();
Sint64 n = Sys_Fread(fd, text, fsiz);
if (n != fsiz)
Bgpgrep_Fatal("Short read from file '%s'", filename);
Sys_Fclose(fd);
// Setup Lex and start reading strings
memset(&lex, 0, sizeof(lex));
BeginLexerSession(&lex, filename, /*line=*/1);
SetLexerTextn(&lex, text, fsiz);
if (!S.noColor)
SetLexerFlags(&lex, L_COLORED);
SetLexerErrorFunc(&lex, LEX_QUIT, LEX_WARN, NULL);
char buf[MAXTOKLEN + 1 + MAXTOKLEN + 1];
while (Lex_ReadToken(&lex, &tok)) {
strcpy(buf, tok.text);
Lex_MatchTokenType(&lex, &tok, TT_PUNCT, P_COLON);
strcat(buf, tok.text);
Lex_MatchAnyToken(&lex, &tok);
strcat(buf, tok.text);
ParseCommunity(dest, buf, &lex);
}
free(text);
return OK;
}
Sint32 BgpgrepC_ParseCommunity(BgpVmOpt opt)
{
const char *tok = BgpgrepC_ExpectAnyToken();
if (strcmp(tok, "()") == 0)
return -1; // empty list always fails
Matchcommlist *list = (Matchcommlist *) malloc(offsetof(Matchcommlist,
c[MINLISTSIZ]));
if (!list)
Sys_OutOfMemory();
list->cap = MINLISTSIZ;
list->len = 0;
if (strcmp(tok, "(") == 0) {
// Explicit inline prefix list, directly inside command line
while (TRUE) {
tok = BgpgrepC_ExpectAnyToken();
if (strcmp(tok, ")") == 0)
break;
ParseCommunity(&list, tok, /*lexp=*/NULL);
}
} else {
// Anything else is interpreted as either:
// - A file argument, containing a space-separated COMMUNITY match list
// - A single match, if the file is not found
// (shorthand for "( <community> )" )
if (ParseCommunityFile(&list, tok) != OK)
ParseCommunity(&list, tok, /*lexp=*/NULL);
}
void *match = Bgp_VmCompileCommunityMatch(&S.vm, list->c, list->len, opt);
if (!match)
Bgpgrep_Fatal("%s: %s", BgpgrepC_CurTerm(), Bgp_ErrorString(S.vm.errCode));
free(list);
Sint32 kidx = BGP_VMSETKA(&S.vm, Bgp_VmNewk(&S.vm), match);
if (kidx == -1)
Bgpgrep_Fatal("Too many community match expressions");
return kidx;
}