源:https://docs.zeek.org/en/stable/devel/plugins.html
一、编写插件
Bro内部提供了一个插件API,支持动态扩展系统,而不需要修改核心代码库。通过这种方式,定制代码保持自包含,并且可以独立地维护、编译和安装。目前,插件可以向Bro添加以下功能:
bro脚本。
为脚本语言构建函数/事件/类型。
协议分析器。
文件分析器。
包源和包转储程序。
日志框架后端。
输入框架阅读器。
插件的功能对于用户来说是可用的,就像Bro有相应的内置代码一样。实际上,Bro的许多组件在内部也是作为插件构造的,它们只是静态编译成二进制文件,而不是在运行时动态加载。
二、快速启动
只要遵循一些约定,编写基本插件是相当简单的。在接下来的代码中,我们创建了一个简单的示例插件,它向Bro添加了一个新的内置函数(bif):我们将添加rot13(s: string): string,一个将字符串中的每个字符旋转13个位置的函数。
通常,插件以目录的形式出现,目录遵循特定的结构。首先,Bro的发行版提供了一个helper脚本aux/ bros -aux/plugin-support/init-plugin,它创建了一个框架skeleton 插件,然后可以对其进行定制。让我们用这个:
# init-plugin ./rot13-plugin Demo Rot13
如您所见,脚本接受三个参数。第一个是一个目录,插件框架( plugin skeleton)将在其中创建。第二个是插件将驻留的名称空间( namespace),第三个是插件本身相对于名称空间的描述性名称(descriptive name)。Bro使用名称空间和名称的组合来标识插件。名称空间( namespace)用于避免独立开发人员编写的插件之间的命名冲突;选择,例如,你的组织名称作为名称空间( namespace)。名称空间(namespace)Bro保留给Bro项目分发的功能。在我们的示例中,这个插件将被称为Demo::Rot13。
init-plugin脚本放置了许多文件。稍后将描述完整的布局。现在,我们只需要src/rot13.bif。它最初是空的,但我们将添加我们的新bif如下:
# cat src/rot13.bif
module Demo;
function rot13%(s: string%) : string
%{
char* rot13 = copy_string(s->CheckString());
for ( char* p = rot13; *p; p++ )
{
char b = islower(*p) ? 'a' : 'A';
*p = (*p - b + 13) % 26 + b;
}
BroString* bs = new BroString(1, reinterpret_cast<byte_vec>(rot13),
strlen(rot13));
return new StringVal(bs);
%}
该文件的语法与任何其他*.bif文件一样;我们不在这里讨论。
现在我们已经可以编译我们的插件了,我们只需要告诉configure脚本(创建的init-plugin) Bro源代码树的位置(Bro需要先在那里构建):
# cd rot13-plugin
# ./configure --bro-dist=/path/to/bro/dist && make
[... cmake output ...]
这将在build/子目录中构建插件。事实上,该子目录成为插件:当make完成时,build/拥有Bro将其识别为动态插件所需的一切。
让我们试试。一旦我们将Bro指向build/目录,它将自动拉入我们的新插件,因为我们可以用-N选项检查:
# export BRO_PLUGIN_PATH=/path/to/rot13-plugin/build
# bro -N
[...]
Demo::Rot13 - <Insert description> (dynamic, version 0.1.0)
[...]
这看起来相当不错,除了我们应该用更好的东西替换的虚拟描述,这样用户就会知道我们的插件是关于什么的。我们通过在src/Plugin.cc中编辑config.description行来实现这一点,如下所示:
[...]
plugin::Configuration Plugin::Configure()
{
plugin::Configuration config;
config.name = "Demo::Rot13";
config.description = "Caesar cipher rotating a string's characters by 13 places.";
config.version.major = 0;
config.version.minor = 1;
config.version.patch = 0;
return config;
}
[...]
现在重建并验证描述是否可见:
# make
[...]
# bro -N | grep Rot13
Demo::Rot13 - Caesar cipher rotating a string's characters by 13 places. (dynamic, version 0.1.0)
Bro还可以向我们展示这个插件提供的更详细的选项-NN:
# bro -NN
[...]
Demo::Rot13 - Caesar cipher rotating a string's characters by 13 places. (dynamic, version 0.1.0)
[Function] Demo::rot13
[...]
我们的函数。现在让我们使用它:
# bro -e 'print Demo::rot13("Hello")'
Uryyb
它是有效的。接下来,我们将安装这个插件和Bro本身,这样它就可以直接找到它,而不需要使用BRO_PLUGIN_PATH环境变量。如果我们先取消变量设置,函数将不再可用:
# unset BRO_PLUGIN_PATH
# bro -e 'print Demo::rot13("Hello")'
error in <command line>, line 1: unknown identifier Demo::rot13, at or near "Demo::rot13"
一旦我们安装了它,它再次工作:
# make install
# bro -e 'print Demo::rot13("Hello")'
Uryyb
安装的版本进入<bro-install-prefix>/lib/bro/plugins/Demo_Rot13。
一个插件可以独立于Bro分发给其他人使用。要以源代码形式分发,只需删除build/目录(让make distclean这样做),然后tar up整个rot13-plugin/目录。其他的则在打开包装后按照上面的步骤操作。
为了以二进制形式分发插件,构建过程可以方便地在build/dist/中创建相应的tarball。在本例中,它被称为Demo_Rot13-0.1.0.tar。版本号来自init-plugin放入的版本文件。二进制tarball包含运行插件所需的所有内容,但是没有进一步的源文件。还可以通过bro_plugin_dist_files宏在插件的CMakeLists.txt中指定进一步的文件;骨架对README,VERSION,CHANGES, 和COPYING都是这样做的。要通过二进制tarball使用该插件,只需将其解压缩到/lib/bro/plugins/。或者,如果您将它解压缩到另一个位置,那么您需要将BRO_PLUGIN_PATH指向那里。
在发布插件之前,您应该编辑init-plugin放置的一些元文件。编辑README和VERSION,并在进行更改时更新更改。还应将许可证文件作为副本放置在适当的位置;如果BSD没问题,您将在copy .edit-me中找到一个模板。
三、插件目录布局
插件的目录需要遵循一组约定,以便Bro(1)将其识别为插件,并(2)知道加载什么。虽然init-plugin解决了大部分问题,但以下是全部内容。我们将使用<base>来表示插件的顶级目录。对于骨架skeleton,<base>对应于build/。
(1)<base>/__bro_plugin__
将一个目录标记为包含Bro插件的文件。文件必须存在,并且它的内容必须由带有插件限定名的一行组成(例如,“Demo::Rot13”)。
(2)<base>/lib/<plugin-name>.<os>-<arch>.so
包含插件编译代码的共享库。如果操作系统和体系结构与当前平台匹配,Bro将在运行时动态加载。
(3)scripts/
带有插件自定义Bro脚本的目录。当插件被激活时,这个目录将自动添加到BROPATH中,这样里面的任何脚本/模块都可以被“@load”。
(4)scripts/__load__.bro
当插件被激活时加载的Bro脚本。当这个脚本执行时,插件定义的任何BiF元素都是可用的。有关激活插件的更多信息,请参见下面。
(5)scripts/ __preload__.bro
一个Bro脚本,它将在插件被激活时加载,但在任何BiF元素可用之前加载。有关激活插件的更多信息,请参见下面。
(6)lib / bif /
带有自动生成的Bro脚本的目录,这些脚本声明插件的bif元素。这里的文件是由bifcl生成的。
<base>中的所有其他文件都忽视了Bro。
按照惯例,插件应该将其自定义脚本放入脚本/的子文件夹中,即scripts/<plugin-namespace>/<plugin-name>/<script>.bro,避免冲突。像往常一样,您可以放置一个 __load__.bro 也在其中,例如@load Demo/Rot13可以以多个单独脚本的形式加载整个模块。
注意,除了上面的路径之外,init-plugin helper还放置了更多的文件和目录来帮助开发和安装(例如,CMakeLists-txt、Makefile和src/中的源代码)。然而,对于Bro来说,所有这些在运行时都没有特殊的意义,插件也不需要运行。
四、init-plugin
init-plugin按照上面的布局放置一个基本的插件结构,并使用CMake构建和安装系统对其进行扩展。具有这种结构的插件既可以直接在其源目录之外使用(在make和设置Bro的BRO_PLUGIN_PATH之后),也可以在Bro旁边安装(在make install之后)。
在lib和scripts目录上安装副本,以及在CMakeLists.txt(例如README, VERSION)中指定的_bro_plugin__ magic文件和任何其他分发文件上安装副本。您可以找到安装在build/MANIFEST中的完整文件列表。在幕后,make install实际上只是将二进制tarball从build/dist解压缩到目标目录中。
init-plugin永远不会覆盖现有文件。如果目标目录已经存在,它将默认拒绝执行任何操作。您可以使用-u运行它来更新现有的插件,但是它永远不会覆盖任何现有的文件;它只会放入它还没有找到的文件。要将文件还原为init-plugin最初创建的文件,请先删除它,然后使用-u重新运行。
init-plugin放置了一个configure脚本,该脚本使用更熟悉的configure-style配置来包装cmake。默认情况下,脚本提供了两个选项,用于指定到Bro源(- bros -dist)和插件安装目录(- install-root)的路径。要使用特定于插件的选项来扩展configure(例如搜索依赖项的路径),不要直接编辑脚本,而是扩展configure.plugin, configure包括。这样,将来当发行版发生更改时,您将能够更容易地更新configure。在configure-plugin中,可以使用预定义的 shell函数append_cache_entry将值种子化到CMake缓存中;有关示例,请参阅已安装的框架版本和现有插件。
五、激活一个插件
需要激活插件才能让用户使用它。激活插件将:
(1)加载动态模块
(2)提供任何bif项目
(3)将scripts/目录添加到BROPATH
(4)加载scripts/__preload__.bro
(5)使BiF元素对脚本可用。
(6)加载脚本scripts/__load__.bro
默认情况下,Bro将自动激活在其搜索路径BRO_PLUGIN_PATH中找到的所有动态插件。但是,在裸机模式下(bro -b),默认情况下不会激活任何动态插件;相反,用户可以使用@load-plugin <qualified-plugin-name>指令(例如,@load-plugin Demo::Rot13)选择性地启用scriptland中的各个插件。</qualified-plugin-name>或者,您可以通过指定插件的全名(Demo::Rot13)从命令行激活插件,或者将环境变量BRO_PLUGIN_ACTIVATE设置为由逗号(!)分隔的插件名称列表,从而无条件地激活插件,即使是在裸机模式下。
bro -N分别显示已激活的插件和尚未激活的插件。注意,静态编译到Bro中的插件总是被激活,因此即使在裸机模式下也会这样显示。
六、插件组件
下面的小节详细介绍了通过插件提供的各种类型的功能。请注意,一个插件可以提供多个组件类型。例如,一个插件可以同时提供多个协议分析器;或者同时使用日志后端和输入读取器。