luci-proto-wireguard: rewrite rpcd handler in ucode
Rewrite the wireguard rpcd plugin in ucode to prevent an implicit dependency on the LuCI Lua runtime. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
parent
984a9a4d69
commit
008fa18878
3 changed files with 106 additions and 163 deletions
|
@ -7,7 +7,7 @@
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
LUCI_TITLE:=Support for WireGuard VPN
|
LUCI_TITLE:=Support for WireGuard VPN
|
||||||
LUCI_DEPENDS:=+wireguard-tools +libuci-lua
|
LUCI_DEPENDS:=+wireguard-tools +ucode
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|
||||||
include ../../luci.mk
|
include ../../luci.mk
|
||||||
|
|
|
@ -1,162 +0,0 @@
|
||||||
#!/usr/bin/env lua
|
|
||||||
|
|
||||||
local json = require "luci.jsonc"
|
|
||||||
local util = require "luci.util"
|
|
||||||
local sys = require "luci.sys"
|
|
||||||
local io = require "io"
|
|
||||||
local uci = require "uci"
|
|
||||||
local fs = require "nixio.fs"
|
|
||||||
|
|
||||||
local methods = {
|
|
||||||
generatePsk = {
|
|
||||||
call = function()
|
|
||||||
local psk = sys.exec("wg genpsk"):sub(1, -2)
|
|
||||||
|
|
||||||
return {psk = psk}
|
|
||||||
end
|
|
||||||
},
|
|
||||||
generateKeyPair = {
|
|
||||||
call = function()
|
|
||||||
local prv = sys.exec("wg genkey 2>/dev/null"):sub(1, -2)
|
|
||||||
local pub = sys.exec("echo %s | wg pubkey 2>/dev/null" % util.shellquote(prv)):sub(1, -2)
|
|
||||||
|
|
||||||
return {keys = {priv = prv, pub = pub}}
|
|
||||||
end
|
|
||||||
},
|
|
||||||
getPublicAndPrivateKeyFromPrivate = {
|
|
||||||
args = {privkey = "privkey"},
|
|
||||||
call = function(args)
|
|
||||||
local pubkey = sys.exec("echo %s | wg pubkey 2>/dev/null" % util.shellquote(args.privkey)):sub(1, -2)
|
|
||||||
|
|
||||||
return {keys = {priv = args.privkey, pub = pubkey}}
|
|
||||||
end
|
|
||||||
},
|
|
||||||
getWgInstances = {
|
|
||||||
call = function()
|
|
||||||
local data = {}
|
|
||||||
local last_device = ""
|
|
||||||
local qr_pubkey = {}
|
|
||||||
|
|
||||||
local wg_dump = io.popen("wg show all dump 2>/dev/null")
|
|
||||||
if wg_dump then
|
|
||||||
local line
|
|
||||||
for line in wg_dump:lines() do
|
|
||||||
local line = string.split(line, "\t")
|
|
||||||
if not (last_device == line[1]) then
|
|
||||||
last_device = line[1]
|
|
||||||
data[line[1]] = {
|
|
||||||
name = line[1],
|
|
||||||
public_key = line[3],
|
|
||||||
listen_port = line[4],
|
|
||||||
fwmark = line[5],
|
|
||||||
peers = {}
|
|
||||||
}
|
|
||||||
if not line[3] or line[3] == "" or line[3] == "(none)" then
|
|
||||||
qr_pubkey[line[1]] = ""
|
|
||||||
else
|
|
||||||
qr_pubkey[line[1]] = "PublicKey = " .. line[3]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local peer_name
|
|
||||||
local cur = uci.cursor()
|
|
||||||
|
|
||||||
cur:foreach(
|
|
||||||
"network",
|
|
||||||
"wireguard_" .. line[1],
|
|
||||||
function(s)
|
|
||||||
if s.public_key == line[2] then
|
|
||||||
peer_name = s.description
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
|
|
||||||
local peer = {
|
|
||||||
name = peer_name,
|
|
||||||
public_key = line[2],
|
|
||||||
endpoint = line[4],
|
|
||||||
allowed_ips = {},
|
|
||||||
latest_handshake = line[6],
|
|
||||||
transfer_rx = line[7],
|
|
||||||
transfer_tx = line[8],
|
|
||||||
persistent_keepalive = line[9]
|
|
||||||
}
|
|
||||||
|
|
||||||
if not (line[4] == "(none)") then
|
|
||||||
local ipkey, ipvalue
|
|
||||||
for ipkey, ipvalue in pairs(string.split(line[5], ",")) do
|
|
||||||
if #ipvalue > 0 then
|
|
||||||
table.insert(peer["allowed_ips"], ipvalue)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(data[line[1]].peers, peer)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return data
|
|
||||||
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,105 @@
|
||||||
|
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
|
||||||
|
// Licensed to the public under the Apache License 2.0.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { cursor } from 'uci';
|
||||||
|
import { popen } from 'fs';
|
||||||
|
|
||||||
|
|
||||||
|
function shellquote(s) {
|
||||||
|
return `'${replace(s ?? '', "'", "'\\''")}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function command(cmd) {
|
||||||
|
return trim(popen(cmd)?.read?.('all'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const methods = {
|
||||||
|
generatePsk: {
|
||||||
|
call: function() {
|
||||||
|
return { psk: command('wg genpsk 2>/dev/null') };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
generateKeyPair: {
|
||||||
|
call: function() {
|
||||||
|
const priv = command('wg genkey 2>/dev/null');
|
||||||
|
const pub = command(`echo ${shellquote(priv)} | wg pubkey 2>/dev/null`);
|
||||||
|
|
||||||
|
return { keys: { priv, pub } };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getPublicAndPrivateKeyFromPrivate: {
|
||||||
|
args: { privkey: "privkey" },
|
||||||
|
call: function(req) {
|
||||||
|
const priv = req.args?.privkey;
|
||||||
|
const pub = command(`echo ${shellquote(priv)} | wg pubkey 2>/dev/null`);
|
||||||
|
|
||||||
|
return { keys: { priv, pub } };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getWgInstances: {
|
||||||
|
call: function() {
|
||||||
|
const data = {};
|
||||||
|
let last_device;
|
||||||
|
let qr_pubkey = {};
|
||||||
|
|
||||||
|
const uci = cursor();
|
||||||
|
const wg_dump = popen("wg show all dump 2>/dev/null");
|
||||||
|
|
||||||
|
if (wg_dump) {
|
||||||
|
for (let line = wg_dump.read('line'); length(line); line = wg_dump.read('line')) {
|
||||||
|
const record = split(rtrim(line, '\n'), '\t');
|
||||||
|
|
||||||
|
if (last_device != record[0]) {
|
||||||
|
last_device = record[0];
|
||||||
|
data[last_device] = {
|
||||||
|
name: last_device,
|
||||||
|
public_key: record[2],
|
||||||
|
listen_port: record[3],
|
||||||
|
fwmark: record[4],
|
||||||
|
peers: []
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!length(record[2]) || record[2] == '(none)')
|
||||||
|
qr_pubkey[last_device] = '';
|
||||||
|
else
|
||||||
|
qr_pubkey[last_device] = `PublicKey = ${record[2]}`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let peer_name;
|
||||||
|
|
||||||
|
uci.foreach('network', `wireguard_${last_device}`, (s) => {
|
||||||
|
if (s.public_key == record[1])
|
||||||
|
peer_name = s.description;
|
||||||
|
});
|
||||||
|
|
||||||
|
const peer = {
|
||||||
|
name: peer_name,
|
||||||
|
public_key: record[1],
|
||||||
|
endpoint: record[3],
|
||||||
|
allowed_ips: [],
|
||||||
|
latest_handshake: record[5],
|
||||||
|
transfer_rx: record[6],
|
||||||
|
transfer_tx: record[7],
|
||||||
|
persistent_keepalive: record[8]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (record[3] != '(none)' && length(record[4]))
|
||||||
|
push(peer.allowed_ips, ...split(record[4], ','));
|
||||||
|
|
||||||
|
push(data[last_device].peers, peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { 'luci.wireguard': methods };
|
Loading…
Reference in a new issue