[*] Initial commit

This commit is contained in:
Lorenzo Cogotti
2021-06-07 16:55:13 +02:00
commit b0ef4dd774
117 changed files with 29737 additions and 0 deletions

1235
lonetix/bgp/attribute.c Normal file

File diff suppressed because it is too large Load Diff

611
lonetix/bgp/bgp.c Normal file
View File

@@ -0,0 +1,611 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/bgp.c
*
* BGP message decoding.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "sys/sys_local.h"
#include "sys/dbg.h"
#include "sys/endian.h"
#include "sys/ip.h"
#include "sys/con.h"
#include "argv.h"
#include "numlib.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const Uint8 bgp_marker[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
STATIC_ASSERT(sizeof(bgp_marker) == BGP_MARKER_LEN, "Malformed 'bgp_marker'");
static THREAD_LOCAL BgpErrStat bgp_errs;
const char *Bgp_ErrorString(BgpRet code)
{
switch (code) {
case BGPEBADVM: return "Attempting operation on BGP VM under error state";
case BGPEVMNOPROG: return "Attempt to execute BGP VM with empty bytecode";
case BGPEVMBADCOMTCH: return "Bad COMMUNITY match expression";
case BGPEVMASMTCHESIZE: return "BGP VM AS_PATH match expression too complex";
case BGPEVMASNGRPLIM: return "BGP VM AS_PATH match expression group limit hit";
case BGPEVMBADASMTCH: return "Bad AS_PATH match expression";
case BGPEVMBADJMP: return "BGP VM jump instruction target is out of bounds";
case BGPEVMILL: return "Illegal instruction in BGP VM bytecode";
case BGPEVMOOM: return "BGP VM heap memory exhausted";
case BGPEVMBADENDBLK: return "Encountered ENDBLK with no corresponding BLK";
case BGPEVMUFLOW: return "BGP VM stack underflow";
case BGPEVMOFLOW: return "BGP VM stack overflow";
case BGPEVMBADFN: return "CALL instruction index is out of bounds";
case BGPEVMBADK: return "LOADK instruction index is out of bounds";
case BGPEVMMSGERR: return "Error encountered during BGP message access";
case BGPEVMBADOP: return "BGP VM instruction has invalid operand";
case BGPENOERR: return "No error";
case BGPEIO: return "Input/Output error";
case BGPEBADTYPE: return "Provided BGP message has inconsistent type";
case BGPENOADDPATH: return "Provided BGP message contains no ADD_PATH information";
case BGPEBADATTRTYPE: return "Provided BGP attribute has inconsistent type";
case BGPEBADMARKER: return "BGP message marker mismatch";
case BGPENOMEM: return "Memory allocation failure";
case BGPETRUNCMSG: return "Truncated BGP message";
case BGPEOVRSIZ: return "Oversized BGP message";
case BGPEBADOPENLEN: return "BGP OPEN message has inconsistent length";
case BGPEDUPNLRIATTR: return "Duplicate NLRI attribute detected inside UPDATE message";
case BGPEBADPFXWIDTH: return "Bad prefix width";
case BGPETRUNCPFX: return "Truncated prefix";
case BGPETRUNCATTR: return "Truncated BGP attribute";
case BGPEBADAGGR: return "Malformed AGGREGATOR attribute";
case BGPEBADAGGR4: return "Malformed AS4_AGGREGATOR attribute";
case BGPEAFIUNSUP: return "Unsupported Address Family Identifier";
case BGPESAFIUNSUP: return "Unsupported Subsequent Address Family Identifier";
case BGPEBADMRTTYPE: return "Provided MRT record has inconsistent type";
case BGPETRUNCMRT: return "Truncated MRT record";
case BGPEBADPEERIDXCNT: return "TABLE_DUMPV2 Peer Index Table record has incoherent peer entry count";
case BGPETRUNCPEERV2: return "Truncated peer entry in TABLE_DUMPV2 Peer Index Table record";
case BGPEBADRIBV2CNT: return "TABLE_DUMPV2 RIB record has incoherent RIB entry count";
case BGPETRUNCRIBV2: return "Truncated entry in TABLE_DUMPV2 RIB record";
case BGPEBADRIBV2MPREACH: return "TABLE_DUMPV2 RIB record contains an illegal MP_REACH attribute";
case BGPERIBNOMPREACH: return "IPv6 RIB entry lacks MP_REACH_NLRI attribute";
case BGPEBADPEERIDX: return "Peer entry index is out of bounds";
default: return "Unknown BGP error";
}
}
static NOINLINE NORETURN void Bgp_Quit(BgpRet code, Srcloc *loc, void *obj)
{
USED(obj);
loc->call_depth++; // include Bgp_Quit() itself
if (com_progName) {
Sys_Print(STDERR, com_progName);
Sys_Print(STDERR, ": ");
}
Sys_Print(STDERR, "Terminate called in response to an unrecoverable BGP error.\n\t");
if (loc) {
#ifndef NDEBUG
if (loc->filename && loc->line > 0) {
char buf[64];
Utoa(loc->line, buf);
Sys_Print(STDERR, "Error occurred in file ");
Sys_Print(STDERR, loc->filename);
Sys_Print(STDERR, ":");
Sys_Print(STDERR, buf);
Sys_Print(STDERR, "\n\t");
}
#endif
if (loc->func) {
Sys_Print(STDERR, "[");
Sys_Print(STDERR, loc->func);
Sys_Print(STDERR, "()]: ");
}
}
Sys_Print(STDERR, Bgp_ErrorString(code));
Sys_Print(STDERR, ".\n");
exit(EXIT_FAILURE);
}
Judgement _Bgp_SetErrStat(BgpRet code,
const char *filename,
const char *func,
unsigned long long line,
unsigned depth)
{
// Don't clobber error code on BGP message access error inside filtering VM
if (code != BGPEVMMSGERR)
bgp_errs.code = code;
if (code == BGPENOERR)
return OK; // usual case
void (*err_func)(BgpRet, Srcloc *, void *);
Srcloc loc;
err_func = bgp_errs.func;
if (err_func) {
loc.filename = filename;
loc.func = func;
loc.line = line;
loc.call_depth = depth + 1;
if (err_func == BGP_ERR_QUIT)
err_func = Bgp_Quit;
err_func(code, &loc, bgp_errs.obj);
}
return NG;
}
void Bgp_SetErrFunc(void (*func)(BgpRet, Srcloc *, void *),
void *arg)
{
bgp_errs.func = func;
bgp_errs.obj = arg;
}
BgpRet Bgp_GetErrStat(BgpErrStat *stat)
{
if (stat)
*stat = bgp_errs;
return bgp_errs.code;
}
Uint16 Bgp_CheckMsgHdr(const void *data,
size_t nbytes,
Boolean allowExtendedSize)
{
if (nbytes < BGP_HDRSIZ) {
Bgp_SetErrStat(BGPETRUNCMSG);
return 0;
}
const Bgphdr *hdr = (const Bgphdr *) data;
if (memcmp(hdr->marker, bgp_marker, BGP_MARKER_LEN) != 0) {
Bgp_SetErrStat(BGPEBADMARKER);
return 0;
}
Uint16 len = beswap16(hdr->len);
if (len < BGP_HDRSIZ) {
Bgp_SetErrStat(BGPETRUNCMSG);
return 0;
}
if (len > BGP_MSGSIZ && !allowExtendedSize) {
Bgp_SetErrStat(BGPEOVRSIZ);
return 0;
}
if (len > nbytes) {
Bgp_SetErrStat(BGPETRUNCMSG);
return 0;
}
return len;
}
Judgement Bgp_MsgFromBuf(Bgpmsg *msg,
const void *data,
size_t nbytes,
unsigned flags)
{
// Immediately initialize flags (mask away superflous ones)
msg->flags = flags & (BGPF_ADDPATH|BGPF_ASN32BIT|BGPF_EXMSG|BGPF_UNOWNED);
// Check header data for correctness
Uint16 len = Bgp_CheckMsgHdr(data, nbytes, BGP_ISEXMSG(msg->flags));
if (len == 0)
return NG; // error already set by Bgp_CheckHdr()
if (BGP_ISUNOWNED(msg->flags))
msg->buf = (Uint8 *) data; // don't copy data over
else {
// Copy over relevant data
const MemOps *memOps = BGP_MEMOPS(msg);
msg->buf = (Uint8 *) memOps->Alloc(msg->allocp, len, NULL);
if (!msg->buf)
return Bgp_SetErrStat(BGPENOMEM);
memcpy(msg->buf, data, len);
}
BGP_CLRATTRTAB(msg->table);
return Bgp_SetErrStat(BGPENOERR);
}
Judgement Bgp_ReadMsg(Bgpmsg *msg,
void *streamp,
const StmOps *ops,
unsigned flags)
{
// Immediately initialize flags (mask away superflous ones)
msg->flags = flags & (BGPF_ADDPATH|BGPF_ASN32BIT|BGPF_EXMSG|BGPF_UNOWNED);
Uint8 buf[BGP_HDRSIZ];
Sint64 n = ops->Read(streamp, buf, BGP_HDRSIZ);
if (n == 0) {
// precisely at end of stream, no error, just no more BGP messages
Bgp_SetErrStat(BGPENOERR);
return NG;
}
if (n < 0)
return Bgp_SetErrStat(BGPEIO);
if ((size_t) n != BGP_HDRSIZ)
return Bgp_SetErrStat(BGPEIO);
// Retrieve memory allocator
const MemOps *memOps = BGP_MEMOPS(msg);
// Check header and allocate message
Uint16 len = Bgp_CheckMsgHdr(buf, BGP_HDRSIZ, BGP_ISEXMSG(msg->flags));
if (len == 0)
return NG;
msg->buf = (Uint8 *) memOps->Alloc(msg->allocp, len, NULL);
if (!msg->buf)
return Bgp_SetErrStat(BGPENOMEM);
// Copy over the message
memcpy(msg->buf, buf, BGP_HDRSIZ);
len -= BGP_HDRSIZ;
n = ops->Read(streamp, msg->buf + BGP_HDRSIZ, len);
if (n < 0) {
Bgp_SetErrStat(BGPEIO);
goto fail;
}
if ((size_t) n != len) {
Bgp_SetErrStat(BGPEIO);
goto fail;
}
BGP_CLRATTRTAB(msg->table);
return Bgp_SetErrStat(BGPENOERR);
fail:
memOps->Free(msg->allocp, msg->buf);
return NG;
}
#define BGP_OPEN_MINSIZ (BGP_HDRSIZ + 1uLL + 2uLL + 2uLL + 4uLL + 1uLL)
Bgpopen *Bgp_GetMsgOpen(Bgpmsg *msg)
{
Bgphdr *hdr = BGP_HDR(msg);
if (hdr->type != BGP_OPEN) {
Bgp_SetErrStat(BGPEBADTYPE);
return NULL;
}
size_t len = beswap16(hdr->len);
if (len < BGP_OPEN_MINSIZ) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgpopen *open = (Bgpopen *) hdr;
size_t nbytes = BGP_OPEN_MINSIZ + open->parmsLen;
if (nbytes != len) {
Bgp_SetErrStat((nbytes > len) ? BGPETRUNCMSG : BGPEBADOPENLEN);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return open;
}
#define BGP_UPDATE_MINSIZ (BGP_HDRSIZ + 2uLL + 2uLL)
Bgpupdate *Bgp_GetMsgUpdate(Bgpmsg *msg)
{
Bgphdr *hdr = BGP_HDR(msg);
if (hdr->type != BGP_UPDATE) {
Bgp_SetErrStat(BGPEBADTYPE);
return NULL;
}
Uint16 len = beswap16(hdr->len);
if (len < BGP_UPDATE_MINSIZ) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return (Bgpupdate *) hdr;
}
static Boolean Bgp_SwitchMpIterator(Bgpmpiter *it)
{
if (!it->nextAttr)
return FALSE; // no additional attribute to iterate, we're done
// Switch iterator
const Bgpmpfam *family = Bgp_GetMpFamily(it->nextAttr); // sets error
if (!family)
return FALSE; // corrupted attribute
size_t nbytes;
void *routes = Bgp_GetMpRoutes(it->nextAttr, &nbytes);
if (!routes)
return FALSE; // corrupted message
// Begin subsequent iteration
Afi afi = family->afi;
Safi safi = family->safi;
if (Bgp_StartPrefixes(&it->rng, afi, safi, routes, nbytes, it->rng.isAddPath) != OK)
return FALSE; // shouldn't happen
// Switch complete, clear attribute and move on
it->nextAttr = NULL;
return TRUE;
}
Judgement Bgp_StartMsgWithdrawn(Prefixiter *it, Bgpmsg *msg)
{
Bgpupdate *update = Bgp_GetMsgUpdate(msg);
if (!update)
return NG;
Bgpwithdrawnseg *withdrawn = Bgp_GetUpdateWithdrawn(update);
if (!withdrawn)
return NG;
return Bgp_StartPrefixes(it, AFI_IP, SAFI_UNICAST,
withdrawn->nlri, beswap16(withdrawn->len),
BGP_ISADDPATH(msg->flags));
}
Judgement Bgp_StartAllMsgWithdrawn(Bgpmpiter *it, Bgpmsg *msg)
{
it->nextAttr = Bgp_GetMsgAttribute(msg, BGP_ATTR_MP_UNREACH_NLRI);
if (!it->nextAttr && Bgp_GetErrStat(NULL))
return NG;
if (Bgp_StartMsgWithdrawn(&it->rng, msg) != OK)
return NG;
return OK;
}
void Bgp_InitMpWithdrawn(Bgpmpiter *it,
const Bgpwithdrawnseg *withdrawn,
const Bgpattr *mpUnreach,
Boolean isAddPath)
{
it->nextAttr = (Bgpattr *) mpUnreach;
Bgp_StartPrefixes(&it->rng,
AFI_IP, SAFI_UNICAST,
withdrawn->nlri, beswap16(withdrawn->len),
isAddPath);
}
void Bgp_InitMpNlri(Bgpmpiter *it,
const void *nlri,
size_t nbytes,
const Bgpattr *mpReach,
Boolean isAddPath)
{
it->nextAttr = (Bgpattr *) mpReach;
Bgp_StartPrefixes(&it->rng, AFI_IP, SAFI_UNICAST, nlri, nbytes, isAddPath);
}
Judgement Bgp_StartMsgNlri(Prefixiter *it, Bgpmsg *msg)
{
Bgpupdate *update = Bgp_GetMsgUpdate(msg);
if (!update)
return NG;
size_t nbytes;
void *nlri = Bgp_GetUpdateNlri(update, &nbytes);
if (!nlri)
return NG;
return Bgp_StartPrefixes(it, AFI_IP, SAFI_UNICAST,
nlri, nbytes,
BGP_ISADDPATH(msg->flags));
}
Judgement Bgp_StartAllMsgNlri(Bgpmpiter *it, Bgpmsg *msg)
{
it->nextAttr = Bgp_GetMsgAttribute(msg, BGP_ATTR_MP_REACH_NLRI);
if (!it->nextAttr && Bgp_GetErrStat(NULL))
return NG;
if (Bgp_StartMsgNlri(&it->rng, msg) != OK)
return NG;
return OK;
}
Prefix *Bgp_NextMpPrefix(Bgpmpiter *it)
{
void *rawPfx;
do {
rawPfx = Bgp_NextPrefix(&it->rng); // sets error
if (!rawPfx) {
// Swap iterator if necessary and try again
if (Bgp_GetErrStat(NULL))
return NULL;
if (!Bgp_SwitchMpIterator(it))
return NULL;
}
} while (!rawPfx);
// Extended prefix info
Prefix *cur = &it->pfx;
cur->afi = it->rng.afi;
cur->safi = it->rng.safi;
if (it->rng.isAddPath) {
// ADD-PATH enabled prefix
const ApRawPrefix *pfx = (const ApRawPrefix *) rawPfx;
cur->isAddPath = TRUE;
cur->pathId = pfx->pathId;
cur->width = pfx->width;
memcpy(cur->bytes, pfx->bytes, PFXLEN(pfx->width));
} else {
// Regular prefix
const RawPrefix *pfx = (const RawPrefix *) rawPfx;
cur->isAddPath = FALSE;
cur->pathId = 0;
cur->width = pfx->width;
memcpy(cur->bytes, pfx->bytes, PFXLEN(pfx->width));
}
return cur;
}
#define BGP_NOTIFICATION_MINSIZ (BGP_HDRSIZ + 1uLL + 1uLL)
Bgpnotification *Bgp_GetNotification(Bgpmsg *msg)
{
Bgphdr *hdr = BGP_HDR(msg);
if (hdr->type != BGP_NOTIFICATION) {
Bgp_SetErrStat(BGPEBADTYPE);
return NULL;
}
Uint16 len = beswap16(hdr->len);
if (len < BGP_NOTIFICATION_MINSIZ) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return (Bgpnotification *) hdr;
}
Bgpparmseg *Bgp_GetParmsFromMemory(const void *data, size_t size)
{
const size_t BGP_OPEN_PARMSOFF = BGP_OPEN_MINSIZ - BGP_HDRSIZ;
if (size < BGP_OPEN_PARMSOFF) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgpparmseg *parms = (Bgpparmseg *) ((Uint8 *) data + BGP_OPEN_PARMSOFF - 1);
size_t nbytes = BGP_OPEN_PARMSOFF + parms->len;
if (nbytes != size) {
Bgp_SetErrStat((nbytes > size) ? BGPETRUNCMSG : BGPEBADOPENLEN);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return parms;
}
Bgpparmseg *Bgp_GetOpenParms(const Bgpopen *open)
{
assert(open->hdr.type == BGP_OPEN);
size_t len = beswap16(open->hdr.len) - BGP_HDRSIZ;
return Bgp_GetParmsFromMemory(&open->version, len);
}
Bgpwithdrawnseg *Bgp_GetWithdrawnFromMemory(const void *data, size_t size)
{
if (size < 2uLL) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgpwithdrawnseg *withdrawn = (Bgpwithdrawnseg *) data;
size -= 2;
if (size < beswap16(withdrawn->len) + 2uLL) { // also accounts for TPA length
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return withdrawn;
}
Bgpwithdrawnseg *Bgp_GetUpdateWithdrawn(const Bgpupdate *msg)
{
assert(msg->hdr.type == BGP_UPDATE);
size_t len = beswap16(msg->hdr.len);
return Bgp_GetWithdrawnFromMemory(msg->data, len - BGP_HDRSIZ);
}
Bgpattrseg *Bgp_GetAttributesFromMemory(const void *data, size_t size)
{
Bgpwithdrawnseg *withdrawn = Bgp_GetWithdrawnFromMemory(data, size);
if (!withdrawn)
return NULL; // sets error
size_t withdrawnLen = beswap16(withdrawn->len);
Bgpattrseg *tpa = (Bgpattrseg *) &withdrawn->nlri[withdrawnLen];
size_t tpaLen = beswap16(tpa->len);
if (size < 2uLL + withdrawnLen + 2uLL + tpaLen) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
return tpa; // error already cleared
}
Bgpattrseg *Bgp_GetUpdateAttributes(const Bgpupdate *msg)
{
assert(msg->hdr.type == BGP_UPDATE);
size_t len = beswap16(msg->hdr.len);
return Bgp_GetAttributesFromMemory(msg->data, len - BGP_HDRSIZ);
}
void *Bgp_GetNlriFromMemory(const void *nlri, size_t size, size_t *nbytes)
{
Bgpattrseg *tpa = Bgp_GetAttributesFromMemory(nlri, size);
if (!tpa)
return NULL; // error already set
size_t tpaLen = beswap16(tpa->len);
if (nbytes) {
size_t offset = &tpa->attrs[tpaLen] - (Uint8 *) nlri;
*nbytes = size - offset;
}
return &tpa->attrs[tpaLen];
}
void *Bgp_GetUpdateNlri(const Bgpupdate *msg, size_t *nbytes)
{
assert(msg->hdr.type == BGP_UPDATE);
size_t len = beswap16(msg->hdr.len);
return Bgp_GetNlriFromMemory(msg->data, len - BGP_HDRSIZ, nbytes);
}
void Bgp_ClearMsg(Bgpmsg *msg)
{
if (!BGP_ISUNOWNED(msg->flags))
BGP_MEMOPS(msg)->Free(msg->allocp, msg->buf);
msg->flags = 0;
msg->buf = NULL;
}

45
lonetix/bgp/bgp_local.h Normal file
View File

@@ -0,0 +1,45 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/bgp_local.h
*
* Private BGP library header.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGP_LOCAL_H_
#define DF_BGP_LOCAL_H_
#include "bgp/mrt.h"
// Low level prefix operations
void Bgp_InitMpWithdrawn(Bgpmpiter *it, const Bgpwithdrawnseg *withdrawn, const Bgpattr *mpUnreach, Boolean isAddPath);
void Bgp_InitMpNlri(Bgpmpiter *it, const void *data, size_t nbytes, const Bgpattr *mpReach, Boolean isAddPath);
// Low level BGP operations
Uint16 Bgp_CheckMsgHdr(const void *data, size_t nbytes, Boolean allowExtendedSize);
Bgpparmseg *Bgp_GetParmsFromMemory(const void *data, size_t size);
Bgpwithdrawnseg *Bgp_GetWithdrawnFromMemory(const void *data, size_t size);
Bgpattrseg *Bgp_GetAttributesFromMemory(const void *data, size_t size);
void *Bgp_GetNlriFromMemory(const void *nlri, size_t size, size_t *nbytes);
// Extension in attribute.c special iteration on attributes
/// Non-caching variant of `Bgp_NextAttribute()`, doesn't update `it->table`.
Bgpattr *Bgp_NcNextAttribute(Bgpattriter *it);
#define Bgp_SetErrStat(code) \
_Bgp_SetErrStat(code, __FILE__, __func__, __LINE__, 0)
NOINLINE Judgement _Bgp_SetErrStat(BgpRet code,
const char *filename,
const char *func,
unsigned long long line,
unsigned depth);
#endif

177
lonetix/bgp/bytebuf.c Normal file
View File

@@ -0,0 +1,177 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/bytebuf.c
*
* Trivial BGP memory allocator for basic BGP workloads.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bytebuf.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
STATIC_ASSERT(BGP_MEMBUF_ALIGN >= 4, "bytebuf.c assumes Uint32 header");
#define USEDBIT BIT(0)
#define MAXBUFCHUNKSIZ 0xffffffffuLL
#define BLKSIZ(ptr) ((*(Uint32 *) (ptr)) & ~USEDBIT)
#define ISUSED(ptr) (((*(Uint32 *) (ptr)) & USEDBIT) != 0)
#define SETUSED(ptr) ((void) ((*(Uint32 *) (ptr)) |= USEDBIT))
#define CLRUSED(ptr) ((void) ((*(Uint32 *) (ptr)) &= ~USEDBIT))
static Boolean Mem_IsInBuffer(Bgpbytebuf *buf, void *ptr)
{
return (Uint8 *) ptr >= buf->base &&
(Uint8 *) ptr < buf->base + buf->size;
}
static Uint8 *Mem_FindPrevChunk(Bgpbytebuf *buf, void *chunk)
{
assert(Mem_IsInBuffer(buf, chunk));
Uint8 *p = buf->base;
while (p < (Uint8 *) chunk) {
size_t siz = BLKSIZ(p);
if (p + siz == (Uint8 *) chunk)
return p;
p += siz;
}
return NULL;
}
static void Mem_BgpFree(void *allocator, void *ptr)
{
Bgpbytebuf *buf = (Bgpbytebuf *) allocator;
// Regular free() for out of buffer allocations
if (!Mem_IsInBuffer(buf, ptr)) {
free(ptr);
return;
}
// Get pointer to chunk
Uint8 *p = (Uint8 *) ptr - 4;
assert(ISUSED(p));
// Get buffer limit
Uint8 *lim = buf->base + buf->pos;
Uint32 siz = BLKSIZ(p);
CLRUSED(p); // toggle off USEDBIT
// Find successor if any
Uint8 *next = p + siz;
if (next < lim && !ISUSED(next)) {
// Merge forward
siz += BLKSIZ(next);
*(Uint32 *) p = siz;
}
// Find predecessor, if any
Uint8 *prev = Mem_FindPrevChunk(buf, p);
if (prev && !ISUSED(prev)) {
// Merge backwards
siz += BLKSIZ(prev);
p = prev;
*(Uint32 *) p = siz;
}
// Move position backwards when freeing last block
if (p + siz == lim)
buf->pos -= siz;
}
static void *Mem_BgpDoRealloc(Bgpbytebuf *buf, void *oldp, size_t nbytes)
{
// Use plain realloc() if we're not managing a buffered pointer
if (!Mem_IsInBuffer(buf, oldp))
return realloc(oldp, nbytes);
assert(IS_PTR_ALIGNED(oldp, BGP_MEMBUF_ALIGN));
Uint8 *ptr = (Uint8 *) oldp - 4;
assert(ISUSED(ptr));
Uint32 oldSiz = BLKSIZ(ptr);
assert(buf->pos >= oldSiz);
size_t siz = 4 + ALIGN(nbytes, BGP_MEMBUF_ALIGN);
if (oldSiz >= siz) {
// Shrink operation, free up the trailing part if we're the last chunk
if (ptr + oldSiz == buf->base + buf->pos) {
*(Uint32 *) ptr = siz | USEDBIT;
buf->pos -= (oldSiz - siz);
}
return oldp;
}
// May only grow a chunk if this is the last one and we don't overflow
if (ptr + oldSiz != buf->base + buf->pos)
return NULL;
size_t newPos = buf->pos + (siz - oldSiz);
if (newPos > buf->size)
return NULL;
// Ok to grow the chunk
*(Uint32 *) ptr = siz | USEDBIT;
buf->pos = newPos;
return oldp;
}
static void *Mem_BgpDoAlloc(Bgpbytebuf *buf, size_t nbytes)
{
// Use plain malloc() for large allocations or when out of buffer space
size_t siz = 4 + ALIGN(nbytes, BGP_MEMBUF_ALIGN);
if (buf->pos + siz > buf->size || siz > MAXBUFCHUNKSIZ)
return malloc(nbytes);
// Return the next chunk available
Uint32 *ptr = (Uint32 *) (buf->base + buf->pos);
buf->pos += siz;
assert((siz & USEDBIT) == 0);
*ptr++ = siz | USEDBIT;
return ptr;
}
static void *Mem_BgpAlloc(void *allocator, size_t nbytes, void *oldp)
{
Bgpbytebuf *buf = (Bgpbytebuf *) allocator;
// Handle common allocations with no `oldp`
if (!oldp)
return Mem_BgpDoAlloc(buf, nbytes);
// Attempt memory reuse
void *ptr = Mem_BgpDoRealloc(buf, oldp, nbytes);
if (ptr)
return ptr;
// Fallback to simple allocation+memcpy()
ptr = Mem_BgpDoAlloc(buf, nbytes);
if (ptr) {
memcpy(ptr, oldp, nbytes);
Mem_BgpFree(buf, oldp);
}
return ptr;
}
static const MemOps mem_bgpBufTable = {
Mem_BgpAlloc,
Mem_BgpFree
};
const MemOps *const Mem_BgpBufOps = &mem_bgpBufTable;

82
lonetix/bgp/dump.c Normal file
View File

@@ -0,0 +1,82 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/dump.c
*
* General BGP dump functions wrappers.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "bgp/dump.h"
#include "sys/endian.h"
#define CALLFMT(fn, ...) \
((fn) ? (fn(__VA_ARGS__)) : ((Sint64) Bgp_SetErrStat(BGPENOERR)))
Sint64 Bgp_DumpMrtUpdate(const Mrthdr *hdr,
void *streamp, const StmOps *ops,
const BgpDumpfmt *fmt)
{
Bgpattrtab table;
if (!ops->Write) {
Bgp_SetErrStat(BGPENOERR);
return 0;
}
BGP_CLRATTRTAB(table);
if (MRT_ISBGP4MP(hdr->type)) {
return CALLFMT(fmt->DumpBgp4mp, hdr, streamp, ops, table);
} else if (hdr->type == MRT_BGP) {
return CALLFMT(fmt->DumpZebra, hdr, streamp, ops, table);
} else {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return -1;
}
}
Sint64 Bgp_DumpMrtRibv2(const Mrthdr *hdr,
const Mrtpeerentv2 *peer, const Mrtribentv2 *ent,
void *streamp, const StmOps *ops,
const BgpDumpfmt *fmt)
{
Bgpattrtab table;
if (!ops->Write) {
Bgp_SetErrStat(BGPENOERR);
return 0;
}
if (hdr->type != MRT_TABLE_DUMPV2 || !TABLE_DUMPV2_ISRIB(hdr->subtype)) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return -1;
}
BGP_CLRATTRTAB(table);
return CALLFMT(fmt->DumpRibv2, hdr, peer, ent, streamp, ops, table);
}
Sint64 Bgp_DumpMrtRib(const Mrthdr *hdr,
const Mrtribent *ent,
void *streamp, const StmOps *ops,
const BgpDumpfmt *fmt)
{
Bgpattrtab table;
if (!ops->Write) {
Bgp_SetErrStat(BGPENOERR);
return 0;
}
if (hdr->type != MRT_TABLE_DUMP) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return -1;
}
BGP_CLRATTRTAB(table);
return CALLFMT(fmt->DumpRib, hdr, ent, streamp, ops, table);
}

1085
lonetix/bgp/dump_isolario.c Normal file

File diff suppressed because it is too large Load Diff

751
lonetix/bgp/mrt.c Normal file
View File

@@ -0,0 +1,751 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/mrt.c
*
* Deals with Multi-Threaded Routing Toolkit (MRT) format.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "sys/endian.h"
#include "sys/interlocked.h"
#include <assert.h>
#include <string.h>
static void *MRT_DATAPTR(const Mrtrecord *rec)
{
return rec->buf + MRT_HDRSIZ + (MRT_ISEXHDRTYPE(MRT_HDR(rec)->type) << 2);
}
Judgement Bgp_MrtFromBuf(Mrtrecord *rec, const void *buf, size_t nbytes)
{
if (nbytes < MRT_HDRSIZ)
return Bgp_SetErrStat(BGPETRUNCMRT);
const Mrthdr *hdr = (const Mrthdr *) buf;
size_t left = beswap32(hdr->len);
if (MRT_ISEXHDRTYPE(hdr->type))
left += 4; // account for extended timestamp
size_t siz = sizeof(*hdr) + left;
if (siz > nbytes)
return Bgp_SetErrStat(BGPETRUNCMRT);
const MemOps *memOps = MRT_MEMOPS(rec);
rec->buf = (Uint8 *) memOps->Alloc(rec->allocp, siz, NULL);
if (!rec->buf)
return Bgp_SetErrStat(BGPENOMEM);
rec->peerOffTab = NULL;
memcpy(rec->buf, buf, siz);
return Bgp_SetErrStat(BGPENOERR);
}
Judgement Bgp_ReadMrt(Mrtrecord *rec, void *streamp, const StmOps *ops)
{
Mrthdr hdr;
// Read header
Sint64 n = ops->Read(streamp, &hdr, sizeof(hdr));
if (n == 0) {
// Precisely at end of file, no error, just no more records
Bgp_SetErrStat(BGPENOERR);
return NG;
}
if (n < 0)
return Bgp_SetErrStat(BGPEIO);
if ((size_t) n != sizeof(hdr))
return Bgp_SetErrStat(BGPEIO);
size_t left = beswap32(hdr.len);
if (MRT_ISEXHDRTYPE(hdr.type))
left += 4; // account for extended timestamp
// Allocate buffer
// NOTE: MRT header length doesn't account for header size itself
size_t siz = sizeof(hdr) + left;
const MemOps *memOps = MRT_MEMOPS(rec);
rec->buf = (Uint8 *) memOps->Alloc(rec->allocp, siz, NULL);
if (!rec->buf)
return Bgp_SetErrStat(BGPENOMEM);
// Populate buffer
memcpy(rec->buf, &hdr, sizeof(hdr));
n = ops->Read(streamp, rec->buf + sizeof(hdr), left);
if (n < 0) {
Bgp_SetErrStat(BGPEIO);
goto fail;
}
if ((size_t) n != left) {
Bgp_SetErrStat(BGPEIO);
goto fail;
}
rec->peerOffTab = NULL;
return Bgp_SetErrStat(BGPENOERR);
fail:
memOps->Free(rec->allocp, rec->buf);
return NG;
}
#define MRT_PEERIDX_MINSIZ (4 + 2 + 2)
Mrtpeeridx *Bgp_GetMrtPeerIndex(Mrtrecord *rec)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_TABLE_DUMPV2 || hdr->subtype != TABLE_DUMPV2_PEER_INDEX_TABLE) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
// Basic size check (only for fixed size portion)
size_t len = beswap32(hdr->len);
size_t siz = MRT_PEERIDX_MINSIZ;
if (len < siz) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
// NOTE: PEER_INDEX_TABLE cannot have extended timestamp
assert(!MRT_ISEXHDRTYPE(hdr->subtype));
Mrtpeeridx *peerIdx = (Mrtpeeridx *) (hdr + 1);
// View Name field size check
siz += beswap16(peerIdx->viewNameLen);
if (len < siz) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return peerIdx;
}
void *Bgp_GetMrtPeerIndexPeers(Mrtrecord *rec, size_t *peersCount, size_t *nbytes)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_TABLE_DUMPV2 || hdr->subtype != TABLE_DUMPV2_PEER_INDEX_TABLE) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
// Basic size check
size_t len = beswap32(hdr->len);
size_t off = 4; // BGP Identifier
if (len < off + 2) { // view name length
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
// NOTE: PEER_INDEX_TABLE cannot have extended timestamp
assert(!MRT_ISEXHDRTYPE(hdr->subtype));
Mrtpeeridx *peerIdx = (Mrtpeeridx *) (hdr + 1);
// View Name size check
off += 2 + beswap16(peerIdx->viewNameLen); // skip view name
if (len < off + 2) { // entry count
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
// Calculate relevant sizes and return peers chunk
if (peersCount) {
Uint16 count;
memcpy(&count, (Uint8 *) peerIdx + off, sizeof(count));
*peersCount = beswap16(count);
}
off += 2; // skip peers count
if (nbytes)
*nbytes = len - off;
return (Uint8 *) peerIdx + off;
}
static size_t MRT_PEERENTSIZ(const Mrtpeerentv2 *ent)
{
size_t len = 1 + 4;
len += MRT_ISPEERASN32BIT(ent->type) ? 4 : 2;
len += MRT_ISPEERIPV6(ent->type) ? IPV6_SIZE : IPV4_SIZE;
return len;
}
static Mrtpeertabv2 *Bgp_GetPeerOffsetTable(Mrtrecord *rec)
{
Mrtpeertabv2 *tab;
while (TRUE) {
tab = (Mrtpeertabv2 *) Smp_AtomicLoadPtrAcq(&rec->peerOffTab);
if (tab)
break; // already allocated
// Must allocate the table anew
size_t peerCount;
if (!Bgp_GetMrtPeerIndexPeers(rec, &peerCount, /*nbytes=*/NULL))
return NULL; // bad record type or corrupted PEER_INDEX_TABLE
const MemOps *memOps = MRT_MEMOPS(rec);
tab = (Mrtpeertabv2 *) memOps->Alloc(rec->allocp, offsetof(Mrtpeertabv2, offsets[peerCount]), NULL);
if (!tab) {
Bgp_SetErrStat(BGPENOMEM);
return NULL;
}
Smp_AtomicStore16Rx(&tab->validCount, 0);
Smp_AtomicStore16Rx(&tab->peerCount, peerCount);
if (Smp_AtomicCasPtrRel(&rec->peerOffTab, NULL, tab))
break; // all good
memOps->Free(rec->allocp, tab); // ...somebody just allocated the table for us
}
return tab;
}
Mrtpeerentv2 *Bgp_GetMrtPeerByIndex(Mrtrecord *rec, Uint16 idx)
{
// NOTE: no extended timestamp TABLE_DUMPV2 exists, so we can simplify
// record access
Mrtpeertabv2 *tab = Bgp_GetPeerOffsetTable(rec);
if (!tab)
return NULL;
// If we have a `Mrtpeertabv2` we're positively sure that `Mrtpeeridx`
// is well formed.
const Mrthdr *hdr = MRT_HDR(rec);
const Mrtpeeridx *peerIdx = (const Mrtpeeridx *) (hdr + 1);
size_t baseOff = MRT_HDRSIZ + MRT_PEERIDX_MINSIZ + beswap16(peerIdx->viewNameLen);
// IMPORTANT INVARIANT: `tab->validCount` may change, but will only ever be
// incremented.
Uint16 validCount = Smp_AtomicLoad16Acq(&tab->validCount);
if (idx < validCount) {
// FAST PATH: Offset was cached
Uint32 off = Smp_AtomicLoad32Rx(&tab->offsets[idx]);
Bgp_SetErrStat(BGPENOERR);
return (Mrtpeerentv2 *) (rec->buf + baseOff + off);
}
// SLOW PATH: Must scan PEER_INDEX_TABLE and update offsets
// Check that a valid peer was actually requested
Uint16 peerCount = Smp_AtomicLoad16Rx(&tab->peerCount);
if (idx >= peerCount) {
Bgp_SetErrStat(BGPEBADPEERIDX);
return NULL;
}
/* NOTE: We cheat a bit:
* - if we have a `peerOffTab`, then we know PEER_INDEX_TABLE header is
* well formed, so we can build a `Mrtpeeriterv2` confidently
* without checking data integrity;
* - we know the data was well formed at least up to the last valid
* peer entry, so we can resume iteration there;
*/
Mrtpeeriterv2 it;
Mrtpeerentv2 *ent;
Uint32 lastOff;
// Initialize iterator to last known offset
if (validCount == 0)
lastOff = 0;
else {
lastOff = Smp_AtomicLoad32Rx(&tab->offsets[validCount - 1]);
ent = (Mrtpeerentv2 *) (rec->buf + baseOff + lastOff);
lastOff += MRT_PEERENTSIZ(ent);
}
it.base = rec->buf + baseOff;
it.lim = rec->buf + MRT_HDRSIZ + beswap32(hdr->len);
it.ptr = it.base + lastOff;
it.peerCount = peerCount;
it.nextIdx = validCount;
// Keep iterating to find the new entry, update table in the process
/* NOTE: We don't care if we concurrently write offsets to the table
* while some other thread also updates that, we know we'll be writing
* the same offsets in the same slots there.
*/
Uint16 newValidCount = validCount;
do {
ent = Bgp_NextMrtPeerv2(&it);
if (!ent)
return NULL; // error status already set by iterator
Uint32 off = (Uint8 *) ent - it.base;
Smp_AtomicStore32Rx(&tab->offsets[newValidCount], off);
newValidCount++;
} while (idx >= newValidCount);
// Signal what we've done to the world, don't update anything
// if somebody else changed the table under our feet.
Smp_AtomicCas16Rel(&tab->validCount, validCount, newValidCount);
return ent; // success status already set by iterator
}
Judgement Bgp_StartMrtPeersv2(Mrtpeeriterv2 *it, Mrtrecord *rec)
{
size_t peerCount, nbytes;
void *peers = (Uint8 *) Bgp_GetMrtPeerIndexPeers(rec, &peerCount, &nbytes);
if (!peers)
return NG;
it->base = (Uint8 *) peers;
it->lim = it->base + nbytes;
it->ptr = it->base;
it->peerCount = peerCount;
it->nextIdx = 0;
return OK; // success already set
}
Mrtpeerentv2 *Bgp_NextMrtPeerv2(Mrtpeeriterv2 *it)
{
if (it->ptr >= it->lim) {
// End of iteration, check for correct peer count
if (it->nextIdx == it->peerCount)
Bgp_SetErrStat(BGPENOERR);
else
Bgp_SetErrStat(BGPEBADPEERIDXCNT);
return NULL;
}
size_t left = it->lim - it->ptr;
assert(left > 0);
Mrtpeerentv2 *ent = (Mrtpeerentv2 *) it->ptr;
size_t len = MRT_PEERENTSIZ(ent);
if (left < len) {
Bgp_SetErrStat(BGPETRUNCPEERV2);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
it->ptr += len;
it->nextIdx++;
return ent;
}
Mrtribhdrv2 *Bgp_GetMrtRibHdrv2(Mrtrecord *rec, size_t *nbytes)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_TABLE_DUMPV2) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
Mrtribhdrv2 *rib = RIBV2_HDR(hdr);
size_t len = beswap32(hdr->len);
size_t offset = 0;
offset += 4; // sequence number
size_t maxPfxWidth;
if (TABLE_DUMPV2_ISGENERICRIB(hdr->subtype)) {
offset += 2 + 1; // AFI, SAFI
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
switch (rib->gen.afi) {
case AFI_IP: maxPfxWidth = IPV4_WIDTH; break;
case AFI_IP6: maxPfxWidth = IPV6_WIDTH; break;
default:
Bgp_SetErrStat(BGPEAFIUNSUP);
return NULL;
}
if (rib->gen.safi != SAFI_UNICAST && rib->gen.safi != SAFI_MULTICAST) {
Bgp_SetErrStat(BGPESAFIUNSUP);
return NULL;
}
} else if (TABLE_DUMPV2_ISIPV4RIB(hdr->subtype)) {
maxPfxWidth = IPV4_WIDTH;
} else if (TABLE_DUMPV2_ISIPV6RIB(hdr->subtype)) {
maxPfxWidth = IPV6_WIDTH;
} else {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
const RawPrefix *pfx = (const RawPrefix *) ((Uint8 *) rib + offset);
offset++; // prefix width
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
if (pfx->width > maxPfxWidth) {
Bgp_SetErrStat(BGPEBADPFXWIDTH);
return NULL;
}
offset += PFXLEN(pfx->width);
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
if (nbytes)
*nbytes = offset;
return rib;
}
Mrtribentriesv2 *Bgp_GetMrtRibEntriesv2(Mrtrecord *rec, size_t *nbytes)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_TABLE_DUMPV2) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
if (!TABLE_DUMPV2_ISRIB(hdr->subtype)) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
size_t len = beswap32(hdr->len);
size_t offset;
Mrtribhdrv2 *rib = Bgp_GetMrtRibHdrv2(rec, &offset);
if (!rib)
return NULL; // error already set
Mrtribentriesv2 *ents = (Mrtribentriesv2 *) ((Uint8 *) rib + offset);
offset += 2; // entries count
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
if (nbytes)
*nbytes = len - offset;
return ents;
}
Judgement Bgp_StartMrtRibEntriesv2(Mrtribiterv2 *it, Mrtrecord *rec)
{
size_t nbytes;
Mrtribentriesv2 *ents = Bgp_GetMrtRibEntriesv2(rec, &nbytes);
if (!ents)
return NG;
it->base = ents->entries;
it->lim = it->base + nbytes;
it->ptr = it->base;
it->isAddPath = TABLE_DUMPV2_ISADDPATHRIB(MRT_HDR(rec)->subtype);
it->entryCount = beswap16(ents->entryCount);
it->nextIdx = 0;
return OK;
}
Mrtribentv2 *Bgp_NextRibEntryv2(Mrtribiterv2 *it)
{
if (it->ptr >= it->lim) {
if (it->nextIdx == it->entryCount)
Bgp_SetErrStat(BGPENOERR);
else
Bgp_SetErrStat(BGPEBADRIBV2CNT);
return NULL;
}
size_t left = it->lim - it->ptr;
assert(left > 0);
Mrtribentv2 *ent = (Mrtribentv2 *) it->ptr;
size_t offset = 2 + 4; // peer index, originated time
if (it->isAddPath)
offset += 4; // path id
if (left < offset + 2) { // attributes length
Bgp_SetErrStat(BGPETRUNCRIBV2);
return NULL;
}
Bgpattrseg *tpa = (Bgpattrseg *) ((Uint8 *) ent + offset);
offset += 2 + beswap16(tpa->len);
if (left < offset) {
Bgp_SetErrStat(BGPETRUNCRIBV2);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
it->ptr += offset;
it->nextIdx++;
return ent;
}
Bgp4mphdr *Bgp_GetBgp4mpHdr(Mrtrecord *rec, size_t *nbytes)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (!MRT_ISBGP4MP(hdr->type)) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
size_t len = beswap32(hdr->len);
size_t offset = 0;
Afi afi;
Bgp4mphdr *bgp4mp = (Bgp4mphdr *) MRT_DATAPTR(rec);
if (BGP4MP_ISASN32BIT(hdr->subtype)) {
offset += 2 * 4;
offset += 2 + 2;
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
afi = bgp4mp->a32.afi;
} else {
offset += 2 * 2;
offset += 2 + 2;
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
afi = bgp4mp->a16.afi;
}
switch (afi) {
case AFI_IP: offset += 2 * IPV4_SIZE; break;
case AFI_IP6: offset += 2 * IPV6_SIZE; break;
default:
Bgp_SetErrStat(BGPEAFIUNSUP);
return NULL;
}
if (BGP4MP_ISSTATECHANGE(hdr->subtype))
offset += 2 * 2;
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
if (nbytes)
*nbytes = offset;
return bgp4mp;
}
Judgement Bgp_UnwrapBgp4mp(Mrtrecord *rec, Bgpmsg *dest, unsigned flags)
{
const Mrthdr *hdr = MRT_HDR(rec);
if (!MRT_ISBGP4MP(hdr->type))
return Bgp_SetErrStat(BGPEBADMRTTYPE);
if (!BGP4MP_ISMESSAGE(hdr->subtype))
return Bgp_SetErrStat(BGPEBADMRTTYPE);
Uint32 len = beswap32(hdr->len);
Uint8 *base = (Uint8 *) MRT_DATAPTR(rec);
// Skip header
size_t siz = BGP4MP_ISASN32BIT(hdr->subtype) ? 2*4 : 2*2; // skip ASN
siz += 2; // skip interface index
Afi afi;
if (len < siz + sizeof(afi))
return Bgp_SetErrStat(BGPETRUNCMRT);
// Skip AFI and addresses
memcpy(&afi, base + siz, sizeof(afi));
siz += sizeof(afi);
switch (afi) {
case AFI_IP:
siz += 2 * sizeof(Ipv4adr);
break;
case AFI_IP6:
siz += 2 * sizeof(Ipv6adr);
break;
default:
return Bgp_SetErrStat(BGPEAFIUNSUP);
}
if (len < siz)
return Bgp_SetErrStat(BGPETRUNCMRT);
size_t msgsiz = len - siz;
void *buf = base + siz;
// Mask away ignored flags
flags &= ~(BGPF_ADDPATH|BGPF_ASN32BIT);
// ...and automatically reset them as defined by BGP4MP subtype
if (BGP4MP_ISASN32BIT(hdr->subtype))
flags |= BGPF_ASN32BIT;
if (BGP4MP_ISADDPATH(hdr->subtype))
flags |= BGPF_ADDPATH;
// Unwrap BGP message
return Bgp_MsgFromBuf(dest, buf, msgsiz, flags);
}
#define TABLE_DUMP_MINSIZ (2 + 2 /*+ PFX*/ + 1 + 1 + 4 /*+ IP*/ + 2 + 2)
Mrtribent *Bgp_GetMrtRibHdr(Mrtrecord *rec)
{
Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_TABLE_DUMP) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
size_t len = beswap32(hdr->len);
size_t siz = TABLE_DUMP_MINSIZ - 2; // do not include attribute length
switch (hdr->subtype) {
case AFI_IP: siz += 2 * IPV4_SIZE; break;
case AFI_IP6: siz += 2 * IPV6_SIZE; break;
default:
Bgp_SetErrStat(BGPEAFIUNSUP);
return NULL;
}
if (len < siz + 2) { // include attribute length in size check
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
// NOTE: TABLE_DUMP has no extended timestamp variant
Mrtribent *ent = (Mrtribent *) (hdr + 1);
Uint16 attrLen;
memcpy(&attrLen, (Uint8 *) ent + siz, sizeof(attrLen));
siz += 2; // now include offset
siz += beswap16(attrLen);
if (len < siz) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
return (Mrtribent *) (hdr + 1);
}
Zebrahdr *Bgp_GetZebraHdr(Mrtrecord *rec, size_t *nbytes)
{
Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_BGP) {
Bgp_SetErrStat(BGPEBADMRTTYPE);
return NULL;
}
size_t len = beswap32(hdr->len);
size_t offset = 2 + IPV4_SIZE;
if (ZEBRA_ISMESSAGE(hdr->subtype))
offset += 2 + IPV4_SIZE;
else if (hdr->subtype == ZEBRA_STATE_CHANGE)
offset += 2 * 2;
if (len < offset) {
Bgp_SetErrStat(BGPETRUNCMRT);
return NULL;
}
Bgp_SetErrStat(BGPENOERR);
if (nbytes)
*nbytes = offset;
// NOTE: Legacy ZEBRA type doesn't have extended timestamp variants
return (Zebrahdr *) (hdr + 1);
}
#define ZEBRA_MSGSIZ (2uLL + IPV4_SIZE + 2uLL + IPV4_SIZE)
Judgement Bgp_UnwrapZebra(Mrtrecord *rec, Bgpmsg *dest, unsigned flags)
{
Mrthdr *hdr = MRT_HDR(rec);
if (hdr->type != MRT_BGP)
return Bgp_SetErrStat(BGPEBADMRTTYPE);
// Resolve BGP type from ZEBRA subtype
BgpType type;
switch (hdr->subtype) {
case ZEBRA_UPDATE: type = BGP_UPDATE; break;
case ZEBRA_OPEN: type = BGP_OPEN; break;
case ZEBRA_KEEPALIVE: type = BGP_KEEPALIVE; break;
case ZEBRA_NOTIFY: type = BGP_NOTIFICATION; break;
default: return Bgp_SetErrStat(BGPEBADMRTTYPE);
}
Zebramsghdr *zebra = (Zebramsghdr *) (hdr + 1); // NOTE: ZEBRA doesn't have extended timestamp variants
size_t len = beswap32(hdr->len);
if (len < ZEBRA_MSGSIZ)
return Bgp_SetErrStat(BGPETRUNCMRT);
// ZEBRA dumps don't include BGP message header
size_t zebralen = len - ZEBRA_MSGSIZ;
size_t msglen = BGP_HDRSIZ + zebralen;
// Validate message size
if (msglen > BGP_EXMSGSIZ || (msglen > BGP_MSGSIZ && !BGP_ISEXMSG(flags)))
return Bgp_SetErrStat(BGPEOVRSIZ);
// Allocate new message
const MemOps *ops = BGP_MEMOPS(dest);
Bgphdr *msg = (Bgphdr *) ops->Alloc(dest->allocp, msglen, NULL);
if (!msg)
return Bgp_SetErrStat(BGPENOMEM);
// Build a valid BGP header
memset(msg->marker, 0xff, sizeof(msg->marker));
msg->len = beswap16(msglen);
msg->type = type;
memcpy(msg + 1, zebra->msg, zebralen);
// Populate `dest`
dest->buf = (Uint8 *) msg;
dest->flags = flags & BGPF_EXMSG; // only acceptable flag
BGP_CLRATTRTAB(dest->table);
return Bgp_SetErrStat(BGPENOERR);
}
void Bgp_ClearMrt(Mrtrecord *rec)
{
const MemOps *memOps = MRT_MEMOPS(rec);
memOps->Free(rec->allocp, rec->buf);
memOps->Free(rec->allocp, rec->peerOffTab);
rec->buf = NULL;
rec->peerOffTab = NULL;
}

