summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-upnp/root
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-09-21 23:38:46 +0200
committerJo-Philipp Wich <jo@mein.io>2022-10-25 01:03:37 +0200
commit287775351bf39475f070ffc4ff4162f025ebfd81 (patch)
tree21a497bea4dc3058765e8c25f8a77f917c40e7d2 /applications/luci-app-upnp/root
parentcea2c3578efe7ab36219f06985d01a4771aeb070 (diff)
luci-app-upnp: convert rpcd backend script to ucode
Utilize the rpcd ucode plugin to reimplement the upnp backend ubus actions in ucode, simplifying the implementation and roughly halving the processing time for the `get_status` call. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'applications/luci-app-upnp/root')
-rwxr-xr-xapplications/luci-app-upnp/root/usr/libexec/rpcd/luci.upnp205
-rw-r--r--applications/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp139
2 files changed, 139 insertions, 205 deletions
diff --git a/applications/luci-app-upnp/root/usr/libexec/rpcd/luci.upnp b/applications/luci-app-upnp/root/usr/libexec/rpcd/luci.upnp
deleted file mode 100755
index 37768f972a..0000000000
--- a/applications/luci-app-upnp/root/usr/libexec/rpcd/luci.upnp
+++ /dev/null
@@ -1,205 +0,0 @@
-#!/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
-
- local nft = io.popen("nft --handle list chain inet fw4 upnp_prerouting")
- if nft then
- local num = 1
- local upnpf = lease_file and io.open(lease_file, "r")
- while true do
- local ln = nft:read("*l")
- if not ln then
- break
- elseif ln:match("iif ") then
- local proto, extport, intaddr, intport =
- ln:match('^\t\tiif ".-" @nh,72,8 (0x[0-9a-f]+) th dport ([0-9]+) dnat ip to ([0-9%.]+):([0-9]+)')
- local descr = ""
-
- if (proto == "0x6" or proto == "0x11") and extport and intaddr and intport then
- proto = (proto == "0x6") and "TCP" or "UDP"
- 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, 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 = tostring(num),
- proto = proto,
- extport = extport,
- intaddr = intaddr,
- host_hint = host_hint,
- intport = intport,
- descr = descr
- }
-
- num = num + 1
- end
- end
- end
-
- if upnpf then upnpf:close() end
- nft: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()
-
- 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) })
- sys.call("/etc/init.d/miniupnpd restart")
- 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
diff --git a/applications/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp b/applications/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
new file mode 100644
index 0000000000..9ee47f2968
--- /dev/null
+++ b/applications/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp
@@ -0,0 +1,139 @@
+// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
+// Licensed to the public under the Apache License 2.0.
+
+'use strict';
+
+import { access, open, popen } from 'fs';
+import { connect } from 'ubus';
+import { cursor } from 'uci';
+
+// Establish ubus connection persistently outside of the call handler scope to
+// prevent premature GC'ing. Can be moved into `get_status` callback once
+// https://github.com/jow-/ucode/commit/a58fe4709f661b5f28e26701ea8638efccf5aeb6
+// is merged.
+const ubus = connect();
+
+const methods = {
+ get_status: {
+ call: function(req) {
+ const uci = cursor();
+
+ const rules = [];
+ const leases = [];
+
+ const leasefile = open(uci.get('upnpd', 'config', 'upnp_lease_file'), 'r');
+
+ if (leasefile) {
+ for (let line = leasefile.read('line'); length(line); line = leasefile.read('line')) {
+ const record = split(line, ':', 6);
+
+ if (length(record) == 6) {
+ push(leases, {
+ proto: uc(record[0]),
+ extport: +record[1],
+ intaddr: arrtoip(iptoarr(record[2])),
+ intport: +record[3],
+ expiry: +record[4],
+ description: trim(record[5])
+ });
+ }
+ }
+
+ leasefile.close();
+ }
+
+ const ipt = popen('iptables --line-numbers -t nat -xnvL MINIUPNPD 2>/dev/null');
+
+ if (ipt) {
+ for (let line = ipt.read('line'); length(line); line = ipt.read('line')) {
+ let m = match(line, /^([0-9]+)\s+([a-z]+).+dpt:([0-9]+) to:(\S+):([0-9]+)/);
+
+ if (m) {
+ push(rules, {
+ num: m[1],
+ proto: uc(m[2]),
+ extport: +m[3],
+ intaddr: arrtoip(iptoarr(m[4])),
+ intport: +m[5],
+ descr: ''
+ });
+ }
+ }
+
+ ipt.close();
+ }
+
+ const nft = popen('nft --handle list chain inet fw4 upnp_prerouting 2>/dev/null');
+
+ if (nft) {
+ for (let line = nft.read('line'), num = 1; length(line); line = nft.read('line')) {
+ let m = match(line, /^\t\tiif ".+" @nh,72,8 (0x6|0x11) th dport ([0-9]+) dnat ip to ([0-9.]+):([0-9]+)/);
+
+ if (m) {
+ push(rules, {
+ num: `${num}`,
+ proto: (m[1] == '0x6') ? 'TCP' : 'UDP',
+ extport: +m[2],
+ intaddr: arrtoip(iptoarr(m[3])),
+ intport: +m[4],
+ descr: ''
+ });
+
+ num++;
+ }
+ }
+
+ nft.close();
+ }
+
+ return ubus.defer('luci-rpc', 'getHostHints', {}, function(rc, host_hints) {
+ for (let rule in rules) {
+ for (let lease in leases) {
+ if (lease.proto == rule.proto &&
+ lease.intaddr == rule.intaddr &&
+ lease.intport == rule.intport &&
+ lease.extport == rule.extport)
+ {
+ rule.descr = lease.description;
+ break;
+ }
+ }
+
+ for (let mac, hint in host_hints) {
+ if (rule.intaddr in hint.ipaddrs) {
+ rule.host_hint = hint.name;
+ break;
+ }
+ }
+ }
+
+ req.reply({ rules });
+ });
+ }
+ },
+
+ delete_rule: {
+ args: { token: 'token' },
+ call: function(req) {
+ const idx = +req.args?.token;
+
+ if (idx > 0) {
+ const uci = cursor();
+ const leasefile = uci.get('upnpd', 'config', 'upnp_lease_file');
+
+ if (access(leasefile)) {
+ system(['sed', '-i', '-e', `${idx}d`, leasefile]);
+ system(['/etc/init.d/miniupnpd', 'restart']);
+ }
+
+ return { result: 'OK' };
+ }
+
+ return { result: 'Bad request' };
+ }
+ }
+};
+
+return { 'luci.upnp': methods };
+
+