mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-11 02:44:57 +08:00
This commit is contained in:
@@ -1,39 +1,16 @@
|
||||
# Copyright (C) 2020 Lienol <lawlienol@gmail.com>
|
||||
#
|
||||
# Copyright (C) 2006-2017 OpenWrt.org
|
||||
# Copyright (C) 2022-2026 sirpdboy <herboy2008@gmail.com>
|
||||
# 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 <herboy2008@gmail.com>
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -1,335 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
* Copyright (C) 2022-2026 sirpdboy <herboy2008@gmail.com>
|
||||
*/
|
||||
'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(
|
||||
'<em><span style="color:%s">%s <strong>%s %s</strong></span></em>',
|
||||
color, icon, _('TimeControl Service'), statusText
|
||||
);
|
||||
|
||||
if (isRunning && pid) {
|
||||
statusHtml += ' <small>(PID: ' + pid + ')</small>';
|
||||
}
|
||||
|
||||
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') + '<br/>' +
|
||||
_('黑名单模式时间控制方式:') + '<br/>' +
|
||||
_('1. 时间段控制: 指定的机器在设定时间段内可以上网,其他时间不能上网') + '<br/>' +
|
||||
_('2. 允许上机时长: 指定的机器上线后可以上网指定时长,超过时长后不能上网') + '<br/>' +
|
||||
_('3. 组合控制: 在时间段内+时长限制(在允许的时间段内限制上网时长)') + '<br/>' +
|
||||
_('Suggested feedback:') + ' <a href="https://github.com/sirpdboy/luci-app-timecontrol.git" target="_blank">GitHub @timecontrol</a>');
|
||||
|
||||
s = m.section(form.TypedSection);
|
||||
s.anonymous = true;
|
||||
s.render = function() {
|
||||
var statusView = E('p', { id: 'service_status' },
|
||||
'<span class="spinning"> </span> ' + _('Checking service status...'));
|
||||
|
||||
checkTimeControlProcess()
|
||||
.then(function(res) {
|
||||
var status = renderServiceStatus(res.running, res.pid);
|
||||
statusView.innerHTML = status;
|
||||
})
|
||||
.catch(function(err) {
|
||||
statusView.innerHTML = '<span style="color:orange">⚠ ' +
|
||||
_('Status check failed') + '</span>';
|
||||
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 = '<span style="color:orange">⚠ ' +
|
||||
_('Status check failed') + '</span>';
|
||||
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();
|
||||
}
|
||||
});
|
||||
@@ -1,238 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
/*
|
||||
|
||||
* Copyright (C) 2022-2026 sirpdboy <herboy2008@gmail.com>
|
||||
*/
|
||||
'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
|
||||
});
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,12 @@
|
||||
<% include("cbi/map") %>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "control", "timecontrol", "status")%>', null,
|
||||
function (x, result) {
|
||||
var status = document.getElementsByClassName('timecontrol_status')[0];
|
||||
status.setAttribute("style", "font-weight:bold;");
|
||||
status.setAttribute("color", result.status ? "green" : "red");
|
||||
status.innerHTML = result.status ? '<%=translate("RUNNING")%>' : '<%=translate("NOT RUNNING")%>';
|
||||
}
|
||||
)
|
||||
//]]>
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
<%+cbi/valueheader%>
|
||||
<font class="timecontrol_status"><%=pcdata(self:cfgvalue(section) or self.default or "")%></font>
|
||||
<%+cbi/valuefooter%>
|
||||
@@ -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 "周日"
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,600 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (C) 2006 OpenWrt.org
|
||||
# Copyright 2022-2026 sirpdboy <herboy2008@gmail.com>
|
||||
|
||||
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 <id>|del <id>|status|diagnose|test|flush|help}"
|
||||
echo ""
|
||||
echo "命令说明:"
|
||||
echo " start - 初始化防火墙规则"
|
||||
echo " stop - 停止并清理所有防火墙规则"
|
||||
echo " add <id> - 添加设备到控制列表"
|
||||
echo " del <id> - 从控制列表移除设备"
|
||||
echo " status - 显示防火墙状态"
|
||||
echo " diagnose - 系统诊断"
|
||||
echo " test - 测试地址解析"
|
||||
echo " flush - 清理所有网络连接"
|
||||
echo " help - 显示此帮助信息"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "错误: 未知命令 '$crrun'"
|
||||
echo "使用: $0 help 查看帮助"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -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
|
||||
@@ -1,335 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2006 OpenWrt.org
|
||||
# Copyright 2022-2026 sirpdboy <herboy2008@gmail.com>
|
||||
|
||||
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
|
||||
@@ -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 <<LUA
|
||||
require "luci.i18n".setlanguage("$lang")
|
||||
print(require "luci.i18n".translate([==[$1]==]))
|
||||
LUA
|
||||
)
|
||||
|
||||
lua -e "$lua_script"
|
||||
}
|
||||
|
||||
if [ "$1" == "clear_log" ]; then
|
||||
# 清空日志
|
||||
>"${logfile}"
|
||||
|
||||
elif [ "$1" == "child" ]; then
|
||||
shift
|
||||
command_name=$1
|
||||
shift
|
||||
"$command_name" "$@"
|
||||
fi
|
||||
@@ -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" ]
|
||||
}
|
||||
}
|
||||
@@ -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": ["*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"luci-app-timecontrol": {
|
||||
"description": "Grant UCI access for luci-app-timecontrol",
|
||||
"read": {
|
||||
"uci": [ "timecontrol" ]
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "timecontrol" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"config": "timecontrol",
|
||||
"init": "timecontrol"
|
||||
}
|
||||
+1
-1
@@ -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) <ycarus@zugaina.org>
|
||||
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
@@ -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 "<intf>_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 "<intf>_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
|
||||
|
||||
|
||||
@@ -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 <device>"
|
||||
# (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:
|
||||
|
||||
Reference in New Issue
Block a user