From 16d049f7cd76f0e309814a2b09cde32ff680d962 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sun, 29 Dec 2019 23:03:58 +0100 Subject: [PATCH] luci-mod-status: reimplement route status page as client side view Signed-off-by: Jo-Philipp Wich --- .../root/usr/share/rpcd/acl.d/luci-base.json | 4 + .../resources/view/status/routes.js | 216 ++++++++++++++++++ .../luasrc/view/admin_status/routes.htm | 156 ------------- .../share/luci/menu.d/luci-mod-status.json | 4 +- 4 files changed, 222 insertions(+), 158 deletions(-) create mode 100644 modules/luci-mod-status/htdocs/luci-static/resources/view/status/routes.js delete mode 100644 modules/luci-mod-status/luasrc/view/admin_status/routes.htm diff --git a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json index ba0c213c9..1f5b26f8d 100644 --- a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json +++ b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json @@ -44,6 +44,10 @@ "/bin/ping6 *": [ "exec" ], "/bin/traceroute *": [ "exec" ], "/bin/traceroute6 *": [ "exec" ], + "/sbin/ip -4 neigh show": [ "exec" ], + "/sbin/ip -4 route show table all": [ "exec" ], + "/sbin/ip -6 neigh show": [ "exec" ], + "/sbin/ip -6 route show table all": [ "exec" ], "/sbin/logread -e ^": [ "exec" ], "/usr/bin/ping *": [ "exec" ], "/usr/bin/ping6 *": [ "exec" ], diff --git a/modules/luci-mod-status/htdocs/luci-static/resources/view/status/routes.js b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/routes.js new file mode 100644 index 000000000..a296b627f --- /dev/null +++ b/modules/luci-mod-status/htdocs/luci-static/resources/view/status/routes.js @@ -0,0 +1,216 @@ +'use strict'; +'require fs'; +'require rpc'; +'require validation'; + +var callNetworkInterfaceDump = rpc.declare({ + object: 'network.interface', + method: 'dump', + expect: { interface: [] } +}); + +function applyMask(addr, mask, v6) { + var words = v6 ? validation.parseIPv6(addr) : validation.parseIPv4(addr); + + if (!words || mask < 0 || mask > (v6 ? 128 : 32)) + return null; + + for (var i = 0; i < words.length; i++) { + var b = Math.min(mask, v6 ? 16 : 8); + words[i] &= ((1 << b) - 1); + mask -= b; + } + + return String.prototype.format.apply( + v6 ? '%x:%x:%x:%x:%x:%x:%x:%x' : '%d.%d.%d.%d', words); +} + +return L.view.extend({ + load: function() { + return Promise.all([ + callNetworkInterfaceDump(), + L.resolveDefault(fs.exec('/sbin/ip', [ '-4', 'neigh', 'show' ]), {}), + L.resolveDefault(fs.exec('/sbin/ip', [ '-4', 'route', 'show', 'table', 'all' ]), {}), + L.resolveDefault(fs.exec('/sbin/ip', [ '-6', 'neigh', 'show' ]), {}), + L.resolveDefault(fs.exec('/sbin/ip', [ '-6', 'route', 'show', 'table', 'all' ]), {}) + ]); + }, + + getNetworkByDevice(networks, dev, addr, mask, v6) { + var addr_arrays = [ 'ipv4-address', 'ipv6-address', 'ipv6-prefix', 'ipv6-prefix-assignment', 'route' ], + matching_iface = null, + matching_prefix = -1; + + for (var i = 0; i < networks.length; i++) { + if (!L.isObject(networks[i])) + continue; + + if (networks[i].l3_device != dev && networks[i].device != dev) + continue; + + for (var j = 0; j < addr_arrays.length; j++) { + var addr_list = networks[i][addr_arrays[j]]; + + if (!Array.isArray(addr_list) || addr_list.length == 0) + continue; + + for (var k = 0; k < addr_list.length; k++) { + var cmp_addr = addr_list[k].address || addr_list[k].target, + cmp_mask = addr_list[k].mask; + + if (cmp_addr == null) + continue; + + var addr1 = applyMask(cmp_addr, cmp_mask, v6), + addr2 = applyMask(addr, cmp_mask, v6); + + if (addr1 != addr2 || mask < cmp_mask) + continue; + + if (cmp_mask > matching_prefix) { + matching_iface = networks[i].interface; + matching_prefix = cmp_mask; + } + } + } + } + + return matching_iface; + }, + + parseNeigh: function(s, networks, v6) { + var lines = s.trim().split(/\n/), + res = []; + + for (var i = 0; i < lines.length; i++) { + var m = lines[i].match(/^([0-9a-f:.]+) (.+) (\S+)$/), + addr = m ? m[1] : null, + flags = m ? m[2].trim().split(/\s+/) : [], + state = (m ? m[3] : null) || 'FAILED'; + + if (!addr || state == 'FAILED' || addr.match(/^fe[89a-f][0-9a-f]:/)) + continue; + + for (var j = 0; j < flags.length; j += 2) + flags[flags[j]] = flags[j + 1]; + + if (!flags.lladdr) + continue; + + var net = this.getNetworkByDevice(networks, flags.dev, addr, v6 ? 128 : 32, v6); + + res.push([ + addr, + flags.lladdr.toUpperCase(), + E('span', { 'class': 'ifacebadge' }, [ net ? net : '(%s)'.format(flags.dev) ]) + ]); + } + + return res; + }, + + parseRoute: function(s, networks, v6) { + var lines = s.trim().split(/\n/), + res = []; + + for (var i = 0; i < lines.length; i++) { + var m = lines[i].match(/^(?:([a-z_]+|\d+) )?(default|[0-9a-f:.\/]+) (.+)$/), + type = (m ? m[1] : null) || 'unicast', + dest = m ? (m[2] == 'default' ? (v6 ? '::/0' : '0.0.0.0/0') : m[2]) : null, + flags = m ? m[3].trim().split(/\s+/) : []; + + console.debug(lines[i], m); + + if (!dest || type != 'unicast' || dest == 'fe80::/64' || dest == 'ff00::/8') + continue; + + for (var j = 0; j < flags.length; j += 2) + flags[flags[j]] = flags[j + 1]; + + var addr = dest.split('/'), + bits = (addr[1] != null) ? +addr[1] : (v6 ? 128 : 32), + net = this.getNetworkByDevice(networks, flags.dev, addr[0], bits, v6); + + res.push([ + E('span', { 'class': 'ifacebadge' }, [ net ? net : '(%s)'.format(flags.dev) ]), + dest, + (v6 ? flags.from : flags.via) || '-', + String(flags.metric || 0), + flags.table || 'main' + ]); + } + + return res; + }, + + render: function(data) { + var networks = data[0], + ip4neigh = data[1].stdout || '', + ip4route = data[2].stdout || '', + ip6neigh = data[3].stdout || '', + ip6route = data[4].stdout || ''; + + var neigh4tbl = E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th' }, [ _('IPv4-Address') ]), + E('div', { 'class': 'th' }, [ _('MAC-Address') ]), + E('div', { 'class': 'th' }, [ _('Interface') ]) + ]) + ]); + + var route4tbl = E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th' }, [ _('Network') ]), + E('div', { 'class': 'th' }, [ _('Target') ]), + E('div', { 'class': 'th' }, [ _('IPv4-Gateway') ]), + E('div', { 'class': 'th' }, [ _('Metric') ]), + E('div', { 'class': 'th' }, [ _('Table') ]) + ]) + ]); + + var neigh6tbl = E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th' }, [ _('IPv6-Address') ]), + E('div', { 'class': 'th' }, [ _('MAC-Address') ]), + E('div', { 'class': 'th' }, [ _('Interface') ]) + ]) + ]); + + var route6tbl = E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th' }, [ _('Network') ]), + E('div', { 'class': 'th' }, [ _('Target') ]), + E('div', { 'class': 'th' }, [ _('Source') ]), + E('div', { 'class': 'th' }, [ _('Metric') ]), + E('div', { 'class': 'th' }, [ _('Table') ]) + ]) + ]); + + cbi_update_table(neigh4tbl, this.parseNeigh(ip4neigh, networks, false)); + cbi_update_table(route4tbl, this.parseRoute(ip4route, networks, false)); + cbi_update_table(neigh6tbl, this.parseNeigh(ip6neigh, networks, true)); + cbi_update_table(route6tbl, this.parseRoute(ip6route, networks, true)); + + return E([], [ + E('h2', {}, [ _('Routes') ]), + E('p', {}, [ _('The following rules are currently active on this system.') ]), + + E('h3', {}, [ _('ARP') ]), + neigh4tbl, + + E('h3', {}, _('Active IPv4-Routes')), + route4tbl, + + E('h3', {}, [ _('IPv6 Neighbours') ]), + neigh6tbl, + + E('h3', {}, _('Active IPv6-Routes')), + route6tbl + ]); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); + diff --git a/modules/luci-mod-status/luasrc/view/admin_status/routes.htm b/modules/luci-mod-status/luasrc/view/admin_status/routes.htm deleted file mode 100644 index 74779f6ad..000000000 --- a/modules/luci-mod-status/luasrc/view/admin_status/routes.htm +++ /dev/null @@ -1,156 +0,0 @@ -<%# - Copyright 2008-2009 Steven Barth - Copyright 2008-2015 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<%- - require "luci.tools.webadmin" - require "nixio.fs" - - local ip = require "luci.ip" - local style = true - local _, v - - local rtn = { - [255] = "local", - [254] = "main", - [253] = "default", - [0] = "unspec" - } - - if nixio.fs.access("/etc/iproute2/rt_tables") then - local ln - for ln in io.lines("/etc/iproute2/rt_tables") do - local i, n = ln:match("^(%d+)%s+(%S+)") - if i and n then - rtn[tonumber(i)] = n - end - end - end --%> - -<%+header%> - - -
-

