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

402 lines
14 KiB
C

// SPDX-License-Identifier: LGPL-3.0-or-later
/**
* \file bgp/vm.h
*
* BGP message filtering engine.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGP_VM_H_
#define DF_BGP_VM_H_
#include "bgp.h"
/**
* \name BGP VM bytecode
*
* \brief Filtering engine instruction OPCODEs and arguments.
*
* Bytecode format is:
* ```
* LSB MSB (NATIVE endianness)
* +--------+--------+
* | OPCODE | ARG |
* +--------+--------+
* 0 16 bit
*
* (2 bytes per instruction)
* ```
*
* Bytecode follows native machine endianness, it is NOT endian independent.
*
* @{
*/
/// BGP filter VM OPCODE
typedef Uint8 Bgpvmopc;
/// BGP filter VM bytecode instruction
typedef Uint16 Bgpvmbytec;
/// Returns a VM bytecode with the specified opcode and argument.
#define BGP_VMOP(opc, arg) ((Bgpvmbytec) ((((Uint8) (arg)) << 8) | ((Bgpvmopc) opc)))
/// Extract OPCODE from the specified bytecode.
FORCE_INLINE Bgpvmopc BGP_VMOPC(Bgpvmbytec bytec)
{
return (Bgpvmopc) (bytec & 0xff);
}
/// Extract direct argument from the specified bytecode.
FORCE_INLINE Uint8 BGP_VMOPARG(Bgpvmbytec bytec)
{
return (bytec >> 8);
}
/// NO-OPERATION - does nothing and moves on
#define BGP_VMOP_NOP U8_C(0)
/// LOAD - Pushes `ARG` constant on stack, interpreting it as a `Sint8`
#define BGP_VMOP_LOAD U8_C(1)
/// LOAD UNSIGNED - Pushes `ARG` constant on stack, interpreting it as an `Uint8`
#define BGP_VMOP_LOADU U8_C(2)
/// LOAD NULL - Push a `NULL` address on stack
#define BGP_VMOP_LOADN U8_C(3)
/// LOADK - Push a new constant (K) on stack, `ARG` is the index inside `K`
#define BGP_VMOP_LOADK U8_C(4)
/// CALL - invoke an external native function
#define BGP_VMOP_CALL U8_C(5)
/// BLOCK OPEN - Push a new matching block (used to implement AND/OR chains)
#define BGP_VMOP_BLK U8_C(6)
/// END BLOCK - Pops the current matching block
#define BGP_VMOP_ENDBLK U8_C(7)
/// TAG LAST MATCH - Tags last operation's match with `ARG`
#define BGP_VMOP_TAG U8_C(8)
/// NOT - Boolean negate the topmost stack element
#define BGP_VMOP_NOT U8_C(9)
/// CONDITIONAL FAIL If TRUE - Fail current match `BLK` if topmost stack element is non-zero
#define BGP_VMOP_CFAIL U8_C(10)
/// CONDITIONAL PASS If TRUE - Pass current match `BLK` if topmost stack element is non-zero
#define BGP_VMOP_CPASS U8_C(11)
/// FAIL IF FALSE - Fail current match `BLK` if topmost stack element is zero
#define BGP_VMOP_ORFAIL U8_C(12)
/// PASS IF FALSE - Pass current match `BLK` if topmost stack element is zero
#define BGP_VMOP_ORPASS U8_C(13)
FORCE_INLINE Boolean BGP_ISVMOPBREAKING(Bgpvmopc opc)
{
return opc >= BGP_VMOP_CFAIL && opc <= BGP_VMOP_ORPASS;
}
/// Jump if zero - Skip over a positive number of instructions if topmost stack element is 0.
#define BGP_VMOP_JZ U8_C(14)
/// Jump if non-zero - Skip over a positive number of instructions if topmost stack element is not 0.
#define BGP_VMOP_JNZ U8_C(15)
FORCE_INLINE Boolean Bgp_ISVMOPJMP(Bgpvmopc opc)
{
return opc == BGP_VMOP_JZ || opc == BGP_VMOP_JNZ;
}
/// CHECK TYPE - ARG is the `BgpType` to test against
#define BGP_VMOP_CHKT U8_C(16)
/// CHECK ATTRIBUTE - ARG is the `BgpAttrCode` to test for existence
#define BGP_VMOP_CHKA U8_C(17)
#define BGP_VMOP_EXCT U8_C(18)
#define BGP_VMOP_SUPN U8_C(19)
#define BGP_VMOP_SUBN U8_C(20)
/// RELATED - Tests whether the BGP message contains prefixes related with the provided ones
#define BGP_VMOP_RELT U8_C(21)
/// Returns `TRUE` if `opc` belongs to an instruction operating on NETwork prefixes.
FORCE_INLINE Boolean BGP_ISVMOPNET(Bgpvmopc opc)
{
return opc >= BGP_VMOP_EXCT && opc <= BGP_VMOP_RELT;
}
/// AS PATH MATCH - Tests BGP message AS PATH against a precompiled match expression
#define BGP_VMOP_ASMTCH U8_C(22)
/// COMMUNITY MATCH - COMMUNITY test using a precompiled COMMUNITY match expression
#define BGP_VMOP_COMTCH U8_C(23)
/// ALL COMMUNITY MATCH - Like COMTCH, but requires all communities to be present inside message
#define BGP_VMOP_ACOMTC U8_C(24)
/// END - Terminate VM execution with the latest result
#define BGP_VMOP_END U8_C(25)
FORCE_INLINE Boolean BGP_ISVMOPENDING(Bgpvmopc opc)
{
return opc == BGP_VMOP_ENDBLK || opc == BGP_VMOP_END;
}
// #define BGP_VMOP_MOVK MOVE K - Move topmost K index to ARG K index
// #define BGP_VMOP_DISCRD DISCARD - discard vm->curMatch if any
// Bytecode `ARG` values for `NET` class instructions
#define BGP_VMOPA_NLRI U8_C(0)
#define BGP_VMOPA_MPREACH U8_C(1)
#define BGP_VMOPA_ALL_NLRI U8_C(2)
#define BGP_VMOPA_WITHDRAWN U8_C(3)
#define BGP_VMOPA_MPUNREACH U8_C(4)
#define BGP_VMOPA_ALL_WITHDRAWN U8_C(5)
// Special `Asn` values for `ASMTCH` and `FASMTC` instructions
#define ASNNOTFLAG BIT(61)
/// Matches any ASN **except** the provided one.
#define ASNNOT(asn) ((Asn) ((asn) | ASNNOTFLAG))
/// Tests whether `asn` is a negative ASN match.
FORCE_INLINE Boolean ISASNNOT(Asn asn)
{
return (asn & ASNNOTFLAG) != 0;
}
/// Match with the AS_PATH start (^)
#define ASN_START ((Asn) 0x0000000100000000LL)
/// Match with the AS PATH end ($).
#define ASN_END ((Asn) 0x0000000200000000LL)
/// Match with any ASN (.).
#define ASN_ANY ((Asn) 0x0000000300000000LL)
/// Match zero or more ASN (*).
#define ASN_STAR ((Asn) 0x0000000400000000LL)
/// Match with zero or one ASN (?).
#define ASN_QUEST ((Asn) 0x0000000500000000LL)
/// Match the previous ASN one or more times (+).
#define ASN_PLUS ((Asn) 0x0000000600000000LL)
/// Introduce a new group (opening paren for expressions like `( a b c )`)
#define ASN_NEWGRP ((Asn) 0x0000000700000000LL)
/// Introduce alternative inside matching expression (pipe symbol for expressions like `( a b | b c )`)
#define ASN_ALT ((Asn) 0x0000000800000000LL)
/// Terminate a group expression (closing paren for expressions like `( a b c )`)
#define ASN_ENDGRP ((Asn) 0x0000000900000000LL)
/** @} */
/**
* \brief Stack slot.
*
* The VM stack is a sequence of `Bgpvmval` cells,
* interpreted by each instruction as dictated by the opcode.
*/
typedef union Bgpvmval Bgpvmval;
union Bgpvmval {
void *ptr; ///< Value as a pointer
Sint64 val; ///< Value as a signed integral
};
/// Match info for AS matching expressions.
typedef struct Bgpvmasmatch Bgpvmasmatch;
struct Bgpvmasmatch {
Bgpvmasmatch *next;
Aspathiter spos;
Aspathiter epos;
};
/// Optimization modes for some matching instructions (e.g. BGP_VMOP_COMTCH/BGP_VMOP_ACOMTC).
typedef enum {
BGP_VMOPT_NONE, ///< Do not optimize.
// For COMTCH/ACOMTC
BGP_VMOPT_ASSUME_COMTCH, ///< Assume instruction is going to be `COMTCH`
BGP_VMOPT_ASSUME_ACOMTC ///< Assume instruction is going to be `ACOMTC`
} BgpVmOpt;
typedef struct Bgpmatchcomm Bgpmatchcomm;
struct Bgpmatchcomm {
Boolean8 maskHi; // don't match HI (match of type *:N)
Boolean8 maskLo; // don't match LO (match of type N:*)
Bgpcomm c;
};
/**
* \brief Matching operation result on a BGP message.
*
* Collect relevant information on a matching operation, including the
* direct byte range and position inside BGP message data.
*
* This structure may be used to further process BGP data after the filtering
* is complete. A `Bgpvmmatch` structure is generated for several BGP VM
* OPCODEs, and is only valid up to the next [Bgp_VmExec()](@ref Bgp_VmExec)
* call on the same VM that originated them.
*/
typedef struct Bgpvmmatch Bgpvmmatch;
struct Bgpvmmatch {
Uint32 pc; ///< instruction index that originated this match
Boolean8 isMatching; ///< whether this result declares a match or a mismatch
Boolean8 isPassing; ///< whether this result produced a `PASS` or a `FAIL` inside filter
Uint8 tag; ///< optional tag id for this match (as set by `TAG`)
Uint8 *base, *lim; ///< relevant BGP message segment, if any
void *pos; ///< pointer to detailed match-specific information
Bgpvmmatch *nextMatch; ///< next match in chain (`NULL` if this is the last element)
};
/// Filtering engine error code (a subset of [BgpRet](@ref BgpRet).
typedef Sint8 BgpvmRet;
/// Maximum number of VM constants inside [Bgpvm](@ref Bgpvm).
#define MAXBGPVMK 256
/// Maximum number of VM callable functions inside [Bgpvm](@ref Bgpvm).
#define MAXBGPVMFN 32
/// Maximum allowed nested grouping levels inside a `BGP_VMOP_ASMTCH`.
#define MAXBGPVMASNGRP 32
/**
* \brief Bytecode-based virtual machine operating on BGP messages.
*
* Extensible programmable BGP message matching and filtering engine.
*/
typedef struct Bgpvm Bgpvm;
struct Bgpvm {
Uint32 pc; ///< VM program counter inside `prog`
Uint32 nblk; ///< nested conditional block count
Uint32 nmatches; ///< current execution matches count (length of the `matches` list)
Uint16 si; ///< VM Stack index
Uint16 nk; ///< count of constants (K) available in `k`
Uint8 nfuncs; ///< count of functions (FN) available in `funcs`
Boolean8 setupFailed; ///< whether a `Bgp_VmEmit()` or `Bgp_VmPermAlloc()` on this VM ever failed.
BgpvmRet errCode; ///< whether the VM encountered an error
Uint32 hLowMark; ///< VM heap low memory mark
Uint32 hHighMark; ///< VM heap high memory mark
Uint32 hMemSiz; ///< VM heap size in bytes
Uint32 progLen; ///< bytecode program instruction count
Uint32 progCap; ///< bytecode segment instruction capacity
Bgpmsg *msg; ///< current BGP message being processed
Bgpvmbytec *prog; ///< VM program bytecode, `progLen` instructions (`prog[progLen]` is always `BGP_VMOP_END`)
/**
* Filtering VM heap, managed as follows:
* ```
* hLowMark hHighMark
* heap -+ v v
* v STACK grows upwards .-.-> <.-.-. TEMP grows downwards
* +=====================\-------------------/=============+
* | PERM ALLOCS | STACK > < TEMP ALLOCS |
* +=====================/-------------------\=============+
* [--------------------- hMemSiz -------------------------]
*
* PERM ALLOCS: Allocations that last forever (until the VM is freed)
* - such allocations CANNOT happen while VM is running
*
* TEMP ALLOCS: Allocations that last up to the next Bgp_VmExec()
* - such allocations may only take place while VM is running
* ```
*/
void *heap;
Bgpvmmatch *curMatch; ///< current match being updated during VM execution
Bgpvmmatch *matches; ///< matches produced during execution (contains `nmatches` elements)
Bgpvmval k[MAXBGPVMK]; ///< VM constants (K), `nk` allocated
void (*funcs[MAXBGPVMFN])(Bgpvm *); ///< VM functions (FN), `nfuncs` allocated
};
/// Clear the `errCode` error flag on `vm`.
FORCE_INLINE void BGP_VMCLRERR(Bgpvm *vm)
{
vm->errCode = BGPENOERR;
}
/// Clear the `setupFailed` error flag on `vm`.
FORCE_INLINE void BGP_VMCLRSETUP(Bgpvm *vm)
{
vm->setupFailed = FALSE;
}
FORCE_INLINE Sint32 Bgp_VmNewk(Bgpvm *vm)
{
if (vm->nk >= MAXBGPVMK)
return -1;
return vm->nk++;
}
FORCE_INLINE Sint32 Bgp_VmNewFn(Bgpvm *vm)
{
if (vm->nfuncs >= MAXBGPVMFN)
return -1;
return vm->nfuncs++;
}
FORCE_INLINE Sint32 BGP_VMSETKA(Bgpvm *vm, Sint32 kidx, void *ptr)
{
if (kidx >= 0)
vm->k[kidx].ptr = ptr;
return kidx;
}
FORCE_INLINE Sint32 BGP_VMSETK(Bgpvm *vm, Sint32 kidx, Sint64 val)
{
if (kidx >= 0)
vm->k[kidx].val = val;
return kidx;
}
FORCE_INLINE Sint32 BGP_VMSETFN(Bgpvm *vm, Sint32 idx, void (*fn)(Bgpvm *))
{
if (idx >= 0)
vm->funcs[idx] = fn;
return idx;
}
/**
* \brief Initialize a new VM with the specified heap size.
*
* \return `OK` on success, `NG` on out of memory, sets BGP error, VM error,
* and, on failure, VM setup failure flag.
*/
Judgement Bgp_InitVm(Bgpvm *vm, size_t heapSiz);
/**
* \brief Emit a VM bytecode instruction to `vm`.
*
* \return `OK` if instruction was added successfully, `NG` on out of memory,
* sets BGP error, VM error and, on failure, VM setup failure flag.
*
* \note Emitting `BGP_VMOP_END` has no effect.
*/
Judgement Bgp_VmEmit(Bgpvm *vm, Bgpvmbytec bytec);
/**
* \brief Precompile an AS PATH match expression for use with BGP_VMOP_FASMTC.
*
* \return Pointer suitable as the argument of `BGP_VMOP_FASMTC` instruction
* on success, `NULL` on failure.
* Precompiled expression is stored inside `vm` permanent heap.
*/
void *Bgp_VmCompileAsMatch(Bgpvm *vm, const Asn *match, size_t n);
void *Bgp_VmCompileCommunityMatch(Bgpvm *vm, const Bgpmatchcomm *match, size_t n, BgpVmOpt opt);
/**
* \brief Perform a permanent heap allocation of `size` bytes to `vm`.
*
* \return `vm` heap pointer to memory zone on success, `NULL` on failure,
* sets BGP error, VM error, and, on failure, `vm` setup failure flag.
*
* \note This function may only be called if `vm` is not executing!
*/
void *Bgp_VmPermAlloc(Bgpvm *vm, size_t size);
void *Bgp_VmTempAlloc(Bgpvm *vm, size_t size);
void Bgp_VmTempFree(Bgpvm *vm, size_t size);
/**
* \brief Execute `vm` bytecode on `msg`.
*
* \return `TRUE` if `vm` terminated with PASS, `FALSE` if it terminated on
* FAIL, or if an error was encountered. Sets BGP error and VM error.
*/
Boolean Bgp_VmExec(Bgpvm *vm, Bgpmsg *msg);
/// Print `vm` bytecode dump to stream.
void Bgp_VmDumpProgram(Bgpvm *vm, void *streamp, const StmOps *ops);
/// Reset `vm` state, but keep allocated memory for further setup.
void Bgp_ResetVm(Bgpvm *vm);
/// Clear `vm` and free all memory.
void Bgp_ClearVm(Bgpvm *vm);
#endif