summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-mod-network
diff options
context:
space:
mode:
Diffstat (limited to 'modules/luci-mod-network')
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js136
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js137
-rw-r--r--modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js4
-rw-r--r--modules/luci-mod-network/luasrc/controller/admin/network.lua167
-rw-r--r--modules/luci-mod-network/luasrc/view/admin_network/diagnostics.htm117
-rwxr-xr-xmodules/luci-mod-network/root/usr/libexec/luci-peeraddr46
-rw-r--r--modules/luci-mod-network/root/usr/share/luci/menu.d/luci-mod-network.json85
7 files changed, 399 insertions, 293 deletions
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
index ab6779e149..c4db638b11 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/dhcp.js
@@ -2,8 +2,9 @@
'require rpc';
'require uci';
'require form';
+'require validation';
-var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus;
+var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus, CBILease6Status;
callHostHints = rpc.declare({
object: 'luci-rpc',
@@ -20,8 +21,7 @@ callDUIDHints = rpc.declare({
callDHCPLeases = rpc.declare({
object: 'luci-rpc',
method: 'getDHCPLeases',
- params: [ 'family' ],
- expect: { dhcp_leases: [] }
+ expect: { '': {} }
});
CBILeaseStatus = form.DummyValue.extend({
@@ -33,7 +33,26 @@ CBILeaseStatus = form.DummyValue.extend({
E('div', { 'class': 'th' }, _('Hostname')),
E('div', { 'class': 'th' }, _('IPv4-Address')),
E('div', { 'class': 'th' }, _('MAC-Address')),
- E('div', { 'class': 'th' }, _('Leasetime remaining'))
+ E('div', { 'class': 'th' }, _('Lease time remaining'))
+ ]),
+ E('div', { 'class': 'tr placeholder' }, [
+ E('div', { 'class': 'td' }, E('em', _('Collecting data...')))
+ ])
+ ])
+ ]);
+ }
+});
+
+CBILease6Status = form.DummyValue.extend({
+ renderWidget: function(section_id, option_id, cfgvalue) {
+ return E([
+ E('h4', _('Active DHCPv6 Leases')),
+ E('div', { 'id': 'lease6_status_table', 'class': 'table' }, [
+ E('div', { 'class': 'tr table-titles' }, [
+ E('div', { 'class': 'th' }, _('Host')),
+ E('div', { 'class': 'th' }, _('IPv6-Address')),
+ E('div', { 'class': 'th' }, _('DUID')),
+ E('div', { 'class': 'th' }, _('Lease time remaining'))
]),
E('div', { 'class': 'tr placeholder' }, [
E('div', { 'class': 'td' }, E('em', _('Collecting data...')))
@@ -43,6 +62,67 @@ CBILeaseStatus = form.DummyValue.extend({
}
});
+function validateHostname(sid, s) {
+ if (s.length > 256)
+ return _('Expecting: %s').format(_('valid hostname'));
+
+ var labels = s.replace(/^\.+|\.$/g, '').split(/\./);
+
+ for (var i = 0; i < labels.length; i++)
+ if (!labels[i].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
+ return _('Expecting: %s').format(_('valid hostname'));
+
+ return true;
+}
+
+function validateAddressList(sid, s) {
+ if (s == null || s == '')
+ return true;
+
+ var m = s.match(/^\/(.+)\/$/),
+ names = m ? m[1].split(/\//) : [ s ];
+
+ for (var i = 0; i < names.length; i++) {
+ var res = validateHostname(sid, names[i]);
+
+ if (res !== true)
+ return res;
+ }
+
+ return true;
+}
+
+function validateServerSpec(sid, s) {
+ if (s == null || s == '')
+ return true;
+
+ var m = s.match(/^\/(.+)\/(.*)$/);
+ if (!m)
+ return _('Expecting: %s').format(_('valid hostname'));
+
+ var res = validateAddressList(sid, m[1]);
+ if (res !== true)
+ return res;
+
+ if (m[2] == '' || m[2] == '#')
+ return true;
+
+ // ipaddr%scopeid#srvport@source@interface#srcport
+
+ m = m[2].match(/^([0-9a-f:.]+)(?:%[^#@]+)?(?:#(\d+))?(?:@([0-9a-f:.]+)(?:@[^#]+)?(?:#(\d+))?)?$/);
+
+ if (!m)
+ return _('Expecting: %s').format(_('valid IP address'));
+ else if (validation.parseIPv4(m[1]) && m[3] != null && !validation.parseIPv4(m[3]))
+ return _('Expecting: %s').format(_('valid IPv4 address'));
+ else if (validation.parseIPv6(m[1]) && m[3] != null && !validation.parseIPv6(m[3]))
+ return _('Expecting: %s').format(_('valid IPv6 address'));
+ else if ((m[2] != null && +m[2] > 65535) || (m[4] != null && +m[4] > 65535))
+ return _('Expecting: %s').format(_('valid port value'));
+
+ return true;
+}
+
return L.view.extend({
load: function() {
return Promise.all([
@@ -52,7 +132,8 @@ return L.view.extend({
},
render: function(hosts_duids) {
- var hosts = hosts_duids[0],
+ var has_dhcpv6 = L.hasSystemFeature('dnsmasq', 'dhcpv6') || L.hasSystemFeature('odhcpd'),
+ hosts = hosts_duids[0],
duids = hosts_duids[1],
m, s, o, ss, so;
@@ -182,6 +263,7 @@ return L.view.extend({
o.optional = true;
o.placeholder = '/example.org/10.1.2.3';
+ o.validate = validateServerSpec;
o = s.taboption('general', form.Flag, 'rebind_protection',
@@ -204,8 +286,8 @@ return L.view.extend({
o.optional = true;
o.depends('rebind_protection', '1');
- o.datatype = 'host(1)';
o.placeholder = 'ihost.netflix.com';
+ o.validate = validateAddressList;
o = s.taboption('advanced', form.Value, 'port',
@@ -288,6 +370,7 @@ return L.view.extend({
o = s.taboption('general', form.Flag, 'nonwildcard',
_('Non-wildcard'),
_('Bind dynamically to interfaces rather than wildcard address (recommended as linux default)'));
+ o.default = o.enabled;
o.optional = false;
o.rmempty = true;
@@ -399,9 +482,15 @@ return L.view.extend({
o = s.taboption('leases', CBILeaseStatus, '__status__');
+ if (has_dhcpv6)
+ o = s.taboption('leases', CBILease6Status, '__status6__');
+
return m.render().then(function(mapEl) {
L.Poll.add(function() {
- return callDHCPLeases(4).then(function(leases) {
+ return callDHCPLeases().then(function(leaseinfo) {
+ var leases = Array.isArray(leaseinfo.dhcp_leases) ? leaseinfo.dhcp_leases : [],
+ leases6 = Array.isArray(leaseinfo.dhcp6_leases) ? leaseinfo.dhcp6_leases : [];
+
cbi_update_table(mapEl.querySelector('#lease_status_table'),
leases.map(function(lease) {
var exp;
@@ -421,6 +510,39 @@ return L.view.extend({
];
}),
E('em', _('There are no active leases')));
+
+ if (has_dhcpv6) {
+ cbi_update_table(mapEl.querySelector('#lease6_status_table'),
+ leases6.map(function(lease) {
+ var exp;
+
+ if (lease.expires === false)
+ exp = E('em', _('unlimited'));
+ else if (lease.expires <= 0)
+ exp = E('em', _('expired'));
+ else
+ exp = '%t'.format(lease.expires);
+
+ var hint = lease.macaddr ? hosts[lease.macaddr] : null,
+ name = hint ? (hint.name || hint.ipv4 || hint.ipv6) : null,
+ host = null;
+
+ if (name && lease.hostname && lease.hostname != name && lease.ip6addr != name)
+ host = '%s (%s)'.format(lease.hostname, name);
+ else if (lease.hostname)
+ host = lease.hostname;
+ else if (name)
+ host = name;
+
+ return [
+ host || '-',
+ lease.ip6addrs ? lease.ip6addrs.join(' ') : lease.ip6addr,
+ lease.duid,
+ exp
+ ];
+ }),
+ E('em', _('There are no active leases')));
+ }
});
});
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js
new file mode 100644
index 0000000000..ee2a466151
--- /dev/null
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/diagnostics.js
@@ -0,0 +1,137 @@
+'use strict';
+'require fs';
+'require ui';
+'require uci';
+
+return L.view.extend({
+ handleCommand: function(exec, args) {
+ var buttons = document.querySelectorAll('.diag-action > .cbi-button');
+
+ for (var i = 0; i < buttons.length; i++)
+ buttons[i].setAttribute('disabled', 'true');
+
+ return fs.exec(exec, args).then(function(res) {
+ var out = document.querySelector('.command-output');
+ out.style.display = '';
+
+ L.dom.content(out, [ res.stdout || '', res.stderr || '' ]);
+ }).catch(function(err) {
+ ui.addNotification(null, E('p', [ err ]))
+ }).finally(function() {
+ for (var i = 0; i < buttons.length; i++)
+ buttons[i].removeAttribute('disabled');
+ });
+ },
+
+ handlePing: function(ev, cmd) {
+ var exec = cmd || 'ping',
+ addr = ev.currentTarget.parentNode.previousSibling.value,
+ args = (exec == 'ping') ? [ '-c', '5', '-W', '1', addr ] : [ '-c', '5', addr ];
+
+ return this.handleCommand(exec, args);
+ },
+
+ handleTraceroute: function(ev, cmd) {
+ var exec = cmd || 'traceroute',
+ addr = ev.currentTarget.parentNode.previousSibling.value,
+ args = (exec == 'traceroute') ? [ '-q', '1', '-w', '1', '-n', addr ] : [ '-q', '1', '-w', '2', '-n', addr ];
+
+ return this.handleCommand(exec, args);
+ },
+
+ handleNslookup: function(ev, cmd) {
+ var addr = ev.currentTarget.parentNode.previousSibling.value;
+
+ return this.handleCommand('nslookup', [ addr ]);
+ },
+
+ load: function() {
+ return Promise.all([
+ L.resolveDefault(fs.stat('/bin/ping6'), {}),
+ L.resolveDefault(fs.stat('/usr/bin/ping6'), {}),
+ L.resolveDefault(fs.stat('/bin/traceroute6'), {}),
+ L.resolveDefault(fs.stat('/usr/bin/traceroute6'), {}),
+ uci.load('luci')
+ ]);
+ },
+
+ render: function(res) {
+ var has_ping6 = res[0].path || res[1].path,
+ has_traceroute6 = res[2].path || res[3].path,
+ dns_host = uci.get('luci', 'diag', 'dns') || 'openwrt.org',
+ ping_host = uci.get('luci', 'diag', 'ping') || 'openwrt.org',
+ route_host = uci.get('luci', 'diag', 'route') || 'openwrt.org';
+
+ return E([], [
+ E('h2', {}, [ _('Network Utilities') ]),
+ E('div', { 'class': 'table' }, [
+ E('div', { 'class': 'tr' }, [
+ E('div', { 'class': 'td left' }, [
+ E('input', {
+ 'style': 'margin:5px 0',
+ 'type': 'text',
+ 'value': ping_host
+ }),
+ E('span', { 'class': 'diag-action' }, [
+ has_ping6 ? new ui.ComboButton('ping', {
+ 'ping': '%s %s'.format(_('IPv4'), _('Ping')),
+ 'ping6': '%s %s'.format(_('IPv6'), _('Ping')),
+ }, {
+ 'click': ui.createHandlerFn(this, 'handlePing'),
+ 'classes': {
+ 'ping': 'cbi-button cbi-button-action',
+ 'ping6': 'cbi-button cbi-button-action'
+ }
+ }).render() : E('button', {
+ 'class': 'cbi-button cbi-button-action',
+ 'click': ui.createHandlerFn(this, 'handlePing')
+ }, [ _('Ping') ])
+ ])
+ ]),
+
+ E('div', { 'class': 'td left' }, [
+ E('input', {
+ 'style': 'margin:5px 0',
+ 'type': 'text',
+ 'value': route_host
+ }),
+ E('span', { 'class': 'diag-action' }, [
+ has_traceroute6 ? new ui.ComboButton('traceroute', {
+ 'traceroute': '%s %s'.format(_('IPv4'), _('Traceroute')),
+ 'traceroute6': '%s %s'.format(_('IPv6'), _('Traceroute')),
+ }, {
+ 'click': ui.createHandlerFn(this, 'handleTraceroute'),
+ 'classes': {
+ 'traceroute': 'cbi-button cbi-button-action',
+ 'traceroute6': 'cbi-button cbi-button-action'
+ }
+ }).render() : E('button', {
+ 'class': 'cbi-button cbi-button-action',
+ 'click': ui.createHandlerFn(this, 'handleTraceroute')
+ }, [ _('Traceroute') ])
+ ])
+ ]),
+
+ E('div', { 'class': 'td left' }, [
+ E('input', {
+ 'style': 'margin:5px 0',
+ 'type': 'text',
+ 'value': dns_host
+ }),
+ E('span', { 'class': 'diag-action' }, [
+ E('button', {
+ 'class': 'cbi-button cbi-button-action',
+ 'click': ui.createHandlerFn(this, 'handleNslookup')
+ }, [ _('Nslookup') ])
+ ])
+ ])
+ ])
+ ]),
+ E('pre', { 'class': 'command-output', 'style': 'display:none' })
+ ]);
+ },
+
+ handleSaveApply: null,
+ handleSave: null,
+ handleReset: null
+});
diff --git a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
index 9ca7773fe1..280356efba 100644
--- a/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
+++ b/modules/luci-mod-network/htdocs/luci-static/resources/view/network/interfaces.js
@@ -160,8 +160,8 @@ function iface_updown(up, id, ev, force) {
btns[1].disabled = true;
if (!up) {
- L.Request.get(L.url('admin/network/remote_addr')).then(function(res) {
- var info = res.json();
+ L.resolveDefault(fs.exec_direct('/usr/libexec/luci-peeraddr')).then(function(res) {
+ var info = null; try { info = JSON.parse(res); } catch(e) {}
if (L.isObject(info) &&
Array.isArray(info.inbound_interfaces) &&
diff --git a/modules/luci-mod-network/luasrc/controller/admin/network.lua b/modules/luci-mod-network/luasrc/controller/admin/network.lua
deleted file mode 100644
index bd00235faa..0000000000
--- a/modules/luci-mod-network/luasrc/controller/admin/network.lua
+++ /dev/null
@@ -1,167 +0,0 @@
--- Copyright 2008 Steven Barth <steven@midlink.org>
--- Copyright 2011-2018 Jo-Philipp Wich <jo@mein.io>
--- Licensed to the public under the Apache License 2.0.
-
-module("luci.controller.admin.network", package.seeall)
-
-function index()
- local page
-
--- if page.inreq then
- page = entry({"admin", "network", "switch"}, view("network/switch"), _("Switch"), 20)
- page.uci_depends = { network = { ["@switch[0]"] = "switch" } }
-
- page = entry({"admin", "network", "wireless"}, view("network/wireless"), _('Wireless'), 15)
- page.uci_depends = { wireless = { ["@wifi-device[0]"] = "wifi-device" } }
- page.leaf = true
-
- page = entry({"admin", "network", "remote_addr"}, call("remote_addr"), nil)
- page.leaf = true
-
- page = entry({"admin", "network", "network"}, view("network/interfaces"), _("Interfaces"), 10)
- page.leaf = true
- page.subindex = true
-
- page = node("admin", "network", "dhcp")
- page.uci_depends = { dhcp = true }
- page.target = view("network/dhcp")
- page.title = _("DHCP and DNS")
- page.order = 30
-
- page = node("admin", "network", "hosts")
- page.uci_depends = { dhcp = true }
- page.target = view("network/hosts")
- page.title = _("Hostnames")
- page.order = 40
-
- page = node("admin", "network", "routes")
- page.target = view("network/routes")
- page.title = _("Static Routes")
- page.order = 50
-
- page = node("admin", "network", "diagnostics")
- page.target = template("admin_network/diagnostics")
- page.title = _("Diagnostics")
- page.order = 60
-
- page = entry({"admin", "network", "diag_ping"}, post("diag_ping"), nil)
- page.leaf = true
-
- page = entry({"admin", "network", "diag_nslookup"}, post("diag_nslookup"), nil)
- page.leaf = true
-
- page = entry({"admin", "network", "diag_traceroute"}, post("diag_traceroute"), nil)
- page.leaf = true
-
- page = entry({"admin", "network", "diag_ping6"}, post("diag_ping6"), nil)
- page.leaf = true
-
- page = entry({"admin", "network", "diag_traceroute6"}, post("diag_traceroute6"), nil)
- page.leaf = true
--- end
-end
-
-local function addr2dev(addr, src)
- local ip = require "luci.ip"
- local route = ip.route(addr, src)
- if not src and route and route.src then
- route = ip.route(addr, route.src:string())
- end
- return route and route.dev
-end
-
-function remote_addr()
- local uci = require "luci.model.uci"
- local peer = luci.http.getenv("REMOTE_ADDR")
- local serv = luci.http.getenv("SERVER_ADDR")
- local device = addr2dev(peer, serv)
- local ifaces = luci.util.ubus("network.interface", "dump")
- local indevs = {}
- local inifs = {}
-
- local result = {
- remote_addr = peer,
- server_addr = serv,
- inbound_devices = {},
- inbound_interfaces = {}
- }
-
- if type(ifaces) == "table" and type(ifaces.interface) == "table" then
- for _, iface in ipairs(ifaces.interface) do
- if type(iface) == "table" then
- if iface.device == device or iface.l3_device == device then
- inifs[iface.interface] = true
- indevs[device] = true
- end
-
- local peeraddr = uci:get("network", iface.interface, "peeraddr")
- for _, ai in ipairs(peeraddr and nixio.getaddrinfo(peeraddr) or {}) do
- local peerdev = addr2dev(ai.address)
- if peerdev then
- for _, iface in ipairs(ifaces.interface) do
- if type(iface) == "table" and
- (iface.device == peerdev or iface.l3_device == peerdev)
- then
- inifs[iface.interface] = true
- indevs[peerdev] = true
- end
- end
- end
- end
- end
- end
- end
-
- for k in pairs(inifs) do
- result.inbound_interfaces[#result.inbound_interfaces + 1] = k
- end
-
- for k in pairs(indevs) do
- result.inbound_devices[#result.inbound_devices + 1] = k
- end
-
- luci.http.prepare_content("application/json")
- luci.http.write_json(result)
-end
-
-function diag_command(cmd, addr)
- if addr and addr:match("^[a-zA-Z0-9%-%.:_]+$") then
- luci.http.prepare_content("text/plain")
-
- local util = io.popen(cmd % luci.util.shellquote(addr))
- if util then
- while true do
- local ln = util:read("*l")
- if not ln then break end
- luci.http.write(ln)
- luci.http.write("\n")
- end
-
- util:close()
- end
-
- return
- end
-
- luci.http.status(500, "Bad address")
-end
-
-function diag_ping(addr)
- diag_command("ping -c 5 -W 1 %s 2>&1", addr)
-end
-
-function diag_traceroute(addr)
- diag_command("traceroute -q 1 -w 1 -n %s 2>&1", addr)
-end
-
-function diag_nslookup(addr)
- diag_command("nslookup %s 2>&1", addr)
-end
-
-function diag_ping6(addr)
- diag_command("ping6 -c 5 %s 2>&1", addr)
-end
-
-function diag_traceroute6(addr)
- diag_command("traceroute6 -q 1 -w 2 -n %s 2>&1", addr)
-end
diff --git a/modules/luci-mod-network/luasrc/view/admin_network/diagnostics.htm b/modules/luci-mod-network/luasrc/view/admin_network/diagnostics.htm
deleted file mode 100644
index 03dd5aab29..0000000000
--- a/modules/luci-mod-network/luasrc/view/admin_network/diagnostics.htm
+++ /dev/null
@@ -1,117 +0,0 @@
-<%#
- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
- Licensed to the public under the Apache License 2.0.
--%>
-
-<%+header%>
-
-<%
-local fs = require "nixio.fs"
-local has_ping6 = fs.access("/bin/ping6") or fs.access("/usr/bin/ping6")
-local has_traceroute6 = fs.access("/bin/traceroute6") or fs.access("/usr/bin/traceroute6")
-
-local dns_host = luci.config.diag and luci.config.diag.dns or "dev.openwrt.org"
-local ping_host = luci.config.diag and luci.config.diag.ping or "dev.openwrt.org"
-local route_host = luci.config.diag and luci.config.diag.route or "dev.openwrt.org"
-%>
-
-<script type="text/javascript">//<![CDATA[
- var stxhr = new XHR();
-
- function update_status(field, proto)
- {
- var tool = field.name;
- var addr = field.value;
- var protocol = proto ? "6" : "";
-
- var legend = document.getElementById('diag-rc-legend');
- var output = document.getElementById('diag-rc-output');
-
- if (legend && output)
- {
- output.innerHTML =
- '<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> ' +
- '<%:Waiting for command to complete...%>'
- ;
-
- legend.parentNode.style.display = 'block';
- legend.style.display = 'inline';
-
- stxhr.post('<%=url('admin/network')%>/diag_' + tool + protocol + '/' + addr, { token: '<%=token%>' },
- function(x)
- {
- if (x.responseText)
- {
- legend.style.display = 'none';
- output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
- }
- else
- {
- legend.style.display = 'none';
- output.innerHTML = '<span class="error"><%:Bad address specified!%></span>';
- }
- }
- );
- }
- }
-//]]></script>
-
-<form method="post" action="<%=url('admin/network/diagnostics')%>">
- <div class="cbi-map">
- <h2 name="content"><%:Diagnostics%></h2>
-
- <div class="cbi-section">
- <legend><%:Network Utilities%></legend>
-
- <div class="table">
- <div class="tr">
- <div class="td left">
- <input style="margin: 5px 0" type="text" value="<%=ping_host%>" name="ping" /><br />
- <% if has_ping6 then %>
- <span>
- <select name="ping_proto" style="width:auto">
- <option value="" selected="selected"><%:IPv4%></option>
- <option value="6"><%:IPv6%></option>
- </select>
- </span>
- <input type="button" value="<%:Ping%>" class="cbi-button cbi-button-apply" onclick="update_status(this.form.ping, this.form.ping_proto.selectedIndex)" />
- <% else %>
- <input type="button" value="<%:Ping%>" class="cbi-button cbi-button-apply" onclick="update_status(this.form.ping)" />
- <% end %>
- </div>
-
- <div class="td left">
- <input style="margin: 5px 0" type="text" value="<%=route_host%>" name="traceroute" /><br />
- <% if has_traceroute6 then %>
- <span>
- <select name="traceroute_proto" style="width:auto">
- <option value="" selected="selected"><%:IPv4%></option>
- <option value="6"><%:IPv6%></option>
- </select>
- </span>
- <input type="button" value="<%:Traceroute%>" class="cbi-button cbi-button-apply" onclick="update_status(this.form.traceroute, this.form.traceroute_proto.selectedIndex)" />
- <% else %>
- <input type="button" value="<%:Traceroute%>" class="cbi-button cbi-button-apply" onclick="update_status(this.form.traceroute)" />
- <% end %>
- <% if not has_traceroute6 then %>
- <p>&#160;</p>
- <p><%:Install iputils-traceroute6 for IPv6 traceroute%></p>
- <% end %>
- </div>
-
- <div class="td left">
- <input style="margin: 5px 0" type="text" value="<%=dns_host%>" name="nslookup" /><br />
- <input type="button" value="<%:Nslookup%>" class="cbi-button cbi-button-apply" onclick="update_status(this.form.nslookup)" />
- </div>
- </div>
- </div>
- </div>
- </div>
-
- <div class="cbi-section" style="display:none">
- <strong id="diag-rc-legend"></strong>
- <span id="diag-rc-output"></span>
- </div>
-</form>
-
-<%+footer%>
diff --git a/modules/luci-mod-network/root/usr/libexec/luci-peeraddr b/modules/luci-mod-network/root/usr/libexec/luci-peeraddr
new file mode 100755
index 0000000000..84a0158fd5
--- /dev/null
+++ b/modules/luci-mod-network/root/usr/libexec/luci-peeraddr
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+NL="
+"
+
+function ifaces_by_device() {
+ ubus call network.interface dump 2>/dev/null | \
+ jsonfilter -e "@.interface[@.device='$1' || @.l3_device='$1'].interface"
+}
+
+function device_by_addr() {
+ set -- $(ip route get "$1" ${2:+from "$2"} 2>/dev/null)
+ echo "$5"
+}
+
+for inbound_device in $(device_by_addr "$REMOTE_ADDR" "$SERVER_ADDR"); do
+ inbound_devices="$inbound_device"
+ inbound_interfaces=""
+
+ for iface in $(ifaces_by_device "$inbound_device"); do
+ inbound_interfaces="${inbound_interfaces:+$inbound_interfaces$NL}$iface"
+
+ for peeraddr in $(uci get "network.$iface.peeraddr"); do
+ for ipaddr in $(resolveip -t 1 "$peeraddr" 2>/dev/null); do
+ for peerdev in $(device_by_addr "$ipaddr"); do
+ for iface in $(ifaces_by_device "$peerdev"); do
+ inbound_devices="${inbound_devices:+$inbound_devices$NL}$peerdev"
+ inbound_interfaces="${inbound_interfaces:+$inbound_interfaces$NL}$iface"
+ done
+ done
+ done
+ done
+ done
+done
+
+inbound_devices="$(echo "$inbound_devices" | sort -u | sed ':a;N;$!ba;s/\n/", "/g')"
+inbound_interfaces="$(echo "$inbound_interfaces" | sort -u | sed ':a;N;$!ba;s/\n/", "/g')"
+
+cat <<JSON
+{
+ "remote_addr": "$REMOTE_ADDR",
+ "server_addr": "$SERVER_ADDR",
+ "inbound_devices": [ ${inbound_devices:+\"$inbound_devices\"} ],
+ "inbound_interfaces": [ ${inbound_interfaces:+\"$inbound_interfaces\"} ]
+}
+JSON
diff --git a/modules/luci-mod-network/root/usr/share/luci/menu.d/luci-mod-network.json b/modules/luci-mod-network/root/usr/share/luci/menu.d/luci-mod-network.json
new file mode 100644
index 0000000000..670f2c1a49
--- /dev/null
+++ b/modules/luci-mod-network/root/usr/share/luci/menu.d/luci-mod-network.json
@@ -0,0 +1,85 @@
+{
+ "admin/network/switch": {
+ "title": "Switch",
+ "order": 20,
+ "action": {
+ "type": "view",
+ "path": "network/switch"
+ },
+ "depends": {
+ "fs": { "/sbin/swconfig": "executable" },
+ "uci": { "network": { "@switch": true } }
+ }
+ },
+
+ "admin/network/wireless": {
+ "title": "Wireless",
+ "order": 15,
+ "action": {
+ "type": "view",
+ "path": "network/wireless"
+ },
+ "depends": {
+ "uci": { "wireless": { "@wifi-device": true } }
+ }
+ },
+
+ "admin/network/remote_addr/*": {
+ "action": {
+ "type": "call",
+ "module": "luci.controller.admin.network",
+ "function": "remote_addr"
+ }
+ },
+
+ "admin/network/network": {
+ "title": "Interfaces",
+ "order": 10,
+ "action": {
+ "type": "view",
+ "path": "network/interfaces"
+ }
+ },
+
+ "admin/network/dhcp": {
+ "title": "DHCP and DNS",
+ "order": 30,
+ "action": {
+ "type": "view",
+ "path": "network/dhcp"
+ },
+ "depends": {
+ "uci": { "dhcp": true }
+ }
+ },
+
+ "admin/network/hosts": {
+ "title": "Hostnames",
+ "order": 40,
+ "action": {
+ "type": "view",
+ "path": "network/hosts"
+ },
+ "depends": {
+ "uci": { "dhcp": true }
+ }
+ },
+
+ "admin/network/routes": {
+ "title": "Static Routes",
+ "order": 50,
+ "action": {
+ "type": "view",
+ "path": "network/routes"
+ }
+ },
+
+ "admin/network/diagnostics": {
+ "title": "Diagnostics",
+ "order": 60,
+ "action": {
+ "type": "view",
+ "path": "network/diagnostics"
+ }
+ }
+}