// 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 #include #include #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 "( )" ) 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; }