luci-mod-status: fix sporadic logical interfaces resolve failures
[project/luci.git] / modules / luci-mod-status / htdocs / luci-static / resources / view / status / routes.js
1 'use strict';
2 'require view';
3 'require fs';
4 'require rpc';
5 'require validation';
6 'require ui';
7
8 var callNetworkInterfaceDump = rpc.declare({
9 object: 'network.interface',
10 method: 'dump',
11 expect: { interface: [] }
12 });
13
14 function applyMask(addr, mask, v6) {
15 var words = v6 ? validation.parseIPv6(addr) : validation.parseIPv4(addr);
16 var bword = v6 ? 0xffff : 0xff;
17 var bwlen = v6 ? 16 : 8;
18
19 if (!words || mask < 0 || mask > (v6 ? 128 : 32))
20 return null;
21
22 for (var i = 0; i < words.length; i++) {
23 var b = Math.min(mask, bwlen);
24 words[i] &= (bword << (bwlen - b)) & bword;
25 mask -= b;
26 }
27
28 return String.prototype.format.apply(
29 v6 ? '%x:%x:%x:%x:%x:%x:%x:%x' : '%d.%d.%d.%d', words);
30 }
31
32 return view.extend({
33 load: function() {
34 return Promise.all([
35 callNetworkInterfaceDump(),
36 L.resolveDefault(fs.exec('/sbin/ip', [ '-4', 'neigh', 'show' ]), {}),
37 L.resolveDefault(fs.exec('/sbin/ip', [ '-4', 'route', 'show', 'table', 'all' ]), {}),
38 L.resolveDefault(fs.exec('/sbin/ip', [ '-4', 'rule', 'show' ]), {}),
39 L.resolveDefault(fs.exec('/sbin/ip', [ '-6', 'neigh', 'show' ]), {}),
40 L.resolveDefault(fs.exec('/sbin/ip', [ '-6', 'route', 'show', 'table', 'all' ]), {}),
41 L.resolveDefault(fs.exec('/sbin/ip', [ '-6', 'rule', 'show' ]), {})
42 ]);
43 },
44
45 getNetworkByDevice(networks, dev, addr, mask, v6) {
46 var addr_arrays = [ 'ipv4-address', 'ipv6-address', 'ipv6-prefix', 'ipv6-prefix-assignment', 'route' ],
47 matching_iface = null,
48 matching_prefix = -1;
49
50 for (var i = 0; i < networks.length; i++) {
51 if (!L.isObject(networks[i]))
52 continue;
53
54 if (networks[i].l3_device != dev && networks[i].device != dev)
55 continue;
56
57 for (var j = 0; j < addr_arrays.length; j++) {
58 var addr_list = networks[i][addr_arrays[j]];
59
60 if (!Array.isArray(addr_list) || addr_list.length == 0)
61 continue;
62
63 for (var k = 0; k < addr_list.length; k++) {
64 var cmp_addr = addr_list[k].address || addr_list[k].target,
65 cmp_mask = addr_list[k].mask;
66
67 if (cmp_addr == null)
68 continue;
69
70 var addr1 = applyMask(cmp_addr, cmp_mask, v6),
71 addr2 = applyMask(addr, cmp_mask, v6);
72
73 if (addr1 != addr2 || mask < cmp_mask)
74 continue;
75
76 if (cmp_mask > matching_prefix) {
77 matching_iface = networks[i].interface;
78 matching_prefix = cmp_mask;
79 }
80 }
81 }
82 }
83
84 return matching_iface;
85 },
86
87 parseNeigh: function(s, networks, v6) {
88 var lines = s.trim().split(/\n/),
89 res = [];
90
91 for (var i = 0; i < lines.length; i++) {
92 var m = lines[i].match(/^([0-9a-f:.]+) (.+) (\S+) *$/),
93 addr = m ? m[1] : null,
94 flags = m ? m[2].trim().split(/\s+/) : [],
95 state = (m ? m[3] : null) || 'FAILED';
96
97 if (!addr || state == 'FAILED' || addr.match(/^fe[89a-f][0-9a-f]:/))
98 continue;
99
100 for (var j = 0; j < flags.length; j += 2)
101 flags[flags[j]] = flags[j + 1];
102
103 if (!flags.lladdr)
104 continue;
105
106 var net = this.getNetworkByDevice(networks, flags.dev, addr, v6 ? 128 : 32, v6);
107
108 res.push([
109 addr,
110 flags.lladdr.toUpperCase(),
111 E('span', { 'class': 'ifacebadge' }, [ net ? net : '(%s)'.format(flags.dev) ])
112 ]);
113 }
114
115 return res;
116 },
117
118 parseRoute: function(s, networks, v6) {
119 var lines = s.trim().split(/\n/),
120 res = [];
121
122 for (var i = 0; i < lines.length; i++) {
123 var m = lines[i].match(/^(?:([a-z_]+|\d+) )?(default|[0-9a-f:.\/]+) (.+)$/),
124 type = (m ? m[1] : null) || 'unicast',
125 dest = m ? (m[2] == 'default' ? (v6 ? '::/0' : '0.0.0.0/0') : m[2]) : null,
126 flags = m ? m[3].trim().split(/\s+/) : [];
127
128 if (!dest || type != 'unicast' || dest == 'fe80::/64' || dest == 'ff00::/8')
129 continue;
130
131 for (var j = 0; j < flags.length; j += 2)
132 flags[flags[j]] = flags[j + 1];
133
134 var addr = dest.split('/'),
135 bits = (addr[1] != null) ? +addr[1] : (v6 ? 128 : 32),
136 net = this.getNetworkByDevice(networks, flags.dev, addr[0], bits, v6);
137
138 res.push([
139 E('span', { 'class': 'ifacebadge' }, [ net ? net : '(%s)'.format(flags.dev) ]),
140 dest,
141 (v6 ? flags.from : flags.via) || '-',
142 String(flags.metric || 0),
143 flags.table || 'main',
144 flags.proto,
145 ]);
146 }
147
148 return res;
149 },
150
151 parseRule: function(s) {
152 var lines = s.trim().split(/\n/),
153 res = [];
154
155 for (var i = 0; i < lines.length; i++) {
156 var m = lines[i].match(/^(\d+):\s+(.+)$/),
157 prio = m ? m[1] : null,
158 rule = m ? m[2] : null;
159
160 res.push([
161 prio,
162 rule
163 ]);
164 }
165
166 return res;
167 },
168
169 render: function(data) {
170 var networks = data[0],
171 ip4neigh = data[1].stdout || '',
172 ip4route = data[2].stdout || '',
173 ip4rule = data[3].stdout || '',
174 ip6neigh = data[4].stdout || '',
175 ip6route = data[5].stdout || '',
176 ip6rule = data[6].stdout || '';
177
178 var device_title = _('Which is used to access this %s').format(_('Target'));
179 var target_title = _('Network and its mask that define the size of the destination');
180 var gateway_title = _('The address through which this %s is reachable').format(_('Target'));
181 var metric_title = _('Quantifies the cost or distance to a destination in a way that allows routers to make informed decisions about the optimal path to forward data packets');
182 var table_title = _('Common name or numeric ID of the %s in which this route is found').format(_('Table'));
183 var proto_title = _('The routing protocol identifier of this route');
184 var source_title = _('Network and its mask that define which source addresses use this route');
185
186 var neigh4tbl = E('table', { 'class': 'table' }, [
187 E('tr', { 'class': 'tr table-titles' }, [
188 E('th', { 'class': 'th' }, [ _('IP address') ]),
189 E('th', { 'class': 'th' }, [ _('MAC address') ]),
190 E('th', { 'class': 'th' }, [ _('Interface') ])
191 ])
192 ]);
193
194 var route4tbl = E('table', { 'class': 'table' }, [
195 E('tr', { 'class': 'tr table-titles' }, [
196 E('th', { 'class': 'th', 'title': device_title }, [ _('Device') ]),
197 E('th', { 'class': 'th', 'title': target_title }, [ _('Target') ]),
198 E('th', { 'class': 'th', 'title': gateway_title }, [ _('Gateway') ]),
199 E('th', { 'class': 'th', 'title': metric_title }, [ _('Metric') ]),
200 E('th', { 'class': 'th', 'title': table_title }, [ _('Table') ]),
201 E('th', { 'class': 'th', 'title': proto_title }, [ _('Protocol') ])
202 ])
203 ]);
204
205 var rule4tbl = E('table', { 'class': 'table' }, [
206 E('tr', { 'class': 'tr table-titles' }, [
207 E('th', { 'class': 'th' }, [ _('Priority') ]),
208 E('th', { 'class': 'th' }, [ _('Rule') ])
209 ])
210 ]);
211
212 var neigh6tbl = E('table', { 'class': 'table' }, [
213 E('tr', { 'class': 'tr table-titles' }, [
214 E('th', { 'class': 'th' }, [ _('IP address') ]),
215 E('th', { 'class': 'th' }, [ _('MAC address') ]),
216 E('th', { 'class': 'th' }, [ _('Interface') ])
217 ])
218 ]);
219
220 var route6tbl = E('table', { 'class': 'table' }, [
221 E('tr', { 'class': 'tr table-titles' }, [
222 E('th', { 'class': 'th', 'title': device_title }, [ _('Device') ]),
223 E('th', { 'class': 'th', 'title': target_title }, [ _('Target') ]),
224 E('th', { 'class': 'th', 'title': source_title }, [ _('Source') ]),
225 E('th', { 'class': 'th', 'title': metric_title }, [ _('Metric') ]),
226 E('th', { 'class': 'th', 'title': table_title }, [ _('Table') ]),
227 E('th', { 'class': 'th', 'title': proto_title }, [ _('Protocol') ])
228 ])
229 ]);
230
231 var rule6tbl = E('table', { 'class': 'table' }, [
232 E('tr', { 'class': 'tr table-titles' }, [
233 E('th', { 'class': 'th' }, [ _('Priority') ]),
234 E('th', { 'class': 'th' }, [ _('Rule') ])
235 ])
236 ]);
237
238 cbi_update_table(neigh4tbl, this.parseNeigh(ip4neigh, networks, false),
239 E('em', _('No entries available'))
240 );
241 cbi_update_table(route4tbl, this.parseRoute(ip4route, networks, false),
242 E('em', _('No entries available'))
243 );
244 cbi_update_table(rule4tbl, this.parseRule(ip4rule, networks, false),
245 E('em', _('No entries available'))
246 );
247 cbi_update_table(neigh6tbl, this.parseNeigh(ip6neigh, networks, true),
248 E('em', _('No entries available'))
249 );
250 cbi_update_table(route6tbl, this.parseRoute(ip6route, networks, true),
251 E('em', _('No entries available'))
252 );
253 cbi_update_table(rule6tbl, this.parseRule(ip6rule, networks, false),
254 E('em', _('No entries available'))
255 );
256
257 var view = E([], [
258 E('h2', {}, [ _('Routing') ]),
259 E('p', {}, [ _('The following rules are currently active on this system.') ]),
260 E('div', {}, [
261 E('div', { 'class': 'cbi-section', 'data-tab': 'ipv4routing', 'data-tab-title': _('IPv4 Routing') }, [
262 E('h3', {}, [ _('IPv4 Neighbours') ]),
263 neigh4tbl,
264
265 E('h3', {}, [ _('Active IPv4 Routes') ]),
266 route4tbl,
267
268 E('h3', {}, [ _('Active IPv4 Rules') ]),
269 rule4tbl
270 ]),
271 E('div', { 'class': 'cbi-section', 'data-tab': 'ipv6routing', 'data-tab-title': _('IPv6 Routing') }, [
272 E('h3', {}, [ _('IPv6 Neighbours') ]),
273 neigh6tbl,
274
275 E('h3', {}, [ _('Active IPv6 Routes') ]),
276 route6tbl,
277
278 E('h3', {}, [ _('Active IPv6 Rules') ]),
279 rule6tbl
280 ])
281 ])
282 ]);
283
284 ui.tabs.initTabGroup(view.lastElementChild.childNodes);
285
286 return view;
287 },
288
289 handleSaveApply: null,
290 handleSave: null,
291 handleReset: null
292 });