]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
lua: add initial suricata.flow lib
authorJuliana Fajardini <jufajardini@oisf.net>
Tue, 25 Mar 2025 21:53:19 +0000 (14:53 -0700)
committerVictor Julien <victor@inliniac.net>
Fri, 28 Mar 2025 05:59:38 +0000 (06:59 +0100)
Methods:
`get` creates the flow object.
`id`  returns the flow id.
`has_alerts` returns a boolean indicating if the flow triggered alerts.
`app_layer_proto` returns various app-layer related fields as 5 strings:
  alproto, alproto_ts, alproto_tc, alproto_orig, alproto_expect.
`stats` returns cnts for bytes and packets to sever and to client, as 4
  numbers.
`tuple` -- returns various fields: srcip, dstip, proto, sp, dp.
`timestamps` returns time as 4 numbers: seconds and microseconds, for
  first and last packet of the flow.
`timestring_legacy` returns the first packet from the flow's timestring
  as a string (like fastlog).
`timestring_iso8601` returns the first packet from the flow's
  timestring as an iso8601 compat timestring (like eve).

Example:

```
name = "lua-scflowstats.log"

local flow = require("suricata.flow")

function init(args)
    local needs = {}
    needs["type"] = "flow"
    return needs
end

function setup(args)
    filename = SCLogPath() .. "/" .. name
    file = assert(io.open(filename, "a"))
    SCLogInfo("lua SCFlowStats Log Filename " .. filename)
end

function log(args)
    local f = flow.get()
    timestring = f:timestring_legacy()
    tscnt, tsbytes, tccnt, tcbytes = f:stats()

   file:write ("[**] " .. timestring .. "\nSCFlowStats is\nPacket count to server:  " .. tscnt .. "\nByte count to server: " .. tsbytes .. "\nPacket count to client: " .. tccnt .. "\nByte count to client: " .. tcbytes .. "\n[**]")
    file:flush()
end

function deinit(args)
    file:close(file)
end
```

Task #7489

src/Makefile.am
src/util-lua-builtins.c
src/util-lua-flowlib.c [new file with mode: 0644]
src/util-lua-flowlib.h [new file with mode: 0644]

index 8fd7e20172071b5a4feba3f368ae552988d8a43c..fd950962b59fd06f1a3a5ace845dc0e6a04da3b1 100755 (executable)
@@ -520,6 +520,7 @@ noinst_HEADERS = \
        util-lua-dnp3.h \
        util-lua-dnp3-objects.h \
        util-lua-dns.h \
+       util-lua-flowlib.h \
        util-lua.h \
        util-lua-hashlib.h \
        util-lua-hassh.h \
@@ -1079,6 +1080,7 @@ libsuricata_c_a_SOURCES = \
        util-lua-dnp3.c \
        util-lua-dnp3-objects.c \
        util-lua-dns.c \
+       util-lua-flowlib.c \
        util-lua-hashlib.c \
        util-lua-hassh.c \
        util-lua-http.c \
index d03e56d22422ecf52f511c1f79292b6b77ec0e5d..8b511cbc77862de58f757ef15781c18846397d9d 100644 (file)
@@ -21,6 +21,7 @@
 #include "util-lua-dataset.h"
 #include "util-lua-dnp3.h"
 #include "util-lua-dns.h"
+#include "util-lua-flowlib.h"
 #include "util-lua-hashlib.h"
 #include "util-lua-packetlib.h"
 
