Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 1 | import re |
| 2 | import sys |
| 3 | import subprocess |
| 4 | import json |
| 5 | |
| 6 | |
| 7 | def shell(command): |
| 8 | _ps = subprocess.Popen( |
| 9 | command.split(), |
| 10 | stdout=subprocess.PIPE |
| 11 | ).communicate()[0].decode() |
| 12 | |
| 13 | return _ps |
| 14 | |
| 15 | |
Alex Savatieiev | d79dde1 | 2019-03-13 19:07:46 -0500 | [diff] [blame] | 16 | def cut_option(_param, _options_list, _option="n/a"): |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 17 | _result_list = [] |
| 18 | if _param in _options_list: |
| 19 | _index = _options_list.index(_param) |
| 20 | _option = _options_list[_index+1] |
| 21 | _l1 = _options_list[:_index] |
| 22 | _l2 = _options_list[_index+2:] |
| 23 | _result_list = _l1 + _l2 |
| 24 | else: |
| 25 | _result_list = _options_list |
| 26 | return _option, _result_list |
| 27 | |
| 28 | |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 29 | def get_linked_devices(if_name): |
| 30 | if '@' in if_name: |
| 31 | _name = if_name[:if_name.index('@')] |
| 32 | else: |
| 33 | _name = if_name |
Alex Savatieiev | 5d1eebb | 2019-01-25 18:15:36 +0100 | [diff] [blame] | 34 | # identify device type |
| 35 | _dev_link_path = shell('readlink /sys/class/net/{}'.format(_name)) |
| 36 | _type = "unknown" |
| 37 | if len(_dev_link_path) > 0: |
| 38 | _tmp = _dev_link_path.split('/') |
| 39 | _tmp = _tmp[_tmp.index("devices") + 1] |
| 40 | if _tmp.startswith("pci"): |
| 41 | _type = "physical" |
| 42 | elif _tmp.startswith("virtual"): |
| 43 | _type = "virtual" |
| 44 | |
| 45 | # get linked devices if any |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 46 | _links = shell( |
| 47 | "find /sys/class/net/{}/ -type l".format(_name) |
| 48 | ) |
| 49 | # there can be only one parent device |
| 50 | _lower = None |
| 51 | # can be more than one child device |
| 52 | _upper = None |
| 53 | for line in _links.splitlines(): |
| 54 | _line = line.rsplit('/', 1)[1] |
| 55 | if _line.startswith("upper_"): |
Alex Savatieiev | bd256e8 | 2019-01-25 18:27:01 +0100 | [diff] [blame] | 56 | if not _upper: |
| 57 | _upper = [] |
| 58 | _upper.append(_line[6:]) |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 59 | elif _line.startswith("lower_"): |
| 60 | if not _lower: |
| 61 | _lower = [] |
| 62 | _lower.append(_line[6:]) |
| 63 | |
Alex Savatieiev | 5d1eebb | 2019-01-25 18:15:36 +0100 | [diff] [blame] | 64 | return _lower, _upper, _type |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 65 | |
| 66 | |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 67 | def get_ifs_data(): |
Alex Savatieiev | d79dde1 | 2019-03-13 19:07:46 -0500 | [diff] [blame] | 68 | # Collect interface and IPs data |
| 69 | # Compile regexps for detecting IPs |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 70 | if_start = re.compile("^[0-9]+: .*: \<.*\> .*$") |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 71 | if_link = re.compile("^\s{4}link\/ether\ .*$") |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 72 | if_ipv4 = re.compile("^\s{4}inet\ .*$") |
Alex Savatieiev | d79dde1 | 2019-03-13 19:07:46 -0500 | [diff] [blame] | 73 | # variable prototypes |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 74 | _ifs = {} |
| 75 | _if_name = None |
Alex Savatieiev | d79dde1 | 2019-03-13 19:07:46 -0500 | [diff] [blame] | 76 | # get the "ip a" output |
| 77 | _ifs_raw = shell('ip a') |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 78 | for line in _ifs_raw.splitlines(): |
| 79 | _if_data = {} |
| 80 | if if_start.match(line): |
| 81 | _tmp = line.split(':') |
| 82 | _if_name = _tmp[1].strip() |
| 83 | _if_options = _tmp[2].strip().split(' ') |
Alex Savatieiev | 5d1eebb | 2019-01-25 18:15:36 +0100 | [diff] [blame] | 84 | _lower, _upper, _type = get_linked_devices(_if_name) |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 85 | _if_data['order'] = _tmp[0] |
| 86 | _if_data['mtu'], _if_options = cut_option("mtu", _if_options) |
| 87 | _if_data['qlen'], _if_options = cut_option("qlen", _if_options) |
| 88 | _if_data['state'], _if_options = cut_option("state", _if_options) |
| 89 | _if_data['other'] = _if_options |
| 90 | _if_data['ipv4'] = {} |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 91 | _if_data['mac'] = {} |
Alex Savatieiev | 5d1eebb | 2019-01-25 18:15:36 +0100 | [diff] [blame] | 92 | _if_data['type'] = _type |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 93 | _if_data['upper'] = _upper |
| 94 | _if_data['lower'] = _lower |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 95 | _ifs[_if_name] = _if_data |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 96 | elif if_link.match(line): |
| 97 | if _if_name is None: |
| 98 | continue |
| 99 | else: |
| 100 | _tmp = line.strip().split(' ', 2) |
| 101 | _mac_addr = _tmp[1] |
| 102 | _options = _tmp[2].split(' ') |
| 103 | _brd, _options = cut_option("brd", _options) |
| 104 | _ifs[_if_name]['mac'][_mac_addr] = {} |
| 105 | _ifs[_if_name]['mac'][_mac_addr]['brd'] = _brd |
| 106 | _ifs[_if_name]['mac'][_mac_addr]['other'] = _options |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 107 | elif if_ipv4.match(line): |
| 108 | if _if_name is None: |
| 109 | continue |
| 110 | else: |
| 111 | _tmp = line.strip().split(' ', 2) |
| 112 | _ip = _tmp[1] |
| 113 | _options = _tmp[2].split(' ') |
| 114 | _brd, _options = cut_option("brd", _options) |
| 115 | # TODO: Parse other options, mask, brd, etc... |
| 116 | _ifs[_if_name]['ipv4'][_ip] = {} |
| 117 | _ifs[_if_name]['ipv4'][_ip]['brd'] = _brd |
| 118 | _ifs[_if_name]['ipv4'][_ip]['other'] = _options |
Alex Savatieiev | d79dde1 | 2019-03-13 19:07:46 -0500 | [diff] [blame] | 119 | |
| 120 | # Collect routes data and try to match it with network |
| 121 | # Compile regexp for detecting default route |
| 122 | _routes = { |
| 123 | 'raw': [] |
| 124 | } |
| 125 | _ip_route_raw = shell("ip -4 r") |
| 126 | for line in _ip_route_raw.splitlines(): |
| 127 | _o = line.strip().split(' ') |
| 128 | if line.startswith("default"): |
| 129 | # default gateway found, prepare options and cut word 'default' |
| 130 | _gate, _o = cut_option('via', _o, _option="0.0.0.0") |
| 131 | _dev, _o = cut_option('dev', _o) |
| 132 | _routes[_o[0]] = { |
| 133 | 'gateway': _gate, |
| 134 | 'device': _dev, |
| 135 | 'args': " ".join(_o[1:]) |
| 136 | } |
| 137 | else: |
| 138 | # network specific gateway found |
| 139 | _gate, _o = cut_option('via', _o, _option=None) |
| 140 | _dev, _o = cut_option('dev', _o) |
| 141 | _src, _o = cut_option('src', _o) |
| 142 | _routes[_o[0]] = { |
| 143 | 'gateway': _gate, |
| 144 | 'device': _dev, |
| 145 | 'source': _src, |
| 146 | 'args': " ".join(_o[1:]) |
| 147 | } |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 148 | |
Alex Savatieiev | d79dde1 | 2019-03-13 19:07:46 -0500 | [diff] [blame] | 149 | _ifs["routes"] = _routes |
Oleksandr Savatieiev | fb9f943 | 2018-11-23 17:39:12 +0100 | [diff] [blame] | 150 | return _ifs |
| 151 | |
| 152 | |
| 153 | ifs_data = get_ifs_data() |
| 154 | |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 155 | if len(sys.argv) > 1 and sys.argv[1] == 'json': |
| 156 | sys.stdout.write(json.dumps(ifs_data)) |
| 157 | else: |
| 158 | _ifs = sorted(ifs_data.keys()) |
| 159 | _ifs.remove("lo") |
Alex Savatieiev | d79dde1 | 2019-03-13 19:07:46 -0500 | [diff] [blame] | 160 | _ifs.remove("routes") |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 161 | for _idx in range(len(_ifs)): |
| 162 | _linked = "" |
| 163 | if ifs_data[_ifs[_idx]]['lower']: |
Alex Savatieiev | 5d1eebb | 2019-01-25 18:15:36 +0100 | [diff] [blame] | 164 | _linked += "lower:{} ".format( |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 165 | ','.join(ifs_data[_ifs[_idx]]['lower']) |
| 166 | ) |
| 167 | if ifs_data[_ifs[_idx]]['upper']: |
Alex Savatieiev | bd256e8 | 2019-01-25 18:27:01 +0100 | [diff] [blame] | 168 | _linked += "upper:{} ".format( |
| 169 | ','.join(ifs_data[_ifs[_idx]]['upper']) |
| 170 | ) |
Alex Savatieiev | 5d1eebb | 2019-01-25 18:15:36 +0100 | [diff] [blame] | 171 | _linked = _linked.strip() |
| 172 | print("{0:8} {1:30} {2:18} {3:19} {4:5} {5:4} {6}".format( |
| 173 | ifs_data[_ifs[_idx]]['type'], |
Alex Savatieiev | 0137dad | 2019-01-25 16:18:42 +0100 | [diff] [blame] | 174 | _ifs[_idx], |
| 175 | ",".join(ifs_data[_ifs[_idx]]['mac'].keys()), |
| 176 | ",".join(ifs_data[_ifs[_idx]]['ipv4'].keys()), |
| 177 | ifs_data[_ifs[_idx]]['mtu'], |
| 178 | ifs_data[_ifs[_idx]]['state'], |
| 179 | _linked |
| 180 | )) |
Alex Savatieiev | d79dde1 | 2019-03-13 19:07:46 -0500 | [diff] [blame] | 181 | |
| 182 | print("\n") |
| 183 | # default route |
| 184 | print("default via {} on {} ({})".format( |
| 185 | ifs_data["routes"]["default"]["gateway"], |
| 186 | ifs_data["routes"]["default"]["device"], |
| 187 | ifs_data["routes"]["default"]["args"] |
| 188 | )) |
| 189 | # detected routes |
| 190 | _routes = ifs_data["routes"].keys() |
| 191 | _routes.remove("raw") |
| 192 | _routes.remove("default") |
| 193 | _rt = ifs_data["routes"] |
| 194 | for idx in range(0, len(_routes)): |
| 195 | if _rt[_routes[idx]]["gateway"]: |
| 196 | print("{0:18} <- {1:16} -> {2:18} on {3:30} ({4})".format( |
| 197 | _routes[idx], |
| 198 | _rt[_routes[idx]]["gateway"], |
| 199 | _rt[_routes[idx]]["source"], |
| 200 | _rt[_routes[idx]]["device"], |
| 201 | _rt[_routes[idx]]["args"] |
| 202 | )) |
| 203 | else: |
| 204 | print("{0:18} <- -> {1:18} on {2:30} ({3})".format( |
| 205 | _routes[idx], |
| 206 | _rt[_routes[idx]]["source"], |
| 207 | _rt[_routes[idx]]["device"], |
| 208 | _rt[_routes[idx]]["args"] |
| 209 | )) |