Short introduction to Ulogd2
Ulogd2 is a userspace logging daemon for netfilter/iptables related logging. This is the successor of the ulogd daemon which one of the best mean to log packets coming from Netfilter.
Ulogd-2.x uses latest Netfilter features to provide:
- Packet based logging (via libnetfilter_log or ULOG target)
- Flow based logging (via libnetfilter_conntrack)
Logging can be done to file, syslog, pcap or a database (MySQL, PostgreSQL, …).
To use libnetfilter_log and libnetfilter_conntrack, a kernel superior to 2.6.14 is needed.
If you want to give a try to Ulogd2, I suggest you to read the User documentation available from software.inl.fr or pollux’s tutorial.
Ulogd2 hacking
Getting the sources
Ulogd2 is work in progress. It smoothly leave the beta area and should reach the release candidate phase in the upcoming month. Thus, to play with Ulogd2 code I recommand to use the latest git:
git clone git://git.netfilter.org/ulogd2.git
I hack a lot on Ulogd2 and my version may contain some interesting features. If you want to hack over it, you may want to use my personnal git tree.
You can browse it at: http://git.inl.fr/cgi-bin/gitweb.cgi?p=ulogd2.git;a=summary
To use it:
git clone http://git.inl.fr/git/ulogd2.git ulogd2
This a tree obtained with git-svn, master branch should be in sync with subversion repository and INL branch contains my ulogd2 work. Thus to work over my latest modifications, you can do:
git checkout INL
Hacking modules
Current state of Ulogd2 core (in the src directory) seems to be fairly stable and I will then only focus on modules hacking.
The behaviour of a module is defined in a structure of type ulogd_plugin :
static struct ulogd_plugin ip2str_pluging = { .name = "IP2STR", .input = { .keys = ip2str_inp, .num_keys = ARRAY_SIZE(ip2str_inp), .type = ULOGD_DTYPE_PACKET | ULOGD_DTYPE_FLOW, }, .output = { .keys = ip2str_keys, .num_keys = ARRAY_SIZE(ip2str_keys), .type = ULOGD_DTYPE_PACKET | ULOGD_DTYPE_FLOW, }, .interp = &interp_ip2str, .version = ULOGD_VERSION, };
This is the main starting point when you try to understand inner working of a module.
The other starting points in a module are:
- struct config_keyset: this structure contains a definition of module options
- the input keys defined in a struct ulogd_key, this structure is pointed in the struct ulogd_plugin by .input.keys
- the output keys defined in a struct ulogd_key,this structure is pointed in the struct ulogd_plugin by .output.keys
In the case of filtering modules, the main function is the function pointed by .interp in the struct ulogd_plugin defining the module. This function is in charge of building the output keys of the module respectively to the already defined input keys.
Key definition
The definition of keys is done in an array of struct ulogd_key structures.
static struct ulogd_key ip2str_inp[] = { [KEY_OOB_FAMILY] = { .type = ULOGD_RET_UINT8, .flags = ULOGD_RETF_NONE, .name = "oob.family", }, [KEY_IP_SADDR] = { .type = ULOGD_RET_IPADDR, .flags = ULOGD_RETF_NONE|ULOGD_KEYF_OPTIONAL, .name = "ip.saddr", },
The needed items are:
- .name: this is the identifier of the key
- .type: this define the type of data contained in the key
- .flags: the flags change the behaviour of ulogd2 relatively to the key
Available type and flags are defined in the file include/ulogd/ulogd.h.
Ulogd2 is in charge of managing the memory allocated to the output keys defined in the modules. To tell ulogd2 a key is using allocated memory, just simply add the ULOGD_RETF_FREE flags to the key definition. For example, in IP2STR module we have:
{ .type = ULOGD_RET_STRING, .flags = ULOGD_RETF_FREE, .name = "ip.daddr.str", },
Doing the stuff
The simplest ulogd2 plugin is the MAC2STR module. This is a good starting point to build other modules. The previous sections details the init section of the plugin but the main work is done in the .interp function.
When a message is fetch by the input module, each plugin instance of the stack is called in turn via a call to the function pointed by .interp in the struct ulogd_plugin.
In MAC2STR plugin, the function is defined as follow:
static int interp_mac2str(struct ulogd_pluginstance *pi) { struct ulogd_key *ret = pi->output.keys; struct ulogd_key *inp = pi->input.keys; if (pp_is_valid(inp, KEY_RAW_MAC)) { unsigned char *mac = (unsigned char *) GET_VALUE(inp, KEY_RAW_MAC).ptr; int len = GET_VALUE(inp, KEY_RAW_MACLEN).ui16; char *mac_str = calloc(len/sizeof(char)*3, sizeof(char)); char *buf_cur = mac_str; int i; if (mac_str == NULL) return -1; for (i = 0; i < len; i++) buf_cur += sprintf(buf_cur, "%02x%c", mac[i], i == len - 1 ? 0 : ':'); ret[KEY_MAC_SADDR].u.value.ptr = mac_str; ret[KEY_MAC_SADDR].flags |= ULOGD_RETF_VALID; } return 0; }
The main idea is to work with input keys (struct ulogd_key *inp = pi->input.keys;
) to build output keys (struct ulogd_key *ret = pi->output.keys;
). A value is given to a key by changing the .value element in the structure:
ret[KEY_MAC_SADDR].u.value.ptr = mac_str;
An output key needs to be declared as VALID to be used by other modules. Thus ULOGD_RETF_VALID has to be added to the flags of the key:
ret[KEY_MAC_SADDR].flags |= ULOGD_RETF_VALID;
Can this also log to proprietary databases like MSSQL server or Oracle 12g?
The DBI module could be used to do logging into that databases.
UlogD2 has many function. It will be a great tool to have packets faster and will be a useful medium to make your task faster and reliable.
Good article. I tried to hack the PRINTFLOW module to include start and end time also during DESTROY. But in few log entries Start time is not getting printed while End time is always printed along with other ORIG/REPLY attributes.
More information:
I use debian jessie with ulogd-2.0.5 (with debian patches) and standard conf file with following stack
stack=ct1:NFCT,ip2str1:IP2STR,print1:PRINTFLOW,emu1:LOGEMU
Hacked util/printflow.c: Modified ‘enum printflow_fields; and ‘struct ulogd_key printflow_keys[FLOW_IDS]’ to include flow.start.sec and flow.end.sec with type ULOGD_RET_UINT32
Printing is done via:
pp_print_u(buf_cur, “Start”, res, PRINTFLOW_FLOW_START_SEC, u32);
pp_print_u(buf_cur, “End”, res, PRINTFLOW_FLOW_END_SEC, u32);
I was able to understand that Start Time is not getting printed due to pp_is_valid fails. Any idea why Start time is not valid in DESTROY event for some legitimate sessions?
Thanks
Jacob