Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / tor / tor_ @ 0d549a44

Historique | Voir | Annoter | Télécharger (16 ko)

1
#!/usr/bin/python2
2

    
3
from __future__ import print_function
4
import collections
5
import json
6
import os
7
import stem
8
import stem.control
9
import sys
10

    
11
default_torgeoippath = "/usr/share/GeoIP/GeoIP.dat"
12
default_torcachefile = 'munin_tor_country_stats.json'
13
default_torport = 9051
14
default_torsocket = '/var/run/tor/control'
15
default_torconnectmethod = 'port'
16

    
17
#%# family=auto
18
#%# capabilities=autoconf suggest
19

    
20
def simplify(cn):
21
    """Simplify country name"""
22
    cn = cn.replace(' ', '_')
23
    cn = cn.replace("'", '_')
24
    cn = cn.split(',', 1)[0]
25
    return cn
26

    
27

    
28
class ConnectionError(Exception):
29
    """Error connecting to the controller"""
30

    
31

    
32
class AuthError(Exception):
33
    """Error authenticating to the controller"""
34

    
35

    
36
def authenticate(controller):
37
    try:
38
        controller.authenticate()
39
        return
40
    except stem.connection.MissingPassword:
41
        pass
42

    
43
    try:
44
        password = os.environ['torpassword']
45
    except KeyError:
46
        raise AuthError("Please configure the 'torpassword' "
47
                        "environment variable")
48

    
49
    try:
50
        controller.authenticate(password=password)
51
    except stem.connection.PasswordAuthFailed:
52
        print("Authentication failed (incorrect password)")
53

    
54

    
55
def gen_controller():
56
    connect_method = os.environ.get('connectmethod', default_torconnectmethod)
57
    if connect_method == 'port':
58
        return stem.control.Controller.from_port(port=int(os.environ.get('port', default_torport)))
59
    elif connect_method == 'socket':
60
        return stem.control.Controller.from_socket_file(path=os.environ.get('socket', default_torsocket))
61
    else:
62
        print("env.connectmethod contains an invalid value. Please specify either 'port' or 'socket'.", file=sys.stderr)
63
        sys.exit(-1)
64

    
65

    
66
#########################
67
# Base Class
68
#########################
69

    
70

    
71
class TorPlugin(object):
72
    def __init__(self):
73
        raise NotImplementedError
74

    
75
    def conf(self):
76
        raise NotImplementedError
77

    
78
    @staticmethod
79
    def conf_from_dict(graph, labels):
80
        # header
81
        for key, val in graph.iteritems():
82
            print('graph_{} {}'.format(key, val))
83
        # values
84
        for label, attributes in labels.iteritems():
85
            for key, val in attributes.iteritems():
86
                print('{}.{} {}'.format(label, key, val))
87

    
88
    @staticmethod
89
    def autoconf():
90
        try:
91
            with gen_controller() as controller:
92
                try:
93
                    authenticate(controller)
94
                    print('yes')
95
                except stem.connection.AuthenticationFailure as e:
96
                    print('no (Authentication failed: {})'.format(e))
97

    
98
        except stem.connection:
99
            print('no (Connection failed)')
100

    
101
    @staticmethod
102
    def suggest():
103
        options = ['connections', 'traffic', 'dormant', 'bandwidth', 'flags', 'countries']
104

    
105
        for option in options:
106
            print(option)
107

    
108
    def fetch(self):
109
        raise NotImplementedError
110

    
111

    
112
##########################
113
# Child Classes
114
##########################
115

    
116

    
117
class TorBandwidth(TorPlugin):
118
    def __init__(self):
119
        pass
120

    
121
    def conf(self):
122
        graph = {'title': 'Observed bandwidth',
123
                 'args': '-l 0 --base 1000',
124
                 'vlabel': 'bytes/s',
125
                 'category': 'Tor',
126
                 'info': 'estimated capacity based on usage in bytes/s'}
127
        labels = {'bandwidth': {'label': 'bandwidth', 'min': 0, 'type': 'GAUGE'}}
128

    
129
        TorPlugin.conf_from_dict(graph, labels)
130

    
131
    def fetch(self):
132
        with gen_controller() as controller:
133
            try:
134
                authenticate(controller)
135
            except stem.connection.AuthenticationFailure as e:
136
                print('Authentication failed ({})'.format(e))
137
                return