100
lonetix/bgp/parameters.c Normal file
View File

@@ -0,0 +1,100 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/parameters.c
*
* Deals with BGP OPEN parameters.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
Judgement Bgp_StartMsgParms(Bgpparmiter *it, Bgpmsg *msg)
{
const Bgpopen *open = Bgp_GetMsgOpen(msg);
if (!open)
return NG; // error already set
Bgp_StartParms(it, BGP_OPENPARMS(open));
return OK; // error already cleared
}
void Bgp_StartParms(Bgpparmiter *it, const Bgpparmseg *p)
{
it->ptr = (Uint8 *) p->parms;
it->base = it->ptr;
it->lim = it->base + p->len;
}
Bgpparm *Bgp_NextParm(Bgpparmiter *it)
{
if (it->ptr >= it->lim) {
Bgp_SetErrStat(BGPENOERR);
return NULL;
}
Bgpparm *p = (Bgpparm *) it->ptr;
size_t left = it->lim - it->ptr;
if (left < 2uLL || left < 2uLL + p->len) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
it->ptr += 2 + p->len;
Bgp_SetErrStat(BGPENOERR);
return p;
}
Judgement Bgp_StartMsgCaps(Bgpcapiter *it, Bgpmsg *msg)
{
if (Bgp_StartMsgParms(&it->pi, msg) != OK)
return NG; // error already set
// Set starting point so Bgp_NextCap() scans for next parameter
it->base = it->lim = it->ptr = it->pi.ptr;
return OK;
}
void Bgp_StartCaps(Bgpcapiter *it, const Bgpparmseg *parms)
{
Bgp_StartParms(&it->pi, parms);
// Set starting point so Bgp_NextCap() scans for next parameter
it->base = it->lim = it->ptr = it->pi.ptr;
}
Bgpcap *Bgp_NextCap(Bgpcapiter *it)
{
if (it->ptr >= it->lim) {
// Try to find another CAPABILITY parameter
Bgpparm *p;
while ((p = Bgp_NextParm(&it->pi)) != NULL) {
if (p->code == BGP_PARM_CAPABILITY)
break;
}
if (!p) {
// Scanned all parameters, nothing found
Bgp_SetErrStat(BGPENOERR);
return NULL;
}
// Setup for reading new capabilities from `p`
it->base = (Uint8 *) p + 2;
it->lim = it->base + p->len;
it->ptr = it->base;
}
// Return current capability and move over
size_t left = it->lim - it->ptr;
Bgpcap *cap = (Bgpcap *) it->ptr;
if (left < 2uLL || left < 2uLL + cap->len) {
Bgp_SetErrStat(BGPETRUNCMSG);
return NULL;
}
it->ptr += 2 + cap->len;
Bgp_SetErrStat(BGPENOERR);
return cap;
}

