Storing unbound(8) logs into InfluxDB

I’m using unbound(8) on OpenBSD to block Ads. In the logs, I can see which domains were queried and blocked ; but I like to have a more graphical overview of whats happening over weeks. So I stole a few ideas from the Pi-Hole Web Interface, routed the logs to InfluxDB via syslog-ng and rendered statistics using Grafana.

How it works

The unbound(8) daemon is configured to log via syslogd(8) which forwards relevant logs to a remote syslog-ng(8) instance. The syslog-ng(8) process identifies and parses unbound(8) logs into tags and fields and sends the data to influxd(1) using the InfluxDB HTTP API.

The following describes parsing both “standard” unbound(8) logs and “redirect” logs ; used in my “block Ad” configuration. In the case of a standard / simple unbound(8) configuration, there is no need for the “*_redirect” parts.

The InfluxDB database

I’m using a dedicated InfluxDB database to store all my parsed logs. The database has to be manually created. The measurements will be created by syslog-ng.

# influx -host $_influxdbserver
> CREATE DATABASE logs

A set of measurements are used to stored various aspects of the daemon logs. In the case of unbound(8), the following measurements will be created by the whole process:

> USE logs
> SHOW TAG KEYS FROM "unbound" ; SHOW FIELD KEYS FROM "unbound"
name: unbound
tagKey
class
clientip
from_cache
name
return_code
sysName
type
name: unbound
fieldKey fieldType
-------- ---------
response_size integer
time_to_resolve float
> SHOW TAG KEYS FROM "unbound_redirect" ; SHOW FIELD KEYS FROM "unbound_redirect"
name: unbound_redirect
tagKey
class
clientip
name
sysName
type
name: unbound_redirect
fieldKey fieldType
-------- ---------
void_field integer

The usage of “tags” is required by the “group by” queries that I will use in Grafana. Without the use of “group by”, the values could have been stored as text “fields”.

The syslog-ng configuration

Syslog-ng is configured to listen on port 60514 to receive logs from various other remote syslog instances. Nothing specific here. Use whatever port you like and enable the proper listeners. In my case, the unbound(1) rules are stored in a dedicated file :

# cat /etc/syslog-ng/unbound.conf
(...)
source s_net {
udp(ip(192.168.0.2) port(60514));
tcp(ip(192.168.0.2) port(60514));
};
(...)
@include "/etc/syslog-ng/unbound.conf"

Regarding the parsing process, I’m using the syslog-ng db_parser. Note that OpenBSD 6.4 provides a syslog-ng 3.12 package. The configuration may vary with newer syslog-ng versions:

# cat "/etc/syslog-ng/unbound.conf"
(...)
filter f_unbound_query {
filter(f_unbound) and tags("query");
};
filter f_unbound_redirect {
filter(f_unbound) and tags("redirect");
};
(...)
parser p_unbound {
db_parser(file("/etc/syslog-ng/unbound.xml"));
};
(...)
destination d_influxdb_unbound_query {
http(
url("http://127.0.0.1:8086/write?db=logs&precision=ms")
persist-name("influxdb_unbound_query")
method("POST")
user_agent("syslog-ng")
body("unbound,sysName=${HOST},clientip=${CLIENTIP},name=${NAME},type=${TYPE},class=${CLASS},return_code=${RETURN_CODE},from_cache=${FROM_CACHE} time_to_resolve=${TIME_TO_RESOLVE},response_size=${RESPONSE_SIZE}i ${UNIXTIME}${MSEC}")
);
};
destination d_influxdb_unbound_redirect {
http(
url("http://127.0.0.1:8086/write?db=logs&precision=ms")
persist-name("influxdb_unbound_redirect")
method("POST")
user_agent("syslog-ng")
body("unbound_redirect,sysName=${HOST},clientip=${CLIENTIP},name=${NAME},type=${TYPE},class=${CLASS} void_field=1i ${UNIXTIME}${MSEC}")
);
};
(...)
log {
source(s_net);
filter(f_unbound);
parser(p_unbound);
filter(f_unbound_query);
destination(d_influxdb_unbound_query);
};
log {
source(s_net);
filter(f_unbound);
parser(p_unbound);
filter(f_unbound_redirect);
destination(d_influxdb_unbound_redirect);
};

