mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-01 16:44:45 +08:00
update 2026-10-20 02:12:23
This commit is contained in:
1 parent
313fe55d56
commit
f322ead2a4
28 files changed
+1625
-241
No files matched your search
Submodule luci-app-eqosplus deleted from c7ea61c971.
+47
@@ -0,0 +1,47 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.arch }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- aarch64_generic
|
||||
- i386_pentium-mmx
|
||||
- loongarch64_generic
|
||||
- mips_mips32
|
||||
- mipsel_24kc
|
||||
- mipsel_mips32
|
||||
- x86_64
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Building packages
|
||||
uses: sbwml/openwrt-gh-action-sdk@go1.24
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}-openwrt-24.10
|
||||
FEEDNAME: packages_ci
|
||||
PACKAGES: luci-app-eqosplus
|
||||
NO_REFRESH_CHECK: true
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.arch }}
|
||||
path: bin/packages/${{ matrix.arch }}/packages_ci/*.ipk
|
||||
|
||||
- name: Upload packages
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
allowUpdates: true
|
||||
replacesArtifacts: true
|
||||
artifacts: "bin/packages/${{ matrix.arch }}/packages_ci/*.ipk"
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 sirpdboy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,39 @@
|
||||
#
|
||||
# Copyright (C) 2006-2017 OpenWrt.org#
|
||||
# Copyright (C) 2022-2025 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-eqosplus
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
THEME_NAME:=eqosplus
|
||||
PKG_NAME:=luci-app-$(THEME_NAME)
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
LUCI_TITLE:=LuCI support for eqosplus.
|
||||
LUCI_DESCRIPTION:=LuCI support for Easy eqosplus(Support speed limit based on IP address).
|
||||
LUCI_DEPENDS:=+ip-full +tc +nftables +bc +kmod-ifb
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=1.3.0
|
||||
PKG_RELEASE:=20251128
|
||||
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/eqosplus
|
||||
endef
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
 [](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ)
|
||||
|
||||
<h1 align="center">
|
||||
<br>luci-app-eqosplus<br>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://openwrt.org"><img alt="OpenWrt" src="https://img.shields.io/badge/OpenWrt-%E2%89%A519.07-ff0000?logo=openwrt&logoColor=white"></a>
|
||||
<a href="https://www.google.com/chrome/"><img alt="Chrome" src="https://img.shields.io/badge/Chrome-%E2%89%A5111-4285F3?logo=googlechrome&logoColor=white"></a>
|
||||
<a href="https://www.apple.com/safari/"><img alt="Safari" src="https://img.shields.io/badge/Safari-%E2%89%A516.4-000000?logo=safari&logoColor=white"></a>
|
||||
<a href="https://www.mozilla.org/firefox/"><img alt="Firefox" src="https://img.shields.io/badge/Firefox-%E2%89%A5128-FF7138?logo=firefoxbrowser&logoColor=white"></a>
|
||||
<a target="_blank" href="https://github.com/sirpdboy/luci-app-eqosplus/releases"> <img alt="GitHub release" src="https://img.shields.io/github/v/release/sirpdboy/luci-app-eqosplus"></a>
|
||||
<a href="https://github.com/sirpdboy/luci-app-eqosplus/releases"><img alt="Downloads" src="https://img.shields.io/github/downloads/sirpdboy/luci-app-eqosplus/total"></a>
|
||||
</p>
|
||||
|
||||
[中文] | [English](README.md)
|
||||
|
||||

|
||||
|
||||
Please read this page carefully, which includes precautions and instructions on how to use it.
|
||||
|
||||
## Enhanced version of EQOS with timed speed limit and added features such as timed limit.
|
||||
|
||||
### Method for downloading source code:
|
||||
|
||||
```Brach
|
||||
# downloading
|
||||
git clone https://github.com/sirpdboy/luci-app-eqosplus package/luci-app-eqosplus
|
||||
make menuconfig
|
||||
```
|
||||
### Configuration Menu
|
||||
```Brach
|
||||
make menuconfig
|
||||
# find LuCI -> Applications, select luci-app-eqosplus, save and exit
|
||||
```
|
||||
### compile
|
||||
|
||||
```Brach
|
||||
# compile
|
||||
make package/luci-app-eqosplus/compile V=s
|
||||
```
|
||||
|
||||
|
||||
## describe
|
||||
|
||||
- luci-app-eqosplus : https://github.com/sirpdboy/luci-app-eqosplus
|
||||
|
||||

|
||||
|
||||
|
||||
## interface
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
# My other project
|
||||
|
||||
- Watch Dog : https://github.com/sirpdboy/luci-app-watchdog
|
||||
- Net Speedtest : https://github.com/sirpdboy/luci-app-netspeedtest
|
||||
- Task Plan : https://github.com/sirpdboy/luci-app-taskplan
|
||||
- Power Off Device : https://github.com/sirpdboy/luci-app-poweroffdevice
|
||||
- OpentoPD Theme : https://github.com/sirpdboy/luci-theme-opentopd
|
||||
- KuCat Theme : https://github.com/sirpdboy/luci-theme-kucat
|
||||
- KuCat Theme Config : https://github.com/sirpdboy/luci-app-kucat-config
|
||||
- NFT Time Control : https://github.com/sirpdboy/luci-app-timecontrol
|
||||
- Parent Control: https://github.com/sirpdboy/luci-theme-parentcontrol
|
||||
- Eqos Plus: https://github.com/sirpdboy/luci-app-eqosplus
|
||||
- Advanced : https://github.com/sirpdboy/luci-app-advanced
|
||||
- ddns-go : https://github.com/sirpdboy/luci-app-ddns-go
|
||||
- Advanced Plus): https://github.com/sirpdboy/luci-app-advancedplus
|
||||
- Net Wizard: https://github.com/sirpdboy/luci-app-netwizard
|
||||
- Part Exp: https://github.com/sirpdboy/luci-app-partexp
|
||||
- Lukcy: https://github.com/sirpdboy/luci-app-lukcy
|
||||
|
||||
## HELP
|
||||
|
||||
| <img src="https://img.shields.io/badge/-Alipay-F5F5F5.svg" href="#赞助支持本项目-" height="25" alt="图飞了"/> | <img src="https://img.shields.io/badge/-WeChat-F5F5F5.svg" height="25" alt="图飞了" href="#赞助支持本项目-"/> |
|
||||
| :-----------------: | :-------------: |
|
||||
| |  |
|
||||
|
||||
<a href="#readme">
|
||||
<img src="https://img.shields.io/badge/-TOP-orange.svg" alt="no" title="Return TOP" align="right"/>
|
||||
</a>
|
||||
|
||||
 [](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ)
|
||||
@@ -0,0 +1,109 @@
|
||||
 [](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ)
|
||||
|
||||
<h1 align="center">
|
||||
<br>luci-app-eqosplus<br>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
|
||||
<a target="_blank" href="https://github.com/sirpdboy/luci-app-eqosplus/releases">
|
||||
<img src="https://img.shields.io/github/release/sirpdboy/luci-app-eqosplus.svg?style=flat-square&label=luci-app-eqosplus&colorB=green">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[中文] | [English](README.md)
|
||||
|
||||

|
||||
|
||||
请 **认真阅读完毕** 本页面,本页面包含注意事项和如何使用。
|
||||
|
||||
## 功能说明:
|
||||
|
||||
### 定时限速1.2.2版
|
||||
#### 2023.7.19 定时限速1.2.2:增加更多日期:工作日和休息日,自定义日期1,2,3中间用逗号分隔;加入MAC地址限速,从此不用担心IPV6和IPV4的限速问题。
|
||||
|
||||
### 定时限速1.0版
|
||||
#### 2022.12.24 定时限速在eqos的加强版,加入定时限制等功能。
|
||||
|
||||
## 编译使用方法 [](#编译使用方法-)
|
||||
|
||||
将luci-app-eqosplus添加至 LEDE/OpenWRT 源码的方法。
|
||||
|
||||
### 下载源码方法一:
|
||||
编辑源码文件夹根目录feeds.conf.default并加入如下内容:
|
||||
|
||||
```Brach
|
||||
# feeds获取源码:
|
||||
src-git eqosplus https://github.com/sirpdboy/luci-app-eqosplus
|
||||
```
|
||||
```Brach
|
||||
# 更新feeds,并安装主题:
|
||||
scripts/feeds update eqosplus
|
||||
scripts/feeds install luci-app-eqosplus
|
||||
```
|
||||
|
||||
### 下载源码方法:
|
||||
```Brach
|
||||
# 下载源码
|
||||
git clone https://github.com/sirpdboy/luci-app-eqosplus package/luci-app-eqosplus
|
||||
make menuconfig
|
||||
```
|
||||
### 配置菜单
|
||||
```Brach
|
||||
make menuconfig
|
||||
# 找到 LuCI -> Applications, 选择 luci-app-eqosplus, 保存后退出。
|
||||
```
|
||||
### 编译
|
||||
```Brach
|
||||
# 编译固件
|
||||
make package/luci-app-eqosplus/compile V=s
|
||||
```
|
||||
|
||||
## 说明 [](#说明-)
|
||||
|
||||
源码来源:https://github.com/sirpdboy/luci-app-eqosplus
|
||||
|
||||
|
||||
- 你可以随意使用其中的源码,但请注明出处。
|
||||
|
||||

|
||||
|
||||
## 界面
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
# My other project
|
||||
|
||||
- 路由安全看门狗 :https://github.com/sirpdboy/luci-app-watchdog
|
||||
- 网络速度测试 :https://github.com/sirpdboy/luci-app-netspeedtest
|
||||
- 计划任务插件(原定时设置) : https://github.com/sirpdboy/luci-app-taskplan
|
||||
- 关机功能插件 : https://github.com/sirpdboy/luci-app-poweroffdevice
|
||||
- opentopd主题 : https://github.com/sirpdboy/luci-theme-opentopd
|
||||
- kucat酷猫主题: https://github.com/sirpdboy/luci-theme-kucat
|
||||
- kucat酷猫主题设置工具: https://github.com/sirpdboy/luci-app-kucat-config
|
||||
- NFT版上网时间控制插件: https://github.com/sirpdboy/luci-app-timecontrol
|
||||
- 家长控制: https://github.com/sirpdboy/luci-theme-parentcontrol
|
||||
- 定时限速: https://github.com/sirpdboy/luci-app-eqosplus
|
||||
- 系统高级设置 : https://github.com/sirpdboy/luci-app-advanced
|
||||
- ddns-go动态域名: https://github.com/sirpdboy/luci-app-ddns-go
|
||||
- 进阶设置(系统高级设置+主题设置kucat/agron/opentopd): https://github.com/sirpdboy/luci-app-advancedplus
|
||||
- 网络设置向导: https://github.com/sirpdboy/luci-app-netwizard
|
||||
- 一键分区扩容: https://github.com/sirpdboy/luci-app-partexp
|
||||
- lukcy大吉: https://github.com/sirpdboy/luci-app-lukcy
|
||||
|
||||
## 捐助
|
||||
|
||||

|
||||
|
||||
| <img src="https://img.shields.io/badge/-支付宝-F5F5F5.svg" href="#赞助支持本项目-" height="25" alt="图飞了"/> | <img src="https://img.shields.io/badge/-微信-F5F5F5.svg" height="25" alt="图飞了" href="#赞助支持本项目-"/> |
|
||||
| :-----------------: | :-------------: |
|
||||
| |  |
|
||||
|
||||
<a href="#readme">
|
||||
<img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="图飞了" title="返回顶部" align="right"/>
|
||||
</a>
|
||||
|
||||
 [](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 645 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 664 KiB |
@@ -0,0 +1,19 @@
|
||||
module("luci.controller.eqosplus", package.seeall)
|
||||
-- Copyright 2022-2023 sirpdboy <herboy2008@gmail.com>
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/eqosplus") then return end
|
||||
entry({"admin", "control"}, firstchild(), "Control", 44).dependent = false
|
||||
|
||||
local e = entry({"admin", "control", "eqosplus"}, cbi("eqosplus"), _("Eqosplus"), 10)
|
||||
e.dependent=false
|
||||
e.acl_depends = { "luci-app-eqosplus" }
|
||||
entry({"admin", "control", "eqosplus", "status"}, call("act_status")).leaf = true
|
||||
end
|
||||
|
||||
function act_status()
|
||||
local sys = require "luci.sys"
|
||||
local e = {}
|
||||
e.status = sys.call(" busybox ps -w | grep eqosplus | grep -v grep >/dev/null ") == 0
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
@@ -0,0 +1,190 @@
|
||||
-- Copyright 2022-2023 sirpdboy <herboy2008@gmail.com>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
local sys = require "luci.sys"
|
||||
local ifaces = sys.net:devices()
|
||||
local WADM = require "luci.tools.webadmin"
|
||||
local ipc = require "luci.ip"
|
||||
local a, t, e
|
||||
|
||||
a = Map("eqosplus", translate("Network speed limit"))
|
||||
a.description = translate("Users can limit the network speed for uploading/downloading through MAC, IP, and IP segments (192.168.110.00-192.168.10.200). The speed unit is MB/second.")
|
||||
a.template = "eqosplus/index"
|
||||
|
||||
t = a:section(TypedSection, "eqosplus")
|
||||
t.anonymous = true
|
||||
|
||||
e = t:option(DummyValue, "eqosplus_status", translate("Status"))
|
||||
e.template = "eqosplus/eqosplus"
|
||||
e.value = translate("Collecting data...")
|
||||
|
||||
|
||||
ipi = t:option(ListValue, "ifname", translate("Interface"), translate("Set the interface used for restriction, use pppoe-wan for dialing, use WAN hardware interface for DHCP mode (such as eth1), and use br-lan for bypass mode"))
|
||||
ipi.default = "1"
|
||||
ipi:value(1,translate("Automatic settings"))
|
||||
ipi:value("br-lan", translate("br-lan (LAN Bridge)"))
|
||||
local function get_wan_interfaces()
|
||||
local result = {}
|
||||
local ubus = require "ubus"
|
||||
|
||||
local conn = ubus.connect()
|
||||
if not conn then
|
||||
return result
|
||||
end
|
||||
|
||||
local network_status = conn:call("network.interface", "dump", {})
|
||||
for _, iface in ipairs(network_status.interface) do
|
||||
if iface.interface:match("^wan") or iface.interface:match("^pppoe") or iface.proto == "pppoe" then
|
||||
local dev = iface.l3_device or iface.device
|
||||
if dev then
|
||||
table.insert(result, {
|
||||
name = dev,
|
||||
proto = iface.proto,
|
||||
logical_name = iface.interface
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
conn:close()
|
||||
return result
|
||||
end
|
||||
|
||||
local wan_ifaces = get_wan_interfaces()
|
||||
for _, iface in ipairs(wan_ifaces) do
|
||||
ipi:value(iface.name, translate(iface.name) .. (iface.proto == "pppoe" and " (PPPoE)" or " (WAN)"))
|
||||
end
|
||||
|
||||
t = a:section(TypedSection, "device")
|
||||
t.template = "cbi/tblsection"
|
||||
t.anonymous = true
|
||||
t.addremove = true
|
||||
|
||||
comment = t:option(Value, "comment", translate("Comment"))
|
||||
comment.size = 8
|
||||
|
||||
e = t:option(Flag, "enable", translate("Enabled"))
|
||||
e.rmempty = false
|
||||
e.size = 4
|
||||
|
||||
ip = t:option(Value, "mac", translate("IP/MAC"))
|
||||
ip.size = 8
|
||||
|
||||
local function get_devices()
|
||||
local devices = {}
|
||||
local seen_ips = {}
|
||||
local ubus = require "ubus"
|
||||
local conn = ubus.connect()
|
||||
|
||||
local function get_hostname(ip)
|
||||
local f = io.popen("nslookup "..ip.." 2>/dev/null | grep 'name =' | cut -d'=' -f2 | sed 's/\\.$//'")
|
||||
if f then
|
||||
local name = f:read("*l")
|
||||
f:close()
|
||||
if name and name ~= "" then
|
||||
return name:match("^%s*(.-)%s*$")
|
||||
end
|
||||
end
|
||||
local leases_file = io.open("/tmp/dhcp.leases", "r")
|
||||
if leases_file then
|
||||
for line in leases_file:lines() do
|
||||
local mac, ip_lease, _, hostname = line:match("^(%S+)%s+(%S+)%s+(%S+)%s+(%S+)")
|
||||
if ip_lease == ip and hostname ~= "*" then
|
||||
leases_file:close()
|
||||
return hostname
|
||||
end
|
||||
end
|
||||
leases_file:close()
|
||||
end
|
||||
return "unknown"
|
||||
end
|
||||
if conn then
|
||||
local leases = conn:call("dhcp", "ipv4leases", {}) or {}
|
||||
for _, lease in ipairs(leases) do
|
||||
if lease.ipaddr and lease.mac then
|
||||
local hostname = lease.hostname or get_hostname(lease.ipaddr)
|
||||
devices[#devices+1] = {
|
||||
ip = lease.ipaddr,
|
||||
mac = lease.mac:upper(),
|
||||
hostname = hostname,
|
||||
display = string.format("%s (%s) - %s", lease.ipaddr, lease.mac:upper(), hostname)
|
||||
}
|
||||
seen_ips[lease.ipaddr] = true
|
||||
end
|
||||
end
|
||||
conn:close()
|
||||
end
|
||||
local arp_cmd = io.popen("ip -4 neigh show dev br-lan 2>/dev/null")
|
||||
if arp_cmd then
|
||||
for line in arp_cmd:lines() do
|
||||
local ip_addr, mac = line:match("^(%S+)%s+.+%s+(%S+)%s+")
|
||||
if ip_addr and mac and mac ~= "00:00:00:00:00:00" and not seen_ips[ip_addr] then
|
||||
mac = mac:upper()
|
||||
local hostname = get_hostname(ip_addr)
|
||||
devices[#devices+1] = {
|
||||
ip = ip_addr,
|
||||
mac = mac,
|
||||
hostname = hostname,
|
||||
display = string.format("%s (%s) - %s", ip_addr, mac, hostname)
|
||||
}
|
||||
seen_ips[ip_addr] = true
|
||||
end
|
||||
end
|
||||
arp_cmd:close()
|
||||
end
|
||||
table.sort(devices, function(a, b) return a.ip < b.ip end)
|
||||
return devices
|
||||
end
|
||||
|
||||
local devices = get_devices()
|
||||
for _, dev in ipairs(devices) do
|
||||
ip:value(dev.ip, dev.display)
|
||||
end
|
||||
dl = t:option(Value, "download", translate("Downloads"))
|
||||
dl.default = '0.1'
|
||||
dl.size = 4
|
||||
|
||||
ul = t:option(Value, "upload", translate("Uploads"))
|
||||
ul.default = '0.1'
|
||||
ul.size = 4
|
||||
function validate_time(self, value, section)
|
||||
local hh, mm, ss
|
||||
hh, mm, ss = string.match (value, "^(%d?%d):(%d%d)$")
|
||||
hh = tonumber (hh)
|
||||
mm = tonumber (mm)
|
||||
if hh and mm and hh <= 23 and mm <= 59 then
|
||||
return value
|
||||
else
|
||||
return nil, "Time HH:MM or space"
|
||||
end
|
||||
end
|
||||
|
||||
e = t:option(Value, "timestart", translate("Start control time"))
|
||||
e.placeholder = '00:00'
|
||||
e.default = '00:00'
|
||||
e.validate = validate_time
|
||||
e.rmempty = true
|
||||
e.size = 4
|
||||
|
||||
e = t:option(Value, "timeend", translate("Stop control time"))
|
||||
e.placeholder = '00:00'
|
||||
e.default = '00:00'
|
||||
e.validate = validate_time
|
||||
e.rmempty = true
|
||||
e.size = 4
|
||||
|
||||
week=t:option(Value,"week",translate("Week Day(1~7)"))
|
||||
week.rmempty = true
|
||||
week:value('0',translate("Everyday"))
|
||||
week:value(1,translate("Monday"))
|
||||
week:value(2,translate("Tuesday"))
|
||||
week:value(3,translate("Wednesday"))
|
||||
week:value(4,translate("Thursday"))
|
||||
week:value(5,translate("Friday"))
|
||||
week:value(6,translate("Saturday"))
|
||||
week:value(7,translate("Sunday"))
|
||||
week:value('1,2,3,4,5',translate("Workday"))
|
||||
week:value('6,7',translate("Rest Day"))
|
||||
week.default='0'
|
||||
week.size = 6
|
||||
|
||||
return a
|
||||
@@ -0,0 +1,3 @@
|
||||
<%+cbi/valueheader%>
|
||||
<font class="eqosplus_status"><%=pcdata(self:cfgvalue(section) or self.default or "")%></font>
|
||||
<%+cbi/valuefooter%>
|
||||
@@ -0,0 +1,12 @@
|
||||
<% include("cbi/map") %>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "control", "eqosplus", "status" )%>', null,
|
||||
function (x, result) {
|
||||
var status = document.getElementsByClassName('eqosplus_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,61 @@
|
||||
|
||||
msgid "Eqosplus"
|
||||
msgstr ""
|
||||
|
||||
msgid "Network speed limit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Running state"
|
||||
msgstr ""
|
||||
|
||||
msgid "Not running"
|
||||
msgstr ""
|
||||
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
msgid "Users can limit the network speed for uploading/downloading through MAC, IP, and IP segments (192.168.110.00-192.168.10.200). The speed unit is MB/second."
|
||||
msgstr ""
|
||||
|
||||
msgid "MAC/IP"
|
||||
msgstr ""
|
||||
|
||||
msgid "Downloads"
|
||||
msgstr ""
|
||||
|
||||
msgid "Uploads"
|
||||
msgstr ""
|
||||
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Upload bandwidth(Mbit/s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Download bandwidth(Mbit/s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start control time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Stop control time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Week Day(1~7)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Suggested feedback:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the interface used for restriction, use pppoe-wan for dialing, use WAN hardware interface for DHCP mode (such as eth1), and use br-lan for bypass mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Rest Day"
|
||||
msgstr ""
|
||||
|
||||
msgid "Workday"
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatic settings"
|
||||
msgstr ""
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
msgid "Eqosplus"
|
||||
msgstr "定时限速"
|
||||
|
||||
msgid "Network speed limit"
|
||||
msgstr "定时网速限制"
|
||||
|
||||
msgid "Running state"
|
||||
msgstr "运行状态"
|
||||
|
||||
msgid "Not running"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "Running"
|
||||
msgstr "已运行"
|
||||
|
||||
msgid "Users can limit the network speed for uploading/downloading through MAC, IP.The speed unit is MB/second."
|
||||
msgstr "可以通过MAC,IP限制用户上传/下载的网速。速度单位为<font color=\"red\"><b> MB/秒 </b></font>。"
|
||||
|
||||
msgid "MAC/IP"
|
||||
msgstr "MAC/IP"
|
||||
|
||||
msgid "Downloads"
|
||||
msgstr "下载速度"
|
||||
|
||||
msgid "Uploads"
|
||||
msgstr "上传速度"
|
||||
|
||||
msgid "Comment"
|
||||
msgstr "备注"
|
||||
|
||||
msgid "Upload bandwidth(Mbit/s)"
|
||||
msgstr "上传总带宽(Mbit/s)"
|
||||
|
||||
msgid "Download bandwidth(Mbit/s)"
|
||||
msgstr "下载总带宽(Mbit/s)"
|
||||
|
||||
msgid "Start control time"
|
||||
msgstr "起控时间"
|
||||
|
||||
msgid "Stop control time"
|
||||
msgstr "停控时间"
|
||||
|
||||
msgid "Week Day(1~7)"
|
||||
msgstr "星期(1~7)"
|
||||
|
||||
msgid "Suggested feedback:"
|
||||
msgstr "建议反馈:"
|
||||
|
||||
msgid "Set the interface used for restriction, use pppoe-wan for dialing, use WAN hardware interface for DHCP mode (such as eth1), and use br-lan for bypass mode"
|
||||
msgstr "设定用来限制网络速度的接口,拨号使用pppoe-wan,DHCP模式使用WAN口的硬件接口(如:eth1),通用使用br-lan接口"
|
||||
|
||||
msgid "Rest Day"
|
||||
msgstr "休息日"
|
||||
|
||||
msgid "Workday"
|
||||
msgstr "工作日"
|
||||
|
||||
msgid "Automatic settings"
|
||||
msgstr "自动设置"
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
msgid "Eqosplus"
|
||||
msgstr "定时限速"
|
||||
|
||||
msgid "Network speed limit"
|
||||
msgstr "定时网速限制"
|
||||
|
||||
msgid "Running state"
|
||||
msgstr "运行状态"
|
||||
|
||||
msgid "Not running"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "Running"
|
||||
msgstr "已运行"
|
||||
|
||||
msgid "Users can limit the network speed for uploading/downloading through MAC, IP, and IP segments (192.168.110.00-192.168.10.200). The speed unit is MB/second."
|
||||
msgstr "用户可以通过MAC、IP、IP段(192.168.10.100-192.168.10.200),限制上传/下载的网络速度。速度单位为MB/秒。。"
|
||||
|
||||
msgid "MAC/IP"
|
||||
msgstr "MAC/IP"
|
||||
|
||||
msgid "Downloads"
|
||||
msgstr "下载速度"
|
||||
|
||||
msgid "Uploads"
|
||||
msgstr "上传速度"
|
||||
|
||||
msgid "Comment"
|
||||
msgstr "备注"
|
||||
|
||||
msgid "Upload bandwidth(Mbit/s)"
|
||||
msgstr "上传总带宽(Mbit/s)"
|
||||
|
||||
msgid "Download bandwidth(Mbit/s)"
|
||||
msgstr "下载总带宽(Mbit/s)"
|
||||
|
||||
msgid "Start control time"
|
||||
msgstr "起控时间"
|
||||
|
||||
msgid "Stop control time"
|
||||
msgstr "停控时间"
|
||||
|
||||
msgid "Week Day(1~7)"
|
||||
msgstr "星期(1~7)"
|
||||
|
||||
msgid "Suggested feedback:"
|
||||
msgstr "建议反馈:"
|
||||
|
||||
msgid "Set the interface used for restriction, use pppoe-wan for dialing, use WAN hardware interface for DHCP mode (such as eth1), and use br-lan for bypass mode"
|
||||
msgstr "设定用来限制网络速度的接口,拨号使用pppoe-wan,DHCP模式使用WAN口的硬件接口(如:eth1),通用使用br-lan接口"
|
||||
|
||||
msgid "Rest Day"
|
||||
msgstr "休息日"
|
||||
|
||||
msgid "Workday"
|
||||
msgstr "工作日"
|
||||
|
||||
msgid "Automatic settings"
|
||||
msgstr "自动设置"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
config eqosplus
|
||||
option enabled '0'
|
||||
option download '1000'
|
||||
option upload '30'
|
||||
option ifname '1'
|
||||
|
||||
config device
|
||||
option timestart '00:00'
|
||||
option week '0'
|
||||
option download '1'
|
||||
option timeend '23:55'
|
||||
option upload '1'
|
||||
option mac '192.168.10.10'
|
||||
option enable '0'
|
||||
|
||||
config device
|
||||
option mac '192.168.10.10-192.168.10.200'
|
||||
option timestart '00:00'
|
||||
option timeend '00:00'
|
||||
option week '0'
|
||||
option download '0.1'
|
||||
option upload '0.1'
|
||||
option enable '0'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
[ "$ACTION" = "ifup" ] && [ "$INTERFACE" = "wan" ] && {
|
||||
sleep 5
|
||||
/etc/init.d/eqosplus restart
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
#
|
||||
# Copyright (C) 2025 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-eqosplus
|
||||
#
|
||||
|
||||
START=99
|
||||
USE_PROCD=1
|
||||
|
||||
NAME=eqosplus
|
||||
LOCK="/var/lock/$NAME.lock"
|
||||
CR=/etc/crontabs/root
|
||||
|
||||
start_instance() {
|
||||
procd_open_instance
|
||||
procd_set_param command /usr/bin/eqosplusctrl
|
||||
procd_set_param respawn
|
||||
procd_set_param stderr 1
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
_eqosplus_start() {
|
||||
if [ "$(grep -c 'option enable .1.' /etc/config/$NAME 2>/dev/null)" -gt "0" ]; then
|
||||
if [ x$(uci -q get turboacc.config.sw_flow) = 'x1' ] || [ x$(uci -q get turboacc.config.sfe_flow) = 'x1' ] ;then
|
||||
uci -q set turboacc.config.sw_flow='0'
|
||||
uci -q set turboacc.config.sfe_flow='0'
|
||||
uci -q set turboacc.config.hw_flow='0'
|
||||
uci commit turboacc
|
||||
/etc/init.d/turboacc restart
|
||||
fi
|
||||
touch $LOCK
|
||||
eqosplus start
|
||||
start_instance
|
||||
(crontab -l ; echo "00 1 * * * /etc/init.d/eqosplus start") | sort - | uniq - | crontab -
|
||||
fi
|
||||
}
|
||||
|
||||
start_service(){
|
||||
[ -f $LOCK ] && exit
|
||||
stop_service
|
||||
_eqosplus_start
|
||||
rm -f $LOCK
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger 'eqosplus'
|
||||
}
|
||||
|
||||
stop_service(){
|
||||
kill -9 $(busybox ps -w | grep 'eqosplusctrl' | grep -v 'grep' | awk '{print $1}') >/dev/null 2>&1
|
||||
sed -i '/eqosplus/d' $CR >/dev/null 2>&1
|
||||
rm -f $LOCK 2>/dev/null
|
||||
eqosplus stop
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
chmod +x /etc/init.d/eqosplus /usr/bin/eqosplus*
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@eqosplus[-1]
|
||||
add ucitrack eqosplus
|
||||
set ucitrack.@eqosplus[-1].init=eqosplus
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
[ -s /etc/config/eqosplus ] || echo "config eqosplus" > /etc/config/eqosplus
|
||||
|
||||
rm -f /tmp/luci-indexcache
|
||||
exit 0
|
||||
@@ -0,0 +1,379 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (C) 2006 OpenWrt.org
|
||||
# Copyright 2022-2025 sirpdboy <herboy2008@gmail.com>
|
||||
|
||||
crrun=$1
|
||||
crid=$2
|
||||
NAME=eqosplus
|
||||
IDLIST="/var/$NAME.idlist"
|
||||
LOCK="/var/lock/$NAME.lock"
|
||||
TMPID="/var/$NAME.tmpid"
|
||||
LOG="/var/$NAME_log.log"
|
||||
# idlist=`uci show $NAME | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' `
|
||||
|
||||
|
||||
if [ x$(uci get $NAME.@$NAME[0].ifname 2>/dev/null) = 'x1' ]; then
|
||||
ifname=$(uci -q get network.lan.ifname)
|
||||
[ "x$ifname" = "x" ] && ifname="device" || ifname="ifname"
|
||||
dev=$(uci -q get network.wan.$ifname)
|
||||
[ ! "$dev" ] && dev=br-lan
|
||||
else
|
||||
dev=$(uci -q get $NAME.@$NAME[0].ifname 2>/dev/null)
|
||||
[ ! "$dev" ] && dev=br-lan
|
||||
fi
|
||||
|
||||
bin_nft=$(which nft)
|
||||
bin_tc=$(which tc)
|
||||
bin_ip=$(which ip)
|
||||
DEBUG=1
|
||||
|
||||
nft_type() {
|
||||
if command -v nft >/dev/null; then
|
||||
nftables_ver="true"
|
||||
fi
|
||||
}
|
||||
|
||||
# Debug functions
|
||||
dbg_nft() {
|
||||
[ "${DEBUG:-0}" -eq 0 ] || echo "DEBUG: nft $*"
|
||||
$bin_nft "$@"
|
||||
}
|
||||
|
||||
dbg_tc() {
|
||||
[ "${DEBUG:-0}" -eq 0 ] || echo "DEBUG: tc $*"
|
||||
$bin_tc "$@"
|
||||
}
|
||||
|
||||
dbg_ip() {
|
||||
[ "${DEBUG:-0}" -eq 0 ] || echo "DEBUG: ip $*"
|
||||
$bin_ip "$@"
|
||||
}
|
||||
|
||||
is_macaddr() {
|
||||
ret=1
|
||||
if echo "$1" | grep -qE '^([0-9A-Fa-f]{2}[-:]){5}[0-9A-Fa-f]{2}$'; then
|
||||
ret=0
|
||||
fi
|
||||
return $ret
|
||||
}
|
||||
|
||||
# 数值比较函数
|
||||
is_gt_zero() {
|
||||
local value=$1
|
||||
# 使用bc进行浮点数比较
|
||||
echo "$value > 0" | bc -l 2>/dev/null | grep -q 1
|
||||
}
|
||||
|
||||
# 转换为kbit
|
||||
to_kbit() {
|
||||
local mb=$1
|
||||
# MB/s 转 kbit/s: 1 MB/s = 8192 kbit/s
|
||||
echo "scale=0; $mb * 8192 / 1" | bc 2>/dev/null || echo "0"
|
||||
}
|
||||
|
||||
# Default commands
|
||||
tc="dbg_tc"
|
||||
ip="dbg_ip"
|
||||
nft="dbg_nft"
|
||||
|
||||
nft_type
|
||||
|
||||
stop_qos() {
|
||||
for face in $(tc qdisc show 2>/dev/null | grep htb | awk '{print $5}'); do
|
||||
$tc qdisc del dev $face root 2>/dev/null
|
||||
done
|
||||
|
||||
$tc qdisc del dev ${dev} root 2>/dev/null
|
||||
$tc qdisc del dev ${dev}_ifb root 2>/dev/null
|
||||
$tc qdisc del dev ${dev} ingress 2>/dev/null
|
||||
$ip link del dev ${dev}_ifb 2>/dev/null 2>&1
|
||||
|
||||
if [ -n "$nftables_ver" ]; then
|
||||
$nft delete table inet ${NAME} 2>/dev/null
|
||||
$nft delete table bridge ${NAME} 2>/dev/null
|
||||
fi
|
||||
|
||||
echo "" > "$IDLIST" 2>/dev/null
|
||||
}
|
||||
|
||||
init_qosplus() {
|
||||
|
||||
insmod sch_htb 2>/dev/null
|
||||
insmod act_mirred 2>/dev/null
|
||||
insmod ifb 2>/dev/null
|
||||
|
||||
$ip link del dev ${dev}_ifb 2>/dev/null
|
||||
$ip link add dev ${dev}_ifb name ${dev}_ifb type ifb
|
||||
$ip link set dev ${dev}_ifb up
|
||||
|
||||
$tc qdisc del dev ${dev} root 2>/dev/null
|
||||
$tc qdisc add dev ${dev} root handle 1:0 htb default 1
|
||||
$tc class add dev ${dev} parent 1:0 classid 1:1 htb rate 1000mbit ceil 1000mbit
|
||||
|
||||
$tc qdisc del dev ${dev}_ifb root 2>/dev/null
|
||||
$tc qdisc add dev ${dev}_ifb root handle 1:0 htb default 1
|
||||
$tc class add dev ${dev}_ifb parent 1:0 classid 1:1 htb rate 1000mbit ceil 1000mbit
|
||||
lanipaddr=$(uci -q get network.lan.ipaddr 2>/dev/null | awk -F '.' '{print $1"."$2"."$3".0/24"}')
|
||||
if [ -n "$lanipaddr" ]; then
|
||||
$tc filter add dev $dev parent 1:0 protocol ip prio 1 u32 match ip src "$lanipaddr" match ip dst "$lanipaddr" flowid 1:1
|
||||
$tc filter add dev ${dev}_ifb parent 1:0 protocol ip prio 1 u32 match ip src "$lanipaddr" match ip dst "$lanipaddr" flowid 1:1
|
||||
fi
|
||||
$tc qdisc del dev ${dev} ingress 2>/dev/null
|
||||
$tc qdisc add dev ${dev} ingress
|
||||
$tc filter add dev ${dev} parent ffff: protocol all prio 2 u32 match u32 0 0 flowid 1:1 action mirred egress redirect dev ${dev}_ifb
|
||||
|
||||
if [ -n "$nftables_ver" ]; then
|
||||
$nft delete table inet ${NAME} 2>/dev/null
|
||||
$nft delete table bridge ${NAME} 2>/dev/null
|
||||
|
||||
$nft add table inet ${NAME}
|
||||
|
||||
$nft add chain inet ${NAME} mark_forward { type filter hook forward priority -200\; policy accept\; }
|
||||
$nft add chain inet ${NAME} mark_output { type filter hook output priority -200\; policy accept\; }
|
||||
$nft add chain inet ${NAME} mark_postrouting { type filter hook postrouting priority -200\; policy accept\; }
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
del_id() {
|
||||
local list_id=$1
|
||||
id=$((list_id * 10 + 1000))
|
||||
[ "${DEBUG:-0}" -eq 0 ] || echo "D: del_id $@ --$id --$mac"
|
||||
$tc filter del dev ${dev} parent 1: handle 800::$(printf "%x" $id) 2>/dev/null
|
||||
$tc class del dev ${dev} parent 1:1 classid 1:$id 2>/dev/null
|
||||
$tc filter del dev ${dev}_ifb parent 1: handle 800::$(printf "%x" $id) 2>/dev/null
|
||||
$tc class del dev ${dev}_ifb parent 1:1 classid 1:$id 2>/dev/null
|
||||
mac=$(uci -q get $NAME.@device[$list_id].mac 2>/dev/null)
|
||||
if [ -n "$nftables_ver" ] && [ -n "$mac" ]; then
|
||||
if is_macaddr "$mac"; then
|
||||
$nft delete rule inet ${NAME} mark_forward ether saddr $mac counter 2>/dev/null
|
||||
$nft delete rule inet ${NAME} mark_output ether saddr $mac counter 2>/dev/null
|
||||
$nft delete rule inet ${NAME} mark_postrouting ether saddr $mac counter 2>/dev/null
|
||||
else
|
||||
Z=$(echo $mac | awk -F '[/]' '{print $2}')
|
||||
[ -n "$Z" ] && mac=$(echo $mac | awk -F '[/]' '{print $1}') || Z=32
|
||||
$nft delete rule inet ${NAME} mark_forward ip saddr $mac/$Z counter 2>/dev/null
|
||||
$nft delete rule inet ${NAME} mark_output ip saddr $mac/$Z counter 2>/dev/null
|
||||
$nft delete rule inet ${NAME} mark_postrouting ip saddr $mac/$Z counter 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
add_mac() {
|
||||
local list_id=$1
|
||||
id=$((list_id * 10 + 1000))
|
||||
M0=$(echo $mac | cut -d : -f 1)$(echo $mac | cut -d : -f 2)
|
||||
M1=$(echo $mac | cut -d : -f 3)$(echo $mac | cut -d : -f 4)
|
||||
M2=$(echo $mac | cut -d : -f 5)$(echo $mac | cut -d : -f 6)
|
||||
[ "${DEBUG:-0}" -eq 0 ] || echo "D: add_mac $@ --id:$id --mac:$mac M012--$M0-$M1-$M2"
|
||||
|
||||
if is_gt_zero "$UL"; then
|
||||
if [ -n "$nftables_ver" ]; then
|
||||
$nft add rule inet ${NAME} mark_forward ether saddr $mac counter meta mark set $id
|
||||
$nft add rule inet ${NAME} mark_output ether saddr $mac counter meta mark set $id
|
||||
$nft add rule inet ${NAME} mark_postrouting ether saddr $mac counter meta mark set $id
|
||||
fi
|
||||
$tc class add dev ${dev}_ifb parent 1:1 classid 1:$id htb rate "${UL}kbit" ceil "${UL}kbit" prio $id burst 15k cburst 15k
|
||||
$tc qdisc add dev ${dev}_ifb parent 1:$id handle ${id}: sfq perturb 10
|
||||
|
||||
$tc filter add dev ${dev}_ifb parent 1: protocol ip prio $id handle $id fw flowid 1:$id
|
||||
|
||||
$tc filter add dev ${dev}_ifb parent 1: protocol ip prio $((id + 100)) u32 \
|
||||
match u16 0x0800 0xFFFF at -2 \
|
||||
match u16 0x${M2} 0xFFFF at -4 \
|
||||
match u32 0x${M0}${M1} 0xFFFFFFFF at -8 \
|
||||
flowid 1:$id
|
||||
fi
|
||||
if is_gt_zero "$DL"; then
|
||||
$tc class add dev ${dev} parent 1:1 classid 1:$id htb rate "${DL}kbit" ceil "${DL}kbit" prio $id burst 15k cburst 15k
|
||||
$tc qdisc add dev ${dev} parent 1:$id handle ${id}: sfq perturb 10
|
||||
|
||||
$tc filter add dev ${dev} parent 1: protocol ip prio $id u32 \
|
||||
match u16 0x0800 0xFFFF at -2 \
|
||||
match u32 0x${M1}${M2} 0xFFFFFFFF at -12 \
|
||||
match u16 0x${M0} 0xFFFF at -14 \
|
||||
flowid 1:$id
|
||||
fi
|
||||
}
|
||||
|
||||
add_ip() {
|
||||
local list_id=$1
|
||||
id=$((list_id * 10 + 1000))
|
||||
[ "${DEBUG:-0}" -eq 0 ] || echo "D: add_ip $@ --$id --$mac"
|
||||
|
||||
Z=$(echo $mac | awk -F '[/]' '{print $2}')
|
||||
[ -n "$Z" ] && mac=$(echo $mac | awk -F '[/]' '{print $1}') || Z=32
|
||||
if is_gt_zero "$UL"; then
|
||||
if [ -n "$nftables_ver" ]; then
|
||||
$nft add rule inet ${NAME} mark_forward ip saddr $mac/$Z counter meta mark set $id
|
||||
$nft add rule inet ${NAME} mark_output ip saddr $mac/$Z counter meta mark set $id
|
||||
$nft add rule inet ${NAME} mark_postrouting ip saddr $mac/$Z counter meta mark set $id
|
||||
fi
|
||||
$tc class add dev ${dev}_ifb parent 1:1 classid 1:$id htb rate "${UL}kbit" ceil "${UL}kbit" prio $id burst 15k cburst 15k
|
||||
$tc qdisc add dev ${dev}_ifb parent 1:$id handle ${id}: sfq perturb 10
|
||||
$tc filter add dev ${dev}_ifb parent 1: protocol ip prio $id handle $id fw flowid 1:$id
|
||||
$tc filter add dev ${dev}_ifb parent 1:0 prio $((id + 100)) protocol ip u32 \
|
||||
match ip src "$mac"/"$Z" \
|
||||
flowid 1:$id
|
||||
fi
|
||||
if is_gt_zero "$DL"; then
|
||||
$tc class add dev ${dev} parent 1:1 classid 1:$id htb rate "${DL}kbit" ceil "${DL}kbit" prio $id burst 15k cburst 15k
|
||||
$tc qdisc add dev ${dev} parent 1:$id handle ${id}: sfq perturb 10
|
||||
$tc filter add dev ${dev} parent 1:0 prio $id protocol ip u32 \
|
||||
match ip dst "$mac"/"$Z" \
|
||||
flowid 1:$id
|
||||
fi
|
||||
}
|
||||
|
||||
check_time() {
|
||||
local start=$1
|
||||
local end=$2
|
||||
local current=$(date +%H%M)
|
||||
local start_min=$((10#${start:0:2}*60 + 10#${start:3:2}))
|
||||
local end_min=$((10#${end:0:2}*60 + 10#${end:3:2}))
|
||||
local current_min=$((10#${current:0:2}*60 + 10#${current:2:2}))
|
||||
|
||||
if [[ $start_min -lt $end_min ]]; then
|
||||
[[ $current_min -ge $start_min && $current_min -lt $end_min ]]
|
||||
else
|
||||
[[ $current_min -ge $start_min || $current_min -lt $end_min ]]
|
||||
fi
|
||||
}
|
||||
|
||||
check_list() {
|
||||
local i=$1
|
||||
local start_time=$(uci -q get $NAME.@device[$i].timestart 2>/dev/null)
|
||||
local end_time=$(uci -q get $NAME.@device[$i].timeend 2>/dev/null)
|
||||
local wweek=$(uci -q get $NAME.@device[$i].week 2>/dev/null)
|
||||
local current_weekday=$(date +%u)
|
||||
[ -z "$start_time" ] && [ -z "$end_time" ] && [ -z "$wweek" ] && return 0
|
||||
|
||||
if [ -n "$wweek" ] && [ "$wweek" != "0" ]; then
|
||||
local day_match=0
|
||||
for day in $(echo $wweek | tr ',' ' '); do
|
||||
[ "$day" -eq "$current_weekday" ] && day_match=1 && break
|
||||
done
|
||||
[ "$day_match" -eq 0 ] && return 1
|
||||
fi
|
||||
|
||||
if [ -n "$start_time" ] && [ -n "$end_time" ]; then
|
||||
check_time "$start_time" "$end_time" || return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$crrun" in
|
||||
"stop")
|
||||
stop_qos
|
||||
;;
|
||||
"start")
|
||||
stop_qos > /dev/null 2>&1
|
||||
sleep 2
|
||||
init_qosplus
|
||||
idlist=$(uci show $NAME 2>/dev/null | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' | sort -nu | sed -e 's/^/!/g' -e 's/$/!/g' > $IDLIST; cat $IDLIST 2>/dev/null | sed -e 's/!//g')
|
||||
|
||||
if [ ! -s "$IDLIST" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
device_count=0
|
||||
for list in $(echo $idlist | sed -e 's/!//g'); do
|
||||
if check_list $list; then
|
||||
mac=$(uci -q get $NAME.@device[$list].mac 2>/dev/null)
|
||||
download_mb=$(uci -q get $NAME.@device[$list].download 2>/dev/null)
|
||||
upload_mb=$(uci -q get $NAME.@device[$list].upload 2>/dev/null)
|
||||
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
|
||||
|
||||
# 转换为kbit
|
||||
DL=$(to_kbit "$download_mb")
|
||||
UL=$(to_kbit "$upload_mb")
|
||||
|
||||
if [ -n "$mac" ]; then
|
||||
if is_macaddr "$mac"; then
|
||||
add_mac $list >>$LOG
|
||||
echo "✅ MAC限速: $comment ($mac) - 下载:${download_mb}MB/s(${DL}kbit/s) 上传:${upload_mb}MB/s(${UL}kbit/s)" >>$LOG
|
||||
else
|
||||
add_ip $list >>$LOG
|
||||
echo "✅ IP限速: $comment ($mac) - 下载:${download_mb}MB/s(${DL}kbit/s) 上传:${upload_mb}MB/s(${UL}kbit/s)" >>$LOG
|
||||
fi
|
||||
device_count=$((device_count + 1))
|
||||
fi
|
||||
else
|
||||
[ -f "$IDLIST" ] && [ $(grep "!${list}!" "$IDLIST" 2>/dev/null | wc -l) -gt 0 ] && {
|
||||
del_id $list >>$LOG
|
||||
sed -i "/!$list!/d" "$IDLIST" >/dev/null 2>&1
|
||||
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
|
||||
}
|
||||
fi
|
||||
done
|
||||
echo "EQOSPLUS限速服务启动完成,共配置 $device_count 个设备" >>$LOG
|
||||
echo ""
|
||||
echo "当前限速状态:" >>$LOG
|
||||
tc -s class show dev $dev | grep "rate" | head -5 >>$LOG
|
||||
tc -s class show dev ${dev}_ifb | grep "rate" | head -5 >>$LOG
|
||||
|
||||
;;
|
||||
"add")
|
||||
echo "➕ 添加限速规则..."
|
||||
for list in $(echo $crid | sed -e 's/!//g' | sed 's/,/ /g'); do
|
||||
mac=$(uci -q get $NAME.@device[$list].mac 2>/dev/null)
|
||||
download_mb=$(uci -q get $NAME.@device[$list].download 2>/dev/null)
|
||||
upload_mb=$(uci -q get $NAME.@device[$list].upload 2>/dev/null)
|
||||
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
|
||||
DL=$(to_kbit "$download_mb")
|
||||
UL=$(to_kbit "$upload_mb")
|
||||
|
||||
if is_macaddr $mac; then
|
||||
add_mac $list
|
||||
echo "✅ 添加MAC限速: $comment ($mac)" >>$LOG
|
||||
else
|
||||
add_ip $list
|
||||
echo "✅ 添加IP限速: $comment ($mac)" >>$LOG
|
||||
fi
|
||||
done
|
||||
;;
|
||||
"del")
|
||||
for list in $(echo $crid | sed -e 's/!//g' | sed 's/,/ /g'); do
|
||||
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
|
||||
mac=$(uci -q get $NAME.@device[$list].mac 2>/dev/null)
|
||||
del_id $list
|
||||
done
|
||||
;;
|
||||
"status")
|
||||
echo "EQOSPLUS限速服务状态"
|
||||
echo "### 网络接口: $dev ###"
|
||||
echo "# 下载队列统计 #"
|
||||
tc -s qdisc show dev $dev 2>/dev/null || echo "无下载队列"
|
||||
echo "# 下载类统计 #"
|
||||
tc -s class show dev $dev 2>/dev/null | grep -E "class htb|rate [0-9]" || echo "无下载类"
|
||||
echo "# 下载过滤器 #"
|
||||
tc -s filter show dev $dev parent 1: 2>/dev/null | head -10 || echo "无下载过滤器"
|
||||
|
||||
echo "### 虚拟接口: ${dev}_ifb ###"
|
||||
echo "# 上传队列统计 #"
|
||||
tc -s qdisc show dev ${dev}_ifb 2>/dev/null || echo "无上传队列"
|
||||
echo "# 上传类统计 #"
|
||||
tc -s class show dev ${dev}_ifb 2>/dev/null | grep -E "class htb|rate [0-9]" || echo "无上传类"
|
||||
echo "# 上传过滤器 #"
|
||||
tc -s filter show dev ${dev}_ifb parent 1: 2>/dev/null | head -10 || echo "无上传过滤器"
|
||||
|
||||
if [ -n "$nftables_ver" ]; then
|
||||
echo "### NFTables规则 ###"
|
||||
nft list table inet ${NAME} 2>/dev/null || echo "没有NFTables规则"
|
||||
else
|
||||
echo "NFTables不可用"
|
||||
fi
|
||||
|
||||
# 显示流量统计
|
||||
echo "### 流量统计 ###"
|
||||
echo "下载流量:"
|
||||
tc -s class show dev $dev 2>/dev/null | grep "Sent" | head -5
|
||||
echo "上传流量:"
|
||||
tc -s class show dev ${dev}_ifb 2>/dev/null | grep "Sent" | head -5
|
||||
;;
|
||||
|
||||
esac
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2006 OpenWrt.org
|
||||
# Copyright 2022-2025 sirpdboy <herboy2008@gmail.com>
|
||||
NAME=eqosplus
|
||||
IDLIST="/var/$NAME.idlist"
|
||||
TMPID="/var/$NAME.tmpid"
|
||||
|
||||
idlist=`uci show $NAME | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' `
|
||||
|
||||
check_list() {
|
||||
i=$1
|
||||
checki='0'
|
||||
start_time=$(uci -q get $NAME.@device[$i].timestart 2>/dev/null)
|
||||
end_time=$(uci -q get $NAME.@device[$i].timeend 2>/dev/null)
|
||||
wweek=`uci -q get $NAME.@device[$i].week `
|
||||
current_time=$(date +%H:%M)
|
||||
current_weekday=$(date +%u)
|
||||
for ww in `echo $wweek | sed 's/,/ /g' `; do
|
||||
if [ $current_weekday = $ww ] || [ 'x0' = x$ww ] ;then
|
||||
[ "$start_time" = "$end_time" ] && checki='1' || {
|
||||
if [ "$start_time" \< "$end_time" ]; then
|
||||
if [ "$current_time" \> "$start_time" ] && [ "$current_time" \< "$end_time" ] ; then
|
||||
checki='1'
|
||||
fi
|
||||
else
|
||||
if [ "$start_time" \> "$end_time" ] ; then
|
||||
if [ "$current_time" \< "$start_time" ] && [ "$current_time" \> "$end_time" ] ; then
|
||||
checki='1'
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
fi
|
||||
done
|
||||
return
|
||||
}
|
||||
|
||||
idlistusr(){
|
||||
checki='0'
|
||||
[ -s $IDLIST ] || touch $IDLIST
|
||||
for list in $idlist ;do
|
||||
check_list $list
|
||||
if [ $checki == '1' ] ; then
|
||||
[ `cat $IDLIST 2>/dev/null | grep "!${list}!" | wc -l ` -gt 0 ] || {
|
||||
eqosplus add $list
|
||||
echo "!${list}!" >> $IDLIST ; cat $IDLIST | sort | uniq > $TMPID ;cat $TMPID >$IDLIST ;rm -rf $TMPID
|
||||
}
|
||||
else
|
||||
[ `cat $IDLIST 2>/dev/null | grep "!${list}!" | wc -l ` -gt 0 ] && {
|
||||
eqosplus del $list
|
||||
sed -i "/!$list!/d" $IDLIST >/dev/null 2>&1
|
||||
}
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
while :;do
|
||||
sleep 30
|
||||
idlistusr
|
||||
sleep 30
|
||||
done
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"luci-app-eqosplus": {
|
||||
"description": "Grant UCI access for luci-app-eqosplus",
|
||||
"read": {
|
||||
"uci": [ "eqosplus" ]
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "eqosplus" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 585 KiB |
@@ -3,12 +3,86 @@
|
||||
-- Copyright(c): lwb1978 2025–2027
|
||||
|
||||
local api = require("luci.passwall.api")
|
||||
local urlencode = api.UrlEncode
|
||||
local base64 = api.base64Encode
|
||||
local json = api.jsonc
|
||||
|
||||
local domain_redir = {}
|
||||
|
||||
local function urlencode(value)
|
||||
return api.UrlEncode(value == nil and "" or tostring(value))
|
||||
end
|
||||
|
||||
local function add_param(params, key, value)
|
||||
if value ~= nil and value ~= "" then
|
||||
if type(value) == "boolean" then value = value and "1" or "0" end
|
||||
params[#params + 1] = key .. "=" .. urlencode(value)
|
||||
end
|
||||
end
|
||||
|
||||
local function build_ech(node)
|
||||
local opts = node["ech-opts"]
|
||||
if type(opts) ~= "table" or opts.enable ~= true then return nil end
|
||||
if opts.config and opts.config ~= "" then return opts.config end
|
||||
local name = opts["query-server-name"]
|
||||
if not name or name == "" then name = node.servername or node.sni or node.server end
|
||||
if name == "" then name = node.server end
|
||||
return name .. "+https://223.5.5.5/dns-query"
|
||||
end
|
||||
|
||||
local function bandwidth_mbps(value)
|
||||
if value == nil then return nil end
|
||||
local number = tonumber(value)
|
||||
if number then return number end
|
||||
local amount, prefix, unit = tostring(value):match("^%s*(%d+)%s*([KMGT]?)([Bb])ps%s*$")
|
||||
local scales = { [""] = 1, K = 1000, M = 1000000, G = 1000000000, T = 1000000000000 }
|
||||
if not amount then return nil end
|
||||
return tonumber(amount) * scales[prefix] * (unit == "B" and 8 or 1) / 1000000
|
||||
end
|
||||
|
||||
local function inherit(base, overrides)
|
||||
local result = {}
|
||||
for key, value in pairs(base or {}) do result[key] = value end
|
||||
for key, value in pairs(overrides or {}) do result[key] = value end
|
||||
return result
|
||||
end
|
||||
|
||||
local function build_xhttp_extra(opts)
|
||||
local extra = {}
|
||||
local fields = {
|
||||
headers = "headers", ["x-padding-bytes"] = "xPaddingBytes",
|
||||
["no-grpc-header"] = "noGRPCHeader", ["x-padding-obfs-mode"] = "xPaddingObfsMode",
|
||||
["x-padding-key"] = "xPaddingKey", ["x-padding-header"] = "xPaddingHeader",
|
||||
["x-padding-placement"] = "xPaddingPlacement", ["x-padding-method"] = "xPaddingMethod",
|
||||
["uplink-http-method"] = "uplinkHTTPMethod", ["session-placement"] = "sessionIDPlacement",
|
||||
["session-key"] = "sessionIDKey", ["session-table"] = "sessionIDTable",
|
||||
["session-length"] = "sessionIDLength", ["seq-placement"] = "seqPlacement", ["seq-key"] = "seqKey",
|
||||
["uplink-data-placement"] = "uplinkDataPlacement", ["uplink-data-key"] = "uplinkDataKey",
|
||||
["uplink-chunk-size"] = "uplinkChunkSize", ["sc-max-each-post-bytes"] = "scMaxEachPostBytes",
|
||||
["sc-min-posts-interval-ms"] = "scMinPostsIntervalMs"
|
||||
}
|
||||
for source, target in pairs(fields) do extra[target] = opts[source] end
|
||||
-- jsonc treats empty tables as arrays and repeated table references as null.
|
||||
if type(extra.headers) == "table" then
|
||||
extra.headers = next(extra.headers) and inherit(extra.headers) or nil
|
||||
end
|
||||
local reuse = opts["reuse-settings"]
|
||||
if reuse then
|
||||
local xmux = {}
|
||||
local ranges = {
|
||||
["max-concurrency"] = "maxConcurrency", ["max-connections"] = "maxConnections",
|
||||
["c-max-reuse-times"] = "cMaxReuseTimes", ["h-max-request-times"] = "hMaxRequestTimes",
|
||||
["h-max-reusable-secs"] = "hMaxReusableSecs"
|
||||
}
|
||||
for source, target in pairs(ranges) do
|
||||
local value = reuse[source]
|
||||
if value ~= nil then xmux[target] = tonumber(value) or value end
|
||||
end
|
||||
xmux.hKeepAlivePeriod = tonumber(reuse["h-keep-alive-period"])
|
||||
if next(xmux) then extra.xmux = xmux end
|
||||
end
|
||||
return extra
|
||||
end
|
||||
|
||||
local function host_format(host)
|
||||
if not host then return "" end
|
||||
local str = host:match("%[(.-)%]") or host
|
||||
@@ -62,18 +136,12 @@ local function build_common(node)
|
||||
o.tls.sni = node.servername or node.sni
|
||||
o.tls.fp = node["client-fingerprint"]
|
||||
o.tls.pcs = node.fingerprint
|
||||
o.tls.vcn = node["name-cert-verify"]
|
||||
o.tls.insecure = node["skip-cert-verify"] == true
|
||||
|
||||
o.tls.alpn = build_alpn(node.alpn)
|
||||
|
||||
local ech_opts = node["ech-opts"]
|
||||
if ech_opts and ech_opts.enable == true then
|
||||
if ech_opts["query-server-name"] then
|
||||
o.tls.ech = ech_opts["query-server-name"] .. "+https://223.5.5.5/dns-query"
|
||||
elseif ech_opts.config then
|
||||
o.tls.ech = ech_opts.config
|
||||
end
|
||||
end
|
||||
o.tls.ech = build_ech(node)
|
||||
|
||||
-- ===== transport =====
|
||||
o.transport = {}
|
||||
@@ -89,16 +157,15 @@ local function build_common(node)
|
||||
local opts = node["ws-opts"]
|
||||
if opts then
|
||||
local path = opts.path or "/"
|
||||
local ed = opts["max-early-data"]
|
||||
local eh = opts["early-data-header-name"]
|
||||
if ed then
|
||||
path = path .. "?ed=" .. ed
|
||||
if opts["v2ray-http-upgrade"] ~= true and opts["max-early-data"] then
|
||||
path = path .. (path:find("?", 1, true) and "&ed=" or "?ed=") .. opts["max-early-data"]
|
||||
end
|
||||
if eh then
|
||||
path = path .. (path:find("?", 1, true) and "&eh=" or "?eh=") .. eh
|
||||
if opts["v2ray-http-upgrade"] ~= true and opts["early-data-header-name"] then
|
||||
path = path .. (path:find("?", 1, true) and "&eh=" or "?eh=") .. opts["early-data-header-name"]
|
||||
end
|
||||
o.transport.path = path
|
||||
o.transport.host = opts.headers and opts.headers.Host
|
||||
if opts["v2ray-http-upgrade"] == true then o.transport.type = "httpupgrade" end
|
||||
end
|
||||
|
||||
elseif net == "grpc" then
|
||||
@@ -108,12 +175,17 @@ local function build_common(node)
|
||||
end
|
||||
|
||||
elseif net == "http" then
|
||||
-- Use TCP + HTTP camouflage so each core selects its matching transport.
|
||||
o.transport.type = "tcp"
|
||||
o.transport.headerType = "http"
|
||||
o.transport.host = node.server
|
||||
o.transport.path = "/"
|
||||
local opts = node["http-opts"]
|
||||
if opts then
|
||||
-- Clash http-opts carries the camouflage Host under headers.Host
|
||||
-- (a list), like ws-opts; opts.host is not standard for http.
|
||||
o.transport.host = get_first(opts.host) or (opts.headers and get_first(opts.headers.Host))
|
||||
o.transport.path = get_first(opts.path)
|
||||
o.transport.host = get_first(opts.host) or (opts.headers and get_first(opts.headers.Host)) or o.transport.host
|
||||
o.transport.path = get_first(opts.path) or o.transport.path
|
||||
end
|
||||
|
||||
elseif net == "h2" then
|
||||
@@ -130,85 +202,45 @@ local function build_common(node)
|
||||
o.transport.path = opts.path
|
||||
o.transport.mode = opts.mode
|
||||
|
||||
local extra = {}
|
||||
|
||||
-- headers
|
||||
if opts.headers then
|
||||
extra.headers = opts.headers
|
||||
end
|
||||
|
||||
if opts["x-padding-bytes"] then
|
||||
extra.xPaddingBytes = opts["x-padding-bytes"]
|
||||
end
|
||||
|
||||
if opts["no-grpc-header"] ~= nil then
|
||||
extra.noGRPCHeader = opts["no-grpc-header"]
|
||||
end
|
||||
|
||||
if opts["sc-max-each-post-bytes"] then
|
||||
extra.scMaxEachPostBytes = opts["sc-max-each-post-bytes"]
|
||||
end
|
||||
|
||||
if opts["sc-min-posts-interval-ms"] then
|
||||
extra.scMinPostsIntervalMs = opts["sc-min-posts-interval-ms"]
|
||||
end
|
||||
|
||||
-- xmux
|
||||
if opts["reuse-settings"] then
|
||||
local r = opts["reuse-settings"]
|
||||
local xmux = {}
|
||||
|
||||
if r["max-concurrency"] then xmux.maxConcurrency = r["max-concurrency"] end
|
||||
if r["max-connections"] then xmux.maxConnections = tonumber(r["max-connections"]) end
|
||||
if r["c-max-reuse-times"] then xmux.cMaxReuseTimes = tonumber(r["c-max-reuse-times"]) end
|
||||
if r["h-max-request-times"] then xmux.hMaxRequestTimes = r["h-max-request-times"] end
|
||||
if r["h-max-reusable-secs"] then xmux.hMaxReusableSecs = r["h-max-reusable-secs"] end
|
||||
if r["h-keep-alive-period"] then xmux.hKeepAlivePeriod = tonumber(r["h-keep-alive-period"]) end
|
||||
|
||||
if next(xmux) then extra.xmux = xmux end
|
||||
end
|
||||
local extra = build_xhttp_extra(opts)
|
||||
|
||||
-- download-settings
|
||||
if opts["download-settings"] then
|
||||
local d = opts["download-settings"]
|
||||
local ds = {}
|
||||
|
||||
if d.server then ds.address = domain_redir[d.server] or d.server end
|
||||
if d.port then ds.port = d.port end
|
||||
|
||||
ds.network = "xhttp"
|
||||
|
||||
-- TLS
|
||||
if d.tls then
|
||||
ds.security = "tls"
|
||||
ds.tlsSettings = {}
|
||||
|
||||
if d.servername then
|
||||
ds.tlsSettings.serverName = d.servername
|
||||
end
|
||||
if d["skip-cert-verify"] == true then
|
||||
ds.tlsSettings.allowInsecure = true
|
||||
end
|
||||
if d["client-fingerprint"] then
|
||||
ds.tlsSettings.fingerprint = d["client-fingerprint"]
|
||||
end
|
||||
if d.fingerprint then
|
||||
ds.tlsSettings.pinnedPeerCertSha256 = d.fingerprint
|
||||
end
|
||||
if d.alpn then
|
||||
ds.tlsSettings.alpn = d.alpn
|
||||
local effective = inherit(node, d)
|
||||
local ds = {
|
||||
address = domain_redir[effective.server] or effective.server,
|
||||
port = effective.port,
|
||||
network = "xhttp",
|
||||
security = effective.tls and "tls" or "none"
|
||||
}
|
||||
local sni = effective.servername or effective.sni or effective.server
|
||||
local alpn = build_alpn(effective.alpn)
|
||||
if effective.tls or effective["reality-opts"] then
|
||||
ds.tlsSettings = {
|
||||
serverName = sni,
|
||||
allowInsecure = effective["skip-cert-verify"] == true,
|
||||
fingerprint = effective["client-fingerprint"],
|
||||
pinnedPeerCertSha256 = effective.fingerprint and api.sha256_sb_xray(effective.fingerprint) or nil,
|
||||
verifyPeerCertByName = effective["name-cert-verify"],
|
||||
alpn = alpn and api.split(alpn, ",") or nil,
|
||||
echConfigList = build_ech(effective)
|
||||
}
|
||||
local reality = effective["reality-opts"]
|
||||
if reality then
|
||||
ds.security = "reality"
|
||||
ds.realitySettings = {
|
||||
serverName = sni, fingerprint = effective["client-fingerprint"] or "chrome",
|
||||
publicKey = reality["public-key"], shortId = reality["short-id"] or ""
|
||||
}
|
||||
ds.tlsSettings = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- xhttpSettings
|
||||
local xs = {}
|
||||
|
||||
if d.path then xs.path = d.path end
|
||||
if d.host then xs.host = d.host end
|
||||
|
||||
if next(xs) then
|
||||
ds.xhttpSettings = xs
|
||||
end
|
||||
local xs = build_xhttp_extra(inherit(opts, d))
|
||||
xs.path = d.path or opts.path or "/"
|
||||
xs.host = d.host or opts.host or sni
|
||||
xs.mode = opts.mode or "auto"
|
||||
ds.xhttpSettings = xs
|
||||
|
||||
extra.downloadSettings = ds
|
||||
end
|
||||
@@ -229,27 +261,29 @@ local function encode_vless(node)
|
||||
local link = "vless://" .. node.uuid .. "@" .. o.server .. ":" .. o.port
|
||||
local p = {}
|
||||
|
||||
if type(node.flow) == "string" and node.flow:sub(1, 5) == "xtls-" then table.insert(p, "flow=" .. urlencode(node.flow)) end
|
||||
if node.encryption then table.insert(p, "encryption=" .. urlencode(node.encryption)) end
|
||||
if type(node.flow) == "string" and node.flow:sub(1, 5) == "xtls-" then add_param(p, "flow", node.flow) end
|
||||
add_param(p, "encryption", node.encryption)
|
||||
|
||||
-- TLS
|
||||
if o.tls.security then table.insert(p, "security=" .. o.tls.security) end
|
||||
if o.tls.pbk then table.insert(p, "pbk=" .. urlencode(o.tls.pbk)) end
|
||||
if o.tls.sid then table.insert(p, "sid=" .. urlencode(o.tls.sid)) end
|
||||
if o.tls.sni then table.insert(p, "sni=" .. urlencode(o.tls.sni)) end
|
||||
if o.tls.fp then table.insert(p, "fp=" .. urlencode(o.tls.fp)) end
|
||||
if o.tls.alpn then table.insert(p, "alpn=" .. urlencode(o.tls.alpn)) end
|
||||
if o.tls.ech then table.insert(p, "ech=" .. urlencode(o.tls.ech)) end
|
||||
if o.tls.pcs then table.insert(p, "pcs=" .. urlencode(o.tls.pcs)) end
|
||||
table.insert(p, "allowInsecure=" .. (o.tls.insecure and "1" or "0"))
|
||||
add_param(p, "security", o.tls.security)
|
||||
add_param(p, "pbk", o.tls.pbk)
|
||||
add_param(p, "sid", o.tls.sid)
|
||||
add_param(p, "sni", o.tls.sni)
|
||||
add_param(p, "fp", o.tls.fp)
|
||||
add_param(p, "alpn", o.tls.alpn)
|
||||
add_param(p, "ech", o.tls.ech)
|
||||
add_param(p, "pcs", o.tls.pcs)
|
||||
add_param(p, "vcn", o.tls.vcn)
|
||||
add_param(p, "allowInsecure", o.tls.insecure)
|
||||
|
||||
-- transport
|
||||
table.insert(p, "type=" .. o.transport.type)
|
||||
if o.transport.host then table.insert(p, "host=" .. urlencode(o.transport.host)) end
|
||||
if o.transport.path then table.insert(p, "path=" .. urlencode(o.transport.path)) end
|
||||
if o.transport.serviceName then table.insert(p, "serviceName=" .. urlencode(o.transport.serviceName)) end
|
||||
if o.transport.mode then table.insert(p, "mode=" .. urlencode(o.transport.mode)) end
|
||||
if o.transport.extra then table.insert(p, "extra=" .. urlencode(o.transport.extra)) end
|
||||
add_param(p, "type", o.transport.type)
|
||||
add_param(p, "headerType", o.transport.headerType)
|
||||
add_param(p, "host", o.transport.host)
|
||||
add_param(p, "path", o.transport.path)
|
||||
add_param(p, "serviceName", o.transport.serviceName)
|
||||
add_param(p, "mode", o.transport.mode)
|
||||
add_param(p, "extra", o.transport.extra)
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
@@ -262,23 +296,24 @@ end
|
||||
local function encode_trojan(node)
|
||||
local o = build_common(node)
|
||||
|
||||
local link = "trojan://" .. node.password .. "@" .. o.server .. ":" .. o.port
|
||||
local link = "trojan://" .. urlencode(node.password) .. "@" .. o.server .. ":" .. o.port
|
||||
local p = {}
|
||||
|
||||
if o.tls.security then table.insert(p, "security=" .. o.tls.security) end
|
||||
if o.tls.pbk then table.insert(p, "pbk=" .. urlencode(o.tls.pbk)) end
|
||||
if o.tls.sid then table.insert(p, "sid=" .. urlencode(o.tls.sid)) end
|
||||
if o.tls.sni then table.insert(p, "sni=" .. urlencode(o.tls.sni)) end
|
||||
if o.tls.fp then table.insert(p, "fp=" .. urlencode(o.tls.fp)) end
|
||||
if o.tls.alpn then table.insert(p, "alpn=" .. urlencode(o.tls.alpn)) end
|
||||
if o.tls.ech then table.insert(p, "ech=" .. urlencode(o.tls.ech)) end
|
||||
if o.tls.pcs then table.insert(p, "pcs=" .. urlencode(o.tls.pcs)) end
|
||||
table.insert(p, "allowInsecure=" .. (o.tls.insecure and "1" or "0"))
|
||||
|
||||
table.insert(p, "type=" .. o.transport.type)
|
||||
if o.transport.host then table.insert(p, "host=" .. urlencode(o.transport.host)) end
|
||||
if o.transport.path then table.insert(p, "path=" .. urlencode(o.transport.path)) end
|
||||
if o.transport.serviceName then table.insert(p, "serviceName=" .. urlencode(o.transport.serviceName)) end
|
||||
add_param(p, "security", o.tls.security)
|
||||
add_param(p, "pbk", o.tls.pbk)
|
||||
add_param(p, "sid", o.tls.sid)
|
||||
add_param(p, "sni", o.tls.sni)
|
||||
add_param(p, "fp", o.tls.fp)
|
||||
add_param(p, "alpn", o.tls.alpn)
|
||||
add_param(p, "ech", o.tls.ech)
|
||||
add_param(p, "pcs", o.tls.pcs)
|
||||
add_param(p, "vcn", o.tls.vcn)
|
||||
add_param(p, "allowInsecure", o.tls.insecure)
|
||||
add_param(p, "type", o.transport.type)
|
||||
add_param(p, "headerType", o.transport.headerType)
|
||||
add_param(p, "host", o.transport.host)
|
||||
add_param(p, "path", o.transport.path)
|
||||
add_param(p, "serviceName", o.transport.serviceName)
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
@@ -301,21 +336,22 @@ local function encode_vmess(node)
|
||||
net = o.transport.type,
|
||||
security = node.cipher,
|
||||
scy = node.cipher,
|
||||
type = "none",
|
||||
type = o.transport.headerType or "none",
|
||||
host = o.transport.host or "",
|
||||
path = o.transport.path or "",
|
||||
path = urlencode(o.transport.path or ""),
|
||||
tls = o.tls.security == "tls" and "tls" or "",
|
||||
sni = o.tls.sni,
|
||||
alpn = o.tls.alpn,
|
||||
fp = o.tls.fp,
|
||||
ech = o.tls.ech,
|
||||
pcs = o.tls.pcs,
|
||||
vcn = o.tls.vcn,
|
||||
insecure = o.tls.insecure and "1" or "0",
|
||||
tfo = node.tfo and "1" or "0"
|
||||
}
|
||||
|
||||
if o.transport.type == "grpc" then
|
||||
obj.path = o.transport.serviceName or ""
|
||||
obj.path = urlencode(o.transport.serviceName or "")
|
||||
end
|
||||
|
||||
return 0, "vmess://" .. base64(json.stringify(obj))
|
||||
@@ -337,14 +373,14 @@ local function encode_ss(node)
|
||||
|
||||
local p = {}
|
||||
|
||||
if node.udp then table.insert(p, "udp=1") end
|
||||
if node["udp-over-tcp"] then table.insert(p, "uot=1") end
|
||||
if node.udp then add_param(p, "udp", true) end
|
||||
if node["udp-over-tcp"] then add_param(p, "uot", true) end
|
||||
|
||||
if node.plugin then
|
||||
local plugin = (node.plugin == "obfs") and "obfs-local" or node.plugin
|
||||
if plugin == "shadow-tls" then
|
||||
local shadow_tls = base64(json.stringify(node["plugin-opts"] or {}))
|
||||
table.insert(p, "shadow-tls=" .. urlencode(shadow_tls))
|
||||
add_param(p, "shadow-tls", shadow_tls)
|
||||
elseif plugin == "obfs-local" or plugin == "v2ray-plugin" then
|
||||
local opts = {}
|
||||
for k, v in pairs(node["plugin-opts"] or {}) do
|
||||
@@ -369,7 +405,7 @@ local function encode_ss(node)
|
||||
end
|
||||
end
|
||||
if #opts > 0 then plugin = plugin .. ";" .. table.concat(opts, ";") end
|
||||
table.insert(p, "plugin=" .. urlencode(plugin))
|
||||
add_param(p, "plugin", plugin)
|
||||
else
|
||||
return 1, "订阅转换 → 丢弃 SS 节点:" .. (node.name or "") .. ",因 Core 不支持 " .. plugin .. " 插件"
|
||||
end
|
||||
@@ -383,22 +419,24 @@ local function encode_ss(node)
|
||||
end
|
||||
|
||||
-- Hysteria
|
||||
local function encode_hysteria2(node)
|
||||
local function encode_hysteria(node)
|
||||
if node.protocol and node.protocol ~= "udp" then
|
||||
return 1, "订阅转换 → 丢弃 Hysteria 节点:" .. node.name .. ",当前 Core 不支持 " .. node.protocol .. " 传输"
|
||||
end
|
||||
local link = "hysteria://" .. host_format(node.server) .. ":" .. (node.port or "")
|
||||
local p = {}
|
||||
|
||||
if node["auth-str"] then table.insert(p, "auth=" .. node["auth-str"]) end
|
||||
if node["ports"] then table.insert(p, "mport=" .. node["ports"]) end
|
||||
if node.obfs then table.insert(p, "obfsParam=" .. node.obfs) end
|
||||
if node.sni then table.insert(p, "sni=" .. node.sni) end
|
||||
if node.up then table.insert(p, "upmbps=" .. node.up) end
|
||||
if node.down then table.insert(p, "downmbps=" .. node.down) end
|
||||
if node["skip-cert-verify"] then table.insert(p, "insecure=1") end
|
||||
if node["fingerprint"] then table.insert(p, "pinSHA256=" .. urlencode(node["fingerprint"])) end
|
||||
add_param(p, "auth", node["auth-str"])
|
||||
add_param(p, "mport", node.ports)
|
||||
add_param(p, "obfsParam", node.obfs)
|
||||
add_param(p, "sni", node.sni)
|
||||
add_param(p, "upmbps", bandwidth_mbps(node.up))
|
||||
add_param(p, "downmbps", bandwidth_mbps(node.down))
|
||||
add_param(p, "insecure", node["skip-cert-verify"] == true)
|
||||
add_param(p, "vcn", node["name-cert-verify"])
|
||||
add_param(p, "pinSHA256", node.fingerprint)
|
||||
|
||||
if node.alpn then
|
||||
table.insert(p, "alpn=" .. urlencode(build_alpn(node.alpn)))
|
||||
end
|
||||
add_param(p, "alpn", build_alpn(node.alpn))
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
@@ -409,20 +447,24 @@ end
|
||||
|
||||
-- Hysteria2
|
||||
local function encode_hysteria2(node)
|
||||
local link = "hysteria2://" .. (node.password or "") .. "@" .. host_format(node.server) .. ":" .. (node.port or "")
|
||||
-- Hopping subscriptions may omit port; keep a valid fallback for all cores.
|
||||
local port = node.port or tostring(node.ports or ""):match("^%s*(%d+)")
|
||||
local link = "hysteria2://" .. urlencode(node.password or "") .. "@" .. host_format(node.server) .. ":" .. port
|
||||
local p = {}
|
||||
|
||||
if node["ports"] then table.insert(p, "mport=" .. urlencode(node["ports"])) end
|
||||
if node.obfs then table.insert(p, "obfs=" .. node.obfs) end
|
||||
if node["obfs-password"] then table.insert(p, "obfs-password=" .. node["obfs-password"]) end
|
||||
if node["obfs-min-packet-size"] then table.insert(p, "minPacketSize=" .. node["obfs-min-packet-size"]) end
|
||||
if node["obfs-max-packet-size"] then table.insert(p, "maxPacketSize=" .. node["obfs-max-packet-size"]) end
|
||||
if node.up then table.insert(p, "upmbps=" .. node.up) end
|
||||
if node.down then table.insert(p, "downmbps=" .. node.down) end
|
||||
|
||||
if node.sni then table.insert(p, "sni=" .. urlencode(node.sni)) end
|
||||
if node["skip-cert-verify"] then table.insert(p, "insecure=1") end
|
||||
if node["fingerprint"] then table.insert(p, "pinSHA256=" .. urlencode(node["fingerprint"])) end
|
||||
add_param(p, "mport", node.ports)
|
||||
add_param(p, "hopInterval", node["hop-interval"])
|
||||
add_param(p, "obfs", node.obfs)
|
||||
add_param(p, "obfs-password", node["obfs-password"])
|
||||
add_param(p, "minPacketSize", node["obfs-min-packet-size"])
|
||||
add_param(p, "maxPacketSize", node["obfs-max-packet-size"])
|
||||
add_param(p, "upmbps", bandwidth_mbps(node.up))
|
||||
add_param(p, "downmbps", bandwidth_mbps(node.down))
|
||||
add_param(p, "ech", build_ech(node))
|
||||
add_param(p, "vcn", node["name-cert-verify"])
|
||||
add_param(p, "sni", node.sni)
|
||||
add_param(p, "insecure", node["skip-cert-verify"] == true)
|
||||
add_param(p, "pinSHA256", node.fingerprint)
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
@@ -433,22 +475,23 @@ end
|
||||
|
||||
-- TUIC
|
||||
local function encode_tuic(node)
|
||||
local link = "tuic://" .. node.uuid .. ":" .. node.password .. "@" .. host_format(node.server) .. ":" .. node.port
|
||||
if node.token then
|
||||
return 1, "订阅转换 → 丢弃 TUIC 节点:" .. node.name .. ",当前 Core 不支持 TUIC v4"
|
||||
end
|
||||
if not node.uuid or node.password == nil then
|
||||
return 1, "订阅转换 → 丢弃 TUIC 节点:" .. node.name .. ",认证信息不完整。"
|
||||
end
|
||||
local link = "tuic://" .. urlencode(node.uuid) .. ":" .. urlencode(node.password) .. "@" .. host_format(node.server) .. ":" .. node.port
|
||||
local p = {}
|
||||
|
||||
if node["congestion-controller"] then
|
||||
table.insert(p, "congestion_control=" .. node["congestion-controller"])
|
||||
end
|
||||
|
||||
if node.alpn then
|
||||
table.insert(p, "alpn=" .. urlencode(build_alpn(node.alpn)))
|
||||
end
|
||||
|
||||
if node.sni then table.insert(p, "sni=" .. urlencode(node.sni)) end
|
||||
if node["disable-sni"] then table.insert(p, "disable_sni=1") end
|
||||
if node["skip-cert-verify"] then table.insert(p, "allowInsecure=1") end
|
||||
if node["udp-relay-mode"] then table.insert(p, "udp_relay_mode=" .. node["udp-relay-mode"]) end
|
||||
if node["fingerprint"] then table.insert(p, "pcs=" .. urlencode(node["fingerprint"])) end
|
||||
add_param(p, "congestion_control", node["congestion-controller"])
|
||||
add_param(p, "alpn", build_alpn(node.alpn))
|
||||
add_param(p, "sni", node.sni)
|
||||
if node["disable-sni"] then add_param(p, "disable_sni", true) end
|
||||
add_param(p, "allowInsecure", node["skip-cert-verify"] == true)
|
||||
add_param(p, "udp_relay_mode", node["udp-relay-mode"])
|
||||
add_param(p, "pcs", node.fingerprint)
|
||||
add_param(p, "vcn", node["name-cert-verify"])
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
@@ -474,18 +517,18 @@ local function encode_anytls(node)
|
||||
|
||||
local o = build_common(node)
|
||||
|
||||
local link = "anytls://" .. (node.password or "") .. "@" .. host_format(node.server) .. ":" .. node.port
|
||||
local link = "anytls://" .. urlencode(node.password or "") .. "@" .. host_format(node.server) .. ":" .. node.port
|
||||
local p = {}
|
||||
|
||||
if o.tls.security then table.insert(p, "security=" .. o.tls.security) end
|
||||
if o.tls.pbk then table.insert(p, "pbk=" .. urlencode(o.tls.pbk)) end
|
||||
if o.tls.sid then table.insert(p, "sid=" .. urlencode(o.tls.sid)) end
|
||||
if o.tls.sni then table.insert(p, "sni=" .. urlencode(o.tls.sni)) end
|
||||
if o.tls.alpn then table.insert(p, "alpn=" .. urlencode(o.tls.alpn)) end
|
||||
if o.tls.fp then table.insert(p, "fp=" .. urlencode(o.tls.fp)) end
|
||||
if o.tls.ech then table.insert(p, "ech=" .. urlencode(o.tls.ech)) end
|
||||
if o.tls.pcs then table.insert(p, "pcs=" .. urlencode(o.tls.pcs)) end
|
||||
table.insert(p, "insecure=" .. (o.tls.insecure and "1" or "0"))
|
||||
add_param(p, "security", o.tls.security)
|
||||
add_param(p, "pbk", o.tls.pbk)
|
||||
add_param(p, "sid", o.tls.sid)
|
||||
add_param(p, "sni", o.tls.sni)
|
||||
add_param(p, "alpn", o.tls.alpn)
|
||||
add_param(p, "fp", o.tls.fp)
|
||||
add_param(p, "ech", o.tls.ech)
|
||||
add_param(p, "pcs", o.tls.pcs)
|
||||
add_param(p, "insecure", o.tls.insecure)
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
@@ -514,8 +557,8 @@ end
|
||||
-- snell
|
||||
local function encode_snell(node)
|
||||
local err_msg
|
||||
local obfs = node["obfs-opts"]
|
||||
if obfs and obfs.mode ~= "http" and obfs.mode ~= "none" then
|
||||
local obfs = node["obfs-opts"] or {}
|
||||
if obfs.mode and obfs.mode ~= "http" and obfs.mode ~= "none" then
|
||||
err_msg = obfs.mode
|
||||
end
|
||||
if err_msg then
|
||||
@@ -532,13 +575,13 @@ local function encode_snell(node)
|
||||
local link = "snell://" .. host_format(node.server) .. ":" .. node.port
|
||||
local p = {}
|
||||
|
||||
if node.psk then table.insert(p, "psk=" .. urlencode(node.psk)) end
|
||||
table.insert(p, "version=" .. version)
|
||||
add_param(p, "psk", node.psk)
|
||||
add_param(p, "version", version)
|
||||
if obfs.mode == "http" then
|
||||
table.insert(p, "obfs=http")
|
||||
if obfs.host then table.insert(p, "obfs-host=" .. urlencode(obfs.host)) end
|
||||
add_param(p, "obfs", "http")
|
||||
add_param(p, "obfs-host", obfs.host)
|
||||
end
|
||||
table.insert(p, "reuse=" .. (node.reuse and "1" or "0"))
|
||||
add_param(p, "reuse", node.reuse and "1" or "0")
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
@@ -548,9 +591,22 @@ local function encode_snell(node)
|
||||
end
|
||||
|
||||
local function encode_node(node)
|
||||
if (not node.type) or (not node.name) then return nil end
|
||||
if type(node) ~= "table" or not node.type or not node.name then
|
||||
return 1, "订阅转换 → 跳过格式不完整的节点。"
|
||||
end
|
||||
if not node.server or node.server == "" or not (node.port or (node.type == "hysteria2" and node.ports)) then
|
||||
return 1, "订阅转换 → 丢弃节点:" .. tostring(node.name) .. ",节点信息不完整。"
|
||||
end
|
||||
node = inherit(node)
|
||||
|
||||
local t = node.type
|
||||
if t == "hysteria" or t == "hysteria2" then
|
||||
local up, down = bandwidth_mbps(node.up), bandwidth_mbps(node.down)
|
||||
if (node.up ~= nil and up == nil) or (node.down ~= nil and down == nil) then
|
||||
return 1, "订阅转换 → 丢弃节点:" .. tostring(node.name) .. ",带宽格式不正确。"
|
||||
end
|
||||
node.up, node.down = up, down
|
||||
end
|
||||
|
||||
node.server = domain_redir[node.server] or node.server
|
||||
|
||||
@@ -570,17 +626,22 @@ end
|
||||
|
||||
function parseClashNode(raw, remark)
|
||||
if not raw then return "" end
|
||||
domain_redir = {}
|
||||
local ok, lyaml = pcall(require, "lyaml")
|
||||
if not ok then return raw end
|
||||
|
||||
local data = lyaml.load(raw)
|
||||
local parsed, data = pcall(lyaml.load, raw)
|
||||
if not parsed then
|
||||
api.log('订阅转换 → YAML 解析失败:' .. tostring(remark or ""))
|
||||
return ""
|
||||
end
|
||||
if not data or type(data) ~= "table" then return raw end
|
||||
if not data.proxies then return "" end
|
||||
if type(data.proxies) ~= "table" then return "" end
|
||||
|
||||
api.log('检测到 Clash 订阅,正在进行转换 ...')
|
||||
|
||||
-- Some airports use hosts domain redirect to disguise the real node domain name.
|
||||
for k, v in pairs(data.hosts or {}) do
|
||||
for k, v in pairs(type(data.hosts) == "table" and data.hosts or {}) do
|
||||
if type(k) == "string" and type(v) == "string" then
|
||||
if api.datatypes.hostname(k) and api.datatypes.hostname(v) then
|
||||
domain_redir[k] = v
|
||||
@@ -590,8 +651,11 @@ function parseClashNode(raw, remark)
|
||||
|
||||
local links = {}
|
||||
for _, node in ipairs(data.proxies) do
|
||||
local err, link = encode_node(node)
|
||||
if err == 1 and link then
|
||||
local converted, err, link = pcall(encode_node, node)
|
||||
if not converted then
|
||||
local name = type(node) == "table" and node.name or ""
|
||||
api.log('订阅转换 → 丢弃节点:' .. tostring(name or "") .. ',转换失败,已跳过。')
|
||||
elseif err == 1 and link then
|
||||
api.log(link)
|
||||
elseif link then
|
||||
table.insert(links, link)
|
||||
|
||||
@@ -130,9 +130,13 @@ end
|
||||
|
||||
local nodeResult = {} -- update result
|
||||
local isDebug = false
|
||||
local log_buffer = nil
|
||||
|
||||
local log = function(...)
|
||||
if isDebug == true then
|
||||
if log_buffer then
|
||||
local n = select("#", ...)
|
||||
tinsert(log_buffer, n == 1 and (...) or table.concat({...}, " "))
|
||||
elseif isDebug == true then
|
||||
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
|
||||
print(result)
|
||||
else
|
||||
@@ -140,6 +144,14 @@ local log = function(...)
|
||||
end
|
||||
end
|
||||
|
||||
local function flush_log_buffer()
|
||||
local buffered = log_buffer
|
||||
log_buffer = nil
|
||||
for _, msg in ipairs(buffered or {}) do
|
||||
log(msg)
|
||||
end
|
||||
end
|
||||
|
||||
local nodes_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" then
|
||||
@@ -885,6 +897,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
result.method = method
|
||||
result.ss_method = method
|
||||
result.password = password
|
||||
result.uot = params.uot
|
||||
result.tcp_fast_open = params.tfo
|
||||
result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil
|
||||
result.finalmask = (params.fm and params.fm ~= "") and api.base64Encode(params.fm) or nil
|
||||
@@ -1039,7 +1052,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
end
|
||||
local insecure = params.allowinsecure or params.allowInsecure or params.insecure
|
||||
result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (sub_allowinsecure and "1" or "0")
|
||||
result.uot = params.udp
|
||||
result.uot = params.uot or params.udp
|
||||
else
|
||||
result.error_msg = "请更换 Xray 或 Sing-Box 来支持 SS 更多的传输方式。"
|
||||
end
|
||||
@@ -1420,7 +1433,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
for _, v in pairs(split(query[2], '&')) do
|
||||
local s = v:find("=", 1, true)
|
||||
if s and s > 1 then
|
||||
params[v:sub(1, s - 1)] = v:sub(s + 1)
|
||||
params[v:sub(1, s - 1)] = UrlDecode(v:sub(s + 1))
|
||||
end
|
||||
end
|
||||
-- [2001:4860:4860::8888]:443
|
||||
@@ -1447,6 +1460,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
result.hysteria_up_mbps = params.upmbps or sub_hy_up_mbps
|
||||
result.hysteria_down_mbps = params.downmbps or sub_hy_down_mbps
|
||||
result.hysteria_hop = params.mport
|
||||
result.tls_CertByName = params.vcn
|
||||
|
||||
elseif szType == 'hysteria2' or szType == 'hy2' then
|
||||
local alias = ""
|
||||
@@ -1490,6 +1504,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
result.hysteria2_up_mbps = params.upmbps or (sub_cfg and sub_hy_up_mbps or nil)
|
||||
result.hysteria2_down_mbps = params.downmbps or (sub_cfg and sub_hy_down_mbps or nil)
|
||||
result.hysteria2_hop = params.mport
|
||||
result.hysteria2_hop_interval = params.hopinterval or params["hop-interval"]
|
||||
if params.ech and params.ech ~= "" then
|
||||
result.ech = "1"
|
||||
result.ech_config = params.ech
|
||||
@@ -1569,6 +1584,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
|
||||
end
|
||||
result.tls_serverName = params.sni
|
||||
result.tls_disable_sni = params.disable_sni
|
||||
result.tls_CertByName = params.vcn
|
||||
result.tls_pinSHA256 = params.pcs or params.pinsha256
|
||||
result.tuic_alpn = params.alpn or "h3"
|
||||
result.tuic_congestion_control = params.congestion_control or "cubic"
|
||||
@@ -1994,10 +2010,29 @@ local function select_node(nodes, config, parentConfig)
|
||||
end
|
||||
end
|
||||
|
||||
-- 账户信息只在实际变化时写回,由调用方统一提交。
|
||||
local function update_subscribe_info()
|
||||
local changed = {}
|
||||
for cfgid, info in pairs(subscribe_info) do
|
||||
for key, value in pairs(info) do
|
||||
value = value and value ~= "" and value or nil
|
||||
if uci_get(cfgid, key) ~= value then
|
||||
if value then
|
||||
uci_set(cfgid, key, value)
|
||||
else
|
||||
uci_del(cfgid, key)
|
||||
end
|
||||
changed[cfgid] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return changed
|
||||
end
|
||||
|
||||
local function update_node(manual)
|
||||
if next(nodeResult) == nil then
|
||||
log("没有可用的节点信息更新。")
|
||||
return
|
||||
return false
|
||||
end
|
||||
|
||||
local group = {}
|
||||
@@ -2087,17 +2122,6 @@ local function update_node(manual)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- 更新机场信息
|
||||
for cfgid, info in pairs(subscribe_info) do
|
||||
for key, value in pairs(info) do
|
||||
if value ~= "" then
|
||||
uci_set(cfgid, key, value)
|
||||
else
|
||||
uci_del(cfgid, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if next(CONFIG) then
|
||||
local nodes = {}
|
||||
uci_foreach("nodes", function(node)
|
||||
@@ -2119,9 +2143,7 @@ local function update_node(manual)
|
||||
end
|
||||
end
|
||||
|
||||
uci_save(true)
|
||||
|
||||
if manual ~= 1 then restart_required = true end
|
||||
return true
|
||||
end
|
||||
|
||||
local function parse_link(raw, add_mode, group, sub_cfg)
|
||||
@@ -2187,7 +2209,8 @@ local function parse_link(raw, add_mode, group, sub_cfg)
|
||||
log('丢弃节点:' .. result.remarks .. " ,原因:" .. result.error_msg)
|
||||
elseif not result.type then
|
||||
log('丢弃节点:' .. result.remarks .. " ,找不到可使用二进制。")
|
||||
elseif (add_mode == "2" and is_filter_keyword(keyword_filter, result.remarks)) or not result.address or result.remarks == "NULL" or result.address == "127.0.0.1" or
|
||||
-- 过滤无效物理网络节点(回环、全零等非网络出站卡片)
|
||||
elseif (add_mode == "2" and is_filter_keyword(keyword_filter, result.remarks)) or not result.address or result.remarks == "NULL" or result.address == "127.0.0.1" or result.address == "0.0.0.0" or
|
||||
(not datatypes.hostname(result.address) and not (api.is_ip(result.address))) then
|
||||
log('丢弃过滤节点:' .. result.type .. ' 节点,' .. result.remarks)
|
||||
else
|
||||
@@ -2205,17 +2228,21 @@ local function parse_link(raw, add_mode, group, sub_cfg)
|
||||
end
|
||||
end
|
||||
if #node_list > 0 then
|
||||
nodeResult[#nodeResult + 1] = {
|
||||
remark = group,
|
||||
list = node_list,
|
||||
sub_cfg = sub_cfg
|
||||
}
|
||||
if add_mode ~= "2" then
|
||||
nodeResult[#nodeResult + 1] = {
|
||||
remark = group,
|
||||
list = node_list,
|
||||
sub_cfg = sub_cfg
|
||||
}
|
||||
end
|
||||
end
|
||||
log('成功解析【' .. group .. '】节点数量:' .. #node_list)
|
||||
return node_list
|
||||
else
|
||||
if add_mode == "2" then
|
||||
log('获取到的【' .. group .. '】订阅内容为空,可能是订阅地址无效,或是网络问题,请诊断!')
|
||||
end
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2266,15 +2293,32 @@ local execute = function()
|
||||
local stdout = f:read("*all")
|
||||
f:close()
|
||||
local raw_data = api.trim(stdout)
|
||||
raw_data = parseClashNode(raw_data)
|
||||
-- 优先提取标准 Clash 订阅响应头中的账户用量与到期时间(不受节点变化与否影响)
|
||||
if headers and headers ~= "" then
|
||||
subscribe_info[cfgid] = parse_clash_sub_info(headers)
|
||||
end
|
||||
-- 开启日志事务缓冲:前置解析期间日志暂存,未变更时静默丢弃,避免定时任务刷屏
|
||||
log_buffer = {}
|
||||
local parsed_nodes = parse_link(raw_data, "2", remark, value)
|
||||
local old_md5 = value.md5 or ""
|
||||
local new_md5 = api.md5_file(tmp_file)
|
||||
local new_md5 = #parsed_nodes > 0 and api.md5_string(jsonStringify(parsed_nodes)) or ""
|
||||
-- 仅当解析出有效节点且节点集合与上次完全一致时,跳过节点重写与服务重启
|
||||
if not manual_sub and new_md5 ~= "" and old_md5 == new_md5 then
|
||||
log_buffer = nil -- 无物理变动,直接丢弃暂存的丢弃与计数明细
|
||||
log('订阅:【' .. remark .. '】没有变化,无需更新。')
|
||||
else
|
||||
raw_data = parseClashNode(raw_data)
|
||||
subscribe_info[cfgid] = parse_clash_sub_info(headers)
|
||||
parse_link(raw_data, "2", remark, value)
|
||||
uci_set(cfgid, "md5", new_md5)
|
||||
-- 节点发生变动时,回放前置解析期间的节点统计与丢弃日志
|
||||
flush_log_buffer()
|
||||
-- 暂存有效节点及其 MD5,完成节点更新后再一起写入配置。
|
||||
if #parsed_nodes > 0 then
|
||||
nodeResult[#nodeResult + 1] = {
|
||||
remark = remark,
|
||||
list = parsed_nodes,
|
||||
sub_cfg = value,
|
||||
md5 = new_md5
|
||||
}
|
||||
end
|
||||
end
|
||||
else
|
||||
fail_list[#fail_list + 1] = value
|
||||
@@ -2284,19 +2328,33 @@ local execute = function()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #fail_list > 0 then
|
||||
for index, value in ipairs(fail_list) do
|
||||
log(string.format('【%s】订阅失败,可能是订阅地址无效,或是网络问题,请诊断![%s]', (value.remark or ""), tostring(value.http_code or 0)))
|
||||
end
|
||||
end
|
||||
update_node(0)
|
||||
local nodes_changed = update_node(0)
|
||||
local info_changed = update_subscribe_info()
|
||||
-- 节点、引用关系和 MD5 必须在同一次提交中保存。
|
||||
for _, result in ipairs(nodeResult) do
|
||||
uci_set(result.sub_cfg[".name"], "md5", result.md5)
|
||||
end
|
||||
if nodes_changed or next(info_changed) then
|
||||
uci_save(true)
|
||||
restart_required = nodes_changed
|
||||
for _, value in ipairs(subscribe_list) do
|
||||
if info_changed[value[".name"]] then
|
||||
log(string.format('刷新订阅【%s】账户用量与到期时间信息。', (value.remark or "")))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if arg[1] == "start" then
|
||||
log('开始订阅...')
|
||||
xpcall(execute, function(e)
|
||||
flush_log_buffer()
|
||||
log(e)
|
||||
if type(debug) == "table" and type(debug.traceback) == "function" then
|
||||
log(debug.traceback())
|
||||
@@ -2309,7 +2367,7 @@ elseif arg[1] == "add" then
|
||||
local raw = f:read('*all')
|
||||
f:close()
|
||||
parse_link(raw, "1", arg[2])
|
||||
update_node(1)
|
||||
if update_node(1) then uci_save(true) end
|
||||
api.remove("/tmp/links.conf")
|
||||
elseif arg[1] == "truncate" then
|
||||
truncate_nodes(arg[2])
|
||||
|
||||
@@ -21,13 +21,13 @@ define Download/geoip
|
||||
HASH:=4ff4b2ec242a9e87b296ba27ddb85f8311e8d3e071eaebac5be9231c60e408dd
|
||||
endef
|
||||
|
||||
GEOSITE_VER:=20261019032359
|
||||
GEOSITE_VER:=20261019074502
|
||||
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
|
||||
define Download/geosite
|
||||
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
|
||||
URL_FILE:=dlc.dat
|
||||
FILE:=$(GEOSITE_FILE)
|
||||
HASH:=1092fd1a58deccd964e92b5ff05ce01ecabaf67b8242ade8bdc6b6b35821d402
|
||||
HASH:=37c030c086c57da4d0987c6be836f8ae4f8957cd904cfa0bc6bb6036bb61c816
|
||||
endef
|
||||
|
||||
GEOSITE_IRAN_VER:=202610120242
|
||||
|
||||
+8
-1
@@ -131,7 +131,7 @@ jobs:
|
||||
rm -rf /etc/apt/sources.list.d/*
|
||||
fi
|
||||
apt-get update -o Acquire::Check-Valid-Until=false || apt-get update
|
||||
apt-get install -y --no-install-recommends dwarves || apt-get install -y dwarves || apt-get install -y pahole
|
||||
apt-get install -y --no-install-recommends dwarves llvm || apt-get install -y dwarves llvm || apt-get install -y pahole
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
apk add --no-cache dwarves
|
||||
@@ -149,6 +149,13 @@ jobs:
|
||||
mkdir -p /builder/staging_dir/host/bin
|
||||
ln -sf "$pahole_bin" /builder/staging_dir/host/bin/pahole
|
||||
|
||||
# Ensure real llvm-objcopy is accessible within OpenWrt staging_dir host tools
|
||||
llvm_objcopy_bin="$(command -v llvm-objcopy || find /usr/bin -name 'llvm-objcopy*' 2>/dev/null | head -n 1 || true)"
|
||||
if [ -n "$llvm_objcopy_bin" ]; then
|
||||
ln -sf "$llvm_objcopy_bin" /builder/staging_dir/host/bin/llvm-objcopy
|
||||
echo "Using llvm-objcopy: $llvm_objcopy_bin"
|
||||
fi
|
||||
|
||||
# Fix permissions and compile as buildbot user
|
||||
chown -R 1000:1000 /builder /artifacts
|
||||
su -s /bin/bash buildbot -c '\''
|
||||
|
||||
+7
-15
@@ -35,14 +35,11 @@ define Package/vmlinux-btf/prerm
|
||||
#!/bin/sh
|
||||
[ -n "$${IPKG_INSTROOT}" ] || rm -f /usr/lib/debug/boot/vmlinux-$$(uname -r)
|
||||
endef
|
||||
|
||||
# Toolchain and helpers
|
||||
BTF_CC ?= $(if $(TARGET_CC_NOCACHE),$(TARGET_CC_NOCACHE),$(TARGET_CROSS)gcc)
|
||||
BTF_PAHOLE ?= $(firstword \
|
||||
$(wildcard $(STAGING_DIR_HOST)/bin/pahole /usr/bin/pahole /usr/local/bin/pahole) \
|
||||
$(shell command -v pahole 2>/dev/null) \
|
||||
pahole)
|
||||
PAHOLE_JOBS := $(if $(filter -j%,$(PKG_JOBS)),$(patsubst -j%,%,$(filter -j%,$(PKG_JOBS))),1)
|
||||
KERNEL_RELEASE := $(if $(wildcard $(LINUX_DIR)/include/config/kernel.release),$(shell cat $(LINUX_DIR)/include/config/kernel.release),$(LINUX_VERSION))
|
||||
|
||||
BTF_CFLAGS := \
|
||||
@@ -81,21 +78,16 @@ define Build/Compile
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Compile BTF stub sources
|
||||
$(BTF_CC) $(BTF_CFLAGS) -c $(PKG_BUILD_DIR)/shadow_btf_core.c -o $(PKG_BUILD_DIR)/shadow_btf_core.o
|
||||
-$(BTF_CC) $(BTF_CFLAGS) -c $(PKG_BUILD_DIR)/shadow_btf_modules.c -o $(PKG_BUILD_DIR)/shadow_btf_modules.o 2>/dev/null
|
||||
-$(BTF_CC) $(BTF_CFLAGS) -c $(PKG_BUILD_DIR)/shadow_btf_private.c -o $(PKG_BUILD_DIR)/shadow_btf_private.o 2>/dev/null
|
||||
|
||||
# Link compiled objects into a single relocatable object
|
||||
objs="$(PKG_BUILD_DIR)/shadow_btf_core.o"; \
|
||||
[ ! -f "$(PKG_BUILD_DIR)/shadow_btf_modules.o" ] || objs="$$objs $(PKG_BUILD_DIR)/shadow_btf_modules.o"; \
|
||||
[ ! -f "$(PKG_BUILD_DIR)/shadow_btf_private.o" ] || objs="$$objs $(PKG_BUILD_DIR)/shadow_btf_private.o"; \
|
||||
$(TARGET_CROSS)ld -r -o $(PKG_BUILD_DIR)/shadow_btf.o $$objs
|
||||
|
||||
# Extract detached BTF
|
||||
$(BTF_PAHOLE) --jobs=$(PAHOLE_JOBS) \
|
||||
--btf_encode_detached=$(PKG_BUILD_DIR)/vmlinux-btf \
|
||||
$(PKG_BUILD_DIR)/shadow_btf.o
|
||||
$(TARGET_CROSS)ld -r -o $(PKG_BUILD_DIR)/shadow_btf.o $(PKG_BUILD_DIR)/shadow_btf_*.o
|
||||
if $(BTF_PAHOLE) --help 2>&1 | grep -q -- '--btf_encode_detached'; then \
|
||||
$(BTF_PAHOLE) --btf_encode_detached=$(PKG_BUILD_DIR)/vmlinux-btf $(PKG_BUILD_DIR)/shadow_btf.o; \
|
||||
else \
|
||||
$(BTF_PAHOLE) -J $(PKG_BUILD_DIR)/shadow_btf.o && \
|
||||
$(TARGET_CROSS)objcopy --dump-section .BTF=$(PKG_BUILD_DIR)/vmlinux-btf $(PKG_BUILD_DIR)/shadow_btf.o; \
|
||||
fi
|
||||
test -s $(PKG_BUILD_DIR)/vmlinux-btf
|
||||
$(BTF_PAHOLE) -C sk_buff $(PKG_BUILD_DIR)/shadow_btf.o >/dev/null
|
||||
endef
|
||||
|
||||
Reference in new issue
Block a user