diff --git a/package/lean/luci-app-kcptun/Makefile b/package/lean/luci-app-kcptun/Makefile new file mode 100644 index 000000000..fcdc65d47 --- /dev/null +++ b/package/lean/luci-app-kcptun/Makefile @@ -0,0 +1,35 @@ +# +# Copyright 2016-2017 Xingwang Liao +# Licensed to the public under the Apache License 2.0. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-kcptun +PKG_VERSION:=1.4.3 +PKG_RELEASE:=1 + +PKG_LICENSE:=Apache-2.0 +PKG_MAINTAINER:=Xingwang Liao + +LUCI_TITLE:=LuCI support for Kcptun +LUCI_DEPENDS:=+jshn +wget +luci-lib-jsonc +LUCI_PKGARCH:=all + +define Package/$(PKG_NAME)/conffiles +/etc/config/kcptun +endef + +include $(TOPDIR)/feeds/luci/luci.mk + +define Package/$(PKG_NAME)/postinst +#!/bin/sh +if [ -z "$${IPKG_INSTROOT}" ]; then + ( . /etc/uci-defaults/40_luci-kcptun ) && rm -f /etc/uci-defaults/40_luci-kcptun +fi +chmod 755 $${IPKG_INSTROOT}/etc/init.d/kcptun >/dev/null 2>&1 +$${IPKG_INSTROOT}/etc/init.d/kcptun enable >/dev/null 2>&1 +exit 0 +endef + +# call BuildPackage - OpenWrt buildroot signature diff --git a/package/lean/luci-app-kcptun/luasrc/controller/kcptun.lua b/package/lean/luci-app-kcptun/luasrc/controller/kcptun.lua new file mode 100644 index 000000000..736830a1f --- /dev/null +++ b/package/lean/luci-app-kcptun/luasrc/controller/kcptun.lua @@ -0,0 +1,120 @@ +-- Copyright 2016-2017 Xingwang Liao +-- Licensed to the public under the Apache License 2.0. + +module("luci.controller.kcptun", package.seeall) + +local http = require "luci.http" +local kcp = require "luci.model.kcptun" + +function index() + if not nixio.fs.access("/etc/config/kcptun") then + return + end + + entry({"admin", "services", "kcptun"}, + firstchild(), _("Kcptun Client")).dependent = false + + entry({"admin", "services", "kcptun", "settings"}, + cbi("kcptun/settings"), _("Settings"), 1) + + entry({"admin", "services", "kcptun", "servers"}, + arcombine(cbi("kcptun/servers"), cbi("kcptun/servers-detail")), + _("Server Manage"), 2).leaf = true + + entry({"admin", "services", "kcptun", "log"}, + template("kcptun/log_view"), _("Log"), 3) + + entry({"admin", "services", "kcptun", "status"}, call("action_status")) + + entry({"admin", "services", "kcptun", "check"}, call("action_check")).leaf = true + + entry({"admin", "services", "kcptun", "update"}, call("action_update")).leaf = true + + entry({"admin", "services", "kcptun", "log", "data"}, call("action_log_data")) + + entry({"admin", "services", "kcptun", "log", "clear"}, call("action_log_clear")).leaf = true +end + +local function http_write_json(content) + http.prepare_content("application/json") + http.write_json(content or { code = 1 }) +end + +function action_status() + local client_file = kcp.get_config_option("client_file") + + http_write_json({ + client = kcp.is_running(client_file) + }) +end + +function action_check(type) + local json = nil + if type == "kcptun" then + json = kcp.check_kcptun(http.formvalue("arch")) + elseif type == "luci" then + json = kcp.check_luci() + else + http.status(500, "Bad address") + return + end + + http_write_json(json) +end + +function action_update(type) + local json = nil + if type == "kcptun" then + local task = http.formvalue("task") + if task == "extract" then + json = kcp.extract_kcptun(http.formvalue("file"), http.formvalue("subfix")) + elseif task == "move" then + json = kcp.move_kcptun(http.formvalue("file")) + else + json = kcp.download_kcptun(http.formvalue("url")) + end + elseif type == "luci" then + json = kcp.update_luci(http.formvalue("url"), http.formvalue("save")) + else + http.status(500, "Bad address") + return + end + + http_write_json(json) +end + +function action_log_data() + local util = require "luci.util" + + local log_data = { } + + local enable_logging = kcp.get_config_option("enable_logging", "0") == "1" + + if enable_logging then + local client_log_file = kcp.get_current_log_file("client") + log_data.client = util.trim( + util.exec("tail -n 50 %s 2>/dev/null | sed 'x;1!H;$!d;x'" % client_log_file)) + end + + log_data.syslog = util.trim( + util.exec("logread | grep kcptun | tail -n 50 | sed 'x;1!H;$!d;x'")) + + http_write_json(log_data) +end + +function action_log_clear(type) + if type and type ~= "" then + local log_file = kcp.get_current_log_file(type) + + local fs = require "nixio.fs" + + if fs.access(log_file) then + fs.writefile(log_file, "") + else + http.status(404, "Not found") + return + end + end + + http_write_json({ code = 0 }) +end diff --git a/package/lean/luci-app-kcptun/luasrc/model/cbi/kcptun/servers-detail.lua b/package/lean/luci-app-kcptun/luasrc/model/cbi/kcptun/servers-detail.lua new file mode 100644 index 000000000..a9214b4e1 --- /dev/null +++ b/package/lean/luci-app-kcptun/luasrc/model/cbi/kcptun/servers-detail.lua @@ -0,0 +1,182 @@ +-- Copyright 2016-2017 Xingwang Liao +-- Licensed to the public under the Apache License 2.0. + +local dsp = require "luci.dispatcher" + +local m, s, o +local sid = arg[1] + +local encrypt_methods = { + "aes", + "aes-128", + "aes-192", + "salsa20", + "blowfish", + "twofish", + "cast5", + "3des", + "tea", + "xtea", + "xor", + "none", +} + +local modes = { + "normal", + "fast", + "fast2", + "fast3", + "manual", +} + +m = Map("kcptun", "%s - %s" % { translate("Kcptun"), translate("Edit Server") }) +m.redirect = dsp.build_url("admin/services/kcptun/servers") + +if m.uci:get("kcptun", sid) ~= "servers" then + luci.http.redirect(m.redirect) + return +end + +s = m:section(NamedSection, sid, "servers") +s.anonymous = true +s.addremove = false + +o = s:option(Value, "alias", "%s (%s)" % { translate("Alias"), translate("optional") }) + +o = s:option(Value, "server_addr", translate("Server")) +o.datatype = "host" +o.rmempty = false + +o = s:option(Value, "server_port", translate("Server Port")) +o.datatype = "port" +o.placeholder = "29900" + +o = s:option(Value, "listen_addr", "%s (%s)" % { translate("Local Listen Host"), translate("optional") }, + translate("Local listen host.")) +o.datatype = "host" +o.placeholder = "0.0.0.0" + +o = s:option(Value, "listen_port", translate("Local Port"), translate("Local Listen Port.")) +o.datatype = "port" +o.placeholder = "12948" + +o = s:option(Value, "key", "%s (%s)" % { translate("Key"), translate("optional") }, + translate("Pre-shared secret for client and server.")) +o.password = true +o.placeholder = "it's a secret" + +o = s:option(Value, "crypt", translate("crypt"), translate("Encrypt Method")) +for _, v in ipairs(encrypt_methods) do + o:value(v, v:upper()) +end +o.default = "aes" + +o = s:option(ListValue, "mode", translate("mode"), translate("Embedded Mode")) +for _, v in ipairs(modes) do + o:value(v, v:upper()) +end +o.default = "fast" + +o = s:option(Flag, "nodelay", translate("nodelay"), translate("Enable nodelay Mode.")) +o:depends("mode", "manual") + +o = s:option(Value, "interval", translate("interval")) +o:depends("mode", "manual") +o.datatype = "uinteger" +o.placeholder = "50" + +o = s:option(ListValue, "resend", translate("resend")) +o:depends("mode", "manual") +o:value("0", translate("Off")) +o:value("1", translate("On")) +o:value("2", translate("2nd ACK")) + +o = s:option(Flag, "nc", translate("nc")) +o:depends("mode", "manual") + +o = s:option(Value, "mtu", "%s (%s)" % { translate("mtu"), translate("optional") }, + translate("Maximum transmission unit of UDP packets.")) +o.datatype = "range(64,9200)" +o.placeholder = "1350" + +o = s:option(Value, "sndwnd", "%s (%s)" % { translate("sndwnd"), translate("optional") }, + translate("Send Window Size(num of packets).")) +o.datatype = "min(1)" +o.default = "128" +o.placeholder = "128" + +o = s:option(Value, "rcvwnd", "%s (%s)" % { translate("rcvwnd"), translate("optional") }, + translate("Receive Window Size(num of packets).")) +o.datatype = "min(1)" +o.default = "512" +o.placeholder = "512" + +o = s:option(Value, "datashard", "%s (%s)" % { translate("datashard"), translate("optional") }, + translate("Reed-solomon Erasure Coding - datashard.")) +o.datatype = "uinteger" +o.placeholder = "10" + +o = s:option(Value, "parityshard", "%s (%s)" % { translate("parityshard"), translate("optional") }, + translate("Reed-solomon Erasure Coding - parityshard.")) +o.datatype = "uinteger" +o.placeholder = "3" + +o = s:option(Value, "dscp", "%s (%s)" % { translate("dscp"), translate("optional") }, translate("DSCP(6bit)")) +o.datatype = "uinteger" +o.placeholder = "0" + +o = s:option(Flag, "nocomp", translate("nocomp"), translate("Disable Compression?")) +o.enabled = "true" +o.disabled = "false" +o.rmempty = false + +o = s:option(Flag, "acknodelay", translate("acknodelay")) +o.enabled = "true" +o.disabled = "false" + +o = s:option(Value, "conn", "%s (%s)" %{ translate("conn"), translate("optional") }, + translate("Number of UDP connections to server.")) +o.datatype = "min(1)" +o.placeholder = "1" + +o = s:option(Value, "autoexpire", "%s (%s)" % { translate("autoexpire"), translate("optional") }, + translate("Auto expiration time(in seconds) for a single UDP connection, 0 to disable.")) +o.datatype = "uinteger" +o.placeholder = "0" + +o = s:option(Value, "scavengettl", "%s (%s)" % { translate("scavengettl"), translate("optional") }, + translate("How long an expired connection can live(in sec), -1 to disable.")) +o.datatype = "min(-1)" +o.placeholder = "600" + +o = s:option(Value, "sockbuf", "%s (%s)" % { translate("sockbuf"), translate("optional") }, + translate("Send/secv buffer size of udp sockets, default unit is MB.")) +o.datatype = "uinteger" +o.placeholder = "4" +o.cfgvalue = function(...) + local value = Value.cfgvalue(...) + + if value then + return tonumber(value) / 1024 / 1024 + end +end +o.write = function(self, section, value) + local number = tonumber(value) + if number then + Value.write(self, section, number * 1024 * 1024) + else + Value.remove(self, section) + end +end + +o = s:option(Value, "keepalive", "%s (%s)" % { translate("keepalive"), translate("optional") }, + translate("NAT keepalive interval to prevent your router from removing port mapping, default unit is seconds.")) +o.datatype = "uinteger" +o.placeholder = "10" + +o = s:option(Value, "snmpperiod", "%s (%s)" % { translate("snmpperiod"), + translate("optional") }, translate("SNMP collect period, in seconds")) +o.datatype = "min(1)" +o.placeholder = "60" + +return m diff --git a/package/lean/luci-app-kcptun/luasrc/model/cbi/kcptun/servers.lua b/package/lean/luci-app-kcptun/luasrc/model/cbi/kcptun/servers.lua new file mode 100644 index 000000000..9a44cac1e --- /dev/null +++ b/package/lean/luci-app-kcptun/luasrc/model/cbi/kcptun/servers.lua @@ -0,0 +1,71 @@ +-- Copyright 2016-2017 Xingwang Liao +-- Licensed to the public under the Apache License 2.0. + +local dsp = require "luci.dispatcher" +local http = require "luci.http" + +local m, s, o + +local function get_ip_string(ip) + if ip and ip:find(":") then + return "[%s]" % ip + else + return ip or "" + end +end + +m = Map("kcptun", "%s - %s" % { translate("Kcptun"), translate("Server List") }) + +s = m:section(TypedSection, "servers") +s.anonymous = true +s.addremove = true +s.sortable = true +s.template = "cbi/tblsection" +s.extedit = dsp.build_url("admin/services/kcptun/servers/%s") +function s.create(...) + local sid = TypedSection.create(...) + if sid then + m.uci:save("kcptun") + http.redirect(s.extedit % sid) + return + end +end + +o = s:option(DummyValue, "alias", translate("Alias")) +function o.cfgvalue(self, section) + return Value.cfgvalue(self, section) or translate("None") +end + +o = s:option(DummyValue, "_server_address", translate("Server Address")) +function o.cfgvalue(self, section) + local server = m.uci:get("kcptun", section, "server_addr") or "?" + local server_port = m.uci:get("kcptun", section, "server_port") or "29900" + return "%s:%s" % { get_ip_string(server), server_port } +end + +o = s:option(DummyValue, "_listen_addres", translate("Listen Address")) +function o.cfgvalue(self, section) + local local_host = m.uci.get("kcptun", section, "listen_addr") or "0.0.0.0" + local local_port = m.uci.get("kcptun", section, "listen_port") or "12984" + return "%s:%s" % { get_ip_string(local_host), local_port } +end + +o = s:option(DummyValue, "crypt", translate("Encrypt Method")) +function o.cfgvalue(...) + local v = Value.cfgvalue(...) + return v and v:upper() or "?" +end + +o = s:option(DummyValue, "mode", translate("Embedded Mode")) +function o.cfgvalue(...) + local v = Value.cfgvalue(...) + return v and v:upper() or "?" +end + +o = s:option(DummyValue, "nocomp", translate("Disable Compression")) +function o.cfgvalue(...) + local v = Value.cfgvalue(...) + return v == "true" and translate("True") or translate("False") +end + +return m diff --git a/package/lean/luci-app-kcptun/luasrc/model/cbi/kcptun/settings.lua b/package/lean/luci-app-kcptun/luasrc/model/cbi/kcptun/settings.lua new file mode 100644 index 000000000..6d63eb5de --- /dev/null +++ b/package/lean/luci-app-kcptun/luasrc/model/cbi/kcptun/settings.lua @@ -0,0 +1,112 @@ +-- Copyright 2016-2017 Xingwang Liao +-- Licensed to the public under the Apache License 2.0. + +local uci = require "luci.model.uci".cursor() +local util = require "luci.util" +local sys = require "luci.sys" +local fs = require "nixio.fs" + +local m, s, o +local server_table = { } + +local function get_ip_string(ip) + if ip and ip:find(":") then + return "[%s]" % ip + else + return ip or "" + end +end + +uci:foreach("kcptun", "servers", function(s) + if s.alias then + server_table[s[".name"]] = s.alias + elseif s.server_addr and s.server_port then + server_table[s[".name"]] = "%s:%s" % { get_ip_string(s.server_addr), s.server_port } + end +end) + +m = Map("kcptun", "%s - %s" % { translate("Kcptun"), translate("Settings") }) +m:append(Template("kcptun/status")) + +s = m:section(NamedSection, "general", "general", translate("General Settings")) +s.anonymous = true +s.addremove = false + +o = s:option(ListValue, "server", translate("Server")) +o:value("", translate("Disable")) +for k, v in pairs(server_table) do + o:value(k, v) +end + +o = s:option(Value, "client_file", translate("Client File")) +o.rmempty = false + +o = s:option(ListValue, "daemon_user", translate("Run Daemon as User")) +for u in util.execi("cat /etc/passwd | cut -d ':' -f1") do + o:value(u) +end + +o = s:option(Flag, "enable_logging", translate("Enable Logging")) +o.rmempty = false + +o = s:option(Value, "log_folder", translate("Log Folder")) +o.datatype = "directory" +o.placeholder = "/var/log/kcptun" +o:depends("enable_logging", "1") +o.formvalue = function(...) + local v = (Value.formvalue(...) or ""):trim() + if v ~= "" then + v = string.gsub(v, "\\", "/") + if v:sub(1, 1) ~= "/" then + v = "/" .. v + end + + while v:sub(-1) == "/" do + v = v:sub(1, -2) + end + end + + return v +end +o.validate = function(self, value, section) + if value and not fs.stat(value) then + local res, code, msg = fs.mkdir(value) + if not res then + return nil, msg + end + end + return Value.validate(self, value, section) +end + +o = s:option(ListValue, "arch", translate("CPU Architecture"), + translate("The ARCH for checking updates." .. + " Note: Make sure OpenWrt/LEDE 'MIPS FPU Emulator' is enabled on MIPS/MIPSLE devices.")) +o:value("", translate("Auto")) +o:value("i386", "x86") +o:value("x86_64", "x86_64") +o:value("armv5", "ARMv5") +o:value("armv6", "ARMv6") +o:value("armv7", "ARMv7+") +o:value("ar71xx", "MIPS") +o:value("ramips", "MIPSLE") + +o = s:option(Button, "_check_kcptun", translate("Check Kcptun Update"), + translate("Make sure that the 'Client File' dictionary has enough space.")) +o.template = "kcptun/button" +o.inputstyle = "apply" +o.placeholder = translate("Check Kcptun Update") +o.btnclick = "check_update('kcptun', this);" +o.id = "_kcptun-check_kcptun" + +o = s:option(Flag, "save_config", translate("Save Config File"), + translate("Save config file while upgrade LuCI.")) + +o = s:option(Button, "_check_luci", translate("Check LuCI Update"), + translate("You may need to reload current page after update LuCI. Note that translation will not be updated.")) +o.template = "kcptun/button" +o.inputstyle = "apply" +o.placeholder = translate("Check LuCI Update") +o.btnclick = "check_update('luci', this);" +o.id = "_kcptun-check_luci" + +return m diff --git a/package/lean/luci-app-kcptun/luasrc/model/kcptun.lua b/package/lean/luci-app-kcptun/luasrc/model/kcptun.lua new file mode 100644 index 000000000..4484ff606 --- /dev/null +++ b/package/lean/luci-app-kcptun/luasrc/model/kcptun.lua @@ -0,0 +1,486 @@ +-- Copyright 2016-2017 Xingwang Liao +-- Licensed to the public under the Apache License 2.0. + +local fs = require "nixio.fs" +local sys = require "luci.sys" +local uci = require "luci.model.uci".cursor() +local util = require "luci.util" +local i18n = require "luci.i18n" + +module("luci.model.kcptun", package.seeall) + +local kcptun_api = "https://api.github.com/repos/xtaci/kcptun/releases/latest" +local luci_api = "https://api.github.com/repos/kuoruan/luci-app-kcptun/releases/latest" + +local wget = "/usr/bin/wget" +local wget_args = { "--no-check-certificate", "--quiet", "--timeout=10", "--tries=2" } +local command_timeout = 40 + +local function _unpack(t, i) + i = i or 1 + if t[i] ~= nil then + return t[i], _unpack(t, i + 1) + end +end + +local function exec(cmd, args, writer, timeout) + local os = require "os" + local nixio = require "nixio" + + local fdi, fdo = nixio.pipe() + local pid = nixio.fork() + + if pid > 0 then + fdo:close() + + if writer or timeout then + local starttime = os.time() + while true do + if timeout and os.difftime(os.time(), starttime) >= timeout then + nixio.kill(pid, nixio.const.SIGTERM) + return 1 + end + + if writer then + local buffer = fdi:read(2048) + if buffer and #buffer > 0 then + writer(buffer) + end + end + + local wpid, stat, code = nixio.waitpid(pid, "nohang") + + if wpid and stat == "exited" then + return code + end + + if not writer and timeout then + nixio.nanosleep(1) + end + end + else + local wpid, stat, code = nixio.waitpid(pid) + return wpid and stat == "exited" and code + end + elseif pid == 0 then + nixio.dup(fdo, nixio.stdout) + fdi:close() + fdo:close() + nixio.exece(cmd, args, nil) + nixio.stdout:close() + os.exit(1) + end +end + +local function compare_versions(ver1, comp, ver2) + local table = table + + local av1 = util.split(ver1, "[%.%-]", nil, true) + local av2 = util.split(ver2, "[%.%-]", nil, true) + + local max = table.getn(av1) + local n2 = table.getn(av2) + if (max < n2) then + max = n2 + end + + for i = 1, max, 1 do + local s1 = av1[i] or "" + local s2 = av2[i] or "" + + if comp == "~=" and (s1 ~= s2) then return true end + if (comp == "<" or comp == "<=") and (s1 < s2) then return true end + if (comp == ">" or comp == ">=") and (s1 > s2) then return true end + if (s1 ~= s2) then return false end + end + + return not (comp == "<" or comp == ">") +end + +local function auto_get_arch() + local arch = nixio.uname().machine or "" + + if arch == "mips" then + if fs.access("/usr/lib/os-release") then + arch = sys.exec("grep 'LEDE_BOARD' /usr/lib/os-release | grep -oE 'ramips|ar71xx'") + elseif fs.access("/etc/openwrt_release") then + arch = sys.exec("grep 'DISTRIB_TARGET' /etc/openwrt_release | grep -oE 'ramips|ar71xx'") + end + end + + return util.trim(arch) +end + +local function get_file_info(arch) + local file_tree = "" + local sub_version = "" + + if arch == "x86_64" then + file_tree = "amd64" + elseif arch == "ramips" then + file_tree = "mipsle" + elseif arch == "ar71xx" then + file_tree = "mips" + elseif arch:match("^i[%d]86$") then + file_tree = "386" + elseif arch:match("^armv[5-8]") then + file_tree = "arm" + sub_version = arch:match("[5-8]") + end + + return file_tree, sub_version +end + +local function get_api_json(url) + local jsonc = require "luci.jsonc" + + local output = { } + exec(wget, { "-O-", url, _unpack(wget_args) }, + function(chunk) output[#output + 1] = chunk end) + + local json_content = util.trim(table.concat(output)) + + if json_content == "" then + return { } + end + + return jsonc.parse(json_content) or { } +end + +function get_config_option(option, default) + return uci:get("kcptun", "general", option) or default +end + +function get_current_log_file(type) + local log_folder = get_config_option("log_folder", "/var/log/kcptun") + return "%s/%s.%s.log" % { log_folder, type, "general" } +end + +function is_running(client) + if client and client ~= "" then + local file_name = client:match(".*/([^/]+)$") or "" + if file_name ~= "" then + return sys.call("pidof %s >/dev/null" % file_name) == 0 + end + end + + return false +end + +function get_kcptun_version(file) + if file and file ~= "" then + if not fs.access(file, "rwx", "rx", "rx") then + fs.chmod(file, 755) + end + + local info = util.trim(sys.exec("%s -v 2>/dev/null" % file)) + + if info ~= "" then + local tb = util.split(info, "%s+", nil, true) + return tb[1] == "kcptun" and tb[3] or "" + end + end + + return "" +end + +function get_luci_version() + local ipkg = require "luci.model.ipkg" + + local package_name = "luci-app-kcptun" + local package_info = ipkg.info(package_name) or {} + + if next(package_info) ~= nil then + return package_info[package_name]["Version"] + end + return "" +end + +function check_kcptun(arch) + if not arch or arch == "" then + arch = auto_get_arch() + end + + local file_tree, sub_version = get_file_info(arch) + + if file_tree == "" then + return { + code = 1, + error = i18n.translate("Can't determine ARCH, or ARCH not supported. Please select manually.") + } + end + + local json = get_api_json(kcptun_api) + + if json.tag_name == nil then + return { + code = 1, + error = i18n.translate("Get remote version info failed.") + } + end + + local remote_version = json.tag_name:match("[^v]+") + + local client_file = get_config_option("client_file") + + local needs_update = compare_versions(get_kcptun_version(client_file), "<", remote_version) + local html_url, download_url + + if needs_update then + html_url = json.html_url + for _, v in ipairs(json.assets) do + if v.name and v.name:match("linux%-" .. file_tree) then + download_url = v.browser_download_url + break + end + end + end + + if needs_update and not download_url then + return { + code = 1, + version = remote_version, + html_url = html_url, + error = i18n.translate("New version found, but failed to get new version download url.") + } + end + + return { + code = 0, + update = needs_update, + version = remote_version, + url = { + html = html_url, + download = download_url + }, + type = file_tree .. sub_version + } +end + +function check_luci() + local json = get_api_json(luci_api) + + if json.tag_name == nil then + return { + code = 1, + error = i18n.translate("Get remote version info failed.") + } + end + + local remote_version = json.tag_name:match("[^v]+") + + local needs_update = compare_versions(get_luci_version(), "<", remote_version) + local html_url, luci_url + local i18n_urls = { } + + if needs_update then + html_url = json.html_url + for _, v in ipairs(json.assets) do + local n = v.name + if n then + if n:match("luci%-app%-kcptun") then + luci_url = v.browser_download_url + elseif n:match("luci%-i18n%-kcptun") then + i18n_urls[#i18n_urls + 1] = v.browser_download_url + end + end + end + end + + if needs_update and not luci_url then + return { + code = 1, + version = remote_version, + html_url = html_url, + error = i18n.translate("New version found, but failed to get new version download url.") + } + end + + return { + code = 0, + update = needs_update, + version = remote_version, + url = { + html = html_url, + luci = luci_url, + i18n = i18n_urls + } + } +end + +function download_kcptun(url) + if not url or url == "" then + return { + code = 1, + error = i18n.translate("Download url is required.") + } + end + + sys.call("/bin/rm -f /tmp/kcptun_download.*") + + local tmp_file = util.trim(util.exec("mktemp -u -t kcptun_download.XXXXXX")) + + local result = exec(wget, { + "-O", tmp_file, url, _unpack(wget_args) }, nil, command_timeout) == 0 + + if not result then + exec("/bin/rm", { "-f", tmp_file }) + return { + code = 1, + error = i18n.translatef("File download failed or timed out: %s", url) + } + end + + return { + code = 0, + file = tmp_file + } +end + +function extract_kcptun(file, subfix) + if not file or file == "" or not fs.access(file) then + return { + code = 1, + error = i18n.translate("File path required.") + } + end + + sys.call("/bin/rm -rf /tmp/kcptun_extract.*") + local tmp_dir = util.trim(util.exec("mktemp -d -t kcptun_extract.XXXXXX")) + + local output = { } + exec("/bin/tar", { "-C", tmp_dir, "-zxvf", file }, + function(chunk) output[#output + 1] = chunk end) + + local files = util.split(table.concat(output)) + + exec("/bin/rm", { "-f", file }) + + local new_file = nil + for _, f in pairs(files) do + if f:match("client_linux_%s" % subfix) then + new_file = tmp_dir .. "/" .. util.trim(f) + break + end + end + + if not new_file then + for _, f in pairs(files) do + if f:match("client_") then + new_file = tmp_dir .. "/" .. util.trim(f) + break + end + end + end + + if not new_file then + exec("/bin/rm", { "-rf", tmp_dir }) + return { + code = 1, + error = i18n.translatef("Can't find client in file: %s", file) + } + end + + return { + code = 0, + file = new_file + } +end + +function move_kcptun(file) + if not file or file == "" or not fs.access(file) then + sys.call("/bin/rm -rf /tmp/kcptun_extract.*") + return { + code = 1, + error = i18n.translate("Client file is required.") + } + end + + local version = get_kcptun_version(file) + if version == "" then + sys.call("/bin/rm -rf /tmp/kcptun_extract.*") + return { + code = 1, + error = i18n.translate("The client file is not suitable for current device. Please reselect ARCH.") + } + end + + local client_file = get_config_option("client_file", "/var/kcptun_client") + local client_file_bak + + if fs.access(client_file) then + client_file_bak = client_file .. ".bak" + exec("/bin/mv", { "-f", client_file, client_file_bak }) + end + + local result = exec("/bin/mv", { "-f", file, client_file }, nil, command_timeout) == 0 + + if not result or not fs.access(client_file) then + sys.call("/bin/rm -rf /tmp/kcptun_extract.*") + if client_file_bak then + exec("/bin/mv", { "-f", client_file_bak, client_file }) + end + return { + code = 1, + error = i18n.translatef("Can't move new file to path: %s", client_file) + } + end + + exec("/bin/chmod", { "755", client_file }) + + if client_file_bak then + exec("/bin/rm", { "-f", client_file_bak }) + end + + sys.call("/bin/rm -rf /tmp/kcptun_extract.*") + + uci:set("kcptun", "general", "client_file", client_file) + uci:commit("kcptun") + + return { code = 0 } +end + +function update_luci(url, save) + if not url or url == "" then + return { + code = 1, + error = i18n.translate("Download url is required.") + } + end + + sys.call("/bin/rm -f /tmp/luci_kcptun.*.ipk") + + local tmp_file = util.trim(util.exec("mktemp -u -t luci_kcptun.XXXXXX")) .. ".ipk" + + local result = exec("/usr/bin/wget", { + "-O", tmp_file, url, _unpack(wget_args) }, nil, command_timeout) == 0 + + if not result then + exec("/bin/rm", { "-f", tmp_file }) + return { + code = 1, + error = i18n.translatef("File download failed or timed out: %s", url) + } + end + + local opkg_args = { "--force-downgrade", "--force-reinstall" } + + if save ~= "true" then + opkg_args[#opkg_args + 1] = "--force-maintainer" + end + + result = exec("/bin/opkg", { "install", tmp_file, _unpack(opkg_args) }) == 0 + + if not result then + exec("/bin/rm", { "-f", tmp_file }) + return { + code = 1, + error = i18n.translate("Package update failed.") + } + end + + exec("/bin/rm", { "-f", tmp_file }) + exec("/bin/rm", { "-rf", "/tmp/luci-indexcache", "/tmp/luci-modulecache" }) + + return { code = 0 } +end diff --git a/package/lean/luci-app-kcptun/luasrc/view/kcptun/button.htm b/package/lean/luci-app-kcptun/luasrc/view/kcptun/button.htm new file mode 100644 index 000000000..14ffb09a1 --- /dev/null +++ b/package/lean/luci-app-kcptun/luasrc/view/kcptun/button.htm @@ -0,0 +1,19 @@ +<%# + Copyright 2017 Hsing-wang Liao + Licensed to the public under the Apache License 2.0. +-%> + +<%+cbi/valueheader%> + <% if self:cfgvalue(section) ~= false then %> + " type="button"<%= + attr("name", cbid) .. + attr("id", self.id or cbid) .. + attr("value", self.inputtitle or self.title) .. + ifattr(self.btnclick, "onclick", self.btnclick) .. + ifattr(self.placeholder, "placeholder") + %> /> + + <% else %> + - + <% end %> +<%+cbi/valuefooter%> diff --git a/package/lean/luci-app-kcptun/luasrc/view/kcptun/log_view.htm b/package/lean/luci-app-kcptun/luasrc/view/kcptun/log_view.htm new file mode 100644 index 000000000..b16c45a12 --- /dev/null +++ b/package/lean/luci-app-kcptun/luasrc/view/kcptun/log_view.htm @@ -0,0 +1,74 @@ +<%# + Copyright 2016-2017 Xingwang Liao + Licensed to the public under the Apache License 2.0. +-%> + +<% css = [[ + + #log_text { + padding: 10px; + text-align: left; + } + #log_text pre { + word-break: break-all; + margin: 0; + } + .description { + background-color: #33ccff; + } + +]] +%> + +<%+header%> + +
+

