ubgpsuite/lonetix/bgp/vm.c

789 lines
17 KiB
C

// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm.c
*
* BGP VM initialization and execution loop.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "bgp/patricia.h"
#include "bgp/vmintrin.h"
#include "sys/endian.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#if defined(__GNUC__) && !defined(DF_BGP_VM_NO_COMPUTED_GOTO)
#define DF_BGP_VM_USES_COMPUTED_GOTO
#include "bgp/vm_gccdef.h"
#else
#include "bgp/vm_cdef.h"
#endif
#define BGP_VM_MINHEAPSIZ (4 * 1024)
#define BGP_VM_STKSIZ (4 * 1024)
#define BGP_VM_GROWPROGN 128
/* During VM execution instructions update the current BGP message
* match. But sometimes the match is irrelevant (think about something
* like:
*
* ```
* LOADU 1
* NOT
* CPASS
* ```
*
* This bytecode doesn't examine any actual BGP message segment,
* to simplify instructions, whenever an irrelevant match is being produced,
* the following static variable is referenced by `vm->curMatch`,
* to provide a dummy match that can be updated at will
* by any VM and is always discarded by `Bgp_VmStoreMatch()`.
*/
static Bgpvmmatch discardMatch;
// Used for empty programs
static const Bgpvmbytec emptyProg = BGP_VMOP_END;
Judgement Bgp_InitVm(Bgpvm *vm, size_t heapSiz)
{
size_t siz = BGP_VM_STKSIZ + MAX(heapSiz, BGP_VM_MINHEAPSIZ);
siz = ALIGN(siz, ALIGNMENT);
assert(siz <= 0xffffffffuLL);
void *heap = malloc(siz);
if (!heap)
return Bgp_SetErrStat(BGPENOMEM);
memset(vm, 0, sizeof(*vm));
vm->heap = heap;
vm->hMemSiz = siz;
vm->hHighMark = siz;
vm->prog = (Bgpvmbytec *) &emptyProg;
return Bgp_SetErrStat(BGPENOERR);
}
Judgement Bgp_VmEmit(Bgpvm *vm, Bgpvmbytec bytec)
{
BGP_VMCLRERR(vm);
if (BGP_VMOPC(bytec) == BGP_VMOP_END)
return Bgp_SetErrStat(BGPENOERR); // ignore useless emit
if (vm->progLen + 1 >= vm->progCap) {
// Grow the VM program segment
Bgpvmbytec *oldProg = vm->prog;
if (vm->prog == &emptyProg)
oldProg = NULL;
size_t newSiz = vm->progCap + BGP_VM_GROWPROGN;
Bgpvmbytec *newProg = (Bgpvmbytec *) realloc(oldProg, newSiz * sizeof(*newProg));
if (!newProg) {
// Flag the VM as bad
vm->setupFailed = TRUE;
vm->errCode = BGPENOMEM;
return Bgp_SetErrStat(BGPENOMEM);
}
vm->prog = newProg;
vm->progCap = newSiz;
}
// Append instruction and follow it with BGP_VMOP_END
vm->prog[vm->progLen++] = bytec;
vm->prog[vm->progLen] = BGP_VMOP_END;
return Bgp_SetErrStat(BGPENOERR);
}
void *Bgp_VmPermAlloc(Bgpvm *vm, size_t size)
{
BGP_VMCLRERR(vm);
size = ALIGN(size, ALIGNMENT);
if (vm->hLowMark + size > vm->hMemSiz) {
// Flag the VM as bad
vm->setupFailed = TRUE;
vm->errCode = BGPEVMOOM;
Bgp_SetErrStat(BGPEVMOOM);
return NULL;
}
void *ptr = (Uint8 *) vm->heap + vm->hLowMark;
vm->hLowMark += size;
Bgp_SetErrStat(BGPENOERR);
return ptr;
}
void *Bgp_VmTempAlloc(Bgpvm *vm, size_t size)
{
size = ALIGN(size, ALIGNMENT);
size_t stksiz = vm->si * sizeof(Bgpvmval);
if (vm->hLowMark + stksiz + size > vm->hHighMark) UNLIKELY {
// NOTE: VM is being executed, don't set BGP error state
// it will be updated by Bgp_VmExec() as needed
vm->errCode = BGPEVMOOM;
return NULL;
}
assert(vm->hHighMark >= size);
vm->hHighMark -= size;
return (Uint8 *) vm->heap + vm->hHighMark;
}
void Bgp_VmTempFree(Bgpvm *vm, size_t size)
{
size = ALIGN(size, ALIGNMENT);
assert(size + vm->hHighMark <= vm->hMemSiz);
vm->hHighMark += size;
}
Boolean Bgp_VmExec(Bgpvm *vm, Bgpmsg *msg)
{
// Fundamental sanity checks
if (vm->setupFailed) UNLIKELY {
vm->errCode = BGPEBADVM;
goto cant_run;
}
if (!vm->prog) UNLIKELY {
vm->errCode = BGPEVMNOPROG;
goto cant_run;
}
// Setup initial VM state
Boolean result = TRUE; // assume pass unless found otherwise
vm->pc = 0;
vm->si = 0;
vm->nblk = 0;
vm->nmatches = 0;
vm->hHighMark = vm->hMemSiz;
vm->msg = msg;
vm->curMatch = &discardMatch;
vm->matches = NULL;
BGP_VMCLRERR(vm);
// Populate computed goto table if necessary
#ifdef DF_BGP_VM_USES_COMPUTED_GOTO
#include "bgp/vm_optab.h"
#endif
// Execute bytecode according to the #included vm_<impl>def.h
Bgpvmbytec ir; // Instruction Register
while (TRUE) {
// FETCH stage
FETCH(ir, vm);
// DECODE-DISPATCH stage
DISPATCH(BGP_VMOPC(ir)) {
// EXECUTE stage
EXECUTE(NOP): UNLIKELY;
break;
EXECUTE(LOAD):
Bgp_VmDoLoad(vm, (Sint8) BGP_VMOPARG(ir));
break;
EXECUTE(LOADU):
Bgp_VmDoLoadu(vm, BGP_VMOPARG(ir));
break;
EXECUTE(LOADN):
Bgp_VmDoLoadn(vm);
break;
EXECUTE(LOADK):
Bgp_VmDoLoadk(vm, BGP_VMOPARG(ir));
break;
EXECUTE(CALL):
Bgp_VmDoCall(vm, BGP_VMOPARG(ir));
break;
EXECUTE(BLK):
vm->nblk++;
break;
EXECUTE(ENDBLK):
if (vm->nblk == 0) UNLIKELY {
vm->errCode = BGPEVMBADENDBLK;
goto terminate;
}
vm->nblk--;
break;
EXECUTE(TAG):
Bgp_VmDoTag(vm, BGP_VMOPARG(ir));
break;
EXECUTE(NOT):
Bgp_VmDoNot(vm);
break;
EXECUTE(CFAIL):
if (!Bgp_VmDoBreakPoint(vm, /*breakIf=*/TRUE, /*onBreak=*/FALSE)) {
result = FALSE; // immediate failure
goto terminate;
}
break;
EXECUTE(CPASS):
if (!Bgp_VmDoBreakPoint(vm, /*breakIf=*/TRUE, /*onBreak=*/TRUE))
goto terminate; // immediate pass
break;
EXECUTE(ORFAIL):
if (!Bgp_VmDoBreakPoint(vm, /*breakIf=*/FALSE, /*onBreak=*/FALSE)) {
result = FALSE; // immediate failure
goto terminate;
}
break;
EXECUTE(ORPASS):
if (!Bgp_VmDoBreakPoint(vm, /*breakIf=*/FALSE, /*onBreak=*/TRUE))
goto terminate; // immediate pass
break;
EXECUTE(JZ):
if (!BGP_VMCHKSTKSIZ(vm, 1)) UNLIKELY
break;
if (!BGP_VMPEEK(vm, -1)) {
// Zero, do jump
vm->pc += BGP_VMOPARG(ir);
if (vm->pc > vm->progLen) UNLIKELY
vm->errCode = BGPEVMBADJMP; // jump target out of bounds
} else {
BGP_VMPOP(vm); // no jump, pop the stack and move on
}
break;
EXECUTE(JNZ):
if (!BGP_VMCHKSTKSIZ(vm, 1)) UNLIKELY
break;
if (BGP_VMPEEK(vm, -1)) {
// Non-Zero, do jump
vm->pc += BGP_VMOPARG(ir);
if (vm->pc > vm->progLen) UNLIKELY
vm->errCode = BGPEVMBADJMP; // jump target out of bounds
} else {
BGP_VMPOP(vm); // no jump, pop the stack and move on
}
break;
EXECUTE(CHKT):
Bgp_VmDoChkt(vm, (BgpType) BGP_VMOPARG(ir));
break;
EXECUTE(CHKA):
Bgp_VmDoChka(vm, (BgpAttrCode) BGP_VMOPARG(ir));
break;
EXECUTE(EXCT):
Bgp_VmDoExct(vm, BGP_VMOPARG(ir));
break;
EXECUTE(SUPN):
Bgp_VmDoSupn(vm, BGP_VMOPARG(ir));
break;
EXECUTE(SUBN):
Bgp_VmDoSubn(vm, BGP_VMOPARG(ir));
break;
EXECUTE(RELT):
Bgp_VmDoRelt(vm, BGP_VMOPARG(ir));
break;
EXECUTE(ASMTCH):
Bgp_VmDoAsmtch(vm);
break;
EXECUTE(COMTCH):
Bgp_VmDoComtch(vm);
break;
EXECUTE(ACOMTC):
Bgp_VmDoAcomtc(vm);
break;
EXECUTE(END): UNLIKELY;
// Implicitly PASS current match
vm->curMatch->isPassing = TRUE;
goto terminate;
EXECUTE_SIGILL: UNLIKELY;
vm->errCode = BGPEVMILL;
break;
}
if (vm->errCode) UNLIKELY
goto terminate; // error encountered, abort execution
}
terminate:
if (Bgp_SetErrStat(vm->errCode) != OK) UNLIKELY
result = FALSE;
return result;
cant_run:
Bgp_SetErrStat(vm->errCode);
return FALSE;
}
Judgement Bgp_VmStoreMsgTypeMatch(Bgpvm *vm, Boolean isMatching)
{
BGP_VMPUSH(vm, isMatching);
vm->curMatch = (Bgpvmmatch *) Bgp_VmTempAlloc(vm, sizeof(*vm->curMatch));
if (!vm->curMatch) UNLIKELY
return NG;
Bgphdr *hdr = BGP_HDR(vm->msg);
vm->curMatch->pc = BGP_VMCURPC(vm);
vm->curMatch->tag = 0;
vm->curMatch->base = (Uint8 *) hdr;
vm->curMatch->lim = (Uint8 *) (hdr + 1);
vm->curMatch->pos = &hdr->type;
vm->curMatch->isMatching = isMatching;
Bgp_VmStoreMatch(vm);
return OK;
}
void Bgp_VmStoreMatch(Bgpvm *vm)
{
if (vm->curMatch == &discardMatch)
return; // discard store request
// Prepend match to matches list, still keep the `curMatch` pointer
// around in case result is updated by following instructions (e.g. NOT)
vm->curMatch->nextMatch = vm->matches;
vm->matches = vm->curMatch;
vm->nmatches++;
}
Boolean Bgp_VmDoBreakPoint(Bgpvm *vm,
Boolean breakIf,
Boolean onBreak)
{
/* POPS:
* -1: Last operation Boolean result (as a Sint64, 0 for FALSE)
*
* PUSHES:
* * On break result:
* - `onBreak`
* * Otherwise:
* - Nothing.
*
* SIDE-EFFECTS:
* - Breaks current BLK if condition is met
* - Updates `curMatch->isPassing` flag accordingly
*/
if (!BGP_VMCHKSTKSIZ(vm, 1)) UNLIKELY
return TRUE; // error, let vm->errCode handle this
Boolean res = TRUE; // unless we're out of blocks
Bgpvmval *v = BGP_VMSTKGET(vm, -1);
if (!!(v->val) == breakIf) {
// Push `onBreak` and break current BLK
vm->curMatch->isPassing = onBreak;
v->val = onBreak;
if (vm->nblk > 0)
Bgp_VmDoBreak(vm);
else
res = FALSE; // no more BLK
} else {
// Pop the stack and move on, no break
vm->curMatch->isPassing = !onBreak;
BGP_VMPOP(vm);
}
// Current match information has been collected,
// discard anything up to the next relevant operation
vm->curMatch = &discardMatch;
return res;
}
void Bgp_VmDoChkt(Bgpvm *vm, BgpType type)
{
/* PUSHES:
* TRUE if type matches, FALSE otherwise
*/
if (!BGP_VMCHKSTK(vm, 1)) UNLIKELY
return;
Boolean isMatching = (BGP_VMCHKMSGTYPE(vm, type) != NULL);
Bgp_VmStoreMsgTypeMatch(vm, isMatching);
}
void Bgp_VmDoChka(Bgpvm *vm, BgpAttrCode code)
{
/* PUSHES:
* TRUE if attribute exists inside UPDATE message, FALSE otherwise
*/
if (!BGP_VMCHKSTK(vm, 1)) UNLIKELY
return;
Bgpupdate *update = (Bgpupdate *) BGP_VMCHKMSGTYPE(vm, BGP_UPDATE);
if (!update) {
Bgp_VmStoreMsgTypeMatch(vm, /*isMatching=*/FALSE);
return;
}
// Attribute lookup
Bgpattrseg *tpa = Bgp_GetUpdateAttributes(update);
if (!tpa) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgpattr *attr = Bgp_GetUpdateAttribute(tpa, code, vm->msg->table);
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Boolean isMatching = (attr != NULL);
BGP_VMPUSH(vm, isMatching);
// Create a new match
vm->curMatch = (Bgpvmmatch *) Bgp_VmTempAlloc(vm, sizeof(*vm->curMatch));
if (!vm->curMatch) UNLIKELY
return;
vm->curMatch->pc = BGP_VMCURPC(vm);
vm->curMatch->tag = 0;
vm->curMatch->base = tpa->attrs;
vm->curMatch->lim = &tpa->attrs[beswap16(tpa->len)];
vm->curMatch->pos = attr;
vm->curMatch->isMatching = isMatching;
Bgp_VmStoreMatch(vm);
}
static Judgement Bgp_VmStartNets(Bgpvm *vm, Bgpmpiter *it, Uint8 mode)
{
if (!BGP_VMCHKMSGTYPE(vm, BGP_UPDATE)) {
Bgp_VmStoreMsgTypeMatch(vm, /*isMatching=*/FALSE);
return NG;
}
switch (mode) {
case BGP_VMOPA_NLRI:
Bgp_StartMsgNlri(&it->rng, vm->msg);
it->nextAttr = NULL;
break;
case BGP_VMOPA_WITHDRAWN:
Bgp_StartMsgWithdrawn(&it->rng, vm->msg);
it->nextAttr = NULL;
break;
case BGP_VMOPA_ALL_NLRI:
Bgp_StartAllMsgNlri(it, vm->msg);
break;
case BGP_VMOPA_ALL_WITHDRAWN:
Bgp_StartAllMsgWithdrawn(it, vm->msg);
break;
default: UNLIKELY;
vm->errCode = BGPEVMBADOP;
return NG;
}
if (Bgp_GetErrStat(NULL)) UNLIKELY {
vm->errCode = BGPEVMMSGERR;
return NG;
}
return OK;
}
static void Bgp_VmCollectNetMatch(Bgpvm *vm, Bgpmpiter *it)
{
// Push on stack first --
// we know we have at least one available spot on the stack for
// any network operation (we POP at least one Patricia address from it).
// Perform the temporary allocation afterwards.
// This saves one check on stack space
BGP_VMPUSH(vm, TRUE);
vm->curMatch = (Bgpvmmatch *) Bgp_VmTempAlloc(vm, sizeof(*vm->curMatch));
if (!vm->curMatch) UNLIKELY
return;
vm->curMatch->pc = BGP_VMCURPC(vm);
vm->curMatch->tag = 0;
vm->curMatch->base = BGP_CURMPBASE(it);
vm->curMatch->lim = BGP_CURMPLIM(it);
vm->curMatch->pos = BGP_CURMPPFX(it);
vm->curMatch->isMatching = TRUE;
Bgp_VmStoreMatch(vm);
}
static void Bgp_VmNetMatchFailed(Bgpvm *vm)
{
BGP_VMPUSH(vm, FALSE); // NOTE: See `Bgp_VmCollectNetMatch()`
vm->curMatch = (Bgpvmmatch *) Bgp_VmTempAlloc(vm, sizeof(*vm->curMatch));
if (!vm->curMatch) UNLIKELY
return;
vm->curMatch->pc = BGP_VMCURPC(vm);
vm->curMatch->tag = 0;
vm->curMatch->base = NULL;
vm->curMatch->lim = NULL;
vm->curMatch->pos = NULL;
vm->curMatch->isMatching = FALSE;
Bgp_VmStoreMatch(vm);
}
static const Patricia emptyTrie4 = { AFI_IP };
static const Patricia emptyTrie6 = { AFI_IP6 };
void Bgp_VmDoExct(Bgpvm *vm, Uint8 arg)
{
// POPS:
// -1: AFI_IP6 Patricia (may be NULL)
// -2: AFI_IP Patricia (may be NULL)
//
// PUSHES:
// TRUE on EXACT match of any network prefix in message, FALSE otherwise
if (!BGP_VMCHKSTKSIZ(vm, 2)) UNLIKELY
return;
Bgpmpiter it;
const Patricia *trie6 = (Patricia *) BGP_VMPOPA(vm);
const Patricia *trie = (Patricia *) BGP_VMPOPA(vm);
if (!trie6) trie6 = &emptyTrie6;
if (!trie) trie = &emptyTrie4;
if (trie->afi != AFI_IP || trie6->afi != AFI_IP6) UNLIKELY {
vm->errCode = BGPEVMBADOP;
return;
}
if (Bgp_VmStartNets(vm, &it, arg) != OK) UNLIKELY
return; // error already set
Prefix *pfx;
while ((pfx = Bgp_NextMpPrefix(&it)) != NULL) {
RawPrefix *match = NULL;
// Select appropriate PATRICIA
switch (pfx->afi) {
case AFI_IP:
match = Pat_SearchExact(trie, PLAINPFX(pfx));
break;
case AFI_IP6:
match = Pat_SearchExact(trie6, PLAINPFX(pfx));
break;
default: UNREACHABLE; return;
}
if (match) {
Bgp_VmCollectNetMatch(vm, &it);
return;
}
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgp_VmNetMatchFailed(vm);
}
void Bgp_VmDoSubn(Bgpvm *vm, Uint8 arg)
{
// POPS:
// -1: AFI_IP6 Patricia (may be NULL)
// -2: AFI_IP Patricia (may be NULL)
//
// PUSHES:
// TRUE on SUBN match of any network prefix in message, FALSE otherwise
if (!BGP_VMCHKSTKSIZ(vm, 2)) UNLIKELY
return;
Bgpmpiter it;
const Patricia *trie6 = (Patricia *) BGP_VMPOPA(vm);
const Patricia *trie = (Patricia *) BGP_VMPOPA(vm);
if (!trie6) trie6 = &emptyTrie6;
if (!trie) trie = &emptyTrie4;
if (trie->afi != AFI_IP || trie6->afi != AFI_IP6 ) UNLIKELY {
vm->errCode = BGPEVMBADOP;
return;
}
if (Bgp_VmStartNets(vm, &it, arg) != OK) UNLIKELY
return; // error already set
Prefix *pfx;
while ((pfx = Bgp_NextMpPrefix(&it)) != NULL) {
Boolean isMatching = FALSE;
// Select appropriate PATRICIA
switch (pfx->afi) {
case AFI_IP:
isMatching = Pat_IsSubnetOf(trie, PLAINPFX(pfx));
break;
case AFI_IP6:
isMatching = Pat_IsSubnetOf(trie6, PLAINPFX(pfx));
break;
default: UNREACHABLE; return;
}
if (isMatching) {
Bgp_VmCollectNetMatch(vm, &it);
return;
}
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgp_VmNetMatchFailed(vm);
}
void Bgp_VmDoSupn(Bgpvm *vm, Uint8 arg)
{
// POPS:
// -1: AFI_IP6 Patricia (may be NULL)
// -2: AFI_IP Patricia (may be NULL)
//
// PUSHES:
// TRUE on SUPN match of any network prefix in message, FALSE otherwise
if (!BGP_VMCHKSTKSIZ(vm, 2)) UNLIKELY
return;
Bgpmpiter it;
const Patricia *trie6 = (Patricia *) BGP_VMPOPA(vm);
const Patricia *trie = (Patricia *) BGP_VMPOPA(vm);
if (!trie6) trie6 = &emptyTrie6;
if (!trie) trie = &emptyTrie4;
if (trie->afi != AFI_IP || trie6->afi != AFI_IP6) UNLIKELY {
vm->errCode = BGPEVMBADOP;
return;
}
if (Bgp_VmStartNets(vm, &it, arg) != OK) UNLIKELY
return; // error already set
Prefix *pfx;
while ((pfx = Bgp_NextMpPrefix(&it)) != NULL) {
Boolean isMatching = FALSE;
// Select appropriate PATRICIA
switch (pfx->afi) {
case AFI_IP:
isMatching = Pat_IsSupernetOf(trie, PLAINPFX(pfx));
break;
case AFI_IP6:
isMatching = Pat_IsSupernetOf(trie6, PLAINPFX(pfx));
break;
default: UNREACHABLE; return;
}
if (isMatching) {
Bgp_VmCollectNetMatch(vm, &it);
return;
}
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgp_VmNetMatchFailed(vm);
}
void Bgp_VmDoRelt(Bgpvm *vm, Uint8 arg)
{
// POPS:
// -1: AFI_IP6 Patricia (may be NULL)
// -2: AFI_IP Patricia (may be NULL)
//
// PUSHES:
// TRUE on SUPN match of any network prefix in message, FALSE otherwise
if (!BGP_VMCHKSTKSIZ(vm, 2)) UNLIKELY
return;
Bgpmpiter it;
const Patricia *trie6 = (Patricia *) BGP_VMPOPA(vm);
const Patricia *trie = (Patricia *) BGP_VMPOPA(vm);
if (!trie6) trie6 = &emptyTrie6;
if (!trie) trie = &emptyTrie4;
if (trie->afi != AFI_IP || trie6->afi != AFI_IP6) UNLIKELY {
vm->errCode = BGPEVMBADOP;
return;
}
if (Bgp_VmStartNets(vm, &it, arg) != OK) UNLIKELY
return; // error already set
Prefix *pfx;
while ((pfx = Bgp_NextMpPrefix(&it)) != NULL) {
Boolean isMatching = FALSE;
// Select appropriate PATRICIA
switch (pfx->afi) {
case AFI_IP:
isMatching = Pat_IsRelatedOf(trie, PLAINPFX(pfx));
break;
case AFI_IP6:
isMatching = Pat_IsRelatedOf(trie6, PLAINPFX(pfx));
break;
default: UNREACHABLE; return;
}
if (isMatching) {
Bgp_VmCollectNetMatch(vm, &it);
return;
}
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgp_VmNetMatchFailed(vm);
}
void Bgp_ResetVm(Bgpvm *vm)
{
vm->nk = 0;
vm->nfuncs = 0;
vm->nmatches = 0;
vm->progLen = 0;
vm->hLowMark = 0;
vm->hHighMark = vm->hMemSiz;
BGP_VMCLRSETUP(vm);
BGP_VMCLRERR(vm);
memset(vm->k, 0, sizeof(vm->k));
memset(vm->funcs, 0, sizeof(vm->funcs));
vm->matches = NULL;
}
void Bgp_ClearVm(Bgpvm *vm)
{
free(vm->heap);
if (vm->prog != &emptyProg)
free(vm->prog);
}