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