<%:Kcptun%> - <%:Log Data%>

+
+
+ +
+
+
<%:Loading%><%:Collecting data...%>
+
<%:Refresh every 5 seconds.%>
+
+
+
+ + + + +<%+footer%> diff --git a/package/lean/luci-app-kcptun/luasrc/view/kcptun/status.htm b/package/lean/luci-app-kcptun/luasrc/view/kcptun/status.htm new file mode 100644 index 000000000..d799635fd --- /dev/null +++ b/package/lean/luci-app-kcptun/luasrc/view/kcptun/status.htm @@ -0,0 +1,258 @@ +<%# + Copyright 2016-2017 Xingwang Liao + Licensed to the public under the Apache License 2.0. +-%> + +<% +local kcp = require "luci.model.kcptun" +local dsp = require "luci.dispatcher" + +local client_file = kcp.get_config_option("client_file") +local client_version = kcp.get_kcptun_version(client_file) +local luci_version = kcp.get_luci_version() +-%> + +
+ <%:Running Status%> + + + + + + + <% if luci_version ~= "" then -%> + + <% end -%> +
<%:Client Version%> + <% if client_version == "" then -%> + <%:Invalid Client File.%> + <% else -%> + <%=pcdata(client_version)%> + <%- end %> +
<%:Client Status%><%:Collecting data...%>
<%:LuCI Version%><%=pcdata(luci_version)%>
+
+ + diff --git a/package/lean/luci-app-kcptun/po/templates/kcptun.pot b/package/lean/luci-app-kcptun/po/templates/kcptun.pot new file mode 100644 index 000000000..3f07759eb --- /dev/null +++ b/package/lean/luci-app-kcptun/po/templates/kcptun.pot @@ -0,0 +1,346 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +msgid "2nd ACK" +msgstr "" + +msgid "Alias" +msgstr "" + +msgid "Auto" +msgstr "" + +msgid "" +"Auto expiration time(in seconds) for a single UDP connection, 0 to disable." +msgstr "" + +msgid "CPU Architecture" +msgstr "" + +msgid "Can't determine ARCH, or ARCH not supported. Please select manually." +msgstr "" + +msgid "Can't find client in file: %s" +msgstr "" + +msgid "Can't move new file to path: %s" +msgstr "" + +msgid "Check Kcptun Update" +msgstr "" + +msgid "Check LuCI Update" +msgstr "" + +msgid "Clear Log File" +msgstr "" + +msgid "Click to Update" +msgstr "" + +msgid "Client File" +msgstr "" + +msgid "Client Status" +msgstr "" + +msgid "Client Version" +msgstr "" + +msgid "Client file is required." +msgstr "" + +msgid "Collecting data..." +msgstr "" + +msgid "DSCP(6bit)" +msgstr "" + +msgid "Disable" +msgstr "" + +msgid "Disable Compression" +msgstr "" + +msgid "Disable Compression?" +msgstr "" + +msgid "Download url is required." +msgstr "" + +msgid "Downloading..." +msgstr "" + +msgid "Edit Server" +msgstr "" + +msgid "Embedded Mode" +msgstr "" + +msgid "Enable Logging" +msgstr "" + +msgid "Enable nodelay Mode." +msgstr "" + +msgid "Encrypt Method" +msgstr "" + +msgid "Error get log data." +msgstr "" + +msgid "Extracting..." +msgstr "" + +msgid "False" +msgstr "" + +msgid "File download failed or timed out: %s" +msgstr "" + +msgid "File path required." +msgstr "" + +msgid "General Settings" +msgstr "" + +msgid "Get remote version info failed." +msgstr "" + +msgid "How long an expired connection can live(in sec), -1 to disable." +msgstr "" + +msgid "Invalid Client File." +msgstr "" + +msgid "Kcptun" +msgstr "" + +msgid "Kcptun Client" +msgstr "" + +msgid "Key" +msgstr "" + +msgid "Last 50 lines of log file:" +msgstr "" + +msgid "Last 50 lines of syslog:" +msgstr "" + +msgid "Listen Address" +msgstr "" + +msgid "Loading" +msgstr "" + +msgid "Local Listen Host" +msgstr "" + +msgid "Local Listen Port." +msgstr "" + +msgid "Local Port" +msgstr "" + +msgid "Local listen host." +msgstr "" + +msgid "Log" +msgstr "" + +msgid "Log Data" +msgstr "" + +msgid "Log Folder" +msgstr "" + +msgid "LuCI Version" +msgstr "" + +msgid "Make sure that the 'Client File' dictionary has enough space." +msgstr "" + +msgid "Maximum transmission unit of UDP packets." +msgstr "" + +msgid "Moving..." +msgstr "" + +msgid "" +"NAT keepalive interval to prevent your router from removing port mapping, " +"default unit is seconds." +msgstr "" + +msgid "New version found, but failed to get new version download url." +msgstr "" + +msgid "No Update Found" +msgstr "" + +msgid "No log data." +msgstr "" + +msgid "None" +msgstr "" + +msgid "Not Running" +msgstr "" + +msgid "Number of UDP connections to server." +msgstr "" + +msgid "Off" +msgstr "" + +msgid "On" +msgstr "" + +msgid "Package update failed." +msgstr "" + +msgid "Pre-shared secret for client and server." +msgstr "" + +msgid "Processing..." +msgstr "" + +msgid "Receive Window Size(num of packets)." +msgstr "" + +msgid "Reed-solomon Erasure Coding - datashard." +msgstr "" + +msgid "Reed-solomon Erasure Coding - parityshard." +msgstr "" + +msgid "Refresh every 5 seconds." +msgstr "" + +msgid "Run Daemon as User" +msgstr "" + +msgid "Running" +msgstr "" + +msgid "Running Status" +msgstr "" + +msgid "SNMP collect period, in seconds" +msgstr "" + +msgid "Save Config File" +msgstr "" + +msgid "Save config file while upgrade LuCI." +msgstr "" + +msgid "Send Window Size(num of packets)." +msgstr "" + +msgid "Send/secv buffer size of udp sockets, default unit is MB." +msgstr "" + +msgid "Server" +msgstr "" + +msgid "Server Address" +msgstr "" + +msgid "Server List" +msgstr "" + +msgid "Server Manage" +msgstr "" + +msgid "Server Port" +msgstr "" + +msgid "Settings" +msgstr "" + +msgid "" +"The ARCH for checking updates. Note: Make sure OpenWrt/LEDE 'MIPS FPU " +"Emulator' is enabled on MIPS/MIPSLE devices." +msgstr "" + +msgid "" +"The client file is not suitable for current device. Please reselect ARCH." +msgstr "" + +msgid "True" +msgstr "" + +msgid "Update Success." +msgstr "" + +msgid "Update in progress. Are you sure to close window?" +msgstr "" + +msgid "" +"You may need to reload current page after update LuCI. Note that translation " +"will not be updated." +msgstr "" + +msgid "acknodelay" +msgstr "" + +msgid "autoexpire" +msgstr "" + +msgid "conn" +msgstr "" + +msgid "crypt" +msgstr "" + +msgid "datashard" +msgstr "" + +msgid "dscp" +msgstr "" + +msgid "interval" +msgstr "" + +msgid "keepalive" +msgstr "" + +msgid "mode" +msgstr "" + +msgid "mtu" +msgstr "" + +msgid "nc" +msgstr "" + +msgid "nocomp" +msgstr "" + +msgid "nodelay" +msgstr "" + +msgid "optional" +msgstr "" + +msgid "parityshard" +msgstr "" + +msgid "rcvwnd" +msgstr "" + +msgid "resend" +msgstr "" + +msgid "scavengettl" +msgstr "" + +msgid "sndwnd" +msgstr "" + +msgid "snmpperiod" +msgstr "" + +msgid "sockbuf" +msgstr "" diff --git a/package/lean/luci-app-kcptun/po/zh-cn/kcptun.po b/package/lean/luci-app-kcptun/po/zh-cn/kcptun.po new file mode 100644 index 000000000..6859c977e --- /dev/null +++ b/package/lean/luci-app-kcptun/po/zh-cn/kcptun.po @@ -0,0 +1,348 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8\n" + +msgid "2nd ACK" +msgstr "2次 ACK 跨越重传" + +msgid "Alias" +msgstr "别名" + +msgid "Auto" +msgstr "自动" + +msgid "" +"Auto expiration time(in seconds) for a single UDP connection, 0 to disable." +msgstr "单个 UDP 连接的自动过期时间(秒),设置 0 来禁用" + +msgid "CPU Architecture" +msgstr "CPU 架构" + +msgid "Can't determine ARCH, or ARCH not supported. Please select manually." +msgstr "无法自动确定 ARCH,或者不支持该 ARCH,请手动重新选择。" + +msgid "Can't find client in file: %s" +msgstr "无法在文件中找到客户端:%s" + +msgid "Can't move new file to path: %s" +msgstr "无法移动新文件到:%s" + +msgid "Check Kcptun Update" +msgstr "检查 Kcptun 更新" + +msgid "Check LuCI Update" +msgstr "检查 LuCI 更新" + +msgid "Clear Log File" +msgstr "清理日志文件" + +msgid "Click to Update" +msgstr "点击更新" + +msgid "Client File" +msgstr "客户端文件" + +msgid "Client Status" +msgstr "客户端状态" + +msgid "Client Version" +msgstr "客户端版本" + +msgid "Client file is required." +msgstr "请指定客户端文件。" + +msgid "Collecting data..." +msgstr "正在收集数据..." + +msgid "DSCP(6bit)" +msgstr "DSCP(6bit)" + +msgid "Disable" +msgstr "禁用" + +msgid "Disable Compression" +msgstr "禁用压缩" + +msgid "Disable Compression?" +msgstr "是否禁用压缩?" + +msgid "Download url is required." +msgstr "请指定下载链接。" + +msgid "Downloading..." +msgstr "正在下载..." + +msgid "Edit Server" +msgstr "编辑服务端" + +msgid "Embedded Mode" +msgstr "内置模式" + +msgid "Enable Logging" +msgstr "启用日志记录" + +msgid "Enable nodelay Mode." +msgstr "启用 nodelay 模式" + +msgid "Encrypt Method" +msgstr "加密方式" + +msgid "Error get log data." +msgstr "获取日志数据失败。" + +msgid "Extracting..." +msgstr "正在解压..." + +msgid "False" +msgstr "否" + +msgid "File download failed or timed out: %s" +msgstr "文件下载失败或超时:%s" + +msgid "File path required." +msgstr "请指定文件路径。" + +msgid "General Settings" +msgstr "基本设置" + +msgid "Get remote version info failed." +msgstr "获取远程版本信息失败。" + +msgid "How long an expired connection can live(in sec), -1 to disable." +msgstr "过期连接保留多长时间(秒),设置 -1 来禁用" + +msgid "Invalid Client File." +msgstr "客户端文件配置有误" + +msgid "Kcptun" +msgstr "" + +msgid "Kcptun Client" +msgstr "Kcptun 客户端" + +msgid "Key" +msgstr "密码" + +msgid "Last 50 lines of log file:" +msgstr "日志文件的最新 50 行:" + +msgid "Last 50 lines of syslog:" +msgstr "系统日志的最新 50 行:" + +msgid "Listen Address" +msgstr "监听地址" + +msgid "Loading" +msgstr "正在加载..." + +msgid "Local Listen Host" +msgstr "本地监听地址" + +msgid "Local Listen Port." +msgstr "本地监听端口" + +msgid "Local Port" +msgstr "本地端口" + +msgid "Local listen host." +msgstr "本地监听主机" + +msgid "Log" +msgstr "日志" + +msgid "Log Data" +msgstr "日志数据" + +msgid "Log Folder" +msgstr "日志文件夹" + +msgid "LuCI Version" +msgstr "LuCI 版本" + +msgid "Make sure that the 'Client File' dictionary has enough space." +msgstr "请确保“客户端文件”所在的文件夹具有足够的空间。" + +msgid "Maximum transmission unit of UDP packets." +msgstr "UDP数据包的最大传输单元" + +msgid "Moving..." +msgstr "正在移动..." + +msgid "" +"NAT keepalive interval to prevent your router from removing port mapping, " +"default unit is seconds." +msgstr "NAT Keepalive 包间隔时间(秒), 防止路由器删除端口映射" + +msgid "New version found, but failed to get new version download url." +msgstr "发现新版本,但是获取下载地址失败。" + +msgid "No Update Found" +msgstr "未发现更新" + +msgid "No log data." +msgstr "无日志数据。" + +msgid "None" +msgstr "无" + +msgid "Not Running" +msgstr "未运行" + +msgid "Number of UDP connections to server." +msgstr "到服务端的UDP连接数量" + +msgid "Off" +msgstr "关闭" + +msgid "On" +msgstr "开启" + +msgid "Package update failed." +msgstr "软件包升级失败。" + +msgid "Pre-shared secret for client and server." +msgstr "客户端和服务端的通信密码" + +msgid "Processing..." +msgstr "正在操作..." + +msgid "Receive Window Size(num of packets)." +msgstr "接收窗口大小 (数据包数量)" + +msgid "Reed-solomon Erasure Coding - datashard." +msgstr "前向纠错 - datashard" + +msgid "Reed-solomon Erasure Coding - parityshard." +msgstr "前向纠错 - parityshard" + +msgid "Refresh every 5 seconds." +msgstr "每 5 秒刷新。" + +msgid "Run Daemon as User" +msgstr "以该用户启动" + +msgid "Running" +msgstr "运行中" + +msgid "Running Status" +msgstr "运行状态" + +msgid "SNMP collect period, in seconds" +msgstr "SNMP采集周期(秒)" + +msgid "Save Config File" +msgstr "保留配置文件" + +msgid "Save config file while upgrade LuCI." +msgstr "在更新 LuCI 时保留配置文件" + +msgid "Send Window Size(num of packets)." +msgstr "发送窗口大小 (数据包数量)" + +msgid "Send/secv buffer size of udp sockets, default unit is MB." +msgstr "发送/接收UDP数据包的缓冲区大小(MB)" + +msgid "Server" +msgstr "服务端地址" + +msgid "Server Address" +msgstr "服务端地址" + +msgid "Server List" +msgstr "服务端列表" + +msgid "Server Manage" +msgstr "服务端管理" + +msgid "Server Port" +msgstr "服务端端口" + +msgid "Settings" +msgstr "设置" + +msgid "" +"The ARCH for checking updates. Note: Make sure OpenWrt/LEDE 'MIPS FPU " +"Emulator' is enabled on MIPS/MIPSLE devices." +msgstr "" +"用于检查更新的 ARCH。注意: 在 MIPS/MIPSLE 设备上,请确保 OpenWrt/LEDE 的 " +"'MIPS FPU Emulator' 已经启用。" + +msgid "" +"The client file is not suitable for current device. Please reselect ARCH." +msgstr "客户端文件不适用于当前设备,请重新选择 ARCH。" + +msgid "True" +msgstr "是" + +msgid "Update Success." +msgstr "更新成功!" + +msgid "Update in progress. Are you sure to close window?" +msgstr "正在更新,确定关闭窗口?" + +msgid "" +"You may need to reload current page after update LuCI. Note that translation " +"will not be updated." +msgstr "更新 LuCI 之后你可能需要手动刷新当前页面。注意:翻译不会被更新" + +msgid "acknodelay" +msgstr "" + +msgid "autoexpire" +msgstr "" + +msgid "conn" +msgstr "" + +msgid "crypt" +msgstr "" + +msgid "datashard" +msgstr "" + +msgid "dscp" +msgstr "" + +msgid "interval" +msgstr "" + +msgid "keepalive" +msgstr "" + +msgid "mode" +msgstr "" + +msgid "mtu" +msgstr "" + +msgid "nc" +msgstr "" + +msgid "nocomp" +msgstr "" + +msgid "nodelay" +msgstr "" + +msgid "optional" +msgstr "可选" + +msgid "parityshard" +msgstr "" + +msgid "rcvwnd" +msgstr "" + +msgid "resend" +msgstr "" + +msgid "scavengettl" +msgstr "" + +msgid "sndwnd" +msgstr "" + +msgid "snmpperiod" +msgstr "" + +msgid "sockbuf" +msgstr "" diff --git a/package/lean/luci-app-kcptun/root/etc/config/kcptun b/package/lean/luci-app-kcptun/root/etc/config/kcptun new file mode 100644 index 000000000..8c9564ce3 --- /dev/null +++ b/package/lean/luci-app-kcptun/root/etc/config/kcptun @@ -0,0 +1,15 @@ + +config general 'general' + option server '' + option client_file '/var/kcptun_client' + option daemon_user 'root' + option enable_logging '1' + +config servers 'default' + option server_addr '' + option server_port '29900' + option listen_addr '0.0.0.0' + option listen_port '12948' + option crypt 'aes' + option mode 'fast' + option nocomp 'false' diff --git a/package/lean/luci-app-kcptun/root/etc/init.d/kcptun b/package/lean/luci-app-kcptun/root/etc/init.d/kcptun new file mode 100755 index 000000000..c8bc3dc34 --- /dev/null +++ b/package/lean/luci-app-kcptun/root/etc/init.d/kcptun @@ -0,0 +1,244 @@ +#!/bin/sh /etc/rc.common +# +# Copyright 2016-2017 Xingwang Liao +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +START=99 +USE_PROCD=1 + +KCPTUN=kcptun +CONFIG_FOLDER=/var/etc/$KCPTUN + +if [ -r /usr/share/libubox/jshn.sh ]; then + . /usr/share/libubox/jshn.sh +elif [ -r /lib/functions/jshn.sh ]; then + . /lib/functions/jshn.sh +else + logger -p daemon.err -t "$KCPTUN" \ + "Package required: jshn." + exit 1 +fi + +_log() { + local level="$1" + local msg="$2" + + logger -p "daemon.${level}" -t "$KCPTUN" "$msg" +} + +gen_client_config_file() { + local config_file="$1" + + json_init + json_add_string "remoteaddr" "${server_addr}:${server_port}" + json_add_string "localaddr" "${listen_addr}:${listen_port}" + + add_configs() { + local type="$1"; shift + local k v + + for k in "$@"; do + v="$(eval echo "\$$k")" + + if [ -n "$v" ]; then + if [ "$type" = "string" ]; then + json_add_string "$k" "$v" + elif [ "$type" = "int" ]; then + json_add_int "$k" "$v" + elif [ "$type" = "boolean" ]; then + if [ "$v" = "true" ]; then + json_add_boolean "$k" "1" + else + json_add_boolean "$k" "0" + fi + fi + fi + done + } + + add_configs "string" key crypt mode + add_configs "int" conn autoexpire mtu sndwnd rcvwnd datashard parityshard dscp \ + nodelay interval resend nc sockbuf keepalive scavengettl snmpperiod + add_configs "boolean" nocomp acknodelay + + if [ -n "$log_file" ]; then + json_add_string "log" "$log_file" + fi + + json_close_object + + json_dump -i >"$config_file" +} + +setup_iptables_chain() { + if ! ( iptables -nL "$KCPTUN" >/dev/null 2>&1 ); then + iptables -N "$KCPTUN" 2>/dev/null + fi + + iptables -C OUTPUT -j "$KCPTUN" 2>/dev/null || \ + iptables -A OUTPUT -j "$KCPTUN" 2>/dev/null + iptables -C INPUT -j "$KCPTUN" 2>/dev/null || \ + iptables -A INPUT -j "$KCPTUN" 2>/dev/null +} + +clear_iptables_chain() { + iptables -F "$KCPTUN" >/dev/null 2>&1 +} + +add_iptables_rule() { + local port="$1" + local type="$2" + + if [ "$type" = "client" ]; then + ( iptables -C "$KCPTUN" -p tcp --dport "$port" -m comment \ + --comment "$type" -j ACCEPT 2>/dev/null ) && return 0 + + iptables -A "$KCPTUN" -p tcp --dport "$port" -m comment \ + --comment "$type" -j ACCEPT 2>/dev/null + elif [ "$type" = "server" ]; then + ( iptables -C "$KCPTUN" -p udp --dport "$port" -m comment \ + --comment "$type" -j ACCEPT 2>/dev/null ) && return 0 + + iptables -A "$KCPTUN" -p udp --dport "$port" -m comment \ + --comment "$type" -j ACCEPT 2>/dev/null + fi +} + +validate_config_section() { + uci_validate_section "$KCPTUN" general "$1" \ + 'server:uciname' \ + 'client_file:string' \ + 'daemon_user:string:root' \ + 'enable_logging:bool:0' \ + 'log_folder:directory:/var/log/kcptun' +} + +validate_server_section() { + uci_validate_section "$KCPTUN" servers "$1" \ + 'server_addr:host' \ + 'server_port:port:29900' \ + 'listen_addr:host:0.0.0.0' \ + 'listen_port:port:12948' \ + 'key:string' \ + 'crypt:string:aes' \ + 'mode:or("normal","fast","fast2","fast3","manual"):fast' \ + 'conn:min(1)' \ + 'autoexpire:uinteger' \ + 'scavengettl:min(-1)' \ + 'mtu:range(64,9200)' \ + 'sndwnd:min(1)' \ + 'rcvwnd:min(1)' \ + 'datashard:uinteger' \ + 'parityshard:uinteger' \ + 'dscp:uinteger' \ + 'nocomp:or("true", "false")' \ + 'nodelay:bool' \ + 'interval:uinteger' \ + 'resend:range(0,2)' \ + 'nc:bool' \ + 'acknodelay:or("true", "false")' \ + 'sockbuf:uinteger' \ + 'keepalive:uinteger' \ + 'snmpperiod:min(1)' +} + +validate_client_file() { + local file="$1" + + if [ ! -f "$file" ]; then + return 1 + fi + + [ -x "$file" ] || chmod 755 "$file" + + ( $file -v 2>/dev/null | grep -q "$KCPTUN" ) +} + +start_kcptun_instance() { + local section="$1" + + if ! validate_config_section "$section" ; then + _log "err" "Config validate failed." + return 1 + fi + + if [ -z "$server" ] || [ "$server" = "nil" ]; then + _log "info" "No server selected, Client will stop." + return 0 + elif ! validate_server_section "$server"; then + _log "err" "Server config validation failed." + return 1 + elif [ -z "$server_addr" ] || [ -z "$listen_port" ]; then + _log "err" "Server config validation failed." + return 1 + fi + + if [ -z "$client_file" ]; then + _log "err" "Please set client file path, or use auto download." + return 1; + elif ! validate_client_file "$client_file"; then + _log "err" "Client file validation failed." + return 1 + fi + + is_ipv6_address() { + echo "$1" | grep -q ":" + } + + is_ipv6_address "$server_addr" && server_addr="[${server_addr}]" + is_ipv6_address "$listen_addr" && listen_addr="[${listen_addr}]" + + [ -d "$CONFIG_FOLDER" ] || mkdir -p "$CONFIG_FOLDER" + + log_file="" + if [ "$enable_logging" = "1" ]; then + mkdir -p "$log_folder" + chown -R "$daemon_user" "$log_folder" + log_file="${log_folder}/client.${section}.log" + fi + + local config_file=${CONFIG_FOLDER}/client.${section}.json + + if ! ( gen_client_config_file "$config_file" ); then + _log "err" "Can't create config file". + return 1 + fi + + add_iptables_rule "$listen_port" "client" + + procd_open_instance + procd_set_param command "$client_file" + procd_append_param command -c "$config_file" + procd_set_param respawn + procd_set_param user "$daemon_user" + procd_set_param file "$config_file" + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger "$KCPTUN" +} + +start_service() { + clear_iptables_chain + setup_iptables_chain + + config_load "$KCPTUN" + config_foreach start_kcptun_instance "general" +} + +stop_service() { + clear_iptables_chain +} diff --git a/package/lean/luci-app-kcptun/root/etc/uci-defaults/40_luci-kcptun b/package/lean/luci-app-kcptun/root/etc/uci-defaults/40_luci-kcptun new file mode 100755 index 000000000..c759ba19e --- /dev/null +++ b/package/lean/luci-app-kcptun/root/etc/uci-defaults/40_luci-kcptun @@ -0,0 +1,25 @@ +#!/bin/sh + +uci -q batch <<-EOF >/dev/null + delete ucitrack.@kcptun[-1] + add ucitrack kcptun + set ucitrack.@kcptun[-1].init=kcptun + commit ucitrack +EOF + +general=$(uci -q get kcptun.@general[-1]) + +if [ -z "$general" ]; then + uci -q add kcptun general +fi + +if [ ."$general" != ."general" ]; then + uci -q batch <<-EOF >/dev/null + rename kcptun.@general[-1]="general" + set kcptun.general.server="" + commit kcptun + EOF +fi + +rm -rf /tmp/luci-indexcache /tmp/luci-modulecache +exit 0