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