<%:Routes%>

-
<%:The following rules are currently active on this system.%>
- -
- ARP -
-
-
-
<%_IPv4-Address%>
-
<%_MAC-Address%>
-
<%:Interface%>
-
- - <% - for _, v in ipairs(ip.neighbors({ family = 4 })) do - if v.mac then - %> -
-
<%=v.dest%>
-
<%=v.mac%>
-
<%=luci.tools.webadmin.iface_get_network(v.dev) or '(' .. v.dev .. ')'%>
-
- <% - style = not style - end - end - %> -
-
-
- -
- <%_Active IPv4-Routes%> -
-
-
-
<%:Network%>
-
<%:Target%>
-
<%_IPv4-Gateway%>
-
<%:Metric%>
-
<%:Table%>
-
- <% for _, v in ipairs(ip.routes({ family = 4, type = 1 })) do %> -
-
<%=luci.tools.webadmin.iface_get_network(v.dev) or v.dev%>
-
<%=v.dest%>
-
<%=v.gw or "-"%>
-
<%=v.metric or 0%>
-
<%=rtn[v.table] or v.table%>
-
- <% style = not style end %> -
-
-
- - <% - if nixio.fs.access("/proc/net/ipv6_route") then - style = true - %> -
- <%_Active IPv6-Routes%> -
-
-
-
<%:Network%>
-
<%:Target%>
-
<%:Source%>
-
<%:Metric%>
-
<%:Table%>
-
- <% - for _, v in ipairs(ip.routes({ family = 6, type = 1 })) do - if v.dest and not v.dest:is6linklocal() then - %> -
-
<%=luci.tools.webadmin.iface_get_network(v.dev) or '(' .. v.dev .. ')'%>
-
<%=v.dest%>
-
<%=v.from%>
-
<%=v.metric or 0%>
-
<%=rtn[v.table] or v.table%>
-
- <% - style = not style - end - end - %> -
-
-
- -
- <%:IPv6 Neighbours%> -
-
-
-
<%:IPv6-Address%>
-
<%:MAC-Address%>
-
<%:Interface%>
-
- <% - for _, v in ipairs(ip.neighbors({ family = 6 })) do - if v.dest and not v.dest:is6linklocal() and v.mac then - %> -
-
<%=v.dest%>
-
<%=v.mac%>
-
<%=luci.tools.webadmin.iface_get_network(v.dev) or '(' .. v.dev .. ')'%>
-
- <% - style = not style - end - end - %> -
-
-
- <% end %> -
- -<%+footer%> diff --git a/modules/luci-mod-status/root/usr/share/luci/menu.d/luci-mod-status.json b/modules/luci-mod-status/root/usr/share/luci/menu.d/luci-mod-status.json index 5b53b12d3..0dcf6e843 100644 --- a/modules/luci-mod-status/root/usr/share/luci/menu.d/luci-mod-status.json +++ b/modules/luci-mod-status/root/usr/share/luci/menu.d/luci-mod-status.json @@ -21,8 +21,8 @@ "title": "Routes", "order": 3, "action": { - "type": "template", - "path": "admin_status/routes" + "type": "view", + "path": "status/routes" } }, -- 2.25.1