Completed
Pull Request — master (#4)
by Ryan
01:10
created

calc_ips()   B

Complexity

Conditions 7

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
c 2
b 0
f 0
dl 0
loc 19
rs 7.3333
1
#!/usr/bin/env python
2
3
from __future__ import print_function
4
import sys
5
import json
6
import socket
7
import time
8
import tracerouteparser
9
import signal
10
from requests import post, get, ConnectionError
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in ConnectionError.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
11
from subprocess import check_output, Popen, PIPE
12
from multiprocessing import Pool
13
from traceroutedb.log import logger
14
15
16
ON_POSIX = 'posix' in sys.builtin_module_names
17
18
19
def init_worker(handler=signal.SIG_IGN):
20
    signal.signal(signal.SIGINT, handler)
21
22
23
def own_ips():
24
    return check_output(["ip", "-4", "addr", "list"])
25
26
27
global OWN_IPS
0 ignored issues
show
Best Practice introduced by
Using the global statement at the module level
Loading history...
28
OWN_IPS = own_ips()
29
30
31
def ext_ip():
32
    """
33
    We make the assumtion that we ony have one gateway to any one destination,
34
    could be better to use "ip route get $ip" but that won't cover NAT scenario
35
    """
36
    try:
37
        resp = get('https://httpbin.org/ip')
38
        return resp.json()["origin"]
39
    except ConnectionError as e:
40
        logger.warning("Could not get external ip:\n" + str(e))
41
        return None
42
43
44
def pack_ips(ips, config, URL):
45
    """
46
    The only reason we need to pack all this info into a list is that
47
    we only get to pass one iterable to function from map_async or
48
    thats how I (poorly) understand it
49
    """
50
    ret = []
51
    detected_ext_ip = ext_ip()
52
    for ip in ips:
53
        ip_dict = {}
54
        ip = str(ip)
55
        ip_dict["note"] = config.get("note", None)
56
        ip_dict["ext_ip"] = detected_ext_ip
57
        ip_dict["url"] = URL
58
        ip_dict["ip"] = ip
59
        ip_dict["simulate"] = config.get("simulate", None)
60
        ret.append(ip_dict)
61
    return ret
62
63
64
def calc_ips(config):
65
    """
66
    Collapse ips from file and command line
67
    """
68
    if config.get("ips", False):
69
        ips = config.ips
70
        if config.get("ips_file", False):
71
            logger.warning("-i overrides ips from file with -f")
72
    elif config.ips_file:
73
        ips = []
74
        with open(config.ips_file) as f:
75
            for line in f.readlines():
76
                if line.strip().startswith("#"):
77
                    continue
78
                ips.append(line.strip().split(",")[1])
79
    else:
80
        logger.error("No ips defined, exiting")
81
        sys.exit(1)
82
    return ips
83
84
85
def submit_trace(ip_dict, result):
86
    if result is None:
87
        return
88
    if ip_dict.get("simulate"):
89
        print(json.dumps(result))
90
    else:
91
        try:
92
            r = post(ip_dict["url"], data=json.dumps(result))
93
            logger.debug(r)
94
        except ConnectionError as e:
95
            print(e)
96
97
98
def run_trace(ip_pack):
99
100
    ip = ip_pack["ip"]
101
    if ip in OWN_IPS:
102
        return None
103
104
    signal.signal(signal.SIGINT, signal.SIG_DFL)
105
    try:
106
        proc = Popen(["/usr/sbin/traceroute", ip, "30"], stdout=PIPE, stderr=PIPE)  # noqa
107
        out, err = proc.communicate()
108
    except KeyboardInterrupt:
109
        proc.terminate()
110
        proc.kill()
111
        return None
112
    signal.signal(signal.SIGINT, signal.SIG_IGN)
113
    src_ip = check_output(["ip", "route", "get", ip]).splitlines()[0].split()[-1]  # noqa
114
    trp = tracerouteparser.TracerouteParser()
115
    trp.parse_data(out)
116
    data = {}
117
    data["dst_ip"] = trp.dest_ip
118
    data["src_ip"] = src_ip
119
    data["dst_name"] = trp.dest_name
120
    data["hops"] = {}
121
122
    for hop in trp.hops:
123
        data["hops"][int(hop.idx)] = []
124
        for probe in hop.probes:
125
            data["hops"][int(hop.idx)].append({"name": probe.name,
126
                                               "ip": probe.ipaddr,
127
                                               "rtt": probe.rtt,
128
                                               "anno": probe.anno})
129
    ret = {"reporter": socket.gethostname(),
130
           "note": ip_pack["note"],
131
           "ext_ip": ip_pack["ext_ip"],
132
           "data": data}
133
    submit_trace(ip_pack, ret)
134
135
136
def runner_entry(config):
137
    if config.get("server_url", False):
138
        URL = config.server_url + "/trace"
139
    else:
140
        URL = "http://127.0.0.1:9001" + "/trace"
141
142
    start_time = time.time()
143
144
    logger.warn("Traceroute runner starting")
145
146
    ips = calc_ips(config)
147
    ips_pack = pack_ips(ips, config, URL)
148
149
    logger.debug('IP addresses: ' + str(ips))
150
    logger.debug(str(ips_pack))
151
152
    pool = Pool(config.procs, init_worker)
153
    try:
154
        logger.warn("Starting workers")
155
        res = pool.map_async(run_trace, ips_pack, 1)
156
        res.get(9999999)
157
    except KeyboardInterrupt:
158
        print("Caught KeyboardInterrupt, terminating workers")
159
        pool.terminate()
160
    finally:
161
        pool.close()
162
    pool.join()
163
164
    end_time = time.time()
165
    logger.warn("Traceroute runner done, Took: " +
166
                str(int(end_time - start_time)) + " seconds")
167