@@ -31,6 +32,7 @@ static const luaL_Reg builtins[] = {
     { "suricata.dataset", LuaLoadDatasetLib },
     { "suricata.dnp3", SCLuaLoadDnp3Lib },
     { "suricata.dns", SCLuaLoadDnsLib },
+    { "suricata.flow", LuaLoadFlowLib },
     { "suricata.hashlib", SCLuaLoadHashlib },
     { "suricata.packet", LuaLoadPacketLib },
     { NULL, NULL },
diff --git a/src/util-lua-flowlib.c b/src/util-lua-flowlib.c
new file mode 100644 (file)
index 0000000..67c429f
--- /dev/null
@@ -0,0 +1,295 @@
+/* Copyright (C) 2025 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
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * Flow API fow Lua.
+ *
+ * local flow = require("suricata.flow")
+ */
+
+#include "suricata-common.h"
+
+#include "util-lua-flowlib.h"
+
+#include "app-layer-protos.h" /* Required by util-lua-common. */
+#include "util-lua-common.h"
+#include "util-lua.h"
+#include "util-debug.h"
+#include "util-print.h"
+
+/* key for f (flow) pointer */
+extern const char lua_ext_key_f[];
+static const char suricata_flow[] = "suricata:flow";
+
+struct LuaFlow {
+    Flow *f;
+};
+
+static int LuaFlowGC(lua_State *luastate)
+{
+    SCLogDebug("gc:start");
+    struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+    SCLogDebug("flow %p", s->f);
+    s->f = NULL;
+    SCLogDebug("gc:done");
+    return 0;
+}
+
+/** \internal
+ *  \brief fill lua stack with flow id
+ *  \param luastate the lua state
+ *  \retval cnt number of data items placed on the stack
+ *
+ *  Places: flow id (number)
+ */
+static int LuaFlowId(lua_State *luastate)
+{
+    struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+    if (s == NULL || s->f == NULL) {
+        LUA_ERROR("failed to get flow");
+    }
+
+    Flow *f = s->f;
+
+    int64_t id = FlowGetId(f);
+    lua_pushinteger(luastate, id);
+    return 1;
+}
+
+/** \internal
+ *  \brief fill lua stack with AppLayerProto
+ *  \param luastate the lua state
+ *  \retval cnt number of data items placed on the stack
+ *
+ *  Places: alproto as string (string), alproto_ts as string (string),
+ *          alproto_tc as string (string), alproto_orig as string (string),
+ *          alproto_expect as string (string)
+ */
+static int LuaFlowAppLayerProto(lua_State *luastate)
+{
+    struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+    if (s == NULL || s->f == NULL) {
+        LUA_ERROR("failed to get flow");
+    }
+
+    Flow *f = s->f;
+    lua_pushstring(luastate, AppProtoToString(f->alproto));
+    lua_pushstring(luastate, AppProtoToString(f->alproto_ts));
+    lua_pushstring(luastate, AppProtoToString(f->alproto_tc));
+    lua_pushstring(luastate, AppProtoToString(f->alproto_orig));
+    lua_pushstring(luastate, AppProtoToString(f->alproto_expect));
+    return 5;
+}
+
+/** \internal
+ *  \brief fill lua stack with flow has alerts
+ *  \param luastate the lua state
+ *  \retval cnt number of data items placed on the stack
+ *
+ *  Places: alerts (bool)
+ */
+static int LuaFlowHasAlerts(lua_State *luastate)
+{
+    struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+    if (s == NULL || s->f == NULL) {
+        LUA_ERROR("failed to get flow");
+    }
+
+    Flow *f = s->f;
+    lua_pushboolean(luastate, FlowHasAlerts(f));
+    return 1;
+}
+
+/** \internal
+ *  \brief fill lua stack with flow stats
+ *  \param luastate the lua state
+ *  \retval cnt number of data items placed on the stack
+ *
+ *  Places: ts pkts (number), ts bytes (number), tc pkts (number), tc bytes (number)
+ */
+static int LuaFlowStats(lua_State *luastate)
+{
+    struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+    if (s == NULL || s->f == NULL) {
+        LUA_ERROR("failed to get flow");
+    }
+
+    Flow *f = s->f;
+    lua_pushinteger(luastate, f->todstpktcnt);
+    lua_pushinteger(luastate, f->todstbytecnt);
+    lua_pushinteger(luastate, f->tosrcpktcnt);
+    lua_pushinteger(luastate, f->tosrcbytecnt);
+    return 4;
+}
+
+/** \internal
+ *  \brief fill lua stack with flow timestamps
+ *  \param luastate the lua state
+ *  \retval cnt number of data items placed on the stack
+ *
+ *  Places: seconds (number), seconds (number), microseconds (number),
+ *          microseconds (number)
+ */
+static int LuaFlowTimestamps(lua_State *luastate)
+{
+    struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+    if (s == NULL || s->f == NULL) {
+        LUA_ERROR("failed to get flow");
+    }
+
+    Flow *f = s->f;
+    lua_pushnumber(luastate, (double)SCTIME_SECS(f->startts));
+    lua_pushnumber(luastate, (double)SCTIME_SECS(f->lastts));
+    lua_pushnumber(luastate, (double)SCTIME_USECS(f->startts));
+    lua_pushnumber(luastate, (double)SCTIME_USECS(f->lastts));
+    return 4;
+}
+
+static int LuaFlowTimestringIso8601(lua_State *luastate)
+{
+    struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+    if (s == NULL || s->f == NULL) {
+        LUA_ERROR("failed to get flow");
+    }
+
+    Flow *f = s->f;
+    char timebuf[64];
+    CreateIsoTimeString(f->startts, timebuf, sizeof(timebuf));
+    lua_pushstring(luastate, timebuf);
+    return 1;
+}
+
+/** \internal
+ *  \brief legacy format as used by fast.log, http.log, etc.
+ */
+static int LuaFlowTimestringLegacy(lua_State *luastate)
+{
+    struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+    if (s == NULL || s->f == NULL) {
+        LUA_ERROR("failed to get flow");
+    }
+
+    Flow *f = s->f;
+    char timebuf[64];
+    CreateTimeString(f->startts, timebuf, sizeof(timebuf));
+    lua_pushstring(luastate, timebuf);
+    return 1;
+}
+
+/** \internal
+ *  \brief fill lua stack with header info
+ *  \param luastate the lua state
+ *  \retval cnt number of data items placed on the stack
+ *
+ *  Places: ipver (number), src ip (string), dst ip (string), protocol (number),
+ *          sp or icmp type (number), dp or icmp code (number).
+ */
+static int LuaFlowTuple(lua_State *luastate)
+{
+    struct LuaFlow *s = (struct LuaFlow *)lua_touserdata(luastate, 1);
+    if (s == NULL || s->f == NULL) {
+        LUA_ERROR("failed to get flow");
+    }
+    Flow *f = s->f;
+    int ipver = 0;
+    if (FLOW_IS_IPV4(f)) {
+        ipver = 4;
+    } else if (FLOW_IS_IPV6(f)) {
+        ipver = 6;
+    }
+    lua_pushinteger(luastate, ipver);
+    if (ipver == 0)
+        return 1;
+
+    char srcip[46] = "", dstip[46] = "";
+    if (FLOW_IS_IPV4(f)) {
+        PrintInet(AF_INET, (const void *)&(f->src.addr_data32[0]), srcip, sizeof(srcip));
+        PrintInet(AF_INET, (const void *)&(f->dst.addr_data32[0]), dstip, sizeof(dstip));
+    } else if (FLOW_IS_IPV6(f)) {
+        PrintInet(AF_INET6, (const void *)&(f->src.address), srcip, sizeof(srcip));
+        PrintInet(AF_INET6, (const void *)&(f->dst.address), dstip, sizeof(dstip));
+    }
+
+    lua_pushstring(luastate, srcip);
+    lua_pushstring(luastate, dstip);
+
+    /* proto and ports (or type/ code) */
+    lua_pushinteger(luastate, f->proto);
+    if (f->proto == IPPROTO_TCP || f->proto == IPPROTO_UDP) {
+        lua_pushinteger(luastate, f->sp);
+        lua_pushinteger(luastate, f->dp);
+    } else if (f->proto == IPPROTO_ICMP || f->proto == IPPROTO_ICMPV6) {
+        lua_pushinteger(luastate, f->icmp_s.type);
+        lua_pushinteger(luastate, f->icmp_s.code);
+    } else {
+        lua_pushinteger(luastate, 0);
+        lua_pushinteger(luastate, 0);
+    }
+    return 6;
+}
+
+static int LuaFlowGet(lua_State *luastate)
+{
+    Flow *f = LuaStateGetFlow(luastate);
+    if (f == NULL) {
+        LUA_ERROR("failed to get flow");
+    }
+
+    struct LuaFlow *s = (struct LuaFlow *)lua_newuserdata(luastate, sizeof(*s));
+    if (s == NULL) {
+        LUA_ERROR("failed to allocate userdata");
+    }
+    s->f = f;
+    luaL_getmetatable(luastate, suricata_flow);
+    lua_setmetatable(luastate, -2);
+    return 1;
+}
+
+static const luaL_Reg flowlib[] = {
+    // clang-format off
+    { "get", LuaFlowGet },
+    { NULL, NULL }
+    // clang-format on
+};
+
+static const luaL_Reg flowlib_meta[] = {
+    // clang-format off
+    { "id", LuaFlowId },
+    { "app_layer_proto", LuaFlowAppLayerProto },
+    { "has_alerts", LuaFlowHasAlerts },
+    { "stats", LuaFlowStats },
+    { "timestamps", LuaFlowTimestamps },
+    { "timestring_iso8601", LuaFlowTimestringIso8601 },
+    { "timestring_legacy", LuaFlowTimestringLegacy },
+    { "tuple", LuaFlowTuple },
+    { "__gc", LuaFlowGC },
+    { NULL, NULL }
+    // clang-format on
+};
+
+int LuaLoadFlowLib(lua_State *luastate)
+{
+    luaL_newmetatable(luastate, suricata_flow);
+    lua_pushvalue(luastate, -1);
+    lua_setfield(luastate, -2, "__index");
+    luaL_setfuncs(luastate, flowlib_meta, 0);
+
+    luaL_newlib(luastate, flowlib);
+    return 1;
+}
diff --git a/src/util-lua-flowlib.h b/src/util-lua-flowlib.h
new file mode 100644 (file)
index 0000000..4f933e4
--- /dev/null
@@ -0,0 +1,25 @@
+/* Copyright (C) 2025 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
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef SURICATA_UTIL_LUA_FLOW_H
+#define SURICATA_UTIL_LUA_FLOW_H
+
+#include "lua.h"
+
+int LuaLoadFlowLib(lua_State *luastate);
+
+#endif /* SURICATA_UTIL_LUA_FLOW_H */