1
+# -*- coding: utf-8 -*-
2
+# Description: unbound netdata python.d module
3
+# Author: Austin S. Hemmelgarn (Ferroin)
4
+
5
+import os
6
+import yaml
7
+
8
+from base.FrameworkServices.SocketService import SocketService
9
+
10
+
11
+ORDER = ['queries', 'reqlist', 'recursion']
12
+
13
+CHARTS = {
14
+ 'queries': {
15
+ 'options': [None, 'Queries Processed', 'queries', 'Unbound', 'unbound.queries', 'line'],
16
+ 'lines': [
17
+ ['ratelimit', 'Ratelimited', 'absolute', 1, 1],
18
+ ['cachemiss', 'Cache Miss', 'absolute', 1, 1],
19
+ ['cachehit', 'Cache Hit', 'absolute', 1, 1],
20
+ ['expired', 'Expired', 'absolute', 1, 1],
21
+ ['prefetch', 'Prefetched', 'absolute', 1, 1],
22
+ ['recursive', 'Recursive', 'absolute', 1, 1]
23
+ ]
24
+ },
25
+ 'reqlist': {
26
+ 'options': [None, 'Request List', 'items', 'Unbound', 'unbound.reqlist', 'line'],
27
+ 'lines': [
28
+ ['reqlist_avg', 'Average Size', 'absolute', 1, 1],
29
+ ['reqlist_max', 'Maximum Size', 'absolute', 1, 1],
30
+ ['reqlist_overwritten', 'Overwritten Requests', 'absolute', 1, 1],
31
+ ['reqlist_exceeded', 'Overruns', 'absolute', 1, 1],
32
+ ['reqlist_current', 'Current Size', 'absolute', 1, 1],
33
+ ['reqlist_user', 'User Requests', 'absolute', 1, 1]
34
+ ]
35
+ },
36
+ 'recursion': {
37
+ 'options': [None, 'Recursion Timings', 'seconds', 'Unbound', 'unbound.recursion', 'line'],
38
+ 'lines': [
39
+ ['recursive_avg', 'Average', 'absolute', 1, 1],
40
+ ['recursive_med', 'Median', 'absolute', 1, 1]
41
+ ]
42
+ }
43
+}
44
+
45
+# These get added too if we are told to use extended stats.
46
+EXTENDED_ORDER = ['cache']
47
+
48
+EXTENDED_CHARTS = {
49
+ 'cache': {
50
+ 'options': [None, 'Cache Sizes', 'items', 'Unbound', 'unbound.cache', 'line'],
51
+ 'lines': [
52
+ ['cache_message', 'Message Cache', 'absolute', 1, 1],
53
+ ['cache_rrset', 'RRSet Cache', 'absolute', 1, 1],
54
+ ['cache_infra', 'Infra Cache', 'absolute', 1, 1],
55
+ ['cache_key', 'DNSSEC Key Cache', 'absolute', 1, 1],
56
+ ['cache_dnscss', 'DNSCrypt Shared Secret Cache', 'absolute', 1, 1],
57
+ ['cache_dnscn', 'DNSCrypt Nonce Cache', 'absolute', 1, 1]
58
+ ]
59
+ }
60
+}
61
+
62
+# This maps the Unbound stat names to our names.
63
+STAT_MAP = {
64
+ 'total.num.queries_ip_ratelimited': 'ratelimit',
65
+ 'total.num.cachehits': 'cachehit',
66
+ 'total.num.cachemiss': 'cachemiss',
67
+ 'total.num.zero_ttl': 'expired',
68
+ 'total.num.prefetch': 'prefetch',
69
+ 'total.num.recursivereplies': 'recursive',
70
+ 'total.requestlist.avg': 'reqlist_avg',
71
+ 'total.requestlist.max': 'reqlist_max',
72
+ 'total.requestlist.overwritten': 'reqlist_overwritten',
73
+ 'total.requestlist.exceeded': 'reqlist_exceeded',
74
+ 'total.requestlist.current.all': 'reqlist_current',
75
+ 'total.requestlist.current.user': 'reqlist_user',
76
+ 'total.recursion.time.avg': 'recursive_avg',
77
+ 'total.recursion.time.median': 'recursive_med',
78
+ 'msg.cache.count': 'cache_message',
79
+ 'rrset.cache.count': 'cache_rrset',
80
+ 'infra.cache.count': 'cache_infra',
81
+ 'key.cache.count': 'cache_key',
82
+ 'dnscrypt_shared_secret.cache.count': 'cache_dnscss',
83
+ 'dnscrypt_nonce.cache.count': 'cache_dnscn'
84
+}
85
+
86
+
87
+class Service(SocketService):
88
+ def __init__(self, configuration=None, name=None):
89
+ # The unbound control protocol is always SSL encapsulated
90
+ # unless it's used over a UNIX socket, so enable SSL _before_
91
+ # doing the normal SocketService initialization.
92
+ configuration['ssl'] = True
93
+ self.port = 8935
94
+ SocketService.__init__(self, configuration, name)
95
+ self.ext = self.configuration.get('extended', None)
96
+ self.ubconf = self.configuration.get('ubconf', '/etc/unbound/unbound.conf')
97
+ self.order = ORDER
98
+ self.definitions = CHARTS
99
+ if self.ext:
100
+ self.order = self.order + EXTENDED_ORDER
101
+ self.definitions.update(EXTENDED_CHARTS)
102
+ self.request = 'UBCT1 stats\n'
103
+ self._parse_config()
104
+ self.debug('Unbound config: {0}'.format(self.ubconf)
105
+ if os.access(self.ubconf, os.R_OK):
106
+ with open(self.ubconf, 'r') as ubconf:
107
+ try:
108
+ conf = yaml.load(ubconf)
109
+ except yaml.YAMLError:
110
+ conf = dict()
111
+ if self.ext is None:
112
+ if 'extended-statistics' in conf['server'].keys():
113
+ self.ext = conf['server']['extended-statistics']
114
+ if 'remote-control' in conf.keys():
115
+ if conf['remote-control'].get('control-use-cert', False):
116
+ if not self.key:
117
+ self.key = conf['remote-control'].get('control-key-file', '/etc/unbound/unbound_control.key')
118
+ if not self.cert:
119
+ self.cert = conf['remote-control'].get('control-cert-file', '/etc/unbound/unbound_control.pem')
120
+ self.port = conf['remote-control'].get('control-port', 8953)
121
+ else:
122
+ if not self.unix_socket:
123
+ self.unix_socket = conf['remote-control'].get('control-interface')
124
+ self.debug('Extended stats: {0}'.format(self.ext))
125
+ if self.unix_socket:
126
+ self.debug('Using unix socket: {0}'.format(self.unix_socket))
127
+ for key in self.definitions.keys():
128
+ self.definitions[key]['options'][4] = 'Local'
129
+ else:
130
+ self.debug('Connecting to: {0}:{1}'.format(self.host, self.port))
131
+ self.debug('Using key: {0}'.format(self.key))
132
+ self.debug('Using certificate: {0}'.format(self.cert))
133
+ for key in self.definitions.keys():
134
+ self.definitions[key]['options'][4] = self.host
135
+
136
+
137
+ def check(self):
138
+ # We need to check that auth works, otherwise there's no point.
139
+ self._connect()
140
+ self._disconnect()
141
+ return bool(self._sock)
142
+
143
+ def _check_raw_data(self, data):
144
+ # The server will close the connection when it's done sending
145
+ # data, so just keep looping until that happens.
146
+ return False
147
+
148
+ def _get_data(self):
149
+ raw = self._get_raw_data()
150
+ data = dict()
151
+ tmp = dict()
152
+ for line in raw.splitlines():
153
+ stat = line.split('=')
154
+ tmp[stat[0]] = stat[1]
155
+ for item in STAT_MAP.keys():
156
+ if item in tmp.keys():
157
+ data[STAT_MAP[item]] = float(tmp[item])
158
+ return data