518
lonetix/bgp/patricia.c Normal file
View File

@@ -0,0 +1,518 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/patricia.c
*
* Implements PATRICIA trie utilities.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/patricia.h"
#include "sys/sys.h" // for Sys_OutOfMemory()
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define PAT_BLOCK_ALIGN_SHIFT 2
#define PAT_BLOCK_ALIGN (1uLL << PAT_BLOCK_ALIGN_SHIFT)
#define PAT_BLOCK_SIZE 2048
struct Patblock {
Patblock *nextBlock;
Uint32 freeOfs;
ALIGNED(PAT_BLOCK_ALIGN, Uint8 buf[PAT_BLOCK_SIZE]);
};
/**
* \brief Trie node, contains node and prefix info.
*
* A node may be either:
* - Used - node is in use and part of the trie.
* - Unused - node is inside free node list and may be reclaimed on further
* node insertion.
*/
union Patnode {
// Following struct is significant when node is used.
struct {
Patnode *parent; // NOTE: LSB used to mark glue nodes
Patnode *children[2];
// Prefix part follows...
Uint8 width;
Uint8 bytes[FLEX_ARRAY];
};
// Following field is significant when node is unused.
Patnode *nextFree;
};
static Patnode *Pat_NodeForPrefix(const RawPrefix *pfx)
{
return (Patnode *) ((Uint8 *) pfx - offsetof(Patnode, width));
}
static Boolean Pat_IsNodeGlue(const Patnode *n)
{
return (((Uintptr) n->parent) & 1uLL) != 0;
}
static Patnode *Pat_GetNodeParent(const Patnode *n)
{
return (Patnode *) ((Uintptr) n->parent & ~1uLL);
}
static void Pat_SetNodeParent(Patnode *n, Patnode *parent)
{
n->parent = (Patnode *) ((Uintptr) parent | (((Uintptr) n->parent) & 1uLL));
}
static void Pat_SetNodeGlue(Patnode *n)
{
n->parent = (Patnode *) ((Uintptr) n->parent | 1uLL);
}
static void Pat_ResetNodeGlue(Patnode *n)
{
n->parent = (Patnode *) ((Uintptr) n->parent & ~1uLL);
}
static Patnode *Pat_AllocNode(Patricia *trie, Uint8 width)
{
Patnode *n;
Patblock *block;
size_t siz, len;
unsigned idx;
// Calculate block size and lookup inside free cache
len = PFXLEN(width);
assert(len <= IPV6_SIZE);
idx = len >> PAT_BLOCK_ALIGN_SHIFT;
siz = ALIGN(offsetof(Patnode, bytes[len]), PAT_BLOCK_ALIGN);
n = trie->freeBins[idx];
if (n) {
// ...free list cache hit
trie->freeBins[idx] = n->nextFree;
goto return_node;
}
// Need to allocate a new node
block = trie->blocks;
if (!block || block->freeOfs + siz > PAT_BLOCK_SIZE) {
// Must allocate a new block altoghether
block = (Patblock *) malloc(sizeof(*block));
if (!block) {
Sys_OutOfMemory();
return NULL; // too bad...
}
block->freeOfs = 0;
block->nextBlock = trie->blocks;
trie->blocks = block;
}
n = (Patnode *) (block->buf + block->freeOfs);
block->freeOfs += siz;
return_node:
n->parent = NULL;
n->children[0] = n->children[1] = NULL;
n->width = width;
return n;
}
static void Pat_FreeNode(Patricia *trie, Patnode *n)
{
// Place inside free cache bins
unsigned idx = PFXLEN(n->width) >> PAT_BLOCK_ALIGN_SHIFT;
n->nextFree = trie->freeBins[idx];
trie->freeBins[idx] = n;
}
RawPrefix *Pat_Insert(Patricia *trie, const RawPrefix *pfx)
{
Patnode *n;
unsigned maxWidth;
assert(trie->afi == AFI_IP || trie->afi == AFI_IP6);
maxWidth = (trie->afi == AFI_IP6) ? IPV6_WIDTH : IPV4_WIDTH;
assert(pfx->width <= maxWidth);
n = trie->head;
if (!n) {
// First node ever, create trie head node
n = Pat_AllocNode(trie, pfx->width);
if (!n)
return NULL;
memcpy(n->bytes, pfx->bytes, PFXLEN(pfx->width));
// Place it in `trie`
trie->head = n;
trie->nprefixes++;
return PLAINPFX(n);
}
while (n->width < pfx->width || Pat_IsNodeGlue(n)) {
int bit = (n->width < maxWidth) &&
(pfx->bytes[n->width >> 3] & (0x80 >> (n->width & 0x07)));
if (!n->children[bit])
break;
n = n->children[bit];
}
unsigned checkBit = MIN(n->width, pfx->width);
unsigned differBit = 0;
#if 1
// unoptimized version
unsigned r;
for (unsigned i = 0, z = 0; z < checkBit; i++, z += 8) {
r = (pfx->bytes[i] ^ n->bytes[i]);
if (r == 0) {
differBit = z + 8;
continue;
}
unsigned j;
for (j = 0; j < 8; j++)
if (r & (0x80 >> j))
break;
differBit = z + j;
break;
}
#else
/* TODO possible optimization:
* example 32 bit portion with different endianness:
LSB (visit using LSB->MSB) MSB (LE)
01000000 00000000 00000100 00000000
MSB (visit using MSB->LSB) LSB (BE)
leftmost bit is:
-> 32 - bsr32() (BE)
-> bsf32() - 1 (LE)
*/
for (unsigned i = 0, z = 0; z < checkBit; i++, z += 32) {
Uint32 r = (pfx->u32[i] ^ n->u32[i]);
if (r == 0) {
differBit = z + 32;
continue;
}
unsigned j = (EDN_NATIVE == EDN_BIG) ? 32 - bsr32(r) : bsf32(r) - 1; // clz(beswap32(r));
differBit = z + j;
break;
}
#endif
if (differBit > checkBit)
differBit = checkBit;
Patnode *parent = Pat_GetNodeParent(n);
while (parent && parent->width >= differBit) {
n = parent;
parent = Pat_GetNodeParent(n);
}
if (differBit == pfx->width && n->width == pfx->width) {
if (Pat_IsNodeGlue(n)) {
// Replace glue node
Pat_ResetNodeGlue(n);
memcpy(n->bytes, pfx->bytes, PFXLEN(pfx->width));
}
trie->nprefixes++;
return PLAINPFX(n);
}
// Must allocate new node
Patnode *newNode = Pat_AllocNode(trie, pfx->width);
if (!newNode)
return NULL; // out of memory
memcpy(newNode->bytes, pfx->bytes, PFXLEN(pfx->width));
trie->nprefixes++;
if (n->width == differBit) {
Pat_SetNodeParent(newNode, n);
int bit = (n->width < maxWidth) &&
(pfx->bytes[n->width >> 3] & (0x80 >> (n->width & 0x07)));
n->children[bit] = newNode;
return PLAINPFX(newNode);
}
if (pfx->width == differBit) {
int bit = (pfx->width < maxWidth) &&
(n->bytes[pfx->width >> 3] & (0x80 >> (pfx->width & 0x07)));
newNode->children[bit] = n;
Pat_SetNodeParent(newNode, n);
parent = Pat_GetNodeParent(n);
if (!parent)
trie->head = newNode;
else if (parent->children[1] == n) {
int bit = (parent->children[1] == n);
parent->children[bit] = n;
}
Pat_SetNodeParent(n, newNode);
} else {
Patnode *glue = Pat_AllocNode(trie, differBit);
if (!glue)
return NULL;
parent = Pat_GetNodeParent(n);
glue->parent = parent;
Pat_SetNodeGlue(glue);
int bit = (differBit < maxWidth) &&
(pfx->bytes[differBit >> 3] & (0x80 >> (differBit & 0x07)));
glue->children[bit] = newNode;
glue->children[!bit] = n;
newNode->parent = glue;
if (!parent)
trie->head = glue;
else {
int bit = (parent->children[1] == n);
parent->children[bit] = glue;
}
Pat_SetNodeParent(n, glue);
}
return PLAINPFX(newNode);
}
static Boolean Ip_CompWithMask(const Uint8 *a, const Uint8 *b, Uint8 mask)
{
unsigned n = mask / 8;
if (memcmp(a, b, n) == 0) {
unsigned m = ~0u << (8 - (mask % 8));
if ((mask & 0x7) == 0 || (a[n] & m) == (b[n] & m))
return TRUE;
}
return FALSE;
}
RawPrefix *Pat_SearchExact(const Patricia *trie, const RawPrefix *pfx)
{
const Patnode *n = trie->head;
if (!n)
return NULL;
while (n->width < pfx->width) {
int bit = (pfx->bytes[n->width >> 3] & (0x80 >> (n->width & 0x07))) != 0;
n = n->children[bit];
if (!n)
return NULL;
}
if (n->width > pfx->width || Pat_IsNodeGlue(n))
return NULL;
if (Ip_CompWithMask(n->bytes, pfx->bytes, pfx->width))
return PLAINPFX(n);
return NULL;
}
Boolean Pat_IsSubnetOf(const Patricia *trie, const RawPrefix *pfx)
{
const Patnode *n = trie->head;
while (n && n->width < pfx->width) {
if (!Pat_IsNodeGlue(n))
return Ip_CompWithMask(n->bytes, pfx->bytes, n->width);
int bit = (pfx->bytes[n->width >> 3] & (0x80 >> (n->width & 0x07))) != 0;
n = n->children[bit];
}
return n && !Pat_IsNodeGlue(n) &&
n->width <= pfx->width &&
Ip_CompWithMask(n->bytes, pfx->bytes, pfx->width);
}
Boolean Pat_IsSupernetOf(const Patricia *trie, const RawPrefix *pfx)
{
Patnode *start = trie->head;
while (start && start->width < pfx->width) {
int bit = (pfx->bytes[start->width >> 3] & (0x80 >> (start->width & 0x07))) != 0;
start = start->children[bit];
}
Patnode *node;
Patnode *stack[128+1];
Patnode **sp = stack;
Patnode *next = start;
while ((node = next) != NULL) {
if (!Pat_IsNodeGlue(node)) {
if (Ip_CompWithMask(node->bytes, pfx->bytes, pfx->width))
return TRUE;
break;
}
if (next->children[0]) {
if (next->children[1])
*sp++ = next->children[1];
next = next->children[0];
} else if (next->children[1]) {
next = next->children[1];
} else if (sp != stack) {
next = *(sp--);
} else {
next = NULL;
}
}
return FALSE;
}
Boolean Pat_IsRelatedOf(const Patricia *trie, const RawPrefix *pfx)
{
Patnode *start = trie->head;
while (start && start->width < pfx->width) {
if (!Pat_IsNodeGlue(start) && Ip_CompWithMask(start->bytes, pfx->bytes, start->width))
return TRUE;
int bit = (pfx->bytes[start->width >> 3] & (0x80 >> (start->width & 0x07))) != 0;
start = start->children[bit];
}
Patnode *node;
Patnode *stack[128+1];
Patnode **sp = stack;
Patnode *next = start;
while ((node = next) != NULL) {
if (!Pat_IsNodeGlue(node) && Ip_CompWithMask(node->bytes, pfx->bytes, pfx->width))
return TRUE;
if (next->children[0]) {
if (next->children[1])
*sp++ = next->children[1];
next = next->children[0];
} else if (next->children[1]) {
next = next->children[1];
} else if (sp != stack) {
next = *(sp--);
} else {
next = NULL;
}
}
return FALSE;
}
Boolean Pat_Remove(Patricia *trie, const RawPrefix *pfx)
{
RawPrefix *res = Pat_SearchExact(trie, pfx);
if (!res)
return FALSE;
Patnode *n = Pat_NodeForPrefix(res);
if (!n)
return FALSE;
trie->nprefixes--;
if (n->children[0] && n->children[1]) {
Pat_SetNodeGlue(n);
return TRUE;
}
Patnode *parent, *pparent;
Patnode *child;
int bit;
parent = Pat_GetNodeParent(n);
if (!n->children[0] && !n->children[1]) {
Pat_FreeNode(trie, n);
if (!parent) {
trie->head = NULL;
return TRUE;
}
bit = (parent->children[1] == n);
parent->children[bit] = NULL;
child = parent->children[!bit];
if (!Pat_IsNodeGlue(parent))
return TRUE;
// If here, then parent is glue, we need to remove them both
pparent = Pat_GetNodeParent(parent);
if (!pparent) {
trie->head = child;
} else {
bit = (pparent->children[1] == parent);
pparent->children[bit] = child;
}
Pat_SetNodeParent(child, pparent);
Pat_FreeNode(trie, parent);
return TRUE;
}
bit = (n->children[1] != NULL);
child = n->children[bit];
Pat_SetNodeParent(child, parent);
Pat_FreeNode(trie, n);
if (!parent) {
trie->head = child;
return TRUE;
}
bit = (parent->children[1] == n);
parent->children[bit] = child;
return TRUE;
}
void Pat_Clear(Patricia *trie)
{
while (trie->blocks) {
Patblock *t = trie->blocks;
trie->blocks = t->nextBlock;
free(t);
}
trie->afi = 0;
trie->nprefixes = 0;
trie->head = NULL;
memset(trie->freeBins, 0, sizeof(trie->freeBins));
}

