8d7e36f701fea1d5f1a36cb3ee56c0c60e2603f6
[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 device_title = _('Which is used to access this %s').format(_('Target'));
177 var target_title = _('Network and its mask that define the size of the destination');
178 var gateway_title = _('The address through which this %s is reachable').format(_('Target'));
179 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');
180 var table_title = _('Common name or numeric ID of the %s in which this route is found').format(_('Table'));
181 var proto_title = _('The routing protocol identifier of this route');
182 var source_title = _('Network and its mask that define which source addresses use this route');
183
184 var neigh4tbl = E('table', { 'class': 'table' }, [
185 E('tr', { 'class': 'tr table-titles' }, [
186 E('th', { 'class': 'th' }, [ _('IP address') ]),
187 E('th', { 'class': 'th' }, [ _('MAC address') ]),
188 E('th', { 'class': 'th' }, [ _('Interface') ])
189 ])
190 ]);
191
192 var route4tbl = E('table', { 'class': 'table' }, [
193 E('tr', { 'class': 'tr table-titles' }, [
194 E('th', { 'class': 'th', 'title': device_title }, [ _('Device') ]),
195 E('th', { 'class': 'th', 'title': target_title }, [ _('Target') ]),
196 E('th', { 'class': 'th', 'title': gateway_title }, [ _('Gateway') ]),
197 E('th', { 'class': 'th', 'title': metric_title }, [ _('Metric') ]),
198 E('th', { 'class': 'th', 'title': table_title }, [ _('Table') ]),
199 E('th', { 'class': 'th', 'title': proto_title }, [ _('Protocol') ])
200 ])
201 ]);
202
203 var rule4tbl = E('table', { 'class': 'table' }, [
204 E('tr', { 'class': 'tr table-titles' }, [
205 E('th', { 'class': 'th' }, [ _('Priority') ]),
206 E('th', { 'class': 'th' }, [ _('Rule') ])
207 ])
208 ]);
209
210 var neigh6tbl = E('table', { 'class': 'table' }, [
211 E('tr', { 'class': 'tr table-titles' }, [
212 E('th', { 'class': 'th' }, [ _('IP address') ]),
213 E('th', { 'class': 'th' }, [ _('MAC address') ]),
214 E('th', { 'class': 'th' }, [ _('Interface') ])
215 ])
216 ]);
217
218 var route6tbl = E('table', { 'class': 'table' }, [
219 E('tr', { 'class': 'tr table-titles' }, [
220 E('th', { 'class': 'th', 'title': device_title }, [ _('Device') ]),
221 E('th', { 'class': 'th', 'title': target_title }, [ _('Target') ]),
222 E('th', { 'class': 'th', 'title': source_title }, [ _('Source') ]),
223 E('th', { 'class': 'th', 'title': metric_title }, [ _('Metric') ]),
224 E('th', { 'class': 'th', 'title': table_title }, [ _('Table') ]),
225 E('th', { 'class': 'th', 'title': proto_title }, [ _('Protocol') ])
226 ])
227 ]);
228
229 var rule6tbl = E('table', { 'class': 'table' }, [
230 E('tr', { 'class': 'tr table-titles' }, [
231 E('th', { 'class': 'th' }, [ _('Priority') ]),
232 E('th', { 'class': 'th' }, [ _('Rule') ])
233 ])
234 ]);
235
236 cbi_update_table(neigh4tbl, this.parseNeigh(ip4neigh, networks, false),
237 E('em', _('No entries available'))
238 );
239 cbi_update_table(route4tbl, this.parseRoute(ip4route, networks, false),
240 E('em', _('No entries available'))
241 );
242 cbi_update_table(rule4tbl, this.parseRule(ip4rule, networks, false),
243 E('em', _('No entries available'))
244 );
245 cbi_update_table(neigh6tbl, this.parseNeigh(ip6neigh, networks, true),
246 E('em', _('No entries available'))
247 );
248 cbi_update_table(route6tbl, this.parseRoute(ip6route, networks, true),
249 E('em', _('No entries available'))
250 );
251 cbi_update_table(rule6tbl, this.parseRule(ip6rule, networks, false),
252 E('em', _('No entries available'))
253 );
254
255 var view = E([], [
256 E('h2', {}, [ _('Routing') ]),
257 E('p', {}, [ _('The following rules are currently active on this system.') ]),
258 E('div', {}, [
259 E('div', { 'class': 'cbi-section', 'data-tab': 'ipv4routing', 'data-tab-title': _('IPv4 Routing') }, [
260 E('h3', {}, [ _('IPv4 Neighbours') ]),
261 neigh4tbl,
262
263 E('h3', {}, [ _('Active IPv4 Routes') ]),
264 route4tbl,
265
266 E('h3', {}, [ _('Active IPv4 Rules') ]),
267 rule4tbl
268 ]),
269 E('div', { 'class': 'cbi-section', 'data-tab': 'ipv6routing', 'data-tab-title': _('IPv6 Routing') }, [
270 E('h3', {}, [ _('IPv6 Neighbours') ]),
271 neigh6tbl,
272
273 E('h3', {}, [ _('Active IPv6 Routes') ]),
274 route6tbl,
275
276 E('h3', {}, [ _('Active IPv6 Rules') ]),
277 rule6tbl
278 ])
279 ])
280 ]);
281
282 ui.tabs.initTabGroup(view.lastElementChild.childNodes);
283
284 return view;
285 },
286
287 handleSaveApply: null,
288 handleSave: null,
289 handleReset: null
290 });