| # Author: Alex Savatieiev (osavatieiev@mirantis.com; a.savex@gmail.com) |
| # Copyright 2019-2022 Mirantis, Inc. |
| import json |
| import re |
| import subprocess |
| import sys |
| |
| |
| def shell(command): |
| _ps = subprocess.Popen( |
| command.split(), |
| stdout=subprocess.PIPE |
| ).communicate()[0].decode() |
| |
| return _ps |
| |
| |
| def cut_option(_param, _options_list, _option="n/a"): |
| _result_list = [] |
| if _param in _options_list: |
| _index = _options_list.index(_param) |
| _option = _options_list[_index+1] |
| _l1 = _options_list[:_index] |
| _l2 = _options_list[_index+2:] |
| _result_list = _l1 + _l2 |
| else: |
| _result_list = _options_list |
| return _option, _result_list |
| |
| |
| def get_linked_devices(if_name): |
| if '@' in if_name: |
| _name = if_name[:if_name.index('@')] |
| else: |
| _name = if_name |
| # identify device type |
| _dev_link_path = shell('readlink /sys/class/net/{}'.format(_name)) |
| _type = "unknown" |
| if len(_dev_link_path) > 0: |
| _tmp = _dev_link_path.split('/') |
| _tmp = _tmp[_tmp.index("devices") + 1] |
| if _tmp.startswith("pci"): |
| _type = "physical" |
| elif _tmp.startswith("virtual"): |
| _type = "virtual" |
| |
| # get linked devices if any |
| _links = shell( |
| "find /sys/class/net/{}/ -type l".format(_name) |
| ) |
| # there can be only one parent device |
| _lower = None |
| # can be more than one child device |
| _upper = None |
| for line in _links.splitlines(): |
| _line = line.rsplit('/', 1)[1] |
| if _line.startswith("upper_"): |
| if not _upper: |
| _upper = [] |
| _upper.append(_line[6:]) |
| elif _line.startswith("lower_"): |
| if not _lower: |
| _lower = [] |
| _lower.append(_line[6:]) |
| |
| return _lower, _upper, _type |
| |
| |
| def get_ifs_data(): |
| # Collect interface and IPs data |
| # Compile regexps for detecting IPs |
| if_start = re.compile(r"^[0-9]+: .*: \<.*\> .*$") |
| if_link = re.compile(r"^\s{4}link\/ether\ .*$") |
| if_ipv4 = re.compile(r"^\s{4}inet\ .*$") |
| # variable prototypes |
| _ifs = {} |
| _name = None |
| # get the "ip a" output |
| _ifs_raw = shell('ip a') |
| for line in _ifs_raw.splitlines(): |
| _if_data = {} |
| if if_start.match(line): |
| _tmp = line.split(':') |
| _name = _tmp[1].strip() |
| _name = _name.split('@') if '@' in _name else [_name, ""] |
| _at = _name[1] |
| _name = _name[0] |
| _if_options = _tmp[2].strip().split(' ') |
| _lower, _upper, _type = get_linked_devices(_name) |
| _if_data['if_index'] = _tmp[0] |
| _if_data['at'] = _at |
| _if_data['mtu'], _if_options = cut_option("mtu", _if_options) |
| _if_data['qlen'], _if_options = cut_option("qlen", _if_options) |
| _if_data['state'], _if_options = cut_option("state", _if_options) |
| _if_data['other'] = _if_options |
| _if_data['ipv4'] = {} |
| _if_data['link'] = {} |
| _if_data['type'] = _type |
| _if_data['upper'] = _upper |
| _if_data['lower'] = _lower |
| _ifs[_name] = _if_data |
| elif if_link.match(line): |
| if _name is None: |
| continue |
| else: |
| _tmp = line.strip().split(' ', 2) |
| _mac_addr = _tmp[1] |
| _options = _tmp[2].split(' ') |
| _brd, _options = cut_option("brd", _options) |
| _netnsid, _options = cut_option("link-netnsid", _options) |
| _ifs[_name]['link'][_mac_addr] = {} |
| _ifs[_name]['link'][_mac_addr]['brd'] = _brd |
| _ifs[_name]['link'][_mac_addr]['link-netnsid'] = _netnsid |
| _ifs[_name]['link'][_mac_addr]['other'] = _options |
| elif if_ipv4.match(line): |
| if _name is None: |
| continue |
| else: |
| _tmp = line.strip().split(' ', 2) |
| _ip = _tmp[1] |
| _options = _tmp[2].split(' ') |
| _brd, _options = cut_option("brd", _options) |
| # TODO: Parse other options, mask, brd, etc... |
| _ifs[_name]['ipv4'][_ip] = {} |
| _ifs[_name]['ipv4'][_ip]['brd'] = _brd |
| _ifs[_name]['ipv4'][_ip]['other'] = _options |
| |
| # Collect routes data and try to match it with network |
| # Compile regexp for detecting default route |
| _routes = { |
| 'raw': [] |
| } |
| _ip_route_raw = shell("ip -4 r") |
| for line in _ip_route_raw.splitlines(): |
| _o = line.strip().split(' ') |
| if line.startswith("default"): |
| # default gateway found, prepare options and cut word 'default' |
| _gate, _o = cut_option('via', _o, _option="0.0.0.0") |
| _dev, _o = cut_option('dev', _o) |
| _routes[_o[0]] = { |
| 'gateway': _gate, |
| 'device': _dev, |
| 'args': " ".join(_o[1:]) |
| } |
| else: |
| # network specific gateway found |
| _gate, _o = cut_option('via', _o, _option=None) |
| _dev, _o = cut_option('dev', _o) |
| _src, _o = cut_option('src', _o) |
| _routes[_o[0]] = { |
| 'gateway': _gate, |
| 'device': _dev, |
| 'source': _src, |
| 'args': " ".join(_o[1:]) |
| } |
| |
| _ifs["routes"] = _routes |
| return _ifs |
| |
| |
| ifs_data = get_ifs_data() |
| |
| if len(sys.argv) > 1 and sys.argv[1] == 'json': |
| sys.stdout.write(json.dumps(ifs_data)) |
| else: |
| _ifs = sorted(ifs_data.keys()) |
| _ifs.remove("lo") |
| _ifs.remove("routes") |
| for _idx in range(len(_ifs)): |
| _linked = "" |
| if ifs_data[_ifs[_idx]]['lower']: |
| _linked += "lower:{} ".format( |
| ','.join(ifs_data[_ifs[_idx]]['lower']) |
| ) |
| if ifs_data[_ifs[_idx]]['upper']: |
| _linked += "upper:{} ".format( |
| ','.join(ifs_data[_ifs[_idx]]['upper']) |
| ) |
| _linked = _linked.strip() |
| print("{0:8} {1:30} {2:18} {3:19} {4:5} {5:4} {6}".format( |
| ifs_data[_ifs[_idx]]['type'], |
| _ifs[_idx], |
| ",".join(ifs_data[_ifs[_idx]]['link'].keys()), |
| ",".join(ifs_data[_ifs[_idx]]['ipv4'].keys()), |
| ifs_data[_ifs[_idx]]['mtu'], |
| ifs_data[_ifs[_idx]]['state'], |
| _linked |
| )) |
| |
| print("\n") |
| # default route |
| print("default via {} on {} ({})".format( |
| ifs_data["routes"]["default"]["gateway"], |
| ifs_data["routes"]["default"]["device"], |
| ifs_data["routes"]["default"]["args"] |
| )) |
| # detected routes |
| _routes = ifs_data["routes"].keys() |
| _routes.remove("raw") |
| _routes.remove("default") |
| _rt = ifs_data["routes"] |
| for idx in range(0, len(_routes)): |
| if _rt[_routes[idx]]["gateway"]: |
| print("{0:18} <- {1:16} -> {2:18} on {3:30} ({4})".format( |
| _routes[idx], |
| _rt[_routes[idx]]["gateway"], |
| _rt[_routes[idx]]["source"], |
| _rt[_routes[idx]]["device"], |
| _rt[_routes[idx]]["args"] |
| )) |
| else: |
| print("{0:18} <- -> {1:18} on {2:30} ({3})".format( |
| _routes[idx], |
| _rt[_routes[idx]]["source"], |
| _rt[_routes[idx]]["device"], |
| _rt[_routes[idx]]["args"] |
| )) |