191
lonetix/bgp/prefix.c Normal file
View File

@@ -0,0 +1,191 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/prefix.c
*
* Deal with network prefixes.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "sys/endian.h"
#include "sys/ip.h"
#include "numlib.h"
#include <assert.h>
#include <string.h>
// ===========================================================================
// Some performance oriented macros to avoid branching during prefix iteration
/// Calculate the minimum size of a possibly ADD_PATH enabled prefix.
#define MINPFXSIZ(isAddPath) \
((((isAddPath) != 0) << 2) + 1)
/// Extract the prefix portion out of a possibly ADD_PATH enabled prefix pointer.
#define RAWPFXPTR(base, isAddPath) \
((RawPrefix *) ((Uint8 *) (base) + (((isAddPath) != 0) << 2)))
/// Calculate maximum prefix width in bits given an address family
#define MAXPFXWIDTH(family) (((family) == AFI_IP6) ? IPV6_WIDTH : IPV4_WIDTH) // simple CMOV
// ===========================================================================
char *Bgp_PrefixToString(Afi afi, const RawPrefix *prefix, char *dest)
{
Ipv4adr adr;
Ipv6adr adr6;
switch (afi) {
case AFI_IP:
memset(&adr, 0, sizeof(adr));
memcpy(&adr, prefix->bytes, PFXLEN(prefix->width));
dest = Ipv4_AdrToString(&adr, dest);
break;
case AFI_IP6:
memset(&adr6, 0, sizeof(adr6));
memcpy(&adr6, prefix->bytes, PFXLEN(prefix->width));
dest = Ipv6_AdrToString(&adr6, dest);
break;
default:
return NULL; // invalid argument
}
*dest++ = '/';
dest = Utoa(prefix->width, dest);
return dest;
}
char *Bgp_ApPrefixToString(Afi afi, const ApRawPrefix *prefix, char *dest)
{
// NOTE: Test early to avoid polluting `dest` in case of invalid argument;
// hopefully compilers will flatten this function to
// eliminate duplicate test inside switch
if (afi != AFI_IP && afi != AFI_IP6)
return NULL; // invalid argument
dest = Utoa(beswap32(prefix->pathId), dest);
*dest++ = ' ';
return Bgp_PrefixToString(afi, PLAINPFX(prefix), dest);
}
Judgement Bgp_StringToPrefix(const char *s, Prefix *dest)
{
Ipadr adr;
unsigned width;
NumConvRet res;
const char *ptr = s;
while (*ptr != '/' && *ptr != '\0') ptr++;
size_t len = ptr - s;
char *buf = (char *) alloca(len + 1);
memcpy(buf, s, len);
buf[len] = '\0';
if (Ip_StringToAdr(buf, &adr) != OK)
return NG; // Bad IP string
if (*ptr == '/') {
ptr++; // skip '/' separator
char *eptr;
width = Atou(ptr, &eptr, 10, &res);
if (res != NCVENOERR || *eptr != '\0')
return NG;
} else
width = (adr.family == IP6) ? IPV6_WIDTH : IPV4_WIDTH; // implicit full prefix
switch (adr.family) {
case IP4:
if (width > IPV4_WIDTH) return NG; // illegal prefix length
dest->afi = AFI_IP;
break;
case IP6:
if (width > IPV6_WIDTH) return NG; // illegal prefix length
dest->afi = AFI_IP6;
break;
default:
UNREACHABLE;
return NG;
}
dest->isAddPath = FALSE;
dest->width = width;
memcpy(dest->bytes, adr.bytes, PFXLEN(width));
return OK;
}
Judgement Bgp_StartPrefixes(Prefixiter *it,
Afi afi,
Safi safi,
const void *data,
size_t nbytes,
Boolean isAddPath)
{
if (afi != AFI_IP && afi != AFI_IP6) {
Bgp_SetErrStat(BGPEAFIUNSUP);
return NG;
}
if (safi != SAFI_UNICAST && safi != SAFI_MULTICAST) {
Bgp_SetErrStat(BGPESAFIUNSUP);
return NG;
}
it->afi = afi;
it->safi = safi;
it->isAddPath = isAddPath;
it->base = (Uint8 *) data;
it->lim = it->base + nbytes;
it->ptr = it->base;
Bgp_SetErrStat(BGPENOERR);
return OK;
}
void *Bgp_NextPrefix(Prefixiter *it)
{
if (it->ptr >= it->lim) {
Bgp_SetErrStat(BGPENOERR);
return NULL; // end of iteration
}
// Basic check for prefix initial bytes
size_t left = it->lim - it->ptr;
size_t siz = MINPFXSIZ(it->isAddPath);
if (left < siz) {
Bgp_SetErrStat(BGPETRUNCPFX);
return NULL;
}
// Adjust a pointer to skip Path identifier info if necessary
const RawPrefix *rawPfx = RAWPFXPTR(it->ptr, it->isAddPath);
if (rawPfx->width > MAXPFXWIDTH(it->afi)) {
Bgp_SetErrStat(BGPEBADPFXWIDTH);
return NULL;
}
// Ensure all the necessary prefix bytes are present
siz += PFXLEN(rawPfx->width);
if (left < siz) {
Bgp_SetErrStat(BGPETRUNCPFX);
return NULL;
}
// All good, advance and return
void *pfx = it->ptr;
it->ptr += siz;
Bgp_SetErrStat(BGPENOERR);
return pfx;
}

