From e7329a11ef8eacdeb3a837719cc4750e2f62f689 Mon Sep 17 00:00:00 2001 From: Vadim Vetrov Date: Fri, 20 Dec 2024 20:00:22 +0300 Subject: [PATCH 1/3] Fix page reloads on button click. --- .../luasrc/model/cbi/youtubeUnblock.lua | 137 ++++++++++-------- 1 file changed, 79 insertions(+), 58 deletions(-) diff --git a/luci-app-youtubeUnblock/luasrc/model/cbi/youtubeUnblock.lua b/luci-app-youtubeUnblock/luasrc/model/cbi/youtubeUnblock.lua index 08ed910..a8fc410 100644 --- a/luci-app-youtubeUnblock/luasrc/model/cbi/youtubeUnblock.lua +++ b/luci-app-youtubeUnblock/luasrc/model/cbi/youtubeUnblock.lua @@ -1,84 +1,105 @@ local sys = require "luci.sys" +local redirect_path = luci.dispatcher.build_url( + "admin", "services", "youtubeUnblock" +) + -- local uci = require "luci.model.uci".cursor() local m = Map("youtubeUnblock", "youtubeUnblock", "Bypasses Deep Packet Inspection (DPI) systems that rely on SNI") local s = m:section(NamedSection, "youtubeUnblock", "youtubeUnblock", "youtubeUnblock", "Config. Check the README for more details https://github.com/Waujito/youtubeUnblock") -local o -o = s:option(TextValue, "args", "args", "Pass your list of arguments here.") +local o = s:option(TextValue, "args", "args", "Pass your list of arguments here.") -local bs = m:section(NamedSection, "youtubeUnblock", "youtubeUnblock", "Service status") +s = m:section(NamedSection, "youtubeUnblock", "youtubeUnblock", "Service status") -local asts = sys.call("/etc/init.d/youtubeUnblock enabled &>/dev/null") - -if asts == 0 then - local asto = bs:option(Button, "_autostart_disable", "Autostart") - asto.inputstyle = "negative" - asto.inputtitle = "Disable" - - asto.write = function(self, section) +o = s:option(Button, "_autostart", "Autostart") +o._state = false +function o.cbid(self, section) + local service_enabled = sys.call("/etc/init.d/youtubeUnblock enabled &>/dev/null") + self._state = tonumber(service_enabled) == 1 + self.option = self._state and "disabled" or "enabled" + return AbstractValue.cbid(self, section) +end +function o.cfgvalue(self, section) + self.title = self._state and "Enable" or "Disable" + self.inputstyle = self._state and "positive" or "negative" + self.description = "youtubeUnblock is currently " .. self.option +end +function o.write(self, section) + if self._state then + sys.call("/etc/init.d/youtubeUnblock enable &>/dev/null") + else sys.call("/etc/init.d/youtubeUnblock disable &>/dev/null") end -else - local asto = bs:option(Button, "_autostart_enable", "Autostart") - asto.inputstyle = "positive" - asto.inputtitle = "Enable" - - asto.write = function(self, section) - sys.call("/etc/init.d/youtubeUnblock enable &>/dev/null") - end + luci.http.redirect(redirect_path) end - -local sts = sys.call("/etc/init.d/youtubeUnblock running &>/dev/null") - -if sts == 0 then - local sto = bs:option(Button, "_status_stop", "Status") - sto.inputstyle = "negative" - sto.inputtitle = "Stop" - sto.description = "youtubeUnblock is currently active" - - sto.write = function(self, section) +o = s:option(Button, "_status", "Autostart") +o._state = false +function o.cbid(self, section) + local service_running = sys.call("/etc/init.d/youtubeUnblock running &>/dev/null") + self._state = tonumber(service_running) == 1 + self.option = self._state and "down" or "active" + return AbstractValue.cbid(self, section) +end +function o.cfgvalue(self, section) + self.title = self._state and "Start" or "Stop" + self.inputstyle = self._state and "positive" or "negative" + self.description = "youtubeUnblock is currently " .. self.option +end +function o.write(self, section) + if self._state then + sys.call("/etc/init.d/youtubeUnblock start &>/dev/null") + else sys.call("/etc/init.d/youtubeUnblock stop &>/dev/null") end -else - local sto = bs:option(Button, "_status_start", "Status") - sto.inputstyle = "positive" - sto.inputtitle = "Start" - sto.description = "youtubeUnblock is currently down" - - sto.write = function(self, section) - sys.call("/etc/init.d/youtubeUnblock start &>/dev/null") - end + luci.http.redirect(redirect_path) end -local rso = bs:option(Button, "_restart", "Restart") -rso.inputstyle = "action" -function rso.write(self, section) +local o = s:option(Button, "_restart", "Restart") +o.inputstyle = "action" +function o.write(self, section) sys.call("/etc/init.d/youtubeUnblock restart &>/dev/null") + luci.http.redirect(redirect_path) end -local fwo = bs:option(Button, "_firewall", "Firewall") -fwo.inputtitle = "Reload" -fwo.inputstyle = "action" -function fwo.write(self, section) +local o = s:option(Button, "_firewall", "Firewall") +o.inputtitle = "Reload" +o.inputstyle = "action" +function o.write(self, section) sys.call("/etc/init.d/firewall reload") + luci.http.redirect(redirect_path) end -local rso = bs:option(Button, "_reset_settings", "Reset settings to defaults") -rso.inputtitle = "Reset" -rso.inputstyle = "negative" -function rso.write(self, section) +local o = s:option(Button, "_reset_settings", "Reset settings to defaults") +o.inputtitle = "Reset" +o.inputstyle = "negative" +function o.write(self, section) sys.call("/usr/share/youtubeUnblock/youtubeUnblock_defaults.sh --force") + luci.http.redirect(redirect_path) end -local logs = sys.exec("logread -l 800 -p youtubeUnblock | grep youtubeUnblock | sed '1!G;h;$!d'") -local o = bs:option(DummyValue, "_logs", "Logs") -o.rawhtml = true -o.value = logs -o.wrap = "off" -o.rows = 33 -o.readonly = true -o.template = "cbi/tvalue" -o.width = "100%" +s = m:section(NamedSection, "youtubeUnblock", "youtubeUnblock", "Service logs") + +local o = s:option(Button, "_reload_logs", "Reload") +o.inputstyle = "reload" +o.inputtitle = "Reload logs" +o.redirect = redirect_path .. "#" .. AbstractValue.cbid(o, "youtubeUnblock") +function o.write(self, section) + luci.http.redirect(self.redirect) +end + +local logs_opt = s:option(DummyValue, "_logs", "Logs") +logs_opt.rawhtml = true +logs_opt.wrap = "off" +logs_opt.rows = 33 +logs_opt.readonly = true +logs_opt.template = "cbi/tvalue" +logs_opt.width = "100%" + +function logs_opt.cbid(self, section) + local logs = sys.exec("logread -l 800 -p youtubeUnblock | grep youtubeUnblock | sed '1!G;h;$!d'") + self.value = logs + return AbstractValue.cbid(self, section) +end return m From c9202aeedf3c9da5bfc9ac8165984e9849cb4d22 Mon Sep 17 00:00:00 2001 From: Vadim Vetrov Date: Sat, 21 Dec 2024 02:56:11 +0300 Subject: [PATCH 2/3] Remaster luci-app-youtubeUnblock from deprecated lua scripts to client-side JS --- luci-app-youtubeUnblock/Makefile | 2 +- .../resources/view/youtubeUnblock/status.js | 239 ++++++++++++++++++ .../luasrc/controller/youtubeUnblock.lua | 5 - .../luasrc/model/cbi/youtubeUnblock.lua | 105 -------- .../luci/menu.d/luci-app-youtubeUnblock.json | 28 ++ .../rpcd/acl.d/luci-app-youtubeUnblock.json | 25 ++ 6 files changed, 293 insertions(+), 111 deletions(-) create mode 100644 luci-app-youtubeUnblock/htdocs/luci-static/resources/view/youtubeUnblock/status.js delete mode 100644 luci-app-youtubeUnblock/luasrc/controller/youtubeUnblock.lua delete mode 100644 luci-app-youtubeUnblock/luasrc/model/cbi/youtubeUnblock.lua create mode 100644 luci-app-youtubeUnblock/root/usr/share/luci/menu.d/luci-app-youtubeUnblock.json create mode 100644 luci-app-youtubeUnblock/root/usr/share/rpcd/acl.d/luci-app-youtubeUnblock.json diff --git a/luci-app-youtubeUnblock/Makefile b/luci-app-youtubeUnblock/Makefile index daa18af..d0f16e0 100644 --- a/luci-app-youtubeUnblock/Makefile +++ b/luci-app-youtubeUnblock/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=LuCI Support for youtubeUnblock -LUCI_DEPENDS:=+luci-base +luci-compat +LUCI_DEPENDS:=+luci-base PKG_LICENSE:=GPL PKG_MAINTAINER:=Vadim Vetrov diff --git a/luci-app-youtubeUnblock/htdocs/luci-static/resources/view/youtubeUnblock/status.js b/luci-app-youtubeUnblock/htdocs/luci-static/resources/view/youtubeUnblock/status.js new file mode 100644 index 0000000..01de834 --- /dev/null +++ b/luci-app-youtubeUnblock/htdocs/luci-static/resources/view/youtubeUnblock/status.js @@ -0,0 +1,239 @@ +'use strict'; +'require view'; +'require poll'; +'require fs'; +'require ui'; +'require uci'; +'require form'; +'require tools.widgets as widgets'; +'require tools.views as views'; + +/** + * Big thanks to luci-app-adblock for the best reference implementation + */ + + +/* + button handling +*/ +function handleAction(act, event) { + if (event.target.classList.contains('disabled') || event.target.classList.contains('cbi-button-inactive')) + return; + + function roll_inact(target) { + target.classList.add('spinning'); + target.classList.add('disabled'); + } + function unroll_inact(target) { + target.classList.remove('spinning'); + target.classList.remove('disabled'); + } + function thn_disp() { + unroll_inact(event.target); + } + function thn_inc() { + roll_inact(event.target); + } + + roll_inact(event.target); + if (act == "restart") { + fs.exec_direct('/etc/init.d/youtubeUnblock', [ 'restart' ]).then(thn_disp); + } else if (act == "fw_reload") { + fs.exec_direct('/etc/init.d/firewall', [ 'reload' ]).then(thn_disp); + } else if (act == "status") { + if (event.target.classList.contains('cbi-button-positive')) { + fs.exec_direct('/etc/init.d/youtubeUnblock', [ 'start' ]).then(thn_inc); + } else { + fs.exec_direct('/etc/init.d/youtubeUnblock', [ 'stop' ]).then(thn_inc); + } + } else if (act == "autostart") { + if (event.target.classList.contains('cbi-button-positive')) { + fs.exec_direct('/etc/init.d/youtubeUnblock', [ 'enable' ]).then(thn_inc); + } else { + fs.exec_direct('/etc/init.d/youtubeUnblock', [ 'disable' ]).then(thn_inc); + } + } else { + unroll_inact(event.target); + } + +} + +return view.extend({ + load: function() { + return Promise.all([ + uci.load('youtubeUnblock'), + ]); + }, + + render: function(result) { + let m, s, o; +"youtubeUnblock", "youtubeUnblock", "Bypasses Deep Packet Inspection (DPI) systems that rely on SNI" + m = new form.Map('youtubeUnblock', 'youtubeUnblock', _("Bypasses Deep Packet Inspection (DPI) systems that rely on SNI.
Check the README for more details https://github.com/Waujito/youtubeUnblock")); + + /* + poll runtime information + */ + pollData: poll.add(function() { + fs.exec_direct('/etc/init.d/youtubeUnblock', ['status']) + .then(function(res) { + const status = document.getElementById('ytb_status'); + const btn_status = document.getElementById('btn_status'); + if (status == null || btn_status == null) { + return; + } + + status.classList.remove("spinning"); + res = res.trim(); + status.textContent = res; + + if (res != "inactive" && res != "running") { + return; + } + + btn_status.classList.remove("spinning"); + btn_status.classList.remove("cbi-button-inactive"); + btn_status.classList.remove("cbi-button-negative"); + btn_status.classList.remove("cbi-button-positive"); + btn_status.classList.remove("disabled"); + + + if (res == "running") { + btn_status.textContent = "Stop"; + btn_status.classList.add("cbi-button-negative"); + } else { + btn_status.textContent = "Start"; + btn_status.classList.add("cbi-button-positive"); + } + }); + + fs.exec_direct('/usr/bin/youtubeUnblock', ['--version']) + .then(function(res) { + const elversion = document.getElementById('ytb_version'); + if (elversion == null) { + return; + } + + elversion.classList.remove("spinning"); + + elversion.textContent = res; + }); + + fs.exec('/etc/init.d/youtubeUnblock', ['enabled']) + .then(function(res) { + const autostart = document.getElementById('ytb_autostart'); + const btn_autostart = document.getElementById('btn_autostart'); + if (autostart == null || btn_autostart == null) { + return; + } + + autostart.classList.remove("spinning"); + + btn_autostart.classList.remove("spinning"); + btn_autostart.classList.remove("cbi-button-inactive"); + btn_autostart.classList.remove("cbi-button-negative"); + btn_autostart.classList.remove("cbi-button-positive"); + btn_autostart.classList.remove("disabled"); + + if (res.code == 0) { + autostart.textContent = "enabled"; + btn_autostart.textContent = "Disable"; + btn_autostart.classList.add("cbi-button-negative"); + } else { + autostart.textContent = "disabled"; + btn_autostart.textContent = "Enable"; + btn_autostart.classList.add("cbi-button-positive"); + } + }); + + fs.exec_direct("/sbin/logread", ['-e', "youtubeUnblock", '-l', 200]).then(function(res) { + const log = document.getElementById("ytb_logger"); + if (log == null) + return; + + if (res) { + log.value = res.trim(); + } else { + log.value = _('No related logs yet!'); + } + log.scrollTop = log.scrollHeight; + }); + + }, 1); + + /* + runtime information and buttons + */ + s = m.section(form.NamedSection, 'global'); + s.render = L.bind(function(view, section_id) { + return E('div', { 'class': 'cbi-section' }, [ + E('h3', _('Information')), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Version')), + E('div', { 'class': 'cbi-value-field spinning', 'id': 'ytb_version', 'style': 'color:#37c' },'\xa0') + ]), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Status')), + E('div', { 'class': 'cbi-value-field spinning', 'id': 'ytb_status', 'style': 'color:#37c' },'\xa0') + ]), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Autostart')), + E('div', { 'class': 'cbi-value-field spinning', 'id': 'ytb_autostart', 'style': 'color:#37c' },'\xa0') + ]), + E('div', { class: 'right' }, [ + E('button', { + 'class': 'btn cbi-button cbi-button-inactive disabled spinning', + 'id': 'btn_autostart', + 'click': ui.createHandlerFn(this, function(event) { + return handleAction('autostart', event); + }) + }, [ _('Autostart') ]), + '\xa0\xa0\xa0', + E('button', { + 'class': 'btn cbi-button cbi-button-inactive disabled spinning', + 'id': 'btn_status', + 'click': ui.createHandlerFn(this, function(event) { + return handleAction('status', event); + }) + }, [ _('Status') ]), + '\xa0\xa0\xa0', + E('button', { + 'class': 'btn cbi-button cbi-button-apply', + 'click': ui.createHandlerFn(this, function(event) { + return handleAction('restart', event); + }) + }, [ _('Restart') ]), + '\xa0\xa0\xa0', + E('button', { + 'class': 'btn cbi-button cbi-button-apply', + 'id': 'btn_fw_reload', + 'click': ui.createHandlerFn(this, function(event) { + return handleAction('fw_reload', event); + }) + }, [ _('Firewall reload') ]), + ]) + ]); + }, o, this); + + const logs_s = m.section(form.NamedSection, 'ytb_logs'); + logs_s.render = L.bind(function(view, section_id) { + return E('div', { class: 'cbi-map' }, + E('div', { class: 'cbi-section' }, [ + E('div', { class: 'cbi-section-descr' }, _('The syslog output, pre-filtered for messages related to: youtubeUnblock')), + E('textarea', { + 'id': 'ytb_logger', + 'style': 'width: 100% !important; padding: 5px; font-family: monospace', + 'readonly': 'readonly', + 'wrap': 'off', + 'rows': 25 + }) + ])); + }); + this.pollData; + + return m.render(); + }, + handleReset: null, + handleSaveApply: null, + handleSave: null, +}); + diff --git a/luci-app-youtubeUnblock/luasrc/controller/youtubeUnblock.lua b/luci-app-youtubeUnblock/luasrc/controller/youtubeUnblock.lua deleted file mode 100644 index cab80c5..0000000 --- a/luci-app-youtubeUnblock/luasrc/controller/youtubeUnblock.lua +++ /dev/null @@ -1,5 +0,0 @@ -module("luci.controller.youtubeUnblock", package.seeall) - -function index() - entry( {"admin", "services", "youtubeUnblock"}, cbi("youtubeUnblock"), _("youtubeUnblock")) -end diff --git a/luci-app-youtubeUnblock/luasrc/model/cbi/youtubeUnblock.lua b/luci-app-youtubeUnblock/luasrc/model/cbi/youtubeUnblock.lua deleted file mode 100644 index a8fc410..0000000 --- a/luci-app-youtubeUnblock/luasrc/model/cbi/youtubeUnblock.lua +++ /dev/null @@ -1,105 +0,0 @@ -local sys = require "luci.sys" -local redirect_path = luci.dispatcher.build_url( - "admin", "services", "youtubeUnblock" -) - --- local uci = require "luci.model.uci".cursor() -local m = Map("youtubeUnblock", "youtubeUnblock", "Bypasses Deep Packet Inspection (DPI) systems that rely on SNI") -local s = m:section(NamedSection, "youtubeUnblock", "youtubeUnblock", "youtubeUnblock", "Config. Check the README for more details https://github.com/Waujito/youtubeUnblock") - -local o = s:option(TextValue, "args", "args", "Pass your list of arguments here.") - -s = m:section(NamedSection, "youtubeUnblock", "youtubeUnblock", "Service status") - -o = s:option(Button, "_autostart", "Autostart") -o._state = false -function o.cbid(self, section) - local service_enabled = sys.call("/etc/init.d/youtubeUnblock enabled &>/dev/null") - self._state = tonumber(service_enabled) == 1 - self.option = self._state and "disabled" or "enabled" - return AbstractValue.cbid(self, section) -end -function o.cfgvalue(self, section) - self.title = self._state and "Enable" or "Disable" - self.inputstyle = self._state and "positive" or "negative" - self.description = "youtubeUnblock is currently " .. self.option -end -function o.write(self, section) - if self._state then - sys.call("/etc/init.d/youtubeUnblock enable &>/dev/null") - else - sys.call("/etc/init.d/youtubeUnblock disable &>/dev/null") - end - luci.http.redirect(redirect_path) -end - -o = s:option(Button, "_status", "Autostart") -o._state = false -function o.cbid(self, section) - local service_running = sys.call("/etc/init.d/youtubeUnblock running &>/dev/null") - self._state = tonumber(service_running) == 1 - self.option = self._state and "down" or "active" - return AbstractValue.cbid(self, section) -end -function o.cfgvalue(self, section) - self.title = self._state and "Start" or "Stop" - self.inputstyle = self._state and "positive" or "negative" - self.description = "youtubeUnblock is currently " .. self.option -end -function o.write(self, section) - if self._state then - sys.call("/etc/init.d/youtubeUnblock start &>/dev/null") - else - sys.call("/etc/init.d/youtubeUnblock stop &>/dev/null") - end - luci.http.redirect(redirect_path) -end - -local o = s:option(Button, "_restart", "Restart") -o.inputstyle = "action" -function o.write(self, section) - sys.call("/etc/init.d/youtubeUnblock restart &>/dev/null") - luci.http.redirect(redirect_path) -end - -local o = s:option(Button, "_firewall", "Firewall") -o.inputtitle = "Reload" -o.inputstyle = "action" -function o.write(self, section) - sys.call("/etc/init.d/firewall reload") - luci.http.redirect(redirect_path) -end - -local o = s:option(Button, "_reset_settings", "Reset settings to defaults") -o.inputtitle = "Reset" -o.inputstyle = "negative" -function o.write(self, section) - sys.call("/usr/share/youtubeUnblock/youtubeUnblock_defaults.sh --force") - luci.http.redirect(redirect_path) -end - -s = m:section(NamedSection, "youtubeUnblock", "youtubeUnblock", "Service logs") - -local o = s:option(Button, "_reload_logs", "Reload") -o.inputstyle = "reload" -o.inputtitle = "Reload logs" -o.redirect = redirect_path .. "#" .. AbstractValue.cbid(o, "youtubeUnblock") -function o.write(self, section) - luci.http.redirect(self.redirect) -end - -local logs_opt = s:option(DummyValue, "_logs", "Logs") -logs_opt.rawhtml = true -logs_opt.wrap = "off" -logs_opt.rows = 33 -logs_opt.readonly = true -logs_opt.template = "cbi/tvalue" -logs_opt.width = "100%" - -function logs_opt.cbid(self, section) - local logs = sys.exec("logread -l 800 -p youtubeUnblock | grep youtubeUnblock | sed '1!G;h;$!d'") - self.value = logs - return AbstractValue.cbid(self, section) -end - -return m diff --git a/luci-app-youtubeUnblock/root/usr/share/luci/menu.d/luci-app-youtubeUnblock.json b/luci-app-youtubeUnblock/root/usr/share/luci/menu.d/luci-app-youtubeUnblock.json new file mode 100644 index 0000000..623698b --- /dev/null +++ b/luci-app-youtubeUnblock/root/usr/share/luci/menu.d/luci-app-youtubeUnblock.json @@ -0,0 +1,28 @@ +{ + "admin/services/youtubeUnblock": { + "title": "youtubeUnblock", + "order": 60, + "action": { + "type": "alias", + "path": "admin/services/youtubeUnblock/status" + }, + "depends": { + "acl": [ "luci-app-youtubeUnblock" ], + "fs": { + "/usr/bin/youtubeUnblock": "executable", + "/etc/init.d/youtubeUnblock": "executable" + }, + "uci": { "youtubeUnblock": true } + } + + }, + + "admin/services/youtubeUnblock/status": { + "title": "Service Status", + "order": 10, + "action": { + "type": "view", + "path": "youtubeUnblock/status" + } + } +} diff --git a/luci-app-youtubeUnblock/root/usr/share/rpcd/acl.d/luci-app-youtubeUnblock.json b/luci-app-youtubeUnblock/root/usr/share/rpcd/acl.d/luci-app-youtubeUnblock.json new file mode 100644 index 0000000..dd52c9c --- /dev/null +++ b/luci-app-youtubeUnblock/root/usr/share/rpcd/acl.d/luci-app-youtubeUnblock.json @@ -0,0 +1,25 @@ +{ + "luci-app-youtubeUnblock": { + "description": "Grant UCI access for luci-app-youtubeUnblock", + "read": { + "cgi-io": [ "exec" ], + "file": { + "/etc/init.d/youtubeUnblock status" : [ "exec" ], + "/etc/init.d/youtubeUnblock enabled" : [ "exec" ], + "/etc/init.d/youtubeUnblock start" : [ "exec" ], + "/etc/init.d/youtubeUnblock stop" : [ "exec" ], + "/etc/init.d/youtubeUnblock restart" : [ "exec" ], + "/etc/init.d/youtubeUnblock enable" : [ "exec" ], + "/etc/init.d/youtubeUnblock disable" : [ "exec" ], + "/etc/init.d/firewall reload" : [ "exec" ], + "/usr/bin/youtubeUnblock --version" : [ "exec" ], + "/sbin/logread -e youtubeUnblock -l [0-9]*": [ "exec" ], + "/usr/sbin/logread -e youtubeUnblock -l [0-9]*": [ "exec" ] + }, + "uci": [ "youtubeUnblock" ] + }, + "write": { + "uci": [ "youtubeUnblock" ] + } + } +} From c496b3131fadc57f7923577027a9ebf247dcec02 Mon Sep 17 00:00:00 2001 From: Vadim Vetrov Date: Sun, 22 Dec 2024 03:41:41 +0300 Subject: [PATCH 3/3] Implement luci flags with JS --- .../view/youtubeUnblock/configuration.js | 288 ++++++++++++++++++ .../luci/menu.d/luci-app-youtubeUnblock.json | 9 + .../files/etc/init.d/youtubeUnblock | 111 ++++++- .../etc/uci-defaults/99-youtubeUnblock.sh | 43 ++- 4 files changed, 440 insertions(+), 11 deletions(-) create mode 100644 luci-app-youtubeUnblock/htdocs/luci-static/resources/view/youtubeUnblock/configuration.js diff --git a/luci-app-youtubeUnblock/htdocs/luci-static/resources/view/youtubeUnblock/configuration.js b/luci-app-youtubeUnblock/htdocs/luci-static/resources/view/youtubeUnblock/configuration.js new file mode 100644 index 0000000..1bdb2ac --- /dev/null +++ b/luci-app-youtubeUnblock/htdocs/luci-static/resources/view/youtubeUnblock/configuration.js @@ -0,0 +1,288 @@ +'use strict'; +'require view'; +'require poll'; +'require fs'; +'require ui'; +'require uci'; +'require form'; +'require tools.widgets as widgets'; + +return view.extend({ + load: function() { + return Promise.all([ + uci.load('youtubeUnblock'), + ]); + }, + + renderSectionTLSConfigs: function(s) { + let o; + + o = s.option(form.Flag, "tls_enabled", _("TLS enabled"), _("Disable this flag if you want not to process TLS traffic in current section. May be used if you want to set only UDP-based policy.")); + o.enabled = '1'; + o.disabled = '0'; + o.default = o.enabled; + o.rmempty = false; + + + o = s.option(form.Flag, "fake_sni", _("Fake sni"), _("This flag enables fake-sni which forces youtubeUnblock to send at least three packets instead of one with TLS ClientHello: Fake ClientHello, 1st part of original ClientHello, 2nd part of original ClientHello. This flag may be related to some Operation not permitted error messages, so before open an issue refer to Troubleshooting for EPERMS.")); + o.depends('tls_enabled', '1'); + o.enabled = '1' + o.disabled = '0' + o.default = o.enabled; + o.rmempty = false; + + o = s.option(form.ListValue, "faking_strategy", _("Faking strategy"), ` + This flag determines the strategy of fake packets invalidation. +
    +
  • randseq specifies that random sequence/acknowledgment random will be set. This option may be handled by provider which uses conntrack with drop on invalid conntrack state firewall rule enabled.
  • +
  • ttl specifies that packet will be invalidated after --faking-ttl=n hops. ttl is better but may cause issues if unconfigured.
  • +
  • pastseq is like randseq but sequence number is not random but references the packet sent in the past (before current).
  • +
  • tcp_check will invalidate faking packet with invalid checksum. May be handled and dropped by some providers/TSPUs.
  • +
  • md5sum will invalidate faking packet with invalid TCP md5sum. md5sum is a TCP option which is handled by the destination server but may be skipped by TSPU.
  • +
` + ); + o.depends("fake_sni", '1'); + o.widget="radio"; + o.value("pastseq", "pastseq"); + o.value("randseq", "randseq"); + o.value("ttl", "ttl"); + o.value("tcp_check", "tcp_check"); + o.value("md5sum", "md5sum"); + o.default = "pastseq"; + o.rmempty = false; + + o = s.option(form.Value, "faking_ttl", _("Faking ttl"), _("Tunes the time to live (TTL) of fake SNI messages. TTL is specified like that the packet will go through the DPI system and captured by it, but will not reach the destination server.")); + o.depends("faking_strategy", "ttl"); + o.default = 8; + o.rmempty = false; + + o = s.option(form.Value, "fake_seq_offset", _("Fake seq offset"), _("Tunes the offset from original sequence number for fake packets. Used by randseq faking strategy. If 0, random sequence number will be set.")); + o.depends("faking_strategy", "randseq"); + o.default = 10000; + o.rmempty = false; + + o = s.option(form.Value, "fake_sni_seq_len", _("Fake sni seq len"), _("This flag specifies youtubeUnblock to build a complicated construction of fake client hello packets. length determines how much fakes will be sent.")); + o.depends("fake_sni", "1") + o.default = 1; + o.rmempty = false; + + o = s.option(form.ListValue, "fake_sni_type", _("Fake sni type"), _("This flag specifies which faking message type should be used for fake packets. For random, the message of the random length and with random payload will be sent. For default the default payload (sni=www.google.com) is used. And for the custom option, the payload from --fake-custom-payload section utilized. Defaults to default.")); + o.depends("fake_sni", "1"); + o.widget="radio"; + o.value("default", "default"); + o.value("custom", "custom"); + o.value("random", "random"); + o.default = "default"; + o.rmempty = false; + + o = s.option(form.Value, "fake_custom_payload", _("Fake custom payload"), _("Useful with --fake-sni-type=custom. You should specify the payload for fake message manually. Use hex format: --fake-custom-payload=0001020304 mean that 5 bytes sequence: 0x00, 0x01, 0x02, 0x03, 0x04 used as fake.")); + o.depends("fake_sni_type", "custom"); + + o = s.option(form.ListValue, "frag", _("Fragmentation strategy"), _("Specifies the fragmentation strategy for the packet. Tcp is used by default. Ip fragmentation may be blocked by DPI system. None specifies no fragmentation. Probably this won't work, but may be will work for some fake sni strategies.")); + o.depends('tls_enabled', '1'); + o.widget="radio"; + o.value("tcp", "tcp"); + o.value("ip", "ip"); + o.value("none", "none"); + o.default = "tcp"; + o.rmempty = false; + + o = s.option(form.Flag, "frag_sni_reverse", _("Frag sni reverse"), _("Specifies youtubeUnblock to send ClientHello fragments in the reverse order.")); + o.depends("frag", "tcp"); + o.depends("frag", "ip"); + o.enabled = '1' + o.disabled = '0' + o.default = o.enabled; + o.rmempty = false; + + o = s.option(form.Flag, "frag_sni_faked", _("Frag sni faked"), _("Specifies youtubeUnblock to send fake packets near ClientHello (fills payload with zeroes).")); + o.depends("frag", "tcp"); + o.depends("frag", "ip"); + o.enabled = '1' + o.disabled = '0' + o.default = o.disabled; + o.rmempty = false; + + o = s.option(form.Flag, "frag_middle_sni", _("Frag middle sni"), _("With this options youtubeUnblock will split the packet in the middle of SNI data.")); + o.depends("frag", "tcp"); + o.depends("frag", "ip"); + o.enabled = '1' + o.disabled = '0' + o.default = o.enabled; + o.rmempty = false; + + o = s.option(form.Value, "frag_sni_pos", _("Frag sni pos"), _("With this option youtubeUnblock will split the packet at the position pos.")); + o.depends("frag", "tcp"); + o.depends("frag", "ip"); + o.rmempty = false; + o.default = 1; + + o = s.option(form.Value, "seg2delay", _("seg2delay"), _("This flag forces youtubeUnblock to wait a little bit before send the 2nd part of the split packet.")); + o.depends('tls_enabled', '1'); + o.default = 0; + + o = s.option(form.Value, "fk_winsize", _("Fragmentation winsize"), _("Specifies window size for the fragmented TCP packet. Applicable if you want for response to be fragmented. May slowdown connection initialization. Pass 0 if you don't want this.")); + o.depends("frag", "tcp"); + o.depends("frag", "ip"); + o.default = 0; + o.rmempty = false; + + o = s.option(form.Flag, "synfake", _("Synfake"), _("If 1, syn payload will be sent before each request. The idea is taken from syndata from zapret project. Syn payload will normally be discarded by endpoint but may be handled by TSPU. This option sends normal fake in that payload. Please note, that the option works for all the sites, so --sni-domains won't change anything.")); + o.depends('tls_enabled', '1'); + o.enabled = "1"; + o.disabled = "0"; + o.default = o.disabled; + o.rmempty = false; + + o = s.option(form.Value, "synfake_len", _("synfake len"), _("The fake packet sent in synfake may be too large. If you experience issues, lower up synfake-len. where len stands for how much bytes should be sent as syndata. Pass 0 if you want to send an entire fake packet.")); + o.depends("synfake", "1"); + o.default = 0; + o.rmempty = false; + + o = s.option(form.ListValue, "sni_detection", _("SNI detection"), _("Specifies how to detect SNI. Parse will normally detect it by parsing the Client Hello message. Brute will go through the entire message and check possibility of SNI occurrence. Please note, that when --sni-domains option is not all brute will be O(nm) time complexity where n stands for length of the message and m is number of domains.")); + o.depends('tls_enabled', '1'); + o.widget="radio"; + o.value("parse", "parse"); + o.value("brute", "brute"); + o.default = "parse"; + o.rmempty = false; + + }, + renderSectionUDPConfigs: function(s) { + let o; + + o = s.option(form.Flag, "quic_drop", _("QUIC drop"), _("Drop all QUIC packets which goes to youtubeUnblock. Won't affect any other UDP packets.")); + o.enabled = '1' + o.disabled = '0' + o.default = o.disabled; + o.rmempty = false; + + o = s.option(form.ListValue, "udp_mode", _("UDP mode"), _("This flag specifies udp handling strategy. If drop udp packets will be dropped (useful for quic when browser can fallback to tcp), if fake udp will be faked.")); + o.widget = "radio" + o.depends("quic_drop", "0"); + o.value("fake", "fake"); + o.value("drop", "drop"); + o.default = "fake"; + o.rmempty = false; + + o = s.option(form.Value, "udp_fake_seq_len", _("UDP fake seq length"), _("Specifies how much faking packets will be sent over the network.")); + o.depends("udp_mode", "fake"); + o.default = 6 + o.rmempty = false; + + o = s.option(form.Value, "udp_fake_len", _("UDP fake length"), _("Size of udp fake payload (typically payload is zeroes).")); + o.depends("udp_mode", "fake"); + o.default = 64 + o.rmempty = false; + + o = s.option(form.DynamicList, "udp_dport_filter", _("UDP dport filter"), _("Filter the UDP destination ports. Specifie the ports you want to be handled by youtubeUnblock. Valid inputs are port number or port range (e.g. 200-500).")); + o.depends("quic_drop", "0"); + + o = s.option(form.ListValue, "udp_filter_quic", _("UDP QUIC filter"), _("Enables QUIC filtering for UDP handler. If disabled, quic won't be processed, if all, all quic initial packets will be handled.")); + o.widget = "radio" + o.depends("quic_drop", "0"); + o.value("disabled", "disabled"); + o.value("all", "all"); + o.default = "disabled"; + o.rmempty = false; + + }, + renderGeneralConfigs: function(s) { + let o; + + o = s.option(form.Flag, "silent", _("Silent"), _("Disables verbose mode")); + o.depends("trace", 0); + + o = s.option(form.Flag, "trace", _("Trace"), _("Maximum verbosity for debug purposes")); + o.depends("silent", 0); + + o = s.option(form.Flag, "no_gso", _("No gso"), _("Disables support for Google Chrome fat packets which uses GSO. This feature is well tested now, so this flag probably won't fix anything.")); + + o = s.option(form.Flag, "no_ipv6", _("Disable ipv6"), _("Disables support for ipv6. May be useful if you don't want for ipv6 socket to be opened.")); + + o = s.option(form.Value, "packet_mark", _("Packet mark"), _("Use this option if youtubeUnblock conflicts with other systems rely on packet mark. Note that you may want to change accept rule for iptables to follow the mark.")); + o = s.option(form.Value, "post_args", _("Post args"), _("Anything you pass here will be passed to youtubeUnblock as raw args")); + }, + + render: function(result) { + let m, s, o; + + m = new form.Map('youtubeUnblock', _('youtubeUnblock - Configuration'), _("Check the README for more details https://github.com/Waujito/youtubeUnblock")); + + const general_section = m.section(form.NamedSection, "youtubeUnblock", "youtubeUnblock"); + o = general_section.option(form.ListValue, "conf_strat", _("Configuration strategy"), _("Select to configure youtubeUnblock with plain arguments or with interactive flags")); + o.widget = "radio"; + o.value("args"); + o.value("ui_flags"); + o.default = "ui_flags"; + o.rmempty = false; + + o = general_section.option(form.TextValue, "args", "args", "Pass your list of arguments here."); + o.depends("conf_strat", "args"); + + o = general_section.option(form.SectionValue, "_flags_section", + form.NamedSection, "youtubeUnblock", "youtubeUnblock", _("UI Flags configuration")); + o.depends("conf_strat", "ui_flags"); + + const flags_section = o.subsection; + this.renderGeneralConfigs(flags_section); + + o = flags_section.option(form.SectionValue, "_subsections_section", form.GridSection, "section", _("Section configs")) + const subsects_section = o.subsection; + subsects_section.addremove = true; + subsects_section.anonymous = true; + subsects_section.sortable = true; + subsects_section.cloneable = true; + + subsects_section.sectiontitle = function(section_id) { + return uci.get('youtubeUnblock', section_id, 'name') || _('Unnamed section'); + }; + + o = subsects_section.option(form.Flag, "enabled", _("Enabled")); + o.enabled = '1'; + o.disabled = '0'; + o.default = '1'; + o.modalonly = false; + o.editable = true; + o.rmempty = false; + + subsects_section.tab('general', _("General")); + + o = subsects_section.taboption('general', form.Value, "name", _("Name")); + o.placeholder = _('Unnamed section'); + o.modalonly = true; + + + this.renderSectionTLSConfigs({option(optionclass, ...classargs) { + const o = subsects_section.taboption('general', optionclass, ...classargs); + o.modalonly = true; + return o; + }}); + + subsects_section.tab('domains', _("Domains")); + o = subsects_section.taboption('domains', form.Flag, "all_domains", _("Target all domains"), _("Use this option if you want for every ClientHello to be handled")); + o.enabled = "1"; + o.disabled = "0"; + o.default = o.disabled; + o.rmempty = false; + o.modalonly = true; + + o = subsects_section.taboption('domains', form.DynamicList, "sni_domains", _("Sni domains"), _("List of domains you want to be handled by SNI.")); + o.depends("all_domains", "0"); + o.default = ["googlevideo.com", "ggpht.com", "ytimg.com", "youtube.com", "play.google.com", "youtu.be", "googleapis.com", "googleusercontent.com", "gstatic.com", "l.google.com"]; + o.modalonly = true; + + + o = subsects_section.taboption('domains', form.DynamicList, "exclude_domains", _("Excluded domains"), _("List of domains to be excluded from targeting.")); + o.modalonly = true; + + subsects_section.tab('udp', _("UDP")); + this.renderSectionUDPConfigs({option(optionclass, ...classargs) { + const o = subsects_section.taboption('udp', optionclass, ...classargs); + o.modalonly = true; + return o; + }}); + + return m.render(); + } +}); diff --git a/luci-app-youtubeUnblock/root/usr/share/luci/menu.d/luci-app-youtubeUnblock.json b/luci-app-youtubeUnblock/root/usr/share/luci/menu.d/luci-app-youtubeUnblock.json index 623698b..d6788f0 100644 --- a/luci-app-youtubeUnblock/root/usr/share/luci/menu.d/luci-app-youtubeUnblock.json +++ b/luci-app-youtubeUnblock/root/usr/share/luci/menu.d/luci-app-youtubeUnblock.json @@ -24,5 +24,14 @@ "type": "view", "path": "youtubeUnblock/status" } + }, + + "admin/services/youtubeUnblock/configuration": { + "title": "Configuration", + "order": 20, + "action": { + "type": "view", + "path": "youtubeUnblock/configuration" + } } } diff --git a/youtubeUnblock/files/etc/init.d/youtubeUnblock b/youtubeUnblock/files/etc/init.d/youtubeUnblock index b1d7d8a..4ae76e9 100755 --- a/youtubeUnblock/files/etc/init.d/youtubeUnblock +++ b/youtubeUnblock/files/etc/init.d/youtubeUnblock @@ -8,15 +8,110 @@ USE_PROCD=1 # PROCD_DEBUG=1 PROG=/usr/bin/youtubeUnblock -UCI_ARGS="" +# You should use uci for configuration +OPTS="" -# If you have troubles with uci, pass args here +# If you prefer to pass args as cmdline arguments, pass them here POST_ARGS="" -read_uci_args() { +xappend() { + local name="$1" value="$2" + OPTS="$OPTS --${name//_/-}=$value" +} + +xappend_toggler() { + local name="$1" + OPTS="$OPTS --${name//_/-}" +} + +append_opts() { + local name value cfg="$1"; shift + for name in $*; do + config_get value "$cfg" "$name" + [ -n "$value" ] && xappend "$name" "$value" + done +} + +append_commasep_list() { + local name cfg="$1"; shift + for name in $*; do + local res="" + _handle_list() { + res="$res$1," + } + config_list_foreach "$cfg" "$name" _handle_list + [ -n "$res" ] && xappend "$name" "$res" + done +} + +append_opts_boolean() { + local name value cfg="$1"; shift + for name in $*; do + config_get_bool value "$cfg" "$name" 0 + xappend "$name" "$value" + done +} + +append_opts_btoggler() { + local name value cfg="$1"; shift + for name in $*; do + config_get_bool value "$cfg" "$name" 0 + [ $value -gt 0 ] && xappend_toggler "$name" + done +} + +SECTION_NUMBER=0 + +parse_sections_options() { + local config="$1" local value - config_get value "$1" "args" - UCI_ARGS="$value" + + config_get_bool value "$config" enabled 0 + if [ "$value" -eq "0" ]; then + return + fi + + [ $SECTION_NUMBER -gt 0 ] && xappend_toggler "fbegin" + SECTION_NUMBER=$((SECTION_NUMBER+1)) + + config_get_bool value "$config" tls_enabled 0 + if [ $value -gt 0 ]; then + xappend "tls" "enabled" + else + xappend "tls" "disabled" + fi + + config_get_bool value "$config" all_domains 0 + if [ $value -gt 0 ]; then + xappend "sni_domains" "all" + else + append_commasep_list "$config" sni_domains + fi + + append_opts_boolean "$config" fake_sni frag_sni_reverse frag_sni_faked frag_middle_sni synfake + append_opts "$config" fake_sni_seq_len fake_sni_type fake_custom_payload faking_strategy faking_ttl fake_seq_offset frag frag_sni_pos fk_winsize seg2delay synfake_len sni_detection udp_mode udp_fake_seq_len udp_fake_len udp_filter_quic + append_commasep_list "$config" exclude_domains udp_dport_filter + append_opts_btoggler "$config" quic_drop + +} + +parse_general_options() { + local config="$1" + local value + + config_get value "$config" conf_strat + if [ "$value" = "args" ]; then + config_get value "$config" args + OPTS="$value" + else + append_opts "$config" queue_num packet_mark + append_opts_btoggler "$config" silent trace no_gso no_ipv6 + + config_foreach parse_sections_options section + + config_get value "$config" post_args + POST_ARGS="$value" + fi } # Openwrt procd script: https://openwrt.org/docs/guide-developer/procd-init-script-example @@ -25,12 +120,12 @@ read_uci_args() { start_service() { config_load youtubeUnblock - read_uci_args youtubeUnblock + parse_general_options youtubeUnblock - echo "youtubeUnblock is running as: '$PROG $UCI_ARGS $POST_ARGS'" + echo "youtubeUnblock is running as: '$PROG $OPTS $POST_ARGS'" procd_open_instance 'youtubeUnblock' - procd_set_param command $PROG $UCI_ARGS $POST_ARGS + procd_set_param command $PROG $OPTS $POST_ARGS procd_set_param stdout 1 procd_set_param stderr 1 procd_close_instance diff --git a/youtubeUnblock/files/etc/uci-defaults/99-youtubeUnblock.sh b/youtubeUnblock/files/etc/uci-defaults/99-youtubeUnblock.sh index 2ef1029..eb22d81 100644 --- a/youtubeUnblock/files/etc/uci-defaults/99-youtubeUnblock.sh +++ b/youtubeUnblock/files/etc/uci-defaults/99-youtubeUnblock.sh @@ -1,10 +1,47 @@ #!/bin/sh [[ ! "$(uci -q get youtubeUnblock.youtubeUnblock)" == "" ]] && [[ ! "$1" == "--force" ]] && exit 0 -[[ ! "$(uci -q get youtubeUnblock.youtubeUnblock)" == "" ]] && uci delete youtubeUnblock.youtubeUnblock +while uci -q delete youtubeUnblock.@section[0]; do :; done +uci -q delete youtubeUnblock.youtubeUnblock touch /etc/config/youtubeUnblock uci batch << EOI -set youtubeUnblock.youtubeUnblock='youtubeUnblock' -set youtubeUnblock.youtubeUnblock.args='' +set youtubeUnblock.youtubeUnblock=youtubeUnblock +set youtubeUnblock.youtubeUnblock.conf_strat='ui_flags' +set youtubeUnblock.youtubeUnblock.packet_mark='32768' +set youtubeUnblock.youtubeUnblock.queue_num='537' + +set youtubeUnblock.@section[0]=section +set youtubeUnblock.@section[0].name='Default section' +set youtubeUnblock.@section[0].enabled='1' +set youtubeUnblock.@section[0].tls_enabled='1' +set youtubeUnblock.@section[0].fake_sni='1' +set youtubeUnblock.@section[0].faking_strategy='pastseq' +set youtubeUnblock.@section[0].fake_sni_seq_len='1' +set youtubeUnblock.@section[0].fake_sni_type='default' +set youtubeUnblock.@section[0].frag='tcp' +set youtubeUnblock.@section[0].frag_sni_reverse='1' +set youtubeUnblock.@section[0].frag_sni_faked='0' +set youtubeUnblock.@section[0].frag_middle_sni='1' +set youtubeUnblock.@section[0].frag_sni_pos='1' +set youtubeUnblock.@section[0].seg2delay='0' +set youtubeUnblock.@section[0].fk_winsize='0' +set youtubeUnblock.@section[0].synfake='0' +set youtubeUnblock.@section[0].sni_detection='parse' +set youtubeUnblock.@section[0].all_domains='0' +add_list youtubeUnblock.@section[0].sni_domains='googlevideo.com' +add_list youtubeUnblock.@section[0].sni_domains=ggpht.com' +add_list youtubeUnblock.@section[0].sni_domains=ytimg.com' +add_list youtubeUnblock.@section[0].sni_domains=youtube.com' +add_list youtubeUnblock.@section[0].sni_domains=play.google.com' +add_list youtubeUnblock.@section[0].sni_domains=youtu.be' +add_list youtubeUnblock.@section[0].sni_domains=googleapis.com' +add_list youtubeUnblock.@section[0].sni_domains=googleusercontent.com' +add_list youtubeUnblock.@section[0].sni_domains=gstatic.com' +add_list youtubeUnblock.@section[0].sni_domains=l.google.com' +set youtubeUnblock.@section[0].quic_drop='0' +set youtubeUnblock.@section[0].udp_mode='fake' +set youtubeUnblock.@section[0].udp_fake_seq_len='6' +set youtubeUnblock.@section[0].udp_fake_len='64' +set youtubeUnblock.@section[0].udp_filter_quic='disabled' EOI uci commit