feat: add new smartdns

This commit is contained in:
tqcq
2025-11-22 18:52:16 +08:00
parent f7a880ae66
commit f2c69ec803
17 changed files with 3499 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
#
# Copyright (C) 2018-2024 Ruilin Peng (Nick) <pymumu@gmail.com>.
#
# smartdns is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# smartdns is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
include $(TOPDIR)/rules.mk
PKG_LICENSE:=GPL-3.0-or-later
PKG_MAINTAINER:=Nick Peng <pymumu@gmail.com>
PKG_VERSION:=1.2024.45
PKG_RELEASE:=1
LUCI_TITLE:=LuCI for smartdns
LUCI_DESCRIPTION:=Provides Luci for smartdns
LUCI_DEPENDS:=+smartdns +luci-compat
LUCI_PKGARCH:=all
define Package/$(PKG_NAME)/config
# shown in make menuconfig <Help>
help
$(LUCI_TITLE)
Version: $(PKG_VERSION)-$(PKG_RELEASE)
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@@ -0,0 +1,57 @@
--
-- Copyright (C) 2018-2024 Ruilin Peng (Nick) <pymumu@gmail.com>.
--
-- smartdns is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- smartdns is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
module("luci.controller.smartdns", package.seeall)
local smartdns = require "luci.model.smartdns"
function index()
if not nixio.fs.access("/etc/config/smartdns") then
return
end
local page
page = entry({"admin", "services", "smartdns"}, cbi("smartdns/smartdns"), _("SmartDNS"), 60)
page.dependent = true
page = entry({"admin", "services", "smartdns", "status"}, call("act_status"))
page.leaf = true
page = entry({"admin", "services", "smartdns", "upstream"}, cbi("smartdns/upstream"), nil)
page.leaf = true
end
local function is_running()
return luci.sys.call("pidof smartdns >/dev/null") == 0
end
function act_status()
local e={}
local ipv6_server;
local dnsmasq_server = smartdns.get_config_option("dhcp", "dnsmasq", "server", {nil})[1]
local auto_set_dnsmasq = smartdns.get_config_option("smartdns", "smartdns", "auto_set_dnsmasq", nil);
e.auto_set_dnsmasq = auto_set_dnsmasq
e.dnsmasq_server = dnsmasq_server
e.local_port = smartdns.get_config_option("smartdns", "smartdns", "port", nil);
if e.local_port ~= nil and e.local_port ~= "53" and auto_set_dnsmasq ~= nil and auto_set_dnsmasq == "1" then
local str;
str = "127.0.0.1#" .. e.local_port
if dnsmasq_server ~= str then
e.dnsmasq_redirect_failure = 1
end
end
e.running = is_running()
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,158 @@
--
-- Copyright (C) 2018-2024 Ruilin Peng (Nick) <pymumu@gmail.com>.
--
-- smartdns is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- smartdns is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
local sid = arg[1]
m = Map("smartdns", "%s - %s" %{translate("SmartDNS Server"), translate("Upstream DNS Server Configuration")})
m.redirect = luci.dispatcher.build_url("admin/services/smartdns")
if m.uci:get("smartdns", sid) ~= "server" then
luci.http.redirect(m.redirect)
return
end
-- [[ Edit Server ]]--
s = m:section(NamedSection, sid, "server")
s.anonymous = true
s.addremove = false
---- name
s:option(Value, "name", translate("DNS Server Name"), translate("DNS Server Name"))
---- IP address
o = s:option(Value, "ip", translate("ip"), translate("DNS Server ip"))
o.datatype = "or(host, string)"
o.rmempty = false
---- port
o = s:option(Value, "port", translate("port"), translate("DNS Server port"))
o.placeholder = "default"
o.datatype = "port"
o.rempty = true
o:depends("type", "udp")
o:depends("type", "tcp")
o:depends("type", "tls")
---- type
o = s:option(ListValue, "type", translate("type"), translate("DNS Server type"))
o.placeholder = "udp"
o:value("udp", translate("udp"))
o:value("tcp", translate("tcp"))
o:value("tls", translate("tls"))
o:value("https", translate("https"))
o.default = "udp"
o.rempty = false
---- server group
o = s:option(Value, "server_group", translate("Server Group"), translate("DNS Server group belongs to, such as office, home."))
o.rmempty = true
o.placeholder = "default"
o.datatype = "hostname"
o.rempty = true
---- exclude default group
o = s:option(Flag, "exclude_default_group", translate("Exclude Default Group"), translate("Exclude DNS Server from default group."))
o.rmempty = true
o.default = o.disabled
o.editable = true
o.modalonly = true
---- blacklist_ip
o = s:option(Flag, "blacklist_ip", translate("IP Blacklist Filtering"), translate("Filtering IP with blacklist"))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- TLS host verify
o = s:option(Value, "tls_host_verify", translate("TLS Hostname Verify"), translate("Set TLS hostname to verify."))
o.default = ""
o.datatype = "string"
o.rempty = true
o:depends("type", "tls")
o:depends("type", "https")
---- certificate verify
o = s:option(Flag, "no_check_certificate", translate("No check certificate"), translate("Do not check certificate."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
o:depends("type", "tls")
o:depends("type", "https")
---- SNI host name
o = s:option(Value, "host_name", translate("TLS SNI name"), translate("Sets the server name indication for query."))
o.default = ""
o.datatype = "hostname"
o.rempty = true
o:depends("type", "tls")
o:depends("type", "https")
---- http host
o = s:option(Value, "http_host", translate("HTTP Host"), translate("Set the HTTP host used for the query. Use this parameter when the host of the URL address is an IP address."))
o.default = ""
o.datatype = "hostname"
o.rempty = true
o:depends("type", "https")
---- anti-Answer-Forgery
-- o = s:option(Flag, "check_edns", translate("Anti Answer Forgery"), translate("Anti answer forgery, if DNS does not work properly after enabling, please turn off this feature"))
-- o.rmempty = false
-- o.default = o.disabled
-- o:depends("type", "udp")
-- o.cfgvalue = function(...)
-- return Flag.cfgvalue(...) or "0"
-- end
---- SPKI pin
o = s:option(Value, "spki_pin", translate("TLS SPKI Pinning"), translate("Used to verify the validity of the TLS server, The value is Base64 encoded SPKI fingerprint, leaving blank to indicate that the validity of TLS is not verified."))
o.default = ""
o.datatype = "string"
o.rempty = true
o:depends("type", "tls")
o:depends("type", "https")
---- mark
o = s:option(Value, "set_mark", translate("Marking Packets"), translate("Set mark on packets."))
o.default = ""
o.rempty = true
o.datatype = "uinteger"
---- use proxy
o = s:option(Flag, "use_proxy", translate("Use Proxy"), translate("Use proxy to connect to upstream DNS server."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
function o.validate(self, value, section)
if value == "1" then
local proxy = m.uci:get_first("smartdns", "smartdns", "proxy_server")
if proxy == nil or proxy == "" then
return nil, translate("Please set proxy server first.")
end
end
return value
end
---- other args
o = s:option(Value, "addition_arg", translate("Additional Server Args"), translate("Additional Args for upstream dns servers"))
o.default = ""
o.rempty = true
return m

View File

@@ -0,0 +1,31 @@
--
-- Copyright (C) 2018-2024 Ruilin Peng (Nick) <pymumu@gmail.com>.
--
-- smartdns is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- smartdns is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
require ("nixio.fs")
require ("luci.http")
require ("luci.dispatcher")
require ("nixio.fs")
local uci = require "luci.model.uci".cursor()
module("luci.model.smartdns", package.seeall)
function get_config_option(module, section, option, default)
return uci:get_first(module, section, option) or default
end
return m

View File

@@ -0,0 +1,28 @@
<script type="text/javascript">//<![CDATA[
XHR.poll(3, '<%=luci.dispatcher.build_url("admin", "services", "smartdns", "status")%>', null,
function(x, data) {
var tb = document.getElementById('smartdns_status');
if (data && tb) {
var links = "";
if (data.running) {
links = '<b><font color=green>SmartDNS - <%:RUNNING%></font></b></em>';
if (data.dnsmasq_redirect_failure == 1) {
links += "<br></br><b><font color=red><%:Dnsmasq Forwarded To Smartdns Failure%></font></b>"
}
} else {
links = '<b><font color=red>SmartDNS - <%:NOT RUNNING%></font></b>';
}
tb.innerHTML = links;
}
}
);
//]]>
</script>
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
<fieldset class="cbi-section">
<p id="smartdns_status">
<em><%:Collecting data...%></em>
</p>
</fieldset>

View File

@@ -0,0 +1,854 @@
msgid "Additional Args for upstream dns servers"
msgstr "额外的上游 DNS 服务器参数"
msgid ""
"Additional Flags for rules, read help on domain-rule for more information."
msgstr "额外的规则标识具体参考domain-rule的帮助说明。"
msgid ""
"Additional Flags for rules, read help on ip-rule for more information."
msgstr "额外的规则标识具体参考ip-rule的帮助说明。"
msgid "Additional Rule Flag"
msgstr "额外规则标识"
msgid "Additional Server Args"
msgstr "额外的服务器参数"
msgid "Additional server args, refer to the help description of the bind option."
msgstr "额外的服务器参数参考bind选项的帮助说明。"
msgid "Advanced Settings"
msgstr "高级设置"
msgid "Audit Log Output Mode"
msgstr "审计日志输出模式"
msgid "Audit Log Size"
msgstr "审计日志大小"
msgid "Audit Log Number"
msgstr "审计日志数量"
msgid "Audit Log File"
msgstr "审计日志文件路径"
msgid ""
"Attempts to serve old responses from cache with a TTL of 0 in the response "
"without waiting for the actual resolution to finish."
msgstr "查询性能优化有请求时尝试回应TTL为0的过期记录以避免查询等待。"
msgid "Automatically Set Dnsmasq"
msgstr "自动设置Dnsmasq"
msgid "Automatically set as upstream of dnsmasq when port changes."
msgstr "端口更改时自动设为 dnsmasq 的上游。"
msgid "Basic Settings"
msgstr "基本设置"
msgid "Bind Device"
msgstr "绑定到设备"
msgid "Bind Device Name"
msgstr "绑定的设备名称"
msgid "Bogus nxdomain"
msgstr "假冒IP"
msgid "Blacklist IP"
msgstr "黑名单"
msgid "Blacklist IP Rule, Decline IP addresses within the range."
msgstr "黑名单规则拒绝指定范围的IP地址。"
msgid "Block domain"
msgstr "屏蔽域名"
msgid "Block domain."
msgstr "屏蔽域名。"
msgid "Cache Persist"
msgstr "持久化缓存"
msgid "Cache Size"
msgstr "缓存大小"
msgid "Client Rules"
msgstr "客户端规则"
msgid "Client Address"
msgstr "客户端地址"
msgid "Client Address File"
msgstr "客户端地址文件"
msgid "Client Rules Settings, can achieve parental control functionality."
msgstr "客户端规则设置,可以实现家长控制功能。"
msgid "Collecting data ..."
msgstr "正在收集数据..."
msgid ""
"Configure IP blacklists that will be filtered from the results of specific "
"DNS server."
msgstr "配置需要从指定域名服务器结果过滤的IP黑名单。"
msgid "Configure block domain list."
msgstr "配置屏蔽域名列表"
msgid "Configure domain rule list."
msgstr "配置域名规则列表"
msgid "Configure forwarding domain name list."
msgstr "配置分流域名列表"
msgid "Custom Settings"
msgstr "自定义设置"
msgid "Do not use these IP addresses."
msgstr "忽略这些IP地址"
msgid "DOH Server"
msgstr "DOH服务器"
msgid "DOH Server Port"
msgstr "DOH服务器端口"
msgid "DOT Server"
msgstr "DOT服务器"
msgid "DOT Server Port"
msgstr "DOT服务器端口"
msgid "DNS Block Setting"
msgstr "域名屏蔽设置"
msgid "DNS Forwarding Setting"
msgstr "域名分流设置"
msgid "DNS Server Name"
msgstr "DNS服务器名称"
msgid "DNS Server group"
msgstr "服务器组"
msgid "DNS Server group belongs to, such as office, home."
msgstr "设置服务器组例如officehome"
msgid "DNS Server ip"
msgstr "DNS服务器IP"
msgid "DNS Server port"
msgstr "DNS服务器端口"
msgid "DNS Server type"
msgstr "协议类型"
msgid "DNS domain result cache size"
msgstr "缓存DNS的结果缓存大小配置零则不缓存。"
msgid "DNS64"
msgstr "DNS64"
msgid "DNS64 Server Settings"
msgstr "DNS64服务器配置"
msgid "default"
msgstr "默认"
msgid "Description"
msgstr "描述"
msgid "Dnsmasq Forwarded To Smartdns Failure"
msgstr "重定向dnsmasq到smartdns失败"
msgid "Do not check certificate."
msgstr "不校验证书的合法性。"
msgid "Do not check speed."
msgstr "禁用测速。"
msgid "Domain Address"
msgstr "域名地址"
msgid "Domain List"
msgstr "域名列表"
msgid "Domain List File"
msgstr "域名列表文件"
msgid "Domain Rule List"
msgstr "域名规则列表"
msgid "Domain Rule Name"
msgstr "域名规则名称"
msgid "Domain Rules"
msgstr "域名规则"
msgid "Domain Rules Settings"
msgstr "域名规则设置"
msgid "Domain TTL"
msgstr "域名TTL"
msgid "Domain TTL Max"
msgstr "域名TTL最大值"
msgid "Domain TTL Min"
msgstr "域名TTL最小值"
msgid "Domain prefetch"
msgstr "域名预加载"
msgid "Donate"
msgstr "捐助"
msgid "Donate to smartdns"
msgstr "捐助smartdns项目"
msgid "Download Files"
msgstr "下载文件"
msgid "Download Files Setting"
msgstr "下载文件设置"
msgid ""
"Download domain list files for domain-rule and include config files, please "
"refresh the page after download to take effect."
msgstr ""
"下载域名规则所需要的域名列表文件和smartdns配置文件下载完成后刷新页面。"
msgid "Dual-stack IP Selection"
msgstr "双栈IP优选"
msgid "Enable"
msgstr "启用"
msgid "Enable Auto Update"
msgstr "启用自动更新"
msgid "Enable IP selection between IPV4 and IPV6"
msgstr "启用 IPV4 和 IPV6 间的 IP 优选策略。"
msgid "Enable IPV6 DNS Server"
msgstr "启用IPV6服务器。"
msgid "Enable TCP DNS Server"
msgstr "启用TCP服务器。"
msgid "Enable daily(week) auto update."
msgstr "启用每天(每周)自动更新。"
msgid "Enable DOH DNS Server"
msgstr "启用DOH服务器。"
msgid "Enable DOT DNS Server"
msgstr "启用DOT服务器。"
msgid "Update Time (Every Week)"
msgstr "更新时间(每周)"
msgid "Every Day"
msgstr "每天"
msgid "Every Monday"
msgstr "每周一"
msgid "Every Tuesday"
msgstr "每周二"
msgid "Every Wednesday"
msgstr "每周三"
msgid "Every Thursday"
msgstr "每周四"
msgid "Every Friday"
msgstr "每周五"
msgid "Every Saturday"
msgstr "每周六"
msgid "Every Sunday"
msgstr "每周日"
msgid "Update Time (Every Day)"
msgstr "更新时间(每天)"
msgid "Enable Audit Log"
msgstr "启用审计日志"
msgid "Enable domain prefetch, accelerate domain response speed."
msgstr "启用域名预加载,加速域名响应速度。"
msgid "Enable or disable second DNS server."
msgstr "是否启用第二DNS服务器。"
msgid "Enable or disable smartdns server"
msgstr "启用或禁用SmartDNS服务"
msgid "Exclude DNS Server from default group."
msgstr "从default默认服务器组中排除。"
msgid "Exclude Default Group"
msgstr "从默认组中排除"
msgid "file"
msgstr "文件"
msgid "Fastest IP"
msgstr "最快IP"
msgid "Fastest Response"
msgstr "最快响应"
msgid "File Name"
msgstr "文件名"
msgid "File Type"
msgstr "文件类型"
msgid "Filtering IP with blacklist"
msgstr "使用IP黑名单过滤"
msgid "First Ping"
msgstr "最快PING"
msgid "Force AAAA SOA"
msgstr "停用IPV6地址解析"
msgid "Force AAAA SOA."
msgstr "停用IPV6地址解析。"
msgid "Force HTTPS SOA"
msgstr "停用HTTPS记录解析"
msgid "Force HTTPS SOA."
msgstr "停用HTTPS记录解析。"
msgid "General Settings"
msgstr "常规设置"
msgid "Generate Coredump"
msgstr "生成coredump"
msgid ""
"Generate Coredump file when smartdns crash, coredump file is located at /tmp/"
"smartdns.xxx.core."
msgstr ""
"当smartdns异常时生成coredump文件coredump文件在/tmp/smartdns.xxx.core."
msgid "Grant access to LuCI app smartdns"
msgstr "授予访问 LuCI 应用 smartdns 的权限"
msgid "Hosts File"
msgstr "Hosts文件"
msgid "HTTP Host"
msgstr "HTTP主机"
msgid "IP alias"
msgstr "IP别名"
msgid "IP Alias Setting"
msgstr "IP别名设置"
msgid "IP Blacklist"
msgstr "IP黑名单"
msgid "IP Blacklist Filtering"
msgstr "IP黑名单过滤"
msgid "IP Addresses"
msgstr "IP地址"
msgid "IP Address Mapping, Can be used for CDN acceleration with Anycast IP, such as Cloudflare's CDN."
msgstr "IP地址映射可用于支持AnyCast IP的CDN加速比如Cloudflare的CDN。"
msgid "Ignore IP"
msgstr "忽略IP"
msgid "IP Rules"
msgstr "IP规则"
msgid "IP Rules Settings"
msgstr "IP规则设置"
msgid "IP Rule Name"
msgstr "IP规则名称"
msgid "IP Set File"
msgstr "IP集合列表文件"
msgid "IP addresses, CIDR format."
msgstr "IP地址CIDR格式。"
msgid "IPV6 Server"
msgstr "IPV6服务器"
msgid "IPset Name"
msgstr "IPset名称"
msgid "IPset name."
msgstr "IPset名称。"
msgid ""
"If a client address is specified, only that client will apply this "
"rule. You can enter an IP address, such as 1.2.3.4, or a MAC address, "
"such as aa:bb:cc:dd:ee:ff."
msgstr ""
"如果指定了客户端那么对应的客户端会应用相应的规则可以输入IP地址1.2.3.4或MAC地址aa:bb:cc:dd:ee:ff。"
msgid "If you like this software, please buy me a cup of coffee."
msgstr "如果本软件对你有帮助,请给作者加个蛋。"
msgid "Include Config Files<br>/etc/smartdns/conf.d"
msgstr "包含配置文件<br>/etc/smartdns/conf.d"
msgid "Include hosts file."
msgstr "包含hosts文件。"
msgid ""
"Include other config files from /etc/smartdns/conf.d or custom path, can be "
"downloaded from the download page."
msgstr ""
"包含配置文件,路径为/etc/smartdns/conf.d或自定义配置文件路径可以从下载页"
"配置自动下载。面配置自动下载。"
msgid "Ipset name, Add domain result to ipset when speed check fails."
msgstr "IPset名称当测速失败时将查询到的结果添加到对应的IPSet集合中。"
msgid "List of files to download."
msgstr "下载文件列表。"
msgid "Listen only on the specified interfaces."
msgstr "监听在指定的设备上避免非本地网络的DNS查询请求。"
msgid "Local Port"
msgstr "本地端口"
msgid "Log Output Mode"
msgstr "日志输出模式"
msgid "Log Size"
msgstr "日志大小"
msgid "Log Level"
msgstr "日志级别"
msgid "Log Number"
msgstr "日志数量"
msgid "Log File"
msgstr "日志文件路径"
msgid "mDNS Lookup"
msgstr "mDNS查询"
msgid "Marking Packets"
msgstr "数据包标记"
msgid "Maximum TTL for all domain result."
msgstr "所有域名的最大 TTL 值。"
msgid "Minimum TTL for all domain result."
msgstr "所有域名的最小 TTL 值。"
msgid "NFTset Name"
msgstr "NFTSet名称"
msgid "NFTset name format error, format: [#[4|6]:[family#table#set]]"
msgstr "NFTSet名称格式错误格式[#[4|6]:[family#table#set]]"
msgid "NFTset name, format: [#[4|6]:[family#table#set]]"
msgstr "NFTSet名称格式[#[4|6]:[family#table#set]]"
msgid "NOT RUNNING"
msgstr "未运行"
msgid "Name of device name listen on."
msgstr "绑定的设备名称。"
msgid ""
"Nftset name, Add domain result to nftset when speed check fails, format: "
"[#[4|6]:[family#table#set]]"
msgstr "NFTset名称当测速失败时将查询到的结果添加到对应的NFTSet集合中。"
msgid "No"
msgstr "否"
msgid "No Speed IPset Name"
msgstr "无速度时IPSet名称"
msgid "No Speed NFTset Name"
msgstr "无速度时NFTSet名称"
msgid "No check certificate"
msgstr "停用证书校验"
msgid "None"
msgstr "无"
msgid "Only socks5 proxy support udp server."
msgstr "仅SOCKS5代理支持UDP服务器。"
msgid "Please set proxy server first."
msgstr "请先设置代理服务器。"
msgid "Proxy Server"
msgstr "代理服务器"
msgid "Proxy Server Settings"
msgstr "代理服务器设置"
msgid "Proxy Server URL, format: [socks5|http]://user:pass@ip:port."
msgstr "代理服务器地址,格式:[socks5|http]://user:pass@ip:port。"
msgid ""
"Proxy server URL format error, format: [socks5|http]://user:pass@ip:port."
msgstr "代理服务器地址格式错误,格式:[socks5|http]://user:pass@ip:port。"
msgid "Query DNS through specific dns server group, such as office, home."
msgstr "使用指定服务器组查询比如office, home。"
msgid "RUNNING"
msgstr "运行中"
msgid "Reply Domain TTL Max"
msgstr "回应的域名TTL最大值"
msgid "Reply maximum TTL for all domain result."
msgstr "设置返回给客户端的域名TTL最大值。"
msgid "Report bugs"
msgstr "报告BUG"
msgid "Return SOA when the requested result contains a specified IP address."
msgstr "当结果包含对应范围的IP时返回SOA。"
msgid "Resolve Local Hostnames"
msgstr "解析本地主机名"
msgid "Resolve local hostnames by reading Dnsmasq lease file."
msgstr "读取Dnsmasq的租约文件解析本地主机名。"
msgid "Resolve local network hostname via mDNS protocol."
msgstr "使用mDNS协议解析本地网络主机名。"
msgid "Response Mode"
msgstr "响应模式"
msgid "Restart"
msgstr "重启"
msgid "Restart Service"
msgstr "重启服务"
msgid "syslog"
msgstr "系统日志"
msgid "Second Server Settings"
msgstr "第二DNS服务器"
msgid "Server certificate file path."
msgstr "服务器证书文件路径。"
msgid "Server certificate key file path."
msgstr "服务器证书私钥文件路径。"
msgid "Server certificate key file password."
msgstr "服务器证书私钥文件密码。"
msgid "Serve expired"
msgstr "缓存过期服务"
msgid "Server Group"
msgstr "服务器组"
msgid "Server Group %s not exists"
msgstr "服务器组%s不存在"
msgid "Server Name"
msgstr "服务器名称"
msgid "Server Cert"
msgstr "服务器证书"
msgid "Server Cert Key"
msgstr "服务器证书私钥"
msgid "Server Cert Key Pass"
msgstr "服务器证书私钥密码"
msgid "Set Specific domain ip address."
msgstr "设置指定域名的IP地址。"
msgid "Set Specific domain rule list."
msgstr "设置指定域名的规则列表。"
msgid "Set Specific ip blacklist."
msgstr "设置指定的 IP 黑名单列表。"
msgid "Set TLS hostname to verify."
msgstr "设置校验TLS主机名。"
msgid "Set mark on packets."
msgstr "设置数据包标记。"
msgid ""
"Set the HTTP host used for the query. Use this parameter when the host of "
"the URL address is an IP address."
msgstr "设置查询时使用的HTTP主机当URL地址的host是IP地址时使用此参数。"
msgid "Sets the server name indication for query. '-' for disable SNI name."
msgstr "设置服务器SNI名称-表示禁用SNI名称。"
msgid "Settings"
msgstr "设置"
msgid "Skip Address Rules"
msgstr "跳过address规则"
msgid "Skip Cache"
msgstr "跳过cache"
msgid "Skip Cache."
msgstr "跳过cache。"
msgid "Skip Dualstack Selection"
msgstr "跳过双栈优选"
msgid "Skip Dualstack Selection."
msgstr "跳过双栈优选。"
msgid "Skip IP Alias"
msgstr "跳过IP别名"
msgid "Skip Ipset Rule"
msgstr "跳过ipset规则"
msgid "Skip Nameserver Rule"
msgstr "跳过Nameserver规则"
msgid "Skip SOA Address Rule"
msgstr "跳过address SOA(#)规则"
msgid "Skip SOA address rules."
msgstr "跳过address SOA(#)规则。"
msgid "Skip Speed Check"
msgstr "跳过测速"
msgid "Skip address rules."
msgstr "跳过address规则。"
msgid "Skip ipset rules."
msgstr "跳过ipset规则。"
msgid "Skip nameserver rules."
msgstr "跳过Nameserver规则。"
msgid "SmartDNS"
msgstr "SmartDNS"
msgid "Smartdns DOH server port."
msgstr "Smartdns DOH服务器端口号。
msgid "Smartdns DOT server port."
msgstr "Smartdns DOT服务器端口号。"
msgid "SmartDNS Server"
msgstr "SmartDNS 服务器"
msgid ""
"SmartDNS is a local high-performance DNS server, supports finding fastest "
"IP, supports ad filtering, and supports avoiding DNS poisoning."
msgstr "SmartDNS是一个本地高性能DNS服务器支持返回最快IP支持广告过滤。"
msgid "SmartDNS official website"
msgstr "SmartDNS官方网站"
msgid "Smartdns local server port"
msgstr "SmartDNS本地服务端口"
msgid ""
"Smartdns local server port, smartdns will be automatically set as main dns "
"when the port is 53."
msgstr ""
"SmartDNS本地服务端口当端口号设置为53时smartdns将会自动配置为主dns。"
msgid ""
"Smartdns response mode, First Ping: return the first ping IP, Fastest IP: "
"return the fastest IP, Fastest Response: return the fastest DNS response."
msgstr ""
"SmartDNS响应模式最快PING 返回最早有ping结果的IP速度适中最快IP 返回"
"最快IP查询请求可能延长 最快响应:返回最快响应的结果,查询请求时间短。"
msgid "Smartdns server name"
msgstr "SmartDNS的服务器名称默认为smartdns留空为主机名"
msgid "Smartdns speed check mode."
msgstr "SmartDNS测速模式。"
msgid ""
"Specify an IP address to return for any host in the given domains, Queries "
"in the domains are never forwarded and always replied to with the specified "
"IP address which may be IPv4 or IPv6."
msgstr ""
"配置特定域名返回特定的IP地址域名查询将不到上游服务器请求直接返回配置的IP"
"地址,可用于广告屏蔽。"
msgid "Speed Check Mode"
msgstr "测速模式"
msgid "Speed check mode is invalid."
msgstr "测速模式无效。"
msgid "TCP Server"
msgstr "TCP服务器"
msgid "TCP port is empty"
msgstr "TCP端口号为空"
msgid "TLS Hostname Verify"
msgstr "校验TLS主机名"
msgid "TLS SNI name"
msgstr "TLS SNI名称"
msgid "TLS SPKI Pinning"
msgstr "TLS SPKI 指纹"
msgid "TTL for all domain result."
msgstr "设置所有域名的 TTL 值。"
msgid "Technical Support"
msgstr "技术支持"
msgid "URL"
msgstr "URL"
msgid "URL format error, format: http:// or https://"
msgstr "URL格式错误格式http://或https://"
msgid "Update"
msgstr "更新"
msgid "Update Files"
msgstr "更新文件"
msgid "Upload client address file, same as Client Address function."
msgstr "上传客户端地址文件,与客户端地址功能相同。"
msgid "Upload Config File"
msgstr "上传配置文件"
msgid "Upload Domain List File"
msgstr "上传域名列表文件"
msgid "Upload domain list file to /etc/smartdns/domain-set"
msgstr "上传域名列表文件到/etc/smartdns/domain-set"
msgid ""
"Upload domain list file, or configure auto download from Download File "
"Setting page."
msgstr "上传域名列表文件,或在下载文件设置页面设置自动下载。"
msgid "Upload domain list file."
msgstr "上传域名列表文件"
msgid "Upload File"
msgstr "上传文件"
msgid "Upload IP set file."
msgstr "上传IP集合列表文件。"
msgid "Upload smartdns config file to /etc/smartdns/conf.d"
msgstr "上传配置文件到/etc/smartdns/conf.d"
msgid "Upstream DNS Server Configuration"
msgstr "上游DNS服务器配置"
msgid "Upstream Servers"
msgstr "上游服务器"
msgid ""
"Upstream Servers, support UDP, TCP protocol. Please configure multiple DNS "
"servers, including multiple foreign DNS servers."
msgstr ""
"上游 DNS 服务器,支持 UDPTCP 协议。请配置多个上游 DNS 服务器,包括多个国内"
"外服务器。"
msgid "Use Proxy"
msgstr "使用代理"
msgid "Use proxy to connect to upstream DNS server."
msgstr "使用代理连接上游DNS服务器。"
msgid ""
"Used to verify the validity of the TLS server, The value is Base64 encoded "
"SPKI fingerprint, leaving blank to indicate that the validity of TLS is not "
"verified."
msgstr ""
"用于校验 TLS 服务器的有效性,数值为 Base64 编码的 SPKI 指纹,留空表示不验证 "
"TLS 的合法性。"
msgid "Whitelist IP"
msgstr "白名单"
msgid "Whitelist IP Rule, Accept IP addresses within the range."
msgstr "白名单规则接受指定范围的IP地址。"
msgid "Write cache to disk on exit and load on startup."
msgstr "退出时保存cache到磁盘启动时加载。"
msgid "Yes"
msgstr "是"
msgid "default"
msgstr "默认"
msgid "domain list (/etc/smartdns/domain-set)"
msgstr "域名列表(/etc/smartdns/domain-set"
msgid "other file (/etc/smartdns/download)"
msgstr "其它文件(/etc/smartdns/download"
msgid "https"
msgstr "https"
msgid "ip"
msgstr "ip"
msgid "ip-set file (/etc/smartdns/ip-set)"
msgstr "IP集合列表文件/etc/smartdns/ip-set"
msgid "ipset name format error, format: [#[4|6]:]ipsetname"
msgstr "IPset名称格式错误格式[#[4|6]:]ipsetname"
msgid "open website"
msgstr "打开网站"
msgid "port"
msgstr "端口"
msgid "smartdns config (/etc/smartdns/conf.d)"
msgstr "smartdns 配置文件(/etc/smartdns/conf.d"
msgid "smartdns custom settings"
msgstr "smartdns 自定义设置,具体配置参数参考指导"
msgid "tcp"
msgstr "tcp"
msgid "tls"
msgstr "tls"
msgid "type"
msgstr "类型"
msgid "udp"
msgstr "udp"

View File

@@ -0,0 +1 @@
zh-cn

View File

@@ -0,0 +1,26 @@
#!/bin/sh
#
# Copyright (C) 2018-2024 Ruilin Peng (Nick) <pymumu@gmail.com>.
#
# smartdns is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# smartdns is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
uci -q batch <<-EOF >/dev/null
delete ucitrack.@smartdns[-1]
add ucitrack smartdns
set ucitrack.@smartdns[-1].init=smartdns
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0

202
luci-app-tcpdump/LICENSE Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

20
luci-app-tcpdump/Makefile Normal file
View File

@@ -0,0 +1,20 @@
# Copyright (C) 2016 Openwrt.org
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
# Copyright (C) 2019, KFERMercer <iMercer@yeah.net>
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for tcpdump
LUCI_DEPENDS:=+tcpdump
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-tcpdump
PKG_VERSION:=1.0
PKG_RELEASE:=2
PKG_MAINTAINER:=<https://github.com/KFERMercer/luci-app-tcpdump>
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@@ -0,0 +1,13 @@
# luci-app-tcpdump
LuCI interface for tcpdump.\
It can be used to capture live TCP traffic for analysis.
## How to build into firmware:
`git clone https://github.com/KFERMercer/luci-app-tcpdump.git ./package/luci-app-tcpdump`
`make menuconfig`
### Original codes built by [MacManas](https://github.com/MacManas/luci-app-tcpdump).

View File

@@ -0,0 +1,356 @@
--[[
LuCI - Lua Configuration Interface
Copyright 2013-2014 Diego Manas <diegomanas.dev@gmail.com>
Copyright (C) 2019, KFERMercer <iMercer@yeah.net>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
$Id$
2019-07-12 modified by KFERMercer <iMercer@yeah.com>:
format code
]] --
module("luci.controller.tcpdump", package.seeall)
tcpdump_root_folder = "/tmp/tcpdump/"
tcpdump_cap_folder = tcpdump_root_folder .. "cap/"
tcpdump_filter_folder = tcpdump_root_folder .. "filter/"
pid_file = tcpdump_root_folder .. "tcpdump.pid"
log_file = tcpdump_root_folder .. "tcpdump.log"
out_file = tcpdump_root_folder .. "tcpdump.out"
sleep_file = tcpdump_root_folder .. "tcpdump.sleep"
function index()
template("myapp-mymodule/helloworld")
entry({"admin", "network", "tcpdump"}, template("tcpdump"), _ "Tcpdump", 70).dependent =
false
page = entry({"admin", "network", "tcpdump", "capture_start"},
call("capture_start"), nil)
page.leaf = true
page = entry({"admin", "network", "tcpdump", "capture_stop"},
call("capture_stop"), nil)
page.leaf = true
page = entry({"admin", "network", "tcpdump", "update"}, call("update"), nil)
page.leaf = true
page = entry({"admin", "network", "tcpdump", "capture_get"},
call("capture_get"), nil)
page.leaf = true
page = entry({"admin", "network", "tcpdump", "capture_remove"},
call("capture_remove"), nil)
page.leaf = true
end
function param_check(ifname, stop_value, stop_unit, filter)
local check = false
local message = {}
-- Check interface
-- Check for empty interface
if ifname == nil or ifname == '' then
table.insert(message, "Interface name is null or blank.")
end
-- Check for existing interface
local nixio = require "nixio"
for k, v in ipairs(nixio.getifaddrs()) do
if v.family == "packet" then
if ifname == v.name then
check = true
break
end
end
end
-- Check special interface name "any"
if iface == 'any' then check = true end
-- ERROR interface name not found
if not check then
table.insert(message, "Interface does not exist or is not valid.")
end
-- Check stop condition value
if tonumber(stop_value) == nil then
check = false
table.insert(message, "Capture length parameter must be a number.")
end
-- Check stop condition flag
if stop_unit == nil then
check = false
table.insert(message, "Capture unit is null or blank.")
else
stop_unit = string.upper(stop_unit)
if stop_unit ~= "T" and stop_unit ~= "P" then
check = false
table.insert(message, "Capture unit must be Time(T) or packet(P).")
end
end
return check, message
end
function capture_start(ifname, stop_value, stop_unit, filter)
local active, pid = capture_active()
local res = {}
local cmd = {}
if active then
cmd["ok"] = false
cmd["msg"] = {"Previous capture is still ongoing!"}
else
local check, msg = param_check(ifname, stop_value, stop_unit, filter)
if not check then
cmd["ok"] = false
cmd["msg"] = msg
else
-- Create temporal folders
os.execute("mkdir -p " .. tcpdump_cap_folder)
os.execute("mkdir -p " .. tcpdump_filter_folder)
local prefix = "capture_" .. os.date("%Y-%m-%d_%H.%M.%S")
local pcap_file = tcpdump_cap_folder .. prefix .. ".pcap"
local filter_file = tcpdump_filter_folder .. prefix .. ".filter"
string_to_file(filter_file, filter)
string_to_file(out_file, prefix)
tcpdump_start(ifname, stop_value, stop_unit, filter_file, pcap_file)
res["filter"] = filter
cmd["ok"] = true
cmd["msg"] = {"Capture in progress.."}
end
end
res["cmd"] = cmd
res["capture"] = capture()
res["list"] = list()
luci.http.prepare_content("application/json")
luci.http.write_json(res)
end
function string_to_file(file, data)
if data == nil then data = "" end
local f = io.open(file, "w")
f:write(data)
f:close()
end
function tcpdump_start(ifname, stop_value, stop_unit, filter_file, pcap_file)
local cmd = "tcpdump -i %s -F %s -w %s"
cmd = string.format(cmd, ifname, filter_file, pcap_file)
-- Packet limit if required
if tonumber(stop_value) ~= 0 and stop_unit == "P" then
cmd = cmd .. " -c " .. stop_value
end
-- Mute output and record PID on pid_file
cmd = string.format("%s &> %s & echo $! > %s", cmd, log_file, pid_file)
os.execute(cmd)
-- Time limit if required
if tonumber(stop_value) ~= 0 and stop_unit == "T" then
local f = io.open(pid_file, "r")
if f ~= nil then
local pid = f:read()
f:close()
local t_out =
string.format("sleep %s && kill %s &", stop_value, pid)
os.execute(t_out)
end
end
end
function capture_stop()
local res = {}
local cmd = {}
local _, active, pid = capture()
if active then
luci.sys.process.signal(pid, 9)
cmd["ok"] = true
cmd["msg"] = {"Capture has been terminated"}
else
cmd["ok"] = false
cmd["msg"] = {"There was not active capture!"}
end
capture_cleanup()
res["cmd"] = cmd
res["capture"] = capture()
res["list"] = list()
luci.http.prepare_content("application/json")
luci.http.write_json(res)
end
function capture_active()
local f = io.open(pid_file, "r")
if f ~= nil then
pid = f:read()
f:close()
-- Check it is a legal PID and still alive
if tonumber(pid) ~= nil and luci.sys.process.signal(pid, 0) then
return true, pid
end
end
return false, nil
end
function capture_log()
local log
local f = io.open(log_file, "r")
if f ~= nil then
log = f:read("*all")
f:close()
else
log = ""
end
return log
end
function capture_name()
local cap_name = nil
local f = io.open(out_file, "r")
if f ~= nil then
cap_name = f:read()
f:close()
end
return cap_name
end
function capture()
local fs = require "nixio.fs"
local res = {}
local active, pid = capture_active()
local msg
res["active"] = active
res["log"] = capture_log()
if active then
res["msg"] = "Capture in progress.."
res["cap_name"] = capture_name()
elseif fs.access(pid_file) then
capture_cleanup()
res["msg"] = "Process seems to be dead, removing pid file!"
else
res["msg"] = "No capture in progress"
end
return res, active, pid
end
function capture_cleanup()
-- Careless file removal
os.remove(pid_file)
os.remove(log_file)
os.remove(out_file)
local f = io.open(sleep_file, "r")
if f ~= nil then
pid = f:read()
f:close()
-- Kill sleep process if still alive
if tonumber(pid) ~= nil or not luci.sys.process.signal(pid, 0) then
luci.sys.process.signal(pid, 9)
end
end
-- Careless file removal
os.remove(sleep_file)
end
function list_entries(cap_name)
local fs = require "nixio.fs"
local entries = {}
local name
local size
local mtime
local filter
local glob_str
if cap_name == nil then
glob_str = tcpdump_cap_folder .. "*.pcap"
else
glob_str = tcpdump_cap_folder .. cap_name .. ".pcap"
end
for file in fs.glob(glob_str) do
name = string.sub(fs.basename(file), 1, -6)
size = fs.stat(file, "size")
mtime = fs.stat(file, "ctime")
-- Figure out if there's an associated filter
if fs.access(tcpdump_filter_folder .. name .. ".filter") then
filter = true
else
filter = false
end
table.insert(entries,
{name = name, size = size, mtime = mtime, filter = filter})
end
return entries
end
function list(cap_name)
res = {}
res["entries"] = list_entries(cap_name)
res["update"] = (cap_name ~= nil)
return res
end
function update(cap_name)
local res = {}
local cmd = {}
cmd["ok"] = true
res["cmd"] = cmd
res["capture"] = capture()
res["list"] = list(cap_name)
-- Build response
luci.http.prepare_content("application/json")
luci.http.write_json(res)
end
function pump_file(file, mime_str)
local fh = io.open(file)
local reader = luci.ltn12.source.file(fh)
luci.http.header("Content-Disposition", "attachment; filename=\"" ..
nixio.fs.basename(file) .. "\"")
if mime_str ~= nil then
luci.http.prepare_content(mime_str)
else
luci.http.prepare_content("application/octet-stream")
end
luci.ltn12.pump.all(reader, luci.http.write)
fh:close()
end
function capture_get(file_type, cap_name)
if file_type == "all" then
local system = require "luci.controller.admin.system"
local tar_captures_cmd = "tar -c " .. tcpdump_cap_folder ..
"*.pcap 2>/dev/null"
local reader = system.ltn12_popen(tar_captures_cmd)
luci.http.header('Content-Disposition',
'attachment; filename="captures-%s.tar"' %
{os.date("%Y-%m-%d_%H.%M.%S")})
luci.http.prepare_content("application/x-tar")
luci.ltn12.pump.all(reader, luci.http.write)
elseif file_type == "pcap" then
local file = tcpdump_cap_folder .. cap_name .. '.pcap'
pump_file(file)
elseif file_type == "filter" then
local file = tcpdump_filter_folder .. cap_name .. '.filter'
pump_file(file, "text/plain")
else
-- TODO
end
end
function capture_remove(cap_name)
if cap_name == 'all' then
local fs = require "nixio.fs"
for file in fs.glob(tcpdump_cap_folder .. "*.pcap") do
os.remove(file)
end
for file in fs.glob(tcpdump_filter_folder .. "*.filter") do
os.remove(file)
end
else
-- Remove both, capture and filter file
os.remove(tcpdump_cap_folder .. cap_name .. ".pcap")
os.remove(tcpdump_filter_folder .. cap_name .. ".filter")
end
-- Return current status and list
update()
end

View File

@@ -0,0 +1,246 @@
<%#
LuCI - Lua Configuration Interface
Copyright (C) 2013-2014, Diego Manas <diegomanas.dev@gmail.com>
Initial layout based on cshark project: https://github.com/cloudshark/cshark
Copyright (C) 2014, QA Cafe, Inc.
Copyright (C) 2019, KFERMercer <iMercer@yeah.net>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
$Id$
2019-07-12 modified by KFERMercer <iMercer@yeah.com>:
format code & change tag name
-%>
<%+header%>
<fieldset class="cbi-section">
<legend><%:Start network capture%></legend>
<div class="cbi-section-node">
<table class="cbi-section-table">
<tr>
<th><%:Interface%></th>
<th colspan='2'><%:seconds, packets%></th>
<th><%:Filter%></th>
<th><%:Actions%></th>
</tr>
<tr>
<td>
<select title="<%:Interface%>" style="width:auto" id="cap_ifname">
<%
local nixio = require "nixio"
for k, v in ipairs(nixio.getifaddrs()) do
if v.family == "packet" then
%>
<option value="<%=v.name%>"><%=v.name%> </option>
<%
end
end
%>
<option value="any"><%:any%></option>
</select>
</td>
<td colspan='2'>
<input id="cap_stop_value" type="text" value="0" />
<select title="<%:timeout, bytes, seconds%>" id="cap_stop_unit" style="width:auto">
<option value="T"><%:seconds%></option>
<option value="P"><%:packets%></option>
</select>
</td>
<td>
<input style="margin: 5px 0" type="text" title="<%:Filter%>" placeholder="filter" id="cap_filter" />
</td>
<td>
<input type="button" id="bt_capture" value="<%:Disabled%>" class="cbi-button" disabled />
</td>
</tr>
</table>
</div>
</fieldset>
<fieldset class="cbi-section">
<legend><%:Output%></legend>
<span id="tcpdump-message"></span>
<span id="tcpdump-log"></span>
</fieldset>
<hr />
<fieldset class="cbi-section">
<legend><%:Capture links%></legend>
<div class="cbi-section-node">
<table id="t_list" class="cbi-section-table">
<tr class="cbi-section-table-titles">
<th class="cbi-section-table-cell"><%:Capture file%></th>
<th class="cbi-section-table-cell"><%:Modification date%></th>
<th class="cbi-section-table-cell"><%:Capture size%></th>
<th class="cbi-section-table-cell"><%:Actions%></th>
</tr>
</table>
</div>
</fieldset>
<hr />
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<script type="text/javascript">//<![CDATA[
var capture_active = false;
var capture_name;
function update_button() {
var bt_capture = document.getElementById('bt_capture');
if (!capture_active) {
bt_capture.value = '<%:Start capture%>';
bt_capture.onclick = capture_start;
} else {
bt_capture.value = '<%:Stop capture%>';
bt_capture.onclick = capture_stop;
}
bt_capture.disabled = false;
}
function capture_start() {
var elem_ifname = document.getElementById('cap_ifname');
var elem_stop_value = document.getElementById('cap_stop_value');
var elem_stop_unit = document.getElementById('cap_stop_unit');
var elem_filter = document.getElementById('cap_filter');
var ifname = elem_ifname.options[elem_ifname.selectedIndex].value;
var stop_value = elem_stop_value.value;
var stop_unit = elem_stop_unit.options[elem_stop_unit.selectedIndex].value;
var filter = elem_filter.value;
// TODO Implement checks?
XHR.get('<%=luci.dispatcher.build_url("admin", "network", "tcpdump")%>/capture_start/' +
ifname + '/' + stop_value + '/' + stop_unit + '/' + filter,
null, update_callback)
}
function capture_stop() {
XHR.get('<%=luci.dispatcher.build_url("admin", "network", "tcpdump")%>/capture_stop',
null, update_callback)
}
function update_poll() {
XHR.poll(10, '<%=luci.dispatcher.build_url("admin", "network", "tcpdump")%>/update',
null, update_callback)
}
function update_callback(xhr, json) {
console.log(xhr)
console.log(json)
update_table(xhr, json)
update_status(xhr, json)
}
function update_table(xhr, json) {
var table = document.getElementById("t_list");
if (!table) return;
// Remove all rows except headers
while (table.rows.length > 1) {
table.deleteRow(-1);
}
if (!xhr) {
var cell = table.insertRow(-1).insertCell(0);
cell.colSpan = table.rows[0].cells.length;
cell.innerHTML = '<em><br />Could not retrieve captures.</em>';
return;
}
var entries = json.list.entries;
if (!entries || !entries.length) {
var cell = table.insertRow(-1).insertCell(0);
cell.colSpan = table.rows[0].cells.length;
cell.innerHTML = '<em><br />There are no captures available yet.</em>';
return;
}
// Add rows
var total_size = 0
for (var i = 0; i < entries.length; i++) {
var row = table.insertRow(-1);
total_size += entries[i].size;
var url = '<%=luci.dispatcher.build_url("admin", "network", "tcpdump")%>'
row.insertCell().innerHTML = '<a href="#" onclick="capture_get(\'pcap\', \'' + entries[i].name + '\')">' + entries[i].name + '</a>';
row.insertCell().innerHTML = human_date(entries[i].mtime);
row.insertCell().innerHTML = human_size(entries[i].size);
var cell = row.insertCell();
cell.innerHTML += '<input type="button" onclick="capture_get(\'pcap\', \'' + entries[i].name + '\')" class="cbi-button cbi-button-download" value ="<%:pcap file%>" />';
cell.innerHTML += '<input type="button" onclick="capture_get(\'filter\', \'' + entries[i].name + '\')" class="cbi-button cbi-button-download" value ="<%:filter file%>" />';
cell.lastChild.disabled = !entries[i].filter;
cell.innerHTML += '<input type="button" onclick="capture_remove(\'' + entries[i].name + '\')" class="cbi-button cbi-button-reset" value ="<%:Remove%>" />';
}
// Add summary row at the end
var row = table.insertRow(-1);
row.insertCell().innerHTML = '<b><%:All files%></b>';
row.insertCell();
row.insertCell().innerHTML = human_size(total_size);
row.insertCell().innerHTML = '<input type="button" onclick="capture_get(\'all\')" class="cbi-button cbi-button-download" value ="<%:Download%>" />';
row.cells[row.cells.length - 1].innerHTML += '<input type="button" onclick="capture_remove(\'all\')" class="cbi-button cbi-button-reset" value ="<%:Remove%>" />';
}
function update_status(xhr, json) {
capture_active = json.capture.active;
capture_name = json.capture.cap_name;
var in_use;
in_use = document.getElementById("tcpdump-message");
var msg = ""
if (json.cmd.hasOwnProperty("msg")) {
for (var i = 0; i < json.cmd.msg.length; i++) {
msg += json.cmd.msg[i] + "\n";
}
} else {
msg = json.capture.msg;
}
in_use.innerHTML = "<pre>" + msg + "</pre>";
in_use = document.getElementById("tcpdump-log");
if (capture_active) {
in_use.innerHTML = "<pre>" + json.capture.log + "</pre>";
} else {
in_use.innerHTML = ""
}
update_button()
}
function human_size(size) {
var units = ["B", "KiB", "MiB", "GiB"]
var unit_index = 0
while (size > 1024 && unit_index < 3) {
unit_index += 1
size /= 1024
}
return Math.round(size * 100) / 100 + " " + units[unit_index]
}
function human_date(date_seconds) {
var date = new Date(date_seconds * 1000)
return date.getDate() + "/" + (date.getMonth() + 1) + "/" + date.getFullYear() + " " +
date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds()
}
function capture_get(type, cap_name) {
var iframe;
iframe = document.getElementById("hiddenDownloader");
if (iframe == null) {
iframe = document.createElement('iframe');
iframe.id = "hiddenDownloader";
iframe.style.visibility = 'hidden';
document.body.appendChild(iframe);
}
iframe.src = '<%=luci.dispatcher.build_url("admin", "network", "tcpdump")%>/capture_get/' + type + '/' + cap_name;
}
function capture_remove(cap_name) {
XHR.get('<%=luci.dispatcher.build_url("admin", "network", "tcpdump")%>/capture_remove/' + cap_name, null, update_callback)
}
document.onload = update_poll();
//]]></script>
<%+footer%>

View File

@@ -0,0 +1,107 @@
msgid "Tcpdump"
msgstr "Tcpdump 流量监控"
msgid "Start network capture"
msgstr "Tcpdump 流量监控"
msgid "Interface"
msgstr "捕获指定的接口"
msgid "seconds, packets"
msgstr "捕获限制"
msgid "Filter"
msgstr "过滤"
msgid "Actions"
msgstr "操作"
msgid "any"
msgstr "所有"
msgid "timeout, bytes, seconds"
msgstr "超时, 字节, 秒"
msgid "seconds"
msgstr "秒"
msgid "packets"
msgstr "数据包"
msgid "Disabled"
msgstr "已禁用"
msgid "Output"
msgstr "输出"
msgid "Capture links"
msgstr "捕获结果"
msgid "Capture file"
msgstr "文件名"
msgid "Modification date"
msgstr "停止时间"
msgid "Capture size"
msgstr "文件大小"
msgid "Start capture"
msgstr "开始捕获"
msgid "Stop capture"
msgstr "停止捕获"
msgid "pcap file"
msgstr ".pcap文件"
msgid "filter file"
msgstr ".filter文件"
msgid "Remove"
msgstr "删除"
msgid "All files"
msgstr "所有文件"
msgid "Download"
msgstr "下载"
msgid "Interface name is null or blank."
msgstr "请指定要捕获的接口."
msgid "Interface does not exist or is not valid."
msgstr "接口不存在或无效."
msgid "Capture length parameter must be a number."
msgstr "捕获长度参数必须是数字."
msgid "Capture unit is null or blank."
msgstr "捕获单位为空或空白."
msgid "Capture unit must be Time(T) or packet(P)."
msgstr "捕获单位必须是时间(T)或包(P)."
msgid "Previous capture is still ongoing!"
msgstr "先前的捕获未停止!"
msgid "Capture in progress.."
msgstr "正在捕获..."
msgid "Capture has been terminated"
msgstr "捕获已被终止"
msgid "There was not active capture!"
msgstr "捕获活动未运行!"
msgid "Process seems to be dead, removing pid file!"
msgstr "进程失去响应, 正在删除pid文件!"
msgid "No capture in progress"
msgstr "没有正在进行的捕获"
msgid "Could not retrieve captures."
msgstr "无法检索捕获."
msgid "There are no captures available yet."
msgstr "目前没有可用的捕获."

1
luci-app-tcpdump/po/zh_Hans Symbolic link
View File

@@ -0,0 +1 @@
zh-cn

140
smartdns/Makefile Normal file
View File

@@ -0,0 +1,140 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Copyright (c) 2018-2023 Nick Peng (pymumu@gmail.com)
include $(TOPDIR)/rules.mk
PKG_NAME:=smartdns
PKG_VERSION:=47.1
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/pymumu/smartdns/tar.gz/Release$(PKG_VERSION)?
PKG_HASH:=0a143ee81ccb7a31b7b7b0c29d6a6ee41a54331da75477719925592af124ec97
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-Release$(PKG_VERSION)
PKG_MAINTAINER:=Nick Peng <pymumu@gmail.com>
PKG_LICENSE:=GPL-3.0-or-later
PKG_LICENSE_FILES:=LICENSE
PKG_CONFIG_DEPENDS:=CONFIG_PACKAGE_smartdns-ui
PKG_BUILD_DEPENDS:=PACKAGE_smartdns-ui:node/host PACKAGE_smartdns-ui:rust-bindgen/host
PKG_BUILD_PARALLEL:=1
RUST_PKG_FEATURES:=build-release
RUST_PKG_LOCKED:=0
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/rust/rust-package.mk
MAKE_PATH:=src
MAKE_VARS+= VER=$(PKG_VERSION)
define Package/smartdns/Default
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=smartdns
URL:=https://www.github.com/pymumu/smartdns/
endef
define Package/smartdns
$(call Package/smartdns/Default)
TITLE+= server
DEPENDS:=+i386:libatomic +libopenssl
endef
define Package/smartdns/description
SmartDNS is a local DNS server which accepts DNS query requests from local network clients,
gets DNS query results from multiple upstream DNS servers concurrently, and returns the fastest IP to clients.
Unlike dnsmasq's all-servers, smartdns returns the fastest IP, and encrypt DNS queries with DoT or DoH.
endef
define Package/smartdns-ui
$(call Package/smartdns/Default)
TITLE+= dashboard
DEPENDS:=+smartdns $(RUST_ARCH_DEPENDS) @!(TARGET_x86_geode||TARGET_x86_legacy)
endef
define Package/smartdns-ui/description
A dashboard ui for smartdns server.
endef
define Package/smartdns/conffiles
/etc/config/smartdns
/etc/smartdns/address.conf
/etc/smartdns/blacklist-ip.conf
/etc/smartdns/custom.conf
/etc/smartdns/domain-block.list
/etc/smartdns/domain-forwarding.list
endef
define Download/smartdns-webui
PROTO:=git
URL:=https://github.com/pymumu/smartdns-webui.git
SOURCE_DATE:=2025-09-18
SOURCE_VERSION:=c322303eac2ebee389f4a72a002163552e552f74
MIRROR_HASH:=b239f3d994e05ad08356bf1be629cd84c2bfc6706997aafea881284cacc29545
SUBDIR:=smartdns-webui-$$$$(subst -,.,$$$$(SOURCE_DATE))~$$$$(call version_abbrev,$$$$(SOURCE_VERSION))
FILE:=$$(SUBDIR).tar.zst
endef
define Build/Prepare
$(call Build/Prepare/Default)
ifneq ($(CONFIG_PACKAGE_smartdns-ui),)
$(eval $(call Download,smartdns-webui))
$(eval $(Download/smartdns-webui))
mkdir -p $(PKG_BUILD_DIR)/smartdns-webui
zstdcat $(DL_DIR)/$(FILE) | tar -C $(PKG_BUILD_DIR)/smartdns-webui $(TAR_OPTIONS) --strip-components=1
endif
endef
define Build/Compile
$(call Build/Compile/Default)
ifneq ($(CONFIG_PACKAGE_smartdns-ui),)
( \
pushd $(PKG_BUILD_DIR) ; \
pushd smartdns-webui ; \
npm install ; \
npm run build ; \
popd ; \
pushd plugin/smartdns-ui ; \
$(CARGO_PKG_CONFIG_VARS) \
MAKEFLAGS="$(PKG_JOBS)" \
TARGET_CFLAGS="$(filter-out -O%,$(TARGET_CFLAGS)) $(RUSTC_CFLAGS)" \
BINDGEN_EXTRA_CLANG_ARGS="--sysroot=$(TOOLCHAIN_ROOT_DIR)" \
cargo build -v --profile $(CARGO_PKG_PROFILE) \
$(if $(strip $(RUST_PKG_FEATURES)),--features "$(strip $(RUST_PKG_FEATURES))") \
$(if $(filter --jobserver%,$(PKG_JOBS)),,-j1) \
$(CARGO_PKG_ARGS) ; \
popd ; \
popd ; \
)
endif
endef
define Package/smartdns/install
$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/config $(1)/etc/init.d
$(INSTALL_DIR) $(1)/etc/smartdns $(1)/etc/smartdns/domain-set $(1)/etc/smartdns/conf.d/
$(INSTALL_DIR) $(1)/etc/smartdns/ip-set $(1)/etc/smartdns/download
$(INSTALL_BIN) $(PKG_BUILD_DIR)/src/smartdns $(1)/usr/sbin/smartdns
$(INSTALL_BIN) $(PKG_BUILD_DIR)/package/openwrt/files/etc/init.d/smartdns $(1)/etc/init.d/smartdns
$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/address.conf $(1)/etc/smartdns/address.conf
$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/blacklist-ip.conf $(1)/etc/smartdns/blacklist-ip.conf
$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/custom.conf $(1)/etc/smartdns/custom.conf
$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/domain-block.list $(1)/etc/smartdns/domain-block.list
$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/domain-forwarding.list $(1)/etc/smartdns/domain-forwarding.list
$(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/files/etc/config/smartdns $(1)/etc/config/smartdns
endef
define Package/smartdns-ui/install
$(INSTALL_DIR) $(1)/usr/lib
$(INSTALL_DIR) $(1)/usr/share/smartdns
$(CP) $(PKG_BUILD_DIR)/plugin/smartdns-ui/target/$(RUSTC_TARGET_ARCH)/$(CARGO_PKG_PROFILE)/libsmartdns_ui.so $(1)/usr/lib/smartdns_ui.so
$(CP) $(PKG_BUILD_DIR)/smartdns-webui/out $(1)/usr/share/smartdns/wwwroot
endef
$(eval $(call BuildPackage,smartdns))
$(eval $(call BuildPackage,smartdns-ui))