838
lonetix/bgp/vm.c Normal file
View File

@@ -0,0 +1,838 @@
// 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;
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;
return Bgp_SetErrStat(BGPENOERR);
}
Judgement Bgp_VmEmit(Bgpvm *vm, Bgpvmbytec bytec)
{
assert(!vm->isRunning);
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
size_t newSiz = vm->progCap + BGP_VM_GROWPROGN;
Bgpvmbytec *newProg = (Bgpvmbytec *) realloc(vm->prog, 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)
{
assert(!vm->isRunning);
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)
{
assert(vm->isRunning);
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)
{
assert(vm->isRunning);
size = ALIGN(size, ALIGNMENT);
assert(size + vm->hHighMark <= vm->hMemSiz);
vm->hHighMark += size;
}
Boolean Bgp_VmExec(Bgpvm *vm, Bgpmsg *msg)
{
// Fundamental sanity checks
assert(!vm->isRunning);
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 CFAIL says 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;
vm->errCode = BGPENOERR;
// 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
vm->isRunning = TRUE;
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);
EXPECT(CFAIL, ir, vm);
EXPECT(CPASS, ir, vm);
break;
EXECUTE(CFAIL):
if (Bgp_VmDoCfail(vm)) {
result = FALSE; // immediate terminate on FAIL
goto terminate;
}
break;
EXECUTE(CPASS):
if (Bgp_VmDoCpass(vm))
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(FASMTC):
Bgp_VmDoFasmtc(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:
vm->curMatch = NULL; // prevent accidental access outside Bgp_VmExec()
vm->isRunning = FALSE;
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)
{
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)
{
assert(vm->isRunning);
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_VmDoCpass(Bgpvm *vm)
{
/* POPS:
* -1: Last operation Boolean result (as a Sint64, 0 for FALSE)
*
* PUSHES:
* * On PASS result:
* - TRUE
* * Otherwise:
* - Nothing.
*
* SIDE-EFFECTS:
* - Breaks current BLK on PASS
* - Updates `curMatch->isPassing` flag accordingly
*/
if (!BGP_VMCHKSTKSIZ(vm, 1)) UNLIKELY
return FALSE; // error, let vm->errCode handle this
Boolean shouldTerm = FALSE; // unless proven otherwise
// If stack top is non-zero we FAIL
if (BGP_VMPEEK(vm, -1)) {
// Leave TRUE on stack and break current BLK, this is a PASS
vm->curMatch->isPassing = TRUE;
if (vm->nblk > 0)
Bgp_VmDoBreak(vm);
else
shouldTerm = TRUE; // no more BLK
} else {
// Pop the stack and move on, no PASS
vm->curMatch->isPassing = FALSE;
BGP_VMPOP(vm);
}
// Current match information has been collected,
// discard anything up to the next relevant operation
vm->curMatch = &discardMatch;
return shouldTerm;
}
Boolean Bgp_VmDoCfail(Bgpvm *vm)
{
/* POPS:
* -1: Last operation Boolean result (as a Sint64, 0 for FALSE)
*
* PUSHES:
* * On FAIL result:
* - FALSE
* * Otherwise:
* - Nothing.
*
* SIDE-EFFECTS:
* - Breaks current BLK on FAIL
* - Updates `curMatch->isPassing` flag accordingly
*/
if (!BGP_VMCHKSTKSIZ(vm, 1)) UNLIKELY
return FALSE; // error, let vm->errCode handle this
Boolean shouldTerm = FALSE; // unless proven otherwise
// If stack top is non-zero we FAIL
Bgpvmval *v = BGP_VMSTKGET(vm, -1);
if (v->val) {
// Push FALSE and break current BLK, this is a FAIL
vm->curMatch->isPassing = FALSE;
v->val = FALSE;
if (vm->nblk > 0)
Bgp_VmDoBreak(vm);
else
shouldTerm = TRUE; // no more BLK
} else {
// Pop the stack and move on, no FAIL
vm->curMatch->isPassing = TRUE;
BGP_VMPOP(vm);
}
// Current match information has been collected,
// discard anything up to the next relevant operation
vm->curMatch = &discardMatch;
return shouldTerm;
}
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_VMPUSH(vm, isMatching);
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)
{
assert(!vm->isRunning);
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)
{
assert(!vm->isRunning);
free(vm->heap);
free(vm->prog);
}

834
lonetix/bgp/vm_asmtch.c Normal file
View File

@@ -0,0 +1,834 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_asmtch.c
*
* Implements ASMTCH and FASMTC, and ASN match IR compilation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* This code is a modified version of the Plan9 regexp library matching algorithm.
* The Plan9 regexp library is available under the Lucent Public License
* at: https://9fans.github.io/plan9port/unix/libregexp9.tgz
*
* \see [Regular Expression Matching Can Be Simple And Fast](https://swtch.com/~rsc/regexp/regexp1.html)
*/
#include "bgp/vmintrin.h"
#include "sys/endian.h"
#include <assert.h>
#include <setjmp.h>
#include <string.h>
// Define this to dump the compiled AS MATCH expressions to stderr
//#define DF_DEBUG_ASMTCH
#ifdef NDEBUG
// Force DF_DEBUG_ASMTCH off on release build
#undef DF_DEBUG_ASMTCH
#endif
#ifdef DF_DEBUG_ASMTCH
#include "sys/con.h"
#endif
#define AS 126
#define NAS 127
#define START 128 // start, used for marker on stack
#define RPAR 129 // right parens, )
#define LPAR 130 // left parens, (
#define ALT 131 // alternation, |
#define CAT 132 // concatentation, implicit operator
#define STAR 133 // closure, *
#define PLUS 134 // a+ == aa*
#define QUEST 135 // a? == a|nothing, i.e. 0 or 1 a's
#define ANY 192 // any character except newline, .
#define NOP 193 // no operation
#define BOL 194 // beginning of line, ^
#define EOL 195 // end of line, $
#define STOP 255 // terminate: match found
#define BOTTOM (START - 1)
#define NSTACK 32
#define LISTSIZ 10
#define BIGLISTSIZ (32 * LISTSIZ)
#define ASNBOLFLAG BIT(60) // used to mark the initial ASN, so that BOL operator works
#define ASN_EOL -1 // equals to -1 returned by Bgp_NextAsPath()
// Automaton instruction.
typedef struct Nfainst Nfainst;
struct Nfainst{
int type; // instruction type, any of the macros above
union { // NOTE: keep `next` and `left` in the same union!
Nfainst *next; // next instruction in chain
Nfainst *left; // left output state, for ALT instructions
};
union {
Uint32 asn; // ASN match, for AS/NAS instructions
Uint16 grpid; // match group id, for LPAR/RPAR instructions
Nfainst *right; // right output state, for ALT instructions
};
};
// A block of instructions (used during NFA construction)
typedef struct Nfanode Nfanode;
struct Nfanode {
Nfainst *first, *last;
};
// Start and end position of a subexpression match
typedef struct {
Aspathiter spos;
Aspathiter epos;
} Nfamatch;
typedef struct Nfastate Nfastate;
struct Nfastate {
Nfainst *ip;
Nfamatch se[MAXBGPVMASNGRP];
};
// State list used during simulation (clist, nlist)
typedef struct Nfalist Nfalist;
struct Nfalist {
unsigned ns, lim;
Nfastate list[FLEX_ARRAY];
};
typedef struct Nfacomp Nfacomp;
struct Nfacomp {
Nfainst *basep; // base instruction pointer
Nfainst *freep; // next free instruction pointer
Nfanode andstack[NSTACK]; // operands stack
Uint16 grpidstack[NSTACK]; // subexpression id stack
Uint8 opstack[NSTACK]; // operators stack
Boolean8 lastWasAnd; // whether last encountered term was an operand
Uint16 nands, nops, ngrpids; // counters inside stacks
Uint16 nparens; // currently encountered open parens counter
Uint16 curgrpid; // next group id
jmp_buf oops; // compilation error jump
};
typedef struct Nfa Nfa;
struct Nfa {
Aspathiter spos; // Current ASN iterator start position
Aspathiter cur; // Current ASN iterator position
unsigned nmatches;
Nfamatch se[MAXBGPVMASNGRP];
};
static NORETURN void comperr(Nfacomp *nc, BgpvmRet err)
{
assert(err != BGPENOERR);
longjmp(nc->oops, err);
}
static Nfainst *newinst(Nfacomp *nc, int t)
{
Nfainst *i = nc->freep++;
i->type = t;
i->left = i->right = NULL;
return i;
}
static void pushator(Nfacomp *nc, Asn t)
{
if (nc->nops == NSTACK)
comperr(nc, BGPEVMBADASMTCH);
nc->opstack[nc->nops++] = t;
nc->grpidstack[nc->ngrpids++] = nc->curgrpid;
}
static Nfanode *pushand(Nfacomp *nc, Nfainst *f, Nfainst *l)
{
if (nc->nands == NSTACK)
comperr(nc, BGPEVMBADASMTCH);
Nfanode *n = &nc->andstack[nc->nands++];
n->first = f;
n->last = l;
return n;
}
static Nfanode *popand(Nfacomp *nc)
{
if(nc->nands == 0)
comperr(nc, BGPEVMBADASMTCH);
return &nc->andstack[--nc->nands];
}
static Uint8 popator(Nfacomp *nc)
{
if (nc->nops == 0)
comperr(nc, BGPEVMBADASMTCH);
--nc->ngrpids;
return nc->opstack[--nc->nops];
}
static void evaluntil(Nfacomp *nc, int prio)
{
Nfanode *op1, *op2;
Nfainst *inst1, *inst2;
while (prio == RPAR || nc->opstack[nc->nops-1] >= prio) {
switch (popator(nc)) {
default: UNREACHABLE;
case LPAR:
op1 = popand(nc);
inst2 = newinst(nc, RPAR);
inst2->grpid = nc->grpidstack[nc->ngrpids-1];
op1->last->next = inst2;
inst1 = newinst(nc, LPAR);
inst1->grpid = nc->grpidstack[nc->ngrpids-1];
inst1->next = op1->first;
pushand(nc, inst1, inst2);
return;
case ALT:
op2 = popand(nc), op1 = popand(nc);
inst2 = newinst(nc, NOP);
op2->last->next = inst2;
op1->last->next = inst2;
inst1 = newinst(nc, ALT);
inst1->right = op1->first;
inst1->left = op2->first;
pushand(nc, inst1, inst2);
break;
case CAT:
op2 = popand(nc), op1 = popand(nc);
op1->last->next = op2->first;
pushand(nc, op1->first, op2->last);
break;
case STAR:
op2 = popand(nc);
inst1 = newinst(nc, ALT);
op2->last->next = inst1;
inst1->right = op2->first;
pushand(nc, inst1, inst1);
break;
case PLUS:
op2 = popand(nc);
inst1 = newinst(nc, ALT);
op2->last->next = inst1;
inst1->right = op2->first;
pushand(nc, op2->first, inst1);
break;
case QUEST:
op2 = popand(nc);
inst1 = newinst(nc, ALT);
inst2 = newinst(nc, NOP);
inst1->left = inst2;
inst1->right = op2->first;
op2->last->next = inst2;
pushand(nc, inst1, inst2);
break;
}
}
}
static void operator(Nfacomp *nc, int op)
{
if (op == RPAR) {
if (nc->nparens == 0)
comperr(nc, BGPEVMBADASMTCH);
nc->nparens--;
}
if (op == LPAR) {
nc->curgrpid++;
if (nc->curgrpid == MAXBGPVMASNGRP)
comperr(nc, BGPEVMBADASMTCH);
nc->nparens++;
if (nc->lastWasAnd)
operator(nc, CAT); // add implicit CAT before group
} else
evaluntil(nc, op);
if (op != RPAR)
pushator(nc, op);
// Some operators behave like operands
nc->lastWasAnd = (op == STAR || op == QUEST || op == PLUS || op == RPAR);
}
static void operand(Nfacomp *nc, int t, Asn32 asn)
{
if (nc->lastWasAnd)
operator(nc, CAT); // add implicit CAT
Nfainst *i = newinst(nc, t);
if (t == AS || t == NAS)
i->asn = asn;
pushand(nc, i, i);
nc->lastWasAnd = TRUE;
}
static void compinit(Nfacomp *nc, Nfainst *dest)
{
nc->nands = nc->nops = nc->ngrpids = 0;
nc->nparens = 0;
nc->curgrpid = 0;
nc->lastWasAnd = FALSE;
nc->basep = nc->freep = dest;
}
static Nfainst *compile(Nfacomp *nc, const Asn *expression, size_t n)
{
pushator(nc, BOTTOM);
for (size_t i = 0; i < n; i++) {
Asn asn = expression[i];
switch (asn) {
case ASN_START: operand(nc, BOL, 0); break;
case ASN_END: operand(nc, EOL, 0); break;
case ASN_ANY: operand(nc, ANY, 0); break;
case ASN_STAR: operator(nc, STAR); break;
case ASN_QUEST: operator(nc, QUEST); break;
case ASN_PLUS: operator(nc, PLUS); break;
case ASN_NEWGRP: operator(nc, LPAR); break;
case ASN_ALT: operator(nc, ALT); break;
case ASN_ENDGRP: operator(nc, RPAR); break;
default:
if (ISASNNOT(asn)) operand(nc, NAS, ASN(asn));
else operand(nc, AS, ASN(asn));
break;
}
}
evaluntil(nc, START);
operand(nc, STOP, 0);
evaluntil(nc, START);
if (nc->nparens != 0)
comperr(nc, BGPEVMBADASMTCH);
return nc->andstack[nc->nands - 1].first;
}
static void optimize(Bgpvm *vm, Nfacomp *nc, size_t bufsiz)
{
assert(IS_ALIGNED(bufsiz, ALIGNMENT));
assert(vm->hLowMark >= bufsiz);
// Get rid of NOP chains
for (Nfainst *i = nc->basep; i->type != STOP; i++) {
Nfainst *j = i->next;
while (j->type == NOP)
j = j->next;
i->next = j;
}
// Initial program allocation is an upperbound, release excess memory
size_t siz = (nc->freep - nc->basep) * sizeof(*nc->basep);
size_t alsiz = ALIGN(siz, ALIGNMENT);
assert(alsiz <= bufsiz);
vm->hLowMark -= (bufsiz - alsiz);
assert(IS_ALIGNED(vm->hLowMark, ALIGNMENT));
}
#ifdef DF_DEBUG_ASMTCH
static void dumpprog(const Nfainst *prog)
{
const Nfainst *i = prog;
while (TRUE) {
Sys_Printf(STDERR, "%d:\t%#2x", (int) (i - prog), (unsigned) i->type);
switch (i->type) {
case ALT:
Sys_Printf(STDERR, "\t%d\t%d", (int) (i->left - prog), (int) (i->right - prog));
break;
case AS:
Sys_Printf(STDERR, "\tASN(%lu)\t%d", (unsigned long) beswap32(i->asn), (int) (i->next - prog));
break;
case NAS:
Sys_Printf(STDERR, "\t!ASN(%lu)\t%d", (unsigned long) beswap32(i->asn), (int) (i->next - prog));
break;
case LPAR: case RPAR:
Sys_Printf(STDERR, "\tGRP(%d)", (int) i->grpid);
// FALLTHROUGH
default:
Sys_Printf(STDERR, "\t%d", (int) (i->next - prog));
break;
case NOP: case STOP:
break;
}
Sys_Print(STDERR, "\n");
if (i->type == STOP)
break;
i++;
}
}
#endif
// `TRUE` if `pos` comes before `m` starting position
static Boolean isbefore(const Aspathiter *pos, const Nfamatch *m)
{
return BGP_CURASINDEX(pos) < BGP_CURASINDEX(&m->spos);
}
// `TRUE` if `a` starts at the same position as `b`, but terminates after it
static Boolean islongermatch(const Nfamatch *a, const Nfamatch *b)
{
return BGP_CURASINDEX(&a->spos) == BGP_CURASINDEX(&b->spos) &&
BGP_CURASINDEX(&a->epos) > BGP_CURASINDEX(&b->epos);
}
/* An invalid AS INDEX, there can't be a 65535 AS index,
* Given that an AS is at least 2 bytes wide and a legal TPA segment
* is at most of 64K (though in practice its even smaller)
*/
#define BADASIDX 0xffffu
static void clearmatch(Nfamatch *m)
{
m->spos.asIdx = BADASIDX;
m->epos.asIdx = 0;
}
static Boolean isnullmatch(const Nfamatch *m)
{
return BGP_CURASINDEX(&m->spos) == BADASIDX;
}
static void copymatches(Nfamatch *dest, const Nfamatch *src)
{
do *dest++ = *src; while (!isnullmatch(src++));
}
static Boolean addstartinst(Nfalist *clist, Nfainst *start, const Nfa *nfa)
{
Nfastate *s;
// Don't add the instruction twice
for (unsigned i = 0; i < clist->ns; i++) {
s = &clist->list[i];
if (s->ip == start) {
if (isbefore(&nfa->spos, &s->se[0])) {
// Move match position
s->se[0].spos = nfa->spos;
s->se[0].epos.asIdx = 0; // so any end pos is accepted
clearmatch(&s->se[1]);
}
return TRUE;
}
}
if (clist->ns == clist->lim)
return FALSE;
// Append to list
s = &clist->list[clist->ns++];
s->ip = start;
s->se[0].spos = nfa->spos;
s->se[0].epos.asIdx = 0; // so any end pos is accepted
clearmatch(&s->se[1]);
return TRUE;
}
static Boolean addinst(Nfalist *nlist, Nfainst *in, const Nfamatch *se)
{
Nfastate *s;
// Don't add the same instruction twice
for (unsigned i = 0; i < nlist->ns; i++) {
s = &nlist->list[i];
if (s->ip == in) {
if (isbefore(&se[0].spos, &s->se[0]))
copymatches(s->se, se);
return TRUE;
}
}
if (nlist->ns == nlist->lim)
return FALSE; // instruction list overflow
// Append to list
s = &nlist->list[nlist->ns++];
s->ip = in;
copymatches(s->se, se);
return TRUE;
}
static void newmatch(Nfastate *s, Nfa *nfa)
{
// Accept the new match if it is the first one, or it comes before
// a previous match, or if it is a longer match than the previous one
if (nfa->nmatches == 0 ||
isbefore(&s->se[0].spos, &nfa->se[0]) ||
islongermatch(&s->se[0], &nfa->se[0])) {
copymatches(nfa->se, s->se);
}
nfa->nmatches++;
}
static Judgement step(Nfalist *clist, Asn asn, Nfalist *nlist, Nfa *nfa)
{
nlist->ns = 0;
for (unsigned i = 0; i < clist->ns; i++) {
Nfastate *s = &clist->list[i];
Nfainst *ip = s->ip;
eval_next:
switch (ip->type) {
default: UNREACHABLE;
case NOP:
ip = ip->next;
goto eval_next;
case AS:
if (ip->asn == ASN(asn) && !addinst(nlist, ip->next, s->se))
return NG;
break;
case NAS:
if (ip->asn != ASN(asn) && !addinst(nlist, ip->next, s->se))
return NG;
break;
case ANY:
if (asn != ASN_EOL && !addinst(nlist, ip->next, s->se))
return NG;
break;
case BOL:
if (asn & ASNBOLFLAG) {
ip = ip->next;
goto eval_next;
}
break;
case EOL:
if (asn == ASN_EOL) {
ip = ip->next;
goto eval_next;
}
break;
case LPAR:
for (unsigned i = 0; i < ip->grpid; i++)
assert(!isnullmatch(&s->se[i]));
assert(isnullmatch(&s->se[ip->grpid]));
s->se[ip->grpid].spos = nfa->spos;
clearmatch(&s->se[ip->grpid+1]);
ip = ip->next;
goto eval_next;
case ALT:
// Evaluate right branch later IN THIS LIST
if (!addinst(clist, ip->right, s->se))
return NG;
ip = ip->left; // take left branch now
goto eval_next;
case RPAR:
for (unsigned i = 0; i < ip->grpid; i++)
assert(!isnullmatch(&s->se[i]));
assert(!isnullmatch(&s->se[ip->grpid]));
assert( isnullmatch(&s->se[ip->grpid+1]));
s->se[ip->grpid].epos = nfa->cur;
ip = ip->next;
goto eval_next;
case STOP:
// *** MATCH ***
s->se[0].epos = nfa->cur;
newmatch(s, nfa);
break;
}
}
return BGPENOERR;
}
static void collect(Bgpvm *vm, const Nfa *nfa)
{
Boolean isMatching = (nfa->nmatches > 0);
BGP_VMPUSH(vm, isMatching);
vm->curMatch = (Bgpvmmatch *) Bgp_VmTempAlloc(vm, sizeof(*vm->curMatch));
if (!vm->curMatch)
return; // out of memory
Bgpattrseg *tpa = Bgp_GetUpdateAttributes(BGP_MSGUPDATE(vm->msg));
assert(tpa != NULL);
// Generate matches list
Bgpvmasmatch *matches = NULL;
Bgpvmasmatch **pmatches = &matches;
for (unsigned i = 0; !isnullmatch(&nfa->se[i]); i++) {
const Nfamatch *src = &nfa->se[i];
Bgpvmasmatch *dest = (Bgpvmasmatch *) Bgp_VmTempAlloc(vm, sizeof(*dest));
if (!dest)
return; // out of memory
dest->next = *pmatches;
dest->spos = src->spos;
dest->epos = src->epos;
*pmatches = dest;
pmatches = &dest->next;
}
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 = matches;
vm->curMatch->isMatching = isMatching;
}
static BgpvmRet execute(Bgpvm *vm, Nfainst *program, unsigned listlen, Nfa *nfa)
{
// Prepare AS PATH iterator
BgpvmRet err = BGPENOERR; // unless found otherwise
if (Bgp_StartMsgRealAsPath(&nfa->cur, vm->msg) != OK) {
vm->errCode = BGPEVMMSGERR;
return vm->errCode;
}
// Setup state lists
Nfalist *clist, *nlist, *t;
size_t listsiz = offsetof(Nfalist, list[listlen]);
if (listlen > LISTSIZ) {
// Allocate on temporary memory
clist = (Nfalist *) Bgp_VmTempAlloc(vm, listsiz);
nlist = (Nfalist *) Bgp_VmTempAlloc(vm, listsiz);
} else {
// Allocate on stack
clist = (Nfalist *) alloca(listsiz);
nlist = (Nfalist *) alloca(listsiz);
}
if (!clist || !nlist)
return vm->errCode;
clist->lim = nlist->lim = listlen;
// Simulate NFA, execute once per ASN (including ASN_BOL and ASN_EOL)
nfa->nmatches = 0; // clear result list
clist->ns = 0; // clear current list for the first time
clearmatch(&nfa->se[0]); // by default no match
Asn asn, flag = ASNBOLFLAG; // first ASN is marked as BOL
do {
// Copy initial position to start
nfa->spos = nfa->cur;
// Always include first instruction if no match took place yet
if (nfa->nmatches == 0 && !addstartinst(clist, program, nfa)) {
// State list overflow
err = BGPEVMASMTCHESIZE;
break;
}
// Fetch new ASN
asn = Bgp_NextAsPath(&nfa->cur);
if (asn == -1 && Bgp_GetErrStat(NULL)) {
err = BGPEVMMSGERR;
break;
}
// Advance NFA evaluating the current ASN
if (step(clist, asn | flag, nlist, nfa) != OK) {
// List overflow
err = BGPEVMASMTCHESIZE;
break;
}
t = clist, clist = nlist, nlist = t; // swap lists
flag = 0; // no more the first ASN
} while (asn != -1);
if (listlen > LISTSIZ) {
Bgp_VmTempFree(vm, listsiz);
Bgp_VmTempFree(vm, listsiz);
}
vm->errCode = err;
return err;
}
void *Bgp_VmCompileAsMatch(Bgpvm *vm, const Asn *expression, size_t n)
{
Nfainst *buf, *prog;
// NOTE: Bgp_VmPermAlloc() already clears VM error and asserts !vm->isRunning
const size_t maxsiz = ALIGN(6 * n * sizeof(*buf), ALIGNMENT);
// we request an already aligned chunk
// so optimize() can make accurate memory adjustments
buf = Bgp_VmPermAlloc(vm, maxsiz);
if (!buf)
return NULL;
Nfacomp nc;
compinit(&nc, buf);
int err;
if ((err = setjmp(nc.oops)) != 0) {
vm->errCode = err;
vm->hLowMark -= maxsiz; // release permanent allocation
return NULL;
}
prog = compile(&nc, expression, n);
optimize(vm, &nc, maxsiz);
#ifdef DF_DEBUG_ASMTCH
dumpprog(prog);
#endif
// vm->errCode = BGPENOERR; - already set by Bgp_VmPermAlloc()
return prog;
}
void Bgp_VmDoAsmtch(Bgpvm *vm)
{
/* POPS:
* -1: Asn match array length
* -2: Address to Asn match array
*
* PUSHES:
* TRUE on successful match, FALSE otherwise
*/
Nfacomp nc;
Nfa nfa;
Nfainst *buf, *prog;
if (!BGP_VMCHKSTK(vm, 2))
return;
// Pop arguments from stack
Sint64 n = BGP_VMPOP(vm);
const Asn *match = (const Asn *) BGP_VMPOPA(vm);
if (n <= 0 || match == NULL) {
vm->errCode = BGPEVMBADASMTCH;
return;
}
if (!BGP_VMCHKMSGTYPE(vm, BGP_UPDATE)) {
Bgp_VmStoreMsgTypeMatch(vm, /*isMatching=*/FALSE);
BGP_VMPUSH(vm, FALSE);
return;
}
// Compile on the fly on temporary memory
const size_t maxsiz = 6 * n * sizeof(*buf);
buf = (Nfainst *) Bgp_VmTempAlloc(vm, maxsiz);
if (!buf)
return;
compinit(&nc, buf);
int err; // compilation status
if ((err = setjmp(nc.oops)) != 0) {
vm->errCode = err;
return;
}
prog = compile(&nc, match, n);
#ifdef DF_DEBUG_ASMTCH
dumpprog(prog);
#endif
BgpvmRet status = execute(vm, prog, LISTSIZ, &nfa);
if (status == BGPEVMASMTCHESIZE)
status = execute(vm, prog, BIGLISTSIZ, &nfa);
Bgp_VmTempFree(vm, maxsiz);
if (status == BGPENOERR)
collect(vm, &nfa);
}
void Bgp_VmDoFasmtc(Bgpvm *vm)
{
/* POPS:
* -1: Precompiled NFA instructions
*
* PUSHES:
* TRUE on successful match, FALSE otherwise
*/
Nfa nfa;
if (!BGP_VMCHKSTK(vm, 1))
return;
Nfainst *prog = (Nfainst *) BGP_VMPOPA(vm);
if (!prog) {
vm->errCode = BGPEVMBADASMTCH;
return;
}
if (!BGP_VMCHKMSGTYPE(vm, BGP_UPDATE)) {
Bgp_VmStoreMsgTypeMatch(vm, /*isMatching=*/FALSE);
BGP_VMPUSH(vm, FALSE);
return;
}
BgpvmRet status = execute(vm, prog, LISTSIZ, &nfa);
if (status == BGPEVMASMTCHESIZE)
status = execute(vm, prog, BIGLISTSIZ, &nfa);
if (status == BGPENOERR)
collect(vm, &nfa);
}

33
lonetix/bgp/vm_cdef.h Normal file
View File

@@ -0,0 +1,33 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_cdef.c
*
* Portable implementation for BGP VM execution loop
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* Plain C switch based FETCH-DECODE-DISPATCH-EXECUTE BGP filtering
* engine VM implementation
*
* \note File should be `#include`d by bgp/vm.c
*/
#ifdef DF_BGP_VMDEF_H_
#error "Only one vm_<impl>def.h file may be #include-d"
#endif
#define DF_BGP_VMDEF_H_
#define LIKELY
#define UNLIKELY
#define FETCH(ir, vm) (ir = (vm)->prog[(vm)->pc++])
#define EXPECT(opcode, ir, vm) ((void) 0)
#define DISPATCH(opcode) switch (opcode)
#define EXECUTE(opcode) case BGP_VMOP_ ## opcode
#define EXECUTE_SIGILL default

103
lonetix/bgp/vm_commsort.h Normal file
View File

@@ -0,0 +1,103 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_commsort.h
*
* Generic basic sorting and binary searching over unsigned integer arrays.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* The following defines a bunch of static functions to sort
* and search basic integer arrays.
*
* `#define` `UINT_TYPE` with an unsigned <= 4 bytes and `FNSUFFIX`
* before inclusion.
*
* \note No guards, file `#include`d by bgp/vm_communities.c
*/
#define _CAT(X, Y) X ## Y
#define _XCAT(X, Y) _CAT(X, Y)
#define _MANGLE(FN) _XCAT(FN, FNSUFFIX)
static Sint64 _MANGLE(BinarySearch) (const UINT_TYPE *arr,
Uint32 n,
UINT_TYPE v)
{
Uint32 len = n;
Uint32 mid = n;
Sint64 off = 0;
while (mid > 0) {
mid = len >> 1;
if (arr[off+mid] <= v)
off += mid;
len -= mid;
}
return (off < n && arr[off] == v) ? off : -1;
}
static void _MANGLE(Radix) (int off,
const UINT_TYPE *src,
Uint32 n,
UINT_TYPE *dest)
{
const Uint8 *sortKey;
Uint32 index[256];
Uint32 count[256] = { 0 };
for (Uint32 i = 0; i < n; i++) {
sortKey = ((const Uint8 *) &src[i]) + off;
count[*sortKey]++;
}
index[0] = 0;
for (Uint32 i = 1; i < 256; i++)
index[i] = index[i-1] + count[i-1];
for (Uint32 i = 0; i < n; i++) {
sortKey = ((const Uint8 *) &src[i]) + off;
dest[index[*sortKey]++] = src[i];
}
}
static void _MANGLE(RadixSort) (UINT_TYPE *arr, Uint32 n)
{
UINT_TYPE *scratch = (UINT_TYPE *) alloca(n * sizeof(*scratch));
STATIC_ASSERT(sizeof(UINT_TYPE) % 2 == 0, "?!");
if (EDN_NATIVE == EDN_LE) {
for (unsigned i = 0; i < sizeof(UINT_TYPE); i += 2) {
_MANGLE(Radix) (i + 0, arr, n, scratch);
_MANGLE(Radix) (i + 1, scratch, n, arr);
}
} else {
for (unsigned i = sizeof(UINT_TYPE); i > 0; i -= 2) {
_MANGLE(Radix) (i - 1, arr, n, scratch);
_MANGLE(Radix) (i - 2, scratch, n, arr);
}
}
}
static Uint32 _MANGLE(Uniq) (UINT_TYPE *arr, Uint32 n)
{
Uint32 i, j;
if (n == 0) return 0;
for (i = 0, j = 1; j < n; j++) {
if (arr[i] != arr[j])
arr[++i] = arr[j];
}
return ++i;
}
#undef _MANGLE
#undef _XCAT
#undef _CAT

View File

@@ -0,0 +1,442 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_communities.c
*
* BGP VM COMTCH, ACOMTC instructions and COMMUNITY index.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/bgp_local.h"
#include "bgp/vmintrin.h"
#include "sys/endian.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
BgpVmOpt opt;
Uint32 hiOnlyCount;
Uint32 loOnlyCount;
Uint32 fullCount;
Uint32 bitsetWords;
Uint32 *bitset;
Uint16 *hiOnly; // parital match on community hi
Uint16 *loOnly; // partial match on community lo
Uint32 full[]; // full matches on whole community codes
// Uint32 bitset[]; <- order preserves alignment requirements
// Uint16 hi[];
// Uint16 lo[];
} Bgpcommidx;
FORCE_INLINE size_t BITSETWIDTH(const Bgpcommidx *idx)
{
return idx->hiOnlyCount + idx->loOnlyCount + idx->fullCount;
}
FORCE_INLINE size_t BITSETLEN(size_t width)
{
return (width >> 5) + ((width & 0x1f) != 0);
}
FORCE_INLINE size_t FULLBITIDX(const Bgpcommidx *idx, Uint32 i)
{
USED(idx);
return i;
}
FORCE_INLINE size_t HIBITIDX(const Bgpcommidx *idx, Uint32 i)
{
return (size_t) idx->fullCount + i;
}
FORCE_INLINE size_t LOBITIDX(const Bgpcommidx *idx, Uint32 i)
{
return (size_t) idx->fullCount + idx->hiOnlyCount + i;
}
FORCE_INLINE Boolean ISBITSET(const Uint32 *bitset, size_t idx)
{
return (bitset[idx >> 5] & (1u << (idx & 0x1f))) != 0;
}
FORCE_INLINE void SETBIT(Uint32 *bitset, size_t idx)
{
bitset[idx >> 5] |= (1u << (idx & 0x1f));
}
FORCE_INLINE void CLRBITSET(Uint32 *bitset, size_t len)
{
memset(bitset, 0, len * sizeof(*bitset));
}
#ifdef __GNUC__
FORCE_INLINE unsigned FindFirstSet(Uint32 x)
{
STATIC_ASSERT(sizeof(x) == sizeof(int), "__builtin_ffs() operates on int");
return __builtin_ffs(x);
}
#else
FORCE_INLINE unsigned FindFirstSet(Uint32 x)
{
if (x == 0) return 0;
unsigned n = 0;
if ((x & 0x0000ffffu) == 0) n += 16, x >>= 16;
if ((x & 0x000000ffu) == 0) n += 8, x >>= 8;
if ((x & 0x0000000fu) == 0) n += 4, x >>= 4;
if ((x & 0x00000003u) == 0) n += 2, x >>= 2;
if ((x & 0x00000001u) == 0) n += 1;
return ++n;
}
#endif
static size_t FFZ(const Uint32 *bitset, size_t len)
{
size_t i;
assert(len > 0);
len--;
for (i = 0; i < len && bitset[i] == 0xffffffffu; i++);
size_t n = i << 6;
n += FindFirstSet(~bitset[i]) - 1;
return n;
}
#define UINT_TYPE Uint16
#define FNSUFFIX 16
#include "bgp/vm_commsort.h"
#undef UINT_TYPE
#undef FNSUFFIX
#define UINT_TYPE Uint32
#define FNSUFFIX 32
#include "bgp/vm_commsort.h"
#undef UINT_TYPE
#undef FNSUFFIX
static Boolean MatchCommunity(const Bgpcomm *c, const Bgpcommidx *idx)
{
return BinarySearch16(idx->hiOnly, idx->hiOnlyCount, c->hi) >= 0 ||
BinarySearch16(idx->loOnly, idx->loOnlyCount, c->lo) >= 0 ||
BinarySearch32(idx->full, idx->fullCount, c->code) >= 0;
}
static void OptimizeComtch(Bgpcommidx *idx)
{
// Remove every full match more specific than an existing partial match.
// NOTE: Assumes arrays have been sorted and Uniq()d
Uint32 i, j;
Bgpcomm c;
for (i = 0, j = 0; i < idx->fullCount; i++) {
c.code = idx->full[i];
if (BinarySearch16(idx->hiOnly, idx->hiOnlyCount, c.hi) >= 0 ||
BinarySearch16(idx->loOnly, idx->loOnlyCount, c.lo) >= 0)
continue;
idx->full[j++] = idx->full[i];
}
idx->fullCount = j;
}
static void OptimizeAcomtc(Bgpcommidx *idx)
{
// Remove every partial match less specific than an existing full match
// NOTE: Assumes arrays have been sorted and Uniq()d
Uint32 i, j;
// Mark redundant entries in bitset
CLRBITSET(idx->bitset, idx->bitsetWords);
for (i = 0; i < idx->fullCount; i++) {
Sint64 pos;
Bgpcomm c;
c.code = idx->full[i];
pos = BinarySearch16(idx->hiOnly, idx->hiOnlyCount, c.hi);
if (pos >= 0)
SETBIT(idx->bitset, HIBITIDX(idx, pos));
pos = BinarySearch16(idx->loOnly, idx->loOnlyCount, c.lo);
if (pos >= 0)
SETBIT(idx->bitset, LOBITIDX(idx, pos));
}
// Remove redundant entries
for (i = 0, j = 0; i < idx->hiOnlyCount; i++) {
if (!ISBITSET(idx->bitset, HIBITIDX(idx, i)))
idx->hiOnly[j++] = idx->hiOnly[i];
}
idx->hiOnlyCount = j;
for (i = 0, j = 0; i < idx->loOnlyCount; i++) {
if (!ISBITSET(idx->bitset, LOBITIDX(idx, i)))
idx->loOnly[j++] = idx->loOnly[i];
}
idx->loOnlyCount = j;
}
static void CompactIndex(Bgpvm *vm, Bgpcommidx *idx, size_t idxSize)
{
size_t offset = offsetof(Bgpcommidx, full[idx->fullCount]);
size_t bitsetSiz = idx->bitsetWords * sizeof(*idx->bitset);
size_t hiSiz = idx->hiOnlyCount * sizeof(*idx->hiOnly);
size_t loSiz = idx->loOnlyCount * sizeof(*idx->loOnly);
Uint8 *ptr = (Uint8 *) idx + offset;
idx->bitset = (Uint32 *) memmove(ptr, idx->bitset, bitsetSiz);
ptr += bitsetSiz;
idx->hiOnly = (Uint16 *) memmove(ptr, idx->hiOnly, hiSiz);
ptr += hiSiz;
idx->loOnly = (Uint16 *) memmove(ptr, idx->loOnly, loSiz);
ptr += loSiz;
size_t siz = ptr - (Uint8 *) idx;
siz = ALIGN(siz, ALIGNMENT);
offset = idxSize - siz;
vm->hLowMark -= offset;
}
void *Bgp_VmCompileCommunityMatch(Bgpvm *vm,
const Bgpmatchcomm *match,
size_t n,
BgpVmOpt opt)
{
// NOTE: Bgp_VmPermAlloc() already clears VM error and asserts !vm->isRunning
Sint32 nlow = 0, nhigh = 0, nfull = 0, nbitswords = 0;
for (size_t i = 0; i < n; i++) {
const Bgpmatchcomm *m = &match[i];
if (m->maskLo && m->maskHi) {
vm->errCode = BGPEVMBADCOMTCH;
return NULL;
}
if (m->maskLo)
nhigh++;
else if (m->maskHi)
nlow++;
else
nfull++;
}
Bgpcommidx *idx;
size_t offBits = offsetof(Bgpcommidx, full[nfull]);
if (opt != BGP_VMOPT_ASSUME_COMTCH)
nbitswords = BITSETLEN(nlow + nhigh + nfull); // must allocate bitset
size_t offHigh = offBits + nbitswords * sizeof(*idx->bitset);
size_t offLow = offHigh + nhigh * sizeof(*idx->hiOnly);
size_t nbytes = offLow + nlow * sizeof(*idx->loOnly);
nbytes = ALIGN(nbytes, ALIGNMENT);
idx = Bgp_VmPermAlloc(vm, nbytes);
if (!idx)
return NULL;
idx->bitset = (Uint32 *) ((Uint8 *) idx + offBits);
idx->hiOnly = (Uint16 *) ((Uint8 *) idx + offHigh);
idx->loOnly = (Uint16 *) ((Uint8 *) idx + offLow);
idx->opt = opt;
idx->bitsetWords = nbitswords;
idx->hiOnlyCount = idx->loOnlyCount = idx->fullCount = 0;
for (size_t i = 0; i < n; i++) {
const Bgpmatchcomm *m = &match[i];
if (m->maskLo)
idx->hiOnly[idx->hiOnlyCount++] = m->c.hi;
else if (m->maskHi)
idx->loOnly[idx->loOnlyCount++] = m->c.lo;
else
idx->full[idx->fullCount++] = m->c.code;
}
// Sort lookup arrays
RadixSort16(idx->hiOnly, idx->hiOnlyCount);
RadixSort16(idx->loOnly, idx->loOnlyCount);
RadixSort32(idx->full, idx->fullCount);
// Optimize tables
idx->hiOnlyCount = Uniq16(idx->hiOnly, idx->hiOnlyCount);
idx->loOnlyCount = Uniq16(idx->loOnly, idx->loOnlyCount);
idx->fullCount = Uniq32(idx->full, idx->fullCount);
// Discard redundant entries
switch (opt) {
case BGP_VMOPT_ASSUME_COMTCH: OptimizeComtch(idx); break;
case BGP_VMOPT_ASSUME_ACOMTC: OptimizeAcomtc(idx); break;
default:
case BGP_VMOPT_NONE:
break;
}
// Free-up excess memory after optimization
CompactIndex(vm, idx, nbytes);
return idx;
}
static Bgpattr *Bgp_VmDoComSetup(Bgpvm *vm, Bgpcommiter *it, BgpAttrCode code)
{
Bgpupdate *update = (Bgpupdate *) BGP_VMCHKMSGTYPE(vm, BGP_UPDATE);
if (!update) {
Bgp_VmStoreMsgTypeMatch(vm, /*isMatching=*/FALSE);
return NULL;
}
Bgpattrseg *tpa = Bgp_GetUpdateAttributes(update);
if (!tpa) {
vm->errCode = BGPEVMMSGERR;
return NULL;
}
Bgpattr *attr = Bgp_GetUpdateAttribute(tpa, code, vm->msg->table);
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return NULL;
}
if (attr)
Bgp_StartCommunity(it, attr);
return attr;
}
void Bgp_VmDoComtch(Bgpvm *vm)
{
if (!BGP_VMCHKSTKSIZ(vm, 1))
return;
Bgpcommidx *idx = (Bgpcommidx *) BGP_VMPOPA(vm);
if (!idx || idx->opt == BGP_VMOPT_ASSUME_ACOMTC) {
vm->errCode = BGPEVMBADCOMTCH; // TODO: BGPEVMBADCOMIDX;
return;
}
Boolean isMatching = FALSE; // unless found otherwise
Bgpcommiter it;
Bgpattr *attr = Bgp_VmDoComSetup(vm, &it, BGP_ATTR_COMMUNITY);
if (vm->errCode)
return;
if (!attr)
goto done;
Bgpcomm *c;
while ((c = Bgp_NextCommunity(&it)) != NULL) {
if (MatchCommunity(c, idx)) {
isMatching = TRUE;
break;
}
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
done:
BGP_VMPUSH(vm, isMatching);
}
static void ScMatchCommunityAndSetBit(const Bgpcomm *c, Bgpcommidx *idx)
{
Sint64 pos = BinarySearch16(idx->hiOnly, idx->hiOnlyCount, c->hi);
if (pos >= 0) {
SETBIT(idx->bitset, HIBITIDX(idx, pos));
return;
}
pos = BinarySearch16(idx->loOnly, idx->loOnlyCount, c->lo);
if (pos >= 0) {
SETBIT(idx->bitset, LOBITIDX(idx, pos));
return;
}
pos = BinarySearch32(idx->full, idx->fullCount, c->code);
if (pos >= 0)
SETBIT(idx->bitset, FULLBITIDX(idx, pos));
}
static void MatchCommunityAndSetBit(const Bgpcomm *c, Bgpcommidx *idx)
{
Sint64 pos = BinarySearch16(idx->hiOnly, idx->hiOnlyCount, c->hi);
if (pos >= 0)
SETBIT(idx->bitset, HIBITIDX(idx, pos));
pos = BinarySearch16(idx->loOnly, idx->loOnlyCount, c->lo);
if (pos >= 0)
SETBIT(idx->bitset, LOBITIDX(idx, pos));
pos = BinarySearch32(idx->full, idx->fullCount, c->code);
if (pos >= 0)
SETBIT(idx->bitset, FULLBITIDX(idx, pos));
}
static Boolean Bgp_VmDoAcomtcFast(Bgpcommidx *idx, Bgpcommiter *it)
{
Bgpcomm *c;
while ((c = Bgp_NextCommunity(it)) != NULL)
ScMatchCommunityAndSetBit(c, idx);
return FFZ(idx->bitset, idx->bitsetWords) == BITSETWIDTH(idx);
}
static Boolean Bgp_VmDoAcomtcSlow(Bgpcommidx *idx, Bgpcommiter *it)
{
Bgpcomm *c;
while ((c = Bgp_NextCommunity(it)) != NULL)
MatchCommunityAndSetBit(c, idx);
return FFZ(idx->bitset, idx->bitsetWords) == BITSETWIDTH(idx);
}
void Bgp_VmDoAcomtc(Bgpvm *vm)
{
if (!BGP_VMCHKSTKSIZ(vm, 1))
return;
Bgpcommidx *idx = (Bgpcommidx *) BGP_VMPOPA(vm);
if (!idx || idx->opt == BGP_VMOPT_ASSUME_COMTCH) {
vm->errCode = BGPEVMBADCOMTCH; // TODO: BGPEVMBADCOMIDX;
return;
}
Boolean isMatching = FALSE;
Bgpcommiter it;
Bgpattr *attr = Bgp_VmDoComSetup(vm, &it, BGP_ATTR_COMMUNITY);
if (vm->errCode)
return;
if (!attr)
goto done;
CLRBITSET(idx->bitset, idx->bitsetWords);
if (idx->opt == BGP_VMOPT_ASSUME_ACOMTC)
isMatching = Bgp_VmDoAcomtcFast(idx, &it);
else
isMatching = Bgp_VmDoAcomtcSlow(idx, &it);
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
done:
BGP_VMPUSH(vm, isMatching);
}

303
lonetix/bgp/vm_dump.c Normal file
View File

@@ -0,0 +1,303 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_dump.c
*
* BGP VM bytecode dump.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgp/vmintrin.h"
#include "sys/dbg.h"
#include "numlib.h"
#include "strlib.h"
#include <assert.h>
#include <string.h>
#define LINEDIGS 5
#define MAXOPCSTRLEN 6
#define CODELINELEN 50
#define MAXINDENT 8
#define INDENTLEN 2
#define COMMENTLEN 70
static const char *OpcString(Bgpvmopc opc)
{
// NOTE: Invariant: strlen(return) <= MAXOPCSTRLEN
switch (opc) {
case BGP_VMOP_NOP: return "NOP";
case BGP_VMOP_LOAD: return "LOAD";
case BGP_VMOP_LOADU: return "LOADU";
case BGP_VMOP_LOADN: return "LOADN";
case BGP_VMOP_LOADK: return "LOADK";
case BGP_VMOP_CALL: return "CALL";
case BGP_VMOP_BLK: return "BLK";
case BGP_VMOP_ENDBLK: return "ENDBLK";
case BGP_VMOP_TAG: return "TAG";
case BGP_VMOP_NOT: return "NOT";
case BGP_VMOP_CFAIL: return "CFAIL";
case BGP_VMOP_CPASS: return "CPASS";
case BGP_VMOP_JZ: return "JZ";
case BGP_VMOP_JNZ: return "JNZ";
case BGP_VMOP_CHKT: return "CHKT";
case BGP_VMOP_CHKA: return "CHKA";
case BGP_VMOP_EXCT: return "EXCT";
case BGP_VMOP_SUPN: return "SUPN";
case BGP_VMOP_SUBN: return "SUBN";
case BGP_VMOP_RELT: return "RELT";
case BGP_VMOP_ASMTCH: return "ASMTCH";
case BGP_VMOP_FASMTC: return "FASMTC";
case BGP_VMOP_COMTCH: return "COMTCH";
case BGP_VMOP_ACOMTC: return "ACOMTC";
case BGP_VMOP_END: return "END";
default: return "???";
}
}
static const char *BgpTypeString(BgpType typ)
{
switch (typ) {
case BGP_OPEN: return "OPEN";
case BGP_UPDATE: return "UPDATE";
case BGP_NOTIFICATION: return "NOTIFICATION";
case BGP_KEEPALIVE: return "KEEPALIVE";
case BGP_ROUTE_REFRESH: return "ROUTE_REFRESH";
case BGP_CLOSE: return "CLOSE";
default: return NULL;
}
}
static const char *BgpAttrString(BgpAttrCode code)
{
switch (code) {
case BGP_ATTR_ORIGIN: return "ORIGIN";
case BGP_ATTR_AS_PATH: return "AS_PATH";
case BGP_ATTR_NEXT_HOP: return "NEXT_HOP";
case BGP_ATTR_MULTI_EXIT_DISC: return "MULTI_EXIT_DISC";
case BGP_ATTR_LOCAL_PREF: return "LOCAL_PREF";
case BGP_ATTR_ATOMIC_AGGREGATE: return "ATOMIC_AGGREGATE";
case BGP_ATTR_AGGREGATOR: return "AGGREGATOR";
case BGP_ATTR_COMMUNITY: return "COMMUNITY";
case BGP_ATTR_ORIGINATOR_ID: return "ORIGINATOR_ID";
case BGP_ATTR_CLUSTER_LIST: return "CLUSTER_LIST";
case BGP_ATTR_DPA: return "DPA";
case BGP_ATTR_ADVERTISER: return "ADVERTISER";
case BGP_ATTR_RCID_PATH_CLUSTER_ID: return "RCID_PATH_CLUSTER_ID";
case BGP_ATTR_MP_REACH_NLRI: return "MP_REACH_NLRI";
case BGP_ATTR_MP_UNREACH_NLRI: return "MP_UNREACH_NLRI";
case BGP_ATTR_EXTENDED_COMMUNITY: return "EXTENDED_COMMUNITY";
case BGP_ATTR_AS4_PATH: return "AS4_PATH";
case BGP_ATTR_AS4_AGGREGATOR: return "AS4_AGGREGATOR";
case BGP_ATTR_SAFI_SSA: return "SAFI_SSA";
case BGP_ATTR_CONNECTOR: return "CONNECTOR";
case BGP_ATTR_AS_PATHLIMIT: return "AS_PATHLIMIT";
case BGP_ATTR_PMSI_TUNNEL: return "PMSI_TUNNEL";
case BGP_ATTR_TUNNEL_ENCAPSULATION: return "TUNNEL_ENCAPSULATION";
case BGP_ATTR_TRAFFIC_ENGINEERING: return "TRAFFIC_ENGINEERING";
case BGP_ATTR_IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITY: return "IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITY";
case BGP_ATTR_AIGP: return "AIGP";
case BGP_ATTR_PE_DISTINGUISHER_LABELS: return "PE_DISTINGUISHER_LABELS";
case BGP_ATTR_ENTROPY_LEVEL_CAPABILITY: return "ENTROPY_LEVEL_CAPABILITY";
case BGP_ATTR_LS: return "LS";
case BGP_ATTR_LARGE_COMMUNITY: return "LARGE_COMMUNITY";
case BGP_ATTR_BGPSEC_PATH: return "BGPSEC_PATH";
case BGP_ATTR_COMMUNITY_CONTAINER: return "COMMUNITY_CONTAINER";
case BGP_ATTR_PREFIX_SID: return "PREFIX_SID";
case BGP_ATTR_SET: return "SET";
default: return NULL;
}
}
static const char *NetOpArgString(Uint8 opa)
{
switch (opa) {
case BGP_VMOPA_NLRI: return "NLRI";
case BGP_VMOPA_MPREACH: return "MP_REACH_NLRI";
case BGP_VMOPA_ALL_NLRI: return "ALL_NLRI";
case BGP_VMOPA_WITHDRAWN: return "WITHDRAWN";
case BGP_VMOPA_MPUNREACH: return "MP_UNREACH_NLRI";
case BGP_VMOPA_ALL_WITHDRAWN: return "ALL_WITHDRAWN";
default: return NULL;
}
}
static char *ExplainJump(char *buf, Uint32 ip, Uint8 disp, Uint32 progLen)
{
char *p = buf;
strcpy(p, "to line: "); p += 9;
Uint32 target = ip + 1;
target += 1 + disp;
p = Utoa(target, p);
if (target > progLen)
strcpy(p, " (JUMP TARGET OUT OF BOUNDS!)");
return buf;
}
static char *Indent(char *p, Bgpvmopc opc, int level)
{
int n = CLAMP(level, 0, MAXINDENT);
int last = n - 1;
for (int i = 0; i < n; i++) {
*p++ = (opc == BGP_VMOP_ENDBLK && i == last) ? '+' : '|';
for (int j = 1; j < INDENTLEN; j++)
*p++ = (opc == BGP_VMOP_ENDBLK && i == last) ? '-' : ' ';
}
return p;
}
static char *CommentCodeLine(char *line, const char *comment)
{
char *p = line;
p += Df_strpadr(p, ' ', CODELINELEN);
*p++ = ' ';
*p++ = ';'; *p++ = ' ';
size_t n = strlen(comment);
if (3 + n >= COMMENTLEN) {
n = COMMENTLEN - 3 - 3;
memcpy(p, comment, n);
p += n;
*p++ = '.'; *p++ = '.'; *p++ = '.';
} else {
memcpy(p, comment, n);
p += n;
}
*p = '\0';
return p;
}
void Bgp_VmDumpProgram(Bgpvm *vm, void *streamp, const StmOps *ops)
{
char explainbuf[64];
char buf[256];
int indent = 0;
// NOTE: <= so it includes trailing END
for (Uint32 ip = 0; ip <= vm->progLen; ip++) {
Bgpvmbytec ir = vm->prog[ip];
Bgpvmopc opc = BGP_VMOPC(ir);
Uint8 opa = BGP_VMOPARG(ir);
const char *opcnam = OpcString(opc);
assert(strlen(opcnam) <= MAXOPCSTRLEN);
char *p = buf;
// Line number
Utoa(ip+1, p);
p += Df_strpadl(p, '0', LINEDIGS);
*p++ = ':';
*p++ = ' ';
// Instruction hex dump
*p++ = '0';
*p++ = 'x';
Xtoa(ir, p);
p += Df_strpadl(p, '0', XDIGS(ir));
*p++ = ' ';
// Code indent
p = Indent(p, opc, indent);
// Opcode
strcpy(p, opcnam);
p += Df_strpadr(p, ' ', MAXOPCSTRLEN);
// Instruction argument
const char *opastr = NULL;
switch (opc) {
case BGP_VMOP_LOAD:
*p++ = ' ';
p = Itoa((Sint8) opa, p);
break;
case BGP_VMOP_LOADU:
case BGP_VMOP_JZ:
case BGP_VMOP_JNZ:
*p++ = ' ';
p = Utoa(opa, p);
if (opc == BGP_VMOP_JZ || opc == BGP_VMOP_JNZ)
opastr = ExplainJump(explainbuf, ip, opa, vm->progLen);
break;
case BGP_VMOP_TAG:
case BGP_VMOP_CHKT:
case BGP_VMOP_CHKA:
case BGP_VMOP_EXCT:
case BGP_VMOP_SUBN:
case BGP_VMOP_SUPN:
case BGP_VMOP_RELT:
*p++ = ' ';
*p++ = '0'; *p++ = 'x';
Xtoa(opa, p);
p += Df_strpadl(p, '0', XDIGS(opa));
if (opc == BGP_VMOP_CHKT)
opastr = BgpTypeString(opa);
else if (opc == BGP_VMOP_CHKA)
opastr = BgpAttrString(opa);
else
opastr = NetOpArgString(opa);
break;
case BGP_VMOP_LOADK:
*p++ = ' ';
*p++ = 'K';
*p++ = '[';
p = Utoa(opa, p);
*p++ = ']';
*p = '\0';
break;
case BGP_VMOP_CALL:
*p++ = ' ';
*p++ = 'F';
*p++ = 'N';
*p++ = '[';
p = Utoa(opa, p);
*p++ = ']';
*p = '\0';
if (opa < vm->nfuncs) {
Funsym fsym;
fsym.func = (void (*)(void)) vm->funcs[opa];
opastr = Sys_GetSymbolName(fsym.sym);
}
break;
default:
break;
}
// Optional comment after CODELINELEN columns
if (opastr)
p = CommentCodeLine(buf, opastr);
// Flush line (no need for '\0')
*p++ = '\n';
ops->Write(streamp, buf, p - buf);
// Update indent
if (opc == BGP_VMOP_BLK)
indent++;
if (opc == BGP_VMOP_ENDBLK)
indent--;
}
}

66
lonetix/bgp/vm_gccdef.h Normal file
View File

@@ -0,0 +1,66 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_gccdef.h
*
* `#define`s for GNUC optimized BGP VM execution loop.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* \note This file should be `#include`d by `bgp/vm.c`
*/
#ifdef DF_BGP_VMDEF_H_
#error "Only one vm_<impl>def.h file may be #include-d"
#endif
#define DF_BGP_VMDEF_H_
#define _CONCAT(x, y) x ## y
#define _XCONCAT(x, y) _CONCAT(x, y)
#ifdef __clang__
// No __attribute__ on labels in CLANG
#define LIKELY
#else
#define LIKELY \
_XCONCAT(_BRANCH_PREDICT_HINT, __COUNTER__): \
__attribute__((__hot__, __unused__))
#endif
#ifdef __clang__
// No __attribute__ on labels in CLANG
#define UNLIKELY
#else
#define UNLIKELY \
_XCONCAT(_BRANCH_PREDICT_HINT, __COUNTER__): \
__attribute__((__cold__, __unused__))
#endif
#define FETCH(ir, vm) (ir = (vm)->prog[(vm)->pc++])
#define EXPECT(opcode, ir, vm) \
do { \
if (__builtin_expect( \
BGP_VMOPC((vm)->prog[(vm)->pc]) == BGP_VMOP_ ## opcode, \
1 \
)) { \
ir = (vm)->prog[(vm)->pc++]; \
goto EX_ ## opcode; \
} \
} while (0)
#define DISPATCH(opcode) \
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wpedantic\"") \
goto *bgp_vmOpTab[opcode]; \
_Pragma("GCC diagnostic pop") \
switch (opcode) // This keeps consistency with regular vm_cdef.h
#define EXECUTE(opcode) case BGP_VMOP_ ## opcode: EX_ ## opcode
#define EXECUTE_SIGILL default: EX_SIGILL

