Merge pull request #3473 from Ansuel/upnp-con
luci-app-upnp: convert to client side implementation
This commit is contained in:
commit
30cba86db5
8 changed files with 446 additions and 248 deletions
|
@ -0,0 +1,72 @@
|
|||
'use strict';
|
||||
'require rpc';
|
||||
'require uci';
|
||||
|
||||
var callUpnpGetStatus, callUpnpDeleteRule, handleDelRule;
|
||||
|
||||
callUpnpGetStatus = rpc.declare({
|
||||
object: 'luci.upnp',
|
||||
method: 'get_status',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
callUpnpDeleteRule = rpc.declare({
|
||||
object: 'luci.upnp',
|
||||
method: 'delete_rule',
|
||||
params: [ 'token' ],
|
||||
expect: { result : "OK" },
|
||||
});
|
||||
|
||||
handleDelRule = function(num, ev) {
|
||||
L.dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
|
||||
ev.currentTarget.classList.add('spinning');
|
||||
ev.currentTarget.disabled = true;
|
||||
ev.currentTarget.blur();
|
||||
callUpnpDeleteRule(num);
|
||||
};
|
||||
|
||||
return L.Class.extend({
|
||||
title: _('Active UPnP Redirects'),
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
callUpnpGetStatus(),
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
|
||||
var table = E('div', { 'class': 'table', 'id': 'upnp_status_table' }, [
|
||||
E('div', { 'class': 'tr table-titles' }, [
|
||||
E('div', { 'class': 'th' }, _('Protocol')),
|
||||
E('div', { 'class': 'th' }, _('External Port')),
|
||||
E('div', { 'class': 'th' }, _('Client Address')),
|
||||
E('div', { 'class': 'th' }, _('Host')),
|
||||
E('div', { 'class': 'th' }, _('Client Port')),
|
||||
E('div', { 'class': 'th' }, _('Description')),
|
||||
E('div', { 'class': 'th cbi-section-actions' }, '')
|
||||
])
|
||||
]);
|
||||
|
||||
var rules = Array.isArray(data[0].rules) ? data[0].rules : [];
|
||||
|
||||
var rows = rules.map(function(rule) {
|
||||
return [
|
||||
rule.proto,
|
||||
rule.extport,
|
||||
rule.intaddr,
|
||||
rule.host_hint || _('Unknown'),
|
||||
rule.intport,
|
||||
rule.descr,
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-remove',
|
||||
'click': L.bind(handleDelRule, this, rule.num)
|
||||
}, [ _('Delete') ])
|
||||
];
|
||||
});
|
||||
|
||||
cbi_update_table(table, rows, E('em', _('There are no active redirects.')));
|
||||
|
||||
return table;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,201 @@
|
|||
'use strict';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
'require form';
|
||||
|
||||
var callInitAction, callUpnpGetStatus, callUpnpDeleteRule, handleDelRule;
|
||||
|
||||
callInitAction = rpc.declare({
|
||||
object: 'luci',
|
||||
method: 'setInitAction',
|
||||
params: [ 'name', 'action' ],
|
||||
expect: { result: false }
|
||||
});
|
||||
|
||||
callUpnpGetStatus = rpc.declare({
|
||||
object: 'luci.upnp',
|
||||
method: 'get_status',
|
||||
expect: { }
|
||||
});
|
||||
|
||||
callUpnpDeleteRule = rpc.declare({
|
||||
object: 'luci.upnp',
|
||||
method: 'delete_rule',
|
||||
params: [ 'token' ],
|
||||
expect: { result : "OK" },
|
||||
});
|
||||
|
||||
handleDelRule = function(num, ev) {
|
||||
L.dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5;
|
||||
ev.currentTarget.classList.add('spinning');
|
||||
ev.currentTarget.disabled = true;
|
||||
ev.currentTarget.blur();
|
||||
callUpnpDeleteRule(num);
|
||||
};
|
||||
|
||||
return L.view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
callUpnpGetStatus(),
|
||||
uci.load('upnpd')
|
||||
]);
|
||||
},
|
||||
|
||||
poll_status: function(nodes, data) {
|
||||
|
||||
var rules = Array.isArray(data[0].rules) ? data[0].rules : [];
|
||||
|
||||
var rows = rules.map(function(rule) {
|
||||
return [
|
||||
rule.proto,
|
||||
rule.extport,
|
||||
rule.intaddr,
|
||||
rule.host_hint || _('Unknown'),
|
||||
rule.intport,
|
||||
rule.descr,
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-remove',
|
||||
'click': L.bind(handleDelRule, this, rule.num)
|
||||
}, [ _('Delete') ])
|
||||
];
|
||||
});
|
||||
|
||||
cbi_update_table(nodes.querySelector('#upnp_status_table'), rows, E('em', _('There are no active redirects.')));
|
||||
|
||||
return;
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('upnpd', _('Universal Plug & Play'),
|
||||
_('UPnP allows clients in the local network to automatically configure the router.'));
|
||||
|
||||
s = m.section(form.GridSection, '_active_rules');
|
||||
|
||||
s.render = L.bind(function(view, section_id) {
|
||||
var table = E('div', { 'class': 'table cbi-section-table', 'id': 'upnp_status_table' }, [
|
||||
E('div', { 'class': 'tr table-titles' }, [
|
||||
E('div', { 'class': 'th' }, _('Protocol')),
|
||||
E('div', { 'class': 'th' }, _('External Port')),
|
||||
E('div', { 'class': 'th' }, _('Client Address')),
|
||||
E('div', { 'class': 'th' }, _('Host')),
|
||||
E('div', { 'class': 'th' }, _('Client Port')),
|
||||
E('div', { 'class': 'th' }, _('Description')),
|
||||
E('div', { 'class': 'th cbi-section-actions' }, '')
|
||||
])
|
||||
]);
|
||||
|
||||
var rules = Array.isArray(data[0].rules) ? data[0].rules : [];
|
||||
|
||||
var rows = rules.map(function(rule) {
|
||||
return [
|
||||
rule.proto,
|
||||
rule.extport,
|
||||
rule.intaddr,
|
||||
rule.host_hint || _('Unknown'),
|
||||
rule.intport,
|
||||
rule.descr,
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-remove',
|
||||
'click': L.bind(handleDelRule, this, rule.num)
|
||||
}, [ _('Delete') ])
|
||||
];
|
||||
});
|
||||
|
||||
cbi_update_table(table, rows, E('em', _('There are no active redirects.')));
|
||||
|
||||
return E('div', { 'class': 'cbi-section cbi-tblsection' }, [
|
||||
E('h3', _('Active UPnP Redirects')), table ]);
|
||||
}, o, this);
|
||||
|
||||
s = m.section(form.NamedSection, 'config', 'upnpd', _('MiniUPnP settings'));
|
||||
s.addremove = false;
|
||||
s.tab('general', _('General Settings'));
|
||||
s.tab('advanced', _('Advanced Settings'));
|
||||
|
||||
o = s.taboption('general', form.Flag, 'enabled', _('Start UPnP and NAT-PMP service'));
|
||||
o.rmempty = false;
|
||||
|
||||
s.taboption('general', form.Flag, 'enable_upnp', _('Enable UPnP functionality')).default = '1'
|
||||
s.taboption('general', form.Flag, 'enable_natpmp', _('Enable NAT-PMP functionality')).default = '1'
|
||||
|
||||
s.taboption('general', form.Flag, 'secure_mode', _('Enable secure mode'),
|
||||
_('Allow adding forwards only to requesting ip addresses')).default = '1'
|
||||
|
||||
s.taboption('general', form.Flag, 'igdv1', _('Enable IGDv1 mode'),
|
||||
_('Advertise as IGDv1 device instead of IGDv2')).default = '0'
|
||||
|
||||
s.taboption('general', form.Flag, 'log_output', _('Enable additional logging'),
|
||||
_('Puts extra debugging information into the system log'))
|
||||
|
||||
s.taboption('general', form.Value, 'download', _('Downlink'),
|
||||
_('Value in KByte/s, informational only')).rmempty = true
|
||||
|
||||
s.taboption('general', form.Value, 'upload', _('Uplink'),
|
||||
_('Value in KByte/s, informational only')).rmempty = true
|
||||
|
||||
o = s.taboption('general', form.Value, 'port', _('Port'))
|
||||
o.datatype = 'port'
|
||||
o.default = 5000
|
||||
|
||||
s.taboption('advanced', form.Flag, 'system_uptime', _('Report system instead of daemon uptime')).default = '1'
|
||||
|
||||
s.taboption('advanced', form.Value, 'uuid', _('Device UUID'))
|
||||
s.taboption('advanced', form.Value, 'serial_number', _('Announced serial number'))
|
||||
s.taboption('advanced', form.Value, 'model_number', _('Announced model number'))
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'notify_interval', _('Notify interval'))
|
||||
o.datatype = 'uinteger'
|
||||
o.placeholder = 30
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'clean_ruleset_threshold', _('Clean rules threshold'))
|
||||
o.datatype = 'uinteger'
|
||||
o.placeholder = 20
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'clean_ruleset_interval', _('Clean rules interval'))
|
||||
o.datatype = 'uinteger'
|
||||
o.placeholder = 600
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'presentation_url', _('Presentation URL'))
|
||||
o.placeholder = 'http://192.168.1.1/'
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'upnp_lease_file', _('UPnP lease file'))
|
||||
o.placeholder = '/var/run/miniupnpd.leases'
|
||||
|
||||
s = m.section(form.GridSection, 'perm_rule', _('MiniUPnP ACLs'),
|
||||
_('ACLs specify which external ports may be redirected to which internal addresses and ports'))
|
||||
|
||||
s.sortable = true
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
|
||||
s.option(form.Value, 'comment', _('Comment'))
|
||||
|
||||
o = s.option(form.Value, 'ext_ports', _('External ports'))
|
||||
o.datatype = 'portrange'
|
||||
o.placeholder = '0-65535'
|
||||
|
||||
o = s.option(form.Value, 'int_addr', _('Internal addresses'))
|
||||
o.datatype = 'ip4addr'
|
||||
o.placeholder = '0.0.0.0/0'
|
||||
|
||||
o = s.option(form.Value, 'int_ports', _('Internal ports'))
|
||||
o.datatype = 'portrange'
|
||||
o.placeholder = '0-65535'
|
||||
|
||||
o = s.option(form.ListValue, 'action', _('Action'))
|
||||
o.value('allow')
|
||||
o.value('deny')
|
||||
|
||||
return m.render().then(L.bind(function(m, nodes) {
|
||||
L.Poll.add(L.bind(function() {
|
||||
return Promise.all([
|
||||
callUpnpGetStatus()
|
||||
]).then(L.bind(this.poll_status, this, nodes));
|
||||
}, this), 5);
|
||||
return nodes;
|
||||
}, this, m));
|
||||
}
|
||||
});
|
|
@ -9,91 +9,5 @@ function index()
|
|||
return
|
||||
end
|
||||
|
||||
local page
|
||||
|
||||
page = entry({"admin", "services", "upnp"}, cbi("upnp/upnp"), _("UPnP"))
|
||||
page.dependent = true
|
||||
|
||||
entry({"admin", "services", "upnp", "status"}, call("act_status")).leaf = true
|
||||
entry({"admin", "services", "upnp", "delete"}, post("act_delete")).leaf = true
|
||||
end
|
||||
|
||||
function act_status()
|
||||
local uci = luci.model.uci.cursor()
|
||||
local lease_file = uci:get("upnpd", "config", "upnp_lease_file")
|
||||
|
||||
local ipv4_hints = luci.sys.net.ipv4_hints()
|
||||
|
||||
local ipt = io.popen("iptables --line-numbers -t nat -xnvL MINIUPNPD 2>/dev/null")
|
||||
if ipt then
|
||||
local upnpf = lease_file and io.open(lease_file, "r")
|
||||
local fwd = { }
|
||||
while true do
|
||||
local ln = ipt:read("*l")
|
||||
if not ln then
|
||||
break
|
||||
elseif ln:match("^%d+") then
|
||||
local num, proto, extport, intaddr, intport =
|
||||
ln:match("^(%d+).-([a-z]+).-dpt:(%d+) to:(%S-):(%d+)")
|
||||
local descr = ""
|
||||
|
||||
if num and proto and extport and intaddr and intport then
|
||||
num = tonumber(num)
|
||||
extport = tonumber(extport)
|
||||
intport = tonumber(intport)
|
||||
|
||||
if upnpf then
|
||||
local uln = upnpf:read("*l")
|
||||
if uln then descr = uln:match(string.format("^%s:%d:%s:%d:%%d*:(.*)$", proto:upper(), extport, intaddr, intport)) end
|
||||
if not descr then descr = "" end
|
||||
end
|
||||
|
||||
local host_hint, _, e
|
||||
|
||||
for _,e in pairs(ipv4_hints) do
|
||||
if e[1] == intaddr then
|
||||
host_hint = e[2]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
fwd[#fwd+1] = {
|
||||
num = num,
|
||||
proto = proto:upper(),
|
||||
extport = extport,
|
||||
intaddr = intaddr,
|
||||
host_hint = host_hint,
|
||||
intport = intport,
|
||||
descr = descr
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if upnpf then upnpf:close() end
|
||||
ipt:close()
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(fwd)
|
||||
end
|
||||
end
|
||||
|
||||
function act_delete(num)
|
||||
local idx = tonumber(num)
|
||||
local uci = luci.model.uci.cursor()
|
||||
|
||||
if idx and idx > 0 then
|
||||
luci.sys.call("iptables -t filter -D MINIUPNPD %d 2>/dev/null" % idx)
|
||||
luci.sys.call("iptables -t nat -D MINIUPNPD %d 2>/dev/null" % idx)
|
||||
|
||||
local lease_file = uci:get("upnpd", "config", "upnp_lease_file")
|
||||
if lease_file and nixio.fs.access(lease_file) then
|
||||
luci.sys.call("sed -i -e '%dd' %s" %{ idx, luci.util.shellquote(lease_file) })
|
||||
end
|
||||
|
||||
luci.http.status(200, "OK")
|
||||
return
|
||||
end
|
||||
|
||||
luci.http.status(400, "Bad request")
|
||||
entry({"admin", "services", "upnp"}, view("upnp/upnp"), _("UPnP"))
|
||||
end
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Copyright 2008-2011 Jo-Philipp Wich <jow@openwrt.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
m = Map("upnpd", luci.util.pcdata(translate("Universal Plug & Play")),
|
||||
translate("UPnP allows clients in the local network to automatically configure the router."))
|
||||
|
||||
m:section(SimpleSection).template = "upnp_status"
|
||||
|
||||
s = m:section(NamedSection, "config", "upnpd", translate("MiniUPnP settings"))
|
||||
s.addremove = false
|
||||
s:tab("general", translate("General Settings"))
|
||||
s:tab("advanced", translate("Advanced Settings"))
|
||||
|
||||
e = s:taboption("general", Flag, "enabled", translate("Start UPnP and NAT-PMP service"))
|
||||
e.rmempty = false
|
||||
|
||||
--function e.cfgvalue(self, section)
|
||||
-- return luci.sys.init.enabled("miniupnpd") and self.enabled or self.disabled
|
||||
--end
|
||||
|
||||
function e.write(self, section, value)
|
||||
if value == "1" then
|
||||
luci.sys.call("/etc/init.d/miniupnpd start >/dev/null")
|
||||
else
|
||||
luci.sys.call("/etc/init.d/miniupnpd stop >/dev/null")
|
||||
end
|
||||
|
||||
return Flag.write(self, section, value)
|
||||
end
|
||||
|
||||
s:taboption("general", Flag, "enable_upnp", translate("Enable UPnP functionality")).default = "1"
|
||||
s:taboption("general", Flag, "enable_natpmp", translate("Enable NAT-PMP functionality")).default = "1"
|
||||
|
||||
s:taboption("general", Flag, "secure_mode", translate("Enable secure mode"),
|
||||
translate("Allow adding forwards only to requesting ip addresses")).default = "1"
|
||||
|
||||
s:taboption("general", Flag, "igdv1", translate("Enable IGDv1 mode"),
|
||||
translate("Advertise as IGDv1 device instead of IGDv2")).default = "0"
|
||||
|
||||
s:taboption("general", Flag, "log_output", translate("Enable additional logging"),
|
||||
translate("Puts extra debugging information into the system log"))
|
||||
|
||||
s:taboption("general", Value, "download", translate("Downlink"),
|
||||
translate("Value in KByte/s, informational only")).rmempty = true
|
||||
|
||||
s:taboption("general", Value, "upload", translate("Uplink"),
|
||||
translate("Value in KByte/s, informational only")).rmempty = true
|
||||
|
||||
port = s:taboption("general", Value, "port", translate("Port"))
|
||||
port.datatype = "port"
|
||||
port.default = 5000
|
||||
|
||||
|
||||
s:taboption("advanced", Flag, "system_uptime", translate("Report system instead of daemon uptime")).default = "1"
|
||||
|
||||
s:taboption("advanced", Value, "uuid", translate("Device UUID"))
|
||||
s:taboption("advanced", Value, "serial_number", translate("Announced serial number"))
|
||||
s:taboption("advanced", Value, "model_number", translate("Announced model number"))
|
||||
|
||||
ni = s:taboption("advanced", Value, "notify_interval", translate("Notify interval"))
|
||||
ni.datatype = "uinteger"
|
||||
ni.placeholder = 30
|
||||
|
||||
ct = s:taboption("advanced", Value, "clean_ruleset_threshold", translate("Clean rules threshold"))
|
||||
ct.datatype = "uinteger"
|
||||
ct.placeholder = 20
|
||||
|
||||
ci = s:taboption("advanced", Value, "clean_ruleset_interval", translate("Clean rules interval"))
|
||||
ci.datatype = "uinteger"
|
||||
ci.placeholder = 600
|
||||
|
||||
pu = s:taboption("advanced", Value, "presentation_url", translate("Presentation URL"))
|
||||
pu.placeholder = "http://192.168.1.1/"
|
||||
|
||||
lf = s:taboption("advanced", Value, "upnp_lease_file", translate("UPnP lease file"))
|
||||
lf.placeholder = "/var/run/miniupnpd.leases"
|
||||
|
||||
|
||||
s2 = m:section(TypedSection, "perm_rule", translate("MiniUPnP ACLs"),
|
||||
translate("ACLs specify which external ports may be redirected to which internal addresses and ports"))
|
||||
|
||||
s2.template = "cbi/tblsection"
|
||||
s2.sortable = true
|
||||
s2.anonymous = true
|
||||
s2.addremove = true
|
||||
|
||||
s2:option(Value, "comment", translate("Comment"))
|
||||
|
||||
ep = s2:option(Value, "ext_ports", translate("External ports"))
|
||||
ep.datatype = "portrange"
|
||||
ep.placeholder = "0-65535"
|
||||
|
||||
ia = s2:option(Value, "int_addr", translate("Internal addresses"))
|
||||
ia.datatype = "ip4addr"
|
||||
ia.placeholder = "0.0.0.0/0"
|
||||
|
||||
ip = s2:option(Value, "int_ports", translate("Internal ports"))
|
||||
ip.datatype = "portrange"
|
||||
ip.placeholder = "0-65535"
|
||||
|
||||
ac = s2:option(ListValue, "action", translate("Action"))
|
||||
ac:value("allow")
|
||||
ac:value("deny")
|
||||
|
||||
return m
|
|
@ -1 +0,0 @@
|
|||
<%+upnp_status%>
|
|
@ -1,54 +0,0 @@
|
|||
<script type="text/javascript">//<![CDATA[
|
||||
function upnp_delete_fwd(idx) {
|
||||
(new XHR()).post('<%=url('admin/services/upnp/delete')%>/' + idx, { token: '<%=token%>' },
|
||||
function(x)
|
||||
{
|
||||
var tb = document.getElementById('upnp_status_table');
|
||||
if (tb && (idx + 1 < tb.childNodes.length))
|
||||
tb.removeChild(tb.childNodes[idx + 1]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
XHR.poll(-1, '<%=url('admin/services/upnp/status')%>', null,
|
||||
function(x, st)
|
||||
{
|
||||
var tb = document.getElementById('upnp_status_table');
|
||||
if (st && tb)
|
||||
{
|
||||
var rows = [];
|
||||
|
||||
for (var i = 0; i < st.length; i++)
|
||||
rows.push([
|
||||
st[i].proto,
|
||||
st[i].extport,
|
||||
st[i].intaddr,
|
||||
st[i].host_hint || "<%:Unknown%>",
|
||||
st[i].intport,
|
||||
st[i].descr,
|
||||
E('<div><input class="cbi-button cbi-button-remove" type="button" value="<%:Delete%>" onclick="upnp_delete_fwd(%d)" /></div>'.format(st[i].num))
|
||||
]);
|
||||
|
||||
cbi_update_table(tb, rows, '<em><%:There are no active redirects.%></em>');
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]></script>
|
||||
|
||||
<div class="cbi-section">
|
||||
<h3><%:Active UPnP Redirects%></h3>
|
||||
<div class="table" id="upnp_status_table">
|
||||
<div class="tr table-titles">
|
||||
<div class="th"><%:Protocol%></div>
|
||||
<div class="th"><%:External Port%></div>
|
||||
<div class="th"><%:Client Address%></div>
|
||||
<div class="th"><%:Host%></div>
|
||||
<div class="th"><%:Client Port%></div>
|
||||
<div class="th"><%:Description%></div>
|
||||
<div class="th cbi-section-actions"> </div>
|
||||
</div>
|
||||
<div class="tr placeholder">
|
||||
<div class="td"><em><%:Collecting data...%></em></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
155
applications/luci-app-upnp/root/usr/libexec/rpcd/luci.upnp
Executable file
155
applications/luci-app-upnp/root/usr/libexec/rpcd/luci.upnp
Executable file
|
@ -0,0 +1,155 @@
|
|||
#!/usr/bin/env lua
|
||||
|
||||
local json = require "luci.jsonc"
|
||||
local UCI = require "luci.model.uci"
|
||||
local fs = require "nixio.fs"
|
||||
local sys = require "luci.sys"
|
||||
|
||||
local methods = {
|
||||
get_status = {
|
||||
call = function()
|
||||
local uci = UCI.cursor()
|
||||
local lease_file = uci:get("upnpd", "config", "upnp_lease_file")
|
||||
|
||||
local ipv4_hints = sys.net.ipv4_hints()
|
||||
local rule = { }
|
||||
|
||||
local ipt = io.popen("iptables --line-numbers -t nat -xnvL MINIUPNPD 2>/dev/null")
|
||||
if ipt then
|
||||
local upnpf = lease_file and io.open(lease_file, "r")
|
||||
while true do
|
||||
local ln = ipt:read("*l")
|
||||
if not ln then
|
||||
break
|
||||
elseif ln:match("^%d+") then
|
||||
local num, proto, extport, intaddr, intport =
|
||||
ln:match("^(%d+).-([a-z]+).-dpt:(%d+) to:(%S-):(%d+)")
|
||||
local descr = ""
|
||||
|
||||
if num and proto and extport and intaddr and intport then
|
||||
extport = tonumber(extport)
|
||||
intport = tonumber(intport)
|
||||
|
||||
if upnpf then
|
||||
local uln = upnpf:read("*l")
|
||||
if uln then descr = uln:match(string.format("^%s:%d:%s:%d:%%d*:(.*)$", proto:upper(), extport, intaddr, intport)) end
|
||||
if not descr then descr = "" end
|
||||
end
|
||||
|
||||
local host_hint, _, e
|
||||
|
||||
for _,e in pairs(ipv4_hints) do
|
||||
if e[1] == intaddr then
|
||||
host_hint = e[2]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
rule[#rule+1] = {
|
||||
num = num,
|
||||
proto = proto:upper(),
|
||||
extport = extport,
|
||||
intaddr = intaddr,
|
||||
host_hint = host_hint,
|
||||
intport = intport,
|
||||
descr = descr
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if upnpf then upnpf:close() end
|
||||
ipt:close()
|
||||
end
|
||||
|
||||
return { rules = rule }
|
||||
end
|
||||
},
|
||||
delete_rule = {
|
||||
args = { token = "token" },
|
||||
call = function(args)
|
||||
local util = require "luci.util"
|
||||
local idx = args and tonumber(args.token)
|
||||
local res = {}
|
||||
|
||||
if idx and idx > 0 then
|
||||
local uci = UCI.cursor()
|
||||
|
||||
sys.call("iptables -t filter -D MINIUPNPD %d 2>/dev/null" % idx)
|
||||
sys.call("iptables -t nat -D MINIUPNPD %d 2>/dev/null" % idx)
|
||||
|
||||
local lease_file = uci:get("upnpd", "config", "upnp_lease_file")
|
||||
if lease_file and fs.access(lease_file) then
|
||||
sys.call("sed -i -e '%dd' %s" %{ idx, util.shellquote(lease_file) })
|
||||
end
|
||||
|
||||
uci.unload()
|
||||
|
||||
return { result = "OK" }
|
||||
end
|
||||
|
||||
return { result = "Bad request" }
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
local function parseInput()
|
||||
local parse = json.new()
|
||||
local done, err
|
||||
|
||||
while true do
|
||||
local chunk = io.read(4096)
|
||||
if not chunk then
|
||||
break
|
||||
elseif not done and not err then
|
||||
done, err = parse:parse(chunk)
|
||||
end
|
||||
end
|
||||
|
||||
if not done then
|
||||
print(json.stringify({ error = err or "Incomplete input" }))
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
return parse:get()
|
||||
end
|
||||
|
||||
local function validateArgs(func, uargs)
|
||||
local method = methods[func]
|
||||
if not method then
|
||||
print(json.stringify({ error = "Method not found" }))
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
if type(uargs) ~= "table" then
|
||||
print(json.stringify({ error = "Invalid arguments" }))
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
uargs.ubus_rpc_session = nil
|
||||
|
||||
local k, v
|
||||
local margs = method.args or {}
|
||||
for k, v in pairs(uargs) do
|
||||
if margs[k] == nil or
|
||||
(v ~= nil and type(v) ~= type(margs[k]))
|
||||
then
|
||||
print(json.stringify({ error = "Invalid arguments" }))
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
return method
|
||||
end
|
||||
|
||||
if arg[1] == "list" then
|
||||
local _, method, rv = nil, nil, {}
|
||||
for _, method in pairs(methods) do rv[_] = method.args or {} end
|
||||
print((json.stringify(rv):gsub(":%[%]", ":{}")))
|
||||
elseif arg[1] == "call" then
|
||||
local args = parseInput()
|
||||
local method = validateArgs(arg[2], args)
|
||||
local result, code = method.call(args)
|
||||
print((json.stringify(result):gsub("^%[%]$", "{}")))
|
||||
os.exit(code or 0)
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"luci-app-ddns": {
|
||||
"description": "Grant access to upnp procedures",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.upnp": [ "get_status" ],
|
||||
"luci": [ "setInitAction" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.upnp": [ "delete_rule" ]
|
||||
},
|
||||
"uci": [ "upnpd" ]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue