blob: a87769fcb063108b0ec5793c43c498992d0f3cf0 [file] [log] [blame]
Alex0989ecf2022-03-29 13:43:21 -05001# Author: Alex Savatieiev (osavatieiev@mirantis.com; a.savex@gmail.com)
2# Copyright 2019-2022 Mirantis, Inc.
Oleksandr Savatieievfb9f9432018-11-23 17:39:12 +01003import json
Alex3ebc5632019-04-18 16:47:18 -05004import re
5import subprocess
6import sys
Oleksandr Savatieievfb9f9432018-11-23 17:39:12 +01007
8
9def shell(command):
10 _ps = subprocess.Popen(
11 command.split(),
12 stdout=subprocess.PIPE
13 ).communicate()[0].decode()
14
15 return _ps
16
17
Alex Savatieievd79dde12019-03-13 19:07:46 -050018def cut_option(_param, _options_list, _option="n/a"):
Oleksandr Savatieievfb9f9432018-11-23 17:39:12 +010019 _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 Savatieiev0137dad2019-01-25 16:18:42 +010031def get_linked_devices(if_name):
32 if '@' in if_name:
33 _name = if_name[:if_name.index('@')]
34 else:
35 _name = if_name
Alex Savatieiev5d1eebb2019-01-25 18:15:36 +010036 # 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 Savatieiev0137dad2019-01-25 16:18:42 +010048 _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 Savatieievbd256e82019-01-25 18:27:01 +010058 if not _upper:
59 _upper = []
60 _upper.append(_line[6:])
Alex Savatieiev0137dad2019-01-25 16:18:42 +010061 elif _line.startswith("lower_"):
62 if not _lower:
63 _lower = []
64 _lower.append(_line[6:])
65
Alex Savatieiev5d1eebb2019-01-25 18:15:36 +010066 return _lower, _upper, _type
Alex Savatieiev0137dad2019-01-25 16:18:42 +010067
68
Oleksandr Savatieievfb9f9432018-11-23 17:39:12 +010069def get_ifs_data():
Alex Savatieievd79dde12019-03-13 19:07:46 -050070 # Collect interface and IPs data
71 # Compile regexps for detecting IPs
Alexd0391d42019-05-21 18:48:55 -050072 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 Savatieievd79dde12019-03-13 19:07:46 -050075 # variable prototypes
Oleksandr Savatieievfb9f9432018-11-23 17:39:12 +010076 _ifs = {}
Alex1839bbf2019-08-22 17:17:21 -050077 _name = None
Alex Savatieievd79dde12019-03-13 19:07:46 -050078 # get the "ip a" output
79 _ifs_raw = shell('ip a')
Oleksandr Savatieievfb9f9432018-11-23 17:39:12 +010080 for line in _ifs_raw.splitlines():
81 _if_data = {}
82 if if_start.match(line):
83 _tmp = line.split(':')
Alex1839bbf2019-08-22 17:17:21 -050084 _name = _tmp[1].strip()
85 _name = _name.split('@') if '@' in _name else [_name, ""]
86 _at = _name[1]
87 _name = _name[0]
Oleksandr Savatieievfb9f9432018-11-23 17:39:12 +010088 _if_options = _tmp[2].strip().split(' ')
Alex1839bbf2019-08-22 17:17:21 -050089 _lower, _upper, _type = get_linked_devices(_name)
90 _if_data['if_index'] = _tmp[0]
91 _if_data['at'] = _at
Oleksandr Savatieievfb9f9432018-11-23 17:39:12 +010092 _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'] = {}
Alex1839bbf2019-08-22 17:17:21 -050097 _if_data['link'] = {}
Alex Savatieiev5d1eebb2019-01-25 18:15:36 +010098 _if_data['type'] = _type
Alex Savatieiev0137dad2019-01-25 16:18:42 +010099 _if_data['upper'] = _upper
100 _if_data['lower'] = _lower
Alex1839bbf2019-08-22 17:17:21 -0500101 _ifs[_name] = _if_data
Alex Savatieiev0137dad2019-01-25 16:18:42 +0100102 elif if_link.match(line):
Alex1839bbf2019-08-22 17:17:21 -0500103 if _name is None:
Alex Savatieiev0137dad2019-01-25 16:18:42 +0100104 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)
Alex1839bbf2019-08-22 17:17:21 -0500110 _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 Savatieievfb9f9432018-11-23 17:39:12 +0100115 elif if_ipv4.match(line):
Alex1839bbf2019-08-22 17:17:21 -0500116 if _name is None:
Oleksandr Savatieievfb9f9432018-11-23 17:39:12 +0100117 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...
Alex1839bbf2019-08-22 17:17:21 -0500124 _ifs[_name]['ipv4'][_ip] = {}
125 _ifs[_name]['ipv4'][_ip]['brd'] = _brd
126 _ifs[_name]['ipv4'][_ip]['other'] = _options
Alex3ebc5632019-04-18 16:47:18 -0500127
Alex Savatieievd79dde12019-03-13 19:07:46 -0500128 # 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 Savatieievfb9f9432018-11-23 17:39:12 +0100156
Alex Savatieievd79dde12019-03-13 19:07:46 -0500157 _ifs["routes"] = _routes
Oleksandr Savatieievfb9f9432018-11-23 17:39:12 +0100158 return _ifs
159
160
161ifs_data = get_ifs_data()
162
Alex Savatieiev0137dad2019-01-25 16:18:42 +0100163if len(sys.argv) > 1 and sys.argv[1] == 'json':
164 sys.stdout.write(json.dumps(ifs_data))
165else:
166 _ifs = sorted(ifs_data.keys())
167 _ifs.remove("lo")
Alex Savatieievd79dde12019-03-13 19:07:46 -0500168 _ifs.remove("routes")
Alex Savatieiev0137dad2019-01-25 16:18:42 +0100169 for _idx in range(len(_ifs)):
170 _linked = ""
171 if ifs_data[_ifs[_idx]]['lower']:
Alex Savatieiev5d1eebb2019-01-25 18:15:36 +0100172 _linked += "lower:{} ".format(
Alex Savatieiev0137dad2019-01-25 16:18:42 +0100173 ','.join(ifs_data[_ifs[_idx]]['lower'])
174 )
175 if ifs_data[_ifs[_idx]]['upper']:
Alex Savatieievbd256e82019-01-25 18:27:01 +0100176 _linked += "upper:{} ".format(
177 ','.join(ifs_data[_ifs[_idx]]['upper'])
178 )
Alex Savatieiev5d1eebb2019-01-25 18:15:36 +0100179 _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 Savatieiev0137dad2019-01-25 16:18:42 +0100182 _ifs[_idx],
Alex1839bbf2019-08-22 17:17:21 -0500183 ",".join(ifs_data[_ifs[_idx]]['link'].keys()),
Alex Savatieiev0137dad2019-01-25 16:18:42 +0100184 ",".join(ifs_data[_ifs[_idx]]['ipv4'].keys()),
185 ifs_data[_ifs[_idx]]['mtu'],
186 ifs_data[_ifs[_idx]]['state'],
187 _linked
188 ))
Alex Savatieievd79dde12019-03-13 19:07:46 -0500189
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 ))