var-names: expose outside of detect engine

Until now variable names, such as flowbit names, were local to a detect
engine. This made sense as they were only ever used in that context.

For the purpose of logging these names, this needs a different approach.
The loggers live outside of the detect engine. Also, in the case of
reloads and multi-tenancy, there are even multiple detect engines, so
it would be even more tricky to access them from the outside.

This patch brings a new approach. A any time, there is a single active
hash table mapping the variable names and their id's. For multiple
tenants the table is shared between tenants.

The table is set up in a 'staging' area, where locking makes sure that
multiple loading threads don't mess things up. Then when the preparing
of a detection engine is ready, but before the detect threads are made
aware of the new detect engine, the active varname hash is swapped with
the staging instance.

For this to work, all the mappings from the 'current' or active mapping
are added to the staging table.

After the threads have reloaded and the new detection engine is active,
the old table can be freed.

For multi tenancy things are similar. The staging area is used for
setting up until the new detection engines / tenants are applied to
the system.

This patch also changes the variable 'id'/'idx' field to uint32_t. Due
to data structure padding and alignment, this should have no practical
drawback while allowing for a lot more vars.
pull/2559/head
Victor Julien 9 years ago
parent 43cc06eabe
commit 22f3205664

@ -934,7 +934,6 @@ static DetectEngineCtx *DetectEngineCtxInitReal(int minimal, const char *prefix)
SigGroupHeadHashInit(de_ctx);
MpmStoreInit(de_ctx);
ThresholdHashInit(de_ctx);
VariableNameInitHash(de_ctx);
DetectParseDupSigHashInit(de_ctx);
DetectAddressMapInit(de_ctx);
@ -958,6 +957,7 @@ static DetectEngineCtx *DetectEngineCtxInitReal(int minimal, const char *prefix)
}
de_ctx->version = DetectEngineGetVersion();
VarNameStoreSetupStaging(de_ctx->version);
SCLogDebug("dectx with version %u", de_ctx->version);
return de_ctx;
error:
@ -1033,8 +1033,6 @@ void DetectEngineCtxFree(DetectEngineCtx *de_ctx)
SigCleanSignatures(de_ctx);
SCFree(de_ctx->app_mpms);
de_ctx->app_mpms = NULL;
VariableNameFreeHash(de_ctx);
if (de_ctx->sig_array)
SCFree(de_ctx->sig_array);
@ -1067,6 +1065,9 @@ void DetectEngineCtxFree(DetectEngineCtx *de_ctx)
DetectPortCleanupList(de_ctx->tcp_whitelist);
DetectPortCleanupList(de_ctx->udp_whitelist);
/* freed our var name hash */
VarNameStoreFree(de_ctx->version);
SCFree(de_ctx);
//DetectAddressGroupPrintMemory();
//DetectSigGroupPrintMemory();
@ -2490,6 +2491,9 @@ int DetectEngineMultiTenantSetup(void)
if (DetectLoadersSync() != 0) {
goto error;
}
VarNameStoreActivateStaging();
} else {
SCLogDebug("multi-detect not enabled (multi tenancy)");
}

