mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 16:44:23 +08:00
This commit is contained in:
1 parent
d1e51134a0
commit
afd9decbc5
26 files changed
+269
-97
No files matched your search
@@ -1,5 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_NAME:=luci-app-adguardhome
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-open-box
|
||||
PKG_VERSION:=0.1.383
|
||||
PKG_RELEASE:=55
|
||||
PKG_VERSION:=0.1.385
|
||||
PKG_RELEASE:=56
|
||||
|
||||
PKG_MAINTAINER:=liandu2024
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=26.10.29
|
||||
PKG_RELEASE:=360
|
||||
PKG_RELEASE:=361
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -207,11 +207,11 @@ domain_list.validate = function(self, value)
|
||||
return value
|
||||
end
|
||||
domain_list.description = "<br /><ul>"
|
||||
.. "<li>" .. translate("Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'.") .. "</li>"
|
||||
.. "<li>" .. translate("Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn.") .. "</li>"
|
||||
.. "<li>" .. translate("Plaintext: If this string matches any part of the targeting domain, this rule takes effect. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'.") .. "</li>"
|
||||
.. "<li>" .. translate("Regular expression: Beginning with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Subdomain (recommended): Beginning with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Full domain: Beginning with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'full:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Pre-defined domain list: Beginning with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn.") .. "</li>"
|
||||
.. "<li>"
|
||||
.. translate("Sing-Box is compatible with Geo rules and rule-set. rule-set begin with 'rule-set:remote:' or 'rule-set:local:'.")
|
||||
.. "<ul>"
|
||||
@@ -219,7 +219,7 @@ domain_list.description = "<br /><ul>"
|
||||
.. "<li>" .. translate("Such as:") .. "'rule-set:local:/usr/share/sing-box/geosite-cn.srs'" .. "</li>"
|
||||
.. "</ul>"
|
||||
.. "</li>"
|
||||
.. "<li>" .. translate("Annotation: Begining with #") .. "</li>"
|
||||
.. "<li>" .. translate("Annotation: Beginning with #") .. "</li>"
|
||||
.. "</ul>"
|
||||
ip_list = s:option(TextValue, "ip_list", "IP")
|
||||
ip_list.rows = 10
|
||||
@@ -256,7 +256,7 @@ ip_list.description = "<br /><ul>"
|
||||
.. "<li>" .. translate("Such as:") .. "'rule-set:local:/usr/share/sing-box/geoip-cn.srs'" .. "</li>"
|
||||
.. "</ul>"
|
||||
.. "</li>"
|
||||
.. "<li>" .. translate("Annotation: Begining with #") .. "</li>"
|
||||
.. "<li>" .. translate("Annotation: Beginning with #") .. "</li>"
|
||||
.. "</ul>"
|
||||
|
||||
o = s:option(Flag, "invert", "Invert", translate("Invert match result.") .. " " .. translate("Only support Sing-Box."))
|
||||
|
||||
@@ -1223,19 +1223,19 @@ msgstr "不是有效 IPv6 格式,请重新输入!"
|
||||
msgid "Not true format, please re-enter!"
|
||||
msgstr "不是正确的格式,请重新输入!"
|
||||
|
||||
msgid "Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'."
|
||||
msgid "Plaintext: If this string matches any part of the targeting domain, this rule takes effect. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'."
|
||||
msgstr "纯字符串: 当此字符串匹配目标域名中任意部分,该规则生效。比如'sina.com'可以匹配'sina.com'、'sina.com.cn'和'www.sina.com',但不匹配'sina.cn'。"
|
||||
|
||||
msgid "Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'."
|
||||
msgid "Regular expression: Beginning with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'."
|
||||
msgstr "正则表达式: 由'regexp:'开始,余下部分是一个正则表达式。当此正则表达式匹配目标域名时,该规则生效。例如'regexp:\\.goo.*\\.com$'匹配'www.google.com'、'fonts.googleapis.com',但不匹配'google.com'。"
|
||||
|
||||
msgid "Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'."
|
||||
msgid "Subdomain (recommended): Beginning with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'."
|
||||
msgstr "子域名 (推荐): 由'domain:'开始,余下部分是一个域名。当此域名是目标域名或其子域名时,该规则生效。例如'domain:v2ray.com'匹配'www.v2ray.com'、'v2ray.com',但不匹配'xv2ray.com'。"
|
||||
|
||||
msgid "Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'."
|
||||
msgid "Full domain: Beginning with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'full:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'."
|
||||
msgstr "完整匹配: 由'full:'开始,余下部分是一个域名。当此域名完整匹配目标域名时,该规则生效。例如'full:v2ray.com'匹配'v2ray.com'但不匹配'www.v2ray.com'。"
|
||||
|
||||
msgid "Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
|
||||
msgid "Pre-defined domain list: Beginning with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
|
||||
msgstr "预定义域名列表:由'geosite:'开头,余下部分是一个名称,如geosite:google或者geosite:cn。"
|
||||
|
||||
msgid "Sing-Box is compatible with Geo rules and rule-set. rule-set begin with 'rule-set:remote:' or 'rule-set:local:'."
|
||||
@@ -1244,7 +1244,7 @@ msgstr "Sing-Box 可兼容 Geo 规则和规则集,规则集由 'rule-set:remot
|
||||
msgid "Such as:"
|
||||
msgstr "例如:"
|
||||
|
||||
msgid "Annotation: Begining with #"
|
||||
msgid "Annotation: Beginning with #"
|
||||
msgstr "注释: 由 # 开头"
|
||||
|
||||
msgid "Invert match result."
|
||||
|
||||
@@ -216,7 +216,7 @@ insert_nftset() {
|
||||
gen_nftset() {
|
||||
local nftset_name="${1}"; shift
|
||||
local ip_type="${1}"; shift
|
||||
# 0 - don't set defalut timeout
|
||||
# 0 - don't set default timeout
|
||||
local timeout_argument_set="${1}"; shift
|
||||
local gc_interval_time="1h"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.10.29
|
||||
PKG_RELEASE:=161
|
||||
PKG_RELEASE:=162
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@@ -210,19 +210,19 @@ domain_list.validate = function(self, value)
|
||||
return value
|
||||
end
|
||||
domain_list.description = "<br /><ul>"
|
||||
.. "<li>" .. translate("Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'.") .. "</li>"
|
||||
.. "<li>" .. translate("Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn.") .. "</li>"
|
||||
.. "<li>" .. translate("Plaintext: If this string matches any part of the targeting domain, this rule takes effect. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'.") .. "</li>"
|
||||
.. "<li>" .. translate("Regular expression: Beginning with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Subdomain (recommended): Beginning with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Full domain: Beginning with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'full:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'.") .. "</li>"
|
||||
.. "<li>" .. translate("Pre-defined domain list: Beginning with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn.") .. "</li>"
|
||||
.. "<li>"
|
||||
.. translate("Sing-Box rule-set: Begining with 'rule-set:remote:' or 'rule-set:local:'")
|
||||
.. translate("Sing-Box rule-set: Beginning with 'rule-set:remote:' or 'rule-set:local:'")
|
||||
.. "<ul>"
|
||||
.. "<li>" .. translate("Such as:") .. "'rule-set:remote:https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs'" .. "</li>"
|
||||
.. "<li>" .. translate("Such as:") .. "'rule-set:local:/usr/share/sing-box/geosite-cn.srs'" .. "</li>"
|
||||
.. "</ul>"
|
||||
.. "</li>"
|
||||
.. "<li>" .. translate("Annotation: Begining with #") .. "</li>"
|
||||
.. "<li>" .. translate("Annotation: Beginning with #") .. "</li>"
|
||||
.. "</ul>"
|
||||
ip_list = s:option(TextValue, "ip_list", "IP")
|
||||
ip_list.rows = 10
|
||||
@@ -253,13 +253,13 @@ ip_list.description = "<br /><ul>"
|
||||
.. "<li>" .. translate("CIDR: such as '127.0.0.0/8'.") .. "</li>"
|
||||
.. "<li>" .. translate("GeoIP: such as 'geoip:cn'. It begins with geoip: (lower case) and followed by two letter of country code.") .. "</li>"
|
||||
.. "<li>"
|
||||
.. translate("Sing-Box rule-set: Begining with 'rule-set:remote:' or 'rule-set:local:'")
|
||||
.. translate("Sing-Box rule-set: Beginning with 'rule-set:remote:' or 'rule-set:local:'")
|
||||
.. "<ul>"
|
||||
.. "<li>" .. translate("Such as:") .. "'rule-set:remote:https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs'" .. "</li>"
|
||||
.. "<li>" .. translate("Such as:") .. "'rule-set:local:/usr/share/sing-box/geoip-cn.srs'" .. "</li>"
|
||||
.. "</ul>"
|
||||
.. "</li>"
|
||||
.. "<li>" .. translate("Annotation: Begining with #") .. "</li>"
|
||||
.. "<li>" .. translate("Annotation: Beginning with #") .. "</li>"
|
||||
.. "</ul>"
|
||||
|
||||
o = s:option(Flag, "invert", "invert", translate("Invert match result.") .. " " .. translate("Only support Sing-Box."))
|
||||
|
||||
@@ -1216,28 +1216,28 @@ msgstr "فرمت IPv6 نامعتبر است، لطفاً دوباره وارد
|
||||
msgid "Not true format, please re-enter!"
|
||||
msgstr "فرمت صحیح نیست، لطفاً دوباره وارد کنید!"
|
||||
|
||||
msgid "Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'."
|
||||
msgid "Plaintext: If this string matches any part of the targeting domain, this rule takes effect. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'."
|
||||
msgstr "متن ساده (Plaintext): اگر این رشته با هر بخشی از دامنه هدف مطابقت داشته باشد، این قانون اعمال میشود. مثال: قانون 'sina.com' با دامنه هدف 'sina.com'، 'sina.com.cn' و 'www.sina.com' مطابقت دارد، اما با 'sina.cn' خیر."
|
||||
|
||||
msgid "Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'."
|
||||
msgid "Regular expression: Beginning with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'."
|
||||
msgstr "عبارت باقاعده (Regular expression): با 'regexp:' شروع میشود و بقیه یک عبارت باقاعده است. زمانی که عبارت باقاعده با دامنه هدف مطابقت داشته باشد، این قانون اعمال میشود. مثال: قانون 'regexp:\\.goo.*\\.com$' با 'www.google.com' و 'fonts.googleapis.com' مطابقت دارد، اما با 'google.com' خیر."
|
||||
|
||||
msgid "Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'."
|
||||
msgid "Subdomain (recommended): Beginning with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'."
|
||||
msgstr "زیردامنه (توصیه شده): با 'domain:' شروع میشود و بقیه یک دامنه است. زمانی که دامنه هدف دقیقاً مقدار باشد، یا زیردامنهای از مقدار باشد، این قانون اعمال میشود. مثال: قانون 'domain:v2ray.com' با 'www.v2ray.com' و 'v2ray.com' مطابقت دارد، اما با 'xv2ray.com' خیر."
|
||||
|
||||
msgid "Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'."
|
||||
msgstr "دامنه کامل: با 'full:' شروع میشود و بقیه یک دامنه است. زمانی که دامنه هدف دقیقاً مقدار باشد، قانون اعمال میشود. مثال: قانون 'domain:v2ray.com' با 'v2ray.com' مطابقت دارد، اما با 'www.v2ray.com' خیر."
|
||||
msgid "Full domain: Beginning with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'full:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'."
|
||||
msgstr "دامنه کامل: با 'full:' شروع میشود و بقیه یک دامنه است. زمانی که دامنه هدف دقیقاً مقدار باشد، قانون اعمال میشود. مثال: قانون 'full:v2ray.com' با 'v2ray.com' مطابقت دارد، اما با 'www.v2ray.com' خیر."
|
||||
|
||||
msgid "Sing-Box rule-set: Begining with 'rule-set:remote:' or 'rule-set:local:'"
|
||||
msgid "Sing-Box rule-set: Beginning with 'rule-set:remote:' or 'rule-set:local:'"
|
||||
msgstr "مجموعه قوانین Sing-Box: با 'rule-set:remote:' یا 'rule-set:local:' شروع میشود"
|
||||
|
||||
msgid "Such as:"
|
||||
msgstr "مانند:"
|
||||
|
||||
msgid "Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
|
||||
msgid "Pre-defined domain list: Beginning with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
|
||||
msgstr "لیست دامنه از پیش تعریف شده: با 'geosite:' شروع میشود و بقیه یک نام است، مانند geosite:google یا geosite:cn."
|
||||
|
||||
msgid "Annotation: Begining with #"
|
||||
msgid "Annotation: Beginning with #"
|
||||
msgstr "توضیحات: با # شروع میشود"
|
||||
|
||||
msgid "Invert match result."
|
||||
|
||||
@@ -1217,28 +1217,28 @@ msgstr "Неверный формат IPv6, пожалуйста, введите
|
||||
msgid "Not true format, please re-enter!"
|
||||
msgstr "Неверный формат, пожалуйста, введите заново!"
|
||||
|
||||
msgid "Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'."
|
||||
msgid "Plaintext: If this string matches any part of the targeting domain, this rule takes effect. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'."
|
||||
msgstr "Текст: правило срабатывает, если строка встречается в любом месте домена. Например, 'sina.com' совпадёт с 'sina.com', 'sina.com.cn', 'www.sina.com', но не с 'sina.cn'."
|
||||
|
||||
msgid "Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'."
|
||||
msgid "Regular expression: Beginning with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'."
|
||||
msgstr "Регулярное выражение: начинается с 'regexp:', далее — шаблон. Правило срабатывает при совпадении. Например, 'regexp:\\.goo.*\\.com$' совпадёт с 'www.google.com' и 'fonts.googleapis.com', но не с 'google.com'."
|
||||
|
||||
msgid "Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'."
|
||||
msgid "Subdomain (recommended): Beginning with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'."
|
||||
msgstr "Поддомен (рекомендуется): начинается с 'domain:', далее — домен. Правило срабатывает для точного совпадения или любого поддомена. Например, 'domain:v2ray.com' совпадёт с 'www.v2ray.com' и 'v2ray.com', но не с 'xv2ray.com'."
|
||||
|
||||
msgid "Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'."
|
||||
msgid "Full domain: Beginning with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'full:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'."
|
||||
msgstr "Полное совпадение: начинается с 'full:', далее — домен. Правило срабатывает только при точном совпадении. Например, 'full:v2ray.com' совпадёт только с 'v2ray.com', но не с 'www.v2ray.com'."
|
||||
|
||||
msgid "Sing-Box rule-set: Begining with 'rule-set:remote:' or 'rule-set:local:'"
|
||||
msgid "Sing-Box rule-set: Beginning with 'rule-set:remote:' or 'rule-set:local:'"
|
||||
msgstr "Набор правил Sing-Box: начинается с 'rule-set:remote:' или 'rule-set:local:'"
|
||||
|
||||
msgid "Such as:"
|
||||
msgstr "Например:"
|
||||
|
||||
msgid "Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
|
||||
msgid "Pre-defined domain list: Beginning with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
|
||||
msgstr "Предопределённый список: начинается с 'geosite:', далее — имя набора, например geosite:google или geosite:cn."
|
||||
|
||||
msgid "Annotation: Begining with #"
|
||||
msgid "Annotation: Beginning with #"
|
||||
msgstr "Комментарий начинается с #"
|
||||
|
||||
msgid "Invert match result."
|
||||
|
||||
@@ -1208,28 +1208,28 @@ msgstr "不是有效IPv6格式,请重新输入!"
|
||||
msgid "Not true format, please re-enter!"
|
||||
msgstr "不是正确的格式,请重新输入!"
|
||||
|
||||
msgid "Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'."
|
||||
msgid "Plaintext: If this string matches any part of the targeting domain, this rule takes effect. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'."
|
||||
msgstr "纯字符串: 当此字符串匹配目标域名中任意部分,该规则生效。比如'sina.com'可以匹配'sina.com'、'sina.com.cn'和'www.sina.com',但不匹配'sina.cn'。"
|
||||
|
||||
msgid "Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'."
|
||||
msgid "Regular expression: Beginning with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'."
|
||||
msgstr "正则表达式: 由'regexp:'开始,余下部分是一个正则表达式。当此正则表达式匹配目标域名时,该规则生效。例如'regexp:\\.goo.*\\.com$'匹配'www.google.com'、'fonts.googleapis.com',但不匹配'google.com'。"
|
||||
|
||||
msgid "Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'."
|
||||
msgid "Subdomain (recommended): Beginning with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'."
|
||||
msgstr "子域名 (推荐): 由'domain:'开始,余下部分是一个域名。当此域名是目标域名或其子域名时,该规则生效。例如'domain:v2ray.com'匹配'www.v2ray.com'、'v2ray.com',但不匹配'xv2ray.com'。"
|
||||
|
||||
msgid "Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'."
|
||||
msgid "Full domain: Beginning with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'full:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'."
|
||||
msgstr "完整匹配: 由'full:'开始,余下部分是一个域名。当此域名完整匹配目标域名时,该规则生效。例如'full:v2ray.com'匹配'v2ray.com'但不匹配'www.v2ray.com'。"
|
||||
|
||||
msgid "Sing-Box rule-set: Begining with 'rule-set:remote:' or 'rule-set:local:'"
|
||||
msgid "Sing-Box rule-set: Beginning with 'rule-set:remote:' or 'rule-set:local:'"
|
||||
msgstr "Sing-Box 规则集: 由 'rule-set:remote:' 或 'rule-set:local:' 开始"
|
||||
|
||||
msgid "Such as:"
|
||||
msgstr "例如:"
|
||||
|
||||
msgid "Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
|
||||
msgid "Pre-defined domain list: Beginning with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
|
||||
msgstr "预定义域名列表:由'geosite:'开头,余下部分是一个名称,如geosite:google或者geosite:cn。"
|
||||
|
||||
msgid "Annotation: Begining with #"
|
||||
msgid "Annotation: Beginning with #"
|
||||
msgstr "注释: 由 # 开头"
|
||||
|
||||
msgid "Invert match result."
|
||||
|
||||
@@ -1208,28 +1208,28 @@ msgstr "不是有效IPv6格式,請重新輸入!"
|
||||
msgid "Not true format, please re-enter!"
|
||||
msgstr "不是正確的格式,請重新輸入!"
|
||||
|
||||
msgid "Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'."
|
||||
msgid "Plaintext: If this string matches any part of the targeting domain, this rule takes effect. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'."
|
||||
msgstr "純字符串: 當此字符串匹配目標域名中任意部分,该規則生效。比如'sina.com'可以匹配'sina.com'、'sina.com.cn'和'www.sina.com',但不匹配'sina.cn'。"
|
||||
|
||||
msgid "Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'."
|
||||
msgid "Regular expression: Beginning with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'."
|
||||
msgstr "正則表达式: 由'regexp:'開始,余下部分是一個正則表达式。當此正則表达式匹配目標域名時,该規則生效。例如'regexp:\\.goo.*\\.com$'匹配'www.google.com'、'fonts.googleapis.com',但不匹配'google.com'。"
|
||||
|
||||
msgid "Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'."
|
||||
msgid "Subdomain (recommended): Beginning with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'."
|
||||
msgstr "子域名 (推荐): 由'domain:'開始,余下部分是一個域名。當此域名是目標域名或其子域名時,该規則生效。例如'domain:v2ray.com'匹配'www.v2ray.com'、'v2ray.com',但不匹配'xv2ray.com'。"
|
||||
|
||||
msgid "Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'."
|
||||
msgid "Full domain: Beginning with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'full:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'."
|
||||
msgstr "完整匹配: 由'full:'開始,余下部分是一個域名。當此域名完整匹配目標域名時,该規則生效。例如'full:v2ray.com'匹配'v2ray.com'但不匹配'www.v2ray.com'。"
|
||||
|
||||
msgid "Sing-Box rule-set: Begining with 'rule-set:remote:' or 'rule-set:local:'"
|
||||
msgid "Sing-Box rule-set: Beginning with 'rule-set:remote:' or 'rule-set:local:'"
|
||||
msgstr "Sing-Box 規則集: 由 'rule-set:remote:' 或 'rule-set:local:' 開始"
|
||||
|
||||
msgid "Such as:"
|
||||
msgstr "例如:"
|
||||
|
||||
msgid "Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
|
||||
msgid "Pre-defined domain list: Beginning with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn."
|
||||
msgstr "预定义域名列表:由'geosite:'開头,余下部分是一個名称,如geosite:google或者geosite:cn。"
|
||||
|
||||
msgid "Annotation: Begining with #"
|
||||
msgid "Annotation: Beginning with #"
|
||||
msgstr "注释: 由 # 開头"
|
||||
|
||||
msgid "Invert match result."
|
||||
|
||||
@@ -176,7 +176,7 @@ insert_nftset() {
|
||||
gen_nftset() {
|
||||
local nftset_name="${1}"; shift
|
||||
local ip_type="${1}"; shift
|
||||
# 0 - don't set defalut timeout
|
||||
# 0 - don't set default timeout
|
||||
local timeout_argument_set="${1}"; shift
|
||||
# 0 - don't let element timeout(365 days) when set's timeout parameters be seted
|
||||
# -1 - follow the set's timeout parameters
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-trafficctl
|
||||
PKG_VERSION:=1.25.0
|
||||
PKG_RELEASE:=16
|
||||
PKG_VERSION:=1.25.1
|
||||
PKG_RELEASE:=17
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Denis Iusupov <yusdyr@gmail.com>
|
||||
|
||||
@@ -1,7 +1,26 @@
|
||||
config trafficctl 'main'
|
||||
option enabled '1'
|
||||
option default_mode 'limiter'
|
||||
option persist_rules '0'
|
||||
# Write per-device blocks and rate limits to /etc/trafficctl/rules.json so
|
||||
# the ifup-lan hook can restore them after a reboot.
|
||||
#
|
||||
# On by default since #105. It used to ship off, which made the same
|
||||
# intention behave two ways: a traffic SHAPE persists unconditionally (the
|
||||
# shaper has to record which HTB class belongs to which device, so
|
||||
# shapes.json is always written and always restored), while a block or a
|
||||
# limit vanished on reboot — and the default mode is the limiter, so the
|
||||
# out-of-the-box path was the one that forgot. That was an unexamined
|
||||
# default sitting next to a deliberate one, not a design.
|
||||
#
|
||||
# The cost is one small write to /etc per rule CHANGE. Not periodic, so
|
||||
# unlike the byte counters — which live in tmpfs precisely because they are
|
||||
# rewritten every poll — this is not a flash-wear concern.
|
||||
#
|
||||
# NOT the same switch as the global cut's "keep after reboot": that one is
|
||||
# its own opt-in because an all-devices cut can lock out whoever
|
||||
# administers the router through a LAN host, so a reboot must be able to
|
||||
# undo it. See the cut section below.
|
||||
option persist_rules '1'
|
||||
# Extra subnets to monitor (space-separated CIDRs), for downstream-router
|
||||
# clients that have no kernel route on this router. Subnets with a
|
||||
# "via" route over a LAN interface are picked up automatically.
|
||||
|
||||
@@ -61,12 +61,13 @@ if tctl_block_add "$IP" "$COMMENT"; then
|
||||
else
|
||||
NOTE=" — IPv4 only: no MAC known for $IP (no DHCP lease, no neighbour entry), so IPv6 is not covered"
|
||||
fi
|
||||
NOTE="$NOTE$(tctl_nss_note)"
|
||||
NOTE="$NOTE$(tctl_nss_note)$(tctl_persist_note)"
|
||||
NSS="$(tctl_nss_status)"
|
||||
PERSIST=$(tctl_persist_enabled && echo true || echo false)
|
||||
if [ "$SELF_BLOCK" = "true" ]; then
|
||||
echo "{\"ok\":true,\"ipv6\":$([ "$TCTL_BLOCK_IPV6" = "1" ] && echo true || echo false),\"nss\":\"$NSS\",\"msg\":\"internet blocked for $IP (your device — LuCI access preserved)$NOTE\"}"
|
||||
echo "{\"ok\":true,\"ipv6\":$([ "$TCTL_BLOCK_IPV6" = "1" ] && echo true || echo false),\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"internet blocked for $IP (your device — LuCI access preserved)$NOTE\"}"
|
||||
else
|
||||
echo "{\"ok\":true,\"ipv6\":$([ "$TCTL_BLOCK_IPV6" = "1" ] && echo true || echo false),\"nss\":\"$NSS\",\"msg\":\"internet blocked for $IP$NOTE\"}"
|
||||
echo "{\"ok\":true,\"ipv6\":$([ "$TCTL_BLOCK_IPV6" = "1" ] && echo true || echo false),\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"internet blocked for $IP$NOTE\"}"
|
||||
fi
|
||||
else
|
||||
echo "{\"ok\":false,\"msg\":\"failed to block $IP\"}"
|
||||
|
||||
@@ -1208,6 +1208,18 @@ tctl_nss_note() {
|
||||
"$TCTL_ECM_DEBUGFS"
|
||||
}
|
||||
|
||||
# The suffix a caller appends when the rule it just installed will not be
|
||||
# written down, and so will not come back after a reboot (#105).
|
||||
#
|
||||
# Same principle as the IPv6 and NSS qualifiers: this package says what a
|
||||
# success does NOT cover rather than letting the operator discover it later.
|
||||
# Here "later" meant the next reboot, which is the worst possible moment to
|
||||
# find out that a device you blocked is online again.
|
||||
tctl_persist_note() {
|
||||
tctl_persist_enabled && return 0
|
||||
printf ' [NOT PERSISTENT: this will be lost on reboot — persistence is off. Enable it with `uci set trafficctl.main.persist_rules=1 && uci commit trafficctl`, or in Settings → Logging & Persistence, then re-apply]'
|
||||
}
|
||||
|
||||
# Defunct the flows ECM has already accelerated.
|
||||
#
|
||||
# This is the NSS counterpart of the `conntrack -D` every rule change already
|
||||
|
||||
@@ -104,13 +104,14 @@ else
|
||||
# packets never reach the Linux stack. Reporting an unqualified success
|
||||
# there is the defect in #81, so every message below carries the verdict
|
||||
# and the result carries the status as a field.
|
||||
NSSNOTE="$(tctl_nss_note)"
|
||||
NSSNOTE="$(tctl_nss_note)$(tctl_persist_note)"
|
||||
NSS="$(tctl_nss_status)"
|
||||
PERSIST=$(tctl_persist_enabled && echo true || echo false)
|
||||
if [ "$TCTL_RL_DOWNLOAD_FAILED" = "1" ]; then
|
||||
echo "{\"ok\":true,\"ipv6_upload\":$([ "$TCTL_RL_UPLOAD6_OK" = "1" ] && echo true || echo false),\"nss\":\"$NSS\",\"msg\":\"rate limit ${RATE_UP} kbit/s applied to $IP UPLOAD ONLY — WAN device not resolvable$V6NOTE$NSSNOTE\"}"
|
||||
echo "{\"ok\":true,\"ipv6_upload\":$([ "$TCTL_RL_UPLOAD6_OK" = "1" ] && echo true || echo false),\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"rate limit ${RATE_UP} kbit/s applied to $IP UPLOAD ONLY — WAN device not resolvable$V6NOTE$NSSNOTE\"}"
|
||||
elif [ "$TCTL_RL_UPLOAD_FAILED" = "1" ]; then
|
||||
echo "{\"ok\":true,\"ipv6_upload\":false,\"nss\":\"$NSS\",\"msg\":\"rate limit ${RATE} kbit/s applied to $IP DOWNLOAD ONLY — no LAN ingress device [IPv4 only]$NSSNOTE\"}"
|
||||
echo "{\"ok\":true,\"ipv6_upload\":false,\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"rate limit ${RATE} kbit/s applied to $IP DOWNLOAD ONLY — no LAN ingress device [IPv4 only]$NSSNOTE\"}"
|
||||
else
|
||||
echo "{\"ok\":true,\"ipv6_upload\":$([ "$TCTL_RL_UPLOAD6_OK" = "1" ] && echo true || echo false),\"nss\":\"$NSS\",\"msg\":\"rate limit $(rate_text) for $IP (both directions, $MODE)$V6NOTE$NSSNOTE\"}"
|
||||
echo "{\"ok\":true,\"ipv6_upload\":$([ "$TCTL_RL_UPLOAD6_OK" = "1" ] && echo true || echo false),\"nss\":\"$NSS\",\"persist\":$PERSIST,\"msg\":\"rate limit $(rate_text) for $IP (both directions, $MODE)$V6NOTE$NSSNOTE\"}"
|
||||
fi
|
||||
fi
|
||||
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=LuCI Status Pages
|
||||
LUCI_DEPENDS:=+luci-base +luci-app-oui +luci-mod-dashboard +libiwinfo +rpcd-mod-iwinfo +rpcd-mod-rrdns +ucode +ucode-mod-fs +ucode-mod-ubus +ucode-mod-socket
|
||||
|
||||
PKG_RELEASE:=40
|
||||
PKG_RELEASE:=41
|
||||
PKG_BUILD_DEPENDS:=iwinfo
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
|
||||
@@ -227,11 +227,13 @@ return baseclass.extend({
|
||||
L.resolveDefault(uci.load('dhcp')),
|
||||
L.resolveDefault(callClientWeb(), {}),
|
||||
L.resolveDefault(fs.exec_direct('/usr/libexec/luci-arp'), ''),
|
||||
L.resolveDefault(fs.read('/tmp/luci-client-history.json').then(JSON.parse), {})
|
||||
L.resolveDefault(fs.read('/tmp/luci-client-history.json').then(JSON.parse), {}),
|
||||
L.resolveDefault(rpc.list('hqos').then(methods => methods && methods.hqos && methods.hqos.set ? L.require('hqos') : null), null)
|
||||
]);
|
||||
},
|
||||
|
||||
render([dhcp_leases, host_hints, ufp_list, dhcp_config, web, arp, history]) {
|
||||
render([dhcp_leases, host_hints, ufp_list, dhcp_config, web, arp, history, hqos]) {
|
||||
this.hqos = hqos;
|
||||
if (!this.ratePoll) {
|
||||
this.ratePoll = L.bind(this.refreshRates, this);
|
||||
poll.add(this.ratePoll, 2);
|
||||
@@ -366,6 +368,8 @@ return baseclass.extend({
|
||||
},
|
||||
|
||||
leaseActions(client) {
|
||||
if (this.hqos)
|
||||
return this.hqos.renderAction(client);
|
||||
const actions = [];
|
||||
for (const [family, leases] of [[4, client.leases], [6, client.leases6]]) {
|
||||
for (const lease of leases) {
|
||||
@@ -431,7 +435,7 @@ return baseclass.extend({
|
||||
E('th', { 'class': 'th' }, _('Download')),
|
||||
E('th', { 'class': 'th', 'data-total-traffic': '1' }, _('Total traffic') + ' (↑ / ↓)'),
|
||||
E('th', { 'class': 'th' }, _('Connection count')),
|
||||
isReadonlyView ? E([]) : E('th', { 'class': 'th cbi-section-actions center' }, _('Static Lease'))
|
||||
isReadonlyView ? E([]) : E('th', { 'class': 'th cbi-section-actions center' }, this.hqos ? _('Client rate limit') : _('Static Lease'))
|
||||
])
|
||||
]);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=rpcd-mod-luci
|
||||
PKG_VERSION:=20240305
|
||||
PKG_RELEASE:=3
|
||||
PKG_RELEASE:=4
|
||||
PKG_MAINTAINER:=Jo-Philipp Wich <jo@mein.io>
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <netinet/ether.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <linux/netfilter/nf_conntrack_tcp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/netfilter/nfnetlink.h>
|
||||
@@ -43,6 +45,7 @@ struct host {
|
||||
unsigned char mac[6];
|
||||
uint64_t total[2];
|
||||
uint32_t connections, pending_connections;
|
||||
uint32_t routed_connections, pending_routed_connections;
|
||||
bool available;
|
||||
};
|
||||
struct flow {
|
||||
@@ -63,6 +66,7 @@ struct client {
|
||||
bool warm, ready, missing;
|
||||
};
|
||||
static struct avl_tree clients, flows, hosts;
|
||||
static struct ifaddrs *router_addresses;
|
||||
static struct uloop_fd ct_fd = { .fd = -1 };
|
||||
static struct uloop_timeout timer;
|
||||
static uint32_t sequence, generation;
|
||||
@@ -413,6 +417,30 @@ static void add_bytes(struct client *c, const unsigned char *owner, uint64_t up,
|
||||
}
|
||||
}
|
||||
|
||||
/* The reply source is the destination after DNAT, including hairpin NAT.
|
||||
* Exclude traffic terminating on the router from the HQoS forward-hook count. */
|
||||
static bool router_destination(const struct address *address)
|
||||
{
|
||||
if (address->family == AF_INET) {
|
||||
uint32_t ip;
|
||||
memcpy(&ip, address->bytes, sizeof(ip));
|
||||
if (IN_MULTICAST(ntohl(ip)) || ip == INADDR_BROADCAST)
|
||||
return true;
|
||||
} else if (address->bytes[0] == 0xff) {
|
||||
return true;
|
||||
}
|
||||
for (struct ifaddrs *ifa = router_addresses; ifa; ifa = ifa->ifa_next) {
|
||||
if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != address->family)
|
||||
continue;
|
||||
const void *bytes = address->family == AF_INET ?
|
||||
(void *)&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr :
|
||||
(void *)&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
|
||||
if (!memcmp(address->bytes, bytes, address->family == AF_INET ? 4 : 16))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Count dump records independently of byte accounting and flow tombstones. */
|
||||
static void count_connection(struct nlmsghdr *nlh)
|
||||
{
|
||||
@@ -433,6 +461,12 @@ static void count_connection(struct nlmsghdr *nlh)
|
||||
if (a) a->pending_connections++;
|
||||
/* Hairpin and dual-address connections count once for the same MAC. */
|
||||
if (b && b != a) b->pending_connections++;
|
||||
struct nlattr *info = nested(attr_find(data, len, CTA_PROTOINFO), CTA_PROTOINFO_TCP);
|
||||
uint8_t state = 0;
|
||||
attr_copy(nested(info, CTA_PROTOINFO_TCP_STATE), &state, sizeof(state));
|
||||
if (a && state != TCP_CONNTRACK_TIME_WAIT && state != TCP_CONNTRACK_CLOSE &&
|
||||
!router_destination(&reply_src))
|
||||
a->pending_routed_connections++;
|
||||
}
|
||||
|
||||
static bool process_flow(struct nlmsghdr *nlh)
|
||||
@@ -533,8 +567,10 @@ static void update_rate(struct client *c, uint64_t elapsed)
|
||||
static void finish_dump(void)
|
||||
{
|
||||
struct host *h;
|
||||
avl_for_each_element(&hosts, h, avl)
|
||||
avl_for_each_element(&hosts, h, avl) {
|
||||
h->connections = h->pending_connections;
|
||||
h->routed_connections = h->pending_routed_connections;
|
||||
}
|
||||
uint64_t now = now_ms(), elapsed = now - sampled;
|
||||
struct client *c;
|
||||
struct flow *f, *tmp;
|
||||
@@ -650,6 +686,9 @@ static void sample_timer(struct uloop_timeout *t)
|
||||
return;
|
||||
}
|
||||
discover_clients();
|
||||
freeifaddrs(router_addresses);
|
||||
router_addresses = NULL;
|
||||
getifaddrs(&router_addresses);
|
||||
FILE *acct = fopen("/proc/sys/net/netfilter/nf_conntrack_acct", "r");
|
||||
int enabled = 0;
|
||||
if (acct) { enabled = fgetc(acct) == '1'; fclose(acct); }
|
||||
@@ -677,8 +716,10 @@ static void sample_timer(struct uloop_timeout *t)
|
||||
return;
|
||||
}
|
||||
struct host *h;
|
||||
avl_for_each_element(&hosts, h, avl)
|
||||
avl_for_each_element(&hosts, h, avl) {
|
||||
h->pending_connections = 0;
|
||||
h->pending_routed_connections = 0;
|
||||
}
|
||||
generation++;
|
||||
dumping = true;
|
||||
dump_started = now;
|
||||
@@ -770,6 +811,16 @@ static int get_rates(struct ubus_context *ctx, struct ubus_object *obj,
|
||||
}
|
||||
}
|
||||
blobmsg_close_table(&result, connections);
|
||||
void *routed = blobmsg_open_table(&result, "routed_connections");
|
||||
if (router_addresses && sampled && !failure && now - sampled < 3 * SAMPLE_MS) {
|
||||
avl_for_each_element(&hosts, host, avl) {
|
||||
char mac[18];
|
||||
snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
host->mac[0], host->mac[1], host->mac[2], host->mac[3], host->mac[4], host->mac[5]);
|
||||
blobmsg_add_u32(&result, mac, host->routed_connections);
|
||||
}
|
||||
}
|
||||
blobmsg_close_table(&result, routed);
|
||||
ubus_send_reply(ctx, req, result.head);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -75,3 +75,10 @@ MAC. It includes idle tracked entries and counts records even without byte
|
||||
counters. It is a two-second snapshot, not a TCP ESTABLISHED-only count.
|
||||
Failed or stale snapshots return no counts, rendered as `-`; a valid empty
|
||||
snapshot returns zero. Counts are not restored from historical traffic state.
|
||||
|
||||
The `routed_connections` table exposes original-source connections per MAC for
|
||||
HQoS admission monitoring. It combines IPv4 and IPv6, excludes destinations
|
||||
that terminate on the router after DNAT, multicast, TCP TIME_WAIT and CLOSE.
|
||||
Inbound connections are not charged to the destination client. The existing
|
||||
`connections` field keeps the homepage total count, including router sessions.
|
||||
Tests cover router-local IPv4/IPv6, redirect, hairpin and closed TCP states.
|
||||
@@ -73,7 +73,7 @@ static void feed(int family, const char *src, const char *dst, const char *reply
|
||||
assert(process_flow(msg));
|
||||
}
|
||||
|
||||
static void count_record(int family, const char *src, const char *dst, const char *reply_src)
|
||||
static void count_record_state(int family, const char *src, const char *dst, const char *reply_src, int state)
|
||||
{
|
||||
char buffer[2048] = {0};
|
||||
struct nlmsghdr *msg = (void *)buffer;
|
||||
@@ -82,10 +82,21 @@ static void count_record(int family, const char *src, const char *dst, const cha
|
||||
nf->nfgen_family = family;
|
||||
tuple(msg, CTA_TUPLE_ORIG, src, dst);
|
||||
tuple(msg, CTA_TUPLE_REPLY, reply_src, src);
|
||||
if (state >= 0) {
|
||||
struct nlattr *info = begin(msg, CTA_PROTOINFO), *tcp = begin(msg, CTA_PROTOINFO_TCP);
|
||||
uint8_t value = state;
|
||||
put(msg, CTA_PROTOINFO_TCP_STATE, &value, sizeof(value));
|
||||
end(msg, tcp); end(msg, info);
|
||||
}
|
||||
/* Missing byte counters must not prevent connection counting. */
|
||||
count_connection(msg);
|
||||
}
|
||||
|
||||
static void count_record(int family, const char *src, const char *dst, const char *reply_src)
|
||||
{
|
||||
count_record_state(family, src, dst, reply_src, -1);
|
||||
}
|
||||
|
||||
static struct client *client(const char *ip)
|
||||
{
|
||||
struct client *c = calloc(1, sizeof(*c));
|
||||
@@ -115,6 +126,27 @@ int main(void)
|
||||
assert(get_host(a->mac, false)->pending_connections == 3);
|
||||
assert(get_host(b->mac, false)->pending_connections == 1);
|
||||
assert(get_host(a->mac, false)->connections == 0);
|
||||
assert(get_host(a->mac, false)->pending_routed_connections == 3);
|
||||
assert(get_host(b->mac, false)->pending_routed_connections == 0);
|
||||
struct sockaddr_in local4 = { .sin_family = AF_INET };
|
||||
struct sockaddr_in6 local6 = { .sin6_family = AF_INET6 };
|
||||
inet_pton(AF_INET, "192.168.0.1", &local4.sin_addr);
|
||||
inet_pton(AF_INET6, "fd00::1", &local6.sin6_addr);
|
||||
struct ifaddrs if6 = { .ifa_addr = (void *)&local6 };
|
||||
struct ifaddrs if4 = { .ifa_addr = (void *)&local4, .ifa_next = &if6 };
|
||||
router_addresses = &if4;
|
||||
count_record(AF_INET, "192.168.0.100", "192.168.0.1", "192.168.0.1");
|
||||
count_record(AF_INET6, "fd00::2", "fd00::1", "fd00::1");
|
||||
count_record(AF_INET, "192.168.0.100", "8.8.8.8", "192.168.0.1"); /* Redirect to router. */
|
||||
count_record(AF_INET6, "fd00::2", "ff02::fb", "ff02::fb");
|
||||
count_record_state(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", TCP_CONNTRACK_TIME_WAIT);
|
||||
count_record_state(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", TCP_CONNTRACK_CLOSE);
|
||||
assert(get_host(a->mac, false)->pending_routed_connections == 3);
|
||||
count_record(AF_INET, "192.168.0.100", "203.0.113.1", "192.168.0.101"); /* Hairpin forwards. */
|
||||
count_record_state(AF_INET, "192.168.0.100", "8.8.8.8", "8.8.8.8", TCP_CONNTRACK_ESTABLISHED);
|
||||
assert(get_host(a->mac, false)->pending_routed_connections == 5);
|
||||
router_addresses = NULL;
|
||||
get_host(a->mac, false)->pending_routed_connections = 0;
|
||||
get_host(a->mac, false)->pending_connections = 0;
|
||||
get_host(b->mac, false)->pending_connections = 0;
|
||||
memcpy(v6->mac, v6mac, 6);
|
||||
|
||||
+11
-11
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=vless-core-cli
|
||||
PKG_VERSION:=1.1.4
|
||||
PKG_RELEASE:=1
|
||||
PKG_VERSION:=2.0.0
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/notfence/vless-core-cli/tar.gz/v$(PKG_VERSION)?
|
||||
@@ -11,7 +11,7 @@ PKG_HASH:=skip
|
||||
PKG_MAINTAINER:=Konstantine Shevlakov <kosnt.shell@gmail.com>
|
||||
PKG_LICENSE_FILES:=THIRD_PARTY_LICENSES.txt
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
PKG_BUILD_FLAGS:=gc-sections
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
@@ -19,14 +19,15 @@ define Package/vless-core-cli
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=Web Servers/Proxies
|
||||
TITLE:=Standalone C VLESS client with local SOCKS5
|
||||
TITLE:=Standalone C proxy client (VLESS/Trojan/VMess/SS) with local SOCKS5
|
||||
URL:=https://github.com/notfence/vless-core-cli
|
||||
DEPENDS:=+libopenssl
|
||||
endef
|
||||
|
||||
define Package/vless-core-cli/description
|
||||
Standalone C client for VLESS (TCP/XHTTP/gRPC/WS, Reality/TLS) and SOCKS5
|
||||
upstreams. Listens on a local SOCKS5 port on 127.0.0.1.
|
||||
Standalone C client for VLESS, Trojan, VMess, Shadowsocks and SOCKS5
|
||||
upstreams (TCP/WS/gRPC/XHTTP/HTTPUpgrade, TLS/Reality).
|
||||
Listens on a local SOCKS5 port on 127.0.0.1.
|
||||
endef
|
||||
|
||||
define Package/vless-core-cli/conffiles
|
||||
@@ -34,11 +35,10 @@ define Package/vless-core-cli/conffiles
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
$(MAKE) -C $(PKG_BUILD_DIR) linux \
|
||||
CC="$(TARGET_CC)" \
|
||||
COMMON_CFLAGS="$(TARGET_CFLAGS) $(TARGET_CPPFLAGS) -std=c11 -Wall" \
|
||||
LDFLAGS="$(TARGET_LDFLAGS)" \
|
||||
BIN_LINUX=vless-core
|
||||
$(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_CPPFLAGS) -std=c11 -Wall \
|
||||
-I$(PKG_BUILD_DIR)/include $(PKG_BUILD_DIR)/src/*.c \
|
||||
-o $(PKG_BUILD_DIR)/vless-core \
|
||||
$(TARGET_LDFLAGS) -lssl -lcrypto -lpthread
|
||||
endef
|
||||
|
||||
define Package/vless-core-cli/install
|
||||
|
||||
+54
-10
@@ -1,13 +1,18 @@
|
||||
# vless-core-cli
|
||||
|
||||
OpenWrt package for [notfence/vless-core-cli](https://github.com/notfence/vless-core-cli) —
|
||||
a standalone C client for VLESS that exposes a local SOCKS5 proxy.
|
||||
a standalone C client that connects to a VLESS, Trojan, VMess, Shadowsocks or SOCKS5 server
|
||||
and exposes a local SOCKS5 proxy.
|
||||
|
||||
Compared to Go-based cores it is tiny (the ipk is about ~70 KB) and depends only on `libopenssl`,
|
||||
Compared to Go-based cores it is very small and depends only on `libopenssl`,
|
||||
which makes it suitable for devices with small flash and RAM.
|
||||
|
||||
Supported upstreams: VLESS over TCP / XHTTP / gRPC / WebSocket with Reality, TLS or no security
|
||||
(`xtls-rprx-vision` on TCP), and plain SOCKS5.
|
||||
Supported upstreams:
|
||||
|
||||
- VLESS (with `xtls-rprx-vision` and VLESS Encryption), Trojan, VMess AEAD (`alterId=0`),
|
||||
Shadowsocks (AEAD and 2022) over TCP / WebSocket / gRPC / XHTTP / HTTPUpgrade with
|
||||
no security, TLS or Reality (Reality is not available for WebSocket and HTTPUpgrade).
|
||||
- Plain SOCKS5, optionally with username/password.
|
||||
|
||||
## Files
|
||||
|
||||
@@ -44,13 +49,15 @@ Section `config vless-core 'main'`:
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `enabled` | `0` | Start the service (`1`/`0`) |
|
||||
| `uri` | – | `vless://...` or `socks5://host:port` upstream URI (required) |
|
||||
| `uri` | – | `vless://`, `vmess://`, `trojan://`, `ss://` or `socks5://` upstream URI (required) |
|
||||
| `listen_port` | `1080` | Local SOCKS5 port on `127.0.0.1` |
|
||||
| `routing` | – | Optional routing rules, see below |
|
||||
| `tls_mode` | `strict` | Certificate verification: `strict`, `insecure` or `tofu` |
|
||||
|
||||
Always quote the URI in `uci set`: it contains `&`, `#` and `?`.
|
||||
|
||||
The client serves exactly one upstream URI, so the init script runs a single instance.
|
||||
|
||||
## URI examples
|
||||
|
||||
Reality + Vision:
|
||||
@@ -77,13 +84,38 @@ gRPC + Reality:
|
||||
vless://UUID@example.com:443?type=grpc&security=reality&serviceName=svc&sni=www.example.org&fp=chrome&pbk=PUBLIC_KEY&sid=SHORT_ID#grpc
|
||||
```
|
||||
|
||||
HTTPUpgrade + TLS:
|
||||
|
||||
```
|
||||
vless://UUID@example.com:443?type=httpupgrade&security=tls&sni=example.com&host=example.com&path=%2Fup#httpupgrade
|
||||
```
|
||||
|
||||
Trojan (TLS by default, the whole userinfo is the password):
|
||||
|
||||
```
|
||||
trojan://PASSWORD@example.com:443?sni=example.com#trojan
|
||||
```
|
||||
|
||||
VMess (`encryption` is the body mode, `scy` is an alias; the base64 JSON form is accepted too):
|
||||
|
||||
```
|
||||
vmess://UUID@example.com:443?type=ws&security=tls&sni=example.com&host=example.com&path=%2Fws&encryption=auto#vmess
|
||||
```
|
||||
|
||||
Shadowsocks (SIP002, userinfo is base64url of `method:password`):
|
||||
|
||||
```
|
||||
ss://BASE64URL_METHOD_PASSWORD@example.com:8388#ss
|
||||
```
|
||||
|
||||
Upstream SOCKS5:
|
||||
|
||||
```
|
||||
socks5://192.0.2.1:1080
|
||||
```
|
||||
|
||||
Supported `fp` values: `chrome`, `firefox`, `edge`, `random`, `randomized`, `qq`.
|
||||
Supported `fp` values: `chrome`, `firefox`, `edge`, `random`, `randomized`, `qq`
|
||||
(there are no `safari` or `ios` fingerprints).
|
||||
|
||||
## Routing
|
||||
|
||||
@@ -130,12 +162,24 @@ Notes:
|
||||
- `insecure` — never verify certificates.
|
||||
- `tofu` — pin the certificate on first connection, later connections must match.
|
||||
|
||||
## Reality client version
|
||||
|
||||
For Reality the client writes an Xray version (`x.y.z`, default `26.3.27`) into the ClientHello
|
||||
session ID. Override it with the `XRAY_VER` environment variable or `--xray-version x.y.z`,
|
||||
for example if the server limits client versions with `minClientVer` / `maxClientVer`.
|
||||
This is not exposed in UCI.
|
||||
|
||||
## Build notes
|
||||
|
||||
Upstream hardcodes the output name `vless-core-linux-amd64` regardless of the target
|
||||
architecture, so the package passes `BIN_LINUX=vless-core` to upstream `make` and builds
|
||||
with the SDK `CC`/`CFLAGS`/`LDFLAGS`.
|
||||
Upstream's Makefile runs its test suite (clang sanitizers, a separate OpenSSL 3.5, Xray) and
|
||||
an x86-64-only binary check, and hardcodes the output name `vless-core-linux-amd64`.
|
||||
The package therefore does not use it: it compiles `src/*.c` directly with the SDK toolchain
|
||||
and installs the result as `/usr/bin/vless-core`.
|
||||
|
||||
ML-KEM based features (VLESS Encryption, the X25519MLKEM768 key share) need OpenSSL 3.5 or newer.
|
||||
|
||||
Tested: OpenWrt SDK, musl x86-64, OpenSSL 3.5.9.
|
||||
|
||||
## ToDo
|
||||
|
||||
- `luci-app-vless-core` — LuCI web interface.
|
||||
- `luci-app-vless-core` — LuCI web interface (someday).
|
||||
Reference in new issue
Block a user