138

    
139
            # Get fingerprint of our own relay to look up the descriptor for.
140
            # In Stem 1.3.0 and later, get_server_descriptor() will fetch the
141
            # relay's own descriptor if no argument is provided, so this will
142
            # no longer be needed.
143
            fingerprint = controller.get_info('fingerprint', None)
144
            if fingerprint is None:
145
                print("Error while reading fingerprint from Tor daemon", file=sys.stderr)
146
                sys.exit(-1)
147

    
148
            response = controller.get_server_descriptor(fingerprint, None)
149
            if response is None:
150
                print("Error while getting server descriptor from Tor daemon", file=sys.stderr)
151
                sys.exit(-1)
152
            print('bandwidth.value {}'.format(response.observed_bandwidth))
153

    
154

    
155
class TorConnections(TorPlugin):
156
    def __init__(self):
157
        pass
158

    
159
    def conf(self):
160
        graph = {'title': 'Connections',
161
                 'args': '-l 0 --base 1000',
162
                 'vlabel': 'connections',
163
                 'category': 'Tor',
164
                 'info': 'OR connections by state'}
165
        labels = {'new': {'label': 'new', 'min': 0, 'max': 25000, 'type': 'GAUGE'},
166
                  'launched': {'label': 'launched', 'min': 0, 'max': 25000, 'type': 'GAUGE'},
167
                  'connected': {'label': 'connected', 'min': 0, 'max': 25000, 'type': 'GAUGE'},
168
                  'failed': {'label': 'failed', 'min': 0, 'max': 25000, 'type': 'GAUGE'},
169
                  'closed': {'label': 'closed', 'min': 0, 'max': 25000, 'type': 'GAUGE'}}
170

    
171
        TorPlugin.conf_from_dict(graph, labels)
172

    
173
    def fetch(self):
174
        with gen_controller() as controller:
175
            try:
176
                authenticate(controller)
177

    
178
                response = controller.get_info('orconn-status', None)
179
                if response is None:
180
                    print("No response from Tor daemon in TorConnection.fetch()", file=sys.stderr)
181
                    sys.exit(-1)
182
                else:
183
                    connections = response.split('\n')
184
                    states = dict((state, 0) for state in stem.ORStatus)
185
                    for connection in connections:
186
                        states[connection.rsplit(None, 1)[-1]] += 1
187
                    for state, count in states.iteritems():
188
                        print('{}.value {}'.format(state.lower(), count))
189
            except stem.connection.AuthenticationFailure as e:
190
                print('Authentication failed ({})'.format(e))
191

    
192

    
193
class TorCountries(TorPlugin):
194
    def __init__(self):
195
        # Configure plugin
196
        self.cache_dir_name = os.environ.get('torcachedir', None)
197
        if self.cache_dir_name is not None:
198
            self.cache_dir_name = os.path.join(self.cache_dir_name,
199
                                               os.environ.get('torcachefile', default_torcachefile))
200

    
201
        max_countries = os.environ.get('tormaxcountries', 15)
202
        self.max_countries = int(max_countries)
203

    
204
        geoip_path = os.environ.get('torgeoippath', default_torgeoippath)
205
        try:
206
            import GeoIP
207
            self.geodb = GeoIP.open(geoip_path, GeoIP.GEOIP_MEMORY_CACHE)
208
            self.available = True
209
        except Exception:
210
            self.available = False
211

    
212
    def conf(self):
213
        """Configure plugin"""
214
        if not self.available:
215
            return
216

    
217
        graph = {'title': 'Countries',
218
                 'args': '-l 0 --base 1000',
219
                 'vlabel': 'countries',
220
                 'category': 'Tor',
221
                 'info': 'OR connections by state'}
222
        labels = {}
223

    
224
        countries_num = self.top_countries()
225

    
226
        for c, v in countries_num:
227
            labels[c] = {'label': c, 'min': 0, 'max': 25000, 'type': 'GAUGE'}
228

    
229
        TorPlugin.conf_from_dict(graph, labels)
230

    
231
        # If needed, create cache file at config time
232
        if self.cache_dir_name:
233
            with open(self.cache_dir_name, 'w') as f:
234
                json.dump(countries_num, f)
235

    
236
    def fetch(self):
237
        """Generate metrics"""
238

    
239
        # If possible, read cached data instead of doing the processing twice
240
        if not self.available:
241
            return
242

    
243
        try:
244
            with open(self.cache_dir_name) as f:
245
                countries_num = json.load(f)
246
        except:
247
            # Fallback if cache_dir_name is not set, unreadable or any other
248
            # error
249
            countries_num = self.top_countries()
250

    
251
        for c, v in countries_num:
252
            print("%s.value %d" % (c, v))
253

    
254
    # Unused
255
    #@staticmethod
256
    #def _gen_ipaddrs_from_circuits(controller):
257
    #    """Generate a sequence of ipaddrs for every built circuit"""
258
    #    # Currently unused
259
    #    for circ in controller.get_circuits():
260
    #        if circ.status != CircStatus.BUILT:
261
    #            continue
262
    #
263
    #        for entry in circ.path:
264
    #            fingerprint, nickname = entry
265
    #
266
    #            desc = controller.get_network_status(fingerprint, None)
267
    #            if desc:
268
    #                ipaddr = desc.address
269
    #                yield ipaddr
270

    
271
    @staticmethod
272
    def _gen_ipaddrs_from_statuses(controller):
273
        """Generate a sequence of ipaddrs for every network status"""
274
        for desc in controller.get_network_statuses():
275
            ipaddr = desc.address
276
            yield ipaddr
277

    
278
    def _gen_countries(self, controller):
279
        """Generate a sequence of countries for every built circuit"""
280
        for ipaddr in self._gen_ipaddrs_from_statuses(controller):
281
            country = self.geodb.country_name_by_addr(ipaddr)
282
            if country is None:
283
                yield 'Unknown'
284
                continue
285

    
286
            yield simplify(country)
287

    
288
    def top_countries(self):
289
        """Build a list of top countries by number of circuits"""
290
        with gen_controller() as controller:
291
            try:
292
                authenticate(controller)
293
                c = collections.Counter(self._gen_countries(controller))
294
                return sorted(c.most_common(self.max_countries))
295
            except stem.connection.AuthenticationFailure as e:
296
                print('Authentication failed ({})'.format(e))
297
                return []
298

    
299

    
300
class TorDormant(TorPlugin):
301
    def __init__(self):
302
        pass
303

    
304
    def conf(self):
305
        graph = {'title': 'Dormant',
306
                 'args': '-l 0 --base 1000',
307
                 'vlabel': 'dormant',
308
                 'category': 'Tor',
309
                 'info': 'Is Tor not building circuits because it is idle?'}
310
        labels = {'dormant': {'label': 'dormant', 'min': 0, 'max': 1, 'type': 'GAUGE'}}
311

    
312
        TorPlugin.conf_from_dict(graph, labels)
313

    
314
    def fetch(self):
315
        with gen_controller() as controller:
316
            try:
317
                #controller.authenticate()
318
                authenticate(controller)
319

    
320
                response = controller.get_info('dormant', None)
321
                if response is None:
322
                    print("Error while reading dormant state from Tor daemon", file=sys.stderr)
323
                    sys.exit(-1)
324
                print('dormant.value {}'.format(response))
325
            except stem.connection.AuthenticationFailure as e:
326
                print('Authentication failed ({})'.format(e))
327

    
328

    
329
class TorFlags(TorPlugin):
330
    def __init__(self):
331
        pass
332

    
333
    def conf(self):
334
        graph = {'title': 'Relay flags',
335
                 'args': '-l 0 --base 1000',
336
                 'vlabel': 'flags',
337
                 'category': 'Tor',
338
                 'info': 'Flags active for relay'}
339
        labels = {flag: {'label': flag, 'min': 0, 'max': 1, 'type': 'GAUGE'} for flag in stem.Flag}
340

    
341
        TorPlugin.conf_from_dict(graph, labels)
342

    
343
    def fetch(self):
344
        with gen_controller() as controller:
345
            try:
346
                authenticate(controller)
347
            except stem.connection.AuthenticationFailure as e:
348
                print('Authentication failed ({})'.format(e))
349
                return
350

    
351
            # Get fingerprint of our own relay to look up the status entry for.
352
            # In Stem 1.3.0 and later, get_network_status() will fetch the
353
            # relay's own status entry if no argument is provided, so this will
354
            # no longer be needed.
355
            fingerprint = controller.get_info('fingerprint', None)
356
            if fingerprint is None:
357
                print("Error while reading fingerprint from Tor daemon", file=sys.stderr)
358
                sys.exit(-1)
359

    
360
            response = controller.get_network_status(fingerprint, None)
361
            if response is None:
362
                print("Error while getting server descriptor from Tor daemon", file=sys.stderr)
363
                sys.exit(-1)
364
            for flag in stem.Flag:
365
                if flag in response.flags:
366
                    print('{}.value 1'.format(flag))
367
                else:
368
                    print('{}.value 0'.format(flag))
369

    
370

    
371
class TorRouters(TorPlugin):
372
    def __init__(self):
373
        pass
374

    
375
    def conf(self):
376
        graph = {'title': 'Routers',
377
                 'args': '-l 0',
378
                 'vlabel': 'routers',
379
                 'category': 'Tor',
380
                 'info': 'known Tor onion routers'}
381
        labels = {'routers': {'label': 'routers', 'min': 0, 'type': 'GAUGE'} }
382
                 
383

    
384
        TorPlugin.conf_from_dict(graph, labels)
385

    
386
    def fetch(self):
387
        with gen_controller() as controller:
388
            try:
389
                authenticate(controller)
390
            except stem.connection.AuthenticationFailure as e:
391
                print('Authentication failed ({})'.format(e))
392
                return
393

    
394

    
395
            response = controller.get_info('ns/all', None)
396
            if response is None:
397
                print("Error while reading ns/all from Tor daemon", file=sys.stderr)
398
                sys.exit(-1)
399
            else:
400
                routers = response.split('\n')
401
	        onr = 0
402
		for router in routers:
403
		    if router[0] == "r":
404
	               onr += 1 
405
                
406
                print('routers.value {}'.format(onr))
407

    
408

    
409
class TorTraffic(TorPlugin):
410
    def __init__(self):
411
        pass
412

    
413
    def conf(self):
414
        graph = {'title': 'Traffic',
415
                 'args': '-l 0 --base 1024',
416
                 'vlabel': 'data',
417
                 'category': 'Tor',
418
                 'info': 'bytes read/written'}
419
        labels = {'read': {'label': 'read', 'min': 0, 'type': 'DERIVE'},
420
                  'written': {'label': 'written', 'min': 0, 'type': 'DERIVE'}}
421

    
422
        TorPlugin.conf_from_dict(graph, labels)
423

    
424
    def fetch(self):
425
        with gen_controller() as controller:
426
            try:
427
                authenticate(controller)
428
            except stem.connection.AuthenticationFailure as e:
429
                print('Authentication failed ({})'.format(e))
430
                return
431

    
432
            response = controller.get_info('traffic/read', None)
433
            if response is None:
434
                print("Error while reading traffic/read from Tor daemon", file=sys.stderr)
435
                sys.exit(-1)
436

    
437
            print('read.value {}'.format(response))
438

    
439
            response = controller.get_info('traffic/written', None)
440
            if response is None:
441
                print("Error while reading traffic/write from Tor daemon", file=sys.stderr)
442
                sys.exit(-1)
443
            print('written.value {}'.format(response))
444

    
445

    
446
##########################
447
# Main                   
448
##########################
449

    
450

    
451
def main():
452
    if len(sys.argv) > 1:
453
        param = sys.argv[1].lower()
454
    else:
455
        param = 'fetch'
456

    
457
    if param == 'autoconf':
458
        TorPlugin.autoconf()
459
        sys.exit()
460
    elif param == 'suggest':
461
        TorPlugin.suggest()
462
        sys.exit()
463
    else:
464
        # detect data provider
465
        if __file__.endswith('_bandwidth'):
466
            provider = TorBandwidth()
467
        elif __file__.endswith('_connections'):
468
            provider = TorConnections()
469
        elif __file__.endswith('_countries'):
470
            provider = TorCountries()
471
        elif __file__.endswith('_dormant'):
472
            provider = TorDormant()
473
        elif __file__.endswith('_flags'):
474
            provider = TorFlags()
475
        elif __file__.endswith('_routers'):
476
            provider = TorRouters()
477
        elif __file__.endswith('_traffic'):
478
            provider = TorTraffic()
479
        else:
480
            print('Unknown plugin name, try "suggest" for a list of possible ones.')
481
            sys.exit()
482

    
483
        if param == 'config':
484
            provider.conf()
485
        elif param == 'fetch':
486
            provider.fetch()
487
        else:
488
            print('Unknown parameter "{}"'.format(param))
489

    
490
if __name__ == '__main__':
491
    main()