.. _zkg package manager: https://docs.zeek.org/projects/package-manager/en/stable/ .. _writing-plugins: =============== Writing Plugins =============== Zeek provides a plugin API that enables extending the system dynamically, without modifying the core code base. That way, custom code remains self-contained and can be maintained, compiled, and installed independently. Currently, plugins can add the following functionality to Zeek: - Zeek scripts. - Builtin functions/events/types for the scripting language. - Protocol analyzers. - File analyzers. - Packet sources and packet dumpers. - Logging framework backends. - Input framework readers. A plugin's functionality is available to the user just as if Zeek had the corresponding code built-in. Indeed, internally many of Zeek's pieces are structured as plugins as well, they are just statically compiled into the binary rather than loaded dynamically at runtime. .. note:: Plugins and Zeek packages are related but separate concepts. Both extend Zeek's functionality without modifying Zeek's source code. A plugin achieves this via compiled, native code that Zeek links into its core at runtime. A Zeek package, on the other hand, is a modular addition to Zeek, managed via the `zkg package manager`_, that may or may not include a plugin. More commonly, packages consist of script-layer additions to Zeek's functionality. Packages also feature more elaborate metadata, enabling dependencies on other packages, Zeek versions, etc. Quick Start =========== Writing a basic plugin is quite straight-forward as long as one follows a few conventions. In the following, we create a simple example plugin that adds a new Built-In Function (BIF) to Zeek: we'll add ``rot13(s: string) : string``, a function that rotates every letter in a string by 13 places. Generally, a plugin comes in the form of a directory following a certain structure. To get started, Zeek's distribution provides a helper script ``auxil/zeek-aux/plugin-support/init-plugin`` that creates a skeleton plugin that can then be customized. Let's use that:: # init-plugin ./rot13-plugin Demo Rot13 As you can see, the script takes three arguments. The first is a directory inside which the plugin skeleton will be created. The second is the namespace the plugin will live in, and the third is a descriptive name for the plugin itself relative to the namespace. Zeek uses the combination of namespace and name to identify a plugin. The namespace serves to avoid naming conflicts between plugins written by independent developers; pick, e.g., the name of your organisation. The namespaces ``Bro`` (legacy) and ``Zeek`` are reserved for functionality distributed by the Zeek Project. In our example, the plugin will be called ``Demo::Rot13``. The ``init-plugin`` script puts a number of files in place. The full layout is described later. For now, all we need is ``src/rot13.bif``. It's initially empty, but we'll add our new BIF there as follows:: # cat src/rot13.bif %%{ #include #include #include "zeek/util.h" #include "zeek/ZeekString.h" #include "zeek/Val.h" %%} module Demo; function rot13%(s: string%) : string %{ char* rot13 = util::copy_string(s->CheckString()); for ( char* p = rot13; *p; p++ ) { char b = islower(*p) ? 'a' : 'A'; char d = *p - b + 13; if ( d >= 13 && d <= 38 ) *p = d % 26 + b; } zeek::String* zs = new zeek::String(1, reinterpret_cast(rot13), strlen(rot13)); return make_intrusive(zs); %} The syntax of this file is just like any other ``*.bif`` file; we won't go into it here. Now we are ready to compile our plugin. The configure script will just need to be able to find the location of either a Zeek installation-tree or a Zeek source-tree. When building a plugin against a Zeek installation-tree, simply have the installation's associated ``zeek-config`` in your :envvar:`PATH` and the configure script will detect it and use it to obtain all the information it needs:: # which zeek-config /usr/local/zeek/bin/zeek-config # cd rot13-plugin # ./configure && make [... cmake output ...] When building a plugin against a Zeek source-tree (which itself needs to have first been built), the configure script has to explicitly be told its location:: # cd rot13-plugin # ./configure --zeek-dist=/path/to/zeek/dist && make [... cmake output ...] This builds the plugin in a subdirectory ``build/``. In fact, that subdirectory *becomes* the plugin: when ``make`` finishes, ``build/`` has everything it needs for Zeek to recognize it as a dynamic plugin. Let's try that. Once we point Zeek to the ``build/`` directory, it will pull in our new plugin automatically, as we can check with the ``-N`` option:: # export ZEEK_PLUGIN_PATH=/path/to/rot13-plugin/build # zeek -N [...] Demo::Rot13 - (dynamic, version 0.1.0) [...] That looks quite good, except for the dummy description that we should replace with something nicer so that users will know what our plugin is about. We do this by editing the ``config.description`` line in ``src/Plugin.cc``, like this:: [...] plugin::Configuration Plugin::Configure() { plugin::Configuration config; config.name = "Demo::Rot13"; config.description = "Caesar cipher rotating a string's letters by 13 places."; config.version.major = 0; config.version.minor = 1; config.version.patch = 0; return config; } [...] Now rebuild and verify that the description is visible:: # make [...] # zeek -N | grep Rot13 Demo::Rot13 - Caesar cipher rotating a string's letters by 13 places. (dynamic, version 0.1.0) Zeek can also show us what exactly the plugin provides with the more verbose option ``-NN``:: # zeek -NN [...] Demo::Rot13 - Caesar cipher rotating a string's letters by 13 places. (dynamic, version 0.1.0) [Function] Demo::rot13 [...] There's our function. Now let's use it:: # zeek -e 'print Demo::rot13("Hello")' Uryyb It works. We next install the plugin along with Zeek itself, so that it will find it directly without needing the ``ZEEK_PLUGIN_PATH`` environment variable. If we first unset the variable, the function will no longer be available:: # unset ZEEK_PLUGIN_PATH # zeek -e 'print Demo::rot13("Hello")' error in , line 1: unknown identifier Demo::rot13, at or near "Demo::rot13" Once we install it, it works again:: # make install # zeek -e 'print Demo::rot13("Hello")' Uryyb The installed version went into ``/lib/zeek/plugins/Demo_Rot13``. One can distribute the plugin independently of Zeek for others to use. To distribute in source form, just remove the ``build/`` directory (``make distclean`` does that) and then tar up the whole ``rot13-plugin/`` directory. Others then follow the same process as above after unpacking. To distribute the plugin in binary form, the build process conveniently creates a corresponding tarball in ``build/dist/``. In this case, it's called ``Demo_Rot13-0.1.0.tar.gz``, with the version number coming out of the ``VERSION`` file that ``init-plugin`` put into place. The binary tarball has everything needed to run the plugin, but no further source files. Optionally, one can include further files by specifying them in the plugin's ``CMakeLists.txt`` through the ``zeek_plugin_dist_files`` macro; the skeleton does that for ``README``, ``VERSION``, ``CHANGES``, and ``COPYING``. To use the plugin through the binary tarball, just unpack it into ``/lib/zeek/plugins/``. Alternatively, if you unpack it in another location, then you need to point ``ZEEK_PLUGIN_PATH`` there. Before distributing your plugin, you should edit some of the meta files that ``init-plugin`` puts in place. Edit ``README`` and ``VERSION``, and update ``CHANGES`` when you make changes. Also put a license file in place as ``COPYING``; if BSD is fine, you will find a template in ``COPYING.edit-me``. Plugin Directory Layout ======================= A plugin's directory needs to follow a set of conventions so that Zeek (1) recognizes it as a plugin, and (2) knows what to load. While ``init-plugin`` takes care of most of this, the following is the full story. We'll use ```` to represent a plugin's top-level directory. With the skeleton, ```` corresponds to ``build/``. ``/__zeek_plugin__`` A file that marks a directory as containing a Zeek plugin. The file must exist, and its content must consist of a single line with the qualified name of the plugin (e.g., "Demo::Rot13"). ``/lib/.-.so`` The shared library containing the plugin's compiled code. Zeek will load this in dynamically at run-time if OS and architecture match the current platform. ``scripts/`` A directory with the plugin's custom Zeek scripts. When the plugin gets activated, this directory will be automatically added to ``ZEEKPATH``, so that any scripts/modules inside can be "@load"ed. ``scripts``/__load__.zeek A Zeek script that will be loaded when the plugin gets activated. When this script executes, any BIF elements that the plugin defines will already be available. See below for more information on activating plugins. ``scripts``/__preload__.zeek A Zeek script that will be loaded when the plugin gets activated, but before any BIF elements become available. See below for more information on activating plugins. ``lib/bif/`` Directory with auto-generated Zeek scripts that declare the plugin's BIF elements. The files here are produced by ``bifcl``. Any other files in ```` are ignored by Zeek. By convention, a plugin should put its custom scripts into sub folders of ``scripts/``, i.e., ``scripts///