@ -246,7 +246,7 @@ int DetectFlowbitSetup (DetectEngineCtx *de_ctx, Signature *s, char *rawstr)
if (unlikely(cd == NULL))
goto error;
cd->idx = VariableNameGetIdx(de_ctx, fb_name, VAR_TYPE_FLOW_BIT);
cd->idx = VarNameStoreSetupAdd(fb_name, VAR_TYPE_FLOW_BIT);
cd->cmd = fb_cmd;
SCLogDebug("idx %" PRIu32 ", cmd %s, name %s",
@ -360,6 +360,7 @@ static int FlowBitsTestSig01(void)
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Noalert\"; flowbits:noalert,wrongusage; content:\"GET \"; sid:1;)");
FAIL_IF_NOT_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
}
@ -399,6 +400,7 @@ static int FlowBitsTestSig02(void)
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"toggle rule need an option\"; flowbits:toggle; content:\"GET \"; sid:5;)");
FAIL_IF_NOT_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
@ -424,6 +426,7 @@ static int FlowBitsTestSig03(void)
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Unknown cmd\"; flowbits:wrongcmd; content:\"GET \"; sid:1;)");
FAIL_IF_NOT_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
}
@ -449,9 +452,10 @@ static int FlowBitsTestSig04(void)
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"isset option\"; flowbits:isset,fbt; content:\"GET \"; sid:1;)");
FAIL_IF_NULL(s);
idx = VariableNameGetIdx(de_ctx, "fbt", VAR_TYPE_FLOW_BIT);
idx = VarNameStoreSetupAdd("fbt", VAR_TYPE_FLOW_BIT);
FAIL_IF(idx != 1);
SigGroupBuild(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
}
@ -477,6 +481,7 @@ static int FlowBitsTestSig05(void)
FAIL_IF_NULL(s);
FAIL_IF((s->flags & SIG_FLAG_NOALERT) != SIG_FLAG_NOALERT);
SigGroupBuild(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
}
@ -504,7 +509,7 @@ static int FlowBitsTestSig06(void)
Flow f;
GenericVar flowvar, *gv = NULL;
int result = 0;
int idx = 0;
uint32_t idx = 0;
memset(p, 0, SIZE_OF_PACKET);
memset(&th_v, 0, sizeof(th_v));
@ -531,15 +536,14 @@ static int FlowBitsTestSig06(void)
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Flowbit set\"; flowbits:set,myflow; sid:10;)");
FAIL_IF_NULL(s);
idx = VarNameStoreSetupAdd("myflow", VAR_TYPE_FLOW_BIT);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
idx = VariableNameGetIdx(de_ctx, "myflow", VAR_TYPE_FLOW_BIT);
gv = p->flow->flowvar;
FAIL_IF_NULL(gv);
for ( ; gv != NULL; gv = gv->next) {
if (gv->type == DETECT_FLOWBITS && gv->idx == idx) {
result = 1;
@ -547,13 +551,9 @@ static int FlowBitsTestSig06(void)
}
FAIL_IF_NOT(result);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
if(gv) GenericVarFree(gv);
FLOW_DESTROY(&f);
SCFree(p);
@ -583,7 +583,7 @@ static int FlowBitsTestSig07(void)
Flow f;
GenericVar flowvar, *gv = NULL;
int result = 0;
int idx = 0;
uint32_t idx = 0;
memset(p, 0, SIZE_OF_PACKET);
memset(&th_v, 0, sizeof(th_v));
@ -611,14 +611,14 @@ static int FlowBitsTestSig07(void)
s = s->next = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Flowbit unset\"; flowbits:unset,myflow2; sid:11;)");
FAIL_IF_NULL(s);
idx = VarNameStoreSetupAdd("myflow", VAR_TYPE_FLOW_BIT);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
idx = VariableNameGetIdx(de_ctx, "myflow", VAR_TYPE_FLOW_BIT);
gv = p->flow->flowvar;
FAIL_IF_NULL(gv);
for ( ; gv != NULL; gv = gv->next) {
if (gv->type == DETECT_FLOWBITS && gv->idx == idx) {
@ -627,13 +627,9 @@ static int FlowBitsTestSig07(void)
}
FAIL_IF(result);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
if(gv) GenericVarFree(gv);
FLOW_DESTROY(&f);
SCFree(p);
@ -664,7 +660,7 @@ static int FlowBitsTestSig08(void)
Flow f;
GenericVar flowvar, *gv = NULL;
int result = 0;
int idx = 0;
uint32_t idx = 0;
memset(p, 0, SIZE_OF_PACKET);
memset(&th_v, 0, sizeof(th_v));
@ -692,14 +688,14 @@ static int FlowBitsTestSig08(void)
s = s->next = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Flowbit unset\"; flowbits:toggle,myflow2; sid:11;)");
FAIL_IF_NULL(s);
idx = VarNameStoreSetupAdd("myflow", VAR_TYPE_FLOW_BIT);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
idx = VariableNameGetIdx(de_ctx, "myflow", VAR_TYPE_FLOW_BIT);
gv = p->flow->flowvar;
FAIL_IF_NULL(gv);
for ( ; gv != NULL; gv = gv->next) {
if (gv->type == DETECT_FLOWBITS && gv->idx == idx) {
@ -708,13 +704,9 @@ static int FlowBitsTestSig08(void)
}
FAIL_IF(result);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
if(gv) GenericVarFree(gv);
FLOW_DESTROY(&f);
SCFree(p);

@ -34,7 +34,7 @@
#define DETECT_FLOWBITS_CMD_MAX 6
typedef struct DetectFlowbitsData_ {
uint16_t idx;
uint32_t idx;
uint8_t cmd;
} DetectFlowbitsData;

@ -104,7 +104,7 @@ int DetectFlowintMatch(ThreadVars *t, DetectEngineThreadCtx *det_ctx,
* return zero(not match).
*/
if (sfd->targettype == FLOWINT_TARGET_VAR) {
uint16_t tvar_idx = VariableNameGetIdx(det_ctx->de_ctx, sfd->target.tvar.name, VAR_TYPE_FLOW_INT);
uint32_t tvar_idx = VarNameStoreLookupByName(sfd->target.tvar.name, VAR_TYPE_FLOW_INT);
fvt = FlowVarGet(p->flow, tvar_idx);
/* We don't have that variable initialized yet */
@ -326,8 +326,8 @@ DetectFlowintData *DetectFlowintParse(DetectEngineCtx *de_ctx, char *rawstr)
SCLogError(SC_ERR_MEM_ALLOC, "malloc from strdup failed");
goto error;
}
if (de_ctx != NULL)
sfd->idx = VariableNameGetIdx(de_ctx, varname, VAR_TYPE_FLOW_INT);
sfd->idx = VarNameStoreSetupAdd(varname, VAR_TYPE_FLOW_INT);
SCLogDebug("sfd->name %s id %u", sfd->name, sfd->idx);
sfd->modifier = modifier;
pcre_free_substring(varname);

@ -64,7 +64,7 @@ typedef struct DetectFlowintData_ {
* against the target */
char *name;
/* Internal id of the var */
uint16_t idx;
uint32_t idx;
/* The modifier/operation/condition we are
* going to execute */

@ -166,7 +166,7 @@ static int DetectFlowvarSetup (DetectEngineCtx *de_ctx, Signature *s, char *raws
fd->name = SCStrdup(varname);
if (unlikely(fd->name == NULL))
goto error;
fd->idx = VariableNameGetIdx(de_ctx, varname, VAR_TYPE_FLOW_VAR);
fd->idx = VarNameStoreSetupAdd(varname, VAR_TYPE_FLOW_VAR);
/* Okay so far so good, lets get this into a SigMatch
* and put it in the Signature. */
@ -194,7 +194,7 @@ error:
/** \brief Store flowvar in det_ctx so we can exec it post-match */
int DetectFlowvarStoreMatch(DetectEngineThreadCtx *det_ctx, uint16_t idx,
int DetectFlowvarStoreMatch(DetectEngineThreadCtx *det_ctx, uint32_t idx,
uint8_t *buffer, uint16_t len, int type)
{
DetectFlowvarList *fs = det_ctx->flowvarlist;
@ -229,7 +229,7 @@ int DetectFlowvarStoreMatch(DetectEngineThreadCtx *det_ctx, uint16_t idx,
/** \brief Setup a post-match for flowvar storage
* We're piggyback riding the DetectFlowvarData struct
*/
int DetectFlowvarPostMatchSetup(Signature *s, uint16_t idx)
int DetectFlowvarPostMatchSetup(Signature *s, uint32_t idx)
{
SigMatch *sm = NULL;
DetectFlowvarData *fv = NULL;

@ -26,7 +26,7 @@
typedef struct DetectFlowvarData_ {
char *name;
uint16_t idx;
uint32_t idx;
uint8_t *content;
uint8_t content_len;
uint8_t flags;
@ -35,8 +35,8 @@ typedef struct DetectFlowvarData_ {
/* prototypes */
void DetectFlowvarRegister (void);
int DetectFlowvarPostMatchSetup(Signature *s, uint16_t idx);
int DetectFlowvarStoreMatch(DetectEngineThreadCtx *, uint16_t, uint8_t *, uint16_t, int);
int DetectFlowvarPostMatchSetup(Signature *s, uint32_t idx);
int DetectFlowvarStoreMatch(DetectEngineThreadCtx *, uint32_t, uint8_t *, uint16_t, int);
/* For use only by DetectFlowvarProcessList() */
void DetectFlowvarProcessListInternal(DetectFlowvarList *fs, Flow *f);

@ -385,7 +385,7 @@ int DetectHostbitSetup (DetectEngineCtx *de_ctx, Signature *s, char *rawstr)
if (unlikely(cd == NULL))
goto error;
cd->idx = VariableNameGetIdx(de_ctx, fb_name, VAR_TYPE_HOST_BIT);
cd->idx = VarNameStoreSetupAdd(fb_name, VAR_TYPE_HOST_BIT);
cd->cmd = fb_cmd;
cd->tracker = hb_dir;
cd->type = VAR_TYPE_HOST_BIT;
@ -752,7 +752,7 @@ static int HostBitsTestSig04(void)
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"isset option\"; hostbits:isset,fbt; content:\"GET \"; sid:1;)");
FAIL_IF_NULL(s);
idx = VariableNameGetIdx(de_ctx, "fbt", VAR_TYPE_HOST_BIT);
idx = VarNameStoreSetupAdd("fbt", VAR_TYPE_HOST_BIT);
FAIL_IF(idx != 1);
SigGroupBuild(de_ctx);
@ -760,9 +760,6 @@ static int HostBitsTestSig04(void)
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
HostBitsTestShutdown();

@ -776,7 +776,7 @@ static int DetectLuaSetupPrime(DetectEngineCtx *de_ctx, DetectLuaData *ld)
goto error;
}
uint16_t idx = VariableNameGetIdx(de_ctx, (char *)value, VAR_TYPE_FLOW_VAR);
uint32_t idx = VarNameStoreSetupAdd((char *)value, VAR_TYPE_FLOW_VAR);
ld->flowvar[ld->flowvars++] = idx;
SCLogDebug("script uses flowvar %u with script id %u", idx, ld->flowvars - 1);
}
@ -798,7 +798,7 @@ static int DetectLuaSetupPrime(DetectEngineCtx *de_ctx, DetectLuaData *ld)
goto error;
}
uint16_t idx = VariableNameGetIdx(de_ctx, (char *)value, VAR_TYPE_FLOW_INT);
uint32_t idx = VarNameStoreSetupAdd((char *)value, VAR_TYPE_FLOW_INT);
ld->flowint[ld->flowints++] = idx;
SCLogDebug("script uses flowint %u with script id %u", idx, ld->flowints - 1);
}

@ -42,10 +42,10 @@ typedef struct DetectLuaData {
uint32_t flags;
AppProto alproto;
char *buffername; /* buffer name in case of a single buffer */
uint16_t flowint[DETECT_LUAJIT_MAX_FLOWINTS];
uint32_t flowint[DETECT_LUAJIT_MAX_FLOWINTS];
uint16_t flowints;
uint16_t flowvar[DETECT_LUAJIT_MAX_FLOWVARS];
uint16_t flowvars;
uint32_t flowvar[DETECT_LUAJIT_MAX_FLOWVARS];
uint32_t sid;
uint32_t rev;
uint32_t gid;

@ -657,9 +657,9 @@ static int DetectPcreParseCapture(char *regexstr, DetectEngineCtx *de_ctx, Detec
}
if (pd->capname != NULL) {
if (pd->flags & DETECT_PCRE_CAPTURE_PKT)
pd->capidx = VariableNameGetIdx(de_ctx, (char *)pd->capname, VAR_TYPE_PKT_VAR);
pd->capidx = VarNameStoreSetupAdd((char *)pd->capname, VAR_TYPE_PKT_VAR);
else if (pd->flags & DETECT_PCRE_CAPTURE_FLOW)
pd->capidx = VariableNameGetIdx(de_ctx, (char *)pd->capname, VAR_TYPE_FLOW_VAR);
pd->capidx = VarNameStoreSetupAdd((char *)pd->capname, VAR_TYPE_FLOW_VAR);
}
SCLogDebug("pd->capname %s", pd->capname);

@ -39,7 +39,7 @@ typedef struct DetectPcreData_ {
pcre_extra *sd;
int opts;
uint16_t flags;
uint16_t capidx;
uint32_t capidx;
char *capname;
} DetectPcreData;

@ -304,7 +304,7 @@ static int DetectXbitParse(DetectEngineCtx *de_ctx,
if (unlikely(cd == NULL))
return -1;
cd->idx = VariableNameGetIdx(de_ctx, fb_name, var_type);
cd->idx = VarNameStoreSetupAdd(fb_name, var_type);
cd->cmd = fb_cmd;
cd->tracker = hb_dir;
cd->type = var_type;

@ -40,7 +40,7 @@
#define DETECT_XBITS_EXPIRE_DEFAULT 30
typedef struct DetectXbitsData_ {
uint16_t idx;
uint32_t idx;
uint8_t cmd;
uint8_t tracker;
uint32_t expire;

@ -890,7 +890,7 @@ static void AlertDebugLogModeSyncFlowbitsNamesToPacketStruct(Packet *p, DetectEn
}
FlowBit *fb = (FlowBit *) gv;
const char *name = VariableIdxGetName(de_ctx, fb->idx, VAR_TYPE_FLOW_BIT);
const char *name = VarNameStoreLookupById(fb->idx, VAR_TYPE_FLOW_BIT);
if (name != NULL) {
p->debuglog_flowbits_names[i] = SCStrdup(name);
if (p->debuglog_flowbits_names[i] == NULL) {
@ -3858,6 +3858,10 @@ int SigGroupBuild(DetectEngineCtx *de_ctx)
#endif
SCFree(de_ctx->app_mpms);
de_ctx->app_mpms = NULL;
if (!DetectEngineMultiTenantEnabled()) {
VarNameStoreActivateStaging();
}
return 0;
}

@ -47,6 +47,8 @@
#include "stream.h"
#include "util-var-name.h"
#define DETECT_MAX_RULE_SIZE 8192
/* forward declarations for the structures from detect-engine-sigorder.h */
@ -495,7 +497,7 @@ typedef struct DetectReplaceList_ {
/** list for flowvar store candidates, to be stored from
* post-match function */
typedef struct DetectFlowvarList_ {
uint16_t idx; /**< flowvar name idx */
uint32_t idx; /**< flowvar name idx */
uint16_t len; /**< data len */
int type; /**< type of store candidate POSTMATCH or ALWAYS */
uint8_t *buffer; /**< alloc'd buffer, may be freed by
@ -618,10 +620,6 @@ typedef struct DetectEngineCtx_ {
HashListTable *mpm_hash_table;
HashListTable *variable_names;
HashListTable *variable_idxs;
uint16_t variable_names_idx;
/* hash table used to cull out duplicate sigs */
HashListTable *dup_sig_hash_table;

@ -43,7 +43,7 @@
#include "util-unittest.h"
/* get the flowbit with idx from the flow */
static FlowBit *FlowBitGet(Flow *f, uint16_t idx)
static FlowBit *FlowBitGet(Flow *f, uint32_t idx)
{
GenericVar *gv = f->flowvar;
for ( ; gv != NULL; gv = gv->next) {
@ -56,7 +56,7 @@ static FlowBit *FlowBitGet(Flow *f, uint16_t idx)
}
/* add a flowbit to the flow */
static void FlowBitAdd(Flow *f, uint16_t idx)
static void FlowBitAdd(Flow *f, uint32_t idx)
{
FlowBit *fb = FlowBitGet(f, idx);
if (fb == NULL) {
@ -71,7 +71,7 @@ static void FlowBitAdd(Flow *f, uint16_t idx)
}
}
static void FlowBitRemove(Flow *f, uint16_t idx)
static void FlowBitRemove(Flow *f, uint32_t idx)
{
FlowBit *fb = FlowBitGet(f, idx);
if (fb == NULL)
@ -81,17 +81,17 @@ static void FlowBitRemove(Flow *f, uint16_t idx)
FlowBitFree(fb);
}
void FlowBitSet(Flow *f, uint16_t idx)
void FlowBitSet(Flow *f, uint32_t idx)
{
FlowBitAdd(f, idx);
}
void FlowBitUnset(Flow *f, uint16_t idx)
void FlowBitUnset(Flow *f, uint32_t idx)
{
FlowBitRemove(f, idx);
}
void FlowBitToggle(Flow *f, uint16_t idx)
void FlowBitToggle(Flow *f, uint32_t idx)
{
FlowBit *fb = FlowBitGet(f, idx);
if (fb != NULL) {
@ -101,7 +101,7 @@ void FlowBitToggle(Flow *f, uint16_t idx)
}
}
int FlowBitIsset(Flow *f, uint16_t idx)
int FlowBitIsset(Flow *f, uint32_t idx)
{
int r = 0;
@ -113,7 +113,7 @@ int FlowBitIsset(Flow *f, uint16_t idx)
return r;
}
int FlowBitIsnotset(Flow *f, uint16_t idx)
int FlowBitIsnotset(Flow *f, uint32_t idx)
{
int r = 0;

@ -29,7 +29,7 @@
typedef struct FlowBit_ {
uint8_t type; /* type, DETECT_FLOWBITS in this case */
uint16_t idx; /* name idx */
uint32_t idx; /* name idx */
GenericVar *next; /* right now just implement this as a list,
* in the long run we have think of something
* faster. */
@ -38,10 +38,10 @@ typedef struct FlowBit_ {
void FlowBitFree(FlowBit *);
void FlowBitRegisterTests(void);
void FlowBitSet(Flow *, uint16_t);
void FlowBitUnset(Flow *, uint16_t);
void FlowBitToggle(Flow *, uint16_t);
int FlowBitIsset(Flow *, uint16_t);
int FlowBitIsnotset(Flow *, uint16_t);
void FlowBitSet(Flow *, uint32_t);
void FlowBitUnset(Flow *, uint32_t);
void FlowBitToggle(Flow *, uint32_t);
int FlowBitIsset(Flow *, uint32_t);
int FlowBitIsnotset(Flow *, uint32_t);
#endif /* __FLOW_BIT_H__ */

@ -51,7 +51,7 @@ static void FlowVarUpdateInt(FlowVar *fv, uint32_t value)
* \note flow is not locked by this function, caller is
* responsible
*/
FlowVar *FlowVarGet(Flow *f, uint16_t idx)
FlowVar *FlowVarGet(Flow *f, uint32_t idx)
{
GenericVar *gv = f->flowvar;
@ -64,7 +64,7 @@ FlowVar *FlowVarGet(Flow *f, uint16_t idx)
}
/* add a flowvar to the flow, or update it */
void FlowVarAddStrNoLock(Flow *f, uint16_t idx, uint8_t *value, uint16_t size)
void FlowVarAddStrNoLock(Flow *f, uint32_t idx, uint8_t *value, uint16_t size)
{
FlowVar *fv = FlowVarGet(f, idx);
if (fv == NULL) {
@ -86,13 +86,13 @@ void FlowVarAddStrNoLock(Flow *f, uint16_t idx, uint8_t *value, uint16_t size)
}
/* add a flowvar to the flow, or update it */
void FlowVarAddStr(Flow *f, uint16_t idx, uint8_t *value, uint16_t size)
void FlowVarAddStr(Flow *f, uint32_t idx, uint8_t *value, uint16_t size)
{
FlowVarAddStrNoLock(f, idx, value, size);
}
/* add a flowvar to the flow, or update it */
void FlowVarAddIntNoLock(Flow *f, uint16_t idx, uint32_t value)
void FlowVarAddIntNoLock(Flow *f, uint32_t idx, uint32_t value)
{
FlowVar *fv = FlowVarGet(f, idx);
if (fv == NULL) {
@ -113,7 +113,7 @@ void FlowVarAddIntNoLock(Flow *f, uint16_t idx, uint32_t value)
}
/* add a flowvar to the flow, or update it */
void FlowVarAddInt(Flow *f, uint16_t idx, uint32_t value)
void FlowVarAddInt(Flow *f, uint32_t idx, uint32_t value)
{
FlowVarAddIntNoLock(f, idx, value);
}
@ -144,7 +144,7 @@ void FlowVarPrint(GenericVar *gv)
FlowVar *fv = (FlowVar *)gv;
if (fv->datatype == FLOWVAR_TYPE_STR) {
SCLogDebug("Name idx \"%" PRIu16 "\", Value \"", fv->idx);
SCLogDebug("Name idx \"%" PRIu32 "\", Value \"", fv->idx);
for (u = 0; u < fv->data.fv_str.value_len; u++) {
if (isprint(fv->data.fv_str.value[u]))
SCLogDebug("%c", fv->data.fv_str.value[u]);
@ -153,7 +153,7 @@ void FlowVarPrint(GenericVar *gv)
}
SCLogDebug("\", Len \"%" PRIu16 "\"\n", fv->data.fv_str.value_len);
} else if (fv->datatype == FLOWVAR_TYPE_INT) {
SCLogDebug("Name idx \"%" PRIu16 "\", Value \"%" PRIu32 "\"", fv->idx,
SCLogDebug("Name idx \"%" PRIu32 "\", Value \"%" PRIu32 "\"", fv->idx,
fv->data.fv_int.value);
} else {
SCLogDebug("Unknown data type at flowvars\n");

@ -47,7 +47,7 @@ typedef struct FlowVarTypeInt_ {
/** Generic Flowvar Structure */
typedef struct FlowVar_ {
uint8_t type; /* type, DETECT_FLOWVAR in this case */
uint16_t idx; /* name idx */
uint32_t idx; /* name idx */
GenericVar *next; /* right now just implement this as a list,
* in the long run we have think of something
* faster. */
@ -61,11 +61,11 @@ typedef struct FlowVar_ {
/** Flowvar Interface API */
void FlowVarAddStrNoLock(Flow *, uint16_t, uint8_t *, uint16_t);
void FlowVarAddStr(Flow *, uint16_t, uint8_t *, uint16_t);
void FlowVarAddIntNoLock(Flow *, uint16_t, uint32_t);
void FlowVarAddInt(Flow *, uint16_t, uint32_t);
FlowVar *FlowVarGet(Flow *, uint16_t);
void FlowVarAddStrNoLock(Flow *, uint32_t, uint8_t *, uint16_t);
void FlowVarAddStr(Flow *, uint32_t, uint8_t *, uint16_t);
void FlowVarAddIntNoLock(Flow *, uint32_t, uint32_t);
void FlowVarAddInt(Flow *, uint32_t, uint32_t);
FlowVar *FlowVarGet(Flow *, uint32_t);
void FlowVarFree(FlowVar *);
void FlowVarPrint(GenericVar *);

@ -78,7 +78,7 @@ int HostBitsTimedoutCheck(Host *h, struct timeval *ts)
}
/* get the bit with idx from the host */
static XBit *HostBitGet(Host *h, uint16_t idx)
static XBit *HostBitGet(Host *h, uint32_t idx)
{
GenericVar *gv = HostGetStorageById(h, host_bit_id);
for ( ; gv != NULL; gv = gv->next) {
@ -91,7 +91,7 @@ static XBit *HostBitGet(Host *h, uint16_t idx)
}
/* add a flowbit to the flow */
static void HostBitAdd(Host *h, uint16_t idx, uint32_t expire)
static void HostBitAdd(Host *h, uint32_t idx, uint32_t expire)
{
XBit *fb = HostBitGet(h, idx);
if (fb == NULL) {
@ -114,7 +114,7 @@ static void HostBitAdd(Host *h, uint16_t idx, uint32_t expire)
}
}
static void HostBitRemove(Host *h, uint16_t idx)
static void HostBitRemove(Host *h, uint32_t idx)
{
XBit *fb = HostBitGet(h, idx);
if (fb == NULL)
@ -128,7 +128,7 @@ static void HostBitRemove(Host *h, uint16_t idx)
}
}
void HostBitSet(Host *h, uint16_t idx, uint32_t expire)
void HostBitSet(Host *h, uint32_t idx, uint32_t expire)
{
XBit *fb = HostBitGet(h, idx);
if (fb == NULL) {
@ -136,7 +136,7 @@ void HostBitSet(Host *h, uint16_t idx, uint32_t expire)
}
}
void HostBitUnset(Host *h, uint16_t idx)
void HostBitUnset(Host *h, uint32_t idx)
{
XBit *fb = HostBitGet(h, idx);
if (fb != NULL) {
@ -144,7 +144,7 @@ void HostBitUnset(Host *h, uint16_t idx)
}
}
void HostBitToggle(Host *h, uint16_t idx, uint32_t expire)
void HostBitToggle(Host *h, uint32_t idx, uint32_t expire)
{
XBit *fb = HostBitGet(h, idx);
if (fb != NULL) {
@ -154,7 +154,7 @@ void HostBitToggle(Host *h, uint16_t idx, uint32_t expire)
}
}
int HostBitIsset(Host *h, uint16_t idx, uint32_t ts)
int HostBitIsset(Host *h, uint32_t idx, uint32_t ts)
{
XBit *fb = HostBitGet(h, idx);
if (fb != NULL) {
@ -167,7 +167,7 @@ int HostBitIsset(Host *h, uint16_t idx, uint32_t ts)
return 0;
}
int HostBitIsnotset(Host *h, uint16_t idx, uint32_t ts)
int HostBitIsnotset(Host *h, uint32_t idx, uint32_t ts)
{
XBit *fb = HostBitGet(h, idx);
if (fb == NULL) {

@ -33,9 +33,9 @@ void HostBitRegisterTests(void);
int HostHasHostBits(Host *host);
int HostBitsTimedoutCheck(Host *h, struct timeval *ts);
void HostBitSet(Host *, uint16_t, uint32_t);
void HostBitUnset(Host *, uint16_t);
void HostBitToggle(Host *, uint16_t, uint32_t);
int HostBitIsset(Host *, uint16_t, uint32_t);
int HostBitIsnotset(Host *, uint16_t, uint32_t);
void HostBitSet(Host *, uint32_t, uint32_t);
void HostBitUnset(Host *, uint32_t);
void HostBitToggle(Host *, uint32_t, uint32_t);
int HostBitIsset(Host *, uint32_t, uint32_t);
int HostBitIsnotset(Host *, uint32_t, uint32_t);
#endif /* __HOST_BIT_H__ */

@ -78,7 +78,7 @@ int IPPairBitsTimedoutCheck(IPPair *h, struct timeval *ts)
}
/* get the bit with idx from the ippair */
static XBit *IPPairBitGet(IPPair *h, uint16_t idx)
static XBit *IPPairBitGet(IPPair *h, uint32_t idx)
{
GenericVar *gv = IPPairGetStorageById(h, ippair_bit_id);
for ( ; gv != NULL; gv = gv->next) {
@ -91,7 +91,7 @@ static XBit *IPPairBitGet(IPPair *h, uint16_t idx)
}
/* add a flowbit to the flow */
static void IPPairBitAdd(IPPair *h, uint16_t idx, uint32_t expire)
static void IPPairBitAdd(IPPair *h, uint32_t idx, uint32_t expire)
{
XBit *fb = IPPairBitGet(h, idx);
if (fb == NULL) {
@ -114,7 +114,7 @@ static void IPPairBitAdd(IPPair *h, uint16_t idx, uint32_t expire)
}
}
static void IPPairBitRemove(IPPair *h, uint16_t idx)
static void IPPairBitRemove(IPPair *h, uint32_t idx)
{
XBit *fb = IPPairBitGet(h, idx);
if (fb == NULL)
@ -127,7 +127,7 @@ static void IPPairBitRemove(IPPair *h, uint16_t idx)
}
}
void IPPairBitSet(IPPair *h, uint16_t idx, uint32_t expire)
void IPPairBitSet(IPPair *h, uint32_t idx, uint32_t expire)
{
XBit *fb = IPPairBitGet(h, idx);
if (fb == NULL) {
@ -135,7 +135,7 @@ void IPPairBitSet(IPPair *h, uint16_t idx, uint32_t expire)
}
}
void IPPairBitUnset(IPPair *h, uint16_t idx)
void IPPairBitUnset(IPPair *h, uint32_t idx)
{
XBit *fb = IPPairBitGet(h, idx);
if (fb != NULL) {
@ -143,7 +143,7 @@ void IPPairBitUnset(IPPair *h, uint16_t idx)
}
}
void IPPairBitToggle(IPPair *h, uint16_t idx, uint32_t expire)
void IPPairBitToggle(IPPair *h, uint32_t idx, uint32_t expire)
{
XBit *fb = IPPairBitGet(h, idx);
if (fb != NULL) {
@ -153,7 +153,7 @@ void IPPairBitToggle(IPPair *h, uint16_t idx, uint32_t expire)
}
}
int IPPairBitIsset(IPPair *h, uint16_t idx, uint32_t ts)
int IPPairBitIsset(IPPair *h, uint32_t idx, uint32_t ts)
{
XBit *fb = IPPairBitGet(h, idx);
if (fb != NULL) {
@ -167,7 +167,7 @@ int IPPairBitIsset(IPPair *h, uint16_t idx, uint32_t ts)
return 0;
}
int IPPairBitIsnotset(IPPair *h, uint16_t idx, uint32_t ts)
int IPPairBitIsnotset(IPPair *h, uint32_t idx, uint32_t ts)
{
XBit *fb = IPPairBitGet(h, idx);
if (fb == NULL) {

@ -33,10 +33,10 @@ void IPPairBitRegisterTests(void);
int IPPairHasBits(IPPair *host);
int IPPairBitsTimedoutCheck(IPPair *h, struct timeval *ts);
void IPPairBitSet(IPPair *, uint16_t, uint32_t);
void IPPairBitUnset(IPPair *, uint16_t);
void IPPairBitToggle(IPPair *, uint16_t, uint32_t);
int IPPairBitIsset(IPPair *, uint16_t, uint32_t);
int IPPairBitIsnotset(IPPair *, uint16_t, uint32_t);
void IPPairBitSet(IPPair *, uint32_t, uint32_t);
void IPPairBitUnset(IPPair *, uint32_t);
void IPPairBitToggle(IPPair *, uint32_t, uint32_t);
int IPPairBitIsset(IPPair *, uint32_t, uint32_t);
int IPPairBitIsnotset(IPPair *, uint32_t, uint32_t);
#endif /* __IPPAIR_BIT_H__ */

@ -26,12 +26,43 @@
#include "suricata-common.h"
#include "detect.h"
#include "util-hashlist.h"
#include "util-var-name.h"
/* the way this can be used w/o locking lookups:
* - Lookups use only g_varnamestore_current which is read only
* - Detection setups a new ctx in staging, which will include the 'current'
* entries keeping ID's stable
* - Detection hot swaps staging into current after a new detect engine was
* created. Current remains available through 'old'.
* - When detect reload is complete (threads are all moved over), 'old' can
* be freed.
*/
typedef struct VarNameStore_ {
HashListTable *names;
HashListTable *ids;
uint32_t max_id;
uint32_t de_ctx_version; /**< de_ctx version 'owning' this */
} VarNameStore;
static int initialized = 0;
/* currently VarNameStore that is READ ONLY. This way lookups can
* be done w/o locking or synchronization */
SC_ATOMIC_DECLARE(VarNameStore *, g_varnamestore_current);
/* old VarNameStore on the way out */
static VarNameStore *g_varnamestore_old = NULL;
/* new VarNameStore that is being prepared. Multiple DetectLoader threads
* may be updating it so a lock is used for synchronization. */
static VarNameStore *g_varnamestore_staging = NULL;
static SCMutex g_varnamestore_staging_m = SCMUTEX_INITIALIZER;
/** \brief Name2idx mapping structure for flowbits, flowvars and pktvars. */
typedef struct VariableName_ {
char *name;
uint8_t type; /* flowbit, pktvar, etc */
uint16_t idx;
uint32_t idx;
} VariableName;
static uint32_t VariableNameHash(HashListTable *ht, void *buf, uint16_t buflen)
@ -98,45 +129,49 @@ static void VariableNameFree(void *data)
}
/** \brief Initialize the Name idx hash.
* \param de_ctx Ptr to the detection engine ctx.
* \retval -1 in case of error
* \retval 0 in case of success
*/
int VariableNameInitHash(DetectEngineCtx *de_ctx)
static VarNameStore *VarNameStoreInit(void)
{
de_ctx->variable_names = HashListTableInit(4096, VariableNameHash, VariableNameCompare, VariableNameFree);
if (de_ctx->variable_names == NULL)
return -1;
VarNameStore *v = SCCalloc(1, sizeof(*v));
if (v == NULL)
return NULL;
v->names = HashListTableInit(4096, VariableNameHash, VariableNameCompare, VariableNameFree);
if (v->names == NULL) {
SCFree(v);
return NULL;
}
de_ctx->variable_idxs = HashListTableInit(4096, VariableIdxHash, VariableIdxCompare, NULL);
if (de_ctx->variable_idxs == NULL)
return -1;
v->ids = HashListTableInit(4096, VariableIdxHash, VariableIdxCompare, NULL);
if (v->ids == NULL) {
HashListTableFree(v->names);
SCFree(v);
return NULL;
}
de_ctx->variable_names_idx = 0;
return 0;
v->max_id = 0;
return v;
}
void VariableNameFreeHash(DetectEngineCtx *de_ctx)
static void VarNameStoreDoFree(VarNameStore *v)
{
if (de_ctx->variable_names != NULL) {
HashListTableFree(de_ctx->variable_names);
HashListTableFree(de_ctx->variable_idxs);
de_ctx->variable_names = NULL;
de_ctx->variable_idxs = NULL;
if (v) {
HashListTableFree(v->names);
HashListTableFree(v->ids);
SCFree(v);
}
return;
}
/** \brief Get a name idx for a name. If the name is already used reuse the idx.
* \param name nul terminated string with the name
* \param type variable type
* \retval 0 in case of error
* \retval idx the idx or 0
*/
uint16_t VariableNameGetIdx(DetectEngineCtx *de_ctx, const char *name, enum VarTypes type)
static uint32_t VariableNameGetIdx(VarNameStore *v, const char *name, enum VarTypes type)
{
uint16_t idx = 0;
uint32_t idx = 0;
VariableName *fn = SCMalloc(sizeof(VariableName));
if (unlikely(fn == NULL))
@ -149,13 +184,14 @@ uint16_t VariableNameGetIdx(DetectEngineCtx *de_ctx, const char *name, enum VarT
if (fn->name == NULL)
goto error;
VariableName *lookup_fn = (VariableName *)HashListTableLookup(de_ctx->variable_names, (void *)fn, 0);
VariableName *lookup_fn = (VariableName *)HashListTableLookup(v->names, (void *)fn, 0);
if (lookup_fn == NULL) {
de_ctx->variable_names_idx++;
v->max_id++;
idx = fn->idx = de_ctx->variable_names_idx;
HashListTableAdd(de_ctx->variable_names, (void *)fn, 0);
HashListTableAdd(de_ctx->variable_idxs, (void *)fn, 0);
idx = fn->idx = v->max_id;
HashListTableAdd(v->names, (void *)fn, 0);
HashListTableAdd(v->ids, (void *)fn, 0);
SCLogDebug("new registration %s id %u type %u", fn->name, fn->idx, fn->type);
} else {
idx = lookup_fn->idx;
VariableNameFree(fn);
@ -167,13 +203,15 @@ error:
return 0;
}
#if 0
/** \brief Get a name from the idx.
* \param idx index of the variable whose name is to be fetched
* \param type variable type
* \retval NULL in case of error
* \retval name of the variable if successful.
* \todo no alloc on lookup
*/
const char *VariableIdxGetName(DetectEngineCtx *de_ctx, uint16_t idx, enum VarTypes type)
static const char *VariableIdxGetName(VarNameStore *v, uint32_t idx, enum VarTypes type)
{
VariableName *fn = SCMalloc(sizeof(VariableName));
if (unlikely(fn == NULL))
@ -185,7 +223,7 @@ const char *VariableIdxGetName(DetectEngineCtx *de_ctx, uint16_t idx, enum VarTy
fn->type = type;
fn->idx = idx;
VariableName *lookup_fn = (VariableName *)HashListTableLookup(de_ctx->variable_idxs, (void *)fn, 0);
VariableName *lookup_fn = (VariableName *)HashListTableLookup(v->ids, (void *)fn, 0);
if (lookup_fn != NULL) {
name = SCStrdup(lookup_fn->name);
if (unlikely(name == NULL))
@ -201,3 +239,141 @@ error:
VariableNameFree(fn);
return NULL;
}
#endif
/** \brief setup staging store. Include current store if there is one.
*/
int VarNameStoreSetupStaging(uint32_t de_ctx_version)
{
SCMutexLock(&g_varnamestore_staging_m);
if (!initialized) {
SC_ATOMIC_INIT(g_varnamestore_current);
initialized = 1;
}
if (g_varnamestore_staging != NULL &&
g_varnamestore_staging->de_ctx_version == de_ctx_version) {
SCMutexUnlock(&g_varnamestore_staging_m);
return 0;
}
VarNameStore *nv = VarNameStoreInit();
if (nv == NULL) {
SCMutexUnlock(&g_varnamestore_staging_m);
return -1;
}
g_varnamestore_staging = nv;
nv->de_ctx_version = de_ctx_version;
VarNameStore *current = SC_ATOMIC_GET(g_varnamestore_current);
if (current) {
/* add all entries from the current hash into this new one. */
HashListTableBucket *b = HashListTableGetListHead(current->names);
while (b) {
VariableName *var = HashListTableGetListData(b);
VariableName *newvar = SCCalloc(1, sizeof(*newvar));
BUG_ON(newvar == NULL);
memcpy(newvar, var, sizeof(*newvar));
newvar->name = SCStrdup(var->name);
BUG_ON(newvar->name == NULL);
HashListTableAdd(nv->names, (void *)newvar, 0);
HashListTableAdd(nv->ids, (void *)newvar, 0);
nv->max_id = MAX(nv->max_id, newvar->idx);
SCLogDebug("xfer %s id %u type %u", newvar->name, newvar->idx, newvar->type);
b = HashListTableGetListNext(b);
}
}
SCLogDebug("set up staging with detect engine ver %u", nv->de_ctx_version);
SCMutexUnlock(&g_varnamestore_staging_m);
return 0;
}
const char *VarNameStoreLookupById(const uint32_t id, const enum VarTypes type)
{
VarNameStore *current = SC_ATOMIC_GET(g_varnamestore_current);
BUG_ON(current == NULL);
VariableName lookup = { NULL, type, id };
VariableName *found = (VariableName *)HashListTableLookup(current->ids, (void *)&lookup, 0);
if (found == NULL) {
return NULL;
}
return found->name;
}
uint32_t VarNameStoreLookupByName(const char *name, const enum VarTypes type)
{
VarNameStore *current = SC_ATOMIC_GET(g_varnamestore_current);
BUG_ON(current == NULL);
VariableName lookup = { (char *)name, type, 0 };
VariableName *found = (VariableName *)HashListTableLookup(current->names, (void *)&lookup, 0);
if (found == NULL) {
return 0;
}
SCLogDebug("found %u for %s type %u", found->idx, name, type);
return found->idx;
}
/** \brief add to staging or return existing id if already in there */
uint32_t VarNameStoreSetupAdd(const char *name, const enum VarTypes type)
{
uint32_t id;
SCMutexLock(&g_varnamestore_staging_m);
id = VariableNameGetIdx(g_varnamestore_staging, name, type);
SCMutexUnlock(&g_varnamestore_staging_m);
return id;
}
void VarNameStoreActivateStaging(void)
{
SCMutexLock(&g_varnamestore_staging_m);
if (g_varnamestore_old) {
VarNameStoreDoFree(g_varnamestore_old);
g_varnamestore_old = NULL;
}
g_varnamestore_old = SC_ATOMIC_GET(g_varnamestore_current);
SC_ATOMIC_SET(g_varnamestore_current, g_varnamestore_staging);
g_varnamestore_staging = NULL;
SCMutexUnlock(&g_varnamestore_staging_m);
}
void VarNameStoreFreeOld(void)
{
SCMutexLock(&g_varnamestore_staging_m);
SCLogDebug("freeing g_varnamestore_old %p", g_varnamestore_old);
if (g_varnamestore_old) {
VarNameStoreDoFree(g_varnamestore_old);
g_varnamestore_old = NULL;
}
SCMutexUnlock(&g_varnamestore_staging_m);
}
void VarNameStoreFree(uint32_t de_ctx_version)
{
SCLogDebug("freeing detect engine version %u", de_ctx_version);
SCMutexLock(&g_varnamestore_staging_m);
if (g_varnamestore_old && g_varnamestore_old->de_ctx_version == de_ctx_version) {
VarNameStoreDoFree(g_varnamestore_old);
g_varnamestore_old = NULL;
SCLogDebug("freeing detect engine version %u: old done", de_ctx_version);
}
/* if at this point we have a staging area which matches our version
* we didn't complete the setup and are cleaning up the mess. */
if (g_varnamestore_staging && g_varnamestore_staging->de_ctx_version == de_ctx_version) {
VarNameStoreDoFree(g_varnamestore_staging);
g_varnamestore_staging = NULL;
SCLogDebug("freeing detect engine version %u: staging done", de_ctx_version);
}
VarNameStore *current = SC_ATOMIC_GET(g_varnamestore_current);
if (current && current->de_ctx_version == de_ctx_version) {
VarNameStoreDoFree(current);
SC_ATOMIC_SET(g_varnamestore_current, NULL);
SCLogDebug("freeing detect engine version %u: current done", de_ctx_version);
}
SCMutexUnlock(&g_varnamestore_staging_m);
}

@ -1,4 +1,4 @@
/* Copyright (C) 2007-2010 Open Information Security Foundation
/* Copyright (C) 2007-2016 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
@ -24,13 +24,13 @@
#ifndef __UTIL_VAR_NAME_H__
#define __UTIL_VAR_NAME_H__
int VariableNameInitHash(DetectEngineCtx *);
void VariableNameFreeHash(DetectEngineCtx *);
uint16_t VariableNameGetIdx(DetectEngineCtx *,
const char *name, enum VarTypes type);
const char *VariableIdxGetName(DetectEngineCtx *,
uint16_t id, enum VarTypes type);
int VarNameStoreSetupStaging(uint32_t de_ctx_version);
const char *VarNameStoreLookupById(const uint32_t id, const enum VarTypes type);
uint32_t VarNameStoreLookupByName(const char *name, const enum VarTypes type);
uint32_t VarNameStoreSetupAdd(const char *name, const enum VarTypes type);
void VarNameStoreActivateStaging(void);
void VarNameStoreFreeOld(void);
void VarNameStoreFree(uint32_t de_ctx_version);
#endif

@ -46,13 +46,13 @@ enum VarTypes {
typedef struct GenericVar_ {
uint8_t type;
uint16_t idx;
uint32_t idx;
struct GenericVar_ *next;
} GenericVar;
typedef struct XBit_ {
uint8_t type; /* type, DETECT_XBITS in this case */
uint16_t idx; /* name idx */
uint32_t idx; /* name idx */
GenericVar *next;
uint32_t expire;
} XBit;

Loading…
Cancel
Save