update 2026-10-20 02:12:23

This commit is contained in:
action committed 2026-10-20 02:12:23 +08:00
1 parent 313fe55d56
commit f322ead2a4
28 files changed
+1625 -241

No files matched your search

Submodule luci-app-eqosplus deleted from c7ea61c971.
+47
View File
@@ -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"
+21
View File
@@ -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.
+39
View File
@@ -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
+91
View File
@@ -0,0 +1,91 @@
![hello](https://views.whatilearened.today/views/github/sirpdboy/deplives.svg) [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](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)
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明1.jpg)
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
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明2.jpg)
## interface
![screenshots](./doc/eqosplus0.png)
![screenshots](./doc/eqosplus1.png)
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/eqosplus.png)
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/eqosplus2.png)
# 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="#赞助支持本项目-"/> |
| :-----------------: | :-------------: |
|![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/支付宝.png) | ![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/微信.png) |
<a href="#readme">
<img src="https://img.shields.io/badge/-TOP-orange.svg" alt="no" title="Return TOP" align="right"/>
</a>
![hello](https://visitor-badge-deno.deno.dev/sirpdboy.sirpdboy.svg) [![](https://img.shields.io/badge/TGGroup-ClickJoin-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ)
+109
View File
@@ -0,0 +1,109 @@
![hello](https://views.whatilearened.today/views/github/sirpdboy/deplives.svg) [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](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)
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明1.jpg)
请 **认真阅读完毕** 本页面,本页面包含注意事项和如何使用。
## 功能说明:
### 定时限速1.2.2版
#### 2023.7.19 定时限速1.2.2:增加更多日期:工作日和休息日,自定义日期1,2,3中间用逗号分隔;加入MAC地址限速,从此不用担心IPV6和IPV4的限速问题。
### 定时限速1.0版
#### 2022.12.24 定时限速在eqos的加强版,加入定时限制等功能。
## 编译使用方法 [![](https://img.shields.io/badge/-编译使用方法-F5F5F5.svg)](#编译使用方法-)
将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://img.shields.io/badge/-说明-F5F5F5.svg)](#说明-)
源码来源:https://github.com/sirpdboy/luci-app-eqosplus
- 你可以随意使用其中的源码,但请注明出处。
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明2.jpg)
## 界面
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/eqosplus.png)
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/eqosplus2.png)
# 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
## 捐助
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明3.jpg)
| <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="#赞助支持本项目-"/> |
| :-----------------: | :-------------: |
|![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/支付宝.png) | ![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/微信.png) |
<a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="图飞了" title="返回顶部" align="right"/>
</a>
![](https://visitor-badge-deno.deno.dev/sirpdboy.sirpdboy.svg) [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](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 ""
+61
View File
@@ -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 "自动设置"
+61
View File
@@ -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
+379
View File
@@ -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 -- Copyright(c): lwb1978 2025–2027
local api = require("luci.passwall.api") local api = require("luci.passwall.api")
local urlencode = api.UrlEncode
local base64 = api.base64Encode local base64 = api.base64Encode
local json = api.jsonc local json = api.jsonc
local domain_redir = {} 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) local function host_format(host)
if not host then return "" end if not host then return "" end
local str = host:match("%[(.-)%]") or host 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.sni = node.servername or node.sni
o.tls.fp = node["client-fingerprint"] o.tls.fp = node["client-fingerprint"]
o.tls.pcs = node.fingerprint o.tls.pcs = node.fingerprint
o.tls.vcn = node["name-cert-verify"]
o.tls.insecure = node["skip-cert-verify"] == true o.tls.insecure = node["skip-cert-verify"] == true
o.tls.alpn = build_alpn(node.alpn) o.tls.alpn = build_alpn(node.alpn)
local ech_opts = node["ech-opts"] o.tls.ech = build_ech(node)
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
-- ===== transport ===== -- ===== transport =====
o.transport = {} o.transport = {}
@@ -89,16 +157,15 @@ local function build_common(node)
local opts = node["ws-opts"] local opts = node["ws-opts"]
if opts then if opts then
local path = opts.path or "/" local path = opts.path or "/"
local ed = opts["max-early-data"] if opts["v2ray-http-upgrade"] ~= true and opts["max-early-data"] then
local eh = opts["early-data-header-name"] path = path .. (path:find("?", 1, true) and "&ed=" or "?ed=") .. opts["max-early-data"]
if ed then
path = path .. "?ed=" .. ed
end end
if eh then if opts["v2ray-http-upgrade"] ~= true and opts["early-data-header-name"] then
path = path .. (path:find("?", 1, true) and "&eh=" or "?eh=") .. eh path = path .. (path:find("?", 1, true) and "&eh=" or "?eh=") .. opts["early-data-header-name"]
end end
o.transport.path = path o.transport.path = path
o.transport.host = opts.headers and opts.headers.Host o.transport.host = opts.headers and opts.headers.Host
if opts["v2ray-http-upgrade"] == true then o.transport.type = "httpupgrade" end
end end
elseif net == "grpc" then elseif net == "grpc" then
@@ -108,12 +175,17 @@ local function build_common(node)
end end
elseif net == "http" then 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"] local opts = node["http-opts"]
if opts then if opts then
-- Clash http-opts carries the camouflage Host under headers.Host -- Clash http-opts carries the camouflage Host under headers.Host
-- (a list), like ws-opts; opts.host is not standard for http. -- (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.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) o.transport.path = get_first(opts.path) or o.transport.path
end end
elseif net == "h2" then elseif net == "h2" then
@@ -130,85 +202,45 @@ local function build_common(node)
o.transport.path = opts.path o.transport.path = opts.path
o.transport.mode = opts.mode o.transport.mode = opts.mode
local extra = {} local extra = build_xhttp_extra(opts)
-- 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
-- download-settings -- download-settings
if opts["download-settings"] then if opts["download-settings"] then
local d = opts["download-settings"] local d = opts["download-settings"]
local ds = {} local effective = inherit(node, d)
local ds = {
if d.server then ds.address = domain_redir[d.server] or d.server end address = domain_redir[effective.server] or effective.server,
if d.port then ds.port = d.port end port = effective.port,
network = "xhttp",
ds.network = "xhttp" security = effective.tls and "tls" or "none"
}
-- TLS local sni = effective.servername or effective.sni or effective.server
if d.tls then local alpn = build_alpn(effective.alpn)
ds.security = "tls" if effective.tls or effective["reality-opts"] then
ds.tlsSettings = {} ds.tlsSettings = {
serverName = sni,
if d.servername then allowInsecure = effective["skip-cert-verify"] == true,
ds.tlsSettings.serverName = d.servername fingerprint = effective["client-fingerprint"],
end pinnedPeerCertSha256 = effective.fingerprint and api.sha256_sb_xray(effective.fingerprint) or nil,
if d["skip-cert-verify"] == true then verifyPeerCertByName = effective["name-cert-verify"],
ds.tlsSettings.allowInsecure = true alpn = alpn and api.split(alpn, ",") or nil,
end echConfigList = build_ech(effective)
if d["client-fingerprint"] then }
ds.tlsSettings.fingerprint = d["client-fingerprint"] local reality = effective["reality-opts"]
end if reality then
if d.fingerprint then ds.security = "reality"
ds.tlsSettings.pinnedPeerCertSha256 = d.fingerprint ds.realitySettings = {
end serverName = sni, fingerprint = effective["client-fingerprint"] or "chrome",
if d.alpn then publicKey = reality["public-key"], shortId = reality["short-id"] or ""
ds.tlsSettings.alpn = d.alpn }
ds.tlsSettings = nil
end end
end end
local xs = build_xhttp_extra(inherit(opts, d))
-- xhttpSettings xs.path = d.path or opts.path or "/"
local xs = {} xs.host = d.host or opts.host or sni
xs.mode = opts.mode or "auto"
if d.path then xs.path = d.path end ds.xhttpSettings = xs
if d.host then xs.host = d.host end
if next(xs) then
ds.xhttpSettings = xs
end
extra.downloadSettings = ds extra.downloadSettings = ds
end end
@@ -229,27 +261,29 @@ local function encode_vless(node)
local link = "vless://" .. node.uuid .. "@" .. o.server .. ":" .. o.port local link = "vless://" .. node.uuid .. "@" .. o.server .. ":" .. o.port
local p = {} local p = {}
if type(node.flow) == "string" and node.flow:sub(1, 5) == "xtls-" then table.insert(p, "flow=" .. urlencode(node.flow)) end if type(node.flow) == "string" and node.flow:sub(1, 5) == "xtls-" then add_param(p, "flow", node.flow) end
if node.encryption then table.insert(p, "encryption=" .. urlencode(node.encryption)) end add_param(p, "encryption", node.encryption)
-- TLS -- TLS
if o.tls.security then table.insert(p, "security=" .. o.tls.security) end add_param(p, "security", o.tls.security)
if o.tls.pbk then table.insert(p, "pbk=" .. urlencode(o.tls.pbk)) end add_param(p, "pbk", o.tls.pbk)
if o.tls.sid then table.insert(p, "sid=" .. urlencode(o.tls.sid)) end add_param(p, "sid", o.tls.sid)
if o.tls.sni then table.insert(p, "sni=" .. urlencode(o.tls.sni)) end add_param(p, "sni", o.tls.sni)
if o.tls.fp then table.insert(p, "fp=" .. urlencode(o.tls.fp)) end add_param(p, "fp", o.tls.fp)
if o.tls.alpn then table.insert(p, "alpn=" .. urlencode(o.tls.alpn)) end add_param(p, "alpn", o.tls.alpn)
if o.tls.ech then table.insert(p, "ech=" .. urlencode(o.tls.ech)) end add_param(p, "ech", o.tls.ech)
if o.tls.pcs then table.insert(p, "pcs=" .. urlencode(o.tls.pcs)) end add_param(p, "pcs", o.tls.pcs)
table.insert(p, "allowInsecure=" .. (o.tls.insecure and "1" or "0")) add_param(p, "vcn", o.tls.vcn)
add_param(p, "allowInsecure", o.tls.insecure)
-- transport -- transport
table.insert(p, "type=" .. o.transport.type) add_param(p, "type", o.transport.type)
if o.transport.host then table.insert(p, "host=" .. urlencode(o.transport.host)) end add_param(p, "headerType", o.transport.headerType)
if o.transport.path then table.insert(p, "path=" .. urlencode(o.transport.path)) end add_param(p, "host", o.transport.host)
if o.transport.serviceName then table.insert(p, "serviceName=" .. urlencode(o.transport.serviceName)) end add_param(p, "path", o.transport.path)
if o.transport.mode then table.insert(p, "mode=" .. urlencode(o.transport.mode)) end add_param(p, "serviceName", o.transport.serviceName)
if o.transport.extra then table.insert(p, "extra=" .. urlencode(o.transport.extra)) end add_param(p, "mode", o.transport.mode)
add_param(p, "extra", o.transport.extra)
if #p > 0 then if #p > 0 then
link = link .. "?" .. table.concat(p, "&") link = link .. "?" .. table.concat(p, "&")
@@ -262,23 +296,24 @@ end
local function encode_trojan(node) local function encode_trojan(node)
local o = build_common(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 = {} local p = {}
if o.tls.security then table.insert(p, "security=" .. o.tls.security) end add_param(p, "security", o.tls.security)
if o.tls.pbk then table.insert(p, "pbk=" .. urlencode(o.tls.pbk)) end add_param(p, "pbk", o.tls.pbk)
if o.tls.sid then table.insert(p, "sid=" .. urlencode(o.tls.sid)) end add_param(p, "sid", o.tls.sid)
if o.tls.sni then table.insert(p, "sni=" .. urlencode(o.tls.sni)) end add_param(p, "sni", o.tls.sni)
if o.tls.fp then table.insert(p, "fp=" .. urlencode(o.tls.fp)) end add_param(p, "fp", o.tls.fp)
if o.tls.alpn then table.insert(p, "alpn=" .. urlencode(o.tls.alpn)) end add_param(p, "alpn", o.tls.alpn)
if o.tls.ech then table.insert(p, "ech=" .. urlencode(o.tls.ech)) end add_param(p, "ech", o.tls.ech)
if o.tls.pcs then table.insert(p, "pcs=" .. urlencode(o.tls.pcs)) end add_param(p, "pcs", o.tls.pcs)
table.insert(p, "allowInsecure=" .. (o.tls.insecure and "1" or "0")) add_param(p, "vcn", o.tls.vcn)
add_param(p, "allowInsecure", o.tls.insecure)
table.insert(p, "type=" .. o.transport.type) add_param(p, "type", o.transport.type)
if o.transport.host then table.insert(p, "host=" .. urlencode(o.transport.host)) end add_param(p, "headerType", o.transport.headerType)
if o.transport.path then table.insert(p, "path=" .. urlencode(o.transport.path)) end add_param(p, "host", o.transport.host)
if o.transport.serviceName then table.insert(p, "serviceName=" .. urlencode(o.transport.serviceName)) end add_param(p, "path", o.transport.path)
add_param(p, "serviceName", o.transport.serviceName)
if #p > 0 then if #p > 0 then
link = link .. "?" .. table.concat(p, "&") link = link .. "?" .. table.concat(p, "&")
@@ -301,21 +336,22 @@ local function encode_vmess(node)
net = o.transport.type, net = o.transport.type,
security = node.cipher, security = node.cipher,
scy = node.cipher, scy = node.cipher,
type = "none", type = o.transport.headerType or "none",
host = o.transport.host or "", host = o.transport.host or "",
path = o.transport.path or "", path = urlencode(o.transport.path or ""),
tls = o.tls.security == "tls" and "tls" or "", tls = o.tls.security == "tls" and "tls" or "",
sni = o.tls.sni, sni = o.tls.sni,
alpn = o.tls.alpn, alpn = o.tls.alpn,
fp = o.tls.fp, fp = o.tls.fp,
ech = o.tls.ech, ech = o.tls.ech,
pcs = o.tls.pcs, pcs = o.tls.pcs,
vcn = o.tls.vcn,
insecure = o.tls.insecure and "1" or "0", insecure = o.tls.insecure and "1" or "0",
tfo = node.tfo and "1" or "0" tfo = node.tfo and "1" or "0"
} }
if o.transport.type == "grpc" then if o.transport.type == "grpc" then
obj.path = o.transport.serviceName or "" obj.path = urlencode(o.transport.serviceName or "")
end end
return 0, "vmess://" .. base64(json.stringify(obj)) return 0, "vmess://" .. base64(json.stringify(obj))
@@ -337,14 +373,14 @@ local function encode_ss(node)
local p = {} local p = {}
if node.udp then table.insert(p, "udp=1") end if node.udp then add_param(p, "udp", true) end
if node["udp-over-tcp"] then table.insert(p, "uot=1") end if node["udp-over-tcp"] then add_param(p, "uot", true) end
if node.plugin then if node.plugin then
local plugin = (node.plugin == "obfs") and "obfs-local" or node.plugin local plugin = (node.plugin == "obfs") and "obfs-local" or node.plugin
if plugin == "shadow-tls" then if plugin == "shadow-tls" then
local shadow_tls = base64(json.stringify(node["plugin-opts"] or {})) 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 elseif plugin == "obfs-local" or plugin == "v2ray-plugin" then
local opts = {} local opts = {}
for k, v in pairs(node["plugin-opts"] or {}) do for k, v in pairs(node["plugin-opts"] or {}) do
@@ -369,7 +405,7 @@ local function encode_ss(node)
end end
end end
if #opts > 0 then plugin = plugin .. ";" .. table.concat(opts, ";") end if #opts > 0 then plugin = plugin .. ";" .. table.concat(opts, ";") end
table.insert(p, "plugin=" .. urlencode(plugin)) add_param(p, "plugin", plugin)
else else
return 1, "订阅转换 → 丢弃 SS 节点:" .. (node.name or "") .. ",因 Core 不支持 " .. plugin .. " 插件" return 1, "订阅转换 → 丢弃 SS 节点:" .. (node.name or "") .. ",因 Core 不支持 " .. plugin .. " 插件"
end end
@@ -383,22 +419,24 @@ local function encode_ss(node)
end end
-- Hysteria -- 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 link = "hysteria://" .. host_format(node.server) .. ":" .. (node.port or "")
local p = {} local p = {}
if node["auth-str"] then table.insert(p, "auth=" .. node["auth-str"]) end add_param(p, "auth", node["auth-str"])
if node["ports"] then table.insert(p, "mport=" .. node["ports"]) end add_param(p, "mport", node.ports)
if node.obfs then table.insert(p, "obfsParam=" .. node.obfs) end add_param(p, "obfsParam", node.obfs)
if node.sni then table.insert(p, "sni=" .. node.sni) end add_param(p, "sni", node.sni)
if node.up then table.insert(p, "upmbps=" .. node.up) end add_param(p, "upmbps", bandwidth_mbps(node.up))
if node.down then table.insert(p, "downmbps=" .. node.down) end add_param(p, "downmbps", bandwidth_mbps(node.down))
if node["skip-cert-verify"] then table.insert(p, "insecure=1") end add_param(p, "insecure", node["skip-cert-verify"] == true)
if node["fingerprint"] then table.insert(p, "pinSHA256=" .. urlencode(node["fingerprint"])) end add_param(p, "vcn", node["name-cert-verify"])
add_param(p, "pinSHA256", node.fingerprint)
if node.alpn then add_param(p, "alpn", build_alpn(node.alpn))
table.insert(p, "alpn=" .. urlencode(build_alpn(node.alpn)))
end
if #p > 0 then if #p > 0 then
link = link .. "?" .. table.concat(p, "&") link = link .. "?" .. table.concat(p, "&")
@@ -409,20 +447,24 @@ end
-- Hysteria2 -- Hysteria2
local function encode_hysteria2(node) 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 = {} local p = {}
if node["ports"] then table.insert(p, "mport=" .. urlencode(node["ports"])) end add_param(p, "mport", node.ports)
if node.obfs then table.insert(p, "obfs=" .. node.obfs) end add_param(p, "hopInterval", node["hop-interval"])
if node["obfs-password"] then table.insert(p, "obfs-password=" .. node["obfs-password"]) end add_param(p, "obfs", node.obfs)
if node["obfs-min-packet-size"] then table.insert(p, "minPacketSize=" .. node["obfs-min-packet-size"]) end add_param(p, "obfs-password", node["obfs-password"])
if node["obfs-max-packet-size"] then table.insert(p, "maxPacketSize=" .. node["obfs-max-packet-size"]) end add_param(p, "minPacketSize", node["obfs-min-packet-size"])
if node.up then table.insert(p, "upmbps=" .. node.up) end add_param(p, "maxPacketSize", node["obfs-max-packet-size"])
if node.down then table.insert(p, "downmbps=" .. node.down) end add_param(p, "upmbps", bandwidth_mbps(node.up))
add_param(p, "downmbps", bandwidth_mbps(node.down))
if node.sni then table.insert(p, "sni=" .. urlencode(node.sni)) end add_param(p, "ech", build_ech(node))
if node["skip-cert-verify"] then table.insert(p, "insecure=1") end add_param(p, "vcn", node["name-cert-verify"])
if node["fingerprint"] then table.insert(p, "pinSHA256=" .. urlencode(node["fingerprint"])) end 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 if #p > 0 then
link = link .. "?" .. table.concat(p, "&") link = link .. "?" .. table.concat(p, "&")
@@ -433,22 +475,23 @@ end
-- TUIC -- TUIC
local function encode_tuic(node) 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 = {} local p = {}
if node["congestion-controller"] then add_param(p, "congestion_control", node["congestion-controller"])
table.insert(p, "congestion_control=" .. node["congestion-controller"]) add_param(p, "alpn", build_alpn(node.alpn))
end add_param(p, "sni", node.sni)
if node["disable-sni"] then add_param(p, "disable_sni", true) end
if node.alpn then add_param(p, "allowInsecure", node["skip-cert-verify"] == true)
table.insert(p, "alpn=" .. urlencode(build_alpn(node.alpn))) add_param(p, "udp_relay_mode", node["udp-relay-mode"])
end add_param(p, "pcs", node.fingerprint)
add_param(p, "vcn", node["name-cert-verify"])
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
if #p > 0 then if #p > 0 then
link = link .. "?" .. table.concat(p, "&") link = link .. "?" .. table.concat(p, "&")
@@ -474,18 +517,18 @@ local function encode_anytls(node)
local o = build_common(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 = {} local p = {}
if o.tls.security then table.insert(p, "security=" .. o.tls.security) end add_param(p, "security", o.tls.security)
if o.tls.pbk then table.insert(p, "pbk=" .. urlencode(o.tls.pbk)) end add_param(p, "pbk", o.tls.pbk)
if o.tls.sid then table.insert(p, "sid=" .. urlencode(o.tls.sid)) end add_param(p, "sid", o.tls.sid)
if o.tls.sni then table.insert(p, "sni=" .. urlencode(o.tls.sni)) end add_param(p, "sni", o.tls.sni)
if o.tls.alpn then table.insert(p, "alpn=" .. urlencode(o.tls.alpn)) end add_param(p, "alpn", o.tls.alpn)
if o.tls.fp then table.insert(p, "fp=" .. urlencode(o.tls.fp)) end add_param(p, "fp", o.tls.fp)
if o.tls.ech then table.insert(p, "ech=" .. urlencode(o.tls.ech)) end add_param(p, "ech", o.tls.ech)
if o.tls.pcs then table.insert(p, "pcs=" .. urlencode(o.tls.pcs)) end add_param(p, "pcs", o.tls.pcs)
table.insert(p, "insecure=" .. (o.tls.insecure and "1" or "0")) add_param(p, "insecure", o.tls.insecure)
if #p > 0 then if #p > 0 then
link = link .. "?" .. table.concat(p, "&") link = link .. "?" .. table.concat(p, "&")
@@ -514,8 +557,8 @@ end
-- snell -- snell
local function encode_snell(node) local function encode_snell(node)
local err_msg local err_msg
local obfs = node["obfs-opts"] local obfs = node["obfs-opts"] or {}
if obfs and obfs.mode ~= "http" and obfs.mode ~= "none" then if obfs.mode and obfs.mode ~= "http" and obfs.mode ~= "none" then
err_msg = obfs.mode err_msg = obfs.mode
end end
if err_msg then if err_msg then
@@ -532,13 +575,13 @@ local function encode_snell(node)
local link = "snell://" .. host_format(node.server) .. ":" .. node.port local link = "snell://" .. host_format(node.server) .. ":" .. node.port
local p = {} local p = {}
if node.psk then table.insert(p, "psk=" .. urlencode(node.psk)) end add_param(p, "psk", node.psk)
table.insert(p, "version=" .. version) add_param(p, "version", version)
if obfs.mode == "http" then if obfs.mode == "http" then
table.insert(p, "obfs=http") add_param(p, "obfs", "http")
if obfs.host then table.insert(p, "obfs-host=" .. urlencode(obfs.host)) end add_param(p, "obfs-host", obfs.host)
end 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 if #p > 0 then
link = link .. "?" .. table.concat(p, "&") link = link .. "?" .. table.concat(p, "&")
@@ -548,9 +591,22 @@ local function encode_snell(node)
end end
local function encode_node(node) 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 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 node.server = domain_redir[node.server] or node.server
@@ -570,17 +626,22 @@ end
function parseClashNode(raw, remark) function parseClashNode(raw, remark)
if not raw then return "" end if not raw then return "" end
domain_redir = {}
local ok, lyaml = pcall(require, "lyaml") local ok, lyaml = pcall(require, "lyaml")
if not ok then return raw end 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 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 订阅,正在进行转换 ...') api.log('检测到 Clash 订阅,正在进行转换 ...')
-- Some airports use hosts domain redirect to disguise the real node domain name. -- 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 type(k) == "string" and type(v) == "string" then
if api.datatypes.hostname(k) and api.datatypes.hostname(v) then if api.datatypes.hostname(k) and api.datatypes.hostname(v) then
domain_redir[k] = v domain_redir[k] = v
@@ -590,8 +651,11 @@ function parseClashNode(raw, remark)
local links = {} local links = {}
for _, node in ipairs(data.proxies) do for _, node in ipairs(data.proxies) do
local err, link = encode_node(node) local converted, err, link = pcall(encode_node, node)
if err == 1 and link then 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) api.log(link)
elseif link then elseif link then
table.insert(links, link) table.insert(links, link)
@@ -130,9 +130,13 @@ end
local nodeResult = {} -- update result local nodeResult = {} -- update result
local isDebug = false local isDebug = false
local log_buffer = nil
local log = function(...) 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({...}, " ") local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
print(result) print(result)
else else
@@ -140,6 +144,14 @@ local log = function(...)
end end
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 = {} local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" then if e.node_type == "normal" then
@@ -885,6 +897,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.method = method result.method = method
result.ss_method = method result.ss_method = method
result.password = password result.password = password
result.uot = params.uot
result.tcp_fast_open = params.tfo result.tcp_fast_open = params.tfo
result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil 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 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 end
local insecure = params.allowinsecure or params.allowInsecure or params.insecure 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.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 else
result.error_msg = "请更换 Xray 或 Sing-Box 来支持 SS 更多的传输方式。" result.error_msg = "请更换 Xray 或 Sing-Box 来支持 SS 更多的传输方式。"
end end
@@ -1420,7 +1433,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
for _, v in pairs(split(query[2], '&')) do for _, v in pairs(split(query[2], '&')) do
local s = v:find("=", 1, true) local s = v:find("=", 1, true)
if s and s > 1 then 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
end end
-- [2001:4860:4860::8888]:443 -- [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_up_mbps = params.upmbps or sub_hy_up_mbps
result.hysteria_down_mbps = params.downmbps or sub_hy_down_mbps result.hysteria_down_mbps = params.downmbps or sub_hy_down_mbps
result.hysteria_hop = params.mport result.hysteria_hop = params.mport
result.tls_CertByName = params.vcn
elseif szType == 'hysteria2' or szType == 'hy2' then elseif szType == 'hysteria2' or szType == 'hy2' then
local alias = "" 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_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_down_mbps = params.downmbps or (sub_cfg and sub_hy_down_mbps or nil)
result.hysteria2_hop = params.mport result.hysteria2_hop = params.mport
result.hysteria2_hop_interval = params.hopinterval or params["hop-interval"]
if params.ech and params.ech ~= "" then if params.ech and params.ech ~= "" then
result.ech = "1" result.ech = "1"
result.ech_config = params.ech result.ech_config = params.ech
@@ -1569,6 +1584,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
end end
result.tls_serverName = params.sni result.tls_serverName = params.sni
result.tls_disable_sni = params.disable_sni result.tls_disable_sni = params.disable_sni
result.tls_CertByName = params.vcn
result.tls_pinSHA256 = params.pcs or params.pinsha256 result.tls_pinSHA256 = params.pcs or params.pinsha256
result.tuic_alpn = params.alpn or "h3" result.tuic_alpn = params.alpn or "h3"
result.tuic_congestion_control = params.congestion_control or "cubic" result.tuic_congestion_control = params.congestion_control or "cubic"
@@ -1994,10 +2010,29 @@ local function select_node(nodes, config, parentConfig)
end end
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) local function update_node(manual)
if next(nodeResult) == nil then if next(nodeResult) == nil then
log("没有可用的节点信息更新。") log("没有可用的节点信息更新。")
return return false
end end
local group = {} local group = {}
@@ -2087,17 +2122,6 @@ local function update_node(manual)
end end
end 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 if next(CONFIG) then
local nodes = {} local nodes = {}
uci_foreach("nodes", function(node) uci_foreach("nodes", function(node)
@@ -2119,9 +2143,7 @@ local function update_node(manual)
end end
end end
uci_save(true) return true
if manual ~= 1 then restart_required = true end
end end
local function parse_link(raw, add_mode, group, sub_cfg) 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) log('丢弃节点:' .. result.remarks .. " ,原因:" .. result.error_msg)
elseif not result.type then elseif not result.type then
log('丢弃节点:' .. result.remarks .. " ,找不到可使用二进制。") 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 (not datatypes.hostname(result.address) and not (api.is_ip(result.address))) then
log('丢弃过滤节点:' .. result.type .. ' 节点,' .. result.remarks) log('丢弃过滤节点:' .. result.type .. ' 节点,' .. result.remarks)
else else
@@ -2205,17 +2228,21 @@ local function parse_link(raw, add_mode, group, sub_cfg)
end end
end end
if #node_list > 0 then if #node_list > 0 then
nodeResult[#nodeResult + 1] = { if add_mode ~= "2" then
remark = group, nodeResult[#nodeResult + 1] = {
list = node_list, remark = group,
sub_cfg = sub_cfg list = node_list,
} sub_cfg = sub_cfg
}
end
end end
log('成功解析【' .. group .. '】节点数量:' .. #node_list) log('成功解析【' .. group .. '】节点数量:' .. #node_list)
return node_list
else else
if add_mode == "2" then if add_mode == "2" then
log('获取到的【' .. group .. '】订阅内容为空,可能是订阅地址无效,或是网络问题,请诊断!') log('获取到的【' .. group .. '】订阅内容为空,可能是订阅地址无效,或是网络问题,请诊断!')
end end
return {}
end end
end end
@@ -2266,15 +2293,32 @@ local execute = function()
local stdout = f:read("*all") local stdout = f:read("*all")
f:close() f:close()
local raw_data = api.trim(stdout) 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 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 if not manual_sub and new_md5 ~= "" and old_md5 == new_md5 then
log_buffer = nil -- 无物理变动,直接丢弃暂存的丢弃与计数明细
log('订阅:【' .. remark .. '】没有变化,无需更新。') log('订阅:【' .. remark .. '】没有变化,无需更新。')
else else
raw_data = parseClashNode(raw_data) -- 节点发生变动时,回放前置解析期间的节点统计与丢弃日志
subscribe_info[cfgid] = parse_clash_sub_info(headers) flush_log_buffer()
parse_link(raw_data, "2", remark, value) -- 暂存有效节点及其 MD5,完成节点更新后再一起写入配置。
uci_set(cfgid, "md5", new_md5) if #parsed_nodes > 0 then
nodeResult[#nodeResult + 1] = {
remark = remark,
list = parsed_nodes,
sub_cfg = value,
md5 = new_md5
}
end
end end
else else
fail_list[#fail_list + 1] = value fail_list[#fail_list + 1] = value
@@ -2284,19 +2328,33 @@ local execute = function()
end end
end end
end end
if #fail_list > 0 then if #fail_list > 0 then
for index, value in ipairs(fail_list) do for index, value in ipairs(fail_list) do
log(string.format('【%s】订阅失败,可能是订阅地址无效,或是网络问题,请诊断![%s]', (value.remark or ""), tostring(value.http_code or 0))) log(string.format('【%s】订阅失败,可能是订阅地址无效,或是网络问题,请诊断![%s]', (value.remark or ""), tostring(value.http_code or 0)))
end end
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
end end
if arg[1] == "start" then if arg[1] == "start" then
log('开始订阅...') log('开始订阅...')
xpcall(execute, function(e) xpcall(execute, function(e)
flush_log_buffer()
log(e) log(e)
if type(debug) == "table" and type(debug.traceback) == "function" then if type(debug) == "table" and type(debug.traceback) == "function" then
log(debug.traceback()) log(debug.traceback())
@@ -2309,7 +2367,7 @@ elseif arg[1] == "add" then
local raw = f:read('*all') local raw = f:read('*all')
f:close() f:close()
parse_link(raw, "1", arg[2]) parse_link(raw, "1", arg[2])
update_node(1) if update_node(1) then uci_save(true) end
api.remove("/tmp/links.conf") api.remove("/tmp/links.conf")
elseif arg[1] == "truncate" then elseif arg[1] == "truncate" then
truncate_nodes(arg[2]) truncate_nodes(arg[2])
+2 -2
View File
@@ -21,13 +21,13 @@ define Download/geoip
HASH:=4ff4b2ec242a9e87b296ba27ddb85f8311e8d3e071eaebac5be9231c60e408dd HASH:=4ff4b2ec242a9e87b296ba27ddb85f8311e8d3e071eaebac5be9231c60e408dd
endef endef
GEOSITE_VER:=20261019032359 GEOSITE_VER:=20261019074502
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
define Download/geosite define Download/geosite
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
URL_FILE:=dlc.dat URL_FILE:=dlc.dat
FILE:=$(GEOSITE_FILE) FILE:=$(GEOSITE_FILE)
HASH:=1092fd1a58deccd964e92b5ff05ce01ecabaf67b8242ade8bdc6b6b35821d402 HASH:=37c030c086c57da4d0987c6be836f8ae4f8957cd904cfa0bc6bb6036bb61c816
endef endef
GEOSITE_IRAN_VER:=202610120242 GEOSITE_IRAN_VER:=202610120242
+8 -1
View File
@@ -131,7 +131,7 @@ jobs:
rm -rf /etc/apt/sources.list.d/* rm -rf /etc/apt/sources.list.d/*
fi fi
apt-get update -o Acquire::Check-Valid-Until=false || apt-get update 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/* rm -rf /var/lib/apt/lists/*
elif command -v apk >/dev/null 2>&1; then elif command -v apk >/dev/null 2>&1; then
apk add --no-cache dwarves apk add --no-cache dwarves
@@ -149,6 +149,13 @@ jobs:
mkdir -p /builder/staging_dir/host/bin mkdir -p /builder/staging_dir/host/bin
ln -sf "$pahole_bin" /builder/staging_dir/host/bin/pahole 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 # Fix permissions and compile as buildbot user
chown -R 1000:1000 /builder /artifacts chown -R 1000:1000 /builder /artifacts
su -s /bin/bash buildbot -c '\'' su -s /bin/bash buildbot -c '\''
+7 -15
View File
@@ -35,14 +35,11 @@ define Package/vmlinux-btf/prerm
#!/bin/sh #!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] || rm -f /usr/lib/debug/boot/vmlinux-$$(uname -r) [ -n "$${IPKG_INSTROOT}" ] || rm -f /usr/lib/debug/boot/vmlinux-$$(uname -r)
endef endef
# Toolchain and helpers
BTF_CC ?= $(if $(TARGET_CC_NOCACHE),$(TARGET_CC_NOCACHE),$(TARGET_CROSS)gcc) BTF_CC ?= $(if $(TARGET_CC_NOCACHE),$(TARGET_CC_NOCACHE),$(TARGET_CROSS)gcc)
BTF_PAHOLE ?= $(firstword \ BTF_PAHOLE ?= $(firstword \
$(wildcard $(STAGING_DIR_HOST)/bin/pahole /usr/bin/pahole /usr/local/bin/pahole) \ $(wildcard $(STAGING_DIR_HOST)/bin/pahole /usr/bin/pahole /usr/local/bin/pahole) \
$(shell command -v pahole 2>/dev/null) \ $(shell command -v pahole 2>/dev/null) \
pahole) 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)) KERNEL_RELEASE := $(if $(wildcard $(LINUX_DIR)/include/config/kernel.release),$(shell cat $(LINUX_DIR)/include/config/kernel.release),$(LINUX_VERSION))
BTF_CFLAGS := \ BTF_CFLAGS := \
@@ -81,21 +78,16 @@ define Build/Compile
exit 1; \ exit 1; \
fi 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_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_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 -$(BTF_CC) $(BTF_CFLAGS) -c $(PKG_BUILD_DIR)/shadow_btf_private.c -o $(PKG_BUILD_DIR)/shadow_btf_private.o 2>/dev/null
$(TARGET_CROSS)ld -r -o $(PKG_BUILD_DIR)/shadow_btf.o $(PKG_BUILD_DIR)/shadow_btf_*.o
# Link compiled objects into a single relocatable object if $(BTF_PAHOLE) --help 2>&1 | grep -q -- '--btf_encode_detached'; then \
objs="$(PKG_BUILD_DIR)/shadow_btf_core.o"; \ $(BTF_PAHOLE) --btf_encode_detached=$(PKG_BUILD_DIR)/vmlinux-btf $(PKG_BUILD_DIR)/shadow_btf.o; \
[ ! -f "$(PKG_BUILD_DIR)/shadow_btf_modules.o" ] || objs="$$objs $(PKG_BUILD_DIR)/shadow_btf_modules.o"; \ else \
[ ! -f "$(PKG_BUILD_DIR)/shadow_btf_private.o" ] || objs="$$objs $(PKG_BUILD_DIR)/shadow_btf_private.o"; \ $(BTF_PAHOLE) -J $(PKG_BUILD_DIR)/shadow_btf.o && \
$(TARGET_CROSS)ld -r -o $(PKG_BUILD_DIR)/shadow_btf.o $$objs $(TARGET_CROSS)objcopy --dump-section .BTF=$(PKG_BUILD_DIR)/vmlinux-btf $(PKG_BUILD_DIR)/shadow_btf.o; \
fi
# Extract detached BTF
$(BTF_PAHOLE) --jobs=$(PAHOLE_JOBS) \
--btf_encode_detached=$(PKG_BUILD_DIR)/vmlinux-btf \
$(PKG_BUILD_DIR)/shadow_btf.o
test -s $(PKG_BUILD_DIR)/vmlinux-btf test -s $(PKG_BUILD_DIR)/vmlinux-btf
$(BTF_PAHOLE) -C sk_buff $(PKG_BUILD_DIR)/shadow_btf.o >/dev/null $(BTF_PAHOLE) -C sk_buff $(PKG_BUILD_DIR)/shadow_btf.o >/dev/null
endef endef