98
lonetix/bgp/vm_optab.h Normal file
View File

@@ -0,0 +1,98 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm_optab.h
*
* Computed goto table for GNUC optimized BGP VM execution loop.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*
* File defines a GNUC-specific computed goto table.
*
* It can be used as an alternative to a regular switch to
* accelerate the BGP filtering engine execution loop (Bgp_VmExec()).
*
* Constraints:
* - Array length: 256 (8-bit OPCODE width)
* - Label address MUST follow the convention EX_<OPCODE NAME> (e.g. EX_NOP, EX_EXCT)
* - Any unused OPCODE MUST be set to EX_SIGILL
*
* Operations on the computed goto table are defined in: bgp/vm_gccdef.h
*
* \see [GCC Documentation](https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html)
*
* \warning KEEP TABLE IN SYNC WITH OPCODES IN: bgp/vm.h
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#pragma GCC diagnostic ignored "-Woverride-init"
static void *const bgp_vmOpTab[256] = {
// Following clears everything else in the array to SIGILL,
// 8 instructions per line (256 &&EX_SIGILL)
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
&&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL, &&EX_SIGILL,
// Following initializes valid OPCODEs
[BGP_VMOP_NOP] = &&EX_NOP,
[BGP_VMOP_LOAD] = &&EX_LOAD,
[BGP_VMOP_LOADU] = &&EX_LOADU,
[BGP_VMOP_LOADN] = &&EX_LOADN,
[BGP_VMOP_LOADK] = &&EX_LOADK,
[BGP_VMOP_CALL] = &&EX_CALL,
[BGP_VMOP_BLK] = &&EX_BLK,
[BGP_VMOP_ENDBLK] = &&EX_ENDBLK,
[BGP_VMOP_TAG] = &&EX_TAG,
[BGP_VMOP_NOT] = &&EX_NOT,
[BGP_VMOP_CFAIL] = &&EX_CFAIL,
[BGP_VMOP_CPASS] = &&EX_CPASS,
[BGP_VMOP_JZ] = &&EX_JZ,
[BGP_VMOP_JNZ] = &&EX_JNZ,
[BGP_VMOP_CHKT] = &&EX_CHKT,
[BGP_VMOP_CHKA] = &&EX_CHKA,
[BGP_VMOP_EXCT] = &&EX_EXCT,
[BGP_VMOP_SUPN] = &&EX_SUPN,
[BGP_VMOP_SUBN] = &&EX_SUBN,
[BGP_VMOP_RELT] = &&EX_RELT,
[BGP_VMOP_ASMTCH] = &&EX_ASMTCH,
[BGP_VMOP_FASMTC] = &&EX_FASMTC,
[BGP_VMOP_COMTCH] = &&EX_COMTCH,
[BGP_VMOP_ACOMTC] = &&EX_ACOMTC,
[BGP_VMOP_END] = &&EX_END
};
#pragma GCC diagnostic pop