The pattern database only identifies general queries and queries from the Ad-blocker list. Each matching pattern is then taggued. The tags are used to route the data to the proper InfluxDB measurement:

(...)
<!-- queries -->
<rule id="unbound-query" class='system' provider='syslog-ng'>
<patterns>
<pattern>@ESTRING:PID: @@ESTRING:SEVERITY: @@ESTRING:CLIENTIP: @@ESTRING:NAME: @@ESTRING:TYPE: @@ESTRING:CLASS: @@ESTRING:RETURN_CODE: @@FLOAT:TIME_TO_RESOLVE:@ @NUMBER:FROM_CACHE:@ @NUMBER:RESPONSE_SIZE:@</pattern>
</patterns>
<tags><tag>query</tag></tags>
</rule>
(...)
<!-- redirect queries (AD Blocklists) -->
<rule id="unbound-redirect" class='system' provider='syslog-ng'>
<patterns>
<pattern>@ESTRING:PID: @@ESTRING:SEVERITY: @@ESTRING:NAME: @redirect @IPvANY:CLIENTIP:@@@@NUMBER:PORT:@ @ESTRING:NAME: @@ESTRING:TYPE: @@STRING:CLASS:@</pattern>
</patterns>
<tags><tag>redirect</tag></tags>
</rule>
(...)

The complete current version of the conf file is available here and the XML is there.

The unbound and syslogd parameters

From the local syslogd(8) perspective, it is as simple as:

# cat /etc/syslog.conf
(...)
@udp4://192.168.0.2:60514

Everything will be send to the syslog-ng(8) instance. In the case where only unbound(8) logs should be forwarded, I expect the following syntax to be enough:

!unbound
*.* @udp4://192.168.0.2:60514

Regarding the unbound(8) configuration, I used the following specifics:

# cat /var/unbound/etc/unbound.conf
(...)
use-syslog: yes
log-queries: no
log-replies: yes
log-local-actions: yes
verbosity: 0

The “log-replies” provides more detailed information (like time to resolve and response code) including those provided by “log-queries”. The “log-local-actions” is used to provide information about the local zones (used in the block Ad configuration).

The Grafana dashboard

One could get the data straight from InfluxDB using query like:

> USE logs
> SELECT count("time_to_resolve") AS "Total Queries" FROM "unbound" WHERE (time >= now() - 7d)
name: unbound
time Total Queries
---- -------------
2019-04-10T14:04:17.965045965Z 319943

The complete JSON file for this dashboard can be downloaded from the Grafana.com dashboard section.

Note that the filter parts of the dashboard doesn’t work when using InfluxDB 1.6.1 or 1.6.6 on OpenBSD 6.4/amd64. I couldn’t guess why. This comes from the following error on the influxd side:

> SHOW TAG VALUES FROM unbound WITH KEY = clientip WHERE sysName =~ /srv1/
ERR: SHOW TAG VALUES FROM unbound WITH KEY = clientip WHERE sysName =~ /srv1/ [panic:runtime error: index out of range]

One can force the filtering using variables in the URL. It’s a bit dirty but at least, it works. This error does not happen when using InfluxDB on FreeBSD or Ubuntu Linux. If you can guess what I did wrong, just ping me! :)

Author: Joel Carnat

Technical Architect and SysAdmin @work ; OpenBSD and FOSS @home ; Karate, Kobudō, Jōdō, Bodyweight workout, Photography @hobby

6 thoughts on “Storing unbound(8) logs into InfluxDB”

  1. You said, “the following measurements will be created by the whole process.”
    What “whole process” are you talking about? How was “unbound” created in the logs database?

    1. The “whole process” is the one described: unbound logs to syslogd, syslogd forwards to syslog-ng, syslog-ng parses and write to influxdb. However, the whole configuration processe is described in reverse order: create the “logs” influxdb database, configure syslog-ng to parse and write to influxdb, configure syslogd to forward to syslog-ng, have unbound log to syslogd. Because I prefer to have a bucket ready before sending stuff into it.

      1. You were right :-P . I’m trying to adapt this to CentOS and I’m stuck. Syslog-NG does not ship metrics to influxdb with this configuration, but it makes a great starting point.

        Thank you

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.