From 6348b68a14aecb75d012d71756bdff355e427464 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Sep 2026 03:05:06 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=8B=20Sync=202026-09-07=2003:05:06?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-timecontrol/Makefile | 35 +- .../resources/view/timecontrol/basic.js | 335 ---------- .../resources/view/timecontrol/log.js | 238 ------- .../luasrc/controller/timecontrol.lua | 19 + .../luasrc/model/cbi/timecontrol.lua | 62 ++ .../luasrc/view/timecontrol/index.htm | 12 + .../luasrc/view/timecontrol/timecontrol.htm | 3 + .../po/zh_Hans/timecontrol.po | 112 +--- .../root/etc/config/timecontrol | 20 +- .../root/etc/init.d/timecontrol | 249 +++++++- .../etc/uci-defaults/luci-app-timecontrol | 38 ++ .../root/etc/uci-defaults/luci-timecontrol | 22 - luci-app-timecontrol/root/usr/bin/timecontrol | 600 ------------------ .../root/usr/bin/timecontrol-log | 117 ---- .../root/usr/bin/timecontrolctrl | 335 ---------- .../root/usr/libexec/timecontrol-call | 32 - .../luci/menu.d/luci-app-timecontrol.json | 32 - .../rpcd/acl.d/luci-app-timecontrol.json | 30 +- .../share/ucitrack/luci-app-timecontrol.json | 4 + mptcp/Makefile | 2 +- mptcp/files/etc/hotplug.d/iface/30-mptcp | 24 +- mptcp/files/etc/init.d/mptcp | 25 +- 22 files changed, 428 insertions(+), 1918 deletions(-) delete mode 100644 luci-app-timecontrol/htdocs/luci-static/resources/view/timecontrol/basic.js delete mode 100644 luci-app-timecontrol/htdocs/luci-static/resources/view/timecontrol/log.js create mode 100644 luci-app-timecontrol/luasrc/controller/timecontrol.lua create mode 100644 luci-app-timecontrol/luasrc/model/cbi/timecontrol.lua create mode 100644 luci-app-timecontrol/luasrc/view/timecontrol/index.htm create mode 100644 luci-app-timecontrol/luasrc/view/timecontrol/timecontrol.htm create mode 100644 luci-app-timecontrol/root/etc/uci-defaults/luci-app-timecontrol delete mode 100644 luci-app-timecontrol/root/etc/uci-defaults/luci-timecontrol delete mode 100755 luci-app-timecontrol/root/usr/bin/timecontrol delete mode 100755 luci-app-timecontrol/root/usr/bin/timecontrol-log delete mode 100755 luci-app-timecontrol/root/usr/bin/timecontrolctrl delete mode 100755 luci-app-timecontrol/root/usr/libexec/timecontrol-call delete mode 100644 luci-app-timecontrol/root/usr/share/luci/menu.d/luci-app-timecontrol.json create mode 100644 luci-app-timecontrol/root/usr/share/ucitrack/luci-app-timecontrol.json diff --git a/luci-app-timecontrol/Makefile b/luci-app-timecontrol/Makefile index 9d4acb50..561b34d8 100644 --- a/luci-app-timecontrol/Makefile +++ b/luci-app-timecontrol/Makefile @@ -1,39 +1,16 @@ +# Copyright (C) 2020 Lienol # -# Copyright (C) 2006-2017 OpenWrt.org -# Copyright (C) 2022-2026 sirpdboy -# This is free software, licensed under the GNU General Public License v2. -# See /LICENSE for more information. +# This is free software, licensed under the GNU General Public License v3. # include $(TOPDIR)/rules.mk -THEME_NAME:=timecontrol -PKG_NAME:=luci-app-$(THEME_NAME) - -PKG_LICENSE:=Apache-2.0 - -LUCI_TITLE:=LuCI support for timecontrol for nftables -LUCI_DESCRIPTION:=LuCI support for Easy timecontrol for nftables(Internet time control). -LUCI_DEPENDS:=+bc +nftables +bash +conntrack +LUCI_TITLE:=LuCI support for Time Control +LUCI_DEPENDS:=+luci-base @(PACKAGE_firewall||PACKAGE_firewall4) LUCI_PKGARCH:=all - -PKG_VERSION:=3.2.4 -PKG_RELEASE:=4 -PKG_MAINTAINER:=sirpdboy - -define Build/Compile -endef - -define Package/$(PKG_NAME)/postinst -#!/bin/sh -rm -f /tmp/luci-* -endef - -define Package/$(PKG_NAME)/conffiles -/etc/config/timecontrol -endef +PKG_VERSION:=1.1 +PKG_RELEASE:=5 include $(TOPDIR)/feeds/luci/luci.mk # call BuildPackage - OpenWrt buildroot signature - diff --git a/luci-app-timecontrol/htdocs/luci-static/resources/view/timecontrol/basic.js b/luci-app-timecontrol/htdocs/luci-static/resources/view/timecontrol/basic.js deleted file mode 100644 index fbc46b9e..00000000 --- a/luci-app-timecontrol/htdocs/luci-static/resources/view/timecontrol/basic.js +++ /dev/null @@ -1,335 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -/* - - * Copyright (C) 2022-2026 sirpdboy - */ -'use strict'; -'require view'; -'require fs'; -'require ui'; -'require uci'; -'require form'; -'require poll'; -'require rpc'; -'require network'; - -function checkTimeControlProcess() { - return fs.exec('/bin/ps', ['w']).then(function(res) { - if (res.code !== 0) { - return { running: false, pid: null }; - } - - var lines = res.stdout.split('\n'); - var running = false; - var pid = null; - - for (var i = 0; i < lines.length; i++) { - var line = lines[i]; - if (line.includes('timecontrolctrl')) { - running = true; - // 提取PID - var match = line.match(/^\s*(\d+)/); - if (match) { - pid = match[1]; - } - break; - } - } - - return { running: running, pid: null }; - }).catch(function() { - return { running: false, pid: null }; - }); -} - -function renderServiceStatus(isRunning, pid) { - var statusText = isRunning ? _('RUNNING') : _('NOT RUNNING'); - var color = isRunning ? 'green' : 'red'; - var icon = isRunning ? '✓' : '✗'; - - var statusHtml = String.format( - '%s %s %s', - color, icon, _('TimeControl Service'), statusText - ); - - if (isRunning && pid) { - statusHtml += ' (PID: ' + pid + ')'; - } - - return statusHtml; -} - -function getHostList() { - return L.resolveDefault(network.getHostHints(), []) - .then(function(hosts) { - var hostList = []; - if (hosts && hosts.length > 0) { - hosts.forEach(function(host) { - if (host.ipv4 && host.mac) { - hostList.push({ - ipv4: host.ipv4, - mac: host.mac, - name: host.name || '', - ipv6: host.ipv6 || '' - }); - } - }); - } - return hostList; - }) - .catch(function() { - return []; - }); -} - -var cbiRichListValue = form.ListValue.extend({ - renderWidget: function(section_id, option_index, cfgvalue) { - var choices = this.transformChoices(); - var widget = new ui.Dropdown((cfgvalue != null) ? cfgvalue : this.default, choices, { - id: this.cbid(section_id), - sort: this.keylist, - optional: true, - select_placeholder: this.select_placeholder || this.placeholder, - custom_placeholder: this.custom_placeholder || this.placeholder, - validate: L.bind(this.validate, this, section_id), - disabled: (this.readonly != null) ? this.readonly : this.map.readonly - }); - - return widget.render(); - }, - - value: function(value, title, description) { - if (description) { - form.ListValue.prototype.value.call(this, value, E([], [ - E('span', { 'class': 'hide-open' }, [title]), - E('div', { 'class': 'hide-close', 'style': 'min-width:25vw' }, [ - E('strong', [title]), - E('br'), - E('span', { 'style': 'white-space:normal' }, description) - ]) - ])); - } else { - form.ListValue.prototype.value.call(this, value, title); - } - } -}); - -return view.extend({ - load: function() { - return Promise.all([ - uci.load('timecontrol'), - network.getHostHints() - ]); - }, - - render: function(data) { - var m, s, o; - let hosts = data[1]?.hosts; - - m = new form.Map('timecontrol', _('Internet Time Control'), - _('Users can limit their internet usage time through MAC and IP, with available IP ranges such as 192.168.110.00 to 192.168.10.200') + '
' + - _('黑名单模式时间控制方式:') + '
' + - _('1. 时间段控制: 指定的机器在设定时间段内可以上网,其他时间不能上网') + '
' + - _('2. 允许上机时长: 指定的机器上线后可以上网指定时长,超过时长后不能上网') + '
' + - _('3. 组合控制: 在时间段内+时长限制(在允许的时间段内限制上网时长)') + '
' + - _('Suggested feedback:') + ' GitHub @timecontrol'); - - s = m.section(form.TypedSection); - s.anonymous = true; - s.render = function() { - var statusView = E('p', { id: 'service_status' }, - ' ' + _('Checking service status...')); - - checkTimeControlProcess() - .then(function(res) { - var status = renderServiceStatus(res.running, res.pid); - statusView.innerHTML = status; - }) - .catch(function(err) { - statusView.innerHTML = '⚠ ' + - _('Status check failed') + ''; - console.error('Status check error:', err); - }); - - poll.add(function() { - return checkTimeControlProcess() - .then(function(res) { - var status = renderServiceStatus(res.running, res.pid); - statusView.innerHTML = status; - }) - .catch(function(err) { - statusView.innerHTML = '⚠ ' + - _('Status check failed') + ''; - console.error('Status check error:', err); - }); - }, 5); - - poll.start(); - return E('div', { class: 'cbi-section', id: 'status_bar' }, [ - statusView, - E('div', { 'style': 'text-align: right; font-style: italic;' }, [ - E('span', {}, [ - _('© github '), - E('a', { - 'href': 'https://github.com/sirpdboy', - 'target': '_blank', - 'style': 'text-decoration: none;' - }, 'by sirpdboy') - ]) - ]) - ]); - }; - s = m.section(form.TypedSection, 'timecontrol'); - s.anonymous = true; - s.addremove = false; - - o = s.option(cbiRichListValue, 'list_type', _('Control Mode'), - _('blacklist: Block the networking of the target address, whitelist: Only allow networking for the target address and block all other addresses.')); - o.rmempty = false; - o.value('blacklist', _('Blacklist')); - // o.value('whitelist', _('Whitelist')); - o.default = 'blacklist'; - - o = s.option(cbiRichListValue, 'chain', _('Control Intensity'), - _('Pay attention to strong control: machines under control will not be able to connect to the software router backend!')); - o.value('forward', _('Ordinary forward control')); - o.value('input', _('Strong input control')); - o.default = 'forward'; - o.rmempty = false; - - var s = m.section(form.TableSection, 'device', _('Device Rules')); - s.addremove = true; - s.anonymous = true; - s.sortable = false; - - o = s.option(form.Value, 'comment', _('Comment')); - o.optional = true; - o.placeholder = _('Description'); - - o = s.option(form.Flag, 'enable', _('Enabled')); - o.rmempty = false; - o.default = '1'; - - o = s.option(form.Value, 'mac', _('IP/MAC Address')); - o.rmempty = false; - if (hosts) { - var hostOptions = {}; - - Object.keys(hosts).forEach(function(mac) { - var host = hosts[mac]; - var name = host.name || _(' '); - var ips = L.toArray(host.ipaddrs || host.ipv4 || []); - - if (ips.length > 0) { - ips.forEach(function(ip) { - var macDisplay = 'MAC: %s (%s - %s)'.format(mac,ip, name); - hostOptions['mac:' + mac] = macDisplay; - var ipDisplay = 'IP: %s (%s - %s)'.format(ip, mac, name); - hostOptions['ip:' + ip] = ipDisplay; - }); - } - }); - var sortedKeys = Object.keys(hostOptions).sort(function(a, b) { - return hostOptions[a].localeCompare(hostOptions[b]); - }); - - sortedKeys.forEach(function(key) { - if (key.startsWith('ip:')) { - o.value(key.substring(3), hostOptions[key]); - } - }); - - sortedKeys.forEach(function(key) { - if (key.startsWith('mac:')) { - o.value(key.substring(4), hostOptions[key]); - } - }); - } - - // 时间控制方式选择 - o = s.option(cbiRichListValue, 'time_mode', _('Time Control Mode')); - o.value('period', _('Time Period Control (allow in period)')); - o.value('duration', _('Allow Duration Control (allow limited time)')); - o.value('combined', _('Combined Control (allow in period + limit duration)')); - o.default = 'period'; - o.rmempty = false; - o.onchange = function(ev, mode) { - var row = this.map.findElement('id', this.cbid(this.section_id)); - if (row) { - // 显示/隐藏相关字段 - var startTime = row.querySelector('[data-field="timestart"]'); - var endTime = row.querySelector('[data-field="timeend"]'); - var duration = row.querySelector('[data-field="duration"]'); - var useDuration = row.querySelector('[data-field="use_duration"]'); - var resetCycle = row.querySelector('[data-field="reset_cycle"]'); - - if (startTime) startTime.parentElement.style.display = - (mode === 'period' || mode === 'combined') ? '' : 'none'; - if (endTime) endTime.parentElement.style.display = - (mode === 'period' || mode === 'combined') ? '' : 'none'; - if (duration) duration.parentElement.style.display = - (mode === 'duration' || mode === 'combined') ? '' : 'none'; - if (useDuration) useDuration.parentElement.style.display = - (mode === 'combined') ? '' : 'none'; - if (resetCycle) resetCycle.parentElement.style.display = - (mode === 'duration' || mode === 'combined') ? '' : 'none'; - } - }; - - // 时间段控制字段 - o = s.option(form.Value, 'timestart', _('Allow Start Time')); - o.placeholder = '00:00'; - o.default = '00:00'; - o.depends({ 'time_mode': 'period', '!contains': true }); - o.depends({ 'time_mode': 'combined', '!contains': true }); - - o = s.option(form.Value, 'timeend', _('Allow End Time')); - o.placeholder = '00:00'; - o.default = '00:00'; - o.depends({ 'time_mode': 'period', '!contains': true }); - o.depends({ 'time_mode': 'combined', '!contains': true }); - - // 持续时间控制字段 - o = s.option(form.Value, 'duration', _('Allowed Duration (minutes)')); - o.placeholder = '60'; - o.default = '60'; - o.datatype = 'min(1)'; - o.depends({ 'time_mode': 'duration', '!contains': true }); - o.depends({ 'time_mode': 'combined', '!contains': true }); - o.description = _('设备上线后允许上网的分钟数,超过后将被禁止上网'); - - // 重置周期 - o = s.option(cbiRichListValue, 'reset_cycle', _('Reset Cycle')); - o.value('daily', _('Daily Reset')); - o.value('weekly', _('Weekly Reset')); - o.value('monthly', _('Monthly Reset')); - o.value('never', _('Never Reset (until manual reset)')); - o.default = 'daily'; - o.depends({ 'time_mode': 'duration', '!contains': true }); - o.depends({ 'time_mode': 'combined', '!contains': true }); - o.description = _('时长重置周期'); - - // 组合控制:是否在时间段内启用时长限制 - o = s.option(form.Flag, 'use_duration', _('Enable Duration Limit in Period')); - o.default = '0'; - o.depends({ 'time_mode': 'combined', '!contains': true }); - o.description = _('在允许的时间段内限制上网时长'); - - o = s.option(form.Value, 'week', _('Week Day (1~7)')); - o.value('0', _('Everyday')); - o.value('1', _('Monday')); - o.value('2', _('Tuesday')); - o.value('3', _('Wednesday')); - o.value('4', _('Thursday')); - o.value('5', _('Friday')); - o.value('6', _('Saturday')); - o.value('7', _('Sunday')); - o.value('1,2,3,4,5', _('Workday')); - o.value('6,7', _('Rest Day')); - o.default = '0'; - o.rmempty = false; - o.description = _('允许上网的星期'); - - return m.render(); - } -}); \ No newline at end of file diff --git a/luci-app-timecontrol/htdocs/luci-static/resources/view/timecontrol/log.js b/luci-app-timecontrol/htdocs/luci-static/resources/view/timecontrol/log.js deleted file mode 100644 index bb1c13ae..00000000 --- a/luci-app-timecontrol/htdocs/luci-static/resources/view/timecontrol/log.js +++ /dev/null @@ -1,238 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -/* - - * Copyright (C) 2022-2026 sirpdboy - */ -'use strict'; -'require dom'; -'require fs'; -'require poll'; -'require uci'; -'require view'; -'require form'; - -return view.extend({ - render: function () { - var css = ` - #log_textarea pre { - padding: 10px; /* 内边距 */ - border-bottom: 1px solid #ddd; /* 边框颜色 */ - font-size: small; - line-height: 1.3; /* 行高 */ - white-space: pre-wrap; - word-wrap: break-word; - overflow-y: auto; - } - .cbi-section small { - margin-left: 1rem; - font-size: small; - } - .log-container { - display: flex; - flex-direction: column; - max-height: 1200px; - overflow-y: auto; - border-radius: 3px; - margin-top: 10px; - padding: 5px; - } - .log-line { - padding: 3px 0; - font-family: monospace; - font-size: 12px; - line-height: 1.4; - } - .log-line:last-child { - border-bottom: none; - } - .log-timestamp { - margin-right: 10px; - } - `; - - var log_container = E('div', { 'class': 'log-container', 'id': 'log_container' }, - E('img', { - 'src': L.resource(['icons/loading.gif']), - 'alt': _('Loading...'), - 'style': 'vertical-align:middle' - }, _('Collecting data ...')) - ); - - var log_path = '/var/log/timecontrol.log'; - var lastLogContent = ''; - var lastScrollTop = 0; - var isScrolledToTop = true; - - // 解析日志行的时间戳,用于排序 - function parseLogTimestamp(logLine) { - // 假设日志格式为: [2024-01-01 12:00:00] INFO: some message - var timestampMatch = logLine.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/); - if (timestampMatch) { - return new Date(timestampMatch[1]).getTime(); - } - return Date.now(); - } - - function reverseLogLines(logContent) { - if (!logContent || logContent.trim() === '') { - return logContent; - } - - var lines = logContent.split('\n'); - - lines = lines.filter(function(line) { - return line.trim() !== ''; - }); - - lines.sort(function(a, b) { - var timeA = parseLogTimestamp(a); - var timeB = parseLogTimestamp(b); - return timeB - timeA; // 降序排列 - }); - - return lines.join('\n'); - } - - function formatLogLines(logContent, isNewContent) { - if (!logContent || logContent.trim() === '') { - return E('div', { 'class': 'log-line' }, _('Log is clean.')); - } - - var lines = logContent.split('\n'); - var formattedLines = []; - - for (var i = 0; i < lines.length; i++) { - var line = lines[i].trim(); - if (line === '') continue; - - var timestampMatch = line.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/); - var timestampSpan = null; - var messageSpan = null; - var lineClass = 'log-line'; - - if (timestampMatch) { - timestampSpan = E('span', { - 'class': 'log-timestamp', - 'title': timestampMatch[1] - }, timestampMatch[0] + ' '); - messageSpan = E('span', {}, line.substring(timestampMatch[0].length + 1)); - } else { - messageSpan = E('span', {}, line); - } - - var lineDiv = E('div', { 'class': lineClass }, [ - timestampSpan, - messageSpan - ].filter(function(el) { return el !== null; })); - - formattedLines.push(lineDiv); - } - - return E('div', {}, formattedLines); - } - - var clear_log_button = E('div', {}, [ - E('button', { - 'class': 'cbi-button cbi-button-remove', - 'click': function (ev) { - ev.preventDefault(); - var button = ev.target; - button.disabled = true; - button.textContent = _('Clear Logs...'); - fs.exec_direct('/usr/libexec/timecontrol-call', ['clear_log']) - .then(function () { - button.textContent = _('Logs cleared successfully!'); - button.disabled = false; - button.textContent = _('Clear Logs'); - // 立即刷新日志显示框 - var logContent = _('Log is clean.'); - lastLogContent = logContent; - dom.content(log_container, formatLogLines(logContent, false)); - isScrolledToTop = true; // 清空日志后,保持在顶部 - }) - .catch(function () { - button.textContent = _('Failed to clear log.'); - button.disabled = false; - button.textContent = _('Clear Logs'); - }); - } - }, _('Clear Logs')) - ]); - log_container.addEventListener('scroll', function() { - lastScrollTop = this.scrollTop; - isScrolledToTop = this.scrollTop <= 1; - }); - - poll.add(L.bind(function () { - return fs.read_direct(log_path, 'text') - .then(function (res) { - var logContent = res.trim(); - if (logContent === '') { - logContent = _('Log is clean.'); - } - - // 检查内容是否有变化 - if (logContent !== lastLogContent) { - var isNewContent = lastLogContent !== '' && lastLogContent !== _('Log is clean.'); - var reversedLog = reverseLogLines(logContent); - // 格式化为HTML - var formattedLog = formatLogLines(reversedLog, isNewContent); - - var prevScrollHeight = log_container.scrollHeight; - var prevScrollTop = log_container.scrollTop; - - dom.content(log_container, formattedLog); - lastLogContent = logContent; - - if (isScrolledToTop || isNewContent) { - log_container.scrollTop = 0; - } else { - var newScrollHeight = log_container.scrollHeight; - var heightDiff = newScrollHeight - prevScrollHeight; - log_container.scrollTop = prevScrollTop + heightDiff; - } - } - }).catch(function (err) { - var logContent; - if (err.toString().includes('NotFoundError')) { - logContent = _('Log file does not exist.'); - } else { - logContent = _('Unknown error: %s').format(err); - } - - if (logContent !== lastLogContent) { - dom.content(log_container, formatLogLines(logContent, false)); - lastLogContent = logContent; - } - }); - })); - - // 启动轮询 - poll.start(); - return E('div', { 'class': 'cbi-map' }, [ - E('style', [css]), - E('div', { 'class': 'cbi-section' }, [ - clear_log_button, - log_container, - E('small', {}, _('Refresh every 5 seconds.').format(L.env.pollinterval)), - E('div', { 'class': 'cbi-section-actions cbi-section-actions-right' }) - ]), - E('div', { 'style': 'text-align: right; font-style: italic;' }, [ - E('span', {}, [ - _('© github '), - E('a', { - 'href': 'https://github.com/sirpdboy', - 'target': '_blank', - 'style': 'text-decoration: none;' - }, 'by sirpdboy') - ]) - ]) - - - ]); - }, - - handleSaveApply: null, - handleSave: null, - handleReset: null -}); diff --git a/luci-app-timecontrol/luasrc/controller/timecontrol.lua b/luci-app-timecontrol/luasrc/controller/timecontrol.lua new file mode 100644 index 00000000..81b08119 --- /dev/null +++ b/luci-app-timecontrol/luasrc/controller/timecontrol.lua @@ -0,0 +1,19 @@ +module("luci.controller.timecontrol", package.seeall) + +function index() + if not nixio.fs.access("/etc/config/timecontrol") then return end + + entry({"admin", "control"}, firstchild(), "Control", 44).dependent = false + local page = entry({"admin", "control", "timecontrol"}, cbi("timecontrol"), _("Internet Time Control")) + page.order = 10 + page.dependent = true + page.acl_depends = { "luci-app-timecontrol" } + entry({"admin", "control", "timecontrol", "status"}, call("status")).leaf = true +end + +function status() + local e = {} + e.status = luci.sys.call("/etc/init.d/timecontrol status >/dev/null 2>&1") == 0 + luci.http.prepare_content("application/json") + luci.http.write_json(e) +end diff --git a/luci-app-timecontrol/luasrc/model/cbi/timecontrol.lua b/luci-app-timecontrol/luasrc/model/cbi/timecontrol.lua new file mode 100644 index 00000000..8760cd8b --- /dev/null +++ b/luci-app-timecontrol/luasrc/model/cbi/timecontrol.lua @@ -0,0 +1,62 @@ +local o = require "luci.sys" +local a, t, e +a = Map("timecontrol", translate("Internet Time Control")) +a.template = "timecontrol/index" + +t = a:section(TypedSection, "basic") +t.anonymous = true + +e = t:option(DummyValue, "timecontrol_status", translate("Status")) +e.template = "timecontrol/timecontrol" +e.value = translate("Collecting data...") + +e = t:option(Flag, "enable", translate("Enabled")) +e.rmempty = false + +t = a:section(TypedSection, "macbind", translate("Client Settings")) +t.template = "cbi/tblsection" +t.anonymous = true +t.addremove = true + +e = t:option(Flag, "enable", translate("Enabled")) +e.rmempty = false + +e = t:option(Value, "macaddr", "MAC") +e.rmempty = true +o.net.mac_hints(function(t, a) e:value(t, "%s (%s)" % {t, a}) end) + +e = t:option(Value, "timeon", translate("No Internet start time")) +e.default = "00:00" +e.optional = false + +e = t:option(Value, "timeoff", translate("No Internet end time")) +e.default = "23:59" +e.optional = false + +e = t:option(Flag, "z1", translate("Monday")) +e.rmempty = true + +e = t:option(Flag, "z2", translate("Tuesday")) +e.rmempty = true + +e = t:option(Flag, "z3", translate("Wednesday")) +e.rmempty = true + +e = t:option(Flag, "z4", translate("Thursday")) +e.rmempty = true + +e = t:option(Flag, "z5", translate("Friday")) +e.rmempty = true + +e = t:option(Flag, "z6", translate("Saturday")) +e.rmempty = true + +e = t:option(Flag, "z7", translate("Sunday")) +e.rmempty = true + +a.apply_on_parse = true +a.on_after_apply = function(self) + luci.sys.call("/etc/init.d/timecontrol reload >/dev/null 2>&1") +end + +return a diff --git a/luci-app-timecontrol/luasrc/view/timecontrol/index.htm b/luci-app-timecontrol/luasrc/view/timecontrol/index.htm new file mode 100644 index 00000000..3ff7a845 --- /dev/null +++ b/luci-app-timecontrol/luasrc/view/timecontrol/index.htm @@ -0,0 +1,12 @@ +<% include("cbi/map") %> + \ No newline at end of file diff --git a/luci-app-timecontrol/luasrc/view/timecontrol/timecontrol.htm b/luci-app-timecontrol/luasrc/view/timecontrol/timecontrol.htm new file mode 100644 index 00000000..e4d63fb5 --- /dev/null +++ b/luci-app-timecontrol/luasrc/view/timecontrol/timecontrol.htm @@ -0,0 +1,3 @@ +<%+cbi/valueheader%> +<%=pcdata(self:cfgvalue(section) or self.default or "")%> +<%+cbi/valuefooter%> \ No newline at end of file diff --git a/luci-app-timecontrol/po/zh_Hans/timecontrol.po b/luci-app-timecontrol/po/zh_Hans/timecontrol.po index 79e81960..fc68f620 100644 --- a/luci-app-timecontrol/po/zh_Hans/timecontrol.po +++ b/luci-app-timecontrol/po/zh_Hans/timecontrol.po @@ -1,123 +1,41 @@ -msgid "" -msgstr "" -"Content-Type: text/plain; charset=UTF-8\n" - msgid "Control" msgstr "管控" -msgid "Time Control" -msgstr "时间控制" - msgid "Internet Time Control" msgstr "上网时间控制" -msgid "Users can limit their internet usage time through MAC and IP, with available IP ranges such as 192.168.110.00 to 192.168.10.200" -msgstr "用户可以通过MAC和IP限制其上网时间,可用IP范围如192.168.110.00至192.168.10.200" - -msgid "Suggested feedback:" -msgstr "问题反馈:" - -msgid "Checking service status..." -msgstr "正在检查服务状态..." - -msgid "Status check failed" -msgstr "状态检查失败" - -msgid "RUNNING" -msgstr "运行中" - -msgid "NOT RUNNING" -msgstr "未运行" - -msgid "TimeControl Service" -msgstr "时间控制服务" - -msgid "Control Mode" -msgstr "控制模式" - -msgid "blacklist: Block the networking of the target address, whitelist: Only allow networking for the target address and block all other addresses." -msgstr "黑名单:阻断目标地址的上网;白名单:仅允许目标地址上网,阻断其他所有地址。" - -msgid "Blacklist" -msgstr "黑名单" - -msgid "Whitelist" -msgstr "白名单" - -msgid "Control Intensity" -msgstr "控制强度" - -msgid "Pay attention to strong control: machines under control will not be able to connect to the software router backend!" -msgstr "注意强控制:被控制的机器将无法连接软件路由器后台!" - -msgid "Ordinary forward control" -msgstr "普通管制" - -msgid "Strong input control" -msgstr "强力管制" - -msgid "Device Rules" -msgstr "设备规则" - -msgid "Comment" -msgstr "备注" - -msgid "Description" -msgstr "描述" +msgid "Status" +msgstr "状态" msgid "Enabled" msgstr "启用" -msgid "IP/MAC Address" -msgstr "IP/MAC地址" +msgid "Client Settings" +msgstr "客户端设置" -msgid "192.168.10.100 or 00:11:22:33:44:55" -msgstr "192.168.10.100 或 00:11:22:33:44:55" +msgid "No Internet start time" +msgstr "禁止上网开始时间" -msgid "-- Please select or enter manually --" -msgstr "-- 请选择或手动输入 --" - -msgid "Start Control Time" -msgstr "控制开始时间" - -msgid "00:00" -msgstr "00:00" - -msgid "Stop Control Time" -msgstr "控制结束时间" - -msgid "Week Day (1~7)" -msgstr "星期(1~7)" - -msgid "Everyday" -msgstr "每天" +msgid "No Internet end time" +msgstr "取消禁止上网时间" msgid "Monday" -msgstr "星期一" +msgstr "周一" msgid "Tuesday" -msgstr "星期二" +msgstr "周二" msgid "Wednesday" -msgstr "星期三" +msgstr "周三" msgid "Thursday" -msgstr "星期四" +msgstr "周四" msgid "Friday" -msgstr "星期五" +msgstr "周五" msgid "Saturday" -msgstr "星期六" +msgstr "周六" msgid "Sunday" -msgstr "星期日" - -msgid "Workday" -msgstr "工作日" - -msgid "Rest Day" -msgstr "休息日" - -msgid "© github " -msgstr "© 作者 " +msgstr "周日" diff --git a/luci-app-timecontrol/root/etc/config/timecontrol b/luci-app-timecontrol/root/etc/config/timecontrol index a385d9fd..0c4c4f6b 100644 --- a/luci-app-timecontrol/root/etc/config/timecontrol +++ b/luci-app-timecontrol/root/etc/config/timecontrol @@ -1,21 +1,3 @@ -config timecontrol - option enabled '0' - option control_mode 'blacklist' - option list_type 'blacklist' - option chain 'input' - -config device - option timestart '00:00' - option week '0' - option timeend '23:55' - option mac '' +config basic option enable '0' - -config device - option mac '192.168.10.10/24' - option timestart '00:00' - option timeend '00:00' - option week '0' - option enable '0' - diff --git a/luci-app-timecontrol/root/etc/init.d/timecontrol b/luci-app-timecontrol/root/etc/init.d/timecontrol index 3b1da46a..b408796e 100755 --- a/luci-app-timecontrol/root/etc/init.d/timecontrol +++ b/luci-app-timecontrol/root/etc/init.d/timecontrol @@ -1,51 +1,230 @@ #!/bin/sh /etc/rc.common -# -# Copyright (C) 2022-2026 sirpdboy herboy2008@gmail.com -# - START=99 -USE_PROCD=1 +STOP=10 -NAME=timecontrol -LOCK="/var/lock/$NAME.lock" +EXTRA_COMMANDS="status" +EXTRA_HELP=" status Check if timecontrol rules are active\n" -start_instance() { - procd_open_instance - procd_set_param command /usr/bin/timecontrolctrl - procd_set_param respawn - procd_set_param stderr 1 - procd_close_instance +. /lib/functions.sh + +TABLE="timecontrol" +CHAIN="TIMECONTROL" + +firewall_backend() { + if command -v fw4 >/dev/null 2>&1 && command -v nft >/dev/null 2>&1; then + echo nft + else + echo iptables + fi } -_timecontrol_start() { - if [ "$(grep -c 'option enable .1.' /etc/config/$NAME 2>/dev/null)" -gt "0" ]; then - touch $LOCK - timecontrol start - sleep 2 - start_instance - else - stop_service - fi +have_ip6tables() { + command -v ip6tables >/dev/null 2>&1 } -start_service(){ - [ -f $LOCK ] && exit - _timecontrol_start - rm -f $LOCK +valid_mac() { + printf '%s\n' "$1" | grep -Eq '^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$' } -service_triggers() { - procd_add_reload_trigger 'timecontrol' +valid_time() { + printf '%s\n' "$1" | grep -Eq '^([01][0-9]|2[0-3]):[0-5][0-9]$' } -stop_service(){ - kill -9 $(busybox ps -w | grep 'timecontrolctrl' | grep -v 'grep' | awk '{print $1}') >/dev/null 2>&1 - killall timecontrolctrl 2>/dev/null - rm -f $LOCK 2>/dev/null - timecontrol stop +add_nft_range() { + local macaddr="$1" + local timeon="$2" + local timeoff="$3" + local weekdays="$4" + + nft -f - <<-EOF + add rule inet $TABLE forward ether saddr $macaddr meta day { $weekdays } meta hour "$timeon"-"$timeoff" counter drop + EOF } -reload_service() { - restart +add_nft_rule() { + local macaddr="$1" + local timeon="$2" + local timeoff="$3" + local weekdays="$4" + local weekdays_next="$5" + + if [ "$timeon" \< "$timeoff" ] || [ "$timeon" = "$timeoff" ]; then + add_nft_range "$macaddr" "$timeon" "$timeoff" "$weekdays" + else + # Range spans midnight: block until 23:59:59 on the selected + # days, then from 00:00 until timeoff on the following days. + add_nft_range "$macaddr" "$timeon" "23:59:59" "$weekdays" + add_nft_range "$macaddr" "00:00" "$timeoff" "$weekdays_next" + fi +} + +add_ipt_range() { + local cmd="$1" + local macaddr="$2" + local timeon="$3" + local timeoff="$4" + local weekdays="$5" + + "$cmd" -w -t filter -A "$CHAIN" -m mac --mac-source "$macaddr" \ + -m time --kerneltz --timestart "$timeon" --timestop "$timeoff" \ + --weekdays "$weekdays" -j DROP +} + +add_ipt_rule() { + local macaddr="$1" + local timeon="$2" + local timeoff="$3" + local weekdays="$4" + local weekdays_next="$5" + local cmd + + # Mirror every rule into ip6tables as well, otherwise IPv6 traffic + # would bypass the time control completely. + for cmd in iptables ip6tables; do + command -v "$cmd" >/dev/null 2>&1 || continue + if [ "$timeon" \< "$timeoff" ] || [ "$timeon" = "$timeoff" ]; then + add_ipt_range "$cmd" "$macaddr" "$timeon" "$timeoff" "$weekdays" + else + # Range spans midnight: block until 23:59:59 on the + # selected days, then from 00:00 until timeoff on the + # following days. + add_ipt_range "$cmd" "$macaddr" "$timeon" "23:59:59" "$weekdays" + add_ipt_range "$cmd" "$macaddr" "00:00" "$timeoff" "$weekdays_next" + fi + done +} + +load_rule() { + local section="$1" + local enabled macaddr timeon timeoff + local z1 z2 z3 z4 z5 z6 z7 + local ipt_days nft_days ipt_days_next nft_days_next + + config_get_bool enabled "$section" enable 0 + [ "$enabled" -eq 1 ] || return 0 + + config_get macaddr "$section" macaddr + config_get timeon "$section" timeon + config_get timeoff "$section" timeoff + valid_mac "$macaddr" && valid_time "$timeon" && valid_time "$timeoff" || { + logger -t timecontrol "Ignoring invalid rule in section $section" + return 0 + } + + config_get_bool z1 "$section" z1 0 + config_get_bool z2 "$section" z2 0 + config_get_bool z3 "$section" z3 0 + config_get_bool z4 "$section" z4 0 + config_get_bool z5 "$section" z5 0 + config_get_bool z6 "$section" z6 0 + config_get_bool z7 "$section" z7 0 + + # The *_next lists hold each selected weekday shifted by one day; + # they apply to the after-midnight part of ranges spanning midnight. + [ "$z1" -eq 1 ] && { append ipt_days Mon ,; append ipt_days_next Tue ,; append nft_days monday ,; append nft_days_next tuesday ,; } + [ "$z2" -eq 1 ] && { append ipt_days Tue ,; append ipt_days_next Wed ,; append nft_days tuesday ,; append nft_days_next wednesday ,; } + [ "$z3" -eq 1 ] && { append ipt_days Wed ,; append ipt_days_next Thu ,; append nft_days wednesday ,; append nft_days_next thursday ,; } + [ "$z4" -eq 1 ] && { append ipt_days Thu ,; append ipt_days_next Fri ,; append nft_days thursday ,; append nft_days_next friday ,; } + [ "$z5" -eq 1 ] && { append ipt_days Fri ,; append ipt_days_next Sat ,; append nft_days friday ,; append nft_days_next saturday ,; } + [ "$z6" -eq 1 ] && { append ipt_days Sat ,; append ipt_days_next Sun ,; append nft_days saturday ,; append nft_days_next sunday ,; } + [ "$z7" -eq 1 ] && { append ipt_days Sun ,; append ipt_days_next Mon ,; append nft_days sunday ,; append nft_days_next monday ,; } + [ -n "$ipt_days" ] || return 0 + + if [ "$BACKEND" = nft ]; then + add_nft_rule "$macaddr" "$timeon" "$timeoff" "$nft_days" "$nft_days_next" + else + add_ipt_rule "$macaddr" "$timeon" "$timeoff" "$ipt_days" "$ipt_days_next" + fi +} + +load_basic() { + config_get_bool ENABLED "$1" enable 0 +} + +start_nft() { + nft -f - <<-EOF + table inet $TABLE { + chain forward { + type filter hook forward priority -1; policy accept; + } + } + EOF + + # Flush fw4's flowtable so that connections already on the fast path + # (which bypasses this forward hook) are forced back to the slow path + # where our DROP rules can reach them. Non-blocked devices will + # re-offload within seconds; the disruption is minimal. + nft flush flowtable inet fw4 flowtable_ft 2>/dev/null +} + +start_iptables() { + iptables -w -t filter -N "$CHAIN" || return 1 + iptables -w -t filter -I FORWARD 1 -j "$CHAIN" + if have_ip6tables; then + ip6tables -w -t filter -N "$CHAIN" || return 1 + ip6tables -w -t filter -I FORWARD 1 -j "$CHAIN" + else + logger -t timecontrol "ip6tables not found; IPv6 traffic will not be controlled" + fi +} + +stop_nft() { + command -v nft >/dev/null 2>&1 && nft delete table inet "$TABLE" 2>/dev/null + return 0 +} + +stop_ipt_family() { + local cmd="$1" + + command -v "$cmd" >/dev/null 2>&1 || return 0 + while "$cmd" -w -t filter -C FORWARD -j "$CHAIN" 2>/dev/null; do + "$cmd" -w -t filter -D FORWARD -j "$CHAIN" 2>/dev/null || break + done + "$cmd" -w -t filter -F "$CHAIN" 2>/dev/null + "$cmd" -w -t filter -X "$CHAIN" 2>/dev/null +} + +stop_iptables() { + stop_ipt_family iptables + stop_ipt_family ip6tables +} + +start() { + config_load timecontrol + ENABLED=0 + config_foreach load_basic basic + [ "$ENABLED" -eq 1 ] || return 0 + + stop_nft + stop_iptables + BACKEND="$(firewall_backend)" + mkdir -p /var/etc + printf '%s\n' "/etc/init.d/timecontrol reload" > /var/etc/timecontrol.include + + if [ "$BACKEND" = nft ]; then + start_nft || return 1 + else + start_iptables || return 1 + fi + + config_foreach load_rule macbind +} + +stop() { + stop_nft + stop_iptables +} + +reload() { + stop + start +} + +status() { + if [ "$(firewall_backend)" = nft ]; then + nft list table inet "$TABLE" >/dev/null 2>&1 + else + iptables -w -t filter -S "$CHAIN" >/dev/null 2>&1 + fi } diff --git a/luci-app-timecontrol/root/etc/uci-defaults/luci-app-timecontrol b/luci-app-timecontrol/root/etc/uci-defaults/luci-app-timecontrol new file mode 100644 index 00000000..292b1e8a --- /dev/null +++ b/luci-app-timecontrol/root/etc/uci-defaults/luci-app-timecontrol @@ -0,0 +1,38 @@ +#!/bin/sh +[ ! -f "/usr/share/ucitrack/luci-app-timecontrol.json" ] && { + cat > /usr/share/ucitrack/luci-app-timecontrol.json << EEOF +{ + "config": "timecontrol", + "init": "timecontrol" +} +EEOF +} + +uci -q batch <<-EOF >/dev/null + delete firewall.timecontrol +EOF + +if ! command -v fw4 >/dev/null 2>&1; then + uci -q batch <<-EOF >/dev/null + set firewall.timecontrol=include + set firewall.timecontrol.type=script + set firewall.timecontrol.path=/var/etc/timecontrol.include + set firewall.timecontrol.reload=1 + EOF + install -d /var/etc + printf '%s\n' "/etc/init.d/timecontrol reload" > /var/etc/timecontrol.include +fi + +uci -q commit firewall + +[ -f "/etc/config/ucitrack" ] && { + uci -q batch <<-EOF >/dev/null + delete ucitrack.@timecontrol[-1] + add ucitrack timecontrol + set ucitrack.@timecontrol[-1].init=timecontrol + commit ucitrack + EOF +} + +rm -rf /tmp/luci-*cache +exit 0 diff --git a/luci-app-timecontrol/root/etc/uci-defaults/luci-timecontrol b/luci-app-timecontrol/root/etc/uci-defaults/luci-timecontrol deleted file mode 100644 index e814dc05..00000000 --- a/luci-app-timecontrol/root/etc/uci-defaults/luci-timecontrol +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -[ ! -f "/usr/share/ucitrack/luci-app-timecontrol.json" ] && { - cat > /usr/share/ucitrack/luci-app-timecontrol.json << EEOF -{ - "config": "timecontrol", - "init": "timecontrol" -} -EEOF -} -chmod +x /etc/init.d/timecontrol /usr/bin/timecontrol* /usr/libexec/timecontrol-call -uci -q batch <<-EOF >/dev/null - delete ucitrack.@timecontrol[-1] - add ucitrack timecontrol - set ucitrack.@timecontrol[-1].init=timecontrol - commit ucitrack -EOF - -[ -s /etc/config/timecontrol ] || echo "config timecontrol" > /etc/config/timecontrol - -/etc/init.d/rpcd restart -rm -f /tmp/luci-indexcache -exit 0 diff --git a/luci-app-timecontrol/root/usr/bin/timecontrol b/luci-app-timecontrol/root/usr/bin/timecontrol deleted file mode 100755 index ceb7a98b..00000000 --- a/luci-app-timecontrol/root/usr/bin/timecontrol +++ /dev/null @@ -1,600 +0,0 @@ -#!/bin/bash - -# Copyright (C) 2006 OpenWrt.org -# Copyright 2022-2026 sirpdboy - -crrun=$1 -crid=$2 -NAME=timecontrol -DEBUG=1 # 开启调试 - -config_t_get() { - local index=${3:-0} - local ret=$(uci -q get "${NAME}.@${1}[${index}].${2}") - echo "${ret:-$4}" -} - -LOG_FILE="/var/log/timecontrol.log" -IDLIST="/var/$NAME.idlist" - -bin_nft=$(which nft 2>/dev/null) -bin_iptables=$(which iptables 2>/dev/null) -bin_ip6tables=$(which ip6tables 2>/dev/null) -bin_conntrack=$(which conntrack 2>/dev/null) - -nftables_ver=0 -iptables_ver=0 - -# 获取配置 -chain=$(config_t_get timecontrol chain 0 "forward") -list_type=$(config_t_get timecontrol list_type 0 "blacklist") - -if [ "$chain" = "input" ]; then - StrongCHAIN=1 -else - StrongCHAIN=0 -fi - -dbg() { - if [ "$DEBUG" -eq 1 ]; then - local d="$(date '+%Y-%m-%d %H:%M:%S')" - echo "[$d] FW-DEBUG: $@" >> "$LOG_FILE" - echo "FW-DEBUG: $@" - fi -} - -info() { - local d="$(date '+%Y-%m-%d %H:%M:%S')" - echo "[$d] FW-INFO: $@" >> "$LOG_FILE" - echo "FW-INFO: $@" -} - -# 地址解析函数 - 修复格式问题 -parse_target() { - local target="$1" - - # 去除空格 - target=$(echo "${target}" | xargs) - - # dbg "解析目标地址: $target" - - # IPv4单个地址 - if echo "$target" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then - local octets=(${target//./ }) - local valid=1 - for octet in "${octets[@]}"; do - if [ "$octet" -gt 255 ] || [ "$octet" -lt 0 ]; then - valid=0 - break - fi - done - [ "$valid" -eq 1 ] && { - echo "ipv4:single:$target" - return 0 - } - - # IPv4范围 - elif echo "$target" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}-([0-9]{1,3}\.){3}[0-9]{1,3}$'; then - local start_ip=${target%-*} - local end_ip=${target#*-} - echo "ipv4:range:$start_ip-$end_ip" - return 0 - - # CIDR - elif echo "$target" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$'; then - local ip=${target%/*} - local mask=${target#*/} - [ "$mask" -le 32 ] && [ "$mask" -ge 0 ] && { - echo "ipv4:cidr:$target" - return 0 - } - - # MAC地址 - elif echo "$target" | grep -qE '^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$'; then - echo "mac:single:$(echo "$target" | tr '[:upper:]' '[:lower:]')" - return 0 - - # IPv6地址 - elif echo "$target" | grep -qE '^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$'; then - echo "ipv6:single:$target" - return 0 - - # IPv6 CIDR - elif echo "$target" | grep -qE '^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}/[0-9]{1,3}$'; then - local ipv6=${target%/*} - local mask=${target#*/} - [ "$mask" -le 128 ] && [ "$mask" -ge 0 ] && { - echo "ipv6:cidr:$target" - return 0 - } - fi - - dbg "无法解析地址: $target" - return 1 -} - -# 清理现有连接 -flush_connections() { - local target="$1" - - [ -x "$bin_conntrack" ] || { - dbg "conntrack不可用" - return - } - - local parsed_result=$(parse_target "$target") - [ $? -eq 0 ] || { - dbg "无法解析地址用于清理连接: $target" - return - } - - IFS=':' read -r type subtype value <<< "$parsed_result" - - dbg "清理连接: type=$type, value=$value" - - case "$type" in - "ipv4") - $bin_conntrack -D -s "$value" 2>/dev/null && dbg "清理源连接: $value" - $bin_conntrack -D -d "$value" 2>/dev/null && dbg "清理目标连接: $value" - ;; - "mac") - # MAC地址需要先转换为IP - if [ -f "/proc/net/arp" ]; then - local ip_addr=$(grep -i "$value" /proc/net/arp 2>/dev/null | awk '{print $1}' | head -1) - if [ -n "$ip_addr" ]; then - $bin_conntrack -D -s "$ip_addr" 2>/dev/null && dbg "清理MAC源连接: $value -> $ip_addr" - $bin_conntrack -D -d "$ip_addr" 2>/dev/null && dbg "清理MAC目标连接: $value -> $ip_addr" - fi - fi - ;; - esac -} - -# 检查防火墙工具 -check_firewall_tool() { - if [ -x "$bin_nft" ]; then - nftables_ver=1 - dbg "检测到nftables: $bin_nft" - elif [ -x "$bin_iptables" ] && [ -x "$bin_ip6tables" ]; then - iptables_ver=1 - dbg "检测到iptables: $bin_iptables, $bin_ip6tables" - else - info "错误: 未找到可用的防火墙工具" - return 1 - fi - return 0 -} - -# 初始化防火墙 -init_firewall() { - check_firewall_tool || return 1 - - info "初始化防火墙规则 (模式: $list_type, 强度: $chain)" - - if [ -n "$nftables_ver" ]; then - # 使用nftables - dbg "初始化nftables" - - # 删除可能存在的旧表 - nft delete table inet timecontrol 2>/dev/null - sleep 1 - - # 创建新表 - nft add table inet timecontrol - nft add chain inet timecontrol forward "{ type filter hook forward priority -100; policy accept; }" - - # 创建黑名单集合 - nft add set inet timecontrol blacklist "{ type ipv4_addr; flags interval; }" - nft add set inet timecontrol blacklist6 "{ type ipv6_addr; flags interval; }" - nft add set inet timecontrol blacklist_mac "{ type ether_addr; }" - - # 添加规则(黑名单模式:匹配到就DROP) - nft add rule inet timecontrol forward ip saddr @blacklist drop - nft add rule inet timecontrol forward ip6 saddr @blacklist6 drop - nft add rule inet timecontrol forward ether saddr @blacklist_mac drop - - # 强控制模式 - if [ "$StrongCHAIN" -eq 1 ]; then - nft add chain inet timecontrol input "{ type filter hook input priority -100; policy accept; }" - nft add rule inet timecontrol input ip saddr @blacklist drop - nft add rule inet timecontrol input ip6 saddr @blacklist6 drop - nft add rule inet timecontrol input ether saddr @blacklist_mac drop - dbg "已启用强控制模式 (INPUT链)" - fi - - info "nftables初始化完成" - - elif [ -n "$iptables_ver" ]; then - # 使用iptables - dbg "初始化iptables" - - # 创建ipset(如果不存在) - ipset create timecontrol_blacklist hash:net 2>/dev/null || { - ipset flush timecontrol_blacklist - dbg "已存在的ipset timecontrol_blacklist已清空" - } - - ipset create timecontrol_blacklist6 hash:net family inet6 2>/dev/null || { - ipset flush timecontrol_blacklist6 - dbg "已存在的ipset timecontrol_blacklist6已清空" - } - - # 删除可能存在的旧规则 - iptables -D FORWARD -m set --match-set timecontrol_blacklist src -j DROP 2>/dev/null - ip6tables -D FORWARD -m set --match-set timecontrol_blacklist6 src -j DROP 2>/dev/null - - # 添加新规则(黑名单模式) - iptables -I FORWARD -m set --match-set timecontrol_blacklist src -j DROP - ip6tables -I FORWARD -m set --match-set timecontrol_blacklist6 src -j DROP - - dbg "已添加FORWARD规则" - - # 强控制模式 - if [ "$StrongCHAIN" -eq 1 ]; then - iptables -D INPUT -m set --match-set timecontrol_blacklist src -j DROP 2>/dev/null - ip6tables -D INPUT -m set --match-set timecontrol_blacklist6 src -j DROP 2>/dev/null - - iptables -I INPUT -m set --match-set timecontrol_blacklist src -j DROP - ip6tables -I INPUT -m set --match-set timecontrol_blacklist6 src -j DROP - - dbg "已启用强控制模式 (INPUT链)" - fi - - info "iptables初始化完成" - fi - - return 0 -} - -# 停止防火墙规则 -stop_firewall() { - info "停止防火墙规则" - - if [ -n "$nftables_ver" ]; then - nft delete table inet timecontrol 2>/dev/null && info "nftables规则已删除" - fi - - if [ -n "$iptables_ver" ]; then - # 删除iptables规则 - iptables -D FORWARD -m set --match-set timecontrol_blacklist src -j DROP 2>/dev/null - iptables -D INPUT -m set --match-set timecontrol_blacklist src -j DROP 2>/dev/null - ip6tables -D FORWARD -m set --match-set timecontrol_blacklist6 src -j DROP 2>/dev/null - ip6tables -D INPUT -m set --match-set timecontrol_blacklist6 src -j DROP 2>/dev/null - - # 删除ipset - ipset destroy timecontrol_blacklist 2>/dev/null - ipset destroy timecontrol_blacklist6 2>/dev/null - - info "iptables规则已删除" - fi - - # 清理ID列表 - rm -f "$IDLIST" -} - -# 添加设备到防火墙 -add_device() { - local id="$1" - local target=$(config_t_get device mac "$id") - [ -z "$target" ] && { - dbg "添加设备失败: ID $id 的目标地址为空" - return - } - - local comment=$(config_t_get device comment "$id" "设备$id") - info "添加设备到防火墙: $comment ($target)" - - local parsed_result=$(parse_target "$target") - if [ $? -ne 0 ]; then - info "添加失败: 无法解析地址 $target" - return - fi - - IFS=':' read -r type subtype value <<< "$parsed_result" - dbg "解析结果: type=$type, subtype=$subtype, value=$value" - - if [ -n "$nftables_ver" ]; then - # nftables处理 - case "$type" in - "ipv4") - nft add element inet timecontrol blacklist "{ $value }" 2>&1 | while read line; do dbg "nft: $line"; done - dbg "已添加到nftables黑名单(IPv4): $value" - ;; - "ipv6") - nft add element inet timecontrol blacklist6 "{ $value }" 2>&1 | while read line; do dbg "nft: $line"; done - dbg "已添加到nftables黑名单(IPv6): $value" - ;; - "mac") - nft add element inet timecontrol blacklist_mac "{ $value }" 2>&1 | while read line; do dbg "nft: $line"; done - dbg "已添加到nftables黑名单(MAC): $value" - ;; - esac - - elif [ -n "$iptables_ver" ]; then - # iptables处理 - case "$type" in - "ipv4") - ipset add timecontrol_blacklist "$value" 2>&1 | while read line; do dbg "ipset: $line"; done - dbg "已添加到ipset黑名单(IPv4): $value" - ;; - "ipv6") - ipset add timecontrol_blacklist6 "$value" 2>&1 | while read line; do dbg "ipset: $line"; done - dbg "已添加到ipset黑名单(IPv6): $value" - ;; - "mac") - # iptables不支持MAC地址直接过滤,记录日志 - info "警告: iptables不支持MAC地址过滤,设备 $target 可能无法被阻止" - ;; - esac - fi - - # 强控制模式清理连接 - if [ "$StrongCHAIN" -eq 1 ]; then - dbg "强控制模式,清理现有连接" - flush_connections "$target" - fi - - # 验证规则 - verify_firewall_rule "$target" -} - -# 验证防火墙规则 -verify_firewall_rule() { - local target="$1" - - dbg "验证防火墙规则: $target" - - if [ -n "$nftables_ver" ]; then - nft list table inet timecontrol 2>/dev/null | grep -q "$target" && { - dbg "验证成功: $target 在nftables规则中" - return 0 - } - elif [ -n "$iptables_ver" ]; then - ipset test timecontrol_blacklist "$target" 2>/dev/null && { - dbg "验证成功: $target 在ipset中" - return 0 - } - fi - - dbg "验证失败: $target 不在防火墙规则中" - return 1 -} - -# 从防火墙移除设备 -del_device() { - local id="$1" - local target=$(config_t_get device mac "$id") - [ -z "$target" ] && { - dbg "移除设备失败: ID $id 的目标地址为空" - return - } - - local comment=$(config_t_get device comment "$id" "设备$id") - info "从防火墙移除设备: $comment ($target)" - - local parsed_result=$(parse_target "$target") - [ $? -eq 0 ] || { - info "移除失败: 无法解析地址 $target" - return - } - - IFS=':' read -r type subtype value <<< "$parsed_result" - - if [ -n "$nftables_ver" ]; then - case "$type" in - "ipv4") - nft delete element inet timecontrol blacklist "{ $value }" 2>/dev/null - dbg "已从nftables移除(IPv4): $value" - ;; - "ipv6") - nft delete element inet timecontrol blacklist6 "{ $value }" 2>/dev/null - dbg "已从nftables移除(IPv6): $value" - ;; - "mac") - nft delete element inet timecontrol blacklist_mac "{ $value }" 2>/dev/null - dbg "已从nftables移除(MAC): $value" - ;; - esac - - elif [ -n "$iptables_ver" ]; then - case "$type" in - "ipv4") - ipset del timecontrol_blacklist "$value" 2>/dev/null - dbg "已从ipset移除(IPv4): $value" - ;; - "ipv6") - ipset del timecontrol_blacklist6 "$value" 2>/dev/null - dbg "已从ipset移除(IPv6): $value" - ;; - esac - fi -} - -# 显示防火墙状态 -show_firewall_status() { - echo "" - echo "防火墙状态:" - echo "控制模式: $list_type" - echo "控制强度: $chain $( [ "$StrongCHAIN" -eq 1 ] && echo "(强控制)" )" - echo "" - - if [ -n "$nftables_ver" ]; then - echo "nftables规则:" - nft list table inet timecontrol 2>/dev/null || echo " 未找到timecontrol表" - elif [ -n "$iptables_ver" ]; then - echo "iptables规则:" - echo "FORWARD链:" - iptables -L FORWARD -n | grep -i timecontrol || echo " 未找到timecontrol规则" - ip6tables -L FORWARD -n | grep -i timecontrol || echo " 未找到IPv6 timecontrol规则" - - if [ "$StrongCHAIN" -eq 1 ]; then - echo "" - echo "INPUT链:" - iptables -L INPUT -n | grep -i timecontrol || echo " 未找到timecontrol规则" - ip6tables -L INPUT -n | grep -i timecontrol || echo " 未找到IPv6 timecontrol规则" - fi - - echo "" - echo "ipset内容:" - ipset list timecontrol_blacklist 2>/dev/null | head -20 || echo " timecontrol_blacklist未找到" - echo "" - ipset list timecontrol_blacklist6 2>/dev/null | head -20 || echo " timecontrol_blacklist6未找到" - fi -} - -# 诊断函数 -diagnose() { - echo "" - echo "=== 时间控制系统诊断 ===" - echo "" - - # 检查服务 - echo "1. 服务状态:" - if ps | grep -q "timecontrolctrl"; then - echo " ✓ timecontrolctrl 正在运行" - else - echo " ✗ timecontrolctrl 未运行" - fi - - # 检查配置文件 - echo "" - echo "2. 配置文件:" - if [ -f "/etc/config/timecontrol" ]; then - echo " ✓ 配置文件存在" - uci show timecontrol 2>/dev/null | grep -c "device" | while read count; do - echo " 配置了 $count 个设备" - done - else - echo " ✗ 配置文件不存在" - fi - - # 检查防火墙工具 - echo "" - echo "3. 防火墙工具:" - if [ -x "$bin_nft" ]; then - echo " ✓ nftables: $bin_nft" - echo " 版本: $($bin_nft --version 2>/dev/null | head -1)" - elif [ -x "$bin_iptables" ]; then - echo " ✓ iptables: $bin_iptables" - echo " 版本: $($bin_iptables --version 2>/dev/null | head -1)" - else - echo " ✗ 未找到防火墙工具" - fi - - # 显示当前规则 - show_firewall_status - - # 检查ID列表 - echo "" - echo "4. 当前控制列表:" - if [ -f "$IDLIST" ] && [ -s "$IDLIST" ]; then - echo " 当前禁止的设备:" - cat "$IDLIST" | sed 's/!//g' | while read id; do - local target=$(config_t_get device mac "$id") - local comment=$(config_t_get device comment "$id" "设备$id") - echo " ID$id: $comment ($target)" - done - else - echo " 当前没有设备被禁止" - fi - - echo "" - echo "=== 诊断完成 ===" -} - -# 主命令处理 -case "$crrun" in - "start") - info "启动时间控制" - stop_firewall - init_firewall - if [ $? -eq 0 ]; then - info "时间控制启动成功" - show_firewall_status - else - info "时间控制启动失败" - fi - ;; - - "stop") - info "停止时间控制" - stop_firewall - info "时间控制已停止" - ;; - - "add") - [ -z "$crid" ] && { - echo "错误: 需要指定设备ID" - exit 1 - } - info "添加设备控制: ID=$crid" - add_device "$crid" - show_firewall_status - ;; - - "del") - [ -z "$crid" ] && { - echo "错误: 需要指定设备ID" - exit 1 - } - info "移除设备控制: ID=$crid" - del_device "$crid" - show_firewall_status - ;; - - "status") - show_firewall_status - ;; - - "diagnose") - diagnose - ;; - - "test") - # 测试地址解析 - echo "测试地址解析:" - for test in "192.168.1.100" "192.168.1.0/24" "00:11:22:33:44:55" "invalid"; do - echo -n "$test: " - if parse_target "$test" >/dev/null; then - echo "✓ 有效" - parse_target "$test" - else - echo "✗ 无效" - fi - done - ;; - - "flush") - # 清理所有连接 - info "清理所有连接" - if [ -x "$bin_conntrack" ]; then - $bin_conntrack -F - info "连接已清理" - else - info "conntrack不可用" - fi - ;; - - "help"|"") - echo "时间控制系统命令工具" - echo "" - echo "用法: $0 {start|stop|add |del |status|diagnose|test|flush|help}" - echo "" - echo "命令说明:" - echo " start - 初始化防火墙规则" - echo " stop - 停止并清理所有防火墙规则" - echo " add - 添加设备到控制列表" - echo " del - 从控制列表移除设备" - echo " status - 显示防火墙状态" - echo " diagnose - 系统诊断" - echo " test - 测试地址解析" - echo " flush - 清理所有网络连接" - echo " help - 显示此帮助信息" - ;; - - *) - echo "错误: 未知命令 '$crrun'" - echo "使用: $0 help 查看帮助" - exit 1 - ;; -esac diff --git a/luci-app-timecontrol/root/usr/bin/timecontrol-log b/luci-app-timecontrol/root/usr/bin/timecontrol-log deleted file mode 100755 index 88c390e3..00000000 --- a/luci-app-timecontrol/root/usr/bin/timecontrol-log +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/bash - -# 时间控制日志查看工具 - -NAME=timecontrol -LOG_FILE="/var/log/$NAME.log" -STATUS_LOG="/var/lib/$NAME/status.log" -CONNECTION_LOG="/var/lib/$NAME/connections.log" - -show_realtime_log() { - echo "正在显示实时日志,按 Ctrl+C 退出..." - echo "" - tail -f "$LOG_FILE" | while read line; do - # 高亮显示重要信息 - if echo "$line" | grep -q "STATUS-CHANGE\|TIME_EXCEEDED\|RESET"; then - echo -e "\033[1;31m$line\033[0m" # 红色显示重要变更 - elif echo "$line" | grep -q "ALLOW_ACCESS\|解除限制"; then - echo -e "\033[1;32m$line\033[0m" # 绿色显示允许访问 - elif echo "$line" | grep -q "BLOCK_ACCESS\|添加限制"; then - echo -e "\033[1;33m$line\033[0m" # 黄色显示禁止访问 - else - echo "$line" - fi - done -} - -show_status_log() { - echo "最近状态变更记录:" - echo "────────────────────────────────────────────────────────────────────" - if [ -f "$STATUS_LOG" ]; then - tail -n 20 "$STATUS_LOG" | while read line; do - local time=$(echo "$line" | cut -d']' -f1 | sed 's/\[//') - local message=$(echo "$line" | cut -d']' -f2-) - printf "%-20s %s\n" "$time" "$message" - done - else - echo "暂无状态记录" - fi -} - -show_connection_log() { - echo "设备连接记录:" - echo "────────────────────────────────────────────────────────────────────" - echo "时间 设备 状态" - echo "────────────────────────────────────────────────────────────────────" - - if [ -f "$CONNECTION_LOG" ]; then - tail -n 20 "$CONNECTION_LOG" | while read line; do - local timestamp=$(echo "$line" | cut -d',' -f1) - local target=$(echo "$line" | cut -d',' -f2) - local action=$(echo "$line" | cut -d',' -f3) - - local time_str=$(date -d "@$timestamp" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$timestamp") - local action_text="" - - case "$action" in - "connect") action_text="上线" ;; - "disconnect") action_text="下线" ;; - *) action_text="$action" ;; - esac - - printf "%-20s %-18s %s\n" "$time_str" "$target" "$action_text" - done - else - echo "暂无连接记录" - fi -} - -show_summary() { - local summary_file="/tmp/timecontrol_status.txt" - if [ -f "$summary_file" ]; then - cat "$summary_file" - else - echo "状态摘要文件不存在,正在生成..." - timecontrol status - fi -} - -show_help() { - echo "时间控制日志查看工具" - echo "" - echo "用法: timecontrol-log {realtime|status|connections|summary|help}" - echo "" - echo "命令:" - echo " realtime - 实时显示日志(彩色高亮)" - echo " status - 显示状态变更记录" - echo " connections - 显示设备连接记录" - echo " summary - 显示状态摘要" - echo " help - 显示此帮助" - echo "" - echo "示例:" - echo " timecontrol-log realtime # 实时监控" - echo " timecontrol-log status # 查看状态变更" -} - -case "$1" in - "realtime") - show_realtime_log - ;; - "status") - show_status_log - ;; - "connections") - show_connection_log - ;; - "summary") - show_summary - ;; - "help"|"") - show_help - ;; - *) - echo "未知命令: $1" - show_help - exit 1 - ;; -esac diff --git a/luci-app-timecontrol/root/usr/bin/timecontrolctrl b/luci-app-timecontrol/root/usr/bin/timecontrolctrl deleted file mode 100755 index b4f48868..00000000 --- a/luci-app-timecontrol/root/usr/bin/timecontrolctrl +++ /dev/null @@ -1,335 +0,0 @@ -#!/bin/sh - -# Copyright (C) 2006 OpenWrt.org -# Copyright 2022-2026 sirpdboy - -NAME=timecontrol -LOG_FILE="/var/log/timecontrol.log" -DEBUG=1 - -# 时长数据库目录 -DURATION_DIR="/var/lib/timecontrol" -DURATION_DB="$DURATION_DIR/duration.db" -CONNECTION_LOG="$DURATION_DIR/connections.log" - -# 状态文件 -IDLIST="/var/$NAME.idlist" -STATUS_DB="$DURATION_DIR/status.db" - -# 初始化目录 -init_dirs() { - mkdir -p "$DURATION_DIR" - touch "$DURATION_DB" "$CONNECTION_LOG" "$STATUS_DB" 2>/dev/null -} - -# 日志函数 -dbg() { - [ "$DEBUG" -eq 1 ] && { - local d="$(date '+%Y-%m-%d %H:%M:%S')" - echo "[$d] CTRL-DEBUG: $@" >> "$LOG_FILE" - } -} - -info() { - local d="$(date '+%Y-%m-%d %H:%M:%S')" - echo "[$d] CTRL-INFO: $@" >> "$LOG_FILE" -} - -# 配置文件读取 -config_t_get() { - local index=${3:-0} - local ret=$(uci -q get "${NAME}.@${1}[${index}].${2}") - echo "${ret:=${4}}" -} - -# 获取启用设备 -get_enabled_devices() { - uci show $NAME 2>/dev/null | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' | sort -n -} - -# 时间检查函数 -is_time_in_range() { - local start_time=$1 - local end_time=$2 - local current_time=$(date +%H:%M) - - if [ "$start_time" = "$end_time" ]; then - return 0 - elif [ "$start_time" \< "$end_time" ]; then - [ "$current_time" \> "$start_time" ] && [ "$current_time" \< "$end_time" ] && return 0 - else - [ "$current_time" \> "$start_time" ] || [ "$current_time" \< "$end_time" ] && return 0 - fi - return 1 -} - -is_weekday_in_range() { - local configured_weekdays=$1 - local current_weekday=$(date +%u) - - [ "$configured_weekdays" = "0" ] && return 0 - - for ww in $(echo $configured_weekdays | sed 's/,/ /g'); do - [ "$current_weekday" = "$ww" ] && return 0 - done - return 1 -} - -# 时长管理 -record_connection_time() { - local target="$1" - local action="$2" - local timestamp=$(date +%s) - - echo "$timestamp,$target,$action" >> "$CONNECTION_LOG" - - if [ "$action" = "connect" ]; then - if ! grep -q "^$target," "$DURATION_DB" 2>/dev/null; then - echo "$target,$timestamp,0,0" >> "$DURATION_DB" - dbg "初始化时长: $target" - fi - elif [ "$action" = "disconnect" ]; then - if grep -q "^$target," "$DURATION_DB" 2>/dev/null; then - local last_connect=$(grep "^$target," "$DURATION_DB" | cut -d',' -f2) - local total_used=$(grep "^$target," "$DURATION_DB" | cut -d',' -f3) - local last_reset=$(grep "^$target," "$DURATION_DB" | cut -d',' -f4) - - local session_duration=$((timestamp - last_connect)) - local new_total=$((total_used + session_duration)) - - sed -i "/^$target,/d" "$DURATION_DB" - echo "$target,$timestamp,$new_total,$last_reset" >> "$DURATION_DB" - fi - fi -} - -get_connection_time() { - local target="$1" - local current_time=$(date +%s) - - if grep -q "^$target," "$DURATION_DB" 2>/dev/null; then - local last_connect=$(grep "^$target," "$DURATION_DB" | cut -d',' -f2) - local total_used=$(grep "^$target," "$DURATION_DB" | cut -d',' -f3) - - if tail -n 5 "$CONNECTION_LOG" 2>/dev/null | grep -q "^[0-9]*,$target,connect$"; then - local session_duration=$((current_time - last_connect)) - total_used=$((total_used + session_duration)) - fi - - echo "$total_used" - else - echo "0" - fi -} - -# 重置检查 -should_reset_duration() { - local target="$1" - local reset_cycle="$2" - local last_reset_file="$DURATION_DIR/last_reset_$target" - - [ ! -f "$last_reset_file" ] && return 0 - - local last_reset=$(cat "$last_reset_file" 2>/dev/null) - local current_time=$(date +%s) - - case "$reset_cycle" in - "daily") - local last_date=$(date -d "@$last_reset" +%Y%m%d 2>/dev/null || echo "0") - local current_date=$(date +%Y%m%d) - [ "$last_date" != "$current_date" ] - ;; - "weekly") - local last_week=$(date -d "@$last_reset" +%Y%W 2>/dev/null || echo "0") - local current_week=$(date +%Y%W) - [ "$last_week" != "$current_week" ] - ;; - "monthly") - local last_month=$(date -d "@$last_reset" +%Y%m 2>/dev/null || echo "0") - local current_month=$(date +%Y%m) - [ "$last_month" != "$current_month" ] - ;; - *) - false - ;; - esac -} - -reset_duration_counter() { - local target="$1" - local reset_cycle="$2" - local current_time=$(date +%s) - - if grep -q "^$target," "$DURATION_DB" 2>/dev/null; then - sed -i "/^$target,/d" "$DURATION_DB" - fi - - echo "$target,$current_time,0,$current_time" >> "$DURATION_DB" - echo "$current_time" > "$DURATION_DIR/last_reset_$target" - - info "重置时长: $target (周期: $reset_cycle)" -} - -# 主检查函数 -check_device_control() { - local id="$1" - local target=$(config_t_get device mac "$id") - local time_mode=$(config_t_get device time_mode "$id" "period") - local weekdays=$(config_t_get device week "$id" "0") - - # 检查星期 - is_weekday_in_range "$weekdays" || { - dbg "星期不允许: $target" - return 1 - } - - case "$time_mode" in - "period") - local start_time=$(config_t_get device timestart "$id" "00:00") - local end_time=$(config_t_get device timeend "$id" "00:00") - is_time_in_range "$start_time" "$end_time" || { - dbg "时间段外: $target ($start_time-$end_time)" - return 1 - } - dbg "时间段内: $target" - return 0 - ;; - - "duration") - local duration=$(config_t_get device duration "$id" "60") - local reset_cycle=$(config_t_get device reset_cycle "$id" "daily") - - # 重置检查 - if should_reset_duration "$target" "$reset_cycle"; then - reset_duration_counter "$target" "$reset_cycle" - fi - - # 记录连接 - local last_action=$(grep ",$target," "$CONNECTION_LOG" 2>/dev/null | tail -1 | cut -d',' -f3) - if [ "$last_action" != "connect" ]; then - record_connection_time "$target" "connect" - fi - - # 检查时长 - local used_seconds=$(get_connection_time "$target") - local used_minutes=$((used_seconds / 60)) - local max_minutes=$duration - - dbg "时长检查: $target 已用=${used_minutes}分钟, 限制=${max_minutes}分钟" - - if [ "$used_minutes" -ge "$max_minutes" ]; then - dbg "已超时: $target" - return 1 - fi - dbg "未超时: $target" - return 0 - ;; - - "combined") - local start_time=$(config_t_get device timestart "$id" "00:00") - local end_time=$(config_t_get device timeend "$id" "00:00") - local use_duration=$(config_t_get device use_duration "$id" "0") - - # 时间段检查 - is_time_in_range "$start_time" "$end_time" || { - dbg "时间段外: $target" - return 1 - } - - # 时长检查 - if [ "$use_duration" = "1" ]; then - local duration=$(config_t_get device duration "$id" "60") - local reset_cycle=$(config_t_get device reset_cycle "$id" "daily") - - if should_reset_duration "$target" "$reset_cycle"; then - reset_duration_counter "$target" "$reset_cycle" - fi - - local last_action=$(grep ",$target," "$CONNECTION_LOG" 2>/dev/null | tail -1 | cut -d',' -f3) - if [ "$last_action" != "connect" ]; then - record_connection_time "$target" "connect" - fi - - local used_seconds=$(get_connection_time "$target") - local used_minutes=$((used_seconds / 60)) - local max_minutes=$duration - - if [ "$used_minutes" -ge "$max_minutes" ]; then - dbg "时间段内但已超时: $target" - return 1 - fi - fi - - dbg "时间段内允许: $target" - return 0 - ;; - - *) - # 默认时间段控制 - local start_time=$(config_t_get device timestart "$id" "00:00") - local end_time=$(config_t_get device timeend "$id" "00:00") - is_time_in_range "$start_time" "$end_time" - return $? - ;; - esac -} - -# 更新设备状态 -update_device_status() { - local id="$1" - local should_allow="$2" # 0=允许, 1=禁止 - local target=$(config_t_get device mac "$id") - local comment=$(config_t_get device comment "$id" "设备$id") - - # 检查当前状态 - local current_blocked=0 - if [ -f "$IDLIST" ] && grep -q "!${id}!" "$IDLIST" 2>/dev/null; then - current_blocked=1 - fi - - dbg "设备状态: $target, 应该允许=$should_allow, 当前阻止=$current_blocked" - - if [ "$should_allow" -eq 0 ]; then - # 应该允许 - if [ "$current_blocked" -eq 1 ]; then - dbg "解除阻止: $target" - timecontrol del "$id" - sed -i "/!$id!/d" "$IDLIST" 2>/dev/null - info "允许上网: $comment ($target)" - record_connection_time "$target" "disconnect" - fi - else - # 应该阻止 - if [ "$current_blocked" -eq 0 ]; then - dbg "添加阻止: $target" - timecontrol add "$id" - if ! grep -q "!$id!" "$IDLIST" 2>/dev/null; then - echo "!$id!" >> "$IDLIST" - fi - info "阻止上网: $comment ($target)" - record_connection_time "$target" "disconnect" - fi - fi -} - -# 主处理循环 -main_loop() { - info "时间控制守护进程启动" - init_dirs - while :; do - dbg "开始检查设备" - [ `uci show $NAME 2>/dev/null | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' | sort -n | wc -l` eq 0 ] && timecontrol stop && break - for id in $(get_enabled_devices); do - if check_device_control "$id"; then - update_device_status "$id" 0 # 允许 - else - update_device_status "$id" 1 # 阻止 - fi - done - - sleep 60 - done -} - -# 启动 -main_loop diff --git a/luci-app-timecontrol/root/usr/libexec/timecontrol-call b/luci-app-timecontrol/root/usr/libexec/timecontrol-call deleted file mode 100755 index c2daf084..00000000 --- a/luci-app-timecontrol/root/usr/libexec/timecontrol-call +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2025 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-timecontrol -# - -logfile="/var/log/timecontrol.log" -lang=$(uci get luci.main.lang 2>/dev/null) -if [ -z "$lang" ] || [[ "$lang" == "auto" ]]; then - lang=$(echo "${LANG:-${LANGUAGE:-${LC_ALL:-${LC_MESSAGES:-zh_cn}}}}" | awk -F'[ .@]' '{print tolower($1)}' | sed 's/-/_/' 2>/dev/null) -fi - -translate() { - # 处理特殊字符 - local lua_script=$(cat <"${logfile}" - -elif [ "$1" == "child" ]; then - shift - command_name=$1 - shift - "$command_name" "$@" -fi diff --git a/luci-app-timecontrol/root/usr/share/luci/menu.d/luci-app-timecontrol.json b/luci-app-timecontrol/root/usr/share/luci/menu.d/luci-app-timecontrol.json deleted file mode 100644 index dba688ae..00000000 --- a/luci-app-timecontrol/root/usr/share/luci/menu.d/luci-app-timecontrol.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "admin/control/timecontrol": { - "title": "Time Control", - "order": 10, - "action": { - "type": "firstchild" - }, - "acl": [ "read" ], - "depends": { - "acl": [ "luci-app-timecontrol" ] - }, - "recurse": true - }, - "admin/control/timecontrol/basic": { - "title": "Time Control", - "order": 10, - "action": { - "type": "view", - "path": "timecontrol/basic" - }, - "acl": [ "read" ] - }, - "admin/control/timecontrol/log": { - "title": "Log", - "order": 40, - "action": { - "type": "view", - "path": "timecontrol/log" - }, - "acl": [ "read" ] - } -} diff --git a/luci-app-timecontrol/root/usr/share/rpcd/acl.d/luci-app-timecontrol.json b/luci-app-timecontrol/root/usr/share/rpcd/acl.d/luci-app-timecontrol.json index 1f2f2f12..2ff0fab2 100644 --- a/luci-app-timecontrol/root/usr/share/rpcd/acl.d/luci-app-timecontrol.json +++ b/luci-app-timecontrol/root/usr/share/rpcd/acl.d/luci-app-timecontrol.json @@ -1,21 +1,11 @@ { - "luci-app-timecontrol": { - "description": "Grant UCI Internet time control for luci-app-timecontrol", - "read": { - - "ubus": { - - "file": ["exec", "list", "stat", "read"], - "uci": [ "*" ], - "timecontrol": ["*"] - } - }, - "write": { - "ubus": { - "timecontrol": ["*"], - "file": ["write"], - "uci": ["*"] - } - } - } -} \ No newline at end of file + "luci-app-timecontrol": { + "description": "Grant UCI access for luci-app-timecontrol", + "read": { + "uci": [ "timecontrol" ] + }, + "write": { + "uci": [ "timecontrol" ] + } + } +} diff --git a/luci-app-timecontrol/root/usr/share/ucitrack/luci-app-timecontrol.json b/luci-app-timecontrol/root/usr/share/ucitrack/luci-app-timecontrol.json new file mode 100644 index 00000000..6c57d034 --- /dev/null +++ b/luci-app-timecontrol/root/usr/share/ucitrack/luci-app-timecontrol.json @@ -0,0 +1,4 @@ +{ + "config": "timecontrol", + "init": "timecontrol" +} diff --git a/mptcp/Makefile b/mptcp/Makefile index dc83be44..a7504b5b 100644 --- a/mptcp/Makefile +++ b/mptcp/Makefile @@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=mptcp PKG_VERSION:=6.1 -PKG_RELEASE:=12 +PKG_RELEASE:=13 PKG_MAINTAINER:=Ycarus (Yannick Chabanois) PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) diff --git a/mptcp/files/etc/hotplug.d/iface/30-mptcp b/mptcp/files/etc/hotplug.d/iface/30-mptcp index 86b4e8e1..f9dc4581 100644 --- a/mptcp/files/etc/hotplug.d/iface/30-mptcp +++ b/mptcp/files/etc/hotplug.d/iface/30-mptcp @@ -5,18 +5,34 @@ /etc/init.d/mptcp enabled || exit 0 -if [ "$ACTION" = ifupdate -a -n "$IFUPDATE_ADDRESSES" ] && [ -n "$(uci -q get network.$INTERFACE.multipath)" ] && [ "$(uci -q get network.$INTERFACE.multipath)" != "off" ]; then +# A DHCPv6 companion "_6" (wizard IPv6 SLAAC/DHCPv6 option on a DHCP +# WAN, #4329) shares its parent's device and is kept at multipath=off so it +# never counts as a WAN of its own (status page, MPTCP pages, trackers). +# Its events still matter for the shared device: a new SLAAC address must +# refresh the MPTCP endpoints, and its ifup must re-run the parent's IPv6 +# routing (mptcp reload reads the gateway from "_6" status), so the +# multipath decision is keyed on the parent interface instead. +MP_INTERFACE="$INTERFACE" +case "$INTERFACE" in + *_6) + if [ "$(uci -q get network.$INTERFACE.proto)" = "dhcpv6" ] && [ -n "$(uci -q get network.${INTERFACE%_6})" ]; then + MP_INTERFACE="${INTERFACE%_6}" + fi + ;; +esac +MULTIPATH="$(uci -q get network.$MP_INTERFACE.multipath)" + +if [ "$ACTION" = ifupdate -a -n "$IFUPDATE_ADDRESSES" ] && [ -n "$MULTIPATH" ] && [ "$MULTIPATH" != "off" ]; then logger -t "mptcp" "New IP ($IFUPDATE_ADDRESSES) for $INTERFACE ($DEVICE)" multipath $DEVICE off 2>&1 >/dev/null || exit 0 multipath $DEVICE on 2>&1 >/dev/null || exit 0 -elif [ "$ACTION" = ifupdate ] && [ -n "$(uci -q get network.$INTERFACE.multipath)" ] && [ "$(uci -q get network.$INTERFACE.multipath)" != "off" ]; then +elif [ "$ACTION" = ifupdate ] && [ -n "$MULTIPATH" ] && [ "$MULTIPATH" != "off" ]; then logger -t "mptcp" "Update of $INTERFACE ($DEVICE)" multipath $DEVICE off 2>&1 >/dev/null || exit 0 multipath $DEVICE on 2>&1 >/dev/null || exit 0 -elif [ "$ACTION" = ifup -o "$ACTION" = iflink -o "$ACTION" = link-up ] && [ -z "$(echo $DEVICE | grep oip | grep gre)" ] && [ -n "$(uci -q get network.$INTERFACE.multipath)" ] && [ "$(uci -q get network.$INTERFACE.multipath)" != "off" ]; then +elif [ "$ACTION" = ifup -o "$ACTION" = iflink -o "$ACTION" = link-up ] && [ -z "$(echo $DEVICE | grep oip | grep gre)" ] && [ -n "$MULTIPATH" ] && [ "$MULTIPATH" != "off" ]; then logger -t "mptcp" "Reloading mptcp config due to $ACTION of $INTERFACE ($DEVICE)" /etc/init.d/mptcp reload "$DEVICE" >/dev/null || exit 0 elif [ "$ACTION" = ifdown -o "$ACTION" = link-down ]; then multipath $DEVICE off 2>&1 >/dev/null || exit 0 fi - diff --git a/mptcp/files/etc/init.d/mptcp b/mptcp/files/etc/init.d/mptcp index c2725693..3efee4d3 100755 --- a/mptcp/files/etc/init.d/mptcp +++ b/mptcp/files/etc/init.d/mptcp @@ -190,6 +190,27 @@ interface_max_metric() { esac } +# sqm-scripts (cake / htb+fq_codel / hfsc set up by /usr/lib/sqm) and +# qos-scripts own the root qdisc of the devices they shape. Replacing it with +# fq below silently deleted the egress shaper on every "mptcp reload " +# (tracker status change, IP change, ifup hotplug) while SQM's state file still +# claimed it was running, so SQM never re-applied it until its next restart: +# LuCI and the wizard showed SQM enabled, tc showed plain fq (#4329). +# Leave the root qdisc alone whenever another shaper manages this device. +_root_qdisc_managed_elsewhere() { + local dev="$1" config="$2" state_dir sec + [ -n "$dev" ] || return 1 + state_dir="$(. /etc/sqm/sqm.conf 2>/dev/null; echo "${SQM_STATE_DIR:-/var/run/sqm}")" + [ -f "${state_dir}/${dev}.state" ] && return 0 + # an enabled sqm queue on this device: named after the interface by the + # wizard, or an anonymous section created from the LuCI SQM page + for sec in $(uci -q show sqm 2>/dev/null | sed -n "s/^sqm\.\([^.]*\)\.interface='${dev}'\$/\1/p"); do + [ "$(uci -q get sqm.${sec}.enabled)" = "1" ] && return 0 + done + [ "$(uci -q get qos.${config}.enabled)" = "1" ] && return 0 + return 1 +} + interface_multipath_settings() { local mode iface proto metric ip4table qdisc local config="$1" @@ -433,7 +454,7 @@ interface_multipath_settings() { #ifconfig $iface txqueuelen 1000 > /dev/null 2>&1 ip link set dev $iface txqueuelen 1000 > /dev/null 2>&1 fi - tc qdisc replace dev $iface root ${qdisc:-fq} > /dev/null 2>&1 + _root_qdisc_managed_elsewhere "$iface" "$config" || tc qdisc replace dev $iface root ${qdisc:-fq} > /dev/null 2>&1 fi if [ -z "$gateway" ] && [ -n "$network" ]; then if [ "$uci_route" = "1" ]; then @@ -469,7 +490,7 @@ interface_multipath_settings() { #ifconfig $iface txqueuelen 1000 > /dev/null 2>&1 ip link set dev $iface txqueuelen 1000 > /dev/null 2>&1 fi - tc qdisc replace dev $iface root ${qdisc:-fq} > /dev/null 2>&1 + _root_qdisc_managed_elsewhere "$iface" "$config" || tc qdisc replace dev $iface root ${qdisc:-fq} > /dev/null 2>&1 fi if [ "$(uci -q get openmptcprouter.settings.disable_ipv6)" != "1" ] && [ "$config" != "omr6in4" ]; then # IPv6 Updates: