diff options
Diffstat (limited to 'applications/luci-app-upnp')
-rw-r--r-- | applications/luci-app-upnp/Makefile | 2 | ||||
-rwxr-xr-x | applications/luci-app-upnp/root/usr/libexec/rpcd/luci.upnp | 205 | ||||
-rw-r--r-- | applications/luci-app-upnp/root/usr/share/rpcd/ucode/luci.upnp | 139 |
3 files changed, 140 insertions, 206 deletions
diff --git a/applications/luci-app-upnp/Makefile b/applications/luci-app-upnp/Makefile index 324e3e2aa4..84a4d48576 100644 --- a/applications/luci-app-upnp/Makefile +++ b/applications/luci-app-upnp/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=Universal Plug & Play configuration module -LUCI_DEPENDS:=+miniupnpd +LUCI_DEPENDS:=+miniupnpd +rpcd-mod-ucode include ../../luci.mk 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 }; + + |