Repository: pymumu/smartdns
Branch: master
Commit: 115db1f4537c
Files: 405
Total size: 2.5 MB
Directory structure:
gitextract_d_s45j8t/
├── .clang-format
├── .clang-tidy
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── feature.md
│ │ └── issue.md
│ └── workflows/
│ ├── c-cpp.yml
│ ├── docker.yml
│ └── webui.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── ReadMe.md
├── ReadMe_en.md
├── doc/
│ └── architecture.vsdx
├── etc/
│ ├── default/
│ │ └── smartdns
│ ├── init.d/
│ │ └── smartdns
│ └── smartdns/
│ └── smartdns.conf
├── package/
│ ├── build-pkg.sh
│ ├── copy-smartdns.sh
│ ├── debian/
│ │ ├── DEBIAN/
│ │ │ ├── changelog
│ │ │ ├── compat
│ │ │ ├── conffiles
│ │ │ ├── control
│ │ │ ├── copyright
│ │ │ ├── prerm
│ │ │ └── rules
│ │ └── make.sh
│ ├── linux/
│ │ ├── install
│ │ └── make.sh
│ ├── luci/
│ │ ├── control/
│ │ │ ├── control
│ │ │ ├── postinst
│ │ │ └── prerm
│ │ ├── debian-binary
│ │ ├── files/
│ │ │ ├── luci/
│ │ │ │ └── i18n/
│ │ │ │ └── smartdns.zh-cn.po
│ │ │ └── root/
│ │ │ ├── usr/
│ │ │ │ └── share/
│ │ │ │ ├── luci/
│ │ │ │ │ └── menu.d/
│ │ │ │ │ └── luci-app-smartdns.json
│ │ │ │ └── rpcd/
│ │ │ │ └── acl.d/
│ │ │ │ └── luci-app-smartdns.json
│ │ │ └── www/
│ │ │ └── luci-static/
│ │ │ └── resources/
│ │ │ └── view/
│ │ │ └── smartdns/
│ │ │ └── smartdns.js
│ │ └── make.sh
│ ├── luci-compat/
│ │ ├── control/
│ │ │ ├── control
│ │ │ ├── postinst
│ │ │ └── prerm
│ │ ├── debian-binary
│ │ ├── files/
│ │ │ ├── etc/
│ │ │ │ └── uci-defaults/
│ │ │ │ └── 50_luci-smartdns
│ │ │ ├── luci/
│ │ │ │ ├── controller/
│ │ │ │ │ └── smartdns.lua
│ │ │ │ ├── i18n/
│ │ │ │ │ └── smartdns.zh-cn.po
│ │ │ │ ├── model/
│ │ │ │ │ ├── cbi/
│ │ │ │ │ │ └── smartdns/
│ │ │ │ │ │ ├── smartdns.lua
│ │ │ │ │ │ └── upstream.lua
│ │ │ │ │ └── smartdns.lua
│ │ │ │ └── view/
│ │ │ │ └── smartdns/
│ │ │ │ └── smartdns_status.htm
│ │ │ └── usr/
│ │ │ └── share/
│ │ │ └── rpcd/
│ │ │ └── acl.d/
│ │ │ └── luci-app-smartdns.json
│ │ └── make.sh
│ ├── luci-lite/
│ │ ├── control/
│ │ │ ├── conffiles
│ │ │ ├── control
│ │ │ ├── postinst
│ │ │ └── prerm
│ │ ├── debian-binary
│ │ ├── files/
│ │ │ ├── luci/
│ │ │ │ └── i18n/
│ │ │ │ └── smartdns-lite.zh-cn.po
│ │ │ └── root/
│ │ │ ├── etc/
│ │ │ │ ├── config/
│ │ │ │ │ └── smartdns-lite
│ │ │ │ └── init.d/
│ │ │ │ └── smartdns-lite
│ │ │ ├── usr/
│ │ │ │ └── share/
│ │ │ │ ├── luci/
│ │ │ │ │ └── menu.d/
│ │ │ │ │ └── luci-app-smartdns-lite.json
│ │ │ │ └── rpcd/
│ │ │ │ └── acl.d/
│ │ │ │ └── luci-app-smartdns-lite.json
│ │ │ └── www/
│ │ │ └── luci-static/
│ │ │ └── resources/
│ │ │ └── view/
│ │ │ └── smartdns-lite/
│ │ │ └── smartdns-lite.js
│ │ └── make.sh
│ ├── openwrt/
│ │ ├── Makefile
│ │ ├── address.conf
│ │ ├── blacklist-ip.conf
│ │ ├── control/
│ │ │ ├── conffiles
│ │ │ ├── control
│ │ │ ├── postinst
│ │ │ └── prerm
│ │ ├── custom.conf
│ │ ├── debian-binary
│ │ ├── domain-block.list
│ │ ├── domain-forwarding.list
│ │ ├── files/
│ │ │ └── etc/
│ │ │ ├── config/
│ │ │ │ └── smartdns
│ │ │ └── init.d/
│ │ │ └── smartdns
│ │ └── make.sh
│ ├── optware/
│ │ ├── S50smartdns
│ │ ├── control/
│ │ │ ├── conffiles
│ │ │ ├── control
│ │ │ ├── postinst
│ │ │ └── prerm
│ │ ├── debian-binary
│ │ ├── make.sh
│ │ └── smartdns-opt.conf
│ ├── redhat/
│ │ └── smartdns.spec
│ ├── run-smartdns
│ ├── tool/
│ │ └── po2lmo/
│ │ ├── Makefile
│ │ └── src/
│ │ ├── po2lmo.c
│ │ ├── template_lmo.c
│ │ └── template_lmo.h
│ └── windows/
│ ├── install.bat
│ ├── reload.bat
│ ├── uninstall.bat
│ └── wsl-run.vbs
├── plugin/
│ ├── demo/
│ │ ├── .gitignore
│ │ ├── Makefile
│ │ ├── demo.c
│ │ └── demo.h
│ └── smartdns-ui/
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── Makefile
│ ├── build.rs
│ ├── src/
│ │ ├── data_server.rs
│ │ ├── data_stats.rs
│ │ ├── data_upstream_server.rs
│ │ ├── db.rs
│ │ ├── http_api_msg.rs
│ │ ├── http_error.rs
│ │ ├── http_jwt.rs
│ │ ├── http_server.rs
│ │ ├── http_server_api.rs
│ │ ├── http_server_stream.rs
│ │ ├── lib.rs
│ │ ├── plugin.rs
│ │ ├── server_log.rs
│ │ ├── smartdns.rs
│ │ ├── utils.rs
│ │ └── whois.rs
│ └── tests/
│ ├── common/
│ │ ├── client.rs
│ │ ├── mod.rs
│ │ └── server.rs
│ ├── httpserver_test.rs
│ └── restapi_test.rs
├── src/
│ ├── .gitignore
│ ├── Makefile
│ ├── dns.c
│ ├── dns_cache.c
│ ├── dns_client/
│ │ ├── client_http2.c
│ │ ├── client_http2.h
│ │ ├── client_http3.c
│ │ ├── client_http3.h
│ │ ├── client_https.c
│ │ ├── client_https.h
│ │ ├── client_mdns.c
│ │ ├── client_mdns.h
│ │ ├── client_quic.c
│ │ ├── client_quic.h
│ │ ├── client_socket.c
│ │ ├── client_socket.h
│ │ ├── client_tcp.c
│ │ ├── client_tcp.h
│ │ ├── client_tls.c
│ │ ├── client_tls.h
│ │ ├── client_udp.c
│ │ ├── client_udp.h
│ │ ├── conn_stream.c
│ │ ├── conn_stream.h
│ │ ├── dns_client.c
│ │ ├── dns_client.h
│ │ ├── ecs.c
│ │ ├── ecs.h
│ │ ├── group.c
│ │ ├── group.h
│ │ ├── packet.c
│ │ ├── packet.h
│ │ ├── pending_server.c
│ │ ├── pending_server.h
│ │ ├── proxy.c
│ │ ├── proxy.h
│ │ ├── query.c
│ │ ├── query.h
│ │ ├── server_info.c
│ │ ├── server_info.h
│ │ ├── wake_event.c
│ │ └── wake_event.h
│ ├── dns_conf/
│ │ ├── address.c
│ │ ├── address.h
│ │ ├── bind.c
│ │ ├── bind.h
│ │ ├── bootstrap_dns.c
│ │ ├── bootstrap_dns.h
│ │ ├── client_rule.c
│ │ ├── client_rule.h
│ │ ├── client_subnet.c
│ │ ├── client_subnet.h
│ │ ├── cname.c
│ │ ├── cname.h
│ │ ├── conf_file.c
│ │ ├── conf_file.h
│ │ ├── ddns_domain.c
│ │ ├── ddns_domain.h
│ │ ├── dhcp_lease_dnsmasq.c
│ │ ├── dhcp_lease_dnsmasq.h
│ │ ├── dns64.c
│ │ ├── dns64.h
│ │ ├── dns_conf.c
│ │ ├── dns_conf.h
│ │ ├── dns_conf_group.c
│ │ ├── dns_conf_group.h
│ │ ├── domain_rule.c
│ │ ├── domain_rule.h
│ │ ├── domain_set.c
│ │ ├── domain_set.h
│ │ ├── get_domain.c
│ │ ├── get_domain.h
│ │ ├── group.c
│ │ ├── group.h
│ │ ├── host_file.c
│ │ ├── host_file.h
│ │ ├── https_record.c
│ │ ├── https_record.h
│ │ ├── ip_alias.c
│ │ ├── ip_alias.h
│ │ ├── ip_rule.c
│ │ ├── ip_rule.h
│ │ ├── ip_set.c
│ │ ├── ip_set.h
│ │ ├── ipset.c
│ │ ├── ipset.h
│ │ ├── local_domain.c
│ │ ├── local_domain.h
│ │ ├── nameserver.c
│ │ ├── nameserver.h
│ │ ├── nftset.c
│ │ ├── nftset.h
│ │ ├── plugin.c
│ │ ├── plugin.h
│ │ ├── proxy_names.c
│ │ ├── proxy_names.h
│ │ ├── proxy_server.c
│ │ ├── proxy_server.h
│ │ ├── ptr.c
│ │ ├── ptr.h
│ │ ├── qtype_soa.c
│ │ ├── qtype_soa.h
│ │ ├── server.c
│ │ ├── server.h
│ │ ├── server_group.c
│ │ ├── server_group.h
│ │ ├── set_file.c
│ │ ├── set_file.h
│ │ ├── smartdns_domain.c
│ │ ├── smartdns_domain.h
│ │ ├── speed_check_mode.c
│ │ ├── speed_check_mode.h
│ │ ├── srv_record.c
│ │ └── srv_record.h
│ ├── dns_plugin.c
│ ├── dns_server/
│ │ ├── address.c
│ │ ├── address.h
│ │ ├── answer.c
│ │ ├── answer.h
│ │ ├── audit.c
│ │ ├── audit.h
│ │ ├── cache.c
│ │ ├── cache.h
│ │ ├── client_rule.c
│ │ ├── client_rule.h
│ │ ├── cname.c
│ │ ├── cname.h
│ │ ├── connection.c
│ │ ├── connection.h
│ │ ├── context.c
│ │ ├── context.h
│ │ ├── ddr.c
│ │ ├── ddr.h
│ │ ├── dns64.c
│ │ ├── dns64.h
│ │ ├── dns_server.c
│ │ ├── dns_server.h
│ │ ├── dualstack.c
│ │ ├── dualstack.h
│ │ ├── ip_rule.c
│ │ ├── ip_rule.h
│ │ ├── ipset_nftset.c
│ │ ├── ipset_nftset.h
│ │ ├── local_addr.c
│ │ ├── local_addr.h
│ │ ├── mdns.c
│ │ ├── mdns.h
│ │ ├── neighbor.c
│ │ ├── neighbor.h
│ │ ├── prefetch.c
│ │ ├── prefetch.h
│ │ ├── ptr.c
│ │ ├── ptr.h
│ │ ├── request.c
│ │ ├── request.h
│ │ ├── request_pending.c
│ │ ├── request_pending.h
│ │ ├── rules.c
│ │ ├── rules.h
│ │ ├── server_http2.c
│ │ ├── server_http2.h
│ │ ├── server_https.c
│ │ ├── server_https.h
│ │ ├── server_socket.c
│ │ ├── server_socket.h
│ │ ├── server_tcp.c
│ │ ├── server_tcp.h
│ │ ├── server_tls.c
│ │ ├── server_tls.h
│ │ ├── server_udp.c
│ │ ├── server_udp.h
│ │ ├── soa.c
│ │ ├── soa.h
│ │ ├── speed_check.c
│ │ └── speed_check.h
│ ├── dns_stats.c
│ ├── fast_ping/
│ │ ├── fast_ping.c
│ │ ├── fast_ping.h
│ │ ├── notify_event.c
│ │ ├── notify_event.h
│ │ ├── ping_fake.c
│ │ ├── ping_fake.h
│ │ ├── ping_host.c
│ │ ├── ping_host.h
│ │ ├── ping_icmp.c
│ │ ├── ping_icmp.h
│ │ ├── ping_icmp6.c
│ │ ├── ping_icmp6.h
│ │ ├── ping_tcp.c
│ │ ├── ping_tcp.h
│ │ ├── ping_tcp_syn.c
│ │ ├── ping_tcp_syn.h
│ │ ├── ping_udp.c
│ │ ├── ping_udp.h
│ │ ├── wakeup_event.c
│ │ └── wakeup_event.h
│ ├── http_parse/
│ │ ├── hpack.c
│ │ ├── hpack.h
│ │ ├── http1_parse.c
│ │ ├── http1_parse.h
│ │ ├── http2.c
│ │ ├── http3_parse.c
│ │ ├── http3_parse.h
│ │ ├── http_parse.c
│ │ ├── http_parse.h
│ │ ├── qpack.c
│ │ └── qpack.h
│ ├── lib/
│ │ ├── art.c
│ │ ├── bitops.c
│ │ ├── conf.c
│ │ ├── idna.c
│ │ ├── radix.c
│ │ ├── rbtree.c
│ │ ├── stringutil.c
│ │ └── timer_wheel.c
│ ├── main.c
│ ├── proxy.c
│ ├── smartdns.c
│ ├── timer.c
│ ├── tlog.c
│ └── utils/
│ ├── alpn.c
│ ├── capbility.c
│ ├── daemon.c
│ ├── dns_debug.c
│ ├── ipset.c
│ ├── misc.c
│ ├── neighbors.c
│ ├── net.c
│ ├── nftset.c
│ ├── ssl.c
│ ├── stack.c
│ ├── tls_header_parse.c
│ └── url.c
├── systemd/
│ └── smartdns.service.in
└── test/
├── Makefile
├── cases/
│ ├── test-address.cc
│ ├── test-audit.cc
│ ├── test-bind.cc
│ ├── test-bootstrap.cc
│ ├── test-cache.cc
│ ├── test-client-rule.cc
│ ├── test-cname.cc
│ ├── test-ddns.cc
│ ├── test-ddr.cc
│ ├── test-discard-block-ip.cc
│ ├── test-dns64.cc
│ ├── test-domain-rule.cc
│ ├── test-domain-set.cc
│ ├── test-dualstack.cc
│ ├── test-edns.cc
│ ├── test-group.cc
│ ├── test-hosts.cc
│ ├── test-http.cc
│ ├── test-http2.cc
│ ├── test-https.cc
│ ├── test-idna.cc
│ ├── test-ip-alias.cc
│ ├── test-ip-rule.cc
│ ├── test-lib-http2.cc
│ ├── test-local-domain.cc
│ ├── test-mdns.cc
│ ├── test-mock-server.cc
│ ├── test-nameserver.cc
│ ├── test-perf.cc
│ ├── test-ping.cc
│ ├── test-ptr.cc
│ ├── test-qtype-soa.cc
│ ├── test-rule.cc
│ ├── test-same-pending-query.cc
│ ├── test-server.cc
│ ├── test-speed-check.cc
│ ├── test-srv.cc
│ ├── test-stress.cc
│ └── test-subnet.cc
├── client.cc
├── client.h
├── include/
│ └── utils.h
├── server.cc
├── server.h
├── test.cc
└── utils.cc
================================================
FILE CONTENTS
================================================
================================================
FILE: .clang-format
================================================
#http://clang.llvm.org/docs/ClangFormatStyleOptions.html
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: ForContinuationAndIndentation
MaxEmptyLinesToKeep: 1
AllowShortFunctionsOnASingleLine: Empty
BreakBeforeBraces: Linux
ColumnLimit: 120
================================================
FILE: .clang-tidy
================================================
Checks: >
-*,
modernize-*,
bugprone-*,
concurrency-*,
misc-*,
readability-*,
performance-*,
portability-*,
google-*,
linuxkernel-*,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,
-bugprone-reserved-identifier,
-bugprone-easily-swappable-parameters,
-bugprone-sizeof-expression,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-suspicious-memory-comparison,
-bugprone-not-null-terminated-result,
-bugprone-signal-handler,
-bugprone-assignment-in-if-condition,
-concurrency-mt-unsafe,
-modernize-macro-to-enum,
-misc-unused-parameters,
-misc-misplaced-widening-cast,
-misc-no-recursion,
-misc-include-cleaner,
-readability-magic-numbers,
-readability-use-anyofallof,
-readability-identifier-length,
-readability-function-cognitive-complexity,
-readability-named-parameter,
-readability-isolate-declaration,
-readability-else-after-return,
-readability-redundant-control-flow,
-readability-suspicious-call-argument,
-readability-math-missing-parentheses,
-google-readability-casting,
-google-readability-todo,
-performance-no-int-to-ptr,
# clang-analyzer-*,
# clang-analyzer-deadcode.DeadStores,
# clang-analyzer-optin.performance.Padding,
# -clang-analyzer-security.insecureAPI.*
# Turn all the warnings from the checks above into errors.
FormatStyle: file
================================================
FILE: .github/ISSUE_TEMPLATE/feature.md
================================================
---
name: 需求建议
about: 需求建议描述
title: ''
labels: ''
assignees: ''
---
**需求应用场景**
请描述需求应用的场景和方式。
**建议的方案**
实现上述场景建议的方案。
**设备信息**
1. 设备信息(CPU,厂家)
2. 固件信息
================================================
FILE: .github/ISSUE_TEMPLATE/issue.md
================================================
---
name: 问题报告
about: 问题现象描述
title: ''
labels: ''
assignees: ''
---
**问题现象**
简要描述问题出现的现象
**运行环境**
1. 固件型号
2. 运营商
3. smartdns来源以及版本
4. 涉及的配置(注意去除个人相关信息)
**重现步骤**
1. 上游DNS配置。
2. 访问的域名。
**信息收集**
1. 将/var/log/smrtdns.log日志作为附件上传(注意去除个人相关信息)。
2. 如进程异常,请将coredump功能开启,上传coredump信息文件,同时上传配套的smartdns进程文件。
在自定义界面,开启设置->自定义设置->生成coredump配置,重现问题后提交coredump文件
coredump文件在/tmp目录下
================================================
FILE: .github/workflows/c-cpp.yml
================================================
name: C/C++ CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: prepare
run: |
sudo apt update
sudo apt install libgtest-dev dnsperf
- name: make
run: |
make all -j4
make clean
- name: test
run: |
make -C test test -j8
================================================
FILE: .github/workflows/docker.yml
================================================
name: Publish Docker Image
on:
workflow_dispatch:
inputs:
version:
description: 'new image tag(e.g. v1.1.0)'
required: true
default: 'latest'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{vars.DOCKERHUB_REPO}}:${{ github.event.inputs.version }}
================================================
FILE: .github/workflows/webui.yml
================================================
name: WebUI CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
profile: minimal
toolchain: stable
- name: test
run: |
EXTRA_CFLAGS=-fPIC make -C plugin/smartdns-ui test -j8
================================================
FILE: .gitignore
================================================
.vscode
*.o
*.a
*.pem
.DS_Store
*.swp.
*.a
systemd/smartdns.service
test.bin
package/target
package/*.gz
package/*.ipk
target
================================================
FILE: Dockerfile
================================================
FROM ubuntu:latest AS smartdns-builder
LABEL previous-stage=smartdns-builder
# prepare builder
ARG OPENSSL_VER=3.5.4
ARG NODE_VERSION=20.x
RUN apt update && \
apt install -y binutils perl curl make gcc clang wget unzip ca-certificates && \
update-ca-certificates && \
curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION} | bash - && \
apt install -y nodejs && \
node --version && npm --version && \
\
curl https://sh.rustup.rs -sSf | sh -s -- -y && \
export PATH="$HOME/.cargo/bin:$PATH" && \
\
mkdir -p /build/openssl && \
cd /build/openssl && \
curl -sSL https://www.github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VER}/openssl-${OPENSSL_VER}.tar.gz | tar --strip-components=1 -zxv && \
\
OPENSSL_OPTIONS="no-argon2 no-aria no-async no-bf no-blake2 no-camellia no-cmp no-cms " \
OPENSSL_OPTIONS="$OPENSSL_OPTIONS no-comp no-des no-dh no-dsa no-ec2m no-engine no-gost "\
OPENSSL_OPTIONS="$OPENSSL_OPTIONS no-http no-idea no-legacy no-md4 no-mdc2 no-multiblock "\
OPENSSL_OPTIONS="$OPENSSL_OPTIONS no-nextprotoneg no-ocb no-ocsp no-rc2 no-rc4 no-rmd160 "\
OPENSSL_OPTIONS="$OPENSSL_OPTIONS no-scrypt no-seed no-siphash no-siv no-sm2 no-sm3 no-sm4 "\
OPENSSL_OPTIONS="$OPENSSL_OPTIONS no-srp no-srtp no-ts no-whirlpool no-apps no-ssl-trace "\
OPENSSL_OPTIONS="$OPENSSL_OPTIONS no-ssl no-ssl3 no-tests -Os" \
cd /build/openssl && \
if [ "$(uname -m)" = "aarch64" ]; then \
./config --prefix=/opt/build $OPENSSL_OPTIONS -mno-outline-atomics ; \
else \
./config --prefix=/opt/build $OPENSSL_OPTIONS ; \
fi && \
mkdir -p /opt/build/lib /opt/build/lib64 && \
make all -j8 && make install_sw && \
cd / && rm -rf /build
# do make
COPY . /build/smartdns/
RUN cd /build/smartdns && \
export CFLAGS="-I /opt/build/include" && \
export LDFLAGS="-L /opt/build/lib -L /opt/build/lib64" && \
export PATH="$HOME/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" && \
rm -fr /build/smartdns/package/*.tar.gz && \
sh ./package/build-pkg.sh --platform linux --arch `dpkg --print-architecture` --with-ui --static && \
\
( cd package && tar -xvf *.tar.gz && chmod a+x smartdns/etc/init.d/smartdns ) && \
\
mkdir -p /release/var/log /release/run /release/var/lib/smartdns && \
cp package/smartdns/etc /release/ -a && \
cp package/smartdns/usr /release/ -a && \
rm -f /release/usr/local/smartdns/lib/libssl* && \
rm -f /release/usr/local/smartdns/lib/libcrypto* && \
cp /opt/build/lib/lib*.so* /release/usr/local/lib/smartdns/lib/ -a 2>/dev/null || true && \
cp /opt/build/lib64/lib*.so* /release/usr/local/lib/smartdns/lib/ -a 2>/dev/null || true && \
cd / && rm -rf /build
FROM busybox:stable-musl
COPY --from=smartdns-builder /release/ /
EXPOSE 53/udp 6080/tcp
VOLUME ["/etc/smartdns/", "/var/lib/smartdns/"]
CMD ["/usr/sbin/smartdns", "-f", "-x"]
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program 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.
This program 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 .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
================================================
FILE: Makefile
================================================
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
PKG_CONFIG := pkg-config
DESTDIR :=
PREFIX := /usr
SBINDIR := $(PREFIX)/sbin
SLIBDIR := $(PREFIX)/lib
SYSCONFDIR := /etc
RUNSTATEDIR := /run
SYSTEMDSYSTEMUNITDIR := $(shell ${PKG_CONFIG} --variable=systemdsystemunitdir systemd)
SMARTDNS_SYSTEMD = systemd/smartdns.service
ifneq ($(strip $(DESTDIR)),)
$(shell mkdir -p $(DESTDIR) -m 0755)
override DESTDIR := $(realpath $(DESTDIR))
endif
PLUGINS :=
WITH_UI ?= 0
ifeq ($(WITH_UI), 1)
PLUGINS += plugin/smartdns-ui
endif
define PLUGINS_TARGETS
$(foreach plugin,$(PLUGINS),$(MAKE) $(MFLAGS) DESTDIR=$(DESTDIR) -C $(plugin) $(1);)
endef
.PHONY: all clean install help SMARTDNS_BIN
all: SMARTDNS_BIN
SMARTDNS_BIN: $(SMARTDNS_SYSTEMD)
$(MAKE) $(MFLAGS) -C src all
$(call PLUGINS_TARGETS, all)
$(SMARTDNS_SYSTEMD): systemd/smartdns.service.in
cp $< $@
sed -i 's|@SBINDIR@|$(SBINDIR)|' $@
sed -i 's|@SYSCONFDIR@|$(SYSCONFDIR)|' $@
sed -i 's|@RUNSTATEDIR@|$(RUNSTATEDIR)|' $@
help:
@echo "Options:"
@echo " WITH_UI=1: Build with smartdns-ui plugin"
@echo " OPTIMIZE_SIZE=1: Optimize size of the smartdns-ui plugin (only for smartdns-ui)"
@echo " DESTDIR: Specify the installation directory prefix"
clean:
$(MAKE) $(MFLAGS) -C src clean
$(RM) $(SMARTDNS_SYSTEMD)
$(call PLUGINS_TARGETS, clean)
install: SMARTDNS_BIN
install -v -m 0640 -D -t $(DESTDIR)$(SYSCONFDIR)/default etc/default/smartdns
install -v -m 0755 -D -t $(DESTDIR)$(SYSCONFDIR)/init.d etc/init.d/smartdns
install -v -m 0640 -D -t $(DESTDIR)$(SYSCONFDIR)/smartdns etc/smartdns/smartdns.conf
install -v -m 0755 -D -t $(DESTDIR)$(SBINDIR) src/smartdns
install -v -m 0644 -D -t $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR) systemd/smartdns.service
$(call PLUGINS_TARGETS, install)
================================================
FILE: ReadMe.md
================================================
# SmartDNS
**[English](ReadMe_en.md)**

SmartDNS 是一个运行在本地的 DNS 服务器,它接受来自本地客户端的 DNS 查询请求,然后从多个上游 DNS 服务器获取 DNS 查询结果,并将访问速度最快的结果返回给客户端,以此提高网络访问速度。
SmartDNS 同时支持指定特定域名 IP 地址,并高性匹配,可达到过滤广告的效果; 支持DOT,DOH,DOQ,DOH3,更好的保护隐私。
与 DNSmasq 的 all-servers 不同,SmartDNS 返回的是访问速度最快的解析结果。
支持树莓派、OpenWrt、华硕路由器原生固件和 Windows 系统等。
## 使用指导
SmartDNS官网:[https://pymumu.github.io/smartdns](https://pymumu.github.io/smartdns)
## 软件效果展示
### 仪表盘

### 速度对比
**阿里 DNS**
使用阿里 DNS 查询百度IP,并检测结果。
```shell
$ nslookup www.baidu.com 223.5.5.5
Server: 223.5.5.5
Address: 223.5.5.5#53
Non-authoritative answer:
www.baidu.com canonical name = www.a.shifen.com.
Name: www.a.shifen.com
Address: 180.97.33.108
Name: www.a.shifen.com
Address: 180.97.33.107
$ ping 180.97.33.107 -c 2
PING 180.97.33.107 (180.97.33.107) 56(84) bytes of data.
64 bytes from 180.97.33.107: icmp_seq=1 ttl=55 time=24.3 ms
64 bytes from 180.97.33.107: icmp_seq=2 ttl=55 time=24.2 ms
--- 180.97.33.107 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 24.275/24.327/24.380/0.164 ms
pi@raspberrypi:~/code/smartdns_build $ ping 180.97.33.108 -c 2
PING 180.97.33.108 (180.97.33.108) 56(84) bytes of data.
64 bytes from 180.97.33.108: icmp_seq=1 ttl=55 time=31.1 ms
64 bytes from 180.97.33.108: icmp_seq=2 ttl=55 time=31.0 ms
--- 180.97.33.108 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 31.014/31.094/31.175/0.193 ms
```
**SmartDNS**
使用 SmartDNS 查询百度 IP,并检测结果。
```shell
$ nslookup www.baidu.com
Server: 192.168.1.1
Address: 192.168.1.1#53
Non-authoritative answer:
www.baidu.com canonical name = www.a.shifen.com.
Name: www.a.shifen.com
Address: 14.215.177.39
$ ping 14.215.177.39 -c 2
PING 14.215.177.39 (14.215.177.39) 56(84) bytes of data.
64 bytes from 14.215.177.39: icmp_seq=1 ttl=56 time=6.31 ms
64 bytes from 14.215.177.39: icmp_seq=2 ttl=56 time=5.95 ms
--- 14.215.177.39 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms
```
从对比看出,SmartDNS 找到了访问 `www.baidu.com` 最快的 IP 地址,比阿里 DNS 速度快了 5 倍。
## 特性
1. **多虚拟DNS服务器**
支持多个虚拟DNS服务器,不同虚拟DNS服务器不同的端口,规则,客户端。
1. **多 DNS 上游服务器**
支持配置多个上游 DNS 服务器,并同时进行查询,即使其中有 DNS 服务器异常,也不会影响查询。
1. **支持每个客户端独立控制**
支持基于MAC,IP地址控制客户端使用不同查询规则,可实现家长控制等功能。
1. **返回最快 IP 地址**
支持从域名所属 IP 地址列表中查找到访问速度最快的 IP 地址,并返回给客户端,提高网络访问速度。
1. **支持多种查询协议**
支持 UDP、TCP、DOT、DOH、DOQ 和 DOH3 查询及服务,以及非 53 端口查询;支持通过socks5,HTTP代理查询;
1. **特定域名 IP 地址指定**
支持指定域名的 IP 地址,达到广告过滤效果、避免恶意网站的效果。
1. **域名高性能后缀匹配**
支持域名后缀匹配模式,简化过滤配置,过滤 20 万条记录时间 < 1ms。
1. **域名分流**
支持域名分流,不同类型的域名向不同的 DNS 服务器查询,支持iptable和nftable更好的分流;支持测速失败的情况下设置域名结果到对应ipset和nftset集合。
1. **Windows / Linux 多平台支持**
支持标准 Linux 系统(树莓派)、OpenWrt 系统各种固件和华硕路由器原生固件。同时还支持 WSL(Windows Subsystem for Linux,适用于 Linux 的 Windows 子系统)。
1. **支持 IPv4、IPv6 双栈**
支持 IPv4 和 IPV 6网络,支持查询 A 和 AAAA 记录,支持双栈 IP 速度优化,并支持完全禁用 IPv6 AAAA 解析。
1. **支持DNS64**
支持DNS64转换。
1. **高性能、占用资源少**
多线程异步 IO 模式,cache 缓存查询结果。
1. **主流系统官方支持**
主流路由系统官方软件源安装smartdns。
## 架构

1. SmartDNS 接收本地网络设备的DNS 查询请求,如 PC、手机的查询请求;
1. 然后将查询请求发送到多个上游 DNS 服务器,可支持 UDP 标准端口或非标准端口查询,以及 TCP 查询;
1. 上游 DNS 服务器返回域名对应的服务器 IP 地址列表,SmartDNS 则会检测从本地网络访问速度最快的服务器 IP;
1. 最后将访问速度最快的服务器 IP 返回给本地客户端。
## 编译
- 代码编译:
SmartDNS 提供了编译软件包的脚本(`package/build-pkg.sh`),支持编译 LuCI、Debian、OpenWrt 和 Optware 安装包。
- 文档编译:
文档分支为`doc`,安装`mkdocs`工具后,执行`mkdocs build`编译。
## 捐赠
如果你觉得此项目对你有帮助,请捐助我们,使项目能持续发展和更加完善。
### PayPal 贝宝
[](https://paypal.me/PengNick/)
### AliPay 支付宝

### WeChat Pay 微信支付

## 开源声明
SmartDNS 基于 GPL V3 协议开源。
================================================
FILE: ReadMe_en.md
================================================
# SmartDNS

SmartDNS is a local DNS server. SmartDNS accepts DNS query requests from local clients, obtains DNS query results from multiple upstream DNS servers, and returns the fastest access results to clients. supports secure DNS protocols like DoT, DoH, DoQ, DoH3, better protect privacy,
Avoiding DNS pollution and improving network access speed, supports high-performance ad filtering.
Unlike dnsmasq's all-servers, smartdns returns the fastest access resolution.
Support Raspberry Pi, openwrt, ASUS router, Windows and other devices.
## Usage
Please visit website: [https://pymumu.github.io/smartdns](https://pymumu.github.io/smartdns/en)
## Software Show
### Dashboard

### Speed Comparison
**Ali DNS**
Use Ali DNS to query Baidu's IP and test the results.
```shell
pi@raspberrypi:~/code/smartdns_build $ nslookup www.baidu.com 223.5.5.5
Server: 223.5.5.5
Address: 223.5.5.5#53
Non-authoritative answer:
www.baidu.com canonical name = www.a.shifen.com.
Name: www.a.shifen.com
Address: 180.97.33.108
Name: www.a.shifen.com
Address: 180.97.33.107
pi@raspberrypi:~/code/smartdns_build $ ping 180.97.33.107 -c 2
PING 180.97.33.107 (180.97.33.107) 56(84) bytes of data.
64 bytes from 180.97.33.107: icmp_seq=1 ttl=55 time=24.3 ms
64 bytes from 180.97.33.107: icmp_seq=2 ttl=55 time=24.2 ms
--- 180.97.33.107 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 24.275/24.327/24.380/0.164 ms
pi@raspberrypi:~/code/smartdns_build $ ping 180.97.33.108 -c 2
PING 180.97.33.108 (180.97.33.108) 56(84) bytes of data.
64 bytes from 180.97.33.108: icmp_seq=1 ttl=55 time=31.1 ms
64 bytes from 180.97.33.108: icmp_seq=2 ttl=55 time=31.0 ms
--- 180.97.33.108 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 31.014/31.094/31.175/0.193 ms
```
**smartdns**
Use SmartDNS to query Baidu IP and test the results.
```shell
pi@raspberrypi:~/code/smartdns_build $ nslookup www.baidu.com
Server: 192.168.1.1
Address: 192.168.1.1#53
Non-authoritative answer:
www.baidu.com canonical name = www.a.shifen.com.
Name: www.a.shifen.com
Address: 14.215.177.39
pi@raspberrypi:~/code/smartdns_build $ ping 14.215.177.39 -c 2
PING 14.215.177.39 (14.215.177.39) 56(84) bytes of data.
64 bytes from 14.215.177.39: icmp_seq=1 ttl=56 time=6.31 ms
64 bytes from 14.215.177.39: icmp_seq=2 ttl=56 time=5.95 ms
--- 14.215.177.39 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms
```
From the comparison, smartdns found the fastest IP address to visit www.baidu.com, so accessing Baidu's DNS is 5 times faster than Ali DNS.
## Features
1. **Multiple Virtual DNS server**
Support multiple virtual DNS servers with different ports, rules, and clients.
1. **Multiple upstream DNS servers**
Support configuring multiple upstream DNS servers and query at the same time.the query will not be affected, Even if there is a DNS server exception.
1. **Support per-client query control**
Support controlling clients using different query rules based on MAC and IP addresses, enabling features such as parental control.
1. **Return the fastest IP address**
Support finding the fastest access IP address from the IP address list of the domain name and returning it to the client to avoid DNS pollution and improve network access speed.
1. **Support for multiple query protocols**
Support UDP, TCP, DOT(DNS over TLS), DOH(DNS over HTTPS), DOQ(DNS over Quic), DOH3(DNS over HTTP3) queries and service, and non-53 port queries, effectively avoiding DNS pollution and protect privacy, and support query DNS over socks5, http proxy.
1. **Domain IP address specification**
Support configuring IP address of specific domain to achieve the effect of advertising filtering, and avoid malicious websites.
1. **Domain name high performance rule filtering**
Support domain name suffix matching mode, simplify filtering configuration, filter 200,000 recording and take time <1ms.
1. **Linux/Windows multi-platform support**
Support standard Linux system (Raspberry Pi), openwrt system various firmware, ASUS router native firmware. Support Windows 10 WSL (Windows Subsystem for Linux).
1. **Support IPV4, IPV6 dual stack**
Support IPV4, IPV6 network, support query A, AAAA record, dual-stack IP selection, and filter IPV6 AAAA record.
1. **DNS64**
Support DNS64 translation.
1. **High performance, low resource consumption**
Multi-threaded asynchronous IO mode, cache cache query results.
1. **DNS domain forwarding**
Support DNS forwarding, ipset and nftables. Support setting the domain result to ipset and nftset set when speed check fails.
## Architecture

1. SmartDNS receives DNS query requests from local network devices, such as PCs and mobile phone query requests.
1. SmartDNS sends query requests to multiple upstream DNS servers, using standard UDP queries, non-standard port UDP queries, and TCP queries.
1. The upstream DNS server returns a list of Server IP addresses corresponding to the domain name. SmartDNS detects the fastest Server IP with local network access.
1. Return the fastest accessed Server IP to the local client.
## Compile
smartdns contains scripts for compiling packages, supports compiling luci, debian, openwrt, optware installation packages, and can execute `package/build-pkg.sh` compilation.
## [Donate](#donate)
If you feel that this project is helpful to you, please donate to us so that the project can continue to develop and be more perfect.
### PayPal
[](https://paypal.me/PengNick/)
### Alipay

### Wechat

## Open Source License
Smartdns is licensed to the public under the GPL V3 License.
================================================
FILE: etc/default/smartdns
================================================
# Default settings for smartdns server. This file is sourced by /bin/sh from
# /etc/init.d/smartdns.
# Options to pass to smartdns
SMART_DNS_OPTS=
================================================
FILE: etc/init.d/smartdns
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
### BEGIN INIT INFO
# Provides: smartdns
# Required-Start: $network
# Required-Stop: $network
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: Start smartdns server
### END INIT INFO
PATH=/sbin:/bin:/usr/sbin:/usr/bin
. /etc/default/smartdns
SMARTDNS=/usr/sbin/smartdns
PIDFILE=/run/smartdns.pid
if [ ! -d "/run" ]; then
PIDFILE=/var/run/smartdns.pid
fi
test -x $SMARTDNS || exit 5
case $1 in
start)
$SMARTDNS "$SMART_DNS_OPTS" -R
while true; do
if [ -e "$PIDFILE" ]; then
break;
fi
sleep .5
done
PID="$(cat $PIDFILE 2>/dev/null)"
if [ -z "$PID" ]; then
echo "start smartdns server failed."
exit 1
fi
if [ ! -e "/proc/$PID" ]; then
echo "start smartdns server failed."
exit 1
fi
echo "start smartdns server success."
;;
stop)
if [ ! -f "$PIDFILE" ]; then
echo "smartdns server is stopped."
exit 0
fi
PID="$(cat $PIDFILE 2>/dev/null)"
if [ ! -e "/proc/$PID" ] || [ -z "$PID" ]; then
echo "smartdns server is stopped"
exit 0
fi
kill -TERM "$PID"
if [ $? -ne 0 ]; then
echo "Stop smartdns server failed."
exit 1;
fi
LOOP=1
while true; do
if [ ! -d "/proc/$PID" ]; then
break;
fi
if [ $LOOP -gt 12 ]; then
kill -9 "$PID"
break;
fi
LOOP=$((LOOP+1))
sleep .5
done
echo "Stop smartdns server success."
;;
restart)
"$0" stop && "$0" start
;;
status)
PID="$(cat "$PIDFILE" 2>/dev/null)"
if [ ! -e "/proc/$PID" ] || [ -z "$PID" ]; then
echo "smartdns server is not running."
exit 1
fi
echo "smartdns server is running."
status=0
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 2
;;
esac
exit $status
================================================
FILE: etc/smartdns/smartdns.conf
================================================
# dns server name, default is host name
# server-name,
# example:
# server-name smartdns
#
# whether resolv local hostname to ip address
# resolv-hostname yes
# dns server run user
# user [username]
# example: run as nobody
# user nobody
#
# Include another configuration options, if -group is specified, only include the rules to specified group.
# conf-file [file] [-group group-name]
# conf-file blacklist-ip.conf
# conf-file whitelist-ip.conf -group office
# conf-file *.conf
# dns server bind ip and port, default dns server port is 53, support binding multi ip and port
# bind udp server
# bind [IP]:[port][@device] [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]
# bind tcp server
# bind-tcp [IP]:[port][@device] [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]
# bind tls server
# bind-tls [IP]:[port][@device] [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]
# bind-cert-key-file [path to file]
# tls private key file
# bind-cert-file [path to file]
# tls cert file
# bind-cert-key-pass [password]
# tls private key password
# bind-https server
# bind-https [IP]:[port][@device] [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]
# option:
# -group: set domain request to use the appropriate server group.
# -no-rule-addr: skip address rule.
# -no-rule-nameserver: skip nameserver rule.
# -no-rule-ipset: skip ipset rule or nftset rule.
# -no-speed-check: do not check speed.
# -no-cache: skip cache.
# -no-rule-soa: Skip address SOA(#) rules.
# -no-dualstack-selection: Disable dualstack ip selection.
# -no-ip-alias: ignore ip alias.
# -force-aaaa-soa: force AAAA query return SOA.
# -force-https-soa: force HTTPS query return SOA.
# -no-serve-expired: no serve expired.
# -no-rules: skip all rules.
# -ipset ipsetname: use ipset rule.
# -nftset nftsetname: use nftset rule.
# -ddr: enable ddr.
# example:
# IPV4:
# bind :53
# bind :53@eth0
# bind :6053 -group office -no-speed-check
# IPV6:
# bind [::]:53
# bind [::]:53@eth0
# bind-tcp [::]:53
bind [::]:53
# tcp connection idle timeout
# tcp-idle-time [second]
# dns cache size
# cache-size [number]
# 0: for no cache
# -1: auto set cache size
# cache-size 32768
# dns cache memory size
# cache-mem-size [size]
# enable persist cache when restart
# cache-persist no
# cache persist file
# cache-file /var/cache/smartdns.cache
# cache persist time
# cache-checkpoint-time [second]
# cache-checkpoint-time 86400
# prefetch domain
# prefetch-domain [yes|no]
# prefetch-domain yes
# cache serve expired
# serve-expired [yes|no]
# serve-expired yes
# cache serve expired TTL
# serve-expired-ttl [num]
# serve-expired-ttl 86400
# reply TTL value to use when replying with expired data
# serve-expired-reply-ttl [num]
# serve-expired-reply-ttl 3
# List of hosts that supply bogus NX domain results
# bogus-nxdomain [ip/subnet]
# List of IPs that will be filtered when nameserver is configured -blacklist-ip parameter
# blacklist-ip [ip/subnet]
# List of IPs that will be accepted when nameserver is configured -whitelist-ip parameter
# whitelist-ip [ip/subnet]
# List of IPs that will be ignored
# ignore-ip [ip/subnet]
# alias of IPs
# ip-alias [ip/subnet] [ip1[,ip2]...]
# ip-alias 192.168.0.1/24 10.9.0.1,10.9.0.2
# speed check mode
# speed-check-mode [ping|tcp:port|tcp-syn:port|none|,]
# example:
# speed-check-mode ping,tcp:80,tcp:443
# speed-check-mode tcp:443,ping
# speed-check-mode tcp-syn:80,tcp-syn:443
# speed-check-mode none
# force AAAA query return SOA
# force-AAAA-SOA [yes|no]
# force specific qtype return soa
# force-qtype-SOA [-,][qtypeid |...]
# force-qtype-SOA [qtypeid|start_id-end_id|,...]
# force-qtype-SOA 65 28 add type 65,28
# force-qtype-SOA 65,28 add type 65,28
# force-qtype-SOA 65-68 add type 65-68
# force-qtype-SOA -,65-68, clear type 65-68
# force-qtype-SOA - clear all type
# force-qtype-SOA 65
# Enable IPV4, IPV6 dual stack IP optimization selection strategy
# dualstack-ip-selection-threshold [num] (0~1000)
# dualstack-ip-allow-force-AAAA [yes|no]
# dualstack-ip-selection [yes|no]
# dualstack-ip-selection no
# edns client subnet
# edns-client-subnet [ip/subnet]
# edns-client-subnet 192.168.1.1/24
# edns-client-subnet 8::8/56
# ttl for all resource record
# rr-ttl: ttl for all record
# rr-ttl-min: minimum ttl for resource record
# rr-ttl-max: maximum ttl for resource record
# rr-ttl-reply-max: maximum reply ttl for resource record
# example:
# rr-ttl 300
# rr-ttl-min 60
# rr-ttl-max 86400
# rr-ttl-reply-max 60
# Maximum number of IPs returned to the client|8|number of IPs, 1~16
# example:
# max-reply-ip-num 1
# Maximum number of queries per second|0|number of queries, 0 means no limit.
# example:
# max-query-limit 65535
# response mode
# response-mode [first-ping|fastest-ip|fastest-response]
# set log level
# log-level: [level], level=off, fatal, error, warn, notice, info, debug
# log-file: file path of log file.
# log-console [yes|no]: output log to console.
# log-syslog [yes|no]: output log to syslog.
# log-size: size of each log file, support k,m,g
# log-num: number of logs, 0 means disable log
log-level info
# log-file /var/log/smartdns/smartdns.log
# log-size 128k
# log-num 2
# log-file-mode [mode]: file mode of log file.
# dns audit
# audit-enable [yes|no]: enable or disable audit.
# audit-enable yes
# audit-SOA [yes|no]: enable or disable log soa result.
# audit-size size of each audit file, support k,m,g
# audit-file /var/log/smartdns/smartdns-audit.log
# audit-console [yes|no]: output audit log to console.
# audit-syslog [yes|no]: output audit log to syslog.
# audit-file-mode [mode]: file mode of audit file.
# audit-size 128k
# audit-num 2
# Support reading dnsmasq dhcp file to resolve local hostname
# dnsmasq-lease-file /var/lib/misc/dnsmasq.leases
# certificate file
# ca-file [file]
# ca-file /etc/ssl/certs/ca-certificates.crt
# certificate path
# ca-path [path]
# ca-path /etc/ssl/certs
# remote udp dns server list
# server [IP]:[PORT]|URL [-blacklist-ip] [-whitelist-ip] [-check-edns] [-group [group] ...] [-exclude-default-group]
# default port is 53
# -blacklist-ip: filter result with blacklist ip
# -whitelist-ip: filter result with whitelist ip, result in whitelist-ip will be accepted.
# -check-edns: result must exist edns RR, or discard result.
# g|-group [group]: set server to group, use with nameserver /domain/group.
# e|-exclude-default-group: exclude this server from default group.
# p|-proxy [proxy-name]: use proxy to connect to server.
# b|-bootstrap-dns: set as bootstrap dns server.
# -set-mark: set mark on packets.
# -subnet [ip/subnet]: set edns client subnet.
# -host-ip [ip]: set dns server host ip.
# -interface [interface]: set dns server interface.
# -fallback: set as fallback dns server.
# server 8.8.8.8 -blacklist-ip -check-edns -group g1 -group g2
# server tls://dns.google:853
# server quic://dns.gooel.com:443
# server https://dns.google/dns-query
# remote tcp dns server list
# server-tcp [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-group [group] ...] [-exclude-default-group]
# default port is 53
# server-tcp 8.8.8.8
# remote tls dns server list
# server-tls [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]
# -spki-pin: TLS spki pin to verify.
# -tls-host-verify: cert hostname to verify.
# -host-name: TLS sni hostname.
# k|-no-check-certificate: no check certificate.
# p|-proxy [proxy-name]: use proxy to connect to server.
# -bootstrap-dns: set as bootstrap dns server.
# Get SPKI with this command:
# echo | openssl s_client -connect '[ip]:853' | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
# default port is 853
# server-tls 8.8.8.8
# server-tls 1.0.0.1
# remote quic dns server list
# server-quic [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]
# -spki-pin: TLS spki pin to verify.
# -tls-host-verify: cert hostname to verify.
# -host-name: TLS sni hostname.
# k|-no-check-certificate: no check certificate.
# p|-proxy [proxy-name]: use proxy to connect to server.
# -bootstrap-dns: set as bootstrap dns server.
# Get SPKI with this command:
# echo | openssl s_client -quic -alpn doq -connect '[ip]:853' | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
# default port is 853
# server-quic 223.5.5.5
# remote http3 dns server list
# server-http3 [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]
# server-h3 [IP]:[PORT] [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]
# -spki-pin: TLS spki pin to verify.
# -tls-host-verify: cert hostname to verify.
# -host-name: TLS sni hostname.
# k|-no-check-certificate: no check certificate.
# p|-proxy [proxy-name]: use proxy to connect to server.
# -bootstrap-dns: set as bootstrap dns server.
# Get SPKI with this command:
# echo | openssl s_client -quic -alpn doq -connect '[ip]:853' | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
# default port is 443
# server-http3 https://223.5.5.5/dns-query
# server-h3 h3://223.5.5.5/dns-query
# remote https dns server list
# server-https https://[host]:[port]/path [-blacklist-ip] [-whitelist-ip] [-spki-pin [sha256-pin]] [-group [group] ...] [-exclude-default-group]
# -spki-pin: TLS spki pin to verify.
# -tls-host-verify: cert hostname to verify.
# -host-name: TLS sni hostname.
# -http-host: http host.
# k|-no-check-certificate: no check certificate.
# p|-proxy [proxy-name]: use proxy to connect to server.
# -bootstrap-dns: set as bootstrap dns server.
# default port is 443
# server-https https://cloudflare-dns.com/dns-query
# socks5 and http proxy list
# proxy-server URL -name [proxy name]
# URL: socks5://[username:password@]host:port
# http://[username:password@]host:port
# -name: proxy name, use with server -proxy [proxy-name]
# example:
# proxy-server socks5://user:pass@1.2.3.4:1080 -name proxy
# proxy-server http://user:pass@1.2.3.4:3128 -name proxy
# specific nameserver to domain
# nameserver [/domain/][group|-]
# nameserer group, set the domain name to use the appropriate server group.
# nameserver /www.example.com/office, Set the domain name to use the appropriate server group.
# nameserver /www.example.com/-, ignore this domain
# expand ptr record from address record
# expand-ptr-from-address yes
# specific address to domain
# address [/domain/][ip1,ip2|-|-4|-6|#|#4|#6]
# address #, block all A and AAAA request.
# address #6, block all AAAA request.
# address -6, allow all AAAA request.
# address /www.example.com/1.2.3.4, return ip 1.2.3.4 to client
# address /www.example.com/1.2.3.4,5.6.7.8, return multiple ip addresses
# address /www.example.com/-, ignore address, query from upstream, suffix 4, for ipv4, 6 for ipv6, none for all
# address /www.example.com/#, return SOA to client, suffix 4, for ipv4, 6 for ipv6, none for all
# specific cname to domain
# cname /domain/target
# add srv record, support multiple srv record.
# srv-record /domain/[target][,port][,priority][,weight]
# srv-record /_ldap._tcp.example.com/ldapserver.example.com,389
# srv-record /_ldap._tcp.example.com/
# https-record /domain/[target=][,port=][,priority=][,alph=][,ech=][,ipv4hint=][,ipv6hint=][,noiphint]
# https-record noipv4hint,noipv6hint,noech
# https-record /www.example.com/ipv4hint=192.168.1.2
# enable DNS64 feature
# dns64 [ip/subnet]
# dns64 64:ff9b::/96
# enable ipset timeout by ttl feature
# ipset-timeout [yes]
# specific ipset to domain
# ipset [/domain/][ipsetname|#4:v4setname|#6:v6setname|-|#4:-|#6:-]
# ipset [ipsetname|#4:v4setname|#6:v6setname], set global ipset.
# ipset /www.example.com/block, set ipset with ipset name of block.
# ipset /www.example.com/-, ignore this domain.
# ipset ipsetname, set global ipset.
# add to ipset when ping is unreachable
# ipset-no-speed ipsetname
# ipset-no-speed pass
# enable nftset timeout by ttl feature
# nftset-timeout [yes|no]
# nftset-timeout yes
# add to nftset when ping is unreachable
# nftset-no-speed [#4:ip#table#set,#6:ipv6#table#setv6]
# nftset-no-speed #4:ip#table#set
# enable nftset debug, check nftset setting result, output log when error.
# nftset-debug [yes|no]
# nftset-debug yes
# specific nftset to domain
# nftset [/domain/][#4:ip#table#set,#6:ipv6#table#setv6]
# nftset [#4:ip#table#set,#6:ipv6#table#setv6] set global nftset.
# nftset /www.example.com/ip#table#set, equivalent to 'nft add element ip table set { ... }'
# nftset /www.example.com/-, ignore this domain
# nftset /www.example.com/#6:-, ignore ipv6
# nftset #6:ip#table#set, set global nftset.
# set ddns domain
# ddns-domain domain
# set local domain
# local-domain domain
# lookup local network hostname or ip address from mdns
# mdns-lookup [yes|no]
# mdns-lookup no
# set hosts file
# hosts-file [file]
# set domain rules
# domain-rules /domain/ [-speed-check-mode [...]]
# rules:
# [-c] -speed-check-mode [mode]: speed check mode
# speed-check-mode [ping|tcp:port|none|,]
# [-a] -address [address|-]: same as address option
# [-n] -nameserver [group|-]: same as nameserver option
# [-p] -ipset [ipset|-]: same as ipset option
# [-t] -nftset [nftset|-]: same as nftset option
# [-d] -dualstack-ip-selection [yes|no]: same as dualstack-ip-selection option
# [-g|-group group-name]: set domain-rules to group.
# -no-serve-expired: ignore expired domain
# -delete: delete domain rule
# -no-ip-alias: ignore ip alias
# -no-cache: ignore cache
# collection of domains
# the domain-set can be used with /domain/ for address, nameserver, ipset, etc.
# domain-set -name [set-name] -type list -file [/path/to/file]
# [-n] -name [set name]: domain set name
# [-t] -type [list]: domain set type, list only now
# [-f] -file [path/to/set]: file path of domain set
#
# example:
# domain-set -name domain-list -type list -file /etc/smartdns/domain-list.conf
# address /domain-set:domain-list/1.2.3.4
# nameserver /domain-set:domain-list/server-group
# ipset /domain-set:domain-list/ipset
# domain-rules /domain-set:domain-list/ -speed-check-mode ping
# set ip rules
# ip-rules ip-cidrs [-ip-alias [...]]
# rules:
# [-c] -ip-alias [ip1,ip2]: same as ip-alias option
# [-a] -whitelist-ip: same as whitelist-ip option
# [-n] -blacklist-ip: same as blacklist-ip option
# [-p] -bogus-nxdomain: same as bogus-nxdomain option
# [-t] -ignore-ip: same as ignore-ip option
# collection of IPs
# the ip-set can be used with /ip-cidr/ for ip-alias, ignore-ip, etc.
# ip-set -name [set-name] -type list -file [/path/to/file]
# [-n] -name [set name]: ip set name
# [-t] -type [list]: ip set type, list only now
# [-f] -file [path/to/set]: file path of ip set
#
# example:
# ip-set -name ip-list -file /etc/smartdns/ip-list.conf
# bogus-nxdomain ip-set:ip-list
# ip-alias ip-set:ip-list 1.2.3.4
# ip-alias ip-set:ip-list ip-set:ip-map-list
# set client rules
# client-rules [ip-cidr|mac|ip-set] [-group [group]] [-no-rule-addr] [-no-rule-nameserver] [-no-rule-ipset] [-no-speed-check] [-no-cache] [-no-rule-soa] [-no-dualstack-selection]
# client-rules option is same as bind option, please see bind option for detail.
# set group rules
# group-begin [group-name] [-inherit parent-group|none|default]
# group-match [-g|group group-name] [-domain domain] [-client-ip [ip-cidr|mac|ip-set]]
# group-end
# data directory
# data-dir [path]
# data-dir /var/lib/smartdns
# load plugin
# plugin [path/to/file] [args]
# web ui plugin
# plugin smartdns_ui.so
# smartdns-ui.www-root /usr/share/smartdns/wwwroot
# smartdns-ui.ip http://0.0.0.0:6080
# smartdns-ui.ip https://[::]:6080
# smartdns-ui.token-expire 600
# smartdns-ui.max-query-log-age 86400
# smartdns-ui.enable-terminal yes
# smartdns-ui.enable-cors yes
# smartdns-ui.user admin
# smartdns-ui.password password
================================================
FILE: package/build-pkg.sh
================================================
#!/bin/sh
# Copyright (C) 2018-2025 Nick Peng (pymumu@gmail.com)
CURR_DIR=$(cd $(dirname $0);pwd)
WORKDIR=$CURR_DIR/target
VER="`date +"1.%Y.%m.%d-%H%M"`"
CODE_DIR="$CURR_DIR/.."
IS_BUILD_SMARTDNS=1
OUTPUTDIR=$CURR_DIR
SMARTDNS_WEBUI_URL="https://github.com/pymumu/smartdns-webui/archive/refs/heads/main.zip"
SMARTDNS_WEBUI_SOURCE="$WORKDIR/smartdns-webui"
SMARTDNS_STATIC_DIR="$WORKDIR/smartdns-static"
SMARTDNS_WITH_LIBS=0
MAKE_NJOBS=1
export CC
export STRIP
export WORKDIR
WITH_UI=0
showhelp()
{
echo "Usage: $0 [OPTION]"
echo "Options:"
echo " --platform [luci|luci-compat|debian|openwrt|optware|linux] build for platform. "
echo " --arch [all|armhf|arm64|x86-64|...] build for architecture, e.g. "
echo " --cross-tool [cross-tool] cross compiler, e.g. mips-openwrt-linux-"
echo " --with-ui build with smartdns-ui plugin."
echo ""
echo "Advance Options:"
echo " --static static link smartdns"
echo " --only-package only package, not build source"
echo " --filearch [arch] output file arch, default: equal --arch"
echo " --outputdir [dir] output package to specific directory"
echo " "
echo "Example:"
echo " build luci:"
echo " $0 --platform luci"
echo " build luci:"
echo " $0 --platform luci-compat"
echo " build debian:"
echo " $0 --platform debian --arch x86-64"
echo " build raspbian pi:"
echo " $0 --platform debian --arch arm64 --with-ui"
echo " build optware mips:"
echo " $0 --platform optware --arch mipsbig"
echo " build openwrt mips:"
echo " $0 --platform openwrt --arch mips"
echo " build generic linux:"
echo " $0 --platform linux --arch x86-64 --with-ui"
}
init_env()
{
if [ -z "$CC" ]; then
CC=gcc
fi
MAKE_NJOBS=$(grep processor /proc/cpuinfo | wc -l 2>/dev/null || echo 1)
export MAKE_NJOBS
mkdir -p $WORKDIR
if [ $? -ne 0 ]; then
echo "create work directory failed"
return 1
fi
if [ "$STATIC" = "yes" ] && [ $WITH_UI -eq 1 ]; then
SMARTDNS_WITH_LIBS=1
fi
check_cc="`echo "$CC" | grep -E "(\-gcc|\-cc)"`"
if [ ! -z "$check_cc" ]; then
TARGET_ARCH="`$CC -dumpmachine`"
echo "target arch: $TARGET_ARCH"
fi
if [ $SMARTDNS_WITH_LIBS -eq 1 ]; then
case "$TARGET_ARCH" in
*arm*)
NEED_UPDATE_ARM_CP15=1
echo "Update arm cp15"
;;
*)
;;
esac
LINKER_NAME=`$CC -Xlinker -v 2>&1 | grep -oP '(?<=-dynamic-linker )[^ ]+'`
if [ -z "$LINKER_NAME" ]; then
echo "get linker name failed"
return 1
fi
LINKER_NAME=`basename $LINKER_NAME`
LINKER_SYSROOT="`$CC --print-sysroot`"
export BINDGEN_EXTRA_CLANG_ARGS="--sysroot=$LINKER_SYSROOT"
echo "linker name: $LINKER_NAME"
fi
}
copy_smartdns_libs()
{
SMARTDNS_BIN="$CODE_DIR/src/smartdns"
copy_libs_recursive $SMARTDNS_BIN
if [ $? -ne 0 ]; then
echo "copy libs failed"
return 1
fi
LIB_WEBUI_SO="$CODE_DIR/plugin/smartdns-ui/target/smartdns_ui.so"
copy_libs_recursive $LIB_WEBUI_SO
if [ $? -ne 0 ]; then
echo "copy libs failed"
return 1
fi
}
copy_libs_recursive()
{
local lib=$1
local lib_path=`$CC -print-file-name=$lib`
if [ -z "$lib_path" ]; then
return 0
fi
if [ -e $SMARTDNS_STATIC_DIR/lib/$lib ]; then
return 0
fi
local tmp_path="`echo "$lib_path" | grep "libc.so"`"
if [ ! -z "$tmp_path" ]; then
LIBC_PATH="$tmp_path"
fi
if [ "$lib" != "$SMARTDNS_BIN" ]; then
echo "copy $lib_path to $SMARTDNS_STATIC_DIR/lib"
cp $lib_path $SMARTDNS_STATIC_DIR/lib
if [ $? -ne 0 ]; then
echo "copy $lib failed"
return 1
fi
fi
local shared_libs="`objdump -p $lib_path | grep NEEDED | awk '{print $2}'`"
for sub_lib in $shared_libs; do
copy_libs_recursive $sub_lib
if [ $? -ne 0 ]; then
return 1
fi
done
return 0
}
copy_linker()
{
LINK_PATH=`$CC -print-file-name=$LINKER_NAME`
SYM_LINKER_NAME=`readlink -f $LINK_PATH`
echo "linker: $LINK_PATH"
echo "sym linker: $SYM_LINKER_NAME"
echo "libc: $LIBC_PATH"
if [ "$SYM_LINKER_NAME" = "$LIBC_PATH" ]; then
ln -f -s $(basename $LIBC_PATH) $SMARTDNS_STATIC_DIR/lib/$(basename $LINKER_NAME)
else
cp $LINK_PATH $SMARTDNS_STATIC_DIR/lib -af
if [ $? -ne 0 ]; then
echo "copy $lib failed"
return 1
fi
SYM_LINKER_NAME=`readlink $SMARTDNS_STATIC_DIR/lib/$LINKER_NAME`
if [ ! -e $SMARTDNS_STATIC_DIR/lib/$SYM_LINKER_NAME ]; then
SYM_LINKER_NAME=`basename $SYM_LINKER_NAME`
ln -f -s $SYM_LINKER_NAME $SMARTDNS_STATIC_DIR/lib/$LINKER_NAME
fi
fi
ln -f -s ${LINKER_NAME} ${SMARTDNS_STATIC_DIR}/lib/ld-linux.so
if [ $? -ne 0 ]; then
echo "copy $lib failed"
return 1
fi
return 0
}
build_smartdns()
{
MAKE_WITH_UI=""
if [ $WITH_UI -eq 1 ]; then
MAKE_WITH_UI="WITH_UI=1 OPTIMIZE_SIZE=1"
fi
if [ "$PLATFORM" = "luci" ]; then
return 0
fi
make -C $CODE_DIR clean $MAKE_ARGS
if [ $SMARTDNS_WITH_LIBS -eq 1 ]; then
LINK_LDFLAGS='-Wl,-dynamic-linker,'lib/$(echo $LINKER_NAME)' -Wl,-rpath,\$$ORIGIN:\$$ORIGIN/lib'
export LDFLAGS="$LDFLAGS $LINK_LDFLAGS"
echo "LDFLAGS: $LDFLAGS"
RUSTFLAGS='-C link-arg=-Wl,-rpath,$$ORIGIN'
echo "Building smartdns with specific linker..."
unset STATIC
fi
RUSTFLAGS="$RUSTFLAGS" make -C $CODE_DIR $MAKE_WITH_UI all -j$MAKE_NJOBS VER=$VER $MAKE_ARGS
if [ $? -ne 0 ]; then
echo "make smartdns failed"
exit 1
fi
$STRIP -d $CODE_DIR/src/smartdns >/dev/null 2>&1
rm -fr $SMARTDNS_STATIC_DIR
if [ $SMARTDNS_WITH_LIBS -eq 0 ]; then
return 0;
fi
echo "copy smartdns binary to $SMARTDNS_STATIC_DIR"
mkdir -p $SMARTDNS_STATIC_DIR/lib
if [ $? -ne 0 ]; then
echo "create target directory failed"
return 1
fi
cp $CODE_DIR/src/smartdns $SMARTDNS_STATIC_DIR/
if [ $? -ne 0 ]; then
echo "copy smartdns binary failed"
return 1
fi
cp $CURR_DIR/run-smartdns $SMARTDNS_STATIC_DIR
chmod +x $SMARTDNS_STATIC_DIR/run-smartdns
if [ "$NEED_UPDATE_ARM_CP15" = "1" ]; then
sed -i 's/NEED_CHECK_ARM_CP15=0/NEED_CHECK_ARM_CP15=1/' $SMARTDNS_STATIC_DIR/run-smartdns
if [ $? -ne 0 ]; then
echo "sed run-smartdns failed"
return 1
fi
fi
copy_smartdns_libs
if [ $? -ne 0 ]; then
echo "copy smartdns libs failed"
return 1
fi
rm $SMARTDNS_STATIC_DIR/lib/smartdns_ui.so >/dev/null 2>&1
copy_linker
if [ $? -ne 0 ]; then
echo "copy linker failed"
return 1
fi
return 0
}
build_webpages()
{
if [ ! -f "$WORKDIR/smartdns-webui.zip" ]; then
echo "smartdns-webui source not found, downloading..."
wget -O $WORKDIR/smartdns-webui.zip $SMARTDNS_WEBUI_URL
if [ $? -ne 0 ]; then
echo "Failed to download smartdns-webui source at $SMARTDNS_WEBUI_URL"
return 1
fi
fi
if [ ! -d "$SMARTDNS_WEBUI_SOURCE" ]; then
echo "smartdns-webui source not found, unzipping..."
unzip -q $WORKDIR/smartdns-webui.zip -d $WORKDIR
if [ $? -ne 0 ]; then
echo "Failed to unzip smartdns-webui source."
return 1
fi
mv $WORKDIR/smartdns-webui-main $SMARTDNS_WEBUI_SOURCE
if [ $? -ne 0 ]; then
echo "Failed to rename smartdns-webui directory."
return 1
fi
fi
if [ ! -d "$SMARTDNS_WEBUI_SOURCE" ]; then
echo "smartdns-webui source not found."
return 1
fi
if [ ! -f "$SMARTDNS_WEBUI_SOURCE/package.json" ]; then
echo "smartdns-webui source is not valid."
return 1
fi
if [ -f "$SMARTDNS_WEBUI_SOURCE/out/index.html" ]; then
echo "smartdns-webui already built, skipping build."
return 0
fi
echo "Building smartdns-webui..."
npm install --prefix $SMARTDNS_WEBUI_SOURCE
if [ $? -ne 0 ]; then
echo "Failed to install smartdns-webui dependencies."
return 1
fi
npm run build --prefix $SMARTDNS_WEBUI_SOURCE
if [ $? -ne 0 ]; then
echo "Failed to build smartdns-webui."
return 1
fi
echo "smartdns-webui build completed."
return 0
}
build()
{
echo "build package for $PLATFORM"
if [ $IS_BUILD_SMARTDNS -eq 1 ]; then
build_smartdns
if [ $? -ne 0 ]; then
return 1
fi
fi
WITH_UI_ARGS=""
if [ $WITH_UI -eq 1 ] && [ "$PLATFORM" != "luci" ]; then
build_webpages
if [ $? -ne 0 ]; then
echo "build smartdns-ui failed"
return 1
fi
WITH_UI_ARGS="--with-ui"
fi
chmod +x $CODE_DIR/package/$PLATFORM/make.sh
$CODE_DIR/package/$PLATFORM/make.sh -o $CURR_DIR --arch $ARCH --ver $VER --filearch $FILEARCH $WITH_UI_ARGS -o $OUTPUTDIR
if [ $? -ne 0 ]; then
echo "build package for $PLATFORM failed"
return 1
fi
echo "build package for $PLATFORM success."
return 0
}
main()
{
OPTS=`getopt -o o:h --long arch:,filearch:,ver:,platform:,cross-tool:,with-nftables,static,only-package,with-ui,outputdir: \
-n "" -- "$@"`
if [ "$#" -le "1" ]; then
showhelp
exit 1
fi
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$OPTS"
while true; do
case "$1" in
--arch)
ARCH="$2"
shift 2;;
--filearch)
FILEARCH="$2"
shift 2;;
--platform)
PLATFORM="$2"
shift 2;;
--cross-tool)
CROSS_TOOL="$2"
shift 2;;
--static)
export STATIC="yes"
shift 1;;
--only-package)
IS_BUILD_SMARTDNS=0
shift 1;;
--outputdir)
OUTPUTDIR="$2"
shift 2;;
--with-ui)
WITH_UI=1
shift 1;;
--ver)
VER="$2"
shift 2;;
-h | --help )
showhelp
return 0
shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if [ -z "$PLATFORM" ]; then
echo "please input platform"
echo "run $0 -h for help."
return 1
fi
if [ "$PLATFORM" = "luci" ]; then
ARCH="all"
fi
if [ -z "$ARCH" ]; then
echo "please input arch."
echo "run $0 -h for help."
return 1
fi
if [ -z "$FILEARCH" ]; then
FILEARCH="$ARCH"
fi
if [ -z "$OUTPUTDIR" ]; then
OUTPUTDIR=$CURR_DIR
fi
if [ ! -z "$CROSS_TOOL" ]; then
CC="${CROSS_TOOL}gcc"
STRIP="${CROSS_TOOL}strip"
fi
if [ -z "$CC" ]; then
CC="gcc"
fi
if [ -z "$STRIP" ]; then
if [ ! -z "`echo $CC | grep '\-gcc'`" ]; then
STRIP="`echo "$CC" | sed 's/-gcc\$/-strip/g'`"
else
STRIP="strip"
fi
fi
if [ ! -e "`which $CC`" ]; then
echo "Cannot find compiler $CC"
return 1
fi
init_env
build
}
main $@
exit $?
================================================
FILE: package/copy-smartdns.sh
================================================
#!/bin/sh
CURR_DIR=$(cd $(dirname $0);pwd)
WORKDIR=$CURR_DIR/target
CODE_DIR="$CURR_DIR/.."
SMARTDNS_STATIC_DIR="$WORKDIR/smartdns-static"
main() {
TARGET_DIR=$1
PREFIX=$2
if [ -z "$TARGET_DIR" ]; then
echo "Usage: $0 [prefix_directory]"
exit 1
fi
if [ ! -d "$TARGET_DIR" ]; then
echo "Target directory $TARGET_DIR does not exist."
exit 1
fi
if [ ! -f "$SMARTDNS_STATIC_DIR/smartdns" ]; then
cp "$CODE_DIR/src/smartdns" "$TARGET_DIR$PREFIX/usr/sbin/smartdns"
if [ $? -ne 0 ]; then
echo "Failed to copy smartdns binary to $TARGET_DIR$PREFIX/usr/sbin."
return 1
fi
chmod +x "$TARGET_DIR$PREFIX/usr/sbin/smartdns"
return 0
fi
if [ ! -f "$SMARTDNS_STATIC_DIR/smartdns" ]; then
echo "SmartDNS binary not found in $SMARTDNS_STATIC_DIR."
return 1
fi
mkdir -p "$TARGET_DIR$PREFIX/usr/local/lib/smartdns"
if [ $? -ne 0 ]; then
echo "Failed to create directory $TARGET_DIR$PREFIX/usr/local/lib/smartdns."
return 1
fi
cp $SMARTDNS_STATIC_DIR/* $TARGET_DIR$PREFIX/usr/local/lib/smartdns/ -a
if [ $? -ne 0 ]; then
echo "Failed to copy smartdns static files to $TARGET_DIR$PREFIX/usr/local/lib/smartdns."
return 1
fi
ln -f -s "$PREFIX/usr/local/lib/smartdns/run-smartdns" "$TARGET_DIR$PREFIX/usr/sbin/smartdns"
if [ $? -ne 0 ]; then
echo "Failed to create symlink for smartdns in $TARGET_DIR$PREFIX/usr/sbin."
return 1
fi
chmod +x "$TARGET_DIR$PREFIX/usr/local/lib/smartdns/run-smartdns"
echo "SmartDNS files copied successfully to $TARGET_DIR$PREFIX."
return 0
}
main $@
================================================
FILE: package/debian/DEBIAN/changelog
================================================
smartdns (1:1.2022.04.05) stable; urgency=low
* Initial build
-- initial release. Mon, 9 jul 2018 21:20:28 +0800
================================================
FILE: package/debian/DEBIAN/compat
================================================
9
================================================
FILE: package/debian/DEBIAN/conffiles
================================================
/etc/smartdns/smartdns.conf
================================================
FILE: package/debian/DEBIAN/control
================================================
Source: smartdns
Maintainer: Nick Peng
Build-Depends: debhelper (>= 8.0.0)
Version:
Section: net
Package: smartdns
Priority: extra
Architecture: armhf
Description: a smartdns server
================================================
FILE: package/debian/DEBIAN/copyright
================================================
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: smartdns
Source: http://github.com/pymumu/smartdns
Files: *
Copyright: 2018-2025 Nick peng
License: proprietary
================================================
FILE: package/debian/DEBIAN/prerm
================================================
#!/bin/sh
systemctl stop smartdns
systemctl disable smartdns
================================================
FILE: package/debian/DEBIAN/rules
================================================
#!/usr/bin/make -f
%:
dh $@ --with systemd --builddirectory=./target/
clean:
make -C ../src clean
build:
make -C ../src
override_dh_systemd_enable:
dh_systemd_enable --name=smartdns
override_dh_installinit:
dh_installinit --name=smartdns
override_dh_installdeb:
dh_installdeb
cp ../systemd/smartdns.service ${CURDIR}/debian/
================================================
FILE: package/debian/make.sh
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
CURR_DIR=$(cd $(dirname $0);pwd)
VER="`date +"1.%Y.%m.%d-%H%M"`"
SMARTDNS_DIR=$CURR_DIR/../../
SMARTDNS_CP=$SMARTDNS_DIR/package/copy-smartdns.sh
SMARTDNS_BIN=$SMARTDNS_DIR/src/smartdns
IS_BUILD_SMARTDNS_UI=0
showhelp()
{
echo "Usage: make [OPTION]"
echo "Options:"
echo " -o output directory."
echo " --arch archtecture."
echo " --ver version."
echo " --with-ui build with smartdns-ui plugin."
echo " -h show this message."
}
build()
{
ROOT=/tmp/smartdns-deiban
rm -fr $ROOT
mkdir -p $ROOT
cd $ROOT/
cp $CURR_DIR/DEBIAN $ROOT/ -af
CONTROL=$ROOT/DEBIAN/control
mkdir $ROOT/usr/sbin -p
mkdir $ROOT/etc/smartdns/ -p
mkdir $ROOT/etc/default/ -p
mkdir $ROOT/lib/systemd/system/ -p
pkgver=$(echo ${VER}| sed 's/^1\.//g')
sed -i "s/Version:.*/Version: ${pkgver}/" $ROOT/DEBIAN/control
sed -i "s/Architecture:.*/Architecture: $ARCH/" $ROOT/DEBIAN/control
chmod 0755 $ROOT/DEBIAN/prerm
cp $SMARTDNS_DIR/etc/smartdns/smartdns.conf $ROOT/etc/smartdns/
cp $SMARTDNS_DIR/etc/default/smartdns $ROOT/etc/default/
cp $SMARTDNS_DIR/systemd/smartdns.service $ROOT/lib/systemd/system/
if [ $IS_BUILD_SMARTDNS_UI -eq 1 ]; then
mkdir $ROOT/usr/local/lib/smartdns -p
mkdir $ROOT/usr/share/smartdns/wwwroot -p
cp $SMARTDNS_DIR/plugin/smartdns-ui/target/smartdns_ui.so $ROOT/usr/local/lib/smartdns/smartdns_ui.so -a
if [ $? -ne 0 ]; then
echo "Failed to copy smartdns-ui plugin."
return 1
fi
cp $WORKDIR/smartdns-webui/out/* $ROOT/usr/share/smartdns/wwwroot/ -a
if [ $? -ne 0 ]; then
echo "Failed to copy smartdns-ui plugin."
return 1
fi
else
echo "smartdns-ui plugin not found, skipping copy."
fi
$SMARTDNS_CP $ROOT
if [ $? -ne 0 ]; then
echo "copy smartdns file failed."
return 1
fi
chmod +x $ROOT/usr/sbin/smartdns 2>/dev/null
dpkg -b $ROOT $OUTPUTDIR/smartdns.$VER.$FILEARCH.deb
rm -fr $ROOT/
}
main()
{
OPTS=`getopt -o o:h --long arch:,ver:,with-ui,filearch: \
-n "" -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$OPTS"
while true; do
case "$1" in
--arch)
ARCH="$2"
shift 2;;
--filearch)
FILEARCH="$2"
shift 2;;
--with-ui)
IS_BUILD_SMARTDNS_UI=1
shift ;;
--ver)
VER="$2"
shift 2;;
-o )
OUTPUTDIR="$2"
shift 2;;
-h | --help )
showhelp
return 0
shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if [ -z "$ARCH" ]; then
echo "please input arch."
return 1;
fi
if [ -z "$FILEARCH" ]; then
FILEARCH=$ARCH
fi
if [ -z "$OUTPUTDIR" ]; then
OUTPUTDIR=$CURR_DIR;
fi
build
}
main $@
exit $?
================================================
FILE: package/linux/install
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
INST_DIR=$(cd $(dirname $0);pwd)
ISWSL=1 # 1 means not WSL, 0 means wsl
showhelp()
{
echo "Usage: install [OPTION]"
echo "Options:"
echo " -i install smartdns."
echo " -u uninstall smartdns."
echo " -U upgrade install smartdns."
echo " --prefix [dir] prefix directory."
echo " -h show this message."
}
start_service()
{
if [ $ISSYSTEMD -ne 0 ]; then
chkconfig smartdns on >/dev/null 2>&1
service smartdns start
return $?
fi
systemctl daemon-reload
systemctl enable smartdns
systemctl start smartdns
}
stop_service()
{
if [ $ISSYSTEMD -ne 0 ]; then
service smartdns stop
chkconfig smartdns off >/dev/null 2>&1
return 0
fi
systemctl stop smartdns
systemctl disable smartdns
return 0
}
clean_service()
{
if [ $ISSYSTEMD -ne 0 ]; then
return 0
fi
systemctl daemon-reload
}
get_systemd_path()
{
service="`systemctl --no-legend| grep '\.service' | head -n 1 | awk '{print $1}' 2>/dev/null`"
SERVICE_PATH="`systemctl show $service | grep FragmentPath | awk -F'=' '{print $2}' 2>/dev/null`"
if [ ! -z "$SERVICE_PATH" ]; then
SERVICE_PATH="`dirname $SERVICE_PATH 2>/dev/null`"
if [ -d "$SERVICE_PATH" ]; then
echo "$SERVICE_PATH"
return 0
fi
fi
SERVICE_PATH="`pkg-config systemd --variable=systemdsystemunitdir 2>/dev/null`"
if [ ! -z "$SERVICE_PATH" ]; then
if [ -d "$SERVICE_PATH" ]; then
echo "$SERVICE_PATH"
return 0
fi
fi
SERVICE_PATH="/lib/systemd/system"
if [ -d "$SERVICE_PATH" ]; then
echo "$SERVICE_PATH"
return 0
fi
return 1
}
install_files()
{
install -v -d $SMARTDNS_CONF_DIR
if [ $? -ne 0 ]; then
return 1
fi
install -v -d $SMARTDNS_UI_WWWROOT
if [ $? -ne 0 ]; then
return 1
fi
install -v -d $SMARTDNS_PLUIGN_DIR
if [ $? -ne 0 ]; then
return 1
fi
install -v -t $SMARTDNS_PLUIGN_DIR $INST_DIR/usr/local/lib/smartdns/smartdns_ui.so
if [ $? -ne 0 ]; then
echo "smartdns-ui plugin not found, skipping copy."
fi
if [ -d "$INST_DIR/usr/local/lib/smartdns" ]; then
cp $INST_DIR/usr/local/lib/smartdns/* $SMARTDNS_PLUIGN_DIR/ -a
if [ $? -ne 0 ]; then
echo "Failed to copy smartdns library files."
return 1
fi
fi
if [ -d "$INST_DIR/usr/share/smartdns/wwwroot/" ]; then
cp $INST_DIR/usr/share/smartdns/wwwroot/* $SMARTDNS_UI_WWWROOT/ -a
if [ $? -ne 0 ]; then
echo "Failed to copy smartdns-webui files."
return 1
fi
fi
cp $INST_DIR/usr/sbin/smartdns $PREFIX/usr/sbin -a
if [ $? -ne 0 ]; then
return 1
fi
chmod +x $PREFIX/usr/sbin/smartdns
if [ -e "$PREFIX$SMARTDNS_CONF_DIR/smartdns.conf" ]; then
cp $INST_DIR/etc/smartdns/smartdns.conf $PREFIX$SMARTDNS_CONF_DIR/smartdns.conf.pkg
else
install -v -m 0640 -t $PREFIX$SMARTDNS_CONF_DIR $INST_DIR/etc/smartdns/smartdns.conf
if [ $? -ne 0 ]; then
return 1
fi
fi
install -v -m 0640 -t $PREFIX/etc/default $INST_DIR/etc/default/smartdns
if [ $? -ne 0 ]; then
return 1
fi
install -v -m 0755 -t $SMARTDNS_INIT_DIR $INST_DIR/etc/init.d/smartdns
if [ $? -ne 0 ]; then
if [ $ISSYSTEMD -ne 0 ]; then
return 1
fi
fi
if [ $ISSYSTEMD -eq 0 ]; then
SYSTEM_UNIT_PATH="`get_systemd_path`"
if [ -z "$SYSTEM_UNIT_PATH" ]; then
echo "cannot find systemd path"
return 1
fi
install -v -m 0644 -t $PREFIX$SYSTEM_UNIT_PATH $INST_DIR/systemd/smartdns.service
if [ $? -ne 0 ]; then
return 1
fi
fi
return 0
}
uninstall_smartdns()
{
if [ -z "$PREFIX" ]; then
stop_service 2>/dev/null
fi
rmdir $PREFIX$SMARTDNS_CONF_DIR 2>/dev/null
rm -f $PREFIX/usr/sbin/smartdns
rm -f $PREFIX/etc/default/smartdns
rm -f $PREFIX/etc/init.d/smartdns
rm -fr $PREFIX/usr/share/smartdns/wwwroot
rmdir $PREFIX/usr/share/smartdns 2>/dev/null
rm -fr $PREFIX/usr/local/lib/smartdns 2>/dev/null
if [ $ISWSL -eq 0 ]; then
sed -i '\#%sudo ALL=NOPASSWD: /etc/init.d/smartdns#d' /etc/sudoers 2>/dev/null
fi
if [ $ISSYSTEMD -eq 0 ]; then
SYSTEM_UNIT_PATH="`get_systemd_path`"
if [ ! -z "$SYSTEM_UNIT_PATH" ]; then
rm -f $PREFIX$SYSTEM_UNIT_PATH/smartdns.service
fi
fi
if [ -z "$PREFIX" ]; then
clean_service
fi
}
install_smartdns()
{
local ret
which smartdns >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo "Already installed."
return 1
fi
install_files
ret=$?
if [ $ret -ne 0 ]; then
uninstall_smartdns
return $ret
fi
if [ -z "$PREFIX" ]; then
start_service
fi
if [ $ISWSL -eq 0 ]; then
grep "%sudo ALL=NOPASSWD: /etc/init.d/smartdns" /etc/sudoers >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "%sudo ALL=NOPASSWD: /etc/init.d/smartdns" >> /etc/sudoers
fi
fi
return 0
}
init_dir()
{
local ID=`id -u`
if [ $ID -ne 0 ]; then
echo "Please run as root."
return 1
fi
SMARTDNS_CONF_DIR=$PREFIX/etc/smartdns
SMARTDNS_INIT_DIR=$PREFIX/etc/init.d
SMARTDNS_UI_WWWROOT=$PREFIX/usr/share/smartdns/wwwroot
SMARTDNS_PLUIGN_DIR=$PREFIX/usr/local/lib/smartdns
which systemctl >/dev/null 2>&1
ISSYSTEMD="$?"
# Running under WSL (Windows Subsystem for Linux)?
cat /proc/version | grep -E '[Mm]icrosoft' >/dev/null 2>&1;
if [ $? -eq 0 ]; then
ISSYSTEMD=1
ISWSL=0
fi
cd $INST_DIR
}
main()
{
ACTION=""
OPTS=`getopt -o iuhU --long help,prefix: \
-n "" -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$OPTS"
while true; do
case "$1" in
--prefix)
PREFIX="$2"
shift 2;;
-h | --help )
showhelp
return 0
shift ;;
-i )
ACTION="INSTALL"
shift ;;
-u )
ACTION="UNINSTALL"
shift ;;
-U )
ACTION="UPGRADE"
shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
init_dir
if [ -z "$ACTION" ]; then
showhelp
return 0
elif [ "$ACTION" = "INSTALL" ]; then
install_smartdns
return $?
elif [ "$ACTION" = "UNINSTALL" ]; then
uninstall_smartdns
return 0
elif [ "$ACTION" = "UPGRADE" ]; then
uninstall_smartdns
install_smartdns
return $?
else
showhelp
return 1
fi
}
main $@
exit $?
================================================
FILE: package/linux/make.sh
================================================
#!/bin/sh
CURR_DIR=$(cd $(dirname $0);pwd)
VER="`date +"1.%Y.%m.%d-%H%M"`"
SMARTDNS_DIR=$CURR_DIR/../../
SMARTDNS_CP=$SMARTDNS_DIR/package/copy-smartdns.sh
SMARTDNS_BIN=$SMARTDNS_DIR/src/smartdns
IS_BUILD_SMARTDNS_UI=0
showhelp()
{
echo "Usage: make [OPTION]"
echo "Options:"
echo " -o output directory."
echo " --arch archtecture."
echo " --ver version."
echo " --with-ui build with smartdns-ui plugin."
echo " -h show this message."
}
build()
{
PKG_ROOT=/tmp/smartdns-linux
rm -fr $PKG_ROOT
mkdir -p $PKG_ROOT/smartdns
cd $PKG_ROOT/
# Generic x86_64
mkdir $PKG_ROOT/smartdns/usr/sbin -p
mkdir $PKG_ROOT/smartdns/package -p
mkdir $PKG_ROOT/smartdns/systemd -p
cd $SMARTDNS_DIR
cp package/windows $PKG_ROOT/smartdns/package/ -a
cp etc *.md LICENSE package/linux/install $PKG_ROOT/smartdns/ -a
cp systemd/smartdns.service $PKG_ROOT/smartdns/systemd
$SMARTDNS_CP $PKG_ROOT/smartdns
if [ $? -ne 0 ]; then
echo "copy smartdns file failed."
rm -fr $PKG_ROOT
return 1
fi
if [ $IS_BUILD_SMARTDNS_UI -eq 1 ]; then
mkdir $PKG_ROOT/smartdns/usr/local/lib/smartdns -p
mkdir $PKG_ROOT/smartdns/usr/share/smartdns/wwwroot -p
cp plugin/smartdns-ui/target/smartdns_ui.so $PKG_ROOT/smartdns/usr/local/lib/smartdns/smartdns_ui.so -a
cp $WORKDIR/smartdns-webui/out/* $PKG_ROOT/smartdns/usr/share/smartdns/wwwroot/ -a
if [ $? -ne 0 ]; then
echo "Failed to copy smartdns-ui plugin."
return 1
fi
else
echo "smartdns-ui plugin not found, skipping copy."
fi
chmod +x $PKG_ROOT/smartdns/install
if [ $? -ne 0 ]; then
echo "copy smartdns file failed"
rm -fr $PKG_ROOT
exit 1
fi
cd $PKG_ROOT
tar zcf $OUTPUTDIR/smartdns.$VER.$FILEARCH.tar.gz smartdns
if [ $? -ne 0 ]; then
echo "create package failed"
rm -fr $PKG_ROOT
exit 1
fi
cd $CURR_DIR
rm -fr $PKG_ROOT
}
main()
{
OPTS=`getopt -o o:h --long arch:,ver:,with-ui,filearch: \
-n "" -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$OPTS"
while true; do
case "$1" in
--arch)
ARCH="$2"
shift 2;;
--filearch)
FILEARCH="$2"
shift 2;;
--with-ui)
IS_BUILD_SMARTDNS_UI=1
shift ;;
--ver)
VER="$2"
shift 2;;
-o )
OUTPUTDIR="$2"
shift 2;;
-h | --help )
showhelp
return 0
shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if [ -z "$ARCH" ]; then
echo "please input arch."
return 1;
fi
if [ -z "$FILEARCH" ]; then
FILEARCH=$ARCH
fi
if [ -z "$OUTPUTDIR" ]; then
OUTPUTDIR=$CURR_DIR;
fi
build
}
main $@
exit $?
================================================
FILE: package/luci/control/control
================================================
Package: luci-app-smartdns
Version: git-18.201.27126-7bf0367-1
Depends: libc, smartdns
Source: feeds/luci/applications/luci-app-smartdns
Section: luci
Architecture: all
Description: A smartdns server
================================================
FILE: package/luci/control/postinst
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
[ "${IPKG_NO_SCRIPT}" = "1" ] && exit 0
[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0
. ${IPKG_INSTROOT}/lib/functions.sh
default_postinst $0 $@
================================================
FILE: package/luci/control/prerm
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0
. ${IPKG_INSTROOT}/lib/functions.sh
default_prerm $0 $@
================================================
FILE: package/luci/debian-binary
================================================
2.0
================================================
FILE: package/luci/files/luci/i18n/smartdns.zh-cn.po
================================================
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 "Blacklist IP"
msgstr "黑名单"
msgid "Blacklist IP Rule, Decline IP addresses within the range."
msgstr "黑名单规则,拒绝指定范围的IP地址。"
msgid "Bind Device"
msgstr "绑定到设备"
msgid "Bind Device Name"
msgstr "绑定的设备名称"
msgid "Bogus nxdomain"
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 "Configure ip rule list."
msgstr "配置IP规则列表"
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 "设置服务器组,例如office,home"
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 (weekly) auto update."
msgstr "启用每日(每周)自动更新"
msgid "Enable DOH DNS Server"
msgstr "启用DOH服务器"
msgid "Enable DOT DNS Server"
msgstr "启用DOT服务器"
msgid "Update time (every day)"
msgstr "更新时间(每天)"
msgid "Update Time (Every Week)"
msgstr "更新时间(每周)"
msgid "Enable Audit Log"
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 "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 Blacklist"
msgstr "IP黑名单"
msgid "IP Blacklist Filtering"
msgstr "IP黑名单过滤"
msgid "IPV6 Server"
msgstr "IPV6服务器"
msgid "IP alias"
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 List"
msgstr "IP规则列表"
msgid "IP Rule Name"
msgstr "IP规则名称"
msgid "IP Set File"
msgstr "IP集合列表文件"
msgid "IP addresses, CIDR format."
msgstr "IP地址,CIDR格式。"
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 /etc/smartdns/conf.d"
msgstr "包含配置文件 /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 check the system logs and check if the configuration is valid."
msgstr "请检查系统日志,并检查配置是否合法。"
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 Specific ip rule list."
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 Servers"
msgstr "上游服务器"
msgid ""
"Upstream Servers, support UDP, TCP protocol. Please configure multiple DNS "
"servers, including multiple foreign DNS servers."
msgstr ""
"上游 DNS 服务器,支持 UDP,TCP 协议。请配置多个上游 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"
================================================
FILE: package/luci/files/root/usr/share/luci/menu.d/luci-app-smartdns.json
================================================
{
"admin/services/smartdns": {
"title": "SmartDNS",
"action": {
"type": "view",
"path": "smartdns/smartdns"
},
"depends": {
"acl": [ "luci-app-smartdns" ],
"uci": { "smartdns": true }
}
}
}
================================================
FILE: package/luci/files/root/usr/share/rpcd/acl.d/luci-app-smartdns.json
================================================
{
"luci-app-smartdns": {
"description": "Grant access to LuCI app smartdns",
"read": {
"file": {
"/etc/smartdns/*": [ "read" ]
},
"ubus": {
"service": [ "list" ]
},
"uci": [ "smartdns" ]
},
"write": {
"file": {
"/etc/smartdns/*": [ "write" ],
"/etc/init.d/smartdns restart": [ "exec" ],
"/etc/init.d/smartdns updatefiles": [ "exec" ]
},
"uci": [ "smartdns" ]
}
}
}
================================================
FILE: package/luci/files/root/www/luci-static/resources/view/smartdns/smartdns.js
================================================
/*************************************************************************
*
* Copyright (C) 2018-2025 Ruilin Peng (Nick) .
*
* 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 .
*/
'use strict';
'require fs';
'require uci';
'require form';
'require view';
'require poll';
'require rpc';
'require ui';
var conf = 'smartdns';
var callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
});
var pollAdded = false;
function getServiceStatus() {
return L.resolveDefault(callServiceList(conf), {})
.then(function (res) {
var is_running = false;
try {
is_running = res[conf]['instances']['smartdns']['running'];
} catch (e) { }
return is_running;
});
}
function smartdnsServiceStatus() {
return Promise.all([
getServiceStatus()
]);
}
function smartdnsRenderStatus(res) {
var renderHTML = "";
var isRunning = res[0];
var autoSetDnsmasq = uci.get_first('smartdns', 'smartdns', 'auto_set_dnsmasq');
var smartdnsPort = uci.get_first('smartdns', 'smartdns', 'port');
var smartdnsEnable = uci.get_first('smartdns', 'smartdns', 'enabled');
var dnsmasqServer = uci.get_first('dhcp', 'dnsmasq', 'server');
if (isRunning) {
renderHTML += "SmartDNS - " + _("RUNNING") + "";
} else {
renderHTML += "SmartDNS - " + _("NOT RUNNING") + "";
if (smartdnsEnable === '1') {
renderHTML += " " + _("Please check the system logs and check if the configuration is valid.");
renderHTML += "";
}
return renderHTML;
}
if (autoSetDnsmasq === '1' && smartdnsPort != '53') {
var matchLine = "127.0.0.1#" + smartdnsPort;
uci.unload('dhcp');
uci.load('dhcp');
if (dnsmasqServer == undefined || dnsmasqServer.indexOf(matchLine) < 0) {
renderHTML += " " + _("Dnsmasq Forwarded To Smartdns Failure") + "";
}
}
return renderHTML;
}
return view.extend({
load: function () {
return Promise.all([
uci.load('dhcp'),
uci.load('smartdns'),
]);
},
render: function (stats) {
var m, s, o;
var ss, so;
var servers, download_files;
m = new form.Map('smartdns', _('SmartDNS'));
m.title = _("SmartDNS Server");
m.description = _("SmartDNS is a local high-performance DNS server, supports finding fastest IP, "
+ "supports ad filtering, and supports avoiding DNS poisoning.");
s = m.section(form.NamedSection, '_status');
s.anonymous = true;
s.render = function (section_id) {
var renderStatus = function () {
return L.resolveDefault(smartdnsServiceStatus()).then(function (res) {
var view = document.getElementById("service_status");
if (view == null) {
return;
}
view.innerHTML = smartdnsRenderStatus(res);
});
}
if (pollAdded == false) {
poll.add(renderStatus, 1);
pollAdded = true;
}
return E('div', { class: 'cbi-section' }, [
E('div', { id: 'service_status' },
_('Collecting data ...'))
]);
}
////////////////
// Basic;
////////////////
s = m.section(form.TypedSection, "smartdns", _("Settings"), _("General Settings"));
s.anonymous = true;
s.tab("settings", _("General Settings"));
s.tab("advanced", _('Advanced Settings'));
s.tab("seconddns", _("Second Server Settings"));
s.tab("dns64", _("DNS64 Server Settings"));
s.tab("files", _("Download Files Setting"), _("Download domain list files for domain-rule and include config files, please refresh the page after download to take effect."));
s.tab("proxy", _("Proxy Server Settings"));
s.tab("custom", _("Custom Settings"));
///////////////////////////////////////
// Basic Settings
///////////////////////////////////////
o = s.taboption("settings", form.Flag, "enabled", _("Enable"), _("Enable or disable smartdns server"));
o.rmempty = false;
o.default = o.disabled;
// server name;
o = s.taboption("settings", form.Value, "server_name", _("Server Name"), _("Smartdns server name"));
o.placeholder = "server name";
o.datatype = "hostname";
o.rempty = true;
// Port;
o = s.taboption("settings", form.Value, "port", _("Local Port"),
_("Smartdns local server port, smartdns will be automatically set as main dns when the port is 53."));
o.placeholder = 53;
o.default = 53;
o.datatype = "port";
o.rempty = false;
// auto-conf-dnsmasq;
o = s.taboption("settings", form.Flag, "auto_set_dnsmasq", _("Automatically Set Dnsmasq"), _("Automatically set as upstream of dnsmasq when port changes."));
o.rmempty = false;
o.default = o.enabled;
///////////////////////////////////////
// advanced settings;
///////////////////////////////////////
// Speed check mode;
o = s.taboption("advanced", form.Value, "speed_check_mode", _("Speed Check Mode"), _("Smartdns speed check mode."));
o.rmempty = true;
o.placeholder = "default";
o.value("", _("default"));
o.value("ping,tcp:80,tcp:443");
o.value("ping,tcp:443,tcp:80");
o.value("tcp:80,tcp:443,ping");
o.value("tcp:443,tcp:80,ping");
o.value("none", _("None"));
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
if (value == "none") {
return true;
}
var check_mode = value.split(",")
for (var i = 0; i < check_mode.length; i++) {
if (check_mode[i] == "ping") {
continue;
}
if (check_mode[i].indexOf("tcp:") == 0) {
var port = check_mode[i].split(":")[1];
if (port == "") {
return _("TCP port is empty");
}
continue;
}
return _("Speed check mode is invalid.");
}
return true;
}
// response mode;
o = s.taboption("advanced", form.ListValue, "response_mode", _("Response Mode"),
_("Smartdns response mode, First Ping: return the first ping IP, Fastest IP: return the fastest IP, Fastest Response: return the fastest DNS response."));
o.rmempty = true;
o.placeholder = "default";
o.value("", _("default"));
o.value("first-ping", _("First Ping"));
o.value("fastest-ip", _("Fastest IP"));
o.value("fastest-response", _("Fastest Response"));
// Enable TCP server;
o = s.taboption("advanced", form.Flag, "tcp_server", _("TCP Server"), _("Enable TCP DNS Server"));
o.rmempty = false;
o.default = o.enabled;
// Enable DOT server;
o = s.taboption("advanced", form.Flag, "tls_server", _("DOT Server"), _("Enable DOT DNS Server"));
o.rmempty = false;
o.default = o.disabled;
o = s.taboption("advanced", form.Value, "tls_server_port", _("DOT Server Port"), _("Smartdns DOT server port."));
o.placeholder = 853;
o.default = 853;
o.datatype = "port";
o.rempty = false;
o.depends('tls_server', '1');
// Enable DOH server;
o = s.taboption("advanced", form.Flag, "doh_server", _("DOH Server"), _("Enable DOH DNS Server"));
o.rmempty = false;
o.default = o.disabled;
o = s.taboption("advanced", form.Value, "doh_server_port", _("DOH Server Port"), _("Smartdns DOH server port."));
o.placeholder = 843;
o.default = 843;
o.datatype = "port";
o.rempty = false;
o.depends('doh_server', '1');
o = s.taboption("advanced", form.Value, "bind_cert", _("Server Cert"), _("Server certificate file path."));
o.datatype = "string";
o.placeholder = "/var/etc/smartdns/smartdns/smartdns-cert.pem"
o.rempty = true;
o.depends('tls_server', '1');
o.depends('doh_server', '1');
o = s.taboption("advanced", form.Value, "bind_cert_key", _("Server Cert Key"), _("Server certificate key file path."));
o.datatype = "string";
o.placeholder = "/var/etc/smartdns/smartdns/smartdns-key.pem"
o.rempty = false;
o.depends('tls_server', '1');
o.depends('doh_server', '1');
o = s.taboption("advanced", form.Value, "bind_cert_key_pass", _("Server Cert Key Pass"), _("Server certificate key file password."));
o.datatype = "string";
o.rempty = false;
o.depends('tls_server', '1');
o.depends('doh_server', '1');
// Support IPV6;
o = s.taboption("advanced", form.Flag, "ipv6_server", _("IPV6 Server"), _("Enable IPV6 DNS Server"));
o.rmempty = false;
o.default = o.enabled;
// bind to device;
o = s.taboption("advanced", form.Flag, "bind_device", _("Bind Device"), _("Listen only on the specified interfaces."));
o.rmempty = false;
o.default = o.enabled;
// bind device name;
o = s.taboption("advanced", form.Value, "bind_device_name", _("Bind Device Name"), _("Name of device name listen on."));
o.placeholder = "default";
o.rempty = true;
o.datatype = "string";
// Support DualStack ip selection;
o = s.taboption("advanced", form.Flag, "dualstack_ip_selection", _("Dual-stack IP Selection"),
_("Enable IP selection between IPV4 and IPV6"));
o.rmempty = false;
o.default = o.enabled;
// Domain prefetch load ;
o = s.taboption("advanced", form.Flag, "prefetch_domain", _("Domain prefetch"),
_("Enable domain prefetch, accelerate domain response speed."));
o.rmempty = true;
o.default = o.disabled;
// Domain Serve expired
o = s.taboption("advanced", form.Flag, "serve_expired", _("Serve expired"),
_("Attempts to serve old responses from cache with a TTL of 0 in the response without waiting for the actual resolution to finish."));
o.rmempty = false;
o.default = o.enabled;
// cache-size;
o = s.taboption("advanced", form.Value, "cache_size", _("Cache Size"), _("DNS domain result cache size"));
o.rempty = true;
// cache-persist;
o = s.taboption("advanced", form.Flag, "cache_persist", _("Cache Persist"), _("Write cache to disk on exit and load on startup."));
o.rmempty = false;
o.default = o.enabled;
// resolve local hostname;
o = s.taboption("advanced", form.Flag, "resolve_local_hostnames", _("Resolve Local Hostnames"), _("Resolve local hostnames by reading Dnsmasq lease file."));
o.rmempty = false;
o.default = o.enabled;
// resolve local network hostname via mDNS;
o = s.taboption("advanced", form.Flag, "mdns_lookup", _("mDNS Lookup"), _("Resolve local network hostname via mDNS protocol."));
o.rmempty = true;
o.default = o.disabled;
// Force AAAA SOA
o = s.taboption("advanced", form.Flag, "force_aaaa_soa", _("Force AAAA SOA"), _("Force AAAA SOA."));
o.rmempty = true;
o.default = o.disabled;
// Force HTTPS SOA
o = s.taboption("advanced", form.Flag, "force_https_soa", _("Force HTTPS SOA"), _("Force HTTPS SOA."));
o.rmempty = false;
o.default = o.enabled;
// ipset name;
o = s.taboption("advanced", form.Value, "ipset_name", _("IPset Name"), _("IPset name."));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var ipset = value.split(",")
for (var i = 0; i < ipset.length; i++) {
if (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\-_]+$/)) {
return _("ipset name format error, format: [#[4|6]:]ipsetname");
}
}
return true;
}
// Ipset no speed.
o = s.taboption("advanced", form.Value, "ipset_no_speed", _("No Speed IPset Name"),
_("Ipset name, Add domain result to ipset when speed check fails."));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var ipset = value.split(",")
for (var i = 0; i < ipset.length; i++) {
if (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\-_]+$/)) {
return _("ipset name format error, format: [#[4|6]:]ipsetname");
}
}
return true;
}
// NFTset name;
o = s.taboption("advanced", form.Value, "nftset_name", _("NFTset Name"), _("NFTset name, format: [#[4|6]:[family#table#set]]"));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var nftset = value.split(",")
for (var i = 0; i < nftset.length; i++) {
if (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+$/)) {
return _("NFTset name format error, format: [#[4|6]:[family#table#set]]");
}
}
return true;
}
// NFTset no speed.
o = s.taboption("advanced", form.Value, "nftset_no_speed", _("No Speed NFTset Name"),
_("Nftset name, Add domain result to nftset when speed check fails, format: [#[4|6]:[family#table#set]]"));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var nftset = value.split(",")
for (var i = 0; i < nftset.length; i++) {
if (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+$/)) {
return _("NFTset name format error, format: [#[4|6]:[family#table#set]]");
}
}
return true;
}
// rr-ttl;
o = s.taboption("advanced", form.Value, "rr_ttl", _("Domain TTL"), _("TTL for all domain result."));
o.rempty = true;
// rr-ttl-min;
o = s.taboption("advanced", form.Value, "rr_ttl_min", _("Domain TTL Min"),
_("Minimum TTL for all domain result."));
o.rempty = true;
o.placeholder = "600";
o.default = 600;
o.optional = true;
// rr-ttl-max;
o = s.taboption("advanced", form.Value, "rr_ttl_max", _("Domain TTL Max"),
_("Maximum TTL for all domain result."));
o.rempty = true;
// rr-ttl-reply-max;
o = s.taboption("advanced", form.Value, "rr_ttl_reply_max", _("Reply Domain TTL Max"),
_("Reply maximum TTL for all domain result."));
o.rempty = true;
// other args
o = s.taboption("advanced", form.Value, "server_flags", _("Additional Server Args"),
_("Additional server args, refer to the help description of the bind option."))
o.default = ""
o.rempty = true
// include config
download_files = uci.sections('smartdns', 'download-file');
o = s.taboption("advanced", form.DynamicList, "conf_files", _("Include Config Files /etc/smartdns/conf.d"),
_("Include other config files from /etc/smartdns/conf.d or custom path, can be downloaded from the download page."));
for (var i = 0; i < download_files.length; i++) {
if (download_files[i].type == undefined) {
continue;
}
if (download_files[i].type != 'config') {
continue
}
o.value(download_files[i].name);
}
o = s.taboption("advanced", form.DynamicList, "hosts_files", _("Hosts File"), _("Include hosts file."));
o.rmempty = true;
for (var i = 0; i < download_files.length; i++) {
if (download_files[i].type == undefined) {
continue;
}
if (download_files[i].type != 'other') {
continue
}
o.value(download_files[i].name);
}
///////////////////////////////////////
// second dns server;
///////////////////////////////////////
// Enable;
o = s.taboption("seconddns", form.Flag, "seconddns_enabled", _("Enable"),
_("Enable or disable second DNS server."));
o.default = o.disabled;
o.rempty = true;
// Port;
o = s.taboption("seconddns", form.Value, "seconddns_port", _("Local Port"), _("Smartdns local server port"));
o.placeholder = 6553;
o.default = 6553;
o.datatype = "port";
o.rempty = false;
// Enable TCP server;
o = s.taboption("seconddns", form.Flag, "seconddns_tcp_server", _("TCP Server"), _("Enable TCP DNS Server"));
o.rmempty = false;
o.default = o.enabled;
// dns server group;
o = s.taboption("seconddns", form.Value, "seconddns_server_group", _("Server Group"),
_("Query DNS through specific dns server group, such as office, home."));
o.rmempty = true;
o.placeholder = "default";
o.datatype = "hostname";
o.rempty = true;
o = s.taboption("seconddns", form.Flag, "seconddns_no_speed_check", _("Skip Speed Check"),
_("Do not check speed."));
o.rmempty = true;
o.default = o.disabled;
// skip address rules;
o = s.taboption("seconddns", form.Flag, "seconddns_no_rule_addr", _("Skip Address Rules"),
_("Skip address rules."));
o.rmempty = true;
o.default = o.disabled;
// skip name server rules;
o = s.taboption("seconddns", form.Flag, "seconddns_no_rule_nameserver", _("Skip Nameserver Rule"),
_("Skip nameserver rules."));
o.rmempty = true;
o.default = o.disabled;
// skip ipset rules;
o = s.taboption("seconddns", form.Flag, "seconddns_no_rule_ipset", _("Skip Ipset Rule"),
_("Skip ipset rules."));
o.rmempty = true;
o.default = o.disabled;
// skip soa address rule;
o = s.taboption("seconddns", form.Flag, "seconddns_no_rule_soa", _("Skip SOA Address Rule"),
_("Skip SOA address rules."));
o.rmempty = true;
o.default = o.disabled;
o = s.taboption("seconddns", form.Flag, "seconddns_no_dualstack_selection", _("Skip Dualstack Selection"),
_("Skip Dualstack Selection."));
o.rmempty = true;
o.default = o.disabled;
// skip cache;
o = s.taboption("seconddns", form.Flag, "seconddns_no_cache", _("Skip Cache"), _("Skip Cache."));
o.rmempty = true;
o.default = o.disabled;
// Force AAAA SOA
o = s.taboption("seconddns", form.Flag, "seconddns_force_aaaa_soa", _("Force AAAA SOA"), _("Force AAAA SOA."));
o.rmempty = true;
o.default = o.disabled;
// Force HTTPS SOA
o = s.taboption("seconddns", form.Flag, "seconddns_force_https_soa", _("Force HTTPS SOA"), _("Force HTTPS SOA."));
o.rmempty = true;
o.default = o.disabled;
o = s.taboption("seconddns", form.Flag, "seconddns_no_ip_alias", _("Skip IP Alias"));
o.rmempty = true;
o.default = o.disabled;
o = s.taboption("seconddns", form.Value, "seconddns_ipset_name", _("IPset Name"), _("IPset name."));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var ipset = value.split(",")
for (var i = 0; i < ipset.length; i++) {
if (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\-_]+$/)) {
return _("ipset name format error, format: [#[4|6]:]ipsetname");
}
}
return true;
}
o = s.taboption("seconddns", form.Value, "seconddns_nftset_name", _("NFTset Name"), _("NFTset name, format: [#[4|6]:[family#table#set]]"));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var nftset = value.split(",")
for (var i = 0; i < nftset.length; i++) {
if (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+$/)) {
return _("NFTset name format error, format: [#[4|6]:[family#table#set]]");
}
}
return true;
}
// other args
o = s.taboption("seconddns", form.Value, "seconddns_server_flags", _("Additional Server Args"),
_("Additional server args, refer to the help description of the bind option."))
o.default = ""
o.rempty = true
///////////////////////////////////////
// DNS64 Settings
///////////////////////////////////////
o = s.taboption("dns64", form.Value, "dns64", _("DNS64"));
o.placeholder = "64:ff9b::/96";
o.datatype = "ip6addr";
o.rempty = true;
///////////////////////////////////////
// download Files Settings
///////////////////////////////////////
o = s.taboption("files", form.Flag, "enable_auto_update", _("Enable Auto Update"), _("Enable daily (weekly) auto update."));
o.rmempty = true;
o.default = o.disabled;
o.rempty = true;
o = s.taboption("files", form.ListValue, "auto_update_week_time", _("Update Time (Every Week)"));
o.value('*', _('Every Day'));
o.value('1', _('Every Monday'));
o.value('2', _('Every Tuesday'));
o.value('3', _('Every Wednesday'));
o.value('4', _('Every Thursday'));
o.value('5', _('Every Friday'));
o.value('6', _('Every Saturday'));
o.value('0', _('Every Sunday'));
o.default = "*";
o.depends('enable_auto_update', '1');
o = s.taboption('files', form.ListValue, 'auto_update_day_time', _("Update time (every day)"));
for (var i = 0; i < 24; i++)
o.value(i, i + ':00');
o.default = '5';
o.depends('enable_auto_update', '1');
o = s.taboption("files", form.FileUpload, "upload_conf_file", _("Upload Config File"),
_("Upload smartdns config file to /etc/smartdns/conf.d"));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.root_directory = "/etc/smartdns/conf.d"
o = s.taboption("files", form.FileUpload, "upload_list_file", _("Upload Domain List File"),
_("Upload domain list file to /etc/smartdns/domain-set"));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.root_directory = "/etc/smartdns/domain-set"
o = s.taboption("files", form.FileUpload, "upload_other_file", _("Upload File"));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.root_directory = "/etc/smartdns/download"
o = s.taboption('files', form.DummyValue, "_update", _("Update Files"));
o.renderWidget = function () {
return E('button', {
'class': 'btn cbi-button cbi-button-apply',
'id': 'btn_update',
'click': ui.createHandlerFn(this, function () {
return fs.exec('/etc/init.d/smartdns', ['updatefiles'])
.catch(function (e) { ui.addNotification(null, E('p', e.message), 'error') });
})
}, [_("Update")]);
}
o = s.taboption('files', form.SectionValue, '__files__', form.GridSection, 'download-file', _('Download Files'),
_('List of files to download.'));
ss = o.subsection;
ss.addremove = true;
ss.anonymous = true;
ss.sortable = true;
so = ss.option(form.Value, 'name', _('File Name'), _('File Name'));
so.rmempty = true;
so.datatype = 'file';
so = ss.option(form.Value, 'url', _('URL'), _('URL'));
so.rmempty = true;
so.datatype = 'string';
so.validate = function (section_id, value) {
if (value == "") {
return true;
}
if (!value.match(/^(http|https|ftp|sftp):\/\//)) {
return _("URL format error, format: http:// or https://");
}
return true;
}
so = ss.option(form.ListValue, "type", _("type"), _("File Type"));
so.value("list", _("domain list (/etc/smartdns/domain-set)"));
so.value("config", _("smartdns config (/etc/smartdns/conf.d)"));
so.value("ip-set", _("ip-set file (/etc/smartdns/ip-set)"));
so.value("other", _("other file (/etc/smartdns/download)"));
so.default = "list";
so.rempty = false;
so = ss.option(form.Value, 'desc', _('Description'), _('Description'));
so.rmempty = true;
so.datatype = 'string';
///////////////////////////////////////
// Proxy server settings;
///////////////////////////////////////
o = s.taboption("proxy", form.Value, "proxy_server", _("Proxy Server"), _("Proxy Server URL, format: [socks5|http]://user:pass@ip:port."));
o.datatype = 'string';
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
if (!value.match(/^(socks5|http):\/\//)) {
return _("Proxy server URL format error, format: [socks5|http]://user:pass@ip:port.");
}
return true;
}
///////////////////////////////////////
// custom settings;
///////////////////////////////////////
o = s.taboption("custom", form.TextValue, "custom_conf",
"", _("smartdns custom settings"));
o.rows = 20;
o.cfgvalue = function (section_id) {
return fs.trimmed('/etc/smartdns/custom.conf');
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/etc/smartdns/custom.conf', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
o = s.taboption("custom", form.Flag, "coredump", _("Generate Coredump"),
_("Generate Coredump file when smartdns crash, coredump file is located at /tmp/smartdns.xxx.core."));
o.rmempty = true;
o.default = o.disabled;
o = s.taboption("custom", form.ListValue, "log_level", _("Log Level"));
o.rmempty = true;
o.placeholder = "default";
o.value("", _("default"));
o.value("debug");
o.value("info");
o.value("notice");
o.value("warn");
o.value("error");
o.value("fatal");
o.value("off");
o = s.taboption("custom", form.ListValue, "log_output_mode", _("Log Output Mode"));
o.rmempty = true;
o.placeholder = _("file");
o.value("file", _("file"));
o.value("syslog", _("syslog"));
o = s.taboption("custom", form.Value, "log_size", _("Log Size"));
o.rmempty = true;
o.placeholder = "default";
o.depends("log_output_mode", "file");
o = s.taboption("custom", form.Value, "log_num", _("Log Number"));
o.rmempty = true;
o.placeholder = "default";
o.depends("log_output_mode", "file");
o = s.taboption("custom", form.Value, "log_file", _("Log File"))
o.rmempty = true
o.placeholder = "/var/log/smartdns/smartdns.log"
o.depends("log_output_mode", "file");
o = s.taboption("custom", form.Flag, "enable_audit_log", _("Enable Audit Log"));
o.rmempty = true;
o.default = o.disabled;
o.rempty = true;
o = s.taboption("custom", form.ListValue, "audit_log_output_mode", _("Audit Log Output Mode"));
o.rmempty = true;
o.placeholder = _("file");
o.value("file", _("file"));
o.value("syslog", _("syslog"));
o.depends("enable_audit_log", "1");
o = s.taboption("custom", form.Value, "audit_log_size", _("Audit Log Size"));
o.rmempty = true;
o.placeholder = "default";
o.depends({"enable_audit_log":"1", "audit_log_output_mode":"file"});
o = s.taboption("custom", form.Value, "audit_log_num", _("Audit Log Number"));
o.rmempty = true;
o.placeholder = "default";
o.depends({"enable_audit_log":"1", "audit_log_output_mode":"file"});
o = s.taboption("custom", form.Value, "audit_log_file", _("Audit Log File"))
o.rmempty = true
o.placeholder = "/var/log/smartdns/smartdns-audit.log"
o.depends({"enable_audit_log":"1", "audit_log_output_mode":"file"});
////////////////
// Upstream servers;
////////////////
s = m.section(form.GridSection, "server", _("Upstream Servers"),
_("Upstream Servers, support UDP, TCP protocol. Please configure multiple DNS servers, "
+ "including multiple foreign DNS servers."));
s.anonymous = true;
s.addremove = true;
s.sortable = true;
s.tab('general', _('General Settings'));
s.tab('advanced', _('Advanced Settings'));
// enable flag;
o = s.taboption("general", form.Flag, "enabled", _("Enable"), _("Enable"));
o.rmempty = false;
o.default = o.enabled;
o.editable = true;
// name;
o = s.taboption("general", form.Value, "name", _("DNS Server Name"), _("DNS Server Name"));
// IP address;
o = s.taboption("general", form.Value, "ip", _("ip"), _("DNS Server ip"));
o.datatype = "or(ipaddr, string)";
o.rmempty = false;
// port;
o = s.taboption("general", form.Value, "port", _("port"), _("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.taboption("general", form.ListValue, "type", _("type"), _("DNS Server type"));
o.placeholder = "udp";
o.value("udp", _("udp"));
o.value("tcp", _("tcp"));
o.value("tls", _("tls"));
o.value("https", _("https"));
o.default = "udp";
o.rempty = false;
// server group
o = s.taboption("general", form.Value, "server_group", _("Server Group"), _("DNS Server group"))
o.rmempty = true;
o.placeholder = "default";
o.datatype = "hostname";
o.rempty = true;
servers = uci.sections('smartdns', 'server');
var groupnames = new Set();
for (var i = 0; i < servers.length; i++) {
if (servers[i].server_group == undefined) {
continue;
}
groupnames.add(servers[i].server_group);
}
for (const groupname of groupnames) {
o.value(groupname);
}
// Advanced Options
o = s.taboption("advanced", form.Flag, "exclude_default_group", _("Exclude Default Group"), _("Exclude DNS Server from default group."))
o.rmempty = true;
o.default = o.disabled;
o.editable = true;
o.modalonly = true;
// blacklist_ip
o = s.taboption("advanced", form.Flag, "blacklist_ip", _("IP Blacklist Filtering"),
_("Filtering IP with blacklist"))
o.rmempty = true
o.default = o.disabled
o.modalonly = true;
// TLS host verify
o = s.taboption("advanced", form.Value, "tls_host_verify", _("TLS Hostname Verify"),
_("Set TLS hostname to verify."))
o.default = ""
o.datatype = "string"
o.rempty = true
o.modalonly = true;
o.depends("type", "tls")
o.depends("type", "https")
// certificate verify
o = s.taboption("advanced", form.Flag, "no_check_certificate", _("No check certificate"),
_("Do not check certificate."))
o.rmempty = true
o.default = o.disabled
o.modalonly = true;
o.depends("type", "tls")
o.depends("type", "https")
// SNI host name
o = s.taboption("advanced", form.Value, "host_name", _("TLS SNI name"),
_("Sets the server name indication for query. '-' for disable SNI name."))
o.default = ""
o.datatype = "hostname"
o.rempty = true
o.modalonly = true;
o.depends("type", "tls")
o.depends("type", "https")
// http host
o = s.taboption("advanced", form.Value, "http_host", _("HTTP Host"),
_("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.modalonly = true;
o.depends("type", "https")
// SPKI pin
o = s.taboption("advanced", form.Value, "spki_pin", _("TLS SPKI Pinning"),
_("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.modalonly = true;
o.depends("type", "tls")
o.depends("type", "https")
// mark
o = s.taboption("advanced", form.Value, "set_mark", _("Marking Packets"),
_("Set mark on packets."))
o.default = ""
o.rempty = true
o.datatype = "uinteger"
o.modalonly = true;
// use proxy
o = s.taboption("advanced", form.Flag, "use_proxy", _("Use Proxy"),
_("Use proxy to connect to upstream DNS server."))
o.default = o.disabled
o.modalonly = true;
o.optional = true;
o.rempty = true;
o.validate = function (section_id, value) {
var flag = this.formvalue(section_id);
if (flag == "0") {
return true;
}
var proxy_server = uci.sections("smartdns", "smartdns")[0].proxy_server;
var server_type = this.section.formvalue(section_id, "type");
if (proxy_server == "" || proxy_server == undefined) {
return _("Please set proxy server first.");
}
if (server_type == "udp" && !proxy_server.match(/^(socks5):\/\//)) {
return _("Only socks5 proxy support udp server.");
}
return true;
}
// other args
o = s.taboption("advanced", form.Value, "addition_arg", _("Additional Server Args"),
_("Additional Args for upstream dns servers"))
o.default = ""
o.rempty = true
o.modalonly = true;
////////////////
// client rules;
////////////////
s = m.section(form.TypedSection, "client-rule", _("Client Rules"), _("Client Rules Settings, can achieve parental control functionality."));
s.anonymous = true;
s.nodescriptions = true;
s.tab("basic", _('Basic Settings'));
s.tab("advanced", _('Advanced Settings'));
s.tab("block", _("DNS Block Setting"));
o = s.taboption("basic", form.Flag, "enabled", _("Enable"));
o.rmempty = false;
o.default = o.disabled;
o = s.taboption("basic", form.DynamicList, "client_addr", _("Client Address"),
_("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."));
o.rempty = true
o.rmempty = true;
o.modalonly = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
if (value.match(/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$/)) {
return true;
}
if (value.match(/^([a-fA-F0-9]*:){1,7}[a-fA-F0-9]*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/)) {
return true;
}
if (value.match(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/)) {
return true;
}
return _("Client address format error, please input ip adress or mac address.");
}
o = s.taboption("basic", form.FileUpload, "client_addr_file", _("Client Address File"),
_("Upload client address file, same as Client Address function."));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.modalonly = true;
o.root_directory = "/etc/smartdns/ip-set"
o = s.taboption("basic", form.Value, "server_group", _("Server Group"), _("DNS Server group belongs to, such as office, home."))
o.rmempty = true
o.placeholder = "default"
o.datatype = "hostname"
o.rempty = true
for (const groupname of groupnames) {
o.value(groupname);
}
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var val = uci.sections('smartdns', 'server');
for (var i = 0; i < val.length; i++) {
if (value == val[i].server_group) {
return true;
}
}
return _('Server Group %s not exists').format(value);
}
// Speed check mode;
o = s.taboption("advanced", form.Value, "speed_check_mode", _("Speed Check Mode"), _("Smartdns speed check mode."));
o.rmempty = true;
o.placeholder = "default";
o.value("", _("default"));
o.value("ping,tcp:80,tcp:443");
o.value("ping,tcp:443,tcp:80");
o.value("tcp:80,tcp:443,ping");
o.value("tcp:443,tcp:80,ping");
o.value("none", _("None"));
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
if (value == "none") {
return true;
}
var check_mode = value.split(",")
for (var i = 0; i < check_mode.length; i++) {
if (check_mode[i] == "ping") {
continue;
}
if (check_mode[i].indexOf("tcp:") == 0) {
var port = check_mode[i].split(":")[1];
if (port == "") {
return _("TCP port is empty");
}
continue;
}
return _("Speed check mode is invalid.");
}
return true;
}
// Support DualStack ip selection;
o = s.taboption("advanced", form.Flag, "dualstack_ip_selection", _("Dual-stack IP Selection"),
_("Enable IP selection between IPV4 and IPV6"));
o.rmempty = false;
o.default = o.enabled;
// Force AAAA SOA
o = s.taboption("advanced", form.Flag, "force_aaaa_soa", _("Force AAAA SOA"), _("Force AAAA SOA."));
o.rmempty = true;
o.default = o.disabled;
// Force HTTPS SOA
o = s.taboption("advanced", form.Flag, "force_https_soa", _("Force HTTPS SOA"), _("Force HTTPS SOA."));
o.rmempty = false;
o.default = o.enabled;
// ipset name;
o = s.taboption("advanced", form.Value, "ipset_name", _("IPset Name"), _("IPset name."));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var ipset = value.split(",")
for (var i = 0; i < ipset.length; i++) {
if (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\-_]+$/)) {
return _("ipset name format error, format: [#[4|6]:]ipsetname");
}
}
return true;
}
// NFTset name;
o = s.taboption("advanced", form.Value, "nftset_name", _("NFTset Name"), _("NFTset name, format: [#[4|6]:[family#table#set]]"));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var nftset = value.split(",")
for (var i = 0; i < nftset.length; i++) {
if (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+$/)) {
return _("NFTset name format error, format: [#[4|6]:[family#table#set]]");
}
}
return true;
}
// include config
download_files = uci.sections('smartdns', 'download-file');
o = s.taboption("advanced", form.DynamicList, "conf_files", _("Include Config Files /etc/smartdns/conf.d"),
_("Include other config files from /etc/smartdns/conf.d or custom path, can be downloaded from the download page."));
for (var i = 0; i < download_files.length; i++) {
if (download_files[i].type == undefined) {
continue;
}
if (download_files[i].type != 'config') {
continue
}
o.value(download_files[i].name);
}
o = s.taboption("block", form.FileUpload, "block_domain_set_file", _("Domain List File"), _("Upload domain list file."));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/domain-set"
////////////////
// domain rules;
////////////////
s = m.section(form.TypedSection, "domain-rule", _("Domain Rules"), _("Domain Rules Settings"));
s.anonymous = true;
s.nodescriptions = true;
s.tab("forwarding", _('DNS Forwarding Setting'));
s.tab("block", _("DNS Block Setting"));
s.tab("domain-rule-list", _("Domain Rule List"), _("Set Specific domain rule list."));
s.tab("domain-address", _("Domain Address"), _("Set Specific domain ip address."));
///////////////////////////////////////
// domain forwarding;
///////////////////////////////////////
o = s.taboption("forwarding", form.Value, "server_group", _("Server Group"), _("DNS Server group belongs to, such as office, home."))
o.rmempty = true
o.placeholder = "default"
o.datatype = "hostname"
o.rempty = true
for (const groupname of groupnames) {
o.value(groupname);
}
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var val = uci.sections('smartdns', 'server');
for (var i = 0; i < val.length; i++) {
if (value == val[i].server_group) {
return true;
}
}
return _('Server Group %s not exists').format(value);
}
// Speed check mode;
o = s.taboption("forwarding", form.Value, "speed_check_mode", _("Speed Check Mode"), _("Smartdns speed check mode."));
o.rmempty = true;
o.placeholder = "default";
o.value("", _("default"));
o.value("ping,tcp:80,tcp:443");
o.value("ping,tcp:443,tcp:80");
o.value("tcp:80,tcp:443,ping");
o.value("tcp:443,tcp:80,ping");
o.value("none", _("None"));
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
if (value == "none") {
return true;
}
var check_mode = value.split(",")
for (var i = 0; i < check_mode.length; i++) {
if (check_mode[i] == "ping") {
continue;
}
if (check_mode[i].indexOf("tcp:") == 0) {
var port = check_mode[i].split(":")[1];
if (port == "") {
return _("TCP port is empty");
}
continue;
}
return _("Speed check mode is invalid.");
}
return true;
}
// Support DualStack ip selection;
o = s.taboption("forwarding", form.ListValue, "dualstack_ip_selection", _("Dual-stack IP Selection"),
_("Enable IP selection between IPV4 and IPV6"));
o.rmempty = true;
o.default = "default";
o.modalonly = true;
o.value("", _("default"));
o.value("yes", _("Yes"));
o.value("no", _("No"));
o = s.taboption("forwarding", form.Flag, "force_aaaa_soa", _("Force AAAA SOA"), _("Force AAAA SOA."));
o.rmempty = true;
o.default = o.disabled;
o = s.taboption("forwarding", form.Value, "ipset_name", _("IPset Name"), _("IPset name."));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var ipset = value.split(",")
for (var i = 0; i < ipset.length; i++) {
if (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\-_]+$/)) {
return _("ipset name format error, format: [#[4|6]:]ipsetname");
}
}
return true;
}
o = s.taboption("forwarding", form.Value, "nftset_name", _("NFTset Name"), _("NFTset name, format: [#[4|6]:[family#table#set]]"));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var nftset = value.split(",")
for (var i = 0; i < nftset.length; i++) {
if (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+$/)) {
return _("NFTset name format error, format: [#[4|6]:[family#table#set]]");
}
}
return true;
}
// other args
o = s.taboption("forwarding", form.Value, "addition_flag", _("Additional Rule Flag"),
_("Additional Flags for rules, read help on domain-rule for more information."))
o.default = ""
o.rempty = true
o.modalonly = true;
o = s.taboption("forwarding", form.FileUpload, "forwarding_domain_set_file", _("Domain List File"),
_("Upload domain list file, or configure auto download from Download File Setting page."));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/domain-set"
o = s.taboption("forwarding", form.TextValue, "domain_forwarding_list",
_("Domain List"), _("Configure forwarding domain name list."));
o.rows = 10;
o.cols = 64;
o.monospace = true;
o.cfgvalue = function (section_id) {
return fs.trimmed('/etc/smartdns/domain-forwarding.list').catch(function (e) {
return "";
});
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/etc/smartdns/domain-forwarding.list', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
///////////////////////////////////////
// domain block;
///////////////////////////////////////
o = s.taboption("block", form.FileUpload, "block_domain_set_file", _("Domain List File"), _("Upload domain list file."));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/domain-set"
o = s.taboption("block", form.TextValue, "domain_block_list",
_("Domain List"), _("Configure block domain list."));
o.rows = 10;
o.cols = 64;
o.cfgvalue = function (section_id) {
return fs.trimmed('/etc/smartdns/domain-block.list').catch(function (e) {
return "";
});
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/etc/smartdns/domain-block.list', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
///////////////////////////////////////
// domain rule list;
///////////////////////////////////////
o = s.taboption('domain-rule-list', form.SectionValue, '__domain-rule-list__', form.GridSection, 'domain-rule-list', _('Domain Rule List'),
_('Configure domain rule list.'));
ss = o.subsection;
ss.addremove = true;
ss.anonymous = true;
ss.sortable = true;
// enable flag;
so = ss.option(form.Flag, "enabled", _("Enable"), _("Enable"));
so.rmempty = false;
so.default = so.enabled;
so.editable = true;
// name;
so = ss.option(form.Value, "name", _("Domain Rule Name"), _("Domain Rule Name"));
so = ss.option(form.Value, "server_group", _("Server Group"), _("DNS Server group belongs to, such as office, home."))
so.rmempty = true
so.placeholder = "default"
so.datatype = "hostname"
so.rempty = true
for (const groupname of groupnames) {
so.value(groupname);
}
so.validate = function (section_id, value) {
if (value == "") {
return true;
}
var val = uci.sections('smartdns', 'server');
for (var i = 0; i < val.length; i++) {
if (value == val[i].server_group) {
return true;
}
}
return _('Server Group %s not exists').format(value);
}
so = ss.option(form.FileUpload, "domain_list_file", _("Domain List File"),
_("Upload domain list file, or configure auto download from Download File Setting page."));
so.rmempty = true
so.datatype = "file"
so.rempty = true
so.root_directory = "/etc/smartdns/domain-set"
so = ss.option(form.ListValue, "block_domain_type", _("Block domain"), _("Block domain."));
so.rmempty = true;
so.value("none", _("None"));
so.value("all", "IPv4/IPv6");
so.value("ipv4", "IPv4");
so.value("ipv6", "IPv6");
so.modalonly = true;
// Support DualStack ip selection;
so = ss.option(form.ListValue, "dualstack_ip_selection", _("Dual-stack IP Selection"),
_("Enable IP selection between IPV4 and IPV6"));
so.rmempty = true;
so.default = "default";
so.modalonly = true;
so.value("", _("default"));
so.value("yes", _("Yes"));
so.value("no", _("No"));
so = ss.option(form.Value, "speed_check_mode", _("Speed Check Mode"), _("Smartdns speed check mode."));
so.rmempty = true;
so.placeholder = "default";
so.modalonly = true;
so.value("", _("default"));
so.value("ping,tcp:80,tcp:443");
so.value("ping,tcp:443,tcp:80");
so.value("tcp:80,tcp:443,ping");
so.value("tcp:443,tcp:80,ping");
so.value("none", _("None"));
so.validate = function (section_id, value) {
if (value == "") {
return true;
}
if (value == "none") {
return true;
}
var check_mode = value.split(",")
for (var i = 0; i < check_mode.length; i++) {
if (check_mode[i] == "ping") {
continue;
}
if (check_mode[i].indexOf("tcp:") == 0) {
var port = check_mode[i].split(":")[1];
if (port == "") {
return _("TCP port is empty");
}
continue;
}
return _("Speed check mode is invalid.");
}
return true;
}
so = ss.option(form.Flag, "force_aaaa_soa", _("Force AAAA SOA"), _("Force AAAA SOA."));
so.rmempty = true;
so.default = so.disabled;
so.modalonly = true;
so = ss.option(form.Value, "ipset_name", _("IPset Name"), _("IPset name."));
so.rmempty = true;
so.datatype = "hostname";
so.rempty = true;
so.modalonly = true;
so = ss.option(form.Value, "nftset_name", _("NFTset Name"), _("NFTset name, format: [#[4|6]:[family#table#set]]"));
so.rmempty = true;
so.datatype = "string";
so.rempty = true;
so.modalonly = true;
so.validate = function (section_id, value) {
if (value == "") {
return true;
}
var nftset = value.split(",")
for (var i = 0; i < nftset.length; i++) {
if (!nftset[i].match(/#[4|6]:[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+$/)) {
return _("NFTset name format error, format: [#[4|6]:[family#table#set]]");
}
}
return true;
}
// other args
so = ss.option(form.Value, "addition_flag", _("Additional Rule Flag"),
_("Additional Flags for rules, read help on domain-rule for more information."))
so.default = ""
so.rempty = true
so.modalonly = true;
///////////////////////////////////////
// domain address
///////////////////////////////////////
o = s.taboption("domain-address", form.TextValue, "address_conf",
"",
_("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."));
o.rows = 20;
o.cfgvalue = function (section_id) {
return fs.trimmed('/etc/smartdns/address.conf');
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/etc/smartdns/address.conf', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
////////////////
// ip rules;
////////////////
s = m.section(form.TypedSection, "ip-rule", _("IP Rules"), _("IP Rules Settings"));
s.anonymous = true;
s.nodescriptions = true;
s.tab("ip-rule-list", _("IP Rule List"), _("Set Specific ip rule list."));
s.tab("blackip-list", _("IP Blacklist"), _("Set Specific ip blacklist."));
///////////////////////////////////////
// ip rule list;
///////////////////////////////////////
o = s.taboption('ip-rule-list', form.SectionValue, '__ip-rule-list__', form.GridSection, 'ip-rule-list', _('IP Rule List'),
_('Configure ip rule list.'));
ss = o.subsection;
ss.addremove = true;
ss.anonymous = true;
ss.sortable = true;
// enable flag;
so = ss.option(form.Flag, "enabled", _("Enable"), _("Enable"));
so.rmempty = false;
so.default = so.enabled;
so.editable = true;
// name;
so = ss.option(form.Value, "name", _("IP Rule Name"), _("IP Rule Name"));
so.rmempty = true;
so.datatype = "string";
so = ss.option(form.FileUpload, "ip_set_file", _("IP Set File"), _("Upload IP set file."));
so.rmempty = true
so.datatype = "file"
so.modalonly = true;
so.root_directory = "/etc/smartdns/ip-set"
so = ss.option(form.DynamicList, "ip_addr", _("IP Addresses"), _("IP addresses, CIDR format."));
so.rmempty = true;
so.datatype = "ipaddr"
so.modalonly = true;
so = ss.option(form.Flag, "whitelist_ip", _("Whitelist IP"), _("Whitelist IP Rule, Accept IP addresses within the range."));
so.rmempty = true;
so.default = so.disabled;
so.modalonly = true;
so = ss.option(form.Flag, "blacklist_ip", _("Blacklist IP"), _("Blacklist IP Rule, Decline IP addresses within the range."));
so.rmempty = true;
so.default = so.disabled;
so.modalonly = true;
so = ss.option(form.Flag, "ignore_ip", _("Ignore IP"), _("Do not use these IP addresses."));
so.rmempty = true;
so.default = so.disabled;
so.modalonly = true;
so = ss.option(form.Flag, "bogus_nxdomain", _("Bogus nxdomain"), _("Return SOA when the requested result contains a specified IP address."));
so.rmempty = true;
so.default = so.disabled;
so.modalonly = true;
so = ss.option(form.DynamicList, "ip_alias", _("IP alias"), _("IP Address Mapping, Can be used for CDN acceleration with Anycast IP, such as Cloudflare's CDN."));
so.rmempty = true;
so.datatype = 'ipaddr("nomask")';
so.modalonly = true;
// other args
so = ss.option(form.Value, "addition_flag", _("Additional Rule Flag"),
_("Additional Flags for rules, read help on ip-rule for more information."))
so.default = ""
so.rempty = true
so.modalonly = true;
///////////////////////////////////////
// IP Blacklist;
///////////////////////////////////////
// blacklist;
o = s.taboption("blackip-list", form.TextValue, "blackip_ip_conf",
"", _("Configure IP blacklists that will be filtered from the results of specific DNS server."));
o.rows = 20;
o.cfgvalue = function (section_id) {
return fs.trimmed('/etc/smartdns/blacklist-ip.conf');
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/etc/smartdns/blacklist-ip.conf', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
////////////////
// Support
////////////////
s = m.section(form.TypedSection, "smartdns", _("Technical Support"),
_("If you like this software, please buy me a cup of coffee."));
s.anonymous = true;
o = s.option(form.Button, "web");
o.title = _("SmartDNS official website");
o.inputtitle = _("open website");
o.inputstyle = "apply";
o.onclick = function () {
window.open("https://pymumu.github.io/smartdns", '_blank');
};
o = s.option(form.Button, "report");
o.title = _("Report bugs");
o.inputtitle = _("Report bugs");
o.inputstyle = "apply";
o.onclick = function () {
window.open("https://github.com/pymumu/smartdns/issues", '_blank');
};
o = s.option(form.Button, "Donate");
o.title = _("Donate to smartdns");
o.inputtitle = _("Donate");
o.inputstyle = "apply";
o.onclick = function () {
window.open("https://pymumu.github.io/smartdns/#donate", '_blank');
};
o = s.option(form.DummyValue, "_restart", _("Restart Service"));
o.renderWidget = function () {
return E('button', {
'class': 'btn cbi-button cbi-button-apply',
'id': 'btn_restart',
'click': ui.createHandlerFn(this, function () {
return fs.exec('/etc/init.d/smartdns', ['restart'])
.catch(function (e) { ui.addNotification(null, E('p', e.message), 'error') });
})
}, [_("Restart")]);
}
return m.render();
}
});
================================================
FILE: package/luci/make.sh
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
CURR_DIR=$(cd $(dirname $0);pwd)
VER="`date +"1.%Y.%m.%d-%H%M"`"
SMARTDNS_DIR=$CURR_DIR/../../
PO2LMO=
showhelp()
{
echo "Usage: make [OPTION]"
echo "Options:"
echo " -o output directory."
echo " --arch archtecture."
echo " --ver version."
echo " -h show this message."
}
build_tool()
{
make -C $ROOT/tool/po2lmo -j
PO2LMO="$ROOT/tool/po2lmo/src/po2lmo"
}
clean_tool()
{
make -C $ROOT/tool/po2lmo clean
}
build()
{
ROOT=/tmp/luci-app-smartdns
rm -fr $ROOT
mkdir -p $ROOT
cp $CURR_DIR/* $ROOT/ -af
cp $CURR_DIR/../tool $ROOT/ -af
cd $ROOT/
build_tool
mkdir $ROOT/root/usr/lib/lua/luci -p
mkdir $ROOT/root/usr/share/rpcd/acl.d/ -p
cp $ROOT/files/luci/i18n $ROOT/root/usr/lib/lua/luci/ -avf
#Generate Language
$PO2LMO $ROOT/files/luci/i18n/smartdns.zh-cn.po $ROOT/root/usr/lib/lua/luci/i18n/smartdns.zh-cn.lmo
rm $ROOT/root/usr/lib/lua/luci/i18n/smartdns.zh-cn.po
cp $ROOT/files/root/* $ROOT/root/ -avf
INST_SIZE="`du -sb $ROOT/root/ | awk '{print $1}'`"
sed -i "s/^Architecture.*/Architecture: all/g" $ROOT/control/control
sed -i "s/Version:.*/Version: $VER/" $ROOT/control/control
if [ ! -z "$INST_SIZE" ]; then
echo "Installed-Size: $INST_SIZE" >> $ROOT/control/control
fi
cd $ROOT/control
chmod +x *
tar zcf ../control.tar.gz ./
cd $ROOT
tar zcf $ROOT/data.tar.gz -C root .
tar zcf $OUTPUTDIR/luci-app-smartdns.$VER.$FILEARCH.ipk ./control.tar.gz ./data.tar.gz ./debian-binary
which apk >/dev/null 2>&1
if [ $? -eq 0 ]; then
APK_VER="`echo $VER | sed 's/[-]/-r/'`"
ARCH="`echo $ARCH | sed 's/all/noarch/g'`"
apk mkpkg \
--info "name:luci-app-smartdns" \
--info "version:$APK_VER" \
--info "description:smartdns luci" \
--info "arch:$ARCH" \
--info "license:GPL" \
--info "origin: https://github.com/pymumu/smartdns.git" \
--info "depends:libc smartdns" \
--script "post-install:$ROOT/control/postinst" \
--script "pre-deinstall:$ROOT/control/prerm" \
--files "$ROOT/root/" \
--output "$OUTPUTDIR/luci-app-smartdns.$VER.$FILEARCH.apk"
if [ $? -ne 0 ]; then
echo "build apk package failed."
rm -fr $ROOT/
return 1
fi
else
echo "== warning: apk tool not found, skip build apk package. =="
fi
rm -fr $ROOT/
}
main()
{
OPTS=`getopt -o o:h --long arch:,ver:,filearch: \
-n "" -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$OPTS"
while true; do
case "$1" in
--arch)
ARCH="$2"
shift 2;;
--filearch)
FILEARCH="$2"
shift 2;;
--ver)
VER="$2"
shift 2;;
-o )
OUTPUTDIR="$2"
shift 2;;
-h | --help )
showhelp
return 0
shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if [ -z "$ARCH" ]; then
echo "please input arch."
return 1;
fi
if [ -z "$FILEARCH" ]; then
FILEARCH=$ARCH
fi
if [ -z "$OUTPUTDIR" ]; then
OUTPUTDIR=$CURR_DIR;
fi
build
}
main $@
exit $?
================================================
FILE: package/luci-compat/control/control
================================================
Package: luci-app-smartdns
Version: git-18.201.27126-7bf0367-1
Depends: libc, smartdns
Source: feeds/luci/applications/luci-app-smartdns
Section: luci
Architecture: all
Description: A smartdns server
================================================
FILE: package/luci-compat/control/postinst
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
[ "${IPKG_NO_SCRIPT}" = "1" ] && exit 0
[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0
. ${IPKG_INSTROOT}/lib/functions.sh
default_postinst $0 $@
================================================
FILE: package/luci-compat/control/prerm
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0
. ${IPKG_INSTROOT}/lib/functions.sh
default_prerm $0 $@
================================================
FILE: package/luci-compat/debian-binary
================================================
2.0
================================================
FILE: package/luci-compat/files/etc/uci-defaults/50_luci-smartdns
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
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
================================================
FILE: package/luci-compat/files/luci/controller/smartdns.lua
================================================
--
-- Copyright (C) 2018-2025 Ruilin Peng (Nick) .
--
-- 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 .
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: package/luci-compat/files/luci/i18n/smartdns.zh-cn.po
================================================
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 "设置服务器组,例如office,home"
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 /etc/smartdns/conf.d"
msgstr "包含配置文件 /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 服务器,支持 UDP,TCP 协议。请配置多个上游 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"
================================================
FILE: package/luci-compat/files/luci/model/cbi/smartdns/smartdns.lua
================================================
--
-- Copyright (C) 2018-2025 Ruilin Peng (Nick) .
--
-- 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 .
require ("nixio.fs")
require ("luci.http")
require ("luci.dispatcher")
require ("nixio.fs")
local uci = require "luci.model.uci".cursor()
m = Map("smartdns")
m.title = translate("SmartDNS Server")
m.description = translate("SmartDNS is a local high-performance DNS server, supports finding fastest IP, supports ad filtering, and supports avoiding DNS poisoning.")
m:section(SimpleSection).template = "smartdns/smartdns_status"
-- Basic
s = m:section(TypedSection, "smartdns", translate("Settings"), translate("General Settings"))
s.anonymous = true
s:tab("settings", translate("General Settings"))
s:tab("advanced", translate('Advanced Settings'))
s:tab("seconddns", translate("Second Server Settings"))
s:tab("dns64", translate("DNS64 Server Settings"))
s:tab("proxy", translate("Proxy Server Settings"))
s:tab("custom", translate("Custom Settings"))
---- Eanble
o = s:taboption("settings", Flag, "enabled", translate("Enable"), translate("Enable or disable smartdns server"))
o.default = o.disabled
o.rempty = false
---- server name
o = s:taboption("settings", Value, "server_name", translate("Server Name"), translate("Smartdns server name"))
o.placeholder = "server name"
o.datatype = "hostname"
o.rempty = true
---- Port
o = s:taboption("settings", Value, "port", translate("Local Port"),
translate("Smartdns local server port, smartdns will be automatically set as main dns when the port is 53."))
o.placeholder = 53
o.default = 53
o.datatype = "port"
o.rempty = false
-- Automatically Set Dnsmasq
o = s:taboption("settings", Flag, "auto_set_dnsmasq", translate("Automatically Set Dnsmasq"), translate("Automatically set as upstream of dnsmasq when port changes."))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- Speed check mode;
o = s:taboption("advanced", Value, "speed_check_mode", translate("Speed Check Mode"), translate("Smartdns speed check mode."));
o.rmempty = true;
o.placeholder = "default";
o:value("", translate("default"))
o:value("ping,tcp:80,tcp:443");
o:value("ping,tcp:443,tcp:80");
o:value("tcp:80,tcp:443,ping");
o:value("tcp:443,tcp:80,ping");
o:value("none", translate("None"));
function o.validate (section_id, value)
if value == "" then
return value
end
if value == nil then
return nil, translate("Speed check mode is invalid.")
end
if value == "none" then
return value
end
local mode = value:split(",");
for _, v in ipairs(mode) do repeat
if v == "ping" then
break
end
if v == nil then
return nil, translate("Speed check mode is invalid.")
end
local port = v:split(":");
if "tcp" == port[1] then
if tonumber(port[2]) then
break
end
end
return nil, translate("Speed check mode is invalid.")
until true end
return value
end
---- response mode;
o = s:taboption("advanced", ListValue, "response_mode", translate("Response Mode"),
translate("Smartdns response mode, First Ping: return the first ping IP, Fastest IP: return the fastest IP, Fastest Response: return the fastest DNS response."))
o.rmempty = true
o.placeholder = "default"
o:value("", translate("default"))
o:value("first-ping", translate("First Ping"))
o:value("fastest-ip", translate("Fastest IP"))
o:value("fastest-response", translate("Fastest Response"))
---- Enable TCP server
o = s:taboption("advanced", Flag, "tcp_server", translate("TCP Server"), translate("Enable TCP DNS Server"))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "1"
end
---- Enable DOT server;
o = s:taboption("advanced", Flag, "tls_server", translate("DOT Server"), translate("Enable DOT DNS Server"))
o.rmempty = false
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
o = s:taboption("advanced", Value, "tls_server_port", translate("DOT Server Port"), translate("Smartdns DOT server port."))
o.placeholder = 853
o.default = 853
o.datatype = "port"
o.rempty = false
o:depends('tls_server', '1')
---- Enable DOH server;
o = s:taboption("advanced", Flag, "doh_server", translate("DOH Server"), translate("Enable DOH DNS Server"))
o.rmempty = false
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
o = s:taboption("advanced", Value, "doh_server_port", translate("DOH Server Port"), translate("Smartdns DOH server port."))
o.placeholder = 843
o.default = 843
o.datatype = "port"
o.rempty = false
o:depends('doh_server', '1')
o = s:taboption("advanced", Value, "bind_cert", translate("Server Cert"), translate("Server certificate file path."))
o.datatype = "string"
o.placeholder = "/var/etc/smartdns/smartdns/smartdns-cert.pem"
o.rempty = true
o:depends('tls_server', '1')
o:depends('doh_server', '1')
o = s:taboption("advanced", Value, "bind_cert_key", translate("Server Cert Key"), translate("Server certificate key file path."))
o.datatype = "string"
o.placeholder = "/var/etc/smartdns/smartdns/smartdns-key.pem"
o.rempty = false
o:depends('tls_server', '1')
o:depends('doh_server', '1')
o = s:taboption("advanced", Value, "bind_cert_key_pass", translate("Server Cert Key Pass"), translate("Server certificate key file password."))
o.datatype = "string"
o.rempty = false
o:depends('tls_server', '1')
o:depends('doh_server', '1')
---- Support IPV6
o = s:taboption("advanced", Flag, "ipv6_server", translate("IPV6 Server"), translate("Enable IPV6 DNS Server"))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "1"
end
---- bind to device;
o = s:taboption("advanced", Flag, "bind_device", translate("Bind Device"), translate("Listen only on the specified interfaces."))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "1"
end
---- bind device name;
o = s:taboption("advanced", Value, "bind_device_name", translate("Bind Device Name"), translate("Name of device name listen on."))
o.placeholder = "default"
o.rempty = true
o.datatype = "string"
---- Support DualStack ip selection
o = s:taboption("advanced", Flag, "dualstack_ip_selection", translate("Dual-stack IP Selection"), translate("Enable IP selection between IPV4 and IPV6"))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- Domain prefetch load
o = s:taboption("advanced", Flag, "prefetch_domain", translate("Domain prefetch"), translate("Enable domain prefetch, accelerate domain response speed."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- Domain Serve expired
o = s:taboption("advanced", Flag, "serve_expired", translate("Serve expired"),
translate("Attempts to serve old responses from cache with a TTL of 0 in the response without waiting for the actual resolution to finish."))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- cache-size
o = s:taboption("advanced", Value, "cache_size", translate("Cache Size"), translate("DNS domain result cache size"))
o.rempty = true
---- cache-persist;
o = s:taboption("advanced", Flag, "cache_persist", translate("Cache Persist"), translate("Write cache to disk on exit and load on startup."))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "1"
end
-- resolve local hostname
o = s:taboption("advanced", Flag, "resolve_local_hostnames", translate("Resolve Local Hostnames"), translate("Resolve local hostnames by reading Dnsmasq lease file."))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "1"
end
-- resolve local network hostname via mDNS
o = s:taboption("advanced", Flag, "mdns_lookup", translate("mDNS Lookup"), translate("Resolve local network hostname via mDNS protocol."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
-- Force AAAA SOA
o = s:taboption("advanced", Flag, "force_aaaa_soa", translate("Force AAAA SOA"), translate("Force AAAA SOA."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
-- Force HTTPS SOA
o = s:taboption("advanced", Flag, "force_https_soa", translate("Force HTTPS SOA"), translate("Force HTTPS SOA."))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "1"
end
o = s:taboption("advanced", Value, "ipset_name", translate("IPset Name"), translate("IPset name."))
o.rmempty = true
o.datatype = "string"
o.rempty = true
---- Ipset no speed.
o = s:taboption("advanced", Value, "ipset_no_speed", translate("No Speed IPset Name"),
translate("Ipset name, Add domain result to ipset when speed check fails."));
o.rmempty = true;
o.datatype = "hostname";
o.rempty = true;
o = s:taboption("advanced", Value, "nftset_name", translate("NFTset Name"), translate("NFTset name, format: [#[4|6]:[family#table#set]]"))
o.rmempty = true
o.datatype = "string"
o.rempty = true
function o.validate(self, value)
if (value == "") then
return value
end
if (value:match("#[4|6]:[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+$")) then
return value
end
return nil, translate("NFTset name format error, format: [#[4|6]:[family#table#set]]")
end
---- NFTset no speed.
o = s:taboption("advanced", Value, "nftset_no_speed", translate("No Speed NFTset Name"),
translate("Nftset name, Add domain result to nftset when speed check fails, format: [#[4|6]:[family#table#set]]"));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
function o.validate(self, value)
if (value == "") then
return value
end
if (value:match("#[4|6]:[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+$")) then
return value
end
return nil, translate("NFTset name format error, format: [#[4|6]:[family#table#set]]")
end
---- rr-ttl
o = s:taboption("advanced", Value, "rr_ttl", translate("Domain TTL"), translate("TTL for all domain result."))
o.rempty = true
---- rr-ttl-min
o = s:taboption("advanced", Value, "rr_ttl_min", translate("Domain TTL Min"), translate("Minimum TTL for all domain result."))
o.rempty = true
o.placeholder = "600"
o.default = 600
o.optional = true
---- rr-ttl-max
o = s:taboption("advanced", Value, "rr_ttl_max", translate("Domain TTL Max"), translate("Maximum TTL for all domain result."))
o.rempty = true
---- rr-ttl-reply-max
o = s:taboption("advanced", Value, "rr_ttl_reply_max", translate("Reply Domain TTL Max"), translate("Reply maximum TTL for all domain result."))
o.rempty = true
o = s:taboption("advanced", DynamicList, "conf_files", translate("Include Config Files /etc/smartdns/conf.d"),
translate("Include other config files from /etc/smartdns/conf.d or custom path, can be downloaded from the download page."))
o.rmempty = true
uci:foreach("smartdns", "download-file", function(section)
local filetype = section.type
if (filetype ~= 'config') then
return
end
o:value(section.name);
end)
o = s:taboption("advanced", DynamicList, "hosts_files", translate("Hosts File"), translate("Include hosts file."))
o.rmempty = true
uci:foreach("smartdns", "download-file", function(section)
local filetype = section.type
if (filetype ~= 'other') then
return
end
o:value(section.name);
end)
---- other args
o = s:taboption("advanced", Value, "server_flags", translate("Additional Server Args"), translate("Additional server args, refer to the help description of the bind option."))
o.default = ""
o.rempty = true
o.optional = true
---- second dns server
---- Eanble
o = s:taboption("seconddns", Flag, "seconddns_enabled", translate("Enable"), translate("Enable or disable second DNS server."))
o.default = o.disabled
o.rempty = false
---- Port
o = s:taboption("seconddns", Value, "seconddns_port", translate("Local Port"), translate("Smartdns local server port"))
o.placeholder = 6553
o.default = 6553
o.datatype = "port"
o.rempty = false
---- Enable TCP server
o = s:taboption("seconddns", Flag, "seconddns_tcp_server", translate("TCP Server"), translate("Enable TCP DNS Server"))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "1"
end
---- dns server group
o = s:taboption("seconddns", Value, "seconddns_server_group", translate("Server Group"), translate("Query DNS through specific dns server group, such as office, home."))
o.rmempty = true
o.placeholder = "default"
o.datatype = "hostname"
o.rempty = true
o = s:taboption("seconddns", Flag, "seconddns_no_speed_check", translate("Skip Speed Check"), translate("Do not check speed."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- skip address rules
o = s:taboption("seconddns", Flag, "seconddns_no_rule_addr", translate("Skip Address Rules"), translate("Skip address rules."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- skip name server rules
o = s:taboption("seconddns", Flag, "seconddns_no_rule_nameserver", translate("Skip Nameserver Rule"), translate("Skip nameserver rules."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- skip ipset rules
o = s:taboption("seconddns", Flag, "seconddns_no_rule_ipset", translate("Skip Ipset Rule"), translate("Skip ipset rules."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- skip soa address rule
o = s:taboption("seconddns", Flag, "seconddns_no_rule_soa", translate("Skip SOA Address Rule"), translate("Skip SOA address rules."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
o = s:taboption("seconddns", Flag, "seconddns_no_dualstack_selection", translate("Skip Dualstack Selection"), translate("Skip Dualstack Selection."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- skip cache
o = s:taboption("seconddns", Flag, "seconddns_no_cache", translate("Skip Cache"), translate("Skip Cache."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
---- Force AAAA SOA
o = s:taboption("seconddns", Flag, "seconddns_force_aaaa_soa", translate("Force AAAA SOA"), translate("Force AAAA SOA."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
o = s:taboption("seconddns", Flag, "seconddns_force_https_soa", translate("Force HTTPS SOA"), translate("Force HTTPS SOA."))
o.rmempty = false
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
o = s:taboption("seconddns", Flag, "seconddns_no_ip_alias", translate("Skip IP Alias"))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
o = s:taboption("seconddns", Value, "seconddns_ipset_name", translate("IPset Name"), translate("IPset name."))
o.rmempty = true
o.datatype = "string"
o.rempty = true
o = s:taboption("seconddns", Value, "seconddns_nftset_name", translate("NFTset Name"), translate("NFTset name, format: [#[4|6]:[family#table#set]]"))
o.rmempty = true
o.datatype = "string"
o.rempty = true
function o.validate(self, value)
if (value == "") then
return value
end
if (value:match("#[4|6]:[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+$")) then
return value
end
return nil, translate("NFTset name format error, format: [#[4|6]:[family#table#set]]")
end
---- other args
o = s:taboption("seconddns", Value, "seconddns_server_flags", translate("Additional Server Args"), translate("Additional server args, refer to the help description of the bind option."))
o.default = ""
o.rempty = true
o.optional = true
----- Proxy server settings
o = s:taboption("proxy", Value, "proxy_server", translate("Proxy Server"), translate("Proxy Server URL, format: [socks5|http]://user:pass@ip:port."));
o.datatype = 'string';
function o.validate(self, value)
if (value == "") then
return true
end
if (not value:match("^http://") and not value:match("^socks5://")) then
return nil, translate("Proxy server URL format error, format: [socks5|http]://user:pass@ip:port.")
end
return value
end
----- dns64 server settings
o = s:taboption("dns64", Value, "dns64", translate("DNS64"));
o.placeholder = "64:ff9b::/96"
o.datatype = 'ip6addr'
o.rmempty = true
----- custom settings
custom = s:taboption("custom", Value, "Custom Settings",
translate(""),
translate("smartdns custom settings"))
custom.template = "cbi/tvalue"
custom.rows = 20
function custom.cfgvalue(self, section)
return nixio.fs.readfile("/etc/smartdns/custom.conf")
end
function custom.write(self, section, value)
value = value:gsub("\r\n?", "\n")
nixio.fs.writefile("/etc/smartdns/custom.conf", value)
end
o = s:taboption("custom", Flag, "coredump", translate("Generate Coredump"), translate("Generate Coredump file when smartdns crash, coredump file is located at /tmp/smartdns.xxx.core."))
o.rmempty = true
o.default = o.disabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
o = s:taboption("custom", ListValue, "log_level", translate("Log Level"))
o.rmempty = true
o.placeholder = "default"
o:value("", translate("default"))
o:value("debug")
o:value("info")
o:value("notice")
o:value("warn")
o:value("error")
o:value("fatal")
o:value("off")
o = s:taboption("custom", ListValue, "log_output_mode", translate("Log Output Mode"));
o.rmempty = true;
o.placeholder = translate("file");
o:value("file", translate("file"));
o:value("syslog", translate("syslog"));
o = s:taboption("custom", Value, "log_size", translate("Log Size"));
o.rmempty = true;
o.placeholder = "default";
o:depends("log_output_mode", "file");
o = s:taboption("custom", Value, "log_num", translate("Log Number"));
o.rmempty = true;
o.placeholder = "default";
o:depends("log_output_mode", "file");
o = s:taboption("custom", Value, "log_file", translate("Log File"))
o.rmempty = true
o.placeholder = "/var/log/smartdns/smartdns.log"
o:depends("log_output_mode", "file");
o = s:taboption("custom", Flag, "enable_audit_log", translate("Enable Audit Log"));
o.rmempty = true;
o.default = o.disabled;
o.rempty = true;
o = s:taboption("custom", ListValue, "audit_log_output_mode", translate("Audit Log Output Mode"));
o.rmempty = true;
o.placeholder = translate("file");
o:value("file", translate("file"));
o:value("syslog", translate("syslog"));
o:depends("enable_audit_log", "1");
o = s:taboption("custom", Value, "audit_log_size", translate("Audit Log Size"));
o.rmempty = true;
o.placeholder = "default";
o:depends({enable_audit_log = "1", audit_log_output_mode = "file"});
o = s:taboption("custom", Value, "audit_log_num", translate("Audit Log Number"));
o.rmempty = true;
o.placeholder = "default";
o:depends({enable_audit_log = "1", audit_log_output_mode = "file"});
o = s:taboption("custom", Value, "audit_log_file", translate("Audit Log File"))
o.rmempty = true
o.placeholder = "/var/log/smartdns/smartdns-audit.log"
o:depends({enable_audit_log = "1", audit_log_output_mode = "file"});
-- Upstream servers
s = m:section(TypedSection, "server", translate("Upstream Servers"), translate("Upstream Servers, support UDP, TCP protocol. " ..
"Please configure multiple DNS servers, including multiple foreign DNS servers."))
s.anonymous = true
s.addremove = true
s.template = "cbi/tblsection"
s.extedit = luci.dispatcher.build_url("admin/services/smartdns/upstream/%s")
---- enable flag
o = s:option(Flag, "enabled", translate("Enable"), translate("Enable"))
o.rmempty = false
o.default = o.enabled
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "1"
end
---- 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(ipaddr, 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
-- client rules;
s = m:section(TypedSection, "client-rule", translate("Client Rules"), translate("Client Rules Settings, can achieve parental control functionality."))
s.anonymous = true;
s.nodescriptions = true;
s:tab("basic", translate('Basic Settings'))
s:tab("advanced", translate('Advanced Settings'))
s:tab("block", translate("DNS Block Setting"))
o = s:taboption("basic", Flag, "enabled", translate("Enable"))
o.rmempty = false;
o.default = o.disabled;
o = s:taboption("basic", DynamicList, "client_addr", translate("Client Address"),
translate("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."))
o.rempty = true
o.rmempty = true;
o.datatype = "string"
o = s:taboption("basic", FileUpload, "client_addr_file", translate("Client Address File"),
translate("Upload client address file, same as Client Address function."))
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.root_directory = "/etc/smartdns/ip-set"
o = s:taboption("basic", 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
uci:foreach("smartdns", "server", function(section)
local server_group = section.server_group
if server_group == nil then
return
end
o:value(server_group);
end)
function o.validate (section_id, value)
if value == "" then
return value
end
if value == nil then
return nil, translate('Server Group not exists')
end
local exists = false
uci:foreach("smartdns", "server", function(section)
local server_group = section.server_group
if (exists == true) then
return
end
if (value == server_group) then
exists = true
end
end)
if exists == false then
return nil, translate('Server Group not exists')
end
return value;
end
-- Speed check mode;
o = s:taboption("advanced", Value, "speed_check_mode", translate("Speed Check Mode"), translate("Smartdns speed check mode."))
o.rmempty = true;
o.placeholder = "default";
o:value("", translate("default"))
o:value("ping,tcp:80,tcp:443");
o:value("ping,tcp:443,tcp:80");
o:value("tcp:80,tcp:443,ping");
o:value("tcp:443,tcp:80,ping");
o:value("none", translate("None"));
function o.validate (section_id, value)
if value == "" then
return value
end
if value == nil then
return nil, translate("Speed check mode is invalid.")
end
if value == "none" then
return value
end
local mode = value:split(",");
for _, v in ipairs(mode) do repeat
if v == "ping" then
break
end
if v == nil then
return nil, translate("Speed check mode is invalid.")
end
local port = v:split(":");
if "tcp" == port[1] then
if tonumber(port[2]) then
break
end
end
return nil, translate("Speed check mode is invalid.")
until true end
return value
end
-- Support DualStack ip selection;
o = s:taboption("advanced", Flag, "dualstack_ip_selection", translate("Dual-stack IP Selection"),
translate("Enable IP selection between IPV4 and IPV6"))
o.rmempty = false
o.default = o.enabled
-- Force AAAA SOA
o = s:taboption("advanced", Flag, "force_aaaa_soa", translate("Force AAAA SOA"), translate("Force AAAA SOA."))
o.rmempty = true
o.default = o.disabled
-- Force HTTPS SOA
o = s:taboption("advanced", Flag, "force_https_soa", translate("Force HTTPS SOA"), translate("Force HTTPS SOA."))
o.rmempty = false
o.default = o.enabled
-- ipset name
o = s:taboption("advanced", Value, "ipset_name", translate("IPset Name"), translate("IPset name."))
o.rmempty = true
o.datatype = "string"
o.rempty = true
-- NFTset name
o = s:taboption("advanced", Value, "nftset_name", translate("NFTset Name"), translate("NFTset name, format: [#[4|6]:[family#table#set]]"))
o.rmempty = true
o.datatype = "string"
o.rempty = true
function o.validate(self, value)
if (value == "") then
return value
end
if (value:match("#[4|6]:[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+$")) then
return value
end
return nil, translate("NFTset name format error, format: [#[4|6]:[family#table#set]]")
end
-- include config
o = s:taboption("advanced", DynamicList, "conf_files", translate("Include Config Files /etc/smartdns/conf.d"),
translate("Include other config files from /etc/smartdns/conf.d or custom path, can be downloaded from the download page."))
o.rmempty = true
uci:foreach("smartdns", "download-file", function(section)
local filetype = section.type
if (filetype ~= 'config') then
return
end
o:value(section.name);
end)
o = s:taboption("block", FileUpload, "block_domain_set_file", translate("Domain List File"), translate("Upload domain list file."));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/domain-set"
---- domain rules;
s = m:section(TypedSection, "domain-rule", translate("Domain Rules"), translate("Domain Rules Settings"))
s.anonymous = true
s.nodescriptions = true
s:tab("forwarding", translate('DNS Forwarding Setting'))
s:tab("block", translate("DNS Block Setting"))
s:tab("domain-address", translate("Domain Address"), translate("Set Specific domain ip address."))
s:tab("ip-alias", translate('IP Alias Setting'))
s:tab("blackip-list", translate("IP Blacklist"), translate("Set Specific ip blacklist."))
---- domain forwarding;
o = s:taboption("forwarding", 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
uci:foreach("smartdns", "server", function(section)
local server_group = section.server_group
if server_group == nil then
return
end
o:value(server_group);
end)
function o.validate (section_id, value)
if value == "" then
return value
end
if value == nil then
return nil, translate('Server Group not exists')
end
local exists = false
uci:foreach("smartdns", "server", function(section)
local server_group = section.server_group
if (exists == true) then
return
end
if (value == server_group) then
exists = true
end
end)
if exists == false then
return nil, translate('Server Group not exists')
end
return value;
end
o = s:taboption("forwarding", Value, "speed_check_mode", translate("Speed Check Mode"), translate("Smartdns speed check mode."))
o.rmempty = true;
o.placeholder = "default";
o:value("", translate("default"))
o:value("ping,tcp:80,tcp:443");
o:value("ping,tcp:443,tcp:80");
o:value("tcp:80,tcp:443,ping");
o:value("tcp:443,tcp:80,ping");
o:value("none", translate("None"));
function o.validate (section_id, value)
if value == "" then
return value
end
if value == nil then
return nil, translate("Speed check mode is invalid.")
end
if value == "none" then
return value
end
local mode = value:split(",");
for _, v in ipairs(mode) do repeat
if v == "ping" then
break
end
if v == nil then
return nil, translate("Speed check mode is invalid.")
end
local port = v:split(":");
if "tcp" == port[1] then
if tonumber(port[2]) then
break
end
end
return nil, translate("Speed check mode is invalid.")
until true end
return value
end
o = s:taboption("forwarding", Flag, "force_aaaa_soa", translate("Force AAAA SOA"), translate("Force AAAA SOA."))
o.rmempty = true
o.default = o.disabled
o = s:taboption("forwarding", Value, "ipset_name", translate("IPset Name"), translate("IPset name."))
o.rmempty = true
o.datatype = "hostname"
o.rempty = true
o = s:taboption("forwarding", Value, "nftset_name", translate("NFTset Name"), translate("NFTset name, format: [#[4|6]:[family#table#set]]"))
o.rmempty = true
o.datatype = "string"
o.rempty = true
function o.validate(self, value)
if (value == "") then
return value
end
if (value:match("#[4|6]:[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+#[a-zA-Z0-9%-_]+$")) then
return value
end
return nil, translate("NFTset name format error, format: [#[4|6]:[family#table#set]]")
end
---- other args
o = s:taboption("forwarding", Value, "addition_flag", translate("Additional Rule Flag"), translate("Additional Flags for rules, read help on domain-rule for more information."))
o.default = ""
o.rempty = true
o.modalonly = true;
o = s:taboption("forwarding", FileUpload, "forwarding_domain_set_file", translate("Domain List File"),
translate("Upload domain list file, or configure auto download from Download File Setting page."))
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/domain-set"
o = s:taboption("forwarding", TextValue, "domain_forwarding_list",
translate("Domain List"), translate("Configure forwarding domain name list."))
o.rows = 10
o.cols = 64
o.monospace = true
function o.cfgvalue(self, section)
return nixio.fs.readfile("/etc/smartdns/domain-forwarding.list")
end
function o.write(self, section, value)
value = value:gsub("\r\n?", "\n")
nixio.fs.writefile("/etc/smartdns/domain-forwarding.list", value)
end
---- domain block;
o = s:taboption("block", FileUpload, "block_domain_set_file", translate("Domain List File"), translate("Upload domain list file."))
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/domain-set"
o = s:taboption("block", TextValue, "domain_block_list",
translate("Domain List"), translate("Configure block domain list."))
o.rows = 10
o.cols = 64
function o.cfgvalue(self, section)
return nixio.fs.readfile("/etc/smartdns/domain-block.list")
end
function o.write(self, section, value)
value = value:gsub("\r\n?", "\n")
nixio.fs.writefile("/etc/smartdns/domain-block.list", value)
end
-- Doman addresss
addr = s:taboption("domain-address", Value, "dummy_address",
translate(""),
translate("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."))
addr.template = "cbi/tvalue"
addr.rows = 20
function addr.cfgvalue(self, section)
return nixio.fs.readfile("/etc/smartdns/address.conf")
end
function addr.write(self, section, value)
value = value:gsub("\r\n?", "\n")
nixio.fs.writefile("/etc/smartdns/address.conf", value)
end
---- ip rules;
s = m:section(TypedSection, "ip-rule", translate("IP Rules"), translate("IP Rules Settings"))
s.anonymous = true
s.nodescriptions = true
s:tab("ip-alias", translate('IP Alias Setting'))
s:tab("blackip-list", translate("IP Blacklist"), translate("Set Specific ip blacklist."))
-- enable flag;
o = s:taboption("ip-alias", Flag, "enabled", translate("Enable"), translate("Enable"));
o.rmempty = false;
o.default = o.enabled;
o.editable = true;
-- name;
o = s:taboption("ip-alias", Value, "name", translate("IP Rule Name"), translate("IP Rule Name"));
o.rmempty = true;
o.datatype = "string";
o = s:taboption("ip-alias", FileUpload, "ip_set_file", translate("IP Set File"), translate("Upload IP set file."));
o.rmempty = true
o.datatype = "file"
o.modalonly = true;
o.root_directory = "/etc/smartdns/ip-set"
o = s:taboption("ip-alias", DynamicList, "ip_addr", translate("IP Addresses"), translate("IP addresses, CIDR format."));
o.rmempty = true;
o.datatype = "ipaddr"
o.modalonly = true;
o = s:taboption("ip-alias", Flag, "whitelist_ip", translate("Whitelist IP"), translate("Whitelist IP Rule, Accept IP addresses within the range."));
o.rmempty = true;
o.default = o.disabled;
o.modalonly = true;
o = s:taboption("ip-alias", Flag, "blacklist_ip", translate("Blacklist IP"), translate("Blacklist IP Rule, Decline IP addresses within the range."));
o.rmempty = true;
o.default = o.disabled;
o.modalonly = true;
o = s:taboption("ip-alias", Flag, "ignore_ip", translate("Ignore IP"), translate("Do not use these IP addresses."));
o.rmempty = true;
o.default = o.disabled;
o.modalonly = true;
o = s:taboption("ip-alias", Flag, "bogus_nxdomain", translate("Bogus nxdomain"), translate("Return SOA when the requested result contains a specified IP address."));
o.rmempty = true;
o.default = o.disabled;
o.modalonly = true;
o = s:taboption("ip-alias", DynamicList, "ip_alias", translate("IP alias"), translate("IP Address Mapping, Can be used for CDN acceleration with Anycast IP, such as Cloudflare's CDN."));
o.rmempty = true;
o.datatype = 'ipaddr("nomask")';
o.modalonly = true;
-- other args
o = s:taboption("ip-alias", Value, "addition_flag", translate("Additional Rule Flag"), translate("Additional Flags for rules, read help on ip-rule for more information."))
o.default = ""
o.rempty = true
o.modalonly = true;
-- IP Blacklist
addr = s:taboption("blackip-list", Value, "dummy_blacklist_ip",
translate(""),
translate("Configure IP blacklists that will be filtered from the results of specific DNS server."))
addr.template = "cbi/tvalue"
addr.rows = 20
function addr.cfgvalue(self, section)
return nixio.fs.readfile("/etc/smartdns/blacklist-ip.conf")
end
function addr.write(self, section, value)
-- value = value:gsub("\r\n?", "\n")
nixio.fs.writefile("/etc/smartdns/blacklist-ip.conf", value)
end
s = m:section(TypedSection, "smartdns", translate("Download Files Setting"), translate("Download domain list files for domain-rule and include config files, please refresh the page after download to take effect."))
s.anonymous = true
---- download Files Settings
o = s:option(Flag, "enable_auto_update", translate("Enable Auto Update"), translate("Enable daily(week) auto update."))
o.rmempty = true
o.default = o.disabled
o.rempty = true
o = s:option(ListValue, "auto_update_week_time", translate("Update Time (Every Week)"))
o:value("*", translate("Every Day"))
o:value("1", translate("Every Monday"))
o:value("2", translate("Every Tuesday"))
o:value("3", translate("Every Wednesday"))
o:value("4", translate("Every Thursday"))
o:value("5", translate("Every Friday"))
o:value("6", translate("Every Saturday"))
o:value("0", translate("Every Sunday"))
o.default = "*"
o = s:option(ListValue, "auto_update_day_time", translate("Update Time (Every Day)"))
for i = 0, 23 do o:value(i, i .. ":00") end
o.default = 5
o = s:option(FileUpload, "upload_conf_file", translate("Upload Config File"),
translate("Upload smartdns config file to /etc/smartdns/conf.d"))
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/conf.d"
o = s:option(FileUpload, "upload_list_file", translate("Upload Domain List File"),
translate("Upload domain list file to /etc/smartdns/domain-set"))
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/domain-set"
o = s:option(FileUpload, "upload_other_file", translate("Upload File"))
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/download"
o = s:option(Button, "_updateate")
o.title = translate("Update Files")
o.inputtitle = translate("Update Files")
o.inputstyle = "apply"
o.write = function()
luci.sys.call("/etc/init.d/smartdns updatefiles >/dev/null 2>&1")
end
s = m:section(TypedSection, "download-file", translate("Download Files"), translate("List of files to download."))
s.anonymous = true
s.addremove = true
s.template = "cbi/tblsection"
o = s:option(Value, 'name', translate('File Name'), translate('File Name'))
o.rmempty = true
o.datatype = 'string'
o = s:option(Value, 'url', translate('URL'), translate('URL'))
o.rmempty = true
o.datatype = 'string'
function o.validate(self, value, section)
if value == "" then
return nil, translate("URL format error, format: http:// or https://")
end
if value == nil then
return nil, translate("URL format error, format: http:// or https://")
end
if value.find(value, "http://") then
return value
end
if value.find(value, "https://") then
return value
end
return nil, translate("URL format error, format: http:// or https://")
end
o = s:option(ListValue, "type", translate("type"), translate("File Type"))
o:value("list", translate("domain list (/etc/smartdns/domain-set)"))
o:value("config", translate("smartdns config (/etc/smartdns/conf.d)"))
o:value("ip-set", translate("ip-set file (/etc/smartdns/ip-set)"))
o:value("other", translate("other file (/etc/smartdns/download)"))
o.default = "list"
o.rempty = false
o = s:option(Value, 'desc', translate('Description'), translate('Description'))
o.rmempty = true
o.datatype = 'string'
-- Technical Support
s = m:section(TypedSection, "smartdns", translate("Technical Support"),
translate("If you like this software, please buy me a cup of coffee."))
s.anonymous = true
o = s:option(Button, "web")
o.title = translate("SmartDNS official website")
o.inputtitle = translate("open website")
o.inputstyle = "apply"
o.write = function()
luci.http.redirect("https://pymumu.github.io/smartdns")
end
o = s:option(Button, "report")
o.title = translate("Report bugs")
o.inputtitle = translate("Report bugs")
o.inputstyle = "apply"
o.write = function()
luci.http.redirect("https://github.com/pymumu/smartdns/issues")
end
o = s:option(Button, "Donate")
o.title = translate("Donate to smartdns")
o.inputtitle = translate("Donate")
o.inputstyle = "apply"
o.write = function()
luci.http.redirect("https://pymumu.github.io/smartdns/#donate")
end
o = s:option(Button, "Restart")
o.title = translate("Restart Service")
o.inputtitle = translate("Restart")
o.inputstyle = "apply"
o.write = function()
luci.sys.call("/etc/init.d/smartdns restart >/dev/null 2>&1")
end
return m
================================================
FILE: package/luci-compat/files/luci/model/cbi/smartdns/upstream.lua
================================================
--
-- Copyright (C) 2018-2025 Ruilin Peng (Nick) .
--
-- 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 .
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
================================================
FILE: package/luci-compat/files/luci/model/smartdns.lua
================================================
--
-- Copyright (C) 2018-2025 Ruilin Peng (Nick) .
--
-- 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 .
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
================================================
FILE: package/luci-compat/files/luci/view/smartdns/smartdns_status.htm
================================================
================================================
FILE: package/luci-compat/files/usr/share/rpcd/acl.d/luci-app-smartdns.json
================================================
{
"luci-app-smartdns": {
"description": "Grant access to LuCI app smartdns",
"read": {
"file": {
"/etc/smartdns/*": [ "read" ]
},
"ubus": {
"service": [ "list" ]
},
"uci": [ "smartdns" ]
},
"write": {
"file": {
"/etc/smartdns/*": [ "write" ],
"/etc/init.d/smartdns restart": [ "exec" ],
"/etc/init.d/smartdns updatefiles": [ "exec" ]
},
"uci": [ "smartdns" ]
}
}
}
================================================
FILE: package/luci-compat/make.sh
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
CURR_DIR=$(cd $(dirname $0);pwd)
VER="`date +"1.%Y.%m.%d-%H%M"`"
SMARTDNS_DIR=$CURR_DIR/../../
PO2LMO=
showhelp()
{
echo "Usage: make [OPTION]"
echo "Options:"
echo " -o output directory."
echo " --arch archtecture."
echo " --ver version."
echo " -h show this message."
}
build_tool()
{
make -C $ROOT/tool/po2lmo -j
PO2LMO="$ROOT/tool/po2lmo/src/po2lmo"
}
clean_tool()
{
make -C $ROOT/tool/po2lmo clean
}
build()
{
ROOT=/tmp/luci-app-smartdns
rm -fr $ROOT
mkdir -p $ROOT
cp $CURR_DIR/* $ROOT/ -af
cp $CURR_DIR/../tool $ROOT/ -af
cd $ROOT/
build_tool
mkdir $ROOT/root/usr/lib/lua/ -p
cp $ROOT/files/luci $ROOT/root/usr/lib/lua/ -af
cp $ROOT/files/usr $ROOT/root/ -af
#Generate Language
$PO2LMO $ROOT/files/luci/i18n/smartdns.zh-cn.po $ROOT/root/usr/lib/lua/luci/i18n/smartdns.zh-cn.lmo
rm $ROOT/root/usr/lib/lua/luci/i18n/smartdns.zh-cn.po
cp $ROOT/files/etc $ROOT/root/ -af
INST_SIZE="`du -sb $ROOT/root/ | awk '{print $1}'`"
sed -i "s/^Architecture.*/Architecture: all/g" $ROOT/control/control
sed -i "s/Version:.*/Version: $VER/" $ROOT/control/control
if [ ! -z "$INST_SIZE" ]; then
echo "Installed-Size: $INST_SIZE" >> $ROOT/control/control
fi
cd $ROOT/control
chmod +x *
tar zcf ../control.tar.gz ./
cd $ROOT
tar zcf $ROOT/data.tar.gz -C root .
tar zcf $OUTPUTDIR/luci-app-smartdns.$VER.$FILEARCH.ipk ./control.tar.gz ./data.tar.gz ./debian-binary
rm -fr $ROOT/
}
main()
{
OPTS=`getopt -o o:h --long arch:,ver:,filearch: \
-n "" -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$OPTS"
while true; do
case "$1" in
--arch)
ARCH="$2"
shift 2;;
--filearch)
FILEARCH="$2"
shift 2;;
--ver)
VER="$2"
shift 2;;
-o )
OUTPUTDIR="$2"
shift 2;;
-h | --help )
showhelp
return 0
shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if [ -z "$ARCH" ]; then
echo "please input arch."
return 1;
fi
if [ -z "$FILEARCH" ]; then
FILEARCH=$ARCH
fi
if [ -z "$OUTPUTDIR" ]; then
OUTPUTDIR=$CURR_DIR;
fi
build
}
main $@
exit $?
================================================
FILE: package/luci-lite/control/conffiles
================================================
/etc/config/smartdns-lite
================================================
FILE: package/luci-lite/control/control
================================================
Package: luci-app-smartdns-lite
Version: git-18.201.27126-7bf0367-1
Depends: libc, smartdns
Source: feeds/luci/applications/luci-app-smartdns-lite
Section: luci
Architecture: all
Description: A smartdns server for china
================================================
FILE: package/luci-lite/control/postinst
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
[ "${IPKG_NO_SCRIPT}" = "1" ] && exit 0
[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0
. ${IPKG_INSTROOT}/lib/functions.sh
default_postinst $0 $@
ret=$?
/etc/init.d/smartdns-lite clear_rules
/etc/init.d/smartdns-lite enable
exit 0
================================================
FILE: package/luci-lite/control/prerm
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
[ -e ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0
. ${IPKG_INSTROOT}/lib/functions.sh
default_prerm $0 $@
/etc/init.d/smartdns-lite clear_rules
/etc/init.d/smartdns-lite disable
rm /var/etc/smartdns-lite.conf -f
exit 0
================================================
FILE: package/luci-lite/debian-binary
================================================
2.0
================================================
FILE: package/luci-lite/files/luci/i18n/smartdns-lite.zh-cn.po
================================================
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "A local SmartDNS server for lite users."
msgstr "为入门用户提供的本地SmartDNS服务器。"
msgid "AD Block Domain List File"
msgstr "广告屏蔽域名列表文件"
msgid "Basic Settings"
msgstr "基础设置"
msgid "Block Domain List File for Parental Control."
msgstr "家长控制功能屏蔽的域名列表文件。"
msgid "Client Address"
msgstr "客户端地址"
msgid "Client Address File"
msgstr "客户端地址文件"
msgid "Client address format error, please input ip adress or mac address."
msgstr "客户端地址格式错误,请输入IP地址或MAC地址。"
msgid "CloudFlare CDN IP File"
msgstr "CloudFlare CDN IP文件"
msgid "CloudFlare CDN IP Settings"
msgstr "CloudFlare CDN IP设置"
msgid "Collecting data ..."
msgstr "正在收集数据..."
msgid "Custom Settings"
msgstr "自定义设置"
msgid "DNS Server Mode"
msgstr "DNS服务器模式"
msgid "DNS Server Port"
msgstr "DNS服务器端口号"
msgid "Dnsmasq Forwarded To Smartdns Failure"
msgstr "设置smartdns为dnsmasq上游失败"
msgid "Dnsmasq Upstream Server"
msgstr "Dnsmasq上游服务器"
msgid "Domain List File"
msgstr "域名列表文件"
msgid "Domain Rules Settings"
msgstr "域名规则设置"
msgid "Enable"
msgstr "启用"
msgid "Enable or disable cloudflare cdn ip accelerating."
msgstr "启用或禁用cloudflare CDN IP加速。"
msgid "Enable or disable domain rules."
msgstr "启用或禁用域名规则。"
msgid "Enable or disable smartdns server"
msgstr "启用或禁用smartdns服务器"
msgid "Force AAAA SOA"
msgstr "禁止AAAA记录"
msgid "Force AAAA SOA."
msgstr "强制IPV6记录返回SOA。"
msgid "Force HTTPS SOA"
msgstr "禁用HTTPS记录"
msgid "Force HTTPS SOA."
msgstr "强制HTTPS记录返回SOA"
msgid "Grant access to LuCI app smartdns"
msgstr "获取访问smartdns的权限"
msgid "IP Address Mapping, mapping all CloudFlare CDN IPs to the specified IP, can be used to accelerate CloudFlare's CDN websites."
msgstr "IP地址映射,将所有CloudFlare CDN IP映射到设置的IP,可用于加速CloudFlare的CDN网站。"
msgid "IP alias"
msgstr "IP别名"
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 "Invalid server address: %s"
msgstr "无效的服务器地址:%s"
msgid "Main DNS Server"
msgstr "主DNS服务"
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 "None"
msgstr "无"
msgid "Parental Control Domain File"
msgstr "家长控制域名列表文件"
msgid "Parental Control Settings"
msgstr "家长控制设置"
msgid "Parental Control Upstream Server"
msgstr "家长控制上游服务器"
msgid "Parental control feature is only available in Main DNS mode."
msgstr "家长控制功能仅在作为主DNS时才可用。"
msgid "Please check the system logs and check if the configuration is valid."
msgstr "请检查系统日志,并确保配置正确。"
msgid "RUNNING"
msgstr "运行中"
msgid "Restart"
msgstr "重启"
msgid "Restart Service"
msgstr "重启服务"
msgid "Set the IP addresses for accelerating CloudFlare CDN."
msgstr "设置CloudFlare cdn加速。"
msgid "Set the file for blocking ad domain names."
msgstr "设置屏蔽的域名。"
msgid "Settings"
msgstr "设置"
msgid "SmartDNS"
msgstr "SmartDNS"
msgid "SmartDNS official website"
msgstr "SmartDNS官方网站。"
msgid "SmartDNS Lite"
msgstr "SmartDNS轻量版"
msgid "Smartdns server mode."
msgstr "smartdns服务器模式。"
msgid "Smartdns server port."
msgstr "smartdns服务器端口。"
msgid "Speed check mode for matching domains."
msgstr "匹配域名的测速模式。"
msgid "Speed Check Mode"
msgstr "测速模式"
msgid "Speed check mode is invalid."
msgstr "测速模式无效。"
msgid "TCP port is empty"
msgstr "TCP端口为空"
msgid "TPROXY Server Port"
msgstr "TPROXY服务器端口"
msgid "TPROXY server port used for forwarding data requests, please make sure this port has enabled TPROXY service."
msgstr "用于转发数据请求的TPROXY服务器端口,请确保该端口已启用TPROXY服务,否则链接可能不正常。"
msgid "Use Internal IP Rules"
msgstr "使用内置IP规则"
msgid "Use internal IP rules to forward data to TPROXY service when the domain matches, avoiding the need to configure IP rules."
msgstr "当域名匹配时,使用内置IP规则将数据转发到TPROXY服务,避免复杂的IP规则配置。"
msgid "Upload CloudFlare cdn ip list file, please refer to https://www.cloudflare.com/ips"
msgstr "上传CloudFlare CDN IP列表文件,请参考https://www.cloudflare.com/ips"
msgid "Upload domain list file for matching these rules, if not specified, the rules will be applied to all domains."
msgstr "上传域名列表文件以匹配规则,未设置任何域名时,规则将应用到所有域名。"
msgid "Upstream DNS Server"
msgstr "上游DNS服务器"
msgid "Upstream Server"
msgstr "上游服务器"
msgid "Upstream server for specific domain. If not specified, the default server will be used."
msgstr "指定对应域名的上游服务器,未指定服务器时,将使用默认的服务器。"
msgid ""
"Upstream server with parental control feature. If not specified, the default "
"server will be used."
msgstr "启用家长控制且未配置上游服务器时,将使用默认服务器。"
msgid "Upstream servers, format: [udp://|tcp://|tls://|https://][ip]."
msgstr "上游服务器,格式:[udp://|tcp://|tls://|https://][ip]。"
msgid "default"
msgstr "默认"
msgid "ipset name format error, format: [#[4|6]:]ipsetname"
msgstr "ipset名称格式错误,格式为:[#[4|6]:]ipsetname"
msgid "open website"
msgstr "打开网站"
msgid "smartdns custom settings"
msgstr "smartdns自定义设置"
================================================
FILE: package/luci-lite/files/root/etc/config/smartdns-lite
================================================
config 'smartdns-lite'
option 'enabled' '0'
================================================
FILE: package/luci-lite/files/root/etc/init.d/smartdns-lite
================================================
#!/bin/sh /etc/rc.common
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
START=19
STOP=82
NAME=smartdns-lite
USE_PROCD=1
SMARTDNS_CONF_DIR="/etc/smartdns"
SMARTDNS_CONF_DOWNLOAD_DIR="$SMARTDNS_CONF_DIR/conf.d"
SMARTDNS_VAR_CONF_DIR="/var/etc/smartdns"
SMARTDNS_CONF="$SMARTDNS_VAR_CONF_DIR/smartdns-lite.conf"
CUSTOM_CONF="$SMARTDNS_CONF_DIR/custom.conf"
SMARTDNS_CONF_TMP="${SMARTDNS_CONF}.tmp"
EXTRA_COMMANDS="clear_rules"
EXTRA_HELP=" clear_rules clear all rules"
conf_append()
{
echo "$1 $2" >> $SMARTDNS_CONF_TMP
}
client_rule_addr_append()
{
conf_append "client-rules" "$1"
}
servers_append()
{
conf_append "server" "$1 $server_options"
}
setup_tproxy_rules()
{
local tproxy_port="$1"
local table_type="$2"
ip rule add fwmark 1104 lookup 981
ip route add local 0.0.0.0/0 dev lo table 981
ip -6 route add local ::/0 dev lo table 981
if [ "$table_type" = "iptable" ]; then
iptables -t mangle -N SMARTDNS_LITE
iptables -t mangle -A SMARTDNS_LITE -p tcp -m set --match-set smartdns dst -j TPROXY --on-ip 127.0.0.1 --on-port ${tproxy_port} --tproxy-mark 1104
iptables -t mangle -A SMARTDNS_LITE -p udp -m set --match-set smartdns dst -j TPROXY --on-ip 127.0.0.1 --on-port ${tproxy_port} --tproxy-mark 1104
iptables -t mangle -A SMARTDNS_LITE -j ACCEPT
iptables -t mangle -A PREROUTING -j SMARTDNS_LITE
ip6tables -t mangle -N SMARTDNS_LITE
ip6tables -t mangle -A SMARTDNS_LITE -p tcp -m set --match-set smartdns6 dst -j TPROXY --on-ip ::1 --on-port ${tproxy_port} --tproxy-mark 1104
ip6tables -t mangle -A SMARTDNS_LITE -p udp -m set --match-set smartdns6 dst -j TPROXY --on-ip ::1 --on-port ${tproxy_port} --tproxy-mark 1104
ip6tables -t mangle -A SMARTDNS_LITE -j ACCEPT
ip6tables -t mangle -A PREROUTING -j SMARTDNS_LITE
elif [ "$table_type" = "nftable" ]; then
nft add table ip smartdns_lite
nft add set ip smartdns_lite ipv4 { type ipv4_addr\; flags interval\; auto-merge\; }
nft add chain ip smartdns_lite prerouting { type filter hook prerouting priority 0\; }
nft add rule ip smartdns_lite prerouting meta l4proto tcp ip daddr @ipv4 tproxy to 127.0.0.1:${tproxy_port} mark set 1104
nft add rule ip smartdns_lite prerouting meta l4proto udp ip daddr @ipv4 tproxy to 127.0.0.1:${tproxy_port} mark set 1104
nft add table ip6 smartdns_lite
nft add set ip6 smartdns_lite ipv6 { type ipv6_addr\; flags interval\; auto-merge\; }
nft add chain ip6 smartdns_lite prerouting6 { type filter hook prerouting priority 0\; }
nft add rule ip6 smartdns_lite prerouting6 meta l4proto tcp ip6 daddr @ipv6 tproxy to ::1:${tproxy_port} mark set 1104
nft add rule ip6 smartdns_lite prerouting6 meta l4proto udp ip6 daddr @ipv6 tproxy to ::1:${tproxy_port} mark set 1104
else
echo "table_type error"
return 1
fi
}
clear_tproxy_rules()
{
ip rule del fwmark 1104 > /dev/null 2>&1
ip route flush table 981 > /dev/null 2>&1
iptables -t mangle -D PREROUTING -j SMARTDNS_LITE > /dev/null 2>&1
iptables -t mangle -F SMARTDNS_LITE > /dev/null 2>&1
iptables -t mangle -X SMARTDNS_LITE > /dev/null 2>&1
ip6tables -t mangle -D PREROUTING -j SMARTDNS_LITE > /dev/null 2>&1
ip6tables -t mangle -F SMARTDNS_LITE > /dev/null 2>&1
ip6tables -t mangle -X SMARTDNS_LITE > /dev/null 2>&1
nft delete table ip smartdns_lite > /dev/null 2>&1
nft delete table ip6 smartdns_lite > /dev/null 2>&1
}
clear_rules()
{
clear_tproxy_rules
}
load_parental_control_rules()
{
local section="$1"
local adblock_set_name="$2"
local block_domain_set_file=""
local client_set_name="pc-client-address-$section"
local block_set_name="pc-block-domain-$section"
local server_options="-e"
config_get_bool pc_enabled "$section" "pc_enabled" "0"
[ "$pc_enabled" != "1" ] && return
conf_append "group-begin" "parental-control-${section}"
config_get pc_client_addr_file "$section" "pc_client_addr_file" ""
[ -e "$pc_client_addr_file" ] && {
conf_append "ip-set" "-name ${client_set_name} -file '$pc_client_addr_file'"
conf_append "group-match" "-client-ip ip-set:${client_set_name}"
}
config_list_foreach "$section" "pc_client_addr" client_rule_addr_append
config_list_foreach "$section" "pc_servers" servers_append
config_get pc_block_file "$section" "pc_block_file" ""
[ -e "$pc_block_file" ] && {
conf_append "domain-set" "-name ${block_set_name} -file '$pc_block_file'"
conf_append "domain-rules" "/domain-set:${block_set_name}/ -address #"
}
[ ! -z "$adblock_set_name" ] && {
conf_append "domain-rules" "/domain-set:${adblock_set_name}/ -address #"
}
conf_append "group-end"
}
load_domain_rules()
{
local section="$1"
local domain_set_args=""
local domain_set_name="rules-domain-set-$section"
local domain_rule_name="rules-domain-group-$section"
local as_group=0;
local qtype_soa_list=""
local server_options=""
clear_tproxy_rules
config_get_bool rules_enabled "$section" "rules_enabled" "0"
[ "$rules_enabled" != "1" ] && return
config_list_foreach "$section" "rules_servers" servers_append
config_get rules_domain_file "$section" "rules_domain_file" ""
[ -e "$rules_domain_file" ] && {
conf_append "group-begin" "${domain_rule_name}"
conf_append "domain-set" "-name ${domain_set_name} -file '$rules_domain_file'"
conf_append "group-match" "-domain domain-set:${domain_set_name}"
conf_append "force-qtype-SOA" "-"
server_options="-e"
as_group="1"
}
config_get rules_speed_check_mode "$section" "rules_speed_check_mode" ""
[ ! -z "$rules_speed_check_mode" ] && conf_append "speed-check-mode" "$rules_speed_check_mode"
config_get rules_force_aaaa_soa "$section" "rules_force_aaaa_soa" "0"
[ "$rules_force_aaaa_soa" = "1" ] && qtype_soa_list="$qtype_soa_list 28"
config_get rules_force_https_soa "$section" "rules_force_https_soa" "1"
[ "$rules_force_https_soa" = "1" ] && qtype_soa_list="$qtype_soa_list 65"
[ ! -z "$qtype_soa_list" ] && conf_append "force-qtype-SOA" "$qtype_soa_list"
config_get_bool use_internal_rules "$section" "use_internal_rules" "0"
[ "$use_internal_rules" = "1" ] && {
config_get tproxy_server_port "$section" "tproxy_server_port" ""
[ ! -z "$tproxy_server_port" ] && {
which nft > /dev/null 2>&1
if [ "$?" = "0" ]; then
table_type="nftable"
conf_append "nftset" "#4:ip#smartdns_lite#ipv4"
conf_append "nftset" "#6:ip6#smartdns_lite#ipv6"
else
conf_append "ipset" "SMARTDNS_LITE"
table_type="iptable"
fi
setup_tproxy_rules "$tproxy_server_port" "$table_type"
}
} || {
config_get ipset_name "$section" "ipset_name" ""
[ -z "$ipset_name" ] || conf_append "ipset" "$ipset_name"
config_get nftset_name "$section" "nftset_name" ""
[ -z "$nftset_name" ] || conf_append "nftset" "$nftset_name"
}
[ "$as_group" = "1" ] && {
conf_append "group-end"
}
}
cloudflare_cdn_alias()
{
conf_append "ip-alias" "$1 ip-set:$ipset_set_name"
}
load_cloudflare_cdn_accelerate()
{
local section="$1"
local ipset_set_name="cloudflare-ip-set-$section"
config_get_bool cloudflare_enabled "$section" "cloudflare_enabled" "0"
[ "$cloudflare_enabled" != "1" ] && return
config_get cloudflare_cdn_ip_file "$section" "cloudflare_cdn_ip_file" ""
[ ! -e "$cloudflare_cdn_ip_file" ] && return
conf_append "ip-set" "-name ${ipset_set_name} -file '$cloudflare_cdn_ip_file'"
config_list_foreach "$section" "cloudflare_ip_alias" cloudflare_cdn_alias
}
unload_service()
{
:
}
load_service()
{
local section="$1"
args=""
local device=""
local adblock_set_name=""
local auto_set_dnsmasq="0"
mkdir -p $SMARTDNS_VAR_CONF_DIR
rm -f $SMARTDNS_CONF_TMP
config_get_bool enabled "$section" "enabled" '0'
[ "$enabled" != "1" ] && {
uci -q set smartdns.@smartdns[0].enabled="0"
uci -q del_list smartdns.@smartdns[0].conf_files="$SMARTDNS_CONF"
uci commit smartdns
clear_tproxy_rules
/etc/init.d/smartdns reload
return
}
config_get port "$section" "port" "53"
config_get server_mode "$section" "server_mode" "main"
[ "$server_mode" = "main" ] && {
port="53"
}
[ "$server_mode" = "dnsmasq_upstream" ] && {
auto_set_dnsmasq="1"
}
config_list_foreach "$section" "servers" servers_append
config_get ad_block_file "$section" "ad_block_file" ""
[ -e "$ad_block_file" ] && {
adblock_set_name="adblock-block-$section"
conf_append "domain-set" "-name ${adblock_set_name} -file '$ad_block_file'"
conf_append "domain-rules" "/domain-set:${adblock_set_name}/ -address #"
}
load_cloudflare_cdn_accelerate "$section"
load_parental_control_rules "$section" "$adblock_set_name"
load_domain_rules "$section"
uci -q set smartdns.@smartdns[0].enabled="1"
uci -q set smartdns.@smartdns[0].port="$port"
uci -q set smartdns.@smartdns[0].auto_set_dnsmasq="$auto_set_dnsmasq"
uci -q del_list smartdns.@smartdns[0].conf_files="$SMARTDNS_CONF"
uci -q add_list smartdns.@smartdns[0].conf_files="$SMARTDNS_CONF"
touch $SMARTDNS_CONF_TMP
mv $SMARTDNS_CONF_TMP $SMARTDNS_CONF
uci commit smartdns
/etc/init.d/smartdns reload
}
service_triggers() {
procd_add_reload_trigger smartdns-lite
}
service_stopped()
{
config_load "smartdns-lite"
config_foreach unload_service "smartdns-lite"
}
start_service()
{
config_load "smartdns-lite"
config_foreach load_service "smartdns-lite"
}
reload_service()
{
stop
start
}
================================================
FILE: package/luci-lite/files/root/usr/share/luci/menu.d/luci-app-smartdns-lite.json
================================================
{
"admin/services/smartdns-lite": {
"title": "SmartDNS Lite",
"action": {
"type": "view",
"path": "smartdns-lite/smartdns-lite"
},
"depends": {
"acl": [ "luci-app-smartdns-lite" ],
"uci": { "smartdns-lite": true }
}
}
}
================================================
FILE: package/luci-lite/files/root/usr/share/rpcd/acl.d/luci-app-smartdns-lite.json
================================================
{
"luci-app-smartdns-lite": {
"description": "Grant access to LuCI app smartdns",
"read": {
"file": {
"/etc/smartdns/*": [ "read" ]
},
"ubus": {
"service": [ "list" ]
},
"uci": [ "smartdns-lite", "smartdns" ]
},
"write": {
"file": {
"/etc/smartdns/*": [ "write" ],
"/etc/init.d/smartdns-lite restart": [ "exec" ],
"/etc/init.d/smartdns-lite updatefiles": [ "exec" ]
},
"uci": [ "smartdns-lite", "smartdns" ]
}
}
}
================================================
FILE: package/luci-lite/files/root/www/luci-static/resources/view/smartdns-lite/smartdns-lite.js
================================================
/*************************************************************************
*
* Copyright (C) 2018-2025 Ruilin Peng (Nick) .
*
* 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 .
*/
'use strict';
'require fs';
'require uci';
'require form';
'require view';
'require poll';
'require rpc';
'require ui';
var conf = 'smartdns';
var callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
});
var pollAdded = false;
function getServiceStatus() {
return L.resolveDefault(callServiceList(conf), {})
.then(function (res) {
var is_running = false;
try {
is_running = res[conf]['instances']['smartdns']['running'];
} catch (e) {
}
return is_running;
})
}
function smartdnsServiceStatus() {
return Promise.all([
getServiceStatus()
]);
}
function smartdnsRenderStatus(res) {
var renderHTML = "";
var isRunning = res[0];
var autoSetDnsmasq = uci.get_first('smartdns', 'smartdns', 'auto_set_dnsmasq');
var smartdnsPort = uci.get_first('smartdns', 'smartdns', 'port');
var smartdnsEnable = uci.get_first('smartdns', 'smartdns', 'enabled');
var dnsmasqServer = uci.get_first('dhcp', 'dnsmasq', 'server');
if (isRunning) {
renderHTML += "SmartDNS - " + _("RUNNING") + "";
} else {
renderHTML += "SmartDNS - " + _("NOT RUNNING") + "";
if (smartdnsEnable === '1') {
renderHTML += " " + _("Please check the system logs and check if the configuration is valid.");
renderHTML += "";
}
return renderHTML;
}
if (autoSetDnsmasq === '1' && smartdnsPort != '53') {
var matchLine = "127.0.0.1#" + smartdnsPort;
uci.unload('dhcp');
uci.load('dhcp');
if (dnsmasqServer == undefined || dnsmasqServer.indexOf(matchLine) < 0) {
renderHTML += " " + _("Dnsmasq Forwarded To Smartdns Failure") + "";
}
}
return renderHTML;
}
return view.extend({
load: function () {
return Promise.all([
uci.load('dhcp'),
uci.load('smartdns'),
uci.load('smartdns-lite'),
]);
},
render: function (stats) {
var m, s, o;
m = new form.Map('smartdns-lite', _('SmartDNS Lite'));
m.title = _("SmartDNS Lite");
m.description = _("A local SmartDNS server for lite users.");
s = m.section(form.NamedSection, '_status');
s.anonymous = true;
s.render = function (section_id) {
var renderStatus = function () {
return L.resolveDefault(smartdnsServiceStatus()).then(function (res) {
var view = document.getElementById("service_status");
if (view == null) {
return;
}
view.innerHTML = smartdnsRenderStatus(res);
});
}
if (pollAdded == false) {
poll.add(renderStatus, 1);
pollAdded = true;
}
return E('div', { class: 'cbi-section' }, [
E('div', { id: 'service_status' },
_('Collecting data ...'))
]);
}
////////////////
// Basic;
////////////////
s = m.section(form.TypedSection, "smartdns-lite", _("Settings"));
s.anonymous = true;
s.tab("settings", _("Basic Settings"));
s.tab("parental", _("Parental Control Settings"));
s.tab("rules", _("Domain Rules Settings"));
s.tab("cloudflare", _("CloudFlare CDN IP Settings"), _("Set the IP addresses for accelerating CloudFlare CDN."));
s.tab("custom", _("Custom Settings"));
o = s.taboption("settings", form.Flag, "enabled", _("Enable"), _("Enable or disable smartdns server"));
o.rmempty = false;
o.default = o.disabled;
o = s.taboption("settings", form.DynamicList, "servers", _("Upstream Server"),
_("Upstream servers, format: [udp://|tcp://|tls://|https://][ip]."));
o.rempty = true
o.rmempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var values = value.split(/\s+/);
for (var i = 0; i < values.length; i++) {
if (!values[i].match(/^(https?|udp|tcp|tls|quic):\/\/[0-9a-zA-Z\.\[\]:]+(\/[^\s]*)?$/)) {
return _('Invalid server address: %s').format(values[i]);
}
}
return true;
};
o = s.taboption("settings", form.FileUpload, "ad_block_file", _("AD Block Domain List File"),
_("Set the file for blocking ad domain names."));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/domain-set"
o = s.taboption("settings", form.ListValue, "server_mode", _("DNS Server Mode"), _("Smartdns server mode."));
o.rmempty = false;
o.value("main", _("Main DNS Server"));
o.value("upstream", _("Upstream DNS Server"));
o.value("dnsmasq_upstream", _("Dnsmasq Upstream Server"));
o = s.taboption("settings", form.Value, "port", _("DNS Server Port"), _("Smartdns server port."));
o.rmempty = true
o.default = 6053;
o.datatype = "port";
o.depends("server_mode", "upstream");
o.depends("server_mode", "dnsmasq_upstream");
o = s.taboption("parental", form.Flag, "pc_enabled", _("Enable"), _("Enable or disable smartdns server"));
o.rmempty = false;
o.default = o.disabled;
o.validate = function (section_id, value) {
var v = this.map.lookupOption('pc_enabled', section_id)[0];
if (v.formvalue(section_id) == 0) {
return true;
}
var server_mode = this.map.lookupOption('server_mode', section_id)[0];
if (server_mode.formvalue(section_id) != "main") {
return _("Parental control feature is only available in Main DNS mode.");
}
return true;
}
o = s.taboption("parental", form.DynamicList, "pc_client_addr", _("Client Address"),
_("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."));
o.rempty = true
o.rmempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
if (value.match(/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$/)) {
return true;
}
if (value.match(/^([a-fA-F0-9]*:){1,7}[a-fA-F0-9]*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/)) {
return true;
}
if (value.match(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/)) {
return true;
}
return _("Client address format error, please input ip adress or mac address.");
}
o = s.taboption("parental", form.DynamicList, "pc_servers", _("Parental Control Upstream Server"),
_("Upstream server with parental control feature. If not specified, the default server will be used."));
o.rempty = true
o.rmempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var values = value.split(/\s+/);
for (var i = 0; i < values.length; i++) {
if (!values[i].match(/^(https?|udp|tcp|tls|quic):\/\/[0-9a-zA-Z\.\[\]:]+(\/[^\s]*)?$/)) {
return _('Invalid server address: %s').format(values[i]);
}
}
return true;
};
o = s.taboption("parental", form.FileUpload, "pc_block_file", _("Parental Control Domain File"),
_("Block Domain List File for Parental Control."));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/domain-set"
o = s.taboption("rules", form.Flag, "rules_enabled", _("Enable"), _("Enable or disable domain rules."));
o.rmempty = false;
o.default = o.disabled;
o = s.taboption("rules", form.FileUpload, "rules_domain_file", _("Domain List File"),
_("Upload domain list file for matching these rules, if not specified, the rules will be applied to all domains."));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.editable = true
o.root_directory = "/etc/smartdns/domain-set"
o = s.taboption("rules", form.DynamicList, "rules_servers", _("Upstream Server"),
_("Upstream server for specific domain. If not specified, the default server will be used."));
o.rempty = true
o.rmempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var values = value.split(/\s+/);
for (var i = 0; i < values.length; i++) {
if (!values[i].match(/^(https?|udp|tcp|tls|quic):\/\/[0-9a-zA-Z\.\[\]:]+(\/[^\s]*)?$/)) {
return _('Invalid server address: %s').format(values[i]);
}
}
return true;
};
o = s.taboption("rules", form.Value, "rules_speed_check_mode", _("Speed Check Mode"), _("Speed check mode for matching domains."));
o.rmempty = true;
o.placeholder = _("None");
o.default = "none";
o.value("none", _("None"));
o.value("ping,tcp:80,tcp:443");
o.value("ping,tcp:443,tcp:80");
o.value("tcp:80,tcp:443,ping");
o.value("tcp:443,tcp:80,ping");
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
if (value == "none") {
return true;
}
var check_mode = value.split(",")
for (var i = 0; i < check_mode.length; i++) {
if (check_mode[i] == "ping") {
continue;
}
if (check_mode[i].indexOf("tcp:") == 0) {
var port = check_mode[i].split(":")[1];
if (port == "") {
return _("TCP port is empty");
}
continue;
}
return _("Speed check mode is invalid.");
}
return true;
}
// Force AAAA SOA
o = s.taboption("rules", form.Flag, "rules_force_aaaa_soa", _("Force AAAA SOA"), _("Force AAAA SOA."));
o.rmempty = true;
o.default = o.disabled;
// Force HTTPS SOA
o = s.taboption("rules", form.Flag, "rules_force_https_soa", _("Force HTTPS SOA"), _("Force HTTPS SOA."));
o.rmempty = true;
o.default = o.enabled;
o = s.taboption("rules", form.Flag, "use_internal_rules", _("Use Internal IP Rules"),
_("Use internal IP rules to forward data to TPROXY service when the domain matches, avoiding the need to configure IP rules."));
o.rmempty = true;
o.default = o.disabled;
o = s.taboption("rules", form.Value, "rules_ipset_name", _("IPset Name"), _("IPset name."));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var ipset = value.split(",")
for (var i = 0; i < ipset.length; i++) {
if (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\-_]+$/)) {
return _("ipset name format error, format: [#[4|6]:]ipsetname");
}
}
return true;
}
o.depends("use_internal_rules", "0");
o = s.taboption("rules", form.Value, "rules_nftset_name", _("NFTset Name"), _("NFTset name, format: [#[4|6]:[family#table#set]]"));
o.rmempty = true;
o.datatype = "string";
o.rempty = true;
o.validate = function (section_id, value) {
if (value == "") {
return true;
}
var nftset = value.split(",")
for (var i = 0; i < nftset.length; i++) {
if (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+$/)) {
return _("NFTset name format error, format: [#[4|6]:[family#table#set]]");
}
}
return true;
}
o.depends("use_internal_rules", "0");
o = s.taboption("rules", form.Value, "tproxy_server_port", _("TPROXY Server Port"),
_("TPROXY server port used for forwarding data requests, please make sure this port has enabled TPROXY service."));
o.rmempty = false;
o.datatype = "port";
o.rempty = false;
o.depends("use_internal_rules", "1");
o = s.taboption("cloudflare", form.Flag, "cloudflare_enabled", _("Enable"),
_("Enable or disable cloudflare cdn ip accelerating."));
o.rmempty = false;
o.default = o.disabled;
o = s.taboption("cloudflare", form.FileUpload, "cloudflare_cdn_ip_file", _("CloudFlare CDN IP File"),
_("Upload CloudFlare cdn ip list file, please refer to https://www.cloudflare.com/ips"));
o.rmempty = true
o.datatype = "file"
o.rempty = true
o.modalonly = true;
o.root_directory = "/etc/smartdns/ip-set"
o = s.taboption("cloudflare", form.DynamicList, "cloudflare_ip_alias", _("IP alias"),
_("IP Address Mapping, mapping all CloudFlare CDN IPs to the specified IP, can be used to accelerate CloudFlare's CDN websites."));
o.rmempty = true;
o.datatype = 'ipaddr("nomask")';
o.modalonly = true;
///////////////////////////////////////
// custom settings;
///////////////////////////////////////
o = s.taboption("custom", form.TextValue, "custom_conf",
"", _("smartdns custom settings"));
o.rows = 20;
o.cfgvalue = function (section_id) {
return fs.trimmed('/etc/smartdns/custom.conf');
};
o.write = function (section_id, formvalue) {
return this.cfgvalue(section_id).then(function (value) {
if (value == formvalue) {
return
}
return fs.write('/etc/smartdns/custom.conf', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
});
};
o = s.taboption("custom", form.Button, "web");
o.title = _("SmartDNS official website");
o.inputtitle = _("open website");
o.inputstyle = "apply";
o.onclick = function () {
window.open("https://pymumu.github.io/smartdns", '_blank');
};
o = s.taboption("custom", form.DummyValue, "_restart", _("Restart Service"));
o.renderWidget = function () {
return E('button', {
'class': 'btn cbi-button cbi-button-apply',
'id': 'btn_restart',
'click': ui.createHandlerFn(this, function () {
return fs.exec('/etc/init.d/smartdns-lite', ['restart'])
.catch(function (e) { ui.addNotification(null, E('p', e.message), 'error') });
})
}, [_("Restart")]);
}
return m.render();
}
});
================================================
FILE: package/luci-lite/make.sh
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
CURR_DIR=$(cd $(dirname $0);pwd)
VER="`date +"1.%Y.%m.%d-%H%M"`"
SMARTDNS_DIR=$CURR_DIR/../../
PO2LMO=
showhelp()
{
echo "Usage: make [OPTION]"
echo "Options:"
echo " -o output directory."
echo " --arch archtecture."
echo " --ver version."
echo " -h show this message."
}
build_tool()
{
make -C $ROOT/tool/po2lmo -j
PO2LMO="$ROOT/tool/po2lmo/src/po2lmo"
}
clean_tool()
{
make -C $ROOT/tool/po2lmo clean
}
build()
{
ROOT=/tmp/luci-app-smartdns
rm -fr $ROOT
mkdir -p $ROOT
cp $CURR_DIR/* $ROOT/ -af
cp $CURR_DIR/../tool $ROOT/ -af
cd $ROOT/
build_tool
mkdir $ROOT/root/usr/lib/lua/luci -p
mkdir $ROOT/root/usr/share/rpcd/acl.d/ -p
cp $ROOT/files/luci/i18n $ROOT/root/usr/lib/lua/luci/ -avf
#Generate Language
$PO2LMO $ROOT/files/luci/i18n/smartdns-lite.zh-cn.po $ROOT/root/usr/lib/lua/luci/i18n/smartdns-lite.zh-cn.lmo
rm $ROOT/root/usr/lib/lua/luci/i18n/smartdns-lite.zh-cn.po
chmod +x $ROOT/files/root/etc/init.d/smartdns-lite
cp $ROOT/files/root/* $ROOT/root/ -avf
INST_SIZE="`du -sb $ROOT/root/ | awk '{print $1}'`"
sed -i "s/^Architecture.*/Architecture: all/g" $ROOT/control/control
sed -i "s/Version:.*/Version: $VER/" $ROOT/control/control
if [ ! -z "$INST_SIZE" ]; then
echo "Installed-Size: $INST_SIZE" >> $ROOT/control/control
fi
cd $ROOT/control
chmod +x *
tar zcf ../control.tar.gz ./
cd $ROOT
tar zcf $ROOT/data.tar.gz -C root .
tar zcf $OUTPUTDIR/luci-app-smartdns-lite.$VER.$FILEARCH.ipk ./control.tar.gz ./data.tar.gz ./debian-binary
which apk >/dev/null 2>&1
if [ $? -eq 0 ]; then
APK_VER="`echo $VER | sed 's/[-]/-r/'`"
ARCH="`echo $ARCH | sed 's/all/noarch/g'`"
apk mkpkg \
--info "name:luci-app-smartdns-lite" \
--info "version:$APK_VER" \
--info "description:smartdns luci lite" \
--info "arch:$ARCH" \
--info "license:GPL" \
--info "origin: https://github.com/pymumu/smartdns.git" \
--info "depends:libc smartdns" \
--script "post-install:$ROOT/control/postinst" \
--script "pre-deinstall:$ROOT/control/prerm" \
--files "$ROOT/root/" \
--output "$OUTPUTDIR/luci-app-smartdns-lite.$VER.$FILEARCH.apk"
if [ $? -ne 0 ]; then
echo "build apk package failed."
rm -fr $ROOT/
return 1
fi
else
echo "== warning: apk tool not found, skip build apk package. =="
fi
rm -fr $ROOT/
}
main()
{
OPTS=`getopt -o o:h --long arch:,ver:,filearch: \
-n "" -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$OPTS"
while true; do
case "$1" in
--arch)
ARCH="$2"
shift 2;;
--filearch)
FILEARCH="$2"
shift 2;;
--ver)
VER="$2"
shift 2;;
-o )
OUTPUTDIR="$2"
shift 2;;
-h | --help )
showhelp
return 0
shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if [ -z "$ARCH" ]; then
echo "please input arch."
return 1;
fi
if [ -z "$FILEARCH" ]; then
FILEARCH=$ARCH
fi
if [ -z "$OUTPUTDIR" ]; then
OUTPUTDIR=$CURR_DIR;
fi
build
}
main $@
exit $?
================================================
FILE: package/openwrt/Makefile
================================================
#
# Copyright (c) 2018-2025 Nick Peng (pymumu@gmail.com)
# This is free software, licensed under the GNU General Public License v3.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=smartdns
PKG_VERSION:=1.2025.46.2
PKG_RELEASE:=3
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://www.github.com/pymumu/smartdns.git
PKG_SOURCE_VERSION:=64fc9f20fba0e14cb118fe7f145557971cafd858
PKG_MIRROR_HASH:=skip
SMARTDNS_WEBUI_VERSION:=1.0.0
SMAETDNS_WEBUI_SOURCE_PROTO:=git
SMARTDNS_WEBUI_SOURCE_URL:=https://github.com/pymumu/smartdns-webui.git
SMARTDNS_WEBUI_SOURCE_VERSION:=35cbf4a1940f5dd32670c69bd5cc02437ad073e7
SMARTDNS_WEBUI_FILE:=smartdns-webui-$(SMARTDNS_WEBUI_VERSION).tar.gz
PKG_MAINTAINER:=Nick Peng
PKG_LICENSE:=GPL-3.0-or-later
PKG_LICENSE_FILES:=LICENSE
PKG_BUILD_PARALLEL:=1
# node compile is slow, so do not use it, download node manually.
# PACKAGE_smartdns-ui:node/host
PKG_BUILD_DEPENDS:=PACKAGE_smartdns-ui:rust/host
include ../../lang/rust/rust-package.mk
include $(INCLUDE_DIR)/package.mk
MAKE_VARS += VER=$(PKG_VERSION)
MAKE_PATH:=src
define Package/smartdns/default
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
URL:=https://www.github.com/pymumu/smartdns/
endef
define Package/smartdns
$(Package/smartdns/default)
TITLE:=smartdns server
DEPENDS:=+libpthread +libopenssl +libatomic
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/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 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_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/files/etc/config/smartdns $(1)/etc/config/smartdns
endef
define Package/smartdns-ui
$(Package/smartdns/default)
TITLE:=smartdns dashboard
DEPENDS:=+smartdns $(RUST_ARCH_DEPENDS)
endef
define Package/smartdns-ui/description
A dashboard ui for smartdns server.
endef
define Package/smartdns-ui/conffiles
/etc/config/smartdns
endef
define Package/smartdns-ui/install
$(INSTALL_DIR) $(1)/usr/lib
$(INSTALL_DIR) $(1)/etc/smartdns/conf.d/
$(INSTALL_DIR) $(1)/usr/share/smartdns/wwwroot
$(INSTALL_BIN) $(PKG_BUILD_DIR)/plugin/smartdns-ui/target/smartdns_ui.so $(1)/usr/lib/smartdns_ui.so
$(CP) $(PKG_BUILD_DIR)/smartdns-webui/out/* $(1)/usr/share/smartdns/wwwroot
endef
define Build/Compile/smartdns-webui
which npm || (echo "npm not found, please install npm first" && exit 1)
npm install --prefix $(PKG_BUILD_DIR)/smartdns-webui/
npm run build --prefix $(PKG_BUILD_DIR)/smartdns-webui/
endef
define Build/Compile/smartdns-ui
cargo install --force --locked bindgen-cli
CARGO_BUILD_ARGS="$(if $(strip $(RUST_PKG_FEATURES)),--features "$(strip $(RUST_PKG_FEATURES))") --profile $(CARGO_PKG_PROFILE)"
+$(CARGO_PKG_VARS) CARGO_BUILD_ARGS="$(CARGO_BUILD_ARGS)" CC=$(TARGET_CC) \
PATH="$$(PATH):$(CARGO_HOME)/bin" \
make -C $(PKG_BUILD_DIR)/plugin/smartdns-ui
endef
define Download/smartdns-webui
FILE:=$(SMARTDNS_WEBUI_FILE)
PROTO:=$(SMAETDNS_WEBUI_SOURCE_PROTO)
URL:=$(SMARTDNS_WEBUI_SOURCE_URL)
MIRROR_HASH:=b3f4f73b746ee169708f6504c52b33d9bbeb7c269b731bd7de4f61d0ad212d74
VERSION:=$(SMARTDNS_WEBUI_SOURCE_VERSION)
HASH:=$(SMARTDNS_WEBUI_HASH)
SUBDIR:=smartdns-webui
endef
$(eval $(call Download,smartdns-webui))
ifdef CONFIG_PACKAGE_smartdns-ui
define Build/Prepare
$(call Build/Prepare/Default)
$(TAR) -C $(PKG_BUILD_DIR)/ -xf $(DL_DIR)/$(SMARTDNS_WEBUI_FILE)
endef
endif
define Build/Compile
$(call Build/Compile/Default,smartdns)
ifdef CONFIG_PACKAGE_smartdns-ui
$(call Build/Compile/smartdns-ui)
$(call Build/Compile/smartdns-webui)
endif
endef
$(eval $(call BuildPackage,smartdns))
$(eval $(call RustBinPackage,smartdns-ui))
$(eval $(call BuildPackage,smartdns-ui))
================================================
FILE: package/openwrt/address.conf
================================================
# Add domains which you want to force to an IP address here.
# The example below send any host in example.com to a local webserver.
# address /domain/[ip|-|-4|-6|#|#4|#6]
# address /www.example.com/1.2.3.4, return ip 1.2.3.4 to client
# address /www.example.com/-, ignore address, query from upstream, suffix 4, for ipv4, 6 for ipv6, none for all
# address /www.example.com/#, return SOA to client, suffix 4, for ipv4, 6 for ipv6, none for all
# specific ipset to domain
# ipset /domain/[ipset|-]
# ipset /www.example.com/block, set ipset with ipset name of block
# ipset /www.example.com/-, ignore this domain
# specific nameserver to domain
# nameserver /domain/[group|-]
# nameserver /www.example.com/office, Set the domain name to use the appropriate server group.
# nameserver /www.example.com/-, ignore this domain
================================================
FILE: package/openwrt/blacklist-ip.conf
================================================
# Add IP blacklist which you want to filtering from some DNS server here.
# The example below filtering ip from the result of DNS server which is configured with -blacklist-ip.
# blacklist-ip [ip/subnet]
# blacklist-ip 254.0.0.1/16
================================================
FILE: package/openwrt/control/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
================================================
FILE: package/openwrt/control/control
================================================
Package: smartdns
Architecture:
Priority: optional
Section: net
Version:
Depends: libc, libopenssl, libpthread
Maintainer: pymumu
Source: http://127.0.0.1/
Description: A smart dns server
Enabled: yes
================================================
FILE: package/openwrt/control/postinst
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
chmod +x /usr/sbin/smartdns
chmod +x /etc/init.d/smartdns
mkdir -p /var/etc/smartdns/
[ "${IPKG_NO_SCRIPT}" = "1" ] && exit 0
. ${IPKG_INSTROOT}/lib/functions.sh
default_postinst $0 $@
ret=$?
/etc/init.d/smartdns enable
exit 0
================================================
FILE: package/openwrt/control/prerm
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
. ${IPKG_INSTROOT}/lib/functions.sh
default_prerm $0 $@
/etc/init.d/smartdns disable
rm /var/etc/smartdns.conf -f
rm /var/etc/smartdns/smartdns.conf -f
if [ "$1" = "remove" ]; then
rm /var/run/smartdns.pid -f
rm /var/log/smartdns/ -fr
rm /etc/smartdns/smartdns.cache -f
rm /var/lib/smartdns/ -fr
fi
exit 0
================================================
FILE: package/openwrt/custom.conf
================================================
# Add custom settings here.
# please read https://pymumu.github.io/smartdns/config/basic-config/
================================================
FILE: package/openwrt/debian-binary
================================================
2.0
================================================
FILE: package/openwrt/domain-block.list
================================================
# domain block list, one domain name per line.
# example: block a.com, and b.com
# a.com
# b.com
================================================
FILE: package/openwrt/domain-forwarding.list
================================================
# domain forwarding list, one domain name per line.
# example: forwarding a.com, and b.com
# a.com
# b.com
================================================
FILE: package/openwrt/files/etc/config/smartdns
================================================
config 'smartdns'
option 'enabled' '0'
config 'domain-rule'
================================================
FILE: package/openwrt/files/etc/init.d/smartdns
================================================
#!/bin/sh /etc/rc.common
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
# smartdns is free software under the GPLv3 (or later).
# Distributed without any warranty; see the license for details.
# Full license: http://www.gnu.org/licenses/
START=19
STOP=82
NAME=smartdns
USE_PROCD=1
SERVICE_USE_PID=1
SERVICE_WRITE_PID=1
SERVICE_DAEMONIZE=1
SERVICE_PID_FILE="/run/smartdns.pid"
if [ ! -d "/run" ]; then
SERVICE_PID_FILE="/var/run/smartdns.pid"
fi
SMARTDNS_DOWNLOAD_TMP_DIR="/tmp/smartdns-download"
SMARTDNS_DEFAULT_FORWARDING_FILE="/etc/smartdns/domain-forwarding.list"
SMARTDNS_DEFAULT_DOMAIN_BLOCK_FILE="/etc/smartdns/domain-block.list"
SMARTDNS_CONF_DIR="/etc/smartdns"
SMARTDNS_CONF_DOWNLOAD_DIR="$SMARTDNS_CONF_DIR/conf.d"
SMARTDNS_DOWNLOAD_DIR="$SMARTDNS_CONF_DIR/download"
SMARTDNS_DOMAIN_LIST_DOWNLOAD_DIR="$SMARTDNS_CONF_DIR/domain-set"
SMARTDNS_IP_SET_DOWNLOAD_DIR="$SMARTDNS_CONF_DIR/ip-set"
SMARTDNS_VAR_CONF_DIR="/var/etc/smartdns"
SMARTDNS_CONF="$SMARTDNS_VAR_CONF_DIR/smartdns.conf"
ADDRESS_CONF="$SMARTDNS_CONF_DIR/address.conf"
BLACKLIST_IP_CONF="$SMARTDNS_CONF_DIR/blacklist-ip.conf"
CUSTOM_CONF="$SMARTDNS_CONF_DIR/custom.conf"
SMARTDNS_CONF_TMP="${SMARTDNS_CONF}.tmp"
EXTRA_COMMANDS="updatefiles"
EXTRA_HELP=" updatefiles Update files"
COREDUMP="0"
RESPAWN="1"
DO_RELOAD="0"
set_forward_dnsmasq()
{
local PORT="$1"
addr="127.0.0.1#$PORT"
# space in suffix is important
OLD_SERVER="$(uci -q get dhcp.@dnsmasq[0].server) "
if echo "$OLD_SERVER" | grep "^$addr " >/dev/null 2>&1; then
return
fi
uci_batch=""
uci_batch="$uci_batch delete dhcp.@dnsmasq[0].server\n"
uci_batch="$uci_batch add_list dhcp.@dnsmasq[0].server=\"$addr\"\n"
uci_batch="$uci_batch set dhcp.@dnsmasq[0].noresolv=1\n"
uci_batch="$uci_batch set dhcp.@dnsmasq[0].rebind_protection=0\n"
uci_batch="$uci_batch set dhcp.@dnsmasq[0].domainneeded=0\n"
echo -e "$uci_batch" | uci batch -q -
uci commit dhcp
/etc/init.d/dnsmasq reload
}
stop_forward_dnsmasq()
{
local OLD_PORT="$1"
local norestart="$2"
addr="127.0.0.1#$OLD_PORT"
OLD_SERVER="$(uci -q get dhcp.@dnsmasq[0].server) "
if ! echo "$OLD_SERVER" | grep "^$addr " >/dev/null 2>&1; then
return
fi
uci_batch=""
uci_batch="$uci_batch delete dhcp.@dnsmasq[0].server\n"
uci_batch="$uci_batch delete dhcp.@dnsmasq[0].noresolv\n"
uci_batch="$uci_batch set dhcp.@dnsmasq[0].rebind_protection=1\n"
uci_batch="$uci_batch set dhcp.@dnsmasq[0].domainneeded=1\n"
echo -e "$uci_batch" | uci batch -q -
uci commit dhcp
[ "$norestart" != "1" ] && /etc/init.d/dnsmasq reload
}
set_main_dns()
{
local hostip
hostip="$(uci -q get network.lan.ipaddr | sed 's/\/.*//g')"
dnsmasq_port="$(uci -q get dhcp.@dnsmasq[0].port)"
[ -z "$dnsmasq_port" ] && dnsmasq_port="53"
[ -z "$hostip" ] && return
uci_batch=""
[ "$dnsmasq_port" = "53" ] && {
uci_batch="$uci_batch set dhcp.@dnsmasq[0].port=0\n"
uci_batch="$uci_batch add_list dhcp.lan.dhcp_option=\"6,$hostip\"\n"
}
# for some third-party firmware
redir_dns="$(uci -q get dhcp.@dnsmasq[0].dns_redirect)"
[ "$redir_dns" = "1" ] && {
uci_batch="$uci_batch set dhcp.@dnsmasq[0].dns_redirect=0\n"
uci_batch="$uci_batch set dhcp.@dnsmasq[0].old_dns_redirect=1\n"
}
if [ -z "$uci_batch" ]; then
return
fi
echo -e "$uci_batch" | uci batch -q -
uci commit dhcp
/etc/init.d/dnsmasq reload
}
stop_main_dns()
{
local norestart="$1"
hostip="$(uci -q get network.lan.ipaddr)"
dnsmasq_port="$(uci -q get dhcp.@dnsmasq[0].port)"
redir_dns="$(uci -q get dhcp.@dnsmasq[0].old_dns_redirect)"
[ "$dnsmasq_port" != "0" ] && return
uci_batch=""
[ "$redir_dns" = "1" ] && {
uci_batch="$uci_batch set dhcp.@dnsmasq[0].dns_redirect=1\n"
uci_batch="$uci_batch delete dhcp.@dnsmasq[0].old_dns_redirect\n"
}
uci_batch="$uci_batch delete dhcp.@dnsmasq[0].port\n"
uci_batch="$uci_batch del_list dhcp.lan.dhcp_option=\"6,$hostip\"\n"
echo -e "$uci_batch" | uci batch -q -
uci commit dhcp
[ "$norestart" != "1" ] && /etc/init.d/dnsmasq reload
}
clear_iptable()
{
local OLD_PORT="$1"
local ipv6_server=$2
which iptables >/dev/null 2>&1
[ $? -ne 0 ] && return
IPS="$(ifconfig | grep "inet addr" | grep -v ":127" | grep "Bcast" | awk '{print $2}' | awk -F : '{print $2}')"
for IP in $IPS
do
iptables -t nat -D PREROUTING -p udp -d "$IP" --dport 53 -j REDIRECT --to-ports "$OLD_PORT" >/dev/null 2>&1
iptables -t nat -D PREROUTING -p tcp -d "$IP" --dport 53 -j REDIRECT --to-ports "$OLD_PORT" >/dev/null 2>&1
done
[ "$ipv6_server" = 0 ] && return
IPS="$(ifconfig | grep "inet6 addr" | grep -v " fe80::" | grep -v " ::1" | grep "Global" | awk '{print $3}')"
for IP in $IPS
do
ip6tables -t nat -D PREROUTING -p udp -d "$IP" --dport 53 -j REDIRECT --to-ports "$OLD_PORT" >/dev/null 2>&1
ip6tables -t nat -D PREROUTING -p tcp -d "$IP" --dport 53 -j REDIRECT --to-ports "$OLD_PORT" >/dev/null 2>&1
done
}
service_triggers() {
procd_add_reload_trigger firewall
procd_add_reload_trigger smartdns
}
conf_append()
{
echo "$1 $2" >> $SMARTDNS_CONF_TMP
}
get_tz()
{
SET_TZ=""
[ -e "/etc/localtime" ] && return
for tzfile in /etc/TZ /var/etc/TZ
do
[ -e "$tzfile" ] || continue
tz="$(cat $tzfile 2>/dev/null)"
done
[ -z "$tz" ] && return
SET_TZ=$tz
}
load_server()
{
local section="$1"
local ADDITIONAL_ARGS=""
local DNS_ADDRESS=""
local IS_URI="0"
config_get_bool enabled "$section" "enabled" "1"
config_get port "$section" "port" ""
config_get type "$section" "type" "udp"
config_get ip "$section" "ip" ""
config_get tls_host_verify "$section" "tls_host_verify" ""
config_get no_check_certificate "$section" "no_check_certificate" "0"
config_get host_name "$section" "host_name" ""
config_get http_host "$section" "http_host" ""
config_get server_group "$section" "server_group" ""
config_get_bool exclude_default_group "$section" "exclude_default_group" "0"
config_get blacklist_ip "$section" "blacklist_ip" "0"
config_get check_edns "$section" "check_edns" "0"
config_get spki_pin "$section" "spki_pin" ""
config_get addition_arg "$section" "addition_arg" ""
config_get set_mark "$section" "set_mark" ""
config_get_bool use_proxy "$section" "use_proxy" "0"
config_get fallback "$section" "fallback" "0"
[ "$enabled" = "0" ] && return
if [ -z "$ip" ] || [ -z "$type" ]; then
return
fi
SERVER="server"
if [ "$type" = "tcp" ]; then
SERVER="server-tcp"
elif [ "$type" = "tls" ]; then
SERVER="server-tls"
elif [ "$type" = "https" ]; then
SERVER="server-https"
elif [ "$type" = "quic" ]; then
SERVER="server-quic"
elif [ "$type" = "h3" ]; then
SERVER="server-h3"
fi
if echo "$ip" | grep "://" >/dev/null 2>&1; then
IS_URI="1"
elif echo "$ip" | grep ":" >/dev/null 2>&1; then
if ! echo "$ip" | grep -q "\\[" >/dev/null 2>&1; then
ip="[$ip]"
fi
fi
[ -z "$tls_host_verify" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -tls-host-verify $tls_host_verify"
[ "$no_check_certificate" = "0" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -no-check-certificate"
[ -z "$host_name" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -host-name $host_name"
[ -z "$http_host" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -http-host $http_host"
[ -z "$server_group" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -group $server_group"
[ "$exclude_default_group" = "0" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -exclude-default-group"
[ "$blacklist_ip" = "0" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -blacklist-ip"
[ "$check_edns" = "0" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -check-edns"
[ -z "$spki_pin" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -spki-pin $spki_pin"
[ -z "$set_mark" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -set-mark $set_mark"
[ "$use_proxy" = "0" ] || ADDITIONAL_ARGS="$ADDITIONAL_ARGS -proxy default-proxy"
[ "$fallback" = "1" ] && addition_arg="$addition_arg -fallback"
if [ -z "$port" ] || [ "$IS_URI" = "1" ]; then
DNS_ADDRESS="$ip"
else
DNS_ADDRESS="$ip:$port"
fi
conf_append "$SERVER" "$DNS_ADDRESS $ADDITIONAL_ARGS $addition_arg"
}
restart_crond()
{
/etc/init.d/cron restart >/dev/null 2>&1
}
disable_auto_update()
{
local no_restart="$1"
grep "/etc/init.d/smartdns updatefiles" /etc/crontabs/root 1>/dev/null 2>&1
if [ $? -ne 0 ]; then
return
fi
sed -i '\@/etc/init.d/smartdns updatefiles@d' /etc/crontabs/root
if [ "$no_restart" = "1" ]; then
return
fi
restart_crond
}
enable_auto_update()
{
grep "0 $auto_update_day_time * * $auto_update_week_time /etc/init.d/smartdns updatefiles" /etc/crontabs/root 2>/dev/null
if [ $? -eq 0 ]; then
return
fi
disable_auto_update 1
echo "0 $auto_update_day_time * * $auto_update_week_time /etc/init.d/smartdns updatefiles" >> /etc/crontabs/root
restart_crond
}
load_domain_rules()
{
local section="$1"
local domain_set_args=""
local domain_set_name="domain"
local block_domain_set_file=""
config_get server_group "$section" "server_group" ""
[ ! -z "$server_group" ] && domain_set_args="$domain_set_args -nameserver $server_group"
config_get speed_check_mode "$section" "speed_check_mode" ""
[ ! -z "$speed_check_mode" ] && domain_set_args="$domain_set_args -speed-check-mode $speed_check_mode"
config_get dualstack_ip_selection "$section" "dualstack_ip_selection" ""
[ "$dualstack_ip_selection" = "no" ] && domain_set_args="$domain_set_args -dualstack-ip-selection no"
[ "$dualstack_ip_selection" = "yes" ] && domain_set_args="$domain_set_args -dualstack-ip-selection yes"
config_get_bool force_aaaa_soa "$section" "force_aaaa_soa" "0"
[ "$force_aaaa_soa" = "1" ] && domain_set_args="$domain_set_args -address #6"
config_get ipset_name "$section" "ipset_name" ""
[ ! -z "$ipset_name" ] && domain_set_args="$domain_set_args -ipset $ipset_name"
config_get nftset_name "$section" "nftset_name" ""
[ ! -z "$nftset_name" ] && domain_set_args="$domain_set_args -nftset '$nftset_name'"
config_get addition_flag "$section" "addition_flag" ""
[ ! -z "$addition_flag" ] && domain_set_args="$domain_set_args $addition_flag"
config_get forwarding_domain_set_file "$section" "forwarding_domain_set_file" ""
[ ! -z "$forwarding_domain_set_file" ] && {
[ ! -e "$forwarding_domain_set_file" ] && touch $forwarding_domain_set_file
conf_append "domain-set" "-name ${domain_set_name}-forwarding-file -file '$forwarding_domain_set_file'"
conf_append "domain-rules" "/domain-set:${domain_set_name}-forwarding-file/ $domain_set_args"
}
[ ! -z "$domain_set_args" ] && {
[ ! -e "$SMARTDNS_DEFAULT_FORWARDING_FILE" ] && touch $SMARTDNS_DEFAULT_FORWARDING_FILE
conf_append "domain-set" "-name ${domain_set_name}-forwarding-list -file $SMARTDNS_DEFAULT_FORWARDING_FILE"
conf_append "domain-rules" "/domain-set:${domain_set_name}-forwarding-list/ $domain_set_args"
}
config_get block_domain_set_file "$section" "block_domain_set_file"
[ ! -z "$block_domain_set_file" ] && {
[ ! -e "$block_domain_set_file" ] && touch $block_domain_set_file
conf_append "domain-set" "-name ${domain_set_name}-block-file -file '$block_domain_set_file'"
conf_append "domain-rules" "/domain-set:${domain_set_name}-block-file/ -address #"
}
[ ! -e "$SMARTDNS_DEFAULT_DOMAIN_BLOCK_FILE" ] && touch $SMARTDNS_DEFAULT_DOMAIN_BLOCK_FILE
conf_append "domain-set" "-name ${domain_set_name}-block-list -file $SMARTDNS_DEFAULT_DOMAIN_BLOCK_FILE"
conf_append "domain-rules" "/domain-set:${domain_set_name}-block-list/ -address #"
}
client_rule_addr_append()
{
conf_append "client-rules" "$1"
}
load_client_rules()
{
local section="$1"
local client_set_args=""
local client_set_name="$section"
local block_domain_set_file=""
config_get_bool enabled "$section" "enabled" "0"
[ "$enabled" != "1" ] && return
conf_append "group-begin" "client-group-${section}"
config_list_foreach "$section" "client_addr" client_rule_addr_append
config_get client_addr_file "$section" "client_addr_file" ""
[ ! -z "$client_addr_file" ] && {
[ ! -e "$client_addr_file" ] && touch $client_addr_file
conf_append "ip-set" "-name client-rule-list-${client_set_name} -file '$client_addr_file'"
conf_append "client-rules" "ip-set:client-rule-list-${client_set_name}"
}
config_get server_group "$section" "server_group" ""
[ ! -z "$server_group" ] && conf_append "nameserver $server_group"
config_get speed_check_mode "$section" "speed_check_mode" ""
[ ! -z "$speed_check_mode" ] && conf_append "speed-check-mode" "$speed_check_mode"
config_get dualstack_ip_selection "$section" "dualstack_ip_selection" "0"
[ "$dualstack_ip_selection" = "0" ] && conf_append "dualstack-ip-selection" "no"
config_get force_aaaa_soa "$section" "force_aaaa_soa" "0"
[ "$force_aaaa_soa" = "1" ] && qtype_soa_list="$qtype_soa_list 28"
config_get force_https_soa "$section" "force_https_soa" "1"
[ "$force_https_soa" = "1" ] && qtype_soa_list="$qtype_soa_list 65"
config_get ipset_name "$section" "ipset_name" ""
[ -z "$ipset_name" ] || conf_append "ipset" "$ipset_name"
config_get nftset_name "$section" "nftset_name" ""
[ -z "$nftset_name" ] || conf_append "nftset" "$nftset_name"
config_list_foreach "$section" "conf_files" conf_append_conf_files
[ ! -z "$qtype_soa_list" ] && {
conf_append "force-qtype-SOA" "-"
conf_append "force-qtype-SOA" "$qtype_soa_list"
}
config_get block_domain_set_file "$section" "block_domain_set_file" ""
[ -e "$block_domain_set_file" ] && {
conf_append "domain-set" "-name client-block-file-${client_set_name} -file '$block_domain_set_file'"
conf_append "domain-rules" "/domain-set:client-block-file-${client_set_name}/ -address #"
}
conf_append "group-end"
}
load_domain_rule_list()
{
local section="$1"
local domain_set_args=""
local domain_set_name="$section"
config_get_bool enabled "$section" "enabled" "0"
[ "$enabled" != "1" ] && return
config_get server_group "$section" "server_group" ""
[ ! -z "$server_group" ] && domain_set_args="$domain_set_args -nameserver $server_group"
config_get block_domain_type "$section" "block_domain_type" ""
[ "$block_domain_type" = "all" ] && domain_set_args="$domain_set_args -address #"
[ "$block_domain_type" = "ipv4" ] && domain_set_args="$domain_set_args -address #4"
[ "$block_domain_type" = "ipv6" ] && domain_set_args="$domain_set_args -address #6"
config_get speed_check_mode "$section" "speed_check_mode" ""
[ ! -z "$speed_check_mode" ] && domain_set_args="$domain_set_args -speed-check-mode $speed_check_mode"
config_get dualstack_ip_selection "$section" "dualstack_ip_selection" ""
[ "$dualstack_ip_selection" = "no" ] && domain_set_args="$domain_set_args -dualstack-ip-selection no"
[ "$dualstack_ip_selection" = "yes" ] && domain_set_args="$domain_set_args -dualstack-ip-selection yes"
config_get_bool force_aaaa_soa "$section" "force_aaaa_soa" "0"
[ "$force_aaaa_soa" = "1" ] && domain_set_args="$domain_set_args -address #6"
config_get ipset_name "$section" "ipset_name" ""
[ ! -z "$ipset_name" ] && domain_set_args="$domain_set_args -ipset $ipset_name"
config_get nftset_name "$section" "nftset_name" ""
[ ! -z "$nftset_name" ] && domain_set_args="$domain_set_args -nftset '$nftset_name'"
config_get domain_list_file "$section" "domain_list_file" ""
[ -z "$domain_list_file" ] && return
config_get addition_flag "$section" "addition_flag" ""
[ ! -z "$addition_flag" ] && domain_set_args="$domain_set_args $addition_flag"
[ -z "$domain_set_args" ] && return
[ ! -e "$domain_list_file" ] && touch $domain_list_file
conf_append "domain-set" "-name domain-rule-list-${domain_set_name} -file '$domain_list_file'"
conf_append "domain-rules" "/domain-set:domain-rule-list-${domain_set_name}/ $domain_set_args"
}
ip_rule_addr_append()
{
conf_append "ip-rules" "$1 $IP_set_args"
}
load_IP_rule_list()
{
local section="$1"
local IP_set_args=""
local IP_set_name="$section"
config_get_bool enabled "$section" "enabled" "0"
[ "$enabled" != "1" ] && return
config_get ip_set_file "$section" "ip_set_file" ""
config_get_bool whitelist_ip "$section" "whitelist_ip" "0"
[ "$whitelist_ip" = "1" ] && IP_set_args="$IP_set_args -whitelist-ip"
config_get_bool blacklist_ip "$section" "blacklist_ip" "0"
[ "$blacklist_ip" = "1" ] && IP_set_args="$IP_set_args -blacklist-ip"
config_get_bool ignore_ip "$section" "ignore_ip" "0"
[ "$ignore_ip" = "1" ] && IP_set_args="$IP_set_args -ignore-ip"
config_get_bool bogus_nxdomain "$section" "bogus_nxdomain" "0"
[ "$bogus_nxdomain" = "1" ] && IP_set_args="$IP_set_args -bogus-nxdomain"
config_get ip_alias "$section" "ip_alias" ""
[ ! -z "$ip_alias" ] && {
ip_alias="$(echo "$ip_alias" | sed 's/ /,/g')"
IP_set_args="$IP_set_args -ip-alias $ip_alias"
}
config_get addition_flag "$section" "addition_flag" ""
[ ! -z "$addition_flag" ] && IP_set_args="$IP_set_args $addition_flag"
[ -z "$IP_set_args" ] && return
[ ! -z "$ip_set_file" ] && [ -e "$ip_set_file" ] && {
conf_append "ip-set" "-name ip-rule-list-file-${section} -file '$ip_set_file'"
conf_append "ip-rules" "ip-set:ip-rule-list-file-${section} $IP_set_args"
}
config_list_foreach "$section" "ip_addr" ip_rule_addr_append
}
conf_append_bind()
{
local ADDR=""
local bind_type="$1"
local port="$2"
local devices="$3"
local device=""
local ipv6_server="$4"
local ARGS="$5"
if [ "$ipv6_server" = "1" ]; then
ADDR="[::]"
else
ADDR=""
fi
devices=$(echo "$devices" | sed 's/,/ /g')
[ ! -z "$devices" ] && devices="$devices lo"
[ -z "$devices" ] && devices="-"
for device in $devices; do
device="@$device"
[ "$device" = "@-" ] && device=""
conf_append "$bind_type" "$ADDR:$port$device $ARGS"
done
}
load_second_server()
{
local section="$1"
local ARGS=""
local ADDR=""
local device=""
config_get_bool seconddns_enabled "$section" "seconddns_enabled" "0"
[ "$seconddns_enabled" = "0" ] && return
config_get seconddns_port "$section" "seconddns_port" "6553"
config_get_bool seconddns_no_speed_check "$section" "seconddns_no_speed_check" "0"
[ "$seconddns_no_speed_check" = "1" ] && ARGS="$ARGS -no-speed-check"
config_get seconddns_server_group "$section" "seconddns_server_group" ""
[ -z "$seconddns_server_group" ] || ARGS="$ARGS -group $seconddns_server_group"
config_get_bool seconddns_no_rule_addr "$section" "seconddns_no_rule_addr" "0"
[ "$seconddns_no_rule_addr" = "1" ] && ARGS="$ARGS -no-rule-addr"
config_get_bool seconddns_no_rule_nameserver "$section" "seconddns_no_rule_nameserver" "0"
[ "$seconddns_no_rule_nameserver" = "1" ] && ARGS="$ARGS -no-rule-nameserver"
config_get_bool seconddns_no_rule_ipset "$section" "seconddns_no_rule_ipset" "0"
[ "$seconddns_no_rule_ipset" = "1" ] && ARGS="$ARGS -no-rule-ipset"
config_get_bool seconddns_no_rule_soa "$section" "seconddns_no_rule_soa" "0"
[ "$seconddns_no_rule_soa" = "1" ] && ARGS="$ARGS -no-rule-soa"
config_get_bool seconddns_no_dualstack_selection "$section" "seconddns_no_dualstack_selection" "0"
[ "$seconddns_no_dualstack_selection" = "1" ] && ARGS="$ARGS -no-dualstack-selection"
config_get_bool seconddns_no_cache "$section" "seconddns_no_cache" "0"
[ "$seconddns_no_cache" = "1" ] && ARGS="$ARGS -no-cache"
config_get_bool seconddns_force_aaaa_soa "$section" "seconddns_force_aaaa_soa" "0"
[ "$seconddns_force_aaaa_soa" = "1" ] && ARGS="$ARGS -force-aaaa-soa"
config_get_bool seconddns_force_https_soa "$section" "seconddns_force_https_soa" "0"
[ "$seconddns_force_https_soa" = "1" ] && ARGS="$ARGS -force-https-soa"
config_get_bool seconddns_no_ip_alias "$section" "seconddns_no_ip_alias" "0"
[ "$seconddns_no_ip_alias" = "1" ] && ARGS="$ARGS -no-ip-alias"
config_get seconddns_ipset_name "$section" "seconddns_ipset_name" ""
[ -z "$seconddns_ipset_name" ] || ARGS="$ARGS -ipset $seconddns_ipset_name"
config_get seconddns_nftset_name "$section" "seconddns_nftset_name" ""
[ -z "$seconddns_nftset_name" ] || ARGS="$ARGS -nftset $seconddns_nftset_name"
config_get_bool bind_device "$section" "bind_device" "0"
config_get bind_device_name "$section" "bind_device_name" "${lan_device}"
[ ! -z "$bind_device_name" ] && [ "$bind_device" = "1" ] && device="${bind_device_name}"
config_get_bool "seconddns_tcp_server" "$section" "seconddns_tcp_server" "1"
config_get ipv6_server "$section" "ipv6_server" "1"
config_get seconddns_server_flags "$section" "seconddns_server_flags" ""
[ -z "$seconddns_server_flags" ] || ARGS="$ARGS $seconddns_server_flags"
conf_append_bind "bind" "$seconddns_port" "$device" "$ipv6_server" "$ARGS"
[ "$seconddns_tcp_server" = "1" ] && conf_append_bind "bind-tcp" "$seconddns_port" "$device" "$ipv6_server" "$ARGS"
}
conf_append_conf_files()
{
local conf_file="$1"
if [ "$1" != "${1#/}" ]; then
fullpath="$1"
else
fullpath="$SMARTDNS_CONF_DOWNLOAD_DIR/$conf_file"
fi
[ -f "$fullpath" ] && {
conf_append "conf-file" "'$fullpath'"
}
}
conf_append_hosts_files()
{
local hosts_file="$1"
if [ "$1" != "${1#/}" ]; then
fullpath="$1"
else
fullpath="$SMARTDNS_DOWNLOAD_DIR/$hosts_file"
fi
[ -f "$fullpath" ] && {
conf_append "hosts-file" "'$fullpath'"
}
}
load_service()
{
local section="$1"
args=""
local device=""
dnsmasq_lease_file="$(uci -q get dhcp.@dnsmasq[0].leasefile)"
dnsmasq_port="$(uci -q get dhcp.@dnsmasq[0].port)"
resolve_file="$(uci -q get dhcp.@dnsmasq[0].resolvfile)"
lan_device="$(uci -q get network.lan.device)"
[ -z "$dnsmasq_lease_file" ] && dnsmasq_lease_file="/tmp/dhcp.leases"
[ -z "$dnsmasq_port" ] && dnsmasq_port="53"
[ -z "$resolve_file" ] && resolve_file="/tmp/resolv.conf.d/resolv.conf.auto"
qtype_soa_list=""
mkdir -p $SMARTDNS_VAR_CONF_DIR
rm -f $SMARTDNS_CONF_TMP
config_get_bool enabled "$section" "enabled" '0'
config_get server_name "$section" "server_name" ""
[ -z "$server_name" ] || conf_append "server-name" "$server_name"
config_get coredump "$section" "coredump" "0"
[ "$coredump" = "1" ] && COREDUMP="1"
config_get port "$section" "port" "53"
config_get ipv6_server "$section" "ipv6_server" "1"
config_get tcp_server "$section" "tcp_server" "1"
config_get tls_server "$section" "tls_server" "0"
config_get tls_server_port "$section" "tls_server_port" "853"
config_get doh_server "$section" "doh_server" "0"
config_get doh_server_port "$section" "doh_server_port" "843"
config_get bind_cert "$section" "bind_cert" ""
config_get bind_cert_key "$section" "bind_cert_key" ""
config_get bind_cert_key_pass "$section" "bind_cert_key_pass" ""
config_get server_flags "$section" "server_flags" ""
config_get auto_update_week_time "$section" "auto_update_week_time" "*"
config_get auto_update_day_time "$section" "auto_update_day_time" "5"
config_get speed_check_mode "$section" "speed_check_mode" ""
[ ! -z "$speed_check_mode" ] && conf_append "speed-check-mode" "$speed_check_mode"
config_get dualstack_ip_selection "$section" "dualstack_ip_selection" "0"
[ "$dualstack_ip_selection" = "0" ] && conf_append "dualstack-ip-selection" "no"
config_get prefetch_domain "$section" "prefetch_domain" "0"
[ "$prefetch_domain" = "1" ] && conf_append "prefetch-domain" "yes"
config_get serve_expired "$section" "serve_expired" "0"
[ "$serve_expired" = "1" ] && conf_append "serve-expired" "yes"
config_get cache_size "$section" "cache_size" ""
[ -z "$cache_size" ] || conf_append "cache-size" "$cache_size"
config_get resolve_local_hostnames "$section" "resolve_local_hostnames" "1"
[ "$resolve_local_hostnames" = "1" ] && conf_append "dnsmasq-lease-file" "$dnsmasq_lease_file"
config_get force_aaaa_soa "$section" "force_aaaa_soa" "0"
[ "$force_aaaa_soa" = "1" ] && qtype_soa_list="$qtype_soa_list 28"
config_get force_https_soa "$section" "force_https_soa" "1"
[ "$force_https_soa" = "1" ] && qtype_soa_list="$qtype_soa_list 65"
config_get auto_set_dnsmasq "$section" "auto_set_dnsmasq" "1"
config_get ipset_name "$section" "ipset_name" ""
[ -z "$ipset_name" ] || conf_append "ipset" "$ipset_name"
config_get nftset_name "$section" "nftset_name" ""
[ -z "$nftset_name" ] || conf_append "nftset" "$nftset_name"
config_get ipset_no_speed "$section" "ipset_no_speed" ""
[ -z "$ipset_no_speed" ] || conf_append "ipset-no-speed" "$ipset_no_speed"
config_get nftset_no_speed "$section" "nftset_no_speed" ""
[ -z "$nftset_no_speed" ] || conf_append "nftset-no-speed" "$nftset_no_speed"
config_get rr_ttl "$section" "rr_ttl" ""
[ -z "$rr_ttl" ] || conf_append "rr-ttl" "$rr_ttl"
config_get rr_ttl_min "$section" "rr_ttl_min" ""
[ -z "$rr_ttl_min" ] || conf_append "rr-ttl-min" "$rr_ttl_min"
config_get rr_ttl_max "$section" "rr_ttl_max" ""
[ -z "$rr_ttl_max" ] || conf_append "rr-ttl-max" "$rr_ttl_max"
config_get rr_ttl_reply_max "$section" "rr_ttl_reply_max" ""
[ -z "$rr_ttl_reply_max" ] || conf_append "rr-ttl-reply-max" "$rr_ttl_reply_max"
config_get log_size "$section" "log_size" "64K"
[ -z "$log_size" ] || conf_append "log-size" "$log_size"
config_get log_num "$section" "log_num" "1"
[ -z "$log_num" ] || conf_append "log-num" "$log_num"
config_get log_level "$section" "log_level" "error"
[ -z "$log_level" ]|| conf_append "log-level" "$log_level"
config_get log_file "$section" "log_file" ""
[ -z "$log_file" ] || conf_append "log-file" "$log_file"
config_get log_output_mode "$section" "log_output_mode" ""
[ "$log_output_mode" = "syslog" ] && conf_append "log-syslog" "yes"
config_get_bool enable_audit_log "$section" "enable_audit_log" "0"
[ "$enable_audit_log" = "1" ] && conf_append "audit-enable" "yes"
config_get audit_log_size "$section" "audit_log_size" "64K"
[ -z "$audit_log_size" ] || conf_append "audit-size" "$audit_log_size"
config_get audit_log_num "$section" "audit_log_num" "1"
[ -z "$audit_log_num" ] || conf_append "audit-num" "$audit_log_num"
config_get audit_log_file "$section" "audit_log_file" ""
[ -z "$audit_log_file" ] || conf_append "audit-file" "$audit_log_file"
config_get audit_log_output_mode "$section" "audit_log_output_mode" ""
[ "$audit_log_output_mode" = "syslog" ] && conf_append "audit-syslog" "yes"
config_get response_mode "$section" "response_mode" ""
[ -z "$response_mode" ] || conf_append "response-mode" "$response_mode"
config_get_bool enable_auto_update "$section" "enable_auto_update" "0"
[ "$enabled" = "1" -a "$enable_auto_update" = "1" ] && enable_auto_update || disable_auto_update
config_get_bool bind_device "$section" "bind_device" "0"
config_get bind_device_name "$section" "bind_device_name" "${lan_device}"
[ ! -z "$bind_device_name" ] && [ "$bind_device" = "1" ] && device="${bind_device_name}"
config_get cache_file "$section" "cache_file" "$SMARTDNS_CONF_DIR/smartdns.cache"
config_get_bool cache_persist "$section" "cache_persist" "0"
[ "$cache_persist" = "1" ] && {
conf_append "cache-persist" "yes"
conf_append "cache-file" "$cache_file"
}
[ "$cache_persist" = "0" ] && {
conf_append "cache-persist" "no"
[ -f "$cache_file" ] && rm -f "$cache_file"
}
config_get proxy_server "$section" "proxy_server" ""
[ -z "$proxy_server" ] || conf_append "proxy-server" "$proxy_server -name default-proxy"
config_get dns64 "$section" "dns64" ""
[ -z "$dns64" ] || conf_append "dns64" "$dns64"
config_get ddns_domain "$section" "ddns_domain" ""
[ -z "$ddns_domain" ] || conf_append "ddns-domain" "$ddns_domain"
config_get local_domain "$section" "local_domain" ""
[ -z "$local_domain" ] || conf_append "local-domain" "$local_domain"
config_get_bool mdns_lookup "$section" "mdns_lookup" "0"
[ "$mdns_lookup" = "1" ] && conf_append "mdns-lookup" "yes"
config_get redirect "$section" "redirect" ""
config_get old_port "$section" "old_port" "0"
config_get old_enabled "$section" "old_enabled" "0"
config_get old_auto_set_dnsmasq "$section" "old_auto_set_dnsmasq" "0"
[ -z "$qtype_soa_list" ] || conf_append "force-qtype-SOA" "$qtype_soa_list"
[ -e "$resolve_file" ] && conf_append "resolv-file" "$resolve_file"
# upgrade old configuration
uci_batch=""
if [ "$redirect" = "redirect" ] || [ "$redirect" = "dnsmasq-upstream" ] || [ "$redirect" = "none" ]; then
[ "$redirect" = "redirect" ] && {
clear_iptable "$port"
clear_iptable "$old_port"
uci_batch="$uci_batch delete smartdns.@smartdns[0].port\n"
port="53"
}
[ "$redirect" = "dnsmasq-upstream" ] && {
stop_forward_dnsmasq "$port"
stop_forward_dnsmasq "$old_port"
auto_set_dnsmasq="1"
uci_batch="$uci_batch set smartdns.@smartdns[0].auto_set_dnsmasq=\"1\"\n"
}
[ "$redirect" = "none" ] && {
auto_set_dnsmasq="0"
uci_batch="$uci_batch set smartdns.@smartdns[0].auto_set_dnsmasq=\"0\"\n"
}
uci_batch="$uci_batch delete smartdns.@smartdns[0].redirect\n"
uci_batch="$uci_batch delete smartdns.@smartdns[0].old_redirect\n"
fi
uci_batch="$uci_batch delete smartdns.@smartdns[0].old_port\n"
uci_batch="$uci_batch delete smartdns.@smartdns[0].old_enabled\n"
uci_batch="$uci_batch delete smartdns.@smartdns[0].old_auto_set_dnsmasq\n"
uci_batch="$uci_batch set smartdns.@smartdns[0].old_port=\"$port\"\n"
uci_batch="$uci_batch set smartdns.@smartdns[0].old_enabled=\"$enabled\"\n"
uci_batch="$uci_batch set smartdns.@smartdns[0].old_auto_set_dnsmasq=\"$auto_set_dnsmasq\"\n"
echo -e "$uci_batch" | uci batch -q -
uci commit smartdns
# disable service
[ "$enabled" = "0" ] && {
[ "$old_enabled" = "0" ] && return 1
[ "$old_port" = "53" ] && [ "$old_auto_set_dnsmasq" = "1" ] && stop_main_dns "0"
[ "$old_port" != "53" ] && [ "$old_auto_set_dnsmasq" = "1" ] && stop_forward_dnsmasq "$old_port" "0"
disable_auto_update
return 1
}
# change port
[ "$old_port" != "$port" ] && {
[ "$old_port" = "53" ] && {
no_restart_dnsmasq="1"
[ "$auto_set_dnsmasq" = "0" ] && no_restart_dnsmasq="0"
[ "$old_auto_set_dnsmasq" = "1" ] && stop_main_dns "$no_restart_dnsmasq"
}
[ "$old_port" != "53" ] && [ "$old_auto_set_dnsmasq" = "1" ] && stop_forward_dnsmasq "$old_port" "1"
}
# start service
[ "$port" = "53" ] && {
[ "$auto_set_dnsmasq" = "1" ] && set_main_dns
[ "$auto_set_dnsmasq" = "0" ] && [ "$old_auto_set_dnsmasq" = "1" ] && stop_main_dns "0"
}
[ "$port" != "53" ] && {
[ "$auto_set_dnsmasq" = "1" ] && set_forward_dnsmasq "$port"
[ "$auto_set_dnsmasq" = "0" ] && [ "$old_auto_set_dnsmasq" = "1" ] && stop_forward_dnsmasq "$old_port" "0"
}
conf_append_bind "bind" "$port" "$device" "$ipv6_server" "$server_flags"
[ "$tcp_server" = "1" ] && conf_append_bind "bind-tcp" "$port" "$device" "$ipv6_server" "$server_flags"
[ "$tls_server" = "1" ] && conf_append_bind "bind-tls" "$tls_server_port" "$device" "$ipv6_server" "$server_flags"
[ "$doh_server" = "1" ] && conf_append_bind "bind-https" "$doh_server_port" "$device" "$ipv6_server" "$server_flags"
[ ! -z "$bind_cert" ] && conf_append "bind-cert-file" "$bind_cert"
[ ! -z "$bind_cert_key" ] && conf_append "bind-cert-key-file" "$bind_cert_key"
[ ! -z "$bind_cert_key_pass" ] && conf_append "bind-cert-key-pass" "$bind_cert_key_pass"
load_second_server "$section"
config_foreach load_server "server"
config_list_foreach "$section" "conf_files" conf_append_conf_files
config_list_foreach "$section" "hosts_files" conf_append_hosts_files
config_foreach load_client_rules "client-rule"
config_foreach load_domain_rules "domain-rule"
config_foreach load_domain_rule_list "domain-rule-list"
config_foreach load_IP_rule_list "ip-rule"
config_foreach load_IP_rule_list "ip-rule-list"
config_get_bool ui "$section" "ui" '0'
[ "$ui" = "1" ] && {
config_get ui_port "$section" "ui_port" "6080"
config_get ui_data_dir "$section" "ui_data_dir" "/var/lib/smartdns"
config_get ui_log_max_age "$section" "ui_log_max_age" "30"
ui_log_max_age_s=$((ui_log_max_age * 86400))
conf_append "plugin" "smartdns_ui.so"
conf_append "smartdns-ui.www-root" "/usr/share/smartdns/wwwroot"
conf_append "smartdns-ui.ip" "http://[::]:$ui_port"
conf_append "data-dir" "$ui_data_dir"
conf_append "smartdns-ui.max-query-log-age" "$ui_log_max_age_s"
}
{
echo "conf-file $ADDRESS_CONF"
echo "conf-file $BLACKLIST_IP_CONF"
echo "conf-file $CUSTOM_CONF"
} >> $SMARTDNS_CONF_TMP
mv $SMARTDNS_CONF_TMP $SMARTDNS_CONF
procd_open_instance "smartdns"
[ "$COREDUMP" = "1" ] && {
args="$args -S"
procd_set_param limits core="unlimited"
}
get_tz
[ -z "$SET_TZ" ] || procd_set_param env TZ="$SET_TZ"
procd_set_param command /usr/sbin/smartdns -f -c $SMARTDNS_CONF $args
[ "$RESPAWN" = "1" ] && procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5}
procd_set_param file "$SMARTDNS_CONF"
procd_set_param term_timeout 60
procd_close_instance
}
unload_service()
{
local section="$1"
[ "$DO_RELOAD" = "1" ] && return 0
config_get_bool enabled "$section" "enabled" '0'
dnsmasq_port="$(uci -q get dhcp.@dnsmasq[0].port)"
config_get port "$section" "port" "53"
config_get auto_set_dnsmasq "$section" "auto_set_dnsmasq" "0"
config_get old_enabled "$section" "old_enabled" "0"
config_get old_port "$section" "old_port" "0"
config_get old_auto_set_dnsmasq "$section" "old_auto_set_dnsmasq" "0"
[ -z "${dnsmasq_port}" ] && dnsmasq_port="53"
[ "$enabled" = "1" ] && {
[ "$old_enabled" = "0" ] && return 1
[ "$old_port" = "53" ] && [ "$old_auto_set_dnsmasq" = "1" ] && stop_main_dns "0"
[ "$old_port" != "53" ] && [ "$old_auto_set_dnsmasq" = "1" ] && stop_forward_dnsmasq "$old_port" "0"
}
}
download_file() {
local section="$1"
config_get url "$section" "url" ""
config_get name "$section" "name" ""
config_get filetype "$section" "type" ""
config_get_bool use_proxy "$section" "use_proxy" "0"
[ -z "$url" ] && return 0
[ -z "$name" ] && return 0
[ -z "$filetype" ] && return 0
echo "download $filetype file $name from $url"
[ "$use_proxy" = "1" ] && {
proxy="$(uci -q get smartdns.@smartdns[0].proxy_server)"
[ ! -z "$proxy" ] && {
export http_proxy="$proxy"
export https_proxy="$proxy"
}
}
wget --timeout 120 -q -O "$SMARTDNS_DOWNLOAD_TMP_DIR/$name" "$url"
if [ $? -ne 0 ]; then
echo "download file $name failed"
return 1
fi
echo "download file $name success"
if [ "$filetype" = "list" ]; then
mv "$SMARTDNS_DOWNLOAD_TMP_DIR/$name" "$SMARTDNS_DOMAIN_LIST_DOWNLOAD_DIR/$name"
elif [ "$filetype" = "config" ]; then
mv "$SMARTDNS_DOWNLOAD_TMP_DIR/$name" "$SMARTDNS_CONF_DOWNLOAD_DIR/$name"
elif [ "$filetype" = "ip-set" ]; then
mv "$SMARTDNS_DOWNLOAD_TMP_DIR/$name" "$SMARTDNS_IP_SET_DOWNLOAD_DIR/$name"
else
mv "$SMARTDNS_DOWNLOAD_TMP_DIR/$name" "$SMARTDNS_DOWNLOAD_DIR/$name"
fi
}
check_and_add_entry() {
local docommit=0
uci -q get smartdns.@smartdns[0] >/dev/null
if [ $? -ne 0 ]; then
uci -q add smartdns smartdns >/dev/null
docommit=1
fi
uci -q get smartdns.@client-rule[0] >/dev/null
if [ $? -ne 0 ]; then
uci -q add smartdns client-rule >/dev/null
docommit=1
fi
uci -q get smartdns.@domain-rule[0] >/dev/null
if [ $? -ne 0 ]; then
uci -q add smartdns domain-rule >/dev/null
docommit=1
fi
uci -q get smartdns.@ip-rule[0] >/dev/null
if [ $? -ne 0 ]; then
uci -q add smartdns ip-rule >/dev/null
docommit=1
fi
if [ "$docommit" = "1" ]; then
uci -q commit smartdns >/dev/null
fi
if [ ! -d "$SMARTDNS_DOMAIN_LIST_DOWNLOAD_DIR" ]; then
mkdir -p "$SMARTDNS_DOMAIN_LIST_DOWNLOAD_DIR"
fi
if [ ! -d "$SMARTDNS_CONF_DOWNLOAD_DIR" ]; then
mkdir -p "$SMARTDNS_CONF_DOWNLOAD_DIR"
fi
}
updatefiles() {
config_load "smartdns"
[ ! -d "$SMARTDNS_DOWNLOAD_TMP_DIR" ] && mkdir -p "$SMARTDNS_DOWNLOAD_TMP_DIR"
config_foreach download_file "download-file"
rm -rf "$SMARTDNS_DOWNLOAD_TMP_DIR" >/dev/null 2>&1
reload_service
}
service_stopped()
{
config_load "smartdns"
config_foreach unload_service "smartdns"
}
start_service()
{
check_and_add_entry
config_load "smartdns"
config_foreach load_service "smartdns"
}
reload_service()
{
DO_RELOAD="1"
stop
start
DO_RELOAD="0"
}
================================================
FILE: package/openwrt/make.sh
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
CURR_DIR=$(cd $(dirname $0);pwd)
VER="`date +"1.%Y.%m.%d-%H%M"`"
SMARTDNS_DIR=$CURR_DIR/../../
SMARTDNS_CP=$SMARTDNS_DIR/package/copy-smartdns.sh
SMARTDNS_BIN=$SMARTDNS_DIR/src/smartdns
SMARTDNS_CONF=$SMARTDNS_DIR/etc/smartdns/smartdns.conf
ADDRESS_CONF=$CURR_DIR/address.conf
BLACKLIST_IP_CONF=$CURR_DIR/blacklist-ip.conf
CUSTOM_CONF=$CURR_DIR/custom.conf
DOMAIN_BLOCK_LIST=$CURR_DIR/domain-block.list
DOMAIN_FORWARDING_LIST=$CURR_DIR/domain-forwarding.list
IS_BUILD_SMARTDNS_UI=0
showhelp()
{
echo "Usage: make [OPTION]"
echo "Options:"
echo " -o output directory."
echo " --arch archtecture."
echo " --ver version."
echo " --with-ui build with smartdns-ui plugin."
echo " -h show this message."
}
build()
{
ROOT=/tmp/smartdns-openwrt
rm -fr $ROOT
mkdir -p $ROOT
cp $CURR_DIR/* $ROOT/ -af
cd $ROOT/
mkdir $ROOT/root/usr/sbin -p
mkdir $ROOT/root/etc/init.d -p
mkdir $ROOT/root/etc/smartdns/ -p
mkdir $ROOT/root/etc/smartdns/domain-set/ -p
mkdir $ROOT/root/etc/smartdns/ip-set/ -p
mkdir $ROOT/root/etc/smartdns/conf.d/ -p
mkdir $ROOT/root/etc/smartdns/download/ -p
cp $SMARTDNS_CONF $ROOT/root/etc/smartdns/
cp $ADDRESS_CONF $ROOT/root/etc/smartdns/
cp $BLACKLIST_IP_CONF $ROOT/root/etc/smartdns/
cp $CUSTOM_CONF $ROOT/root/etc/smartdns/
cp $DOMAIN_BLOCK_LIST $ROOT/root/etc/smartdns/
cp $DOMAIN_FORWARDING_LIST $ROOT/root/etc/smartdns/
cp $CURR_DIR/files/etc $ROOT/root/ -af
$SMARTDNS_CP $ROOT/root
if [ $? -ne 0 ]; then
echo "copy smartdns file failed."
rm -fr $ROOT/
return 1
fi
if [ $IS_BUILD_SMARTDNS_UI -ne 0 ]; then
mkdir $ROOT/root/usr/lib/smartdns -p
cp $SMARTDNS_DIR/plugin/smartdns-ui/target/smartdns_ui.so $ROOT/root/usr/lib/smartdns/
if [ $? -ne 0 ]; then
echo "copy smartdns_ui.so file failed."
rm -fr $ROOT/
return 1
fi
mkdir $ROOT/root/usr/share/smartdns/wwwroot -p
cp $WORKDIR/smartdns-webui/out/* $ROOT/root/usr/share/smartdns/wwwroot/ -a
if [ $? -ne 0 ]; then
echo "Failed to copy smartdns-ui web files."
rm -fr $ROOT/
return 1
fi
fi
chmod +x $ROOT/root/etc/init.d/smartdns
INST_SIZE="`du -sb $ROOT/root/ | awk '{print $1}'`"
sed -i "s/^Architecture.*/Architecture: $ARCH/g" $ROOT/control/control
sed -i "s/Version:.*/Version: $VER/" $ROOT/control/control
sed -i "s/^\(bind .*\):53/\1:6053/g" $ROOT/root/etc/smartdns/smartdns.conf
if [ ! -z "$INST_SIZE" ]; then
echo "Installed-Size: $INST_SIZE" >> $ROOT/control/control
fi
if [ "$STATIC" = "yes" ]; then
sed -i "s/Depends:.*/Depends: libc/" $ROOT/control/control
fi
cd $ROOT/control
chmod +x *
tar zcf ../control.tar.gz --owner=0 --group=0 ./
cd $ROOT
tar zcf $ROOT/data.tar.gz -C root --owner=0 --group=0 .
tar zcf $OUTPUTDIR/smartdns.$VER.$FILEARCH.ipk --owner=0 --group=0 ./control.tar.gz ./data.tar.gz ./debian-binary
which apk >/dev/null 2>&1
if [ $? -eq 0 ]; then
APK_VER="`echo $VER | sed 's/[-]/-r/'`"
ARCH="`echo $ARCH | sed 's/all/noarch/g'`"
apk mkpkg \
--info "name:smartdns" \
--info "version:$APK_VER" \
--info "description:A smartdns Server" \
--info "arch:$ARCH" \
--info "license:GPL" \
--info "origin: https://github.com/pymumu/smartdns.git" \
--info "depends:libc libpthread" \
--script "post-install:$ROOT/control/postinst" \
--script "pre-deinstall:$ROOT/control/prerm" \
--files "$ROOT/root/" \
--output "$OUTPUTDIR/smartdns.$VER.$FILEARCH.apk"
if [ $? -ne 0 ]; then
echo "build apk package failed."
rm -fr $ROOT/
return 1
fi
else
echo "== warning: apk tool not found, skip build apk package. =="
fi
rm -fr $ROOT/
}
main()
{
OPTS=`getopt -o o:h --long arch:,ver:,with-ui,filearch: \
-n "" -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$OPTS"
while true; do
case "$1" in
--arch)
ARCH="$2"
shift 2;;
--filearch)
FILEARCH="$2"
shift 2;;
--with-ui)
IS_BUILD_SMARTDNS_UI=1
shift ;;
--ver)
VER="$2"
shift 2;;
-o )
OUTPUTDIR="$2"
shift 2;;
-h | --help )
showhelp
return 0
shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if [ -z "$ARCH" ]; then
echo "please input arch."
return 1;
fi
if [ -z "$FILEARCH" ]; then
FILEARCH=$ARCH
fi
if [ -z "$OUTPUTDIR" ]; then
OUTPUTDIR=$CURR_DIR;
fi
build
}
main $@
exit $?
================================================
FILE: package/optware/S50smartdns
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
SMARTDNS_BIN=/opt/usr/sbin/smartdns
SMARTDNS_CONF=/opt/etc/smartdns/smartdns.conf
DNSMASQ_CONF="/etc/dnsmasq.conf /var/etc/dnsmasq.conf /etc/storage/dnsmasq/dnsmasq.conf"
SMARTDNS_PID=/run/smartdns.pid
SMARTDNS_CHECK_PID=/tmp/smartdns_delay_check.pid
if [ ! -d "/run" ]; then
SMARTDNS_PID=/var/run/smartdns.pid
fi
SMARTDNS_PORT=535
SMARTDNS_OPT=/opt/etc/smartdns/smartdns-opt.conf
# workmode
# DO NOT CHANGE THIS, CHANGE MODE IN smartdns-opt.conf
# 0: run as port only
# 1: redirect port
# 2: replace
SMARTDNS_WORKMODE="1"
SMARTDNS_INIT_SCRIPT="$0"
if [ -f "$SMARTDNS_OPT" ]; then
. "$SMARTDNS_OPT"
fi
set_iptable()
{
local redirect_tcp
redirect_tcp=0
grep ^bind-tcp $SMARTDNS_CONF > /dev/null 2>&1
if [ $? -eq 0 ]; then
redirect_tcp=1;
fi
IPS="$(ifconfig | grep "inet addr" | grep -v ":127" | grep "Bcast" | awk '{print $2}' | awk -F: '{print $2}')"
for IP in $IPS
do
if [ $redirect_tcp -eq 1 ]; then
iptables -t nat -A PREROUTING -p tcp -d "$IP" --dport 53 -j REDIRECT --to-ports "$SMARTDNS_PORT" > /dev/null 2>&1
fi
iptables -t nat -A PREROUTING -p udp -d "$IP" --dport 53 -j REDIRECT --to-ports "$SMARTDNS_PORT" > /dev/null 2>&1
done
}
clear_iptable()
{
IPS="$(ifconfig | grep "inet addr" | grep -v ":127" | grep "Bcast" | awk '{print $2}' | awk -F: '{print $2}')"
for IP in $IPS
do
iptables -t nat -D PREROUTING -p tcp -d "$IP" --dport 53 -j REDIRECT --to-ports "$SMARTDNS_PORT" > /dev/null 2>&1
iptables -t nat -D PREROUTING -p udp -d "$IP" --dport 53 -j REDIRECT --to-ports "$SMARTDNS_PORT" > /dev/null 2>&1
done
}
get_dnsmasq_cmd()
{
CMD="$(ps 2>/dev/null | grep -e '[a-zA-Z]\{0,2\} \{1,\}dnsmasq' | grep -v grep 2>/dev/null)"
if [ ! -z "$CMD" ]; then
return
fi
CMD="$(ps 2>/dev/null | grep '/usr/sbin/dnsmasq' | grep -v grep 2>/dev/null)"
if [ ! -z "$CMD" ]; then
return
fi
CMD="$(ps 2>/dev/null | grep 'dnsmasq' | grep -v grep 2>/dev/null)"
if [ ! -z "$CMD" ]; then
return
fi
CMD="$(ps ax 2>/dev/null | grep -e '[a-zA-Z]\{0,2\} \{1,\}dnsmasq' | grep -v grep 2>/dev/null)"
if [ ! -z "$CMD" ]; then
return
fi
CMD="$(ps ax 2>/dev/null | grep /usr/sbin/dnsmasq | grep -v grep 2>/dev/null)"
if [ ! -z "$CMD" ]; then
return
fi
CMD="$(ps ax 2>/dev/null | grep 'dnsmasq' | grep -v grep 2>/dev/null)"
if [ ! -z "$CMD" ]; then
return
fi
}
get_dnsmasq_cmdline()
{
local CMD=""
local loop=0
while [ $loop -lt 3 ]; do
get_dnsmasq_cmd
if [ ! -z "$CMD" ]; then
break;
fi
$SMARTDNS_INIT_SCRIPT stop
sleep 1
loop=$((loop+1))
done
if [ -z "$CMD" ]; then
echo "cannot find dnsmasq"
service restart_dnsmasq 2>/dev/null
return 1
fi
# check multiple dnsmasq
linecount="$(echo "$CMD" | wc -l)"
if [ $linecount -eq 1 ]; then
PID="$(echo "$CMD" | awk '{print $1}')"
elif [ $linecount -gt 1 ]; then
PID1="$(echo "$CMD" | awk 'NR==1{print $1}')"
PID2="$(echo "$CMD" | awk 'NR==2{print $1}')"
PID2_PPID="$(grep 'PPid:' /proc/$PID2/status | awk '{print $2}' 2>/dev/null)"
if [ "$PID2_PPID" != "$PID1" ]; then
kill -9 "$PID2" 2>/dev/null
fi
PID=$PID1
else
echo "find multiple dnsmasq, but not started by the same process"
return 1
fi
if [ ! -d "/proc/$PID" ]; then
echo "dnsmasq is not running"
return 1
fi
CMD="$(echo "$CMD" | head -n 1)"
DNSMASQ_CMD="$(echo "$CMD" | awk '{for(i=5; i<=NF;i++)printf $i " "}')"
return 0
}
restart_dnsmasq()
{
if [ -z "$DNSMASQ_CMD" ]; then
get_dnsmasq_cmdline
if [ $? -ne 0 ]; then
echo "cannot find dnsmasq"
return 1
fi
fi
if [ ! -z "$PID" ]; then
kill -9 "$PID"
fi
$DNSMASQ_CMD
return $?
}
add_dhcp_options6()
{
CONF_FILE=$1
IPS="$(ifconfig | grep "inet addr" | grep -v ":127" | grep "Bcast" | awk '{print $2}' | awk -F: '{print $2}')"
for IP in $IPS
do
DHCP_OPTION="$(grep "dhcp-option=" "$CONF_FILE" | grep "$IP" | head -n 1)"
if [ -z "$DHCP_OPTION" ]; then
continue
fi
SERVER_TAG="$(echo "$DHCP_OPTION" | awk -F= '{print $2}' | awk -F, '{print $1}')"
LOCAL_SERVER_IP="$IP"
grep "dhcp-option *= *$SERVER_TAG, *6 *, *$LOCAL_SERVER_IP" "$CONF_FILE" 1>/dev/null 2>&1
if [ $? -eq 0 ]; then
continue
fi
DHCP_OPTION="dhcp-option=$SERVER_TAG,6,$LOCAL_SERVER_IP"
echo "$DHCP_OPTION" >> "$CONF_FILE"
RESTART_DNSMASQ=1
done
return 1
}
clear_dhcp_options6()
{
CONF_FILE=$1
IPS="$(ifconfig | grep "inet addr" | grep -v ":127" | grep "Bcast" | awk '{print $2}' | awk -F: '{print $2}')"
for IP in $IPS
do
DHCP_OPTION="$(grep "dhcp-option=" "$CONF_FILE" | grep "$IP" | head -n 1)"
if [ -z "$DHCP_OPTION" ]; then
continue
fi
SERVER_TAG="$(echo "$DHCP_OPTION" | awk -F= '{print $2}' | awk -F, '{print $1}')"
LOCAL_SERVER_IP="$IP"
grep "dhcp-option *= *$SERVER_TAG, *6 *, *$LOCAL_SERVER_IP" "$CONF_FILE" 1>/dev/null 2>&1
if [ $? -ne 0 ]; then
continue
fi
sed -i "/^dhcp-option *=$SERVER_TAG,6,/d" "$CONF_FILE"
RESTART_DNSMASQ=1
done
return 1
}
set_dnsmasq_conf()
{
local LOCAL_SERVER_IP=""
local SERVER_TAG=""
local CONF_FILE=$1
local DHCP_OPTIONS=""
add_dhcp_options6 "$CONF_FILE"
grep "^port *=0" "$CONF_FILE" > /dev/null 2>&1
if [ $? -ne 0 ]; then
sed -i "/^port *=/d" "$CONF_FILE"
echo "port=0" >> "$CONF_FILE"
RESTART_DNSMASQ=1
fi
}
do_set_dnsmasq()
{
local RESTART_DNSMASQ=0
for conf in $DNSMASQ_CONF
do
if [ ! -e "$conf" ]; then
continue
fi
set_dnsmasq_conf "$conf"
done
if [ $RESTART_DNSMASQ -ne 0 ]; then
restart_dnsmasq
fi
}
kill_dnsmasq_delay_check_pid()
{
if [ ! -e "$SMARTDNS_CHECK_PID" ]; then
return
fi
PID="$(cat $SMARTDNS_CHECK_PID)"
if [ -d "/proc/$PID" ]; then
kill -9 $PID
fi
rm -f $SMARTDNS_CHECK_PID
}
dnsmasq_delay_check()
{
sleep 8
rm -f $SMARTDNS_CHECK_PID
get_dnsmasq_cmdline
if [ -z "$DNSMASQ_CMD" ] ; then
$SMARTDNS_INIT_SCRIPT restart
return
fi
do_set_dnsmasq
pid="$(cat $SMARTDNS_PID |head -n 1 2>/dev/null)"
if [ -z "$pid" ]; then
do_clear_dnsmasq
$SMARTDNS_INIT_SCRIPT start > /dev/null 2>&1 &
elif [ ! -d "/proc/$pid" ]; then
do_clear_dnsmasq
$SMARTDNS_INIT_SCRIPT start > /dev/null 2>&1 &
fi
exit 0
}
begin_dnsmasq_delay_check()
{
DNSMASQ_CMD=""
kill_dnsmasq_delay_check_pid
get_dnsmasq_cmdline
dnsmasq_delay_check > /dev/null 2>&1 &
PID=$!
echo $PID > $SMARTDNS_CHECK_PID
}
set_dnsmasq()
{
get_dnsmasq_cmdline
do_set_dnsmasq
begin_dnsmasq_delay_check
}
set_jffs_dnsmasq()
{
local RESTART_DNSMASQ=0
if [ "$(nvram get jffs2_scripts)" -ne 1 ]; then
nvram set jffs2_scripts="1"
nvram commit
fi
touch /jffs/configs/dnsmasq.conf.add
if [ -e "/jffs/configs/dnsmasq.conf.add" ]; then
set_dnsmasq_conf "/jffs/configs/dnsmasq.conf.add"
fi
if [ -e "/jffs/configs/dnsmasq.conf" ]; then
set_dnsmasq_conf "/jffs/configs/dnsmasq.conf"
fi
if [ $RESTART_DNSMASQ -ne 0 ]; then
restart_dnsmasq
fi
}
clear_dnsmasq_conf()
{
local LOCAL_SERVER_IP=""
local SERVER_TAG=""
local CONF_FILE=$1
clear_dhcp_options6 "$CONF_FILE"
grep "^port *=" "$CONF_FILE" > /dev/null 2>&1
if [ $? -eq 0 ]; then
sed -i "/^port *=/d" "$CONF_FILE"
RESTART_DNSMASQ=1
fi
}
do_clear_dnsmasq()
{
local RESTART_DNSMASQ=0
for conf in $DNSMASQ_CONF
do
if [ ! -e "$conf" ]; then
continue
fi
clear_dnsmasq_conf "$conf"
done
if [ $RESTART_DNSMASQ -ne 0 ]; then
if [ $? -eq 0 ]; then
return
fi
restart_dnsmasq
fi
}
clear_dnsmasq()
{
kill_dnsmasq_delay_check_pid
do_clear_dnsmasq
}
clear_jffs_dnsmasq()
{
local RESTART_DNSMASQ=0
if [ -e "/jffs/configs/dnsmasq.conf.add" ]; then
clear_dnsmasq_conf "/jffs/configs/dnsmasq.conf.add"
fi
if [ -e "/jffs/configs/dnsmasq.conf" ]; then
clear_dnsmasq_conf "/jffs/configs/dnsmasq.conf"
fi
if [ $RESTART_DNSMASQ -ne 0 ]; then
restart_dnsmasq
fi
}
set_smartdns_port()
{
if [ "$SMARTDNS_WORKMODE" = "0" ]; then
return 0
elif [ "$SMARTDNS_WORKMODE" = "1" ]; then
sed -i "s/^\(bind .*\):53\(@[^ ]*\)\?\( .*\)\?$/\1:$SMARTDNS_PORT\2 \3/g" $SMARTDNS_CONF
sed -i "s/^\(bind-tcp .*\):53\(@[^ ]*\)\?\( .*\)\?$/\1:$SMARTDNS_PORT\2 \3/g" $SMARTDNS_CONF
elif [ "$SMARTDNS_WORKMODE" = "2" ]; then
sed -i "s/^\(bind .*\):$SMARTDNS_PORT\(@[^ ]*\)\?\( .*\)\?$/\1:53\2 \3/g" $SMARTDNS_CONF
sed -i "s/^\(bind-tcp .*\):$SMARTDNS_PORT\(@[^ ]*\)\?\( .*\)\?$/\1:53\2 \3/g" $SMARTDNS_CONF
elif [ "$SMARTDNS_WORKMODE" = "3" ]; then
return 0
else
return 1
fi
return 0
}
set_rule()
{
if [ "$SMARTDNS_WORKMODE" = "0" ]; then
return 0
elif [ "$SMARTDNS_WORKMODE" = "1" ]; then
set_iptable
return $?
elif [ "$SMARTDNS_WORKMODE" = "2" ]; then
set_dnsmasq
return $?
elif [ "$SMARTDNS_WORKMODE" = "3" ]; then
set_jffs_dnsmasq
return $?
else
return 1
fi
}
clear_rule()
{
if [ "$SMARTDNS_WORKMODE" = "0" ]; then
return 0
elif [ "$SMARTDNS_WORKMODE" = "1" ]; then
clear_iptable
return $?
elif [ "$SMARTDNS_WORKMODE" = "2" ]; then
clear_dnsmasq
return $?
elif [ "$SMARTDNS_WORKMODE" = "3" ]; then
clear_jffs_dnsmasq
return $?
else
return 1
fi
}
get_tz()
{
if [ -e "/etc/localtime" ]; then
return
fi
for tzfile in /etc/TZ /var/etc/TZ
do
if [ ! -e "$tzfile" ]; then
continue
fi
tz="$(cat $tzfile 2>/dev/null)"
done
if [ -z "$tz" ]; then
return
fi
export TZ=$tz
}
case "$1" in
start)
set_rule
if [ $? -ne 0 ]; then
exit 1
fi
SMARTDNS_OPTION=""
[ "$SMARTDNS_CRASH_RESTART" = "1" ] && SMARTDNS_OPTION="$SMARTDNS_OPTION -R"
set_smartdns_port
get_tz
$SMARTDNS_BIN -c "$SMARTDNS_CONF" -p $SMARTDNS_PID $SMARTDNS_OPTION
if [ $? -ne 0 ]; then
clear_rule
exit 1
fi
;;
status)
pid="$(cat $SMARTDNS_PID |head -n 1 2>/dev/null)"
if [ -z "$pid" ]; then
echo "smartdns not running."
exit 0
fi
if [ -d "/proc/$pid" ]; then
echo "smartdns is running"
exit 0
fi
echo "smartdns not running."
exit 0
;;
stop)
pid="$(cat "$SMARTDNS_PID" | head -n 1 2>/dev/null)"
if [ -z "$pid" ]; then
echo "smartdns not running."
exit 0
fi
kill -15 "$pid" 2>/dev/null
SLEEP=$(which usleep 2>/dev/null)
SLEEPTIME=200000
if [ -z "$SLEEP" ]; then
SLEEP="sleep"
SLEEPTIME=0.2
fi
N=300
while [ $N -gt 0 ]
do
pid="$(cat "$SMARTDNS_PID" | head -n 1 2>/dev/null)"
if [ -z "$pid" ]; then
break
fi
if [ ! -d "/proc/$pid" ]; then
break
fi
stat="$(cat /proc/${pid}/stat 2>/dev/null | awk '{print $3}' 2>/dev/null)"
if [ "$stat" = "Z" ]; then
$SLEEP $SLEEPTIME
break
fi
$SLEEP $SLEEPTIME 2>/dev/null
N=$((N-1))
done
kill -9 "$pid" 2>/dev/null
clear_rule
exit 0
;;
restart)
$0 stop
$0 start
;;
reload)
;;
enable)
nvram set apps_state_enable=2
nvram set apps_state_error=0
nvram set apps_state_install=5
nvram set apps_state_action=install
nvram set apps_u2ec_ex=2
;;
firewall-start|reload|force-reload|reconfigure)
$0 restart
;;
*)
;;
esac
================================================
FILE: package/optware/control/conffiles
================================================
/opt/etc/smartdns/smartdns.conf
/opt/etc/smartdns/smartdns-opt.conf
================================================
FILE: package/optware/control/control
================================================
Package: smartdns
Architecture: mipsbig
Priority: optional
Section: net
Version: 2018.7.6-1921
Maintainer: pymumu
Source: http://127.0.0.1/
Description: A smart dns server
Suggests:
Conflicts:
Enabled: yes
================================================
FILE: package/optware/control/postinst
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
chmod +x /opt/usr/sbin/smartdns
chmod +x /opt/etc/init.d/S50smartdns
================================================
FILE: package/optware/control/prerm
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
/opt/etc/init.d/S50smartdns stop
================================================
FILE: package/optware/debian-binary
================================================
2.0
================================================
FILE: package/optware/make.sh
================================================
#!/bin/sh
#
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
CURR_DIR=$(cd $(dirname $0);pwd)
VER="`date +"1.%Y.%m.%d-%H%M"`"
SMARTDNS_DIR=$CURR_DIR/../../
SMARTDNS_CP=$SMARTDNS_DIR/package/copy-smartdns.sh
SMARTDNS_BIN=$SMARTDNS_DIR/src/smartdns
SMARTDNS_CONF=$SMARTDNS_DIR/etc/smartdns/smartdns.conf
SMARTDNS_OPT=$CURR_DIR/smartdns-opt.conf
IS_BUILD_SMARTDNS_UI=0
showhelp()
{
echo "Usage: make [OPTION]"
echo "Options:"
echo " -o output directory."
echo " --arch archtecture."
echo " --ver version."
echo " --with-ui build with smartdns-ui plugin."
echo " -h show this message."
}
build()
{
ROOT=/tmp/smartdns-optware
rm -fr $ROOT
mkdir -p $ROOT
cp $CURR_DIR/* $ROOT/ -af
cd $ROOT/
mkdir $ROOT/opt/usr/sbin -p
mkdir $ROOT/opt/etc/init.d -p
mkdir $ROOT/opt/etc/smartdns/ -p
cp $SMARTDNS_CONF $ROOT/opt/etc/smartdns/
cp $SMARTDNS_OPT $ROOT/opt/etc/smartdns/
cp $CURR_DIR/S50smartdns $ROOT/opt/etc/init.d/
$SMARTDNS_CP $ROOT /opt
if [ $? -ne 0 ]; then
echo "copy smartdns file failed."
rm -fr $PKG_ROOT
return 1
fi
if [ $IS_BUILD_SMARTDNS_UI -ne 0 ]; then
mkdir $ROOT/opt/usr/lib/smartdns -p
cp $SMARTDNS_DIR/plugin/smartdns-ui/target/smartdns_ui.so $ROOT/opt/usr/lib/smartdns/
if [ $? -ne 0 ]; then
echo "copy smartdns_ui.so file failed."
rm -fr $ROOT/
return 1
fi
mkdir $ROOT/opt/usr/share/smartdns/wwwroot -p
cp $WORKDIR/smartdns-webui/out/* $ROOT/opt/usr/share/smartdns/wwwroot/ -a
if [ $? -ne 0 ]; then
echo "Failed to copy smartdns-ui web files."
rm -fr $ROOT/
return 1
fi
fi
sed -i "s/# *server-name smartdns/server-name smartdns/g" $ROOT/opt/etc/smartdns/smartdns.conf
sed -i "s/^Architecture.*/Architecture: $ARCH/g" $ROOT/control/control
sed -i "s/Version:.*/Version: $VER/" $ROOT/control/control
cd $ROOT/control
chmod +x *
tar zcf ../control.tar.gz --owner=0 --group=0 ./
cd $ROOT
tar zcf data.tar.gz --owner=0 --group=0 opt
tar zcf $OUTPUTDIR/smartdns.$VER.$FILEARCH.ipk --owner=0 --group=0 control.tar.gz data.tar.gz debian-binary
rm -fr $ROOT/
}
main()
{
OPTS=`getopt -o o:h --long arch:,ver:,with-ui,filearch: \
-n "" -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$OPTS"
while true; do
case "$1" in
--arch)
ARCH="$2"
shift 2;;
--filearch)
FILEARCH="$2"
shift 2;;
--with-ui)
IS_BUILD_SMARTDNS_UI=1
shift ;;
--ver)
VER="$2"
shift 2;;
-o )
OUTPUTDIR="$2"
shift 2;;
-h | --help )
showhelp
return 0
shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if [ -z "$ARCH" ]; then
echo "please input arch."
return 1;
fi
if [ -z "$FILEARCH" ]; then
FILEARCH=$ARCH
fi
if [ -z "$OUTPUTDIR" ]; then
OUTPUTDIR=$CURR_DIR;
fi
build
}
main $@
exit $?
================================================
FILE: package/optware/smartdns-opt.conf
================================================
# workmode
# 0: run as port only
# 1: redirect port
# 2: replace
SMARTDNS_WORKMODE="1"
# smartdns port
SMARTDNS_PORT="535"
# restart when crash
SMARTDNS_CRASH_RESTART="1"
================================================
FILE: package/redhat/smartdns.spec
================================================
Name: smartdns
Version: 1.2020.09.08
Release: 2235%{?dist}
Summary: smartdns
License: GPL 3.0
URL: https://github.com/pymumu/smartdns
Source0: %{name}-%{version}.tar.gz
BuildRequires: glibc
BuildRequires: centos-release >= 7
BuildRequires: openssl-devel
Requires: glibc
Requires: openssl
Requires: systemd
%description
A local DNS server to obtain the fastest website IP for the best Internet experience.
%prep
%setup -q
%build
cd src
make %{?_smp_mflags}
%install
rm -rf $RPM_BUILD_ROOT
%{__install} -D -m 755 src/smartdns $RPM_BUILD_ROOT%{_sbindir}/smartdns
%{__install} -D -m 644 etc/smartdns/smartdns.conf $RPM_BUILD_ROOT%{_sysconfdir}/smartdns/smartdns.conf
%{__install} -D -m 644 systemd/smartdns.service.in $RPM_BUILD_ROOT%{_unitdir}/smartdns.service
cat > $RPM_BUILD_ROOT%{_unitdir}/smartdns.service </dev/null)
if [ "$cp15_barrier" != "2" ]; then
sh -c "echo "2" > /proc/sys/abi/cp15_barrier" 2>/dev/null;
if [ "$?" != "0" ]; then
echo "Failed to set cp15_barrier to 2, please run 'echo 2 > /proc/sys/abi/cp15_barrier' manually"
exit 1
else
echo "Set cp15_barrier to 2"
fi
fi
fi
if [ ! -f ${INTERPRETER} ]; then
echo "smartdns dynamic loader not found: ${INTERPRETER}"
exit 1
fi
cd $CURDIR
SMARTDNS_WORKDIR="$CWD" exec "${SMARTDNS_BIN}" $@
================================================
FILE: package/tool/po2lmo/Makefile
================================================
INSTALL = install
PREFIX = /usr/bin
CFLAGS = -Wall -O2
po2lmo: src/po2lmo.o src/template_lmo.o
$(CC) $(LDFLAGS) -o src/po2lmo src/po2lmo.o src/template_lmo.o
install:
$(INSTALL) -m 755 src/po2lmo $(PREFIX)
clean:
$(RM) src/po2lmo src/*.o
================================================
FILE: package/tool/po2lmo/src/po2lmo.c
================================================
/*
* lmo - Lua Machine Objects - PO to LMO conversion tool
*
* Copyright (C) 2009-2012 Jo-Philipp Wich
*
* 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.
*/
#include "template_lmo.h"
static void die(const char *msg)
{
fprintf(stderr, "Error: %s\n", msg);
exit(1);
}
static void usage(const char *name)
{
fprintf(stderr, "Usage: %s input.po output.lmo\n", name);
exit(1);
}
static void print(const void *ptr, size_t size, size_t nmemb, FILE *stream)
{
if( fwrite(ptr, size, nmemb, stream) == 0 )
die("Failed to write stdout");
}
static int extract_string(const char *src, char *dest, int len)
{
int pos = 0;
int esc = 0;
int off = -1;
for( pos = 0; (pos < strlen(src)) && (pos < len); pos++ )
{
if( (off == -1) && (src[pos] == '"') )
{
off = pos + 1;
}
else if( off >= 0 )
{
if( esc == 1 )
{
switch (src[pos])
{
case '"':
case '\\':
off++;
break;
}
dest[pos-off] = src[pos];
esc = 0;
}
else if( src[pos] == '\\' )
{
dest[pos-off] = src[pos];
esc = 1;
}
else if( src[pos] != '"' )
{
dest[pos-off] = src[pos];
}
else
{
dest[pos-off] = '\0';
break;
}
}
}
return (off > -1) ? strlen(dest) : -1;
}
static int cmp_index(const void *a, const void *b)
{
uint32_t x = ((const lmo_entry_t *)a)->key_id;
uint32_t y = ((const lmo_entry_t *)b)->key_id;
if (x < y)
return -1;
else if (x > y)
return 1;
return 0;
}
static void print_uint32(uint32_t x, FILE *out)
{
uint32_t y = htonl(x);
print(&y, sizeof(uint32_t), 1, out);
}
static void print_index(void *array, int n, FILE *out)
{
lmo_entry_t *e;
qsort(array, n, sizeof(*e), cmp_index);
for (e = array; n > 0; n--, e++)
{
print_uint32(e->key_id, out);
print_uint32(e->val_id, out);
print_uint32(e->offset, out);
print_uint32(e->length, out);
}
}
int main(int argc, char *argv[])
{
char line[4096];
char key[4096];
char val[4096];
char tmp[4096];
int state = 0;
int offset = 0;
int length = 0;
int n_entries = 0;
void *array = NULL;
lmo_entry_t *entry = NULL;
uint32_t key_id, val_id;
FILE *in;
FILE *out;
if( (argc != 3) || ((in = fopen(argv[1], "r")) == NULL) || ((out = fopen(argv[2], "w")) == NULL) )
usage(argv[0]);
memset(line, 0, sizeof(key));
memset(key, 0, sizeof(val));
memset(val, 0, sizeof(val));
while( (NULL != fgets(line, sizeof(line), in)) || (state >= 2 && feof(in)) )
{
if( state == 0 && strstr(line, "msgid \"") == line )
{
switch(extract_string(line, key, sizeof(key)))
{
case -1:
die("Syntax error in msgid");
case 0:
state = 1;
break;
default:
state = 2;
}
}
else if( state == 1 || state == 2 )
{
if( strstr(line, "msgstr \"") == line || state == 2 )
{
switch(extract_string(line, val, sizeof(val)))
{
case -1:
state = 4;
break;
default:
state = 3;
}
}
else
{
switch(extract_string(line, tmp, sizeof(tmp)))
{
case -1:
state = 2;
break;
default:
strcat(key, tmp);
}
}
}
else if( state == 3 )
{
switch(extract_string(line, tmp, sizeof(tmp)))
{
case -1:
state = 4;
break;
default:
strcat(val, tmp);
}
}
if( state == 4 )
{
if( strlen(key) > 0 && strlen(val) > 0 )
{
key_id = sfh_hash(key, strlen(key));
val_id = sfh_hash(val, strlen(val));
if( key_id != val_id )
{
n_entries++;
array = realloc(array, n_entries * sizeof(lmo_entry_t));
entry = (lmo_entry_t *)array + n_entries - 1;
if (!array)
die("Out of memory");
entry->key_id = key_id;
entry->val_id = val_id;
entry->offset = offset;
entry->length = strlen(val);
length = strlen(val) + ((4 - (strlen(val) % 4)) % 4);
print(val, length, 1, out);
offset += length;
}
}
state = 0;
memset(key, 0, sizeof(key));
memset(val, 0, sizeof(val));
}
memset(line, 0, sizeof(line));
}
print_index(array, n_entries, out);
if( offset > 0 )
{
print_uint32(offset, out);
fsync(fileno(out));
fclose(out);
}
else
{
fclose(out);
unlink(argv[2]);
}
fclose(in);
return(0);
}
================================================
FILE: package/tool/po2lmo/src/template_lmo.c
================================================
/*
* lmo - Lua Machine Objects - Base functions
*
* Copyright (C) 2009-2010 Jo-Philipp Wich
*
* 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.
*/
#include "template_lmo.h"
/*
* Hash function from http://www.azillionmonkeys.com/qed/hash.html
* Copyright (C) 2004-2008 by Paul Hsieh
*/
uint32_t sfh_hash(const char *data, int len)
{
uint32_t hash = len, tmp;
int rem;
if (len <= 0 || data == NULL) return 0;
rem = len & 3;
len >>= 2;
/* Main loop */
for (;len > 0; len--) {
hash += sfh_get16(data);
tmp = (sfh_get16(data+2) << 11) ^ hash;
hash = (hash << 16) ^ tmp;
data += 2*sizeof(uint16_t);
hash += hash >> 11;
}
/* Handle end cases */
switch (rem) {
case 3: hash += sfh_get16(data);
hash ^= hash << 16;
hash ^= data[sizeof(uint16_t)] << 18;
hash += hash >> 11;
break;
case 2: hash += sfh_get16(data);
hash ^= hash << 11;
hash += hash >> 17;
break;
case 1: hash += *data;
hash ^= hash << 10;
hash += hash >> 1;
}
/* Force "avalanching" of final 127 bits */
hash ^= hash << 3;
hash += hash >> 5;
hash ^= hash << 4;
hash += hash >> 17;
hash ^= hash << 25;
hash += hash >> 6;
return hash;
}
uint32_t lmo_canon_hash(const char *str, int len)
{
char res[4096];
char *ptr, prev;
int off;
if (!str || len >= sizeof(res))
return 0;
for (prev = ' ', ptr = res, off = 0; off < len; prev = *str, off++, str++)
{
if (isspace(*str))
{
if (!isspace(prev))
*ptr++ = ' ';
}
else
{
*ptr++ = *str;
}
}
if ((ptr > res) && isspace(*(ptr-1)))
ptr--;
return sfh_hash(res, ptr - res);
}
lmo_archive_t * lmo_open(const char *file)
{
int in = -1;
uint32_t idx_offset = 0;
struct stat s;
lmo_archive_t *ar = NULL;
if (stat(file, &s) == -1)
goto err;
if ((in = open(file, O_RDONLY)) == -1)
goto err;
if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL)
{
memset(ar, 0, sizeof(*ar));
ar->fd = in;
ar->size = s.st_size;
fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);
if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED)
goto err;
idx_offset = ntohl(*((const uint32_t *)
(ar->mmap + ar->size - sizeof(uint32_t))));
if (idx_offset >= ar->size)
goto err;
ar->index = (lmo_entry_t *)(ar->mmap + idx_offset);
ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t);
ar->end = ar->mmap + ar->size;
return ar;
}
err:
if (in > -1)
close(in);
if (ar != NULL)
{
if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
munmap(ar->mmap, ar->size);
free(ar);
}
return NULL;
}
void lmo_close(lmo_archive_t *ar)
{
if (ar != NULL)
{
if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
munmap(ar->mmap, ar->size);
close(ar->fd);
free(ar);
ar = NULL;
}
}
lmo_catalog_t *_lmo_catalogs = NULL;
lmo_catalog_t *_lmo_active_catalog = NULL;
int lmo_load_catalog(const char *lang, const char *dir)
{
DIR *dh = NULL;
char pattern[16];
char path[PATH_MAX];
struct dirent *de = NULL;
lmo_archive_t *ar = NULL;
lmo_catalog_t *cat = NULL;
if (!lmo_change_catalog(lang))
return 0;
if (!dir || !(dh = opendir(dir)))
goto err;
if (!(cat = malloc(sizeof(*cat))))
goto err;
memset(cat, 0, sizeof(*cat));
snprintf(cat->lang, sizeof(cat->lang), "%s", lang);
snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang);
while ((de = readdir(dh)) != NULL)
{
if (!fnmatch(pattern, de->d_name, 0))
{
snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
ar = lmo_open(path);
if (ar)
{
ar->next = cat->archives;
cat->archives = ar;
}
}
}
closedir(dh);
cat->next = _lmo_catalogs;
_lmo_catalogs = cat;
if (!_lmo_active_catalog)
_lmo_active_catalog = cat;
return 0;
err:
if (dh) closedir(dh);
if (cat) free(cat);
return -1;
}
int lmo_change_catalog(const char *lang)
{
lmo_catalog_t *cat;
for (cat = _lmo_catalogs; cat; cat = cat->next)
{
if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
{
_lmo_active_catalog = cat;
return 0;
}
}
return -1;
}
static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash)
{
unsigned int m, l, r;
uint32_t k;
l = 0;
r = ar->length - 1;
while (1)
{
m = l + ((r - l) / 2);
if (r < l)
break;
k = ntohl(ar->index[m].key_id);
if (k == hash)
return &ar->index[m];
if (k > hash)
{
if (!m)
break;
r = m - 1;
}
else
{
l = m + 1;
}
}
return NULL;
}
int lmo_translate(const char *key, int keylen, char **out, int *outlen)
{
uint32_t hash;
lmo_entry_t *e;
lmo_archive_t *ar;
if (!key || !_lmo_active_catalog)
return -2;
hash = lmo_canon_hash(key, keylen);
for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
{
if ((e = lmo_find_entry(ar, hash)) != NULL)
{
*out = ar->mmap + ntohl(e->offset);
*outlen = ntohl(e->length);
return 0;
}
}
return -1;
}
void lmo_close_catalog(const char *lang)
{
lmo_archive_t *ar, *next;
lmo_catalog_t *cat, *prev;
for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next)
{
if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
{
if (prev)
prev->next = cat->next;
else
_lmo_catalogs = cat->next;
for (ar = cat->archives; ar; ar = next)
{
next = ar->next;
lmo_close(ar);
}
free(cat);
break;
}
}
}
================================================
FILE: package/tool/po2lmo/src/template_lmo.h
================================================
/*
* lmo - Lua Machine Objects - General header
*
* Copyright (C) 2009-2012 Jo-Philipp Wich
*
* 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.
*/
#ifndef _TEMPLATE_LMO_H_
#define _TEMPLATE_LMO_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if (defined(__GNUC__) && defined(__i386__))
#define sfh_get16(d) (*((const uint16_t *) (d)))
#else
#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
+(uint32_t)(((const uint8_t *)(d))[0]) )
#endif
struct lmo_entry {
uint32_t key_id;
uint32_t val_id;
uint32_t offset;
uint32_t length;
} __attribute__((packed));
typedef struct lmo_entry lmo_entry_t;
struct lmo_archive {
int fd;
int length;
uint32_t size;
lmo_entry_t *index;
char *mmap;
char *end;
struct lmo_archive *next;
};
typedef struct lmo_archive lmo_archive_t;
struct lmo_catalog {
char lang[6];
struct lmo_archive *archives;
struct lmo_catalog *next;
};
typedef struct lmo_catalog lmo_catalog_t;
uint32_t sfh_hash(const char *data, int len);
uint32_t lmo_canon_hash(const char *data, int len);
lmo_archive_t * lmo_open(const char *file);
void lmo_close(lmo_archive_t *ar);
extern lmo_catalog_t *_lmo_catalogs;
extern lmo_catalog_t *_lmo_active_catalog;
int lmo_load_catalog(const char *lang, const char *dir);
int lmo_change_catalog(const char *lang);
int lmo_translate(const char *key, int keylen, char **out, int *outlen);
void lmo_close_catalog(const char *lang);
#endif
================================================
FILE: package/windows/install.bat
================================================
@echo off
set "CURR_PATH=%~dp0"
set "STARTUP_PATH=%userprofile%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
FOR /F %%i IN ('wsl pwd') DO @set DIR_IN_WSL=%%i
wsl sudo %DIR_IN_WSL%/../../install -i
IF NOT %ERRORLEVEL% == 0 (
echo Install smartdns failed.
pause
exit 1
)
copy %CURR_PATH%\wsl-run.vbs "%STARTUP_PATH%/"
IF NOT %ERRORLEVEL% == 0 (
echo Install startup script failed.
pause
exit 1
)
echo Install smartdns success
pause
================================================
FILE: package/windows/reload.bat
================================================
@echo off
set "CURR_PATH=%~dp0"
set "STARTUP_PATH=%userprofile%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
FOR /F %%i IN ('wsl pwd') DO @set DIR_IN_WSL=%%i
wsl sudo cp -avf %DIR_IN_WSL%/../../etc/smartdns/* /etc/smartdns/
IF NOT %ERRORLEVEL% == 0 (
echo copy smartdns configuration file failed.
pause
exit 1
)
wsl sudo /etc/init.d/smartdns restart
IF NOT %ERRORLEVEL% == 0 (
echo reload smartdns failed.
pause
exit 1
)
echo reload smartdns success
pause
================================================
FILE: package/windows/uninstall.bat
================================================
@echo off
set "CURR_PATH=%~dp0"
set "STARTUP_PATH=%userprofile%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"
FOR /F %%i IN ('wsl pwd') DO @set DIR_IN_WSL=%%i
wsl sudo %DIR_IN_WSL%/../../install -u
IF NOT %ERRORLEVEL% == 0 (
echo Uninstall smartdns failed.
pause
exit 1
)
del "%STARTUP_PATH%\wsl-run.vbs"
IF NOT %ERRORLEVEL% == 0 (
echo Uninstall startup script failed.
pause
exit 1
)
echo uninstall success
pause
================================================
FILE: package/windows/wsl-run.vbs
================================================
Set ws = WScript.CreateObject("WScript.Shell")
ws.run "wsl sudo /etc/init.d/smartdns restart", vbhide
================================================
FILE: plugin/demo/.gitignore
================================================
.vscode
*.o
*.so
*.swp.
================================================
FILE: plugin/demo/Makefile
================================================
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
BIN=smartdns_demo.so
OBJS_MAIN=$(patsubst %.c,%.o,$(wildcard *.c))
OBJS=$(OBJS_MAIN)
# cflags
ifndef CFLAGS
ifdef DEBUG
CFLAGS = -g -DDEBUG
else
CFLAGS = -O2
endif
CFLAGS +=-fPIC -Wall -Wstrict-prototypes -fno-omit-frame-pointer -Wstrict-aliasing -funwind-tables -Wmissing-prototypes -Wshadow -Wextra -Wno-unused-parameter -Wno-implicit-fallthrough
endif
override CFLAGS +=-Iinclude -I../../src/include -I../../src
override CFLAGS += -DBASE_FILE_NAME='"$(notdir $<)"'
override CFLAGS += $(EXTRA_CFLAGS)
override LDFLAGS += -lpthread -shared
.PHONY: all clean
all: $(BIN)
$(BIN) : $(OBJS)
$(CC) $(OBJS) -o $@ $(LDFLAGS)
clean:
$(RM) $(OBJS) $(BIN)
================================================
FILE: plugin/demo/demo.c
================================================
#include "demo.h"
#include "smartdns/dns_server.h"
#include "smartdns/tlog.h"
#include "smartdns/util.h"
#include
#include
#include
#include
static int demo_server_recv(struct dns_packet *packet, unsigned char *inpacket, int inpacket_len,
struct sockaddr_storage *local, socklen_t local_len, struct sockaddr_storage *from,
socklen_t from_len)
{
char hostname[256] = {0};
tlog(TLOG_INFO, "recv packet from %s", get_host_by_addr(hostname, sizeof(hostname), (struct sockaddr *)from));
return 0;
}
static void demo_server_request_complete(struct dns_request *request)
{
tlog(TLOG_INFO, "server complete request, request domain is %s", dns_server_request_get_domain(request));
}
struct smartdns_operations demo_ops = {
.server_recv = demo_server_recv,
.server_query_complete = demo_server_request_complete,
};
int dns_plugin_init(struct dns_plugin *plugin)
{
char options[4096] = {0};
int argc = dns_plugin_get_argc(plugin);
const char **argv = dns_plugin_get_argv(plugin);
for (int i = 0; i < argc; i++) {
snprintf(options + strlen(options), sizeof(options) - strlen(options), "%s ", argv[i]);
}
tlog(TLOG_INFO, "demo plugin init, options: %s", options);
smartdns_operations_register(&demo_ops);
return 0;
}
int dns_plugin_exit(struct dns_plugin *plugin)
{
tlog(TLOG_INFO, "demo plugin exit.");
smartdns_operations_unregister(&demo_ops);
return 0;
}
int dns_plugin_api_version(void)
{
return SMARTDNS_PLUGIN_API_VERSION;
}
================================================
FILE: plugin/demo/demo.h
================================================
#ifndef SMART_DNS_PLUGIN_DEMO_H
#define SMART_DNS_PLUGIN_DEMO_H
#include "smartdns/dns_plugin.h"
#ifdef __cplusplus
extern "C" {
#endif /*__cplusplus */
#ifdef __cplusplus
}
#endif /*__cplusplus */
#endif
================================================
FILE: plugin/smartdns-ui/.gitignore
================================================
/target
Cargo.lock
================================================
FILE: plugin/smartdns-ui/Cargo.toml
================================================
[package]
name = "smartdns-ui"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
[dependencies]
ctor = "0.4.3"
bytes = "1.11.1"
rusqlite = { version = "0.37.0", features = ["bundled"] }
hyper = { version = "1.8.1", features = ["full"] }
hyper-util = { version = "0.1.20", features = ["full"] }
hyper-tungstenite = "0.18.0"
tokio = { version = "1.50.0", features = ["full"] }
serde = { version = "1.0.228", features = ["derive"] }
tokio-rustls = { version = "0.26.4", default-features = false, features = ["ring", "tls12"], optional = true }
rustls = { version = "0.23.37", default-features = false, features = ["ring", "tls12"] }
rustls-pemfile = { version = "2.2.0", optional = true}
serde_json = "1.0.149"
http-body-util = "0.1.3"
getopts = "0.2.24"
url = "2.5.8"
jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] }
matchit = "0.8.6"
futures = "0.3.32"
socket2 = "0.6.3"
cfg-if = "1.0.4"
urlencoding = "2.1.3"
chrono = "0.4.44"
nix = "0.30.1"
tokio-fd = "0.3.0"
pbkdf2 = { version = "0.12.2", features = ["simple"] }
rand_core = { version = "0.6", features = ["std"] }
[features]
build-release = []
https = ["tokio-rustls", "rustls-pemfile"]
default = ["https"]
[dev-dependencies]
reqwest = {version = "0.12.28", features = ["blocking"]}
tungstenite = "0.23.0"
tokio-tungstenite = "0.23.1"
tempfile = "3.27.0"
[build-dependencies]
bindgen = "0.69.5"
[profile.release-optmize-size]
inherits = "release"
lto = true
opt-level = "s"
strip = true
codegen-units = 1
panic = "abort"
================================================
FILE: plugin/smartdns-ui/Makefile
================================================
# Copyright (C) 2018-2025 Ruilin Peng (Nick) .
#
# 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 .
BIN=smartdns-ui
PREFIX := /usr
SBINDIR := $(PREFIX)/sbin
SLIBDIR := $(PREFIX)/lib
DESTDIR :=
CARGO_BUILD_ENV :=
SMARTDNS_SRC_DIR=../../src
ifeq ($(origin CC), environment)
ifeq ($(CARGO_BUILD_TARGET),)
ifeq ($(CARGO_BUILD_ARGS),)
# find default target by compiler
ARCH_TARGET:=$(shell $(CC) -dumpmachine)
override CARGO_BUILD_TARGET:=$(shell rustc --print target-list | grep $(ARCH_TARGET) | head -n 1)
ifeq ($(CARGO_BUILD_TARGET),)
# not found, try to find target by compiler
ARCH_TARGET:=$(shell $(CC) -dumpmachine | sed 's/-[^-]*-linux-/-linux-/')
ARCH=$(shell echo $(ARCH_TARGET) | cut -d - -f 1)
ABI=$(shell echo $(ARCH_TARGET) | cut -d - -f 3)
ifneq ($(ABI),)
# force set target to $(ARCH)-unknown-linux-$(ABI)
override CARGO_BUILD_TARGET:=$(shell rustc --print target-list | grep $(ARCH) | grep linux | grep $(ABI) | grep unknown | head -n 1)
endif
endif
ifneq ($(CARGO_BUILD_TARGET),)
ARCH_TARGET_PATH=$(CARGO_BUILD_TARGET)/
override CARGO_RUSTFLAGS=-C linker=$(CC)
CARGO_BUILD_ARGS +=--target=$(CARGO_BUILD_TARGET)
endif
endif
endif
IS_NATIVE_BUILD=$(shell [ "$(CC)" = "cc" ] || [ "$(CC)" = "gcc" ] && echo 1 || echo 0)
ifeq ($(IS_NATIVE_BUILD), 0)
# find sysroot
SYSROOT:=$(shell $(CC) -print-sysroot)
ifeq ($(SYSROOT),)
# if sysroot is not set, try find sysroot from compiler default include path
SYSROOT:=$(shell $(CC) -xc /dev/null -E -Wp,-v 2>&1 | sed -n 's,^ ,,p' | head -n 1)
ifneq ($(SYSROOT),)
# find sysroot, add sysroot to BINDGEN_EXTRA_CLANG_ARGS
SYSROOT:=$(SYSROOT)/..
override CARGO_BUILD_ENV += BINDGEN_EXTRA_CLANG_ARGS="--sysroot=$(SYSROOT)"
CARGO_BUILD_ARGS +=--target=$(CARGO_BUILD_TARGET)
endif
endif
endif
endif
ifneq ($(CARGO_BUILD_TARGET),)
ARCH_TARGET_PATH=$(CARGO_BUILD_TARGET)/
endif
# check and install bindgen
IS_BINDGEN_INSTALL=$(shell which bindgen 2>/dev/null)
ifeq ($(IS_BINDGEN_INSTALL),)
$(shell cargo install --force --locked bindgen-cli)
endif
ifneq ($(CARGO_BUILD_TARGET),)
CARGO_BUILD_TARGET_INSTALL=$(shell rustup target list | grep $(CARGO_BUILD_TARGET) | grep installed)
ifeq ($(CARGO_BUILD_TARGET_INSTALL),)
# install target
$(shell rustup target add $(CARGO_BUILD_TARGET))
endif
endif
IS_MUSL=$(shell $(CC) -v 2>&1 | grep -i musl >/dev/null 2>&1 && echo 1 || echo 0)
ifeq ($(IS_MUSL), 1)
override CARGO_RUSTFLAGS +=-C target-feature=-crt-static
endif
ifdef DEBUG
CARGO_BUILD_PATH=target/$(ARCH_TARGET_PATH)debug
SMARTDNS_BUILD_TYPE=DEBUG=1
else
ifdef OPTIMIZE_SIZE
CARGO_BUILD_ARGS += --profile release-optmize-size
CARGO_BUILD_PATH=target/$(ARCH_TARGET_PATH)release-optmize-size
else
CARGO_BUILD_ARGS +=--release
CARGO_BUILD_PATH=target/$(ARCH_TARGET_PATH)release
endif
SMARTDNS_BUILD_TYPE=
endif
.PHONY: all clean install $(BIN)
all: $(BIN)
test-prepare:
$(MAKE) -C $(SMARTDNS_SRC_DIR) libsmartdns-test.a
$(BIN):
CXXFLAGS= CFLAGS= MAKEFLAGS= RUSTFLAGS="$(CARGO_RUSTFLAGS) $(RUSTFLAGS)" $(CARGO_BUILD_ENV) cargo build $(CARGO_BUILD_ARGS) --features "build-release"
cp $(CARGO_BUILD_PATH)/libsmartdns_ui.so target/smartdns_ui.so
install: $(BIN)
install -v -m 0644 -D -t $(DESTDIR)$(SLIBDIR)/smartdns target/smartdns_ui.so
test: test-prepare
MAKEFLAGS= cargo test
clean:
cargo clean
$(MAKE) -C $(SMARTDNS_SRC_DIR) clean
rm -rf target/smartdns_ui.so
================================================
FILE: plugin/smartdns-ui/build.rs
================================================
use std::collections::HashSet;
use std::env;
use std::path::PathBuf;
#[derive(Debug)]
struct IgnoreMacros(HashSet);
impl bindgen::callbacks::ParseCallbacks for IgnoreMacros {
fn will_parse_macro(&self, name: &str) -> bindgen::callbacks::MacroParsingBehavior {
if self.0.contains(name) {
bindgen::callbacks::MacroParsingBehavior::Ignore
} else {
bindgen::callbacks::MacroParsingBehavior::Default
}
}
}
fn get_git_commit_version() {
let result = std::process::Command::new("git")
.args(&["describe", "--tags", "--always", "--dirty"])
.output();
let git_version = match result {
Ok(output) => output.stdout,
Err(_) => Vec::new(),
};
let git_version = String::from_utf8(git_version).expect("Invalid UTF-8 sequence");
println!("cargo:rustc-env=GIT_VERSION={}", git_version.trim());
}
fn link_rename_lib() {
/*
rename the output file to smartdns_ui.so
*/
let release_plugin = env::var("RELEASE_PLUGIN").is_ok();
if release_plugin == false {
// In debug mode, we don't rename the output file
return;
}
let curr_source_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let target_dir =
env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| format!("{}/target", curr_source_dir));
let crate_name = std::env::var("CARGO_PKG_NAME").unwrap().replace("-", "_");
let so_path = format!("{}/{}.so", target_dir, crate_name);
println!("cargo:rustc-link-arg=-o");
println!("cargo:rustc-link-arg={}", so_path);
}
fn link_smartdns_lib() {
let curr_source_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let smartdns_src_dir = format!("{}/../../src", curr_source_dir);
let smartdns_inc_dir = format!("{}/include", smartdns_src_dir);
let smartdns_lib_file = format!("{}/libsmartdns-test.a", smartdns_src_dir);
let cc = env::var("RUSTC_LINKER")
.unwrap_or_else(|_| env::var("CC").unwrap_or_else(|_| "cc".to_string()));
let sysroot_output = std::process::Command::new(&cc)
.arg("--print-sysroot")
.output();
let mut sysroot = None;
if let Ok(output) = sysroot_output {
if output.status.success() {
let path = String::from_utf8(output.stdout).unwrap();
sysroot = Some(path.trim().to_string());
}
}
let ignored_macros = IgnoreMacros(vec!["IPPORT_RESERVED".into()].into_iter().collect());
let mut bindings_builder =
bindgen::Builder::default().header(format!("{}/smartdns/smartdns.h", smartdns_inc_dir));
if let Some(sysroot) = sysroot {
bindings_builder = bindings_builder.clang_arg(format!("--sysroot={}", sysroot));
}
let bindings = bindings_builder
.clang_arg(format!("-I{}/include", smartdns_src_dir))
.parse_callbacks(Box::new(ignored_macros))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("smartdns_bindings.rs"))
.expect("Couldn't write bindings!");
/*
to run tests, please run the following command:
make test-prepare
*/
if std::path::Path::new(&smartdns_lib_file).exists() && !cfg!(feature = "build-release") {
println!("cargo:rerun-if-changed={}", smartdns_lib_file);
println!("cargo:rustc-link-lib=static=smartdns-test");
println!("cargo:rustc-link-lib=ssl");
println!("cargo:rustc-link-lib=crypto");
println!("cargo:rustc-link-search=native={}", smartdns_src_dir);
}
}
fn main() {
get_git_commit_version();
link_smartdns_lib();
link_rename_lib();
}
================================================
FILE: plugin/smartdns-ui/src/data_server.rs
================================================
/*************************************************************************
*
* Copyright (C) 2018-2025 Ruilin Peng (Nick) .
*
* 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 .
*/
use crate::data_stats::*;
use crate::data_upstream_server::UpstreamServerInfo;
use crate::db::*;
use crate::dns_log;
use crate::plugin::SmartdnsPlugin;
use crate::server_log::ServerAuditLog;
use crate::server_log::ServerAuditLogMsg;
use crate::server_log::ServerLog;
use crate::server_log::ServerLogMsg;
use crate::smartdns;
use crate::smartdns::*;
use crate::utils;
use crate::whois;
use crate::whois::WhoIsInfo;
use std::collections::HashMap;
use std::error::Error;
use std::sync::atomic::AtomicBool;
use std::sync::Weak;
use std::sync::{Arc, Mutex, RwLock};
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use tokio::time::Duration;
use tokio::time::Instant;
pub const DEFAULT_MAX_LOG_AGE: u64 = 30 * 24 * 60 * 60;
pub const DEFAULT_MAX_LOG_AGE_MS: u64 = DEFAULT_MAX_LOG_AGE * 1000;
pub const MAX_LOG_AGE_VALUE_MIN: u64 = 600;
pub const MAX_LOG_AGE_VALUE_MAX: u64 = 365 * 24 * 60 * 60 * 10;
pub const MIN_FREE_DISK_SPACE: u64 = 1024 * 1024 * 8;
pub const DB_FILE_NAME: &str = "smartdns.db";
#[derive(Clone)]
pub struct OverviewData {
pub server_name: String,
pub db_size: u64,
pub startup_timestamp: u64,
pub free_disk_space: u64,
pub is_process_suspended: bool,
}
#[derive(Clone)]
pub struct MetricsData {
pub total_query_count: u64,
pub block_query_count: u64,
pub request_drop_count: u64,
pub fail_query_count: u64,
pub avg_query_time: f64,
pub cache_hit_rate: f64,
pub cache_number: u64,
pub cache_memory_size: u64,
pub qps: u32,
pub memory_usage: u64,
pub is_metrics_suspended: bool,
}
#[derive(Clone)]
pub struct DataServerConfig {
pub db_file: String,
pub data_path: String,
pub max_log_age_ms: u64,
}
impl DataServerConfig {
pub fn new() -> Self {
DataServerConfig {
data_path: Plugin::dns_conf_data_dir(),
db_file: Plugin::dns_conf_data_dir() + "/" + DB_FILE_NAME,
max_log_age_ms: DEFAULT_MAX_LOG_AGE_MS,
}
}
pub fn load_config(&mut self, data_server: Arc) -> Result<(), Box> {
self.max_log_age_ms = utils::parse_value(
data_server.get_server_config("smartdns-ui.max-query-log-age"),
MAX_LOG_AGE_VALUE_MIN,
MAX_LOG_AGE_VALUE_MAX,
DEFAULT_MAX_LOG_AGE,
) * 1000;
let log_level = data_server.get_server_config("log-level");
if let Some(log_level) = log_level {
let log_level = log_level.try_into();
match log_level {
Ok(log_level) => {
dns_log_set_level(log_level);
}
Err(_) => {
dns_log!(LogLevel::WARN, "log level is invalid");
}
}
}
Ok(())
}
}
pub struct DataServerControl {
data_server: Arc,